diff --git a/package-lock.json b/package-lock.json index e6e97786e..549fd892a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -4692,6 +4691,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.1.tgz", "integrity": "sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==" }, + "node_modules/@types/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/object.omit": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/object.omit/-/object.omit-3.0.0.tgz", @@ -5306,6 +5314,118 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "peer": true, + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "peer": true, + "dependencies": { + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "peer": true, + "dependencies": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "peer": true, + "dependencies": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "peer": true, + "dependencies": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + }, + "peerDependencies": { + "vue": "3.3.4" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", + "peer": true + }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -5318,11 +5438,21 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -5372,6 +5502,17 @@ "node": ">= 6.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/aggregate-error": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", @@ -5388,6 +5529,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ai": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/ai/-/ai-2.2.14.tgz", + "integrity": "sha512-4kL2iYPVhH1pl6jJFIJCYcgx5mHzGOmdwiSYWVadmSkNOxKqokgevHyJKiyL9B9DjlreM9cDqkQop56Hdfkb0w==", + "dependencies": { + "eventsource-parser": "1.0.0", + "nanoid": "3.3.6", + "solid-swr-store": "0.10.7", + "sswr": "2.0.0", + "swr": "2.2.0", + "swr-store": "0.10.6", + "swrv": "1.0.4" + }, + "engines": { + "node": ">=14.6" + }, + "peerDependencies": { + "react": "^18.2.0", + "solid-js": "^1.7.7", + "svelte": "^3.0.0 || ^4.0.0", + "vue": "^3.3.4" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/ai/node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5466,7 +5661,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, "dependencies": { "dequal": "^2.0.3" } @@ -5659,8 +5853,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -5695,7 +5888,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==", - "dev": true, "dependencies": { "dequal": "^2.0.3" } @@ -5798,6 +5990,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -6315,6 +6512,28 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/code-red": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", + "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, + "node_modules/code-red/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -6332,7 +6551,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -6551,6 +6769,19 @@ "jss-preset-default": "^10.10.0" } }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "peer": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-vendor": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", @@ -6723,7 +6954,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -6758,6 +6988,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/digest-fetch": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz", + "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==", + "dependencies": { + "base-64": "^0.1.0", + "md5": "^2.3.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7593,8 +7832,7 @@ "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/esutils": { "version": "2.0.3", @@ -7605,6 +7843,14 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -7614,6 +7860,14 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource-parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.0.0.tgz", + "integrity": "sha512-9jgfSCa3dmEme2ES3mPByGXfgZ87VbP97tng1G2nWwWx6bV2nYxm2AWCrbQjXToSe+yYlqaZNtxffR9IeQr95g==", + "engines": { + "node": ">=14.18" + } + }, "node_modules/execa": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", @@ -7915,7 +8169,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7925,6 +8178,31 @@ "node": ">= 6" } }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -8608,6 +8886,14 @@ "node": ">=14.18.0" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/hyphenate-style-name": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", @@ -9045,6 +9331,15 @@ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", "dev": true }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "peer": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -9749,6 +10044,12 @@ "lie": "3.1.1" } }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "peer": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -9863,7 +10164,6 @@ "version": "0.30.3", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz", "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -10169,6 +10469,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "peer": true + }, "node_modules/mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -10758,7 +11064,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -10767,7 +11072,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -11252,7 +11556,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "dev": true, "funding": [ { "type": "github", @@ -11564,6 +11867,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.11.1.tgz", + "integrity": "sha512-GU0HQWbejXuVAQlDjxIE8pohqnjptFDIm32aPlNT1H9ucMz1VJJD0DaTJRQsagNaJ97awWjjVLEG7zCM6sm4SA==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "digest-fetch": "^1.3.0", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.18.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz", + "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==" + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -12054,11 +12380,30 @@ "resolved": "https://registry.npmjs.org/penpal/-/penpal-6.2.2.tgz", "integrity": "sha512-RQD7hTx14/LY7QoS3tQYO3/fzVtwvZI+JeS5udgsu7FPaEDjlvfK9HBcme9/ipzSPKnrxSgacI9PI7154W62YQ==" }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/periscopic/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -12222,7 +12567,6 @@ "version": "8.4.28", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -12250,7 +12594,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, "funding": [ { "type": "github", @@ -13597,6 +13940,15 @@ "randombytes": "^2.1.0" } }, + "node_modules/seroval": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-0.5.1.tgz", + "integrity": "sha512-ZfhQVB59hmIauJG5Ydynupy8KHyr5imGNtdDhbZG68Ufh1Ynkv9KOYOAABf71oVbQxJ8VkWnMHAjEHE7fWkH5g==", + "peer": true, + "engines": { + "node": ">=10" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -13682,6 +14034,28 @@ "node": ">=6" } }, + "node_modules/solid-js": { + "version": "1.7.12", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.7.12.tgz", + "integrity": "sha512-QoyoOUKu14iLoGxjxWFIU8+/1kLT4edQ7mZESFPonsEXZ//VJtPKD8Ud1aTKzotj+MNWmSs9YzK6TdY+fO9Eww==", + "peer": true, + "dependencies": { + "csstype": "^3.1.0", + "seroval": "^0.5.0" + } + }, + "node_modules/solid-swr-store": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/solid-swr-store/-/solid-swr-store-0.10.7.tgz", + "integrity": "sha512-A6d68aJmRP471aWqKKPE2tpgOiR5fH4qXQNfKIec+Vap+MGQm3tvXlT8n0I8UgJSlNAsSAUuw2VTviH2h3Vv5g==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "solid-js": "^1.2", + "swr-store": "^0.10" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -13694,7 +14068,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -13735,6 +14108,17 @@ "node": ">=0.10.0" } }, + "node_modules/sswr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sswr/-/sswr-2.0.0.tgz", + "integrity": "sha512-mV0kkeBHcjcb0M5NqKtKVg/uTIYNlIIniyDfSGrSfxpEdM9C365jK0z55pl9K0xAkNTJi2OAOVFQpgMPUk+V0w==", + "dependencies": { + "swrev": "^4.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -13975,6 +14359,74 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svelte": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.1.tgz", + "integrity": "sha512-LpLqY2Jr7cRxkrTc796/AaaoMLF/1ax7cto8Ot76wrvKQhrPmZ0JgajiWPmg9mTSDqO16SSLiD17r9MsvAPTmw==", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^3.2.1", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.0", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/svelte/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/swr": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", + "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "dependencies": { + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/swr-store": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/swr-store/-/swr-store-0.10.6.tgz", + "integrity": "sha512-xPjB1hARSiRaNNlUQvWSVrG5SirCjk2TmaUyzzvk69SZQan9hCJqw/5rG9iL7xElHU784GxRPISClq4488/XVw==", + "dependencies": { + "dequal": "^2.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz", + "integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==" + }, + "node_modules/swrv": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz", + "integrity": "sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==", + "peerDependencies": { + "vue": ">=3.2.26 < 4" + } + }, "node_modules/symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -14805,6 +15257,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/utf-8-validate": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", @@ -15098,6 +15558,19 @@ "resolved": "https://registry.npmjs.org/vscode-lib/-/vscode-lib-0.1.2.tgz", "integrity": "sha512-X7YTInfdx0D7O5d5jxv5tirYNlZT3wwmB/auEWDq8nKrJCkZea48y1brADKWSfmmSCvmaZwG5RJ3VOQf/pPwMg==" }, + "node_modules/vue": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -15660,11 +16133,13 @@ "@typecell-org/shared": "^0.0.3", "@typecell-org/util": "^0.0.3", "@typecell-org/y-penpal": "^0.0.3", + "ai": "2.2.14", "classnames": "^2.3.1", "fractional-indexing": "^2.0.0", "lodash.memoize": "^4.1.2", "mobx": "^6.2.0", "mobx-react-lite": "^3.2.0", + "openai": "^4.11.1", "penpal": "^6.1.0", "react": "^18.2.0", "react-avatar": "^5.0.3", @@ -16070,6 +16545,7 @@ "name": "@typecell-org/frame", "version": "0.0.3", "dependencies": { + "@atlaskit/form": "^8.11.8", "@blocknote/core": "^0.9.3", "@blocknote/react": "^0.9.3", "@floating-ui/react": "^0.25.1", @@ -16099,6 +16575,7 @@ "react-inspector": "^6.0.1", "typescript": "5.0.4", "vscode-lib": "^0.1.2", + "y-prosemirror": "^1.0.20", "y-protocols": "^1.0.5", "yjs": "^13.6.4" }, @@ -16107,9 +16584,11 @@ "@types/prettier": "^3.0.0", "@vitejs/plugin-react": "^4.1.0", "@vitest/coverage-v8": "^0.33.0", + "ai": "2.2.14", "chai": "^4.3.7", "cross-fetch": "^4.0.0", "jsdom": "^22.1.0", + "openai": "^4.11.1", "playwright-test": "^12.1.1", "typescript": "5.0.4", "vitest": "^0.33.0" diff --git a/packages/editor/package.json b/packages/editor/package.json index 37d2b2c1f..fecdea84e 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -38,6 +38,8 @@ "@typecell-org/parsers": "^0.0.3", "@typecell-org/frame": "^0.0.3", "@typecell-org/y-penpal": "^0.0.3", + "openai": "^4.11.1", + "ai": "2.2.14", "speakingurl": "^14.0.1", "classnames": "^2.3.1", "fractional-indexing": "^2.0.0", diff --git a/packages/editor/src/app/documentRenderers/richtext/FrameHost.tsx b/packages/editor/src/app/documentRenderers/richtext/FrameHost.tsx index 624082ba4..c9c1d781c 100644 --- a/packages/editor/src/app/documentRenderers/richtext/FrameHost.tsx +++ b/packages/editor/src/app/documentRenderers/richtext/FrameHost.tsx @@ -1,10 +1,11 @@ -import { IframeBridgeMethods } from "@typecell-org/shared"; +import { HostBridgeMethods, IframeBridgeMethods } from "@typecell-org/shared"; import { ContainedElement, useResource } from "@typecell-org/util"; import { PenPalProvider } from "@typecell-org/y-penpal"; import { AsyncMethodReturns, connectToChild } from "penpal"; import { useRef } from "react"; import * as awarenessProtocol from "y-protocols/awareness"; import { parseIdentifier } from "../../../identifiers"; +import { queryOpenAI } from "../../../integrations/ai/openai"; import { DocumentResource } from "../../../store/DocumentResource"; import { DocumentResourceModelProvider } from "../../../store/DocumentResourceModelProvider"; import { SessionStore } from "../../../store/local/SessionStore"; @@ -64,7 +65,7 @@ export function FrameHost(props: { { provider: DocumentResourceModelProvider; forwarder: ModelForwarder } >(); - const methods = { + const methods: HostBridgeMethods = { processYjsMessage: async (message: ArrayBuffer) => { provider.onMessage(message, "penpal"); }, @@ -110,6 +111,7 @@ export function FrameHost(props: { moduleManager.forwarder.dispose(); moduleManagers.delete(identifierStr); }, + queryLLM: queryOpenAI, }; const iframe = document.createElement("iframe"); diff --git a/packages/editor/src/integrations/ai/openai.ts b/packages/editor/src/integrations/ai/openai.ts new file mode 100644 index 000000000..831a0fd2d --- /dev/null +++ b/packages/editor/src/integrations/ai/openai.ts @@ -0,0 +1,43 @@ +import { OpenAIStream, StreamingTextResponse } from "ai"; +import { OpenAI } from "openai"; + +export async function queryOpenAI(parameters: { + messages: OpenAI.Chat.ChatCompletionCreateParams["messages"]; + functions?: OpenAI.Chat.ChatCompletionCreateParams["functions"]; + function_call?: OpenAI.Chat.ChatCompletionCreateParams["function_call"]; +}) { + // get key from localstorage + let key = localStorage.getItem("oai-key"); + if (!key) { + key = prompt( + "Please enter your OpenAI key (not shared with TypeCell, stored in your browser):", + ); + if (!key) { + return { + status: "error", + error: "no-key", + } as const; + } + localStorage.setItem("oai-key", key); + } + + const openai = new OpenAI({ + apiKey: key, + // this should be ok as we are not exposing any keys + dangerouslyAllowBrowser: true, + }); + + const response = await openai.chat.completions.create({ + model: "gpt-4", + stream: true, + ...parameters, + }); + const stream = OpenAIStream(response); + // Respond with the stream + const ret = new StreamingTextResponse(stream); + const data = await ret.text(); + return { + status: "ok", + result: data, + } as const; +} diff --git a/packages/engine/src/executor.ts b/packages/engine/src/executor.ts index acbcc89ee..79f222f54 100644 --- a/packages/engine/src/executor.ts +++ b/packages/engine/src/executor.ts @@ -13,6 +13,7 @@ async function resolveDependencyArray( userDisposes: Array<() => void>, ) { const runContext = { + context, onDispose: (disposer: () => void) => { userDisposes.push(() => { try { @@ -47,6 +48,7 @@ async function resolveDependencyArray( } export type RunContext = { + context: TypeCellContext; onDispose: (disposer: () => void) => void; }; @@ -120,6 +122,7 @@ export async function runModule( disposeEveryRun.push(hooks.disposeAll); let executionPromise: Promise; try { + console.log("execute", mod.factoryFunction + ""); executionPromise = mod.factoryFunction.apply( undefined, argsToCallFunctionWith, diff --git a/packages/engine/src/modules.ts b/packages/engine/src/modules.ts index 8a7506afe..2af8aa50f 100644 --- a/packages/engine/src/modules.ts +++ b/packages/engine/src/modules.ts @@ -28,10 +28,11 @@ export function getModulesFromWrappedPatchedTypeCellFunction( // eslint-disable-next-line @typescript-eslint/no-explicit-any caller: () => any, // eslint-disable-next-line @typescript-eslint/no-explicit-any - scope: any + scope: any, ): Module[] { const modules: Module[] = []; const define = createDefine(modules); + console.log("evaluate (module)", caller + ""); caller.apply({ ...scope, define }); return modules; } @@ -43,10 +44,11 @@ export function getModulesFromWrappedPatchedTypeCellFunction( export function getModulesFromPatchedTypeCellCode( code: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any - scope: any + scope: any, ): Module[] { const modules: Module[] = []; const define = createDefine(modules); + console.log("evaluate (module)", code); // eslint-disable-next-line const f = new Function(code); f.apply({ ...scope, define }); @@ -57,7 +59,7 @@ function createDefine(modules: Module[]) { return function typeCellDefine( moduleNameOrDependencyArray: string | string[], dependencyArrayOrFactoryFunction: string[] | Function, - factoryFunction?: Function + factoryFunction?: Function, ) { const moduleName: string | typeof unnamedModule = typeof moduleNameOrDependencyArray === "string" @@ -118,7 +120,7 @@ export function getPatchedTypeCellCode(compiledCode: string, scope: any) { totalCode = totalCode.replace( /^\s*(define\((".*", )?\[.*\], )function/gm, - "$1async function" + "$1async function", ); // TODO: remove await? return totalCode; diff --git a/packages/frame/package.json b/packages/frame/package.json index 588799328..d559f14fd 100644 --- a/packages/frame/package.json +++ b/packages/frame/package.json @@ -3,6 +3,7 @@ "version": "0.0.3", "private": true, "dependencies": { + "@atlaskit/form": "^8.11.8", "@blocknote/core": "^0.9.3", "@blocknote/react": "^0.9.3", "@typecell-org/util": "^0.0.3", @@ -14,12 +15,12 @@ "@floating-ui/react": "^0.25.1", "@syncedstore/yjs-reactive-bindings": "^0.5.1", "lodash.memoize": "^4.1.2", - "mobx-utils": "^6.0.8", "localforage": "^1.10.0", "lz-string": "^1.4.4", "monaco-editor": "^0.35.0", "mobx": "^6.2.0", "mobx-react-lite": "^3.2.0", + "mobx-utils": "^6.0.8", "prosemirror-model": "^1.19.3", "prosemirror-view": "^1.31.7", "prosemirror-state": "^1.4.3", @@ -33,7 +34,8 @@ "typescript": "5.0.4", "vscode-lib": "^0.1.2", "y-protocols": "^1.0.5", - "yjs": "^13.6.4" + "yjs": "^13.6.4", + "y-prosemirror": "^1.0.20" }, "devDependencies": { "cross-fetch": "^4.0.0", @@ -45,7 +47,9 @@ "@vitest/coverage-v8": "^0.33.0", "@vitejs/plugin-react": "^4.1.0", "@types/prettier": "^3.0.0", - "chai": "^4.3.7" + "chai": "^4.3.7", + "openai": "^4.11.1", + "ai": "2.2.14" }, "type": "module", "source": "src/index.ts", diff --git a/packages/frame/src/EditorStore.ts b/packages/frame/src/EditorStore.ts index 111399b80..8487ab710 100644 --- a/packages/frame/src/EditorStore.ts +++ b/packages/frame/src/EditorStore.ts @@ -23,11 +23,17 @@ export class EditorStore { public executionHost: LocalExecutionHost | undefined; public topLevelBlocks: any; + public readonly customBlocks = new Map(); + public readonly blockSettings = new Map(); + constructor() { makeObservable(this, { customBlocks: observable.shallow, - add: action, - delete: action, + addCustomBlock: action, + deleteCustomBlock: action, + blockSettings: observable.shallow, + addBlockSettings: action, + deleteBlockSettings: action, topLevelBlocks: observable.ref, }); @@ -45,12 +51,10 @@ export class EditorStore { }); } - customBlocks = new Map(); - /** * Add a custom block (slash menu command) to the editor */ - public add(config: any) { + public addCustomBlock(config: any) { if (this.customBlocks.has(config.id)) { // already has block with this id, maybe loop of documents? return; @@ -61,10 +65,28 @@ export class EditorStore { /** * Remove a custom block (slash menu command) from the editor */ - public delete(config: any) { + public deleteCustomBlock(config: any) { this.customBlocks.delete(config.id); } + /** + * Add a block settings (block settings menu) to the editor + */ + public addBlockSettings(config: any) { + if (this.blockSettings.has(config.id)) { + // already has block with this id, maybe loop of documents? + return; + } + this.blockSettings.set(config.id, config); + } + + /** + * Remove block settings (block settings menu) from the editor + */ + public deleteBlockSettings(config: any) { + this.blockSettings.delete(config.id); + } + /** * EXPERIMENTAL * @internal @@ -180,11 +202,11 @@ class TypeCellBlock { runInAction(() => { this.block = newBlock; - if (newBlock.props.storage !== JSON.stringify(this.storage)) { - if (newBlock.props.storage) { + if ((newBlock.props as any).storage !== JSON.stringify(this.storage)) { + if (newBlock.props as any) { try { console.log("update cell storage"); - this.storage = JSON.parse(newBlock.props.storage) || {}; + this.storage = JSON.parse((newBlock.props as any).storage) || {}; } catch (e) { console.error(e); } @@ -221,7 +243,7 @@ class TypeCellBlock { editor.updateBlock(this.block, { props: { storage: val, - }, + } as any, }); } }, diff --git a/packages/frame/src/Frame.tsx b/packages/frame/src/Frame.tsx index e57e55bd6..c6edf1bef 100644 --- a/packages/frame/src/Frame.tsx +++ b/packages/frame/src/Frame.tsx @@ -32,16 +32,18 @@ import styles from "./Frame.module.css"; import { RichTextContext } from "./RichTextContext"; import { MonacoCodeBlock } from "./codeblocks/MonacoCodeBlock"; import SourceModelCompiler from "./runtime/compiler/SourceModelCompiler"; +import { setMonacoDefaults } from "./runtime/editor"; import { MonacoContext } from "./runtime/editor/MonacoContext"; -import { ExecutionHost } from "./runtime/executor/executionHosts/ExecutionHost"; import LocalExecutionHost from "./runtime/executor/executionHosts/local/LocalExecutionHost"; -import { setMonacoDefaults } from "./runtime/editor"; - import { variables } from "@typecell-org/util"; import { RiCodeSSlashFill } from "react-icons/ri"; +import { VscWand } from "react-icons/vsc"; import { EditorStore } from "./EditorStore"; import { MonacoColorManager } from "./MonacoColorManager"; + +import { getAICode } from "./ai/ai"; +import { applyChanges } from "./ai/applyChanges"; import { MonacoInlineCode } from "./codeblocks/MonacoInlineCode"; import monacoStyles from "./codeblocks/MonacoSelection.module.css"; import { setupTypecellHelperTypeResolver } from "./runtime/editor/languages/typescript/TypeCellHelperTypeResolver"; @@ -94,10 +96,11 @@ const originalItems = [ ...getDefaultReactSlashMenuItems(), { name: "Code block", - execute: (editor: any) => + // eslint-disable-next-line @typescript-eslint/no-explicit-any + execute: (editor: BlockNoteEditor) => insertOrUpdateBlock(editor, { type: "codeblock", - } as any), + }), aliases: ["code"], hint: "Add a live code block", group: "Code", @@ -105,7 +108,8 @@ const originalItems = [ }, { name: "Inline", - execute: (editor: any) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + execute: (editor: BlockNoteEditor) => { // state.tr.replaceSelectionWith(dinoType.create({type})) const node = editor._tiptapEditor.schema.node( "inlineCode", @@ -274,12 +278,13 @@ export const Frame: React.FC = observer((props) => { resolver.resolveImport, ); - const newExecutionHost: ExecutionHost = new LocalExecutionHost( + const newExecutionHost = new LocalExecutionHost( props.documentIdString, newCompiler, monaco, newEngine, ); + return [ { newCompiler, newExecutionHost }, () => { @@ -290,15 +295,46 @@ export const Frame: React.FC = observer((props) => { [props.documentIdString, monaco], ); - console.log("size", editorStore.current.customBlocks.size); slashMenuItems.splice( originalItems.length, slashMenuItems.length, - ...[...editorStore.current.customBlocks.values()].map((data: any) => { + { + name: "AI", + // eslint-disable-next-line @typescript-eslint/no-explicit-any + execute: async (editor: BlockNoteEditor) => { + const p = prompt("What would you like TypeCell AI to do?"); + if (!p) { + return; + } + + const commands = await getAICode( + p, + props.documentIdString, + tools.newExecutionHost, + editor, + editorStore.current, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + connectionMethods.current!.queryLLM, + ); + + // TODO: we should validate the commands before applying them + applyChanges( + commands, + document.ydoc.getXmlFragment("doc"), + document.awareness, + ); + }, + aliases: ["ai", "wizard", "openai", "llm"], + hint: "Prompt your TypeCell AI assistant", + group: "Code", + icon: , + }, + ...[...editorStore.current.customBlocks.values()].map((data) => { console.log("update blocks"); return { name: data.name, - execute: (editor: any) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + execute: (editor: BlockNoteEditor) => { const origVarName = variables.toCamelCaseVariableName(data.name); let varName = origVarName; let i = 0; @@ -306,48 +342,47 @@ export const Frame: React.FC = observer((props) => { while (true) { // append _1, _2, _3, ... to the variable name until it is unique - if ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ( - tools.newExecutionHost.engine.observableContext - .rawContext as any - )[varName] === undefined - ) { + const context = + tools.newExecutionHost.engine.observableContext.rawContext; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((context as any)[varName] === undefined) { break; } i++; varName = origVarName + "_" + i; } - insertOrUpdateBlock( - editor as any, - { - type: "codeblock", - props: { - language: "typescript", - // moduleName: moduleName, - // key, - storage: "", - }, - content: `// @default-collapsed + const settingsPart = data.settings + ? ` +typecell.editor.registerBlockSettings({ + content: (visible: boolean) => ( + + ), +}); +` + : ""; + insertOrUpdateBlock(editor, { + type: "codeblock", + props: { + language: "typescript", + storage: "", + }, + content: `// @default-collapsed import * as doc from "${data.documentId}"; -export let ${varName} = doc.${data.blockVariable}; +export let ${varName} = doc.${data.blockExport}; export let ${varName}Scope = doc; - +${settingsPart} export default ${varName}; `, - } as any, - ); + }); }, - // execute: (editor) => - // insertOrUpdateBlock(editor, { - // type: data[0], - // }), - // aliases: [data[0]], - // hint: "Add a " + data[0], group: "Custom", - } as any; + }; }), ); @@ -400,6 +435,7 @@ export default ${varName}; }); if (editorStore.current.editor !== editor) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any editorStore.current.editor = editor as any; } @@ -412,6 +448,7 @@ export default ${varName}; ({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + editorStore: undefined as any, // eslint-disable-next-line @typescript-eslint/no-explicit-any executionHost: undefined as any, // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/frame/src/ai/ai.ts b/packages/frame/src/ai/ai.ts new file mode 100644 index 000000000..c038981fd --- /dev/null +++ b/packages/frame/src/ai/ai.ts @@ -0,0 +1,376 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// import LocalExecutionHost from "../../../runtime/executor/executionHosts/local/LocalExecutionHost" +import "@blocknote/core/style.css"; +import * as mobx from "mobx"; +import * as monaco from "monaco-editor"; +import type openai from "openai"; + +import { BlockNoteEditor } from "@blocknote/core"; +import { HostBridgeMethods } from "@typecell-org/shared"; +import { uri } from "vscode-lib"; +import { EditorStore } from "../EditorStore"; +import { ExecutionHost } from "../runtime/executor/executionHosts/ExecutionHost"; +import { trimmedStringify } from "./trimmedStringify"; + +const TYPECELL_PROMPT = ` +You're a smart AI assistant for TypeCell: a rich text document tool that also supports interactive Code Blocks written in Typescript. + +TypeCell Documents look like this: +- Documents consists of a list of blocks (e.g.: headings, paragraphs, code blocks), Notion style. Code Blocks are unique to TypeCell and execute live, as-you type. + +TypeCell Code Blocks works as follows: +- Code Blocks can export variables using the javascript / typescript \`export\` syntax. These variables are shown as the output of the cell. +- The exported variables by a Code Block are available in other cells, under the \`$\` variable. e.g.: \`$.exportedVariableFromOtherCell\` +- Different cells MUST NOT output variables with the same name, because then they would collide under the \`$\` variable. +- When the exports of one Code Block change, other cells that depend on those exports, update live, automatically. +- React / JSX components will be displayed automatically. E.g.: \`export let component =
hello world
\` will display a div with hello world. +- Note that exported functions are not called automatically. They'll simply become a callable variable under the $ scope. This means simply exporting a function and not calling it anywhere is not helpful + +Example document: + +[ + { + id: "block-1", + type: "codeblock", + content: "export let name = 'James';", + }, + { + id: "block-2", + type: "codeblock", + content: "export let nameLength = $.name.length; // updates reactively based on the $.name export from block-1", + }, + { + id: "block-3", + type: "codeblock", + content: "// This uses the exported \`name\` from code block 1, using the TypeCell \`$.name\` syntax, and shows the capitalized name using React + export let capitalized =
{$.name.toUpperCase()}
", + } +] + +The runtime data of this would be: + +{ name: "James", nameLength: 5, capitalized: "[REACTELEMENT]"} + +This is the type of a document: + +type Block = { + id: string; + type: "paragraph" | "heading" | "codeblock"; + content?: string; + children?: Block[]; +}; + +export type Document = Block[]; + +Example prompts: +- If the user would ask you to update the name in the document, you would issue an Update operation to block-1. +- If the user would ask you to add a button to prompt for a name, you would issue an Add operation for a new codeblock with code \`export default \` +- If the user would ask you to output the name in reverse, you would issue an Add operation with code \`export let reverseName = $.name.split('').reverse().join('');\` + +NEVER write code that depends on and updates the same variable, as that would cause a loop. You can directly modify (mutate) variables. So don't do this: + +$.complexObject = { ...$.complexObject, newProperty: 5 }; + +but instead: + +$.complexObject.newProperty = 5; +`; + +export async function getAICode( + prompt: string, + documentId: string, + executionHost: ExecutionHost, + editor: BlockNoteEditor, + editorStore: EditorStore, + queryLLM: HostBridgeMethods["queryLLM"], +) { + const blocks = editor.topLevelBlocks; + + let blockContexts: any[] = []; + const iterateBlocks = (blocks: any[]) => { + for (const block of blocks) { + const b = editorStore.getBlock(block.id); + if (b?.context?.default) { + blockContexts.push(b.context.default); + } + iterateBlocks(block.children); + } + }; + iterateBlocks(blocks); + + blockContexts = blockContexts.map((output) => + Object.fromEntries( + Object.getOwnPropertyNames(output).map((key) => [ + key, + mobx.toJS(output[key]), + ]), + ), + ); + + const tmpModel = monaco.editor.createModel( + "", + "typescript", + uri.URI.parse("file:///tmp.tsx"), + ); + + tmpModel.setValue(`import * as React from "react"; + import * as $ from "!${documentId}"; + // expands object types one level deep +type Expand = T extends infer O ? { [K in keyof O]: O[K] extends { Key: React.Key | null } ? "[REACT]" : O[K] } : never; + +// expands object types recursively +type ExpandRecursively = T extends object + ? T extends (...args: any[]) => any + ? T + : T extends infer O + ? { + [K in keyof O]: O[K] extends { key: React.Key } + ? "[REACT ELEMENT]" + : ExpandRecursively; + } + : never + : T; + + // ? T extends infer O ? { [K in keyof O]: ExpandRecursively } : never + type ContextType = ExpandRecursively;`); + + const worker = await monaco.languages.typescript.getTypeScriptWorker(); + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const ts = (await worker(tmpModel.uri))!; + const pos = + tmpModel.getValue().length - "pe = ExpandRecursively;".length; + // const def = await ts.getDefinitionAtPosition(tmpModel.uri.toString(), pos); + const def2 = await ts.getQuickInfoAtPosition(tmpModel.uri.toString(), pos); + + const contextType = def2.displayParts.map((x: any) => x.text).join(""); + + // const def3 = await ts.get(tmpModel.uri.toString(), pos, {}); + tmpModel.dispose(); + + /* + // const models = monaco.editor.getModels(); + // const typeCellModels = models.filter((m) => + // m.uri.path.startsWith("/!typecell:typecell.org"), + // ); + + + const codeInfoPromises = typeCellModels.map(async (m) => { + const code = await compile(m, monaco); + const output = await executionHost.outputs.get(m.uri.toString())?.value; + + let data: any; + if (output) { + const outputJS = Object.fromEntries( + Object.getOwnPropertyNames(output).map((key) => [ + key, + mobx.toJS(output[key]), + ]), + ); + data = JSON.parse(customStringify(outputJS)); + // console.log(data); + } + const path = m.uri.path.split("/"); // /!typecell:typecell.org/dVVAYmvBaeQdE/c58863ef-2f82-4fd7-ab0c-f1f760eb9578.cell.tsx" + const blockId = path[path.length - 1].replace(".cell.tsx", ""); + const imported = !blocks.find((b) => b.id === blockId); + + const ret: CodeBlockRuntimeInfo = { + // code: imported ? m.getValue() : undefined, + types: code.types, + blockId, + data, + ...(imported + ? { documentId: path[path.length - 2]!, imported, code: m.getValue() } + : { imported }), + }; + return ret; + }); + let codeInfos = await Promise.all(codeInfoPromises); + codeInfos = codeInfos.filter((x) => !!x.imported);*/ + + const context = executionHost.engine.observableContext.rawContext as any; + + let outputJS = Object.fromEntries( + Object.getOwnPropertyNames(context).map((key) => [ + key, + mobx.toJS(context[key]), + ]), + ); + outputJS = JSON.parse(trimmedStringify(outputJS)); + + function cleanBlock(block: any) { + if (!block.content?.length && !block.children?.length) { + return undefined; + } + delete block.props; + if (block.children) { + block.children = block.children.map(cleanBlock); + } + if (Array.isArray(block.content)) { + block.content = block.content.map((x: any) => x.text).join(""); + } + return block; + } + + const sanitized = blocks.map(cleanBlock).filter((x) => !!x); + const contextInfo = + contextType.replace("type ContextType = ", "const $: ") + + " = " + + JSON.stringify(outputJS); + + const blockContextInfo = blockContexts.length + ? `typecell.editor.findBlocks = (predicate: (context) => boolean) { + return (${JSON.stringify(blockContexts)}).find(predicate); + }` + : undefined; + + const messages: openai.Chat.ChatCompletionCreateParams["messages"] = [ + { + role: "system", + content: TYPECELL_PROMPT, + }, + { + role: "user", + content: `This is my document data: +"""${JSON.stringify(sanitized)}"""`, + }, + { + role: "user", + content: + "This is the type and runtime data available under the reactive $ variable for read / write access. If you need to change / read some information from the live document, it's likely you need to access it from here using $. \n" + + contextInfo + + (blockContextInfo + ? "\n" + + `We also have this function "typecell.editor.findBlocks" to extract runtime data from blocks \n` + + blockContextInfo + : ""), + }, + { + role: "system", + content: `You are an AI assistant helping user to modify his document. This means changes can either be code related (in that case, you'll need to add / modify Code Blocks), + or not at all (in which case you'll need to add / modify regular blocks), or a mix of both.`, + }, + { + role: "user", + content: prompt, + }, + ]; + + // Ask OpenAI for a streaming chat completion given the prompt + const response = await queryLLM({ + messages, + functions: [ + { + name: "updateDocument", + description: "Update the document with operations", + parameters: { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + required: ["operations"], + properties: { + operations: { + type: "array", + items: { + oneOf: [ + { + type: "object", + properties: { + explanation: { + type: "string", + description: + "explanation of why this block was deleted (your reasoning as AI agent)", + }, + type: { + type: "string", + enum: ["delete"], + description: + "Operation to delete a block in the document", + }, + id: { + type: "string", + description: "id of block to delete", + }, + }, + required: ["type", "id"], + additionalProperties: false, + }, + { + type: "object", + properties: { + explanation: { + type: "string", + description: + "explanation of why this block was updated (your reasoning as AI agent)", + }, + type: { + type: "string", + enum: ["update"], + description: + "Operation to update a block in the document", + }, + id: { + type: "string", + description: "id of block to delete", + }, + content: { + type: "string", + description: "new content of block", + }, + }, + required: ["type", "id", "content"], + additionalProperties: false, + }, + { + type: "object", + properties: { + explanation: { + type: "string", + description: + "explanation of why this block was added (your reasoning as AI agent)", + }, + type: { + type: "string", + enum: ["add"], + description: + "Operation to insert a new block in the document", + }, + afterId: { + type: "string", + description: + "id of block after which to insert a new block in the document", + }, + content: { + type: "string", + description: "content of new block", + }, + blockType: { + type: "string", + enum: ["codeblock", "paragraph", "heading"], + description: "type of new block", + }, + }, + required: ["afterId", "type", "content", "blockType"], + additionalProperties: false, + }, + ], + }, + }, + }, + }, + }, + ], + function_call: { + name: "updateDocument", + }, + }); + + console.log(messages); + + if (response.status === "ok") { + const data = JSON.parse(response.result); + return JSON.parse(data.function_call.arguments).operations; + } else { + console.error("queryLLM error", response.error); + } + return undefined; +} diff --git a/packages/frame/src/ai/applyChanges.ts b/packages/frame/src/ai/applyChanges.ts new file mode 100644 index 000000000..5f39cad18 --- /dev/null +++ b/packages/frame/src/ai/applyChanges.ts @@ -0,0 +1,172 @@ +import { error, uniqueId } from "@typecell-org/util"; +import * as ypm from "y-prosemirror"; +import { Awareness } from "y-protocols/awareness"; +import * as Y from "yjs"; +import { BlockOperation, OperationsResponse } from "./types"; +import { getYjsDiffs } from "./yjsDiff"; + +function findBlock(id: string, data: Y.XmlFragment) { + const node = data + .createTreeWalker( + (el) => el instanceof Y.XmlElement && el.getAttribute("id") === id, + ) + .next(); + if (node.done) { + return undefined; + } + return node.value as Y.XmlElement; +} + +function findParentIndex(node: Y.XmlFragment) { + const parent = node.parent as Y.XmlElement; + for (let i = 0; i < parent.length; i++) { + if (parent.get(i) === node) { + return i; + } + } + throw new Error("not found"); +} + +function updateState( + awareness: Awareness, + head: Y.RelativePosition, + anchor: Y.RelativePosition, +) { + // const initial = !awareness.states.has(99); + awareness.states.set(99, { + user: { + name: "@AI", + color: "#94FADB", + }, + cursor: { + anchor, + head, + }, + }); + + awareness.emit("change", [ + { + added: 0, + updated: 1, + removed: 0, + }, + origin, + ]); +} + +export async function applyChange( + op: BlockOperation, + data: Y.XmlFragment, + awareness: Awareness, +) { + const transact = (op: () => void) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + Y.transact(data.doc!, op, ypm.ySyncPluginKey); + }; + if (op.type === "add") { + const node = findBlock(op.afterId, data); + if (!node) { + throw new Error("Block not found"); + } + const newElement = new Y.XmlElement("blockContainer"); + const child = new Y.XmlElement(op.blockType); + child.setAttribute("id", uniqueId.generateId("block")); + const yText = new Y.XmlText(); + child.insert(0, [yText]); + newElement.insert(0, [child]); + + transact(() => { + (node.parent as Y.XmlElement).insertAfter(node, [newElement]); + }); + // start typing text content + for (let i = 0; i < op.content.length; i++) { + const start = Y.createRelativePositionFromTypeIndex(yText, i); + const end = Y.createRelativePositionFromTypeIndex(yText, i); + updateState(awareness, start, end); + + transact(() => { + yText.insert(i, op.content[i]); + }); + await new Promise((resolve) => setTimeout(resolve, 20)); + } + } else if (op.type === "delete") { + const node = findBlock(op.id, data); + if (!node) { + throw new Error("Block not found"); + } + const blockNode = node.firstChild as Y.XmlElement; + const yText = blockNode.firstChild as Y.XmlText; + + const start = Y.createRelativePositionFromTypeIndex(yText, 0); + const end = Y.createRelativePositionFromTypeIndex(yText, yText.length - 1); + + updateState(awareness, start, end); + + await new Promise((resolve) => setTimeout(resolve, 200)); + + transact(() => { + (node.parent as Y.XmlElement).delete(findParentIndex(node), 1); + }); + await new Promise((resolve) => setTimeout(resolve, 20)); + } else if (op.type === "update") { + const node = findBlock(op.id, data); + if (!node) { + throw new Error("Block not found"); + } + + // let gptCode = "\n" + gptCell.code + "\n"; + // gptCode = gptCode.replaceAll("import React from 'react';\n", ""); + // gptCode = gptCode.replaceAll("import * as React from 'react';\n", ""); + // console.log("diffs", cell.code.toJSON(), gptCode); + const blockNode = node.firstChild as Y.XmlElement; + const yText = blockNode.firstChild as Y.XmlText; + const steps = getYjsDiffs(yText, op.content); + for (const step of steps) { + if (step.type === "insert") { + for (let i = 0; i < step.text.length; i++) { + const start = Y.createRelativePositionFromTypeIndex( + yText, + step.from + i, + ); + const end = Y.createRelativePositionFromTypeIndex( + yText, + step.from + i, + ); + updateState(awareness, start, end); + + transact(() => { + yText.insert(step.from + i, step.text[i]); + }); + await new Promise((resolve) => setTimeout(resolve, 20)); + } + } else if (step.type === "delete") { + const start = Y.createRelativePositionFromTypeIndex(yText, step.from); + const end = Y.createRelativePositionFromTypeIndex( + yText, + step.from + step.length, + ); + updateState(awareness, start, end); + await new Promise((resolve) => setTimeout(resolve, 200)); + transact(() => { + yText.delete(step.from, step.length); + }); + await new Promise((resolve) => setTimeout(resolve, 20)); + } + } + } else { + throw new error.UnreachableCaseError(op); + } +} + +export async function applyChanges( + commands: OperationsResponse, + fragment: Y.XmlFragment, + awareness: Awareness, +) { + const doc = new Y.Doc(); + + for (const op of commands) { + await applyChange(op, fragment, awareness); + } + return doc; +} diff --git a/packages/frame/src/ai/trimmedStringify.ts b/packages/frame/src/ai/trimmedStringify.ts new file mode 100644 index 000000000..ce263b5c3 --- /dev/null +++ b/packages/frame/src/ai/trimmedStringify.ts @@ -0,0 +1,141 @@ +import React from "react"; + +// quickly generated with chatgpt: +// "create a json stringify alternative that can trim long fields / deeply nested objects, +// and serializes property getters" + +// https://chat.openai.com/share/693c349f-3f74-4913-8f1d-ba4477f93e87 + +type Serializable = + | string + | number + | boolean + | null + | { [key: string]: Serializable } + | Serializable[]; + +interface QueueItem { + obj: Serializable; + path: (string | number)[]; +} + +/** + * a stringify function that trims long fields / deeply nested objects, and serializes property getters + */ +export function trimmedStringify(obj: Serializable, budget = 1000): string { + const seen = new Set(); + const queue: QueueItem[] = [{ obj, path: [] }]; + const output: Serializable = Array.isArray(obj) ? [] : {}; + + while (queue.length > 0 && budget > 0) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { obj: currentObj, path } = queue.shift()!; + + if (typeof currentObj !== "object" || currentObj === null) { + continue; + } + + // Handle circular references + if (seen.has(currentObj)) { + setByPath(output, path, "[CIRCULAR]"); + continue; + } + seen.add(currentObj); + + for (const key in currentObj) { + if (budget <= 0) { + break; + } + + const value = (currentObj as any)[key]; + const newPath = path.concat(key); + + if (typeof value === "string") { + if (value.length <= budget) { + setByPath(output, newPath, value); + budget -= value.length; + } else { + setByPath( + output, + newPath, + value.substring(0, budget - "[TRIMMED]".length) + "[TRIMMED]", + ); + budget = 0; + } + } else if (typeof value === "object" && value !== null) { + if (Array.isArray(value)) { + const newValue: Serializable[] = []; + setByPath(output, newPath, newValue); + if (JSON.stringify(value).length > budget) { + newValue.push("[TRIMMEDARRAY]"); + budget -= "[TRIMMEDARRAY]".length; + } else { + queue.push({ obj: value, path: newPath }); + } + } else if (React.isValidElement(value)) { + setByPath(output, newPath, "[REACTELEMENT]"); + } else { + const newValue: Serializable = {}; + setByPath(output, newPath, newValue); + if (JSON.stringify(value).length > budget) { + for (const prop in newValue) { + delete newValue[prop]; + } + setByPath(output, newPath, "[TRIMMEDOBJECT]"); + budget -= "[TRIMMEDOBJECT]".length; + } else { + queue.push({ obj: value, path: newPath }); + } + } + } else { + setByPath(output, newPath, value); + } + } + } + + return JSON.stringify(output); + + function setByPath( + obj: Serializable, + path: (string | number)[], + value: Serializable, + ): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let current: any = obj; // We'll refine this with type assertions as we traverse + for (let i = 0; i < path.length - 1; i++) { + if (typeof current[path[i]] === "undefined") { + current[path[i]] = typeof path[i + 1] === "number" ? [] : {}; + } + current = current[path[i]]; + } + current[path[path.length - 1]] = value; + } +} + +// Example usage: +// const obj = { +// name: "John", +// details: { +// age: 25, +// address: { +// street: "123 Main St", +// city: "Anytown", +// state: "CA", +// country: { +// name: "USA", +// code: "US", +// continent: { +// name: "North America", +// code: "NA", +// }, +// }, +// }, +// }, +// hobbies: ["reading", "traveling", "swimming", "hiking", "cycling"], +// bio: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", +// get fullName() { +// return this.name + " Doe"; +// }, +// }; + +// console.log(customStringify(obj, 200)); diff --git a/packages/frame/src/ai/types.ts b/packages/frame/src/ai/types.ts new file mode 100644 index 000000000..3a266f1b5 --- /dev/null +++ b/packages/frame/src/ai/types.ts @@ -0,0 +1,91 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/** + * Operation to the document + * + * Block Id `id` parameters MUST be part of the document the user is editing (NOT a block from an imported library) + */ +export type BlockOperation = + | { + type: "delete"; + id: string; + } + | { + type: "update"; + id: string; + content: string; + } + | { + afterId: string; + type: "add"; + content: string; + blockType: "codeblock" | "paragraph" | "heading"; + }; + +export type OperationsResponse = BlockOperation[]; + +export const OUTPUT_TYPES = `/** +* Operation to the document +* +* Block Id \`id\` parameters MUST be part of the document the user is editing (NOT a block from an imported library) +*/ +type BlockOperation = + | { + type: "delete"; + id: string; + } + | { + type: "update"; + id: string; + content: string; + } + | { + afterId: string; + type: "add"; + content: string; + blockType: "codeblock" | "paragraph" | "heading"; + }; + +type response = BlockOperation[];`; + +type Block = { + id: string; + type: "paragraph" | "heading" | "codeblock"; + content?: string; + children?: Block[]; +}; + +export type Document = Block[]; + +/** + * Runtime information about a code block of the main document + * The code itself is not included (it's in the Block.id with the corresponding blockId) + */ +type MainCodeBlockRuntimeInfo = { + imported: false; + blockId: string; + // .d.ts TypeScript types of values exported by this block + types: string; + // the runtime values exported by this block. Data can be trimmed for brevity + data: any; +}; + +/** + * Runtime + code information of code blocks imported from other documents + */ +type ImportedCodeBlockRuntimeInfo = { + imported: true; + /** + * Because we don't pass the entire document this code is imported from, we need to pass the code of this code block + */ + code: string; + // .d.ts TypeScript types of values exported by this block + types: string; + documentId: string; + blockId: string; + // the runtime values exported by this block. Data can be trimmed for brevity + data: any; +}; + +export type CodeBlockRuntimeInfo = + | MainCodeBlockRuntimeInfo + | ImportedCodeBlockRuntimeInfo; diff --git a/packages/frame/src/ai/yjsDiff.test.ts b/packages/frame/src/ai/yjsDiff.test.ts new file mode 100644 index 000000000..05b5ac900 --- /dev/null +++ b/packages/frame/src/ai/yjsDiff.test.ts @@ -0,0 +1,60 @@ +/** + * @vitest-environment jsdom + */ + +import { describe, expect, it } from "vitest"; +import * as Y from "yjs"; +import { getYjsDiffs } from "./yjsDiff"; + +describe("diffYjs", () => { + it("basic replace", () => { + const doc = new Y.Doc(); + + // const text = new Y.Text("hello world"); + const text = doc.getText("text"); + text.insert(0, "hello world"); + getYjsDiffs(text, "hello there world"); + expect(text.toJSON()).toEqual("hello there world"); + }); + + it("delete", () => { + const doc = new Y.Doc(); + + // const text = new Y.Text("hello world"); + const text = doc.getText("text"); + text.insert(0, "hello there world"); + getYjsDiffs(text, "hello world"); + expect(text.toJSON()).toEqual("hello world"); + }); + + it("insert and delete", () => { + const doc = new Y.Doc(); + + // const text = new Y.Text("hello world"); + const text = doc.getText("text"); + text.insert(0, "hello there world"); + getYjsDiffs(text, "hell crazy world. How are you?"); + expect(text.toJSON()).toEqual("hell crazy world. How are you?"); + }); + + it("advanced", () => { + const orig = `// This generates an array of numbers 1 through 10 + export let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];`; + + const newText = ` + + // This cell exports an array of numbers 1 through 9 + + export let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + + `; + + const doc = new Y.Doc(); + + // const text = new Y.Text("hello world"); + const text = doc.getText("text"); + text.insert(0, orig); + getYjsDiffs(text, newText); + expect(text.toJSON()).toEqual(newText); + }); +}); diff --git a/packages/frame/src/ai/yjsDiff.ts b/packages/frame/src/ai/yjsDiff.ts new file mode 100644 index 000000000..0057e8ffb --- /dev/null +++ b/packages/frame/src/ai/yjsDiff.ts @@ -0,0 +1,75 @@ +import * as Y from "yjs"; + +import diff_match_patch from "../runtime/editor/prettier/diff"; +import { trimPatch } from "../runtime/editor/prettier/trimPatch"; + +const dmp = new diff_match_patch(); + +type Step = + | { + type: "insert"; + text: string; + from: number; + } + | { + type: "delete"; + from: number; + length: number; + }; + +export function getYjsDiffs( + existing: Y.Text, + newText: string, + execute = false, +) { + const steps: Step[] = []; + + const diffs = dmp.diff_main(existing.toJSON(), newText); + const patches = dmp.patch_make(diffs); + + // let posDiff = 0; + for (const patch of patches) { + trimPatch(patch); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const startPos = patch.start1!; // + posDiff; + // posDiff += patch.length1 - patch.length2; + + let tempLengths = 0; + for (const diff of patch.diffs) { + const type = diff[0]; + const text = diff[1]; + + // type 0: keep, type 1: insert, type -1: delete + if (type === 0) { + tempLengths += text.length; + } else if (type === 1) { + // newText += text; + const actionStart = startPos + tempLengths; + steps.push({ + type: "insert", + text, + from: actionStart, + // action: () => existing.insert(actionStart, text), + }); + if (execute) { + existing.insert(actionStart, text); + } + tempLengths += text.length; + } else { + // tempLengths -= text.length; + // posDiff -= patch.length1; + const actionStart = startPos + tempLengths; + + steps.push({ + type: "delete", + from: actionStart, + length: text.length, + }); + if (execute) { + existing.delete(actionStart, text.length); + } + } + } + } + return steps; +} diff --git a/packages/frame/src/codeblocks/MonacoCodeBlock.tsx b/packages/frame/src/codeblocks/MonacoCodeBlock.tsx index 1b70379c7..affe60eb3 100644 --- a/packages/frame/src/codeblocks/MonacoCodeBlock.tsx +++ b/packages/frame/src/codeblocks/MonacoCodeBlock.tsx @@ -85,6 +85,7 @@ export const MonacoCodeBlock = createTipTapBlock<"codeblock", any>({ // class: styles.blockContent, "data-content-type": this.name, }), + 0, ]; }, diff --git a/packages/frame/src/codeblocks/MonacoElement.tsx b/packages/frame/src/codeblocks/MonacoElement.tsx index 60251cff0..527790ace 100644 --- a/packages/frame/src/codeblocks/MonacoElement.tsx +++ b/packages/frame/src/codeblocks/MonacoElement.tsx @@ -10,7 +10,11 @@ import React, { useRef, useState, } from "react"; -import { VscChevronDown, VscChevronRight } from "react-icons/vsc"; +import { + VscChevronDown, + VscChevronRight, + VscSettingsGear, +} from "react-icons/vsc"; import { autoUpdate, @@ -261,9 +265,13 @@ const MonacoBlockElement = ( const [codeVisible, setCodeVisible] = useState( () => props.node.textContent.startsWith("// @default-collapsed") === false, ); + const [settingsVisible, setSettingsVisible] = useState(false); const context = useContext(RichTextContext); + const settings = context.editorStore.blockSettings.get( + props.model.uri.toString(), + ); return (
- {codeVisible ? ( - setCodeVisible(false)} - /> - ) : ( - setCodeVisible(true)} - /> - )} - {} +
+ {codeVisible ? ( + setCodeVisible(false)} + /> + ) : ( + setCodeVisible(true)} + /> + )} + {settings && ( + setSettingsVisible(!settingsVisible)} + /> + )} +
{codeVisible && (
@@ -309,6 +330,7 @@ const MonacoBlockElement = ( }, )}
+ {settings &&
{settings.content(settingsVisible)}
}
); diff --git a/packages/frame/src/runtime/editor/compilerOptions.ts b/packages/frame/src/runtime/editor/compilerOptions.ts index 2c3cc6d95..63a505bd9 100644 --- a/packages/frame/src/runtime/editor/compilerOptions.ts +++ b/packages/frame/src/runtime/editor/compilerOptions.ts @@ -14,6 +14,8 @@ export function getDefaultSandboxCompilerOptions( >, ) { const settings: CompilerOptions = { + // used so that ts.getQuickInfoAtPosition doesn't truncate too soon + noErrorTruncation: true, noImplicitAny: true, strictNullChecks: !config.useJavaScript, strictFunctionTypes: true, diff --git a/packages/frame/src/runtime/editor/prettier/diffToMonacoTextEdits.ts b/packages/frame/src/runtime/editor/prettier/diffToMonacoTextEdits.ts index 27d7c9762..a0e61a9e8 100644 --- a/packages/frame/src/runtime/editor/prettier/diffToMonacoTextEdits.ts +++ b/packages/frame/src/runtime/editor/prettier/diffToMonacoTextEdits.ts @@ -2,40 +2,10 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type * as monaco from "monaco-editor"; import diff_match_patch from "./diff.js"; +import { trimPatch } from "./trimPatch.js"; const dmp = new diff_match_patch(); -/** - * Trim type-0 diffs from a diff_match_patch patch. 0 indicates "keep", so is not really a diff - */ -function trimPatch(patch: any) { - // head - if (patch.diffs[0][0] === 0) { - const len = patch.diffs[0][1].length; - - // adjust patch params - patch.start1 += len; - patch.length1 -= len; - patch.start2 += len; - patch.length2 -= len; - - // remove diff - patch.diffs.shift(); - } - // tail - if (patch.diffs[patch.diffs.length - 1][0] === 0) { - const len = patch.diffs[patch.diffs.length - 1][1].length; - - // adjust patch params - patch.length1 -= len; - patch.length2 -= len; - - // remove diff - patch.diffs.pop(); - } - return patch; -} - /** * This calculates a list of Monaco TextEdit objects, that represent the transformation from * model.getValue() to v2. @@ -55,7 +25,7 @@ export function diffToMonacoTextEdits(model: monaco.editor.IModel, v2: string) { trimPatch(patch); const startPos = model.getPositionAt(patch.start1! + posDiff); const endPos = model.getPositionAt( - patch.start1! + patch.length1! + posDiff + patch.start1! + patch.length1! + posDiff, ); const range: monaco.IRange = { startColumn: startPos.column, diff --git a/packages/frame/src/runtime/editor/prettier/trimPatch.ts b/packages/frame/src/runtime/editor/prettier/trimPatch.ts new file mode 100644 index 000000000..e5d306fd6 --- /dev/null +++ b/packages/frame/src/runtime/editor/prettier/trimPatch.ts @@ -0,0 +1,30 @@ +/** + * Trim type-0 diffs from a diff_match_patch patch. 0 indicates "keep", so is not really a diff + */ +export function trimPatch(patch: any) { + // head + if (patch.diffs[0][0] === 0) { + const len = patch.diffs[0][1].length; + + // adjust patch params + patch.start1 += len; + patch.length1 -= len; + patch.start2 += len; + patch.length2 -= len; + + // remove diff + patch.diffs.shift(); + } + // tail + if (patch.diffs[patch.diffs.length - 1][0] === 0) { + const len = patch.diffs[patch.diffs.length - 1][1].length; + + // adjust patch params + patch.length1 -= len; + patch.length2 -= len; + + // remove diff + patch.diffs.pop(); + } + return patch; +} diff --git a/packages/frame/src/runtime/executor/components/ModelOutput.ts b/packages/frame/src/runtime/executor/components/ModelOutput.ts index 816b9a11a..fef85ffdf 100644 --- a/packages/frame/src/runtime/executor/components/ModelOutput.ts +++ b/packages/frame/src/runtime/executor/components/ModelOutput.ts @@ -11,7 +11,9 @@ export class ModelOutput extends lifecycle.Disposable { private autorunDisposer: (() => void) | undefined; public value: any = undefined; - public _defaultValue: any = {}; + public _defaultValue = { + value: {} as any, + }; public typeVisualizers = observable.map< string, { @@ -21,6 +23,7 @@ export class ModelOutput extends lifecycle.Disposable { constructor(private context: any) { super(); + makeObservable(this, { typeVisualizers: observable.ref, value: observable.ref, @@ -70,13 +73,14 @@ export class ModelOutput extends lifecycle.Disposable { } } - this._defaultValue = newValue.default; + // hacky nesting to make sure our customAnnotation (for react elements) is used + this._defaultValue = { value: newValue.default }; if (changed) { if (Object.hasOwn(newValue, "default")) { Object.defineProperty(newValue, "default", { get: () => { - return this.defaultValue; + return this.defaultValue.value; }, }); } diff --git a/packages/frame/src/runtime/executor/executionHosts/local/LocalExecutionHost.tsx b/packages/frame/src/runtime/executor/executionHosts/local/LocalExecutionHost.tsx index 727c1e15b..a2552e8b8 100644 --- a/packages/frame/src/runtime/executor/executionHosts/local/LocalExecutionHost.tsx +++ b/packages/frame/src/runtime/executor/executionHosts/local/LocalExecutionHost.tsx @@ -35,7 +35,10 @@ export default class LocalExecutionHost // ); // }) // ); - this.engine.registerModelProvider(compileEngine); + + if (!window.location.hash.includes("noRun")) { + this.engine.registerModelProvider(compileEngine); + } const visualizerExtension = this._register( new VisualizerExtension(compileEngine, documentId, monacoInstance), diff --git a/packages/frame/src/runtime/executor/lib/autoForm/FormField.tsx b/packages/frame/src/runtime/executor/lib/autoForm/FormField.tsx index b9b410959..c0c02e126 100644 --- a/packages/frame/src/runtime/executor/lib/autoForm/FormField.tsx +++ b/packages/frame/src/runtime/executor/lib/autoForm/FormField.tsx @@ -20,7 +20,7 @@ export const FormField = observer( [key: string]: unknown; }; fieldKey: Key; - modelPath: string; + // modelPath: string; value: string | undefined; setValue: (value: string | undefined) => void; }) => { @@ -57,6 +57,8 @@ export const FormField = observer( let inputField: React.ReactNode =
Unsupported type
; + const realShowCode = showCode || !canUseInput; + if (canUseInput) { const valueType = currentParsedBinding === undefined @@ -112,10 +114,10 @@ export const FormField = observer( {({ fieldProps, error }) => (
- {showCode ? ( + {realShowCode ? ( { if (!newValue || newValue.trim() === "export default") { props.setValue(undefined); @@ -151,14 +153,14 @@ export const FormField = observer( setShowCode(false)} - isSelected={!showCode} + isSelected={!realShowCode} isDisabled={!canUseInput}> Value view setShowCode(true)} - isSelected={showCode}> + isSelected={realShowCode}> Code view diff --git a/packages/frame/src/runtime/executor/lib/autoForm/MonacoEdit.tsx b/packages/frame/src/runtime/executor/lib/autoForm/MonacoEdit.tsx index 56ed36dfd..8e2c36147 100644 --- a/packages/frame/src/runtime/executor/lib/autoForm/MonacoEdit.tsx +++ b/packages/frame/src/runtime/executor/lib/autoForm/MonacoEdit.tsx @@ -11,7 +11,8 @@ type Props = { }; const MonacoEdit: React.FC = observer((props) => { - console.log(props); + // console.log(props); + const uri = useMemo( () => monaco.Uri.parse(`${props.documentid}.edit.${Math.random()}.tsx`), [props.documentid], diff --git a/packages/frame/src/runtime/executor/lib/autoForm/index.tsx b/packages/frame/src/runtime/executor/lib/autoForm/index.tsx index d0117361b..2a859bdf7 100644 --- a/packages/frame/src/runtime/executor/lib/autoForm/index.tsx +++ b/packages/frame/src/runtime/executor/lib/autoForm/index.tsx @@ -1,5 +1,6 @@ import Form, { FormSection } from "@atlaskit/form"; import { observer } from "mobx-react-lite"; +import React from "react"; import { FormField } from "./FormField"; import { Settings } from "./types"; @@ -54,7 +55,7 @@ export const AutoForm = observer( key={input} inputObject={props.inputObject as any} fieldKey={input} - modelPath={props.modelPath} + // modelPath={props.modelPath} value={props.settings[input]} setValue={(value: string | undefined) => { props.setSetting(input, value); diff --git a/packages/frame/src/runtime/executor/lib/exports.tsx b/packages/frame/src/runtime/executor/lib/exports.tsx index c6d093cc4..2f2194b69 100644 --- a/packages/frame/src/runtime/executor/lib/exports.tsx +++ b/packages/frame/src/runtime/executor/lib/exports.tsx @@ -21,7 +21,11 @@ export default function getExposeGlobalVariables( // registerPlugin: (config: { name: string }) => { // return config.name; // }, - registerBlock: (config: { name: string; blockExport: string }) => { + registerBlock: (config: { + name: string; + blockExport: string; + settings?: Record; + }) => { // TODO: this logic should be part of CodeModel / BasicCodeModel const id = forModelList[forModelList.length - 1].path; const parts = decodeURIComponent(id.replace("file:///", "")).split("/"); @@ -35,11 +39,27 @@ export default function getExposeGlobalVariables( documentId, }; console.log("ADD BLOCK", completeConfig.id); - editorStore.add(completeConfig); + editorStore.addCustomBlock(completeConfig); runContext.onDispose(() => { console.log("REMOVE BLOCK", completeConfig.id); - editorStore.delete(completeConfig); + editorStore.deleteCustomBlock(completeConfig); + }); + }, + registerBlockSettings: (config: any) => { + // TODO: this logic should be part of CodeModel / BasicCodeModel + const id = forModelList[forModelList.length - 1].uri; + + const completeConfig: any = { + ...config, + id: id.toString(), + }; + // console.log("ADD BLOCK", completeConfig.id); + editorStore.addBlockSettings(completeConfig); + + runContext.onDispose(() => { + // console.log("REMOVE BLOCK", completeConfig.id); + editorStore.deleteBlockSettings(completeConfig); }); }, /** @@ -125,7 +145,7 @@ export default function getExposeGlobalVariables( }, }; return { - memoize: (func: (...args: any[]) => any) => { + memoize: any>(func: T): T => { const wrapped = async function (this: any, ...args: any[]) { const ret = await func.apply(this, args); // if (typeof ret === "object") { @@ -135,7 +155,7 @@ export default function getExposeGlobalVariables( }; return memoize(wrapped, (args) => { return JSON.stringify(args); - }); + }) as any as T; }, // routing, // // DocumentView, @@ -163,7 +183,9 @@ export default function getExposeGlobalVariables( [key: string]: unknown; }, >( - props: Exclude, "settings" | "setSettings">, + props: Exclude, "settings" | "setSettings"> & { + visible: boolean; + }, ) => { const storage = editor.currentBlock.storage; @@ -182,7 +204,7 @@ export default function getExposeGlobalVariables( const func = new Function("$target", "$", sanitizedCode); return () => { console.log("eval", key, code); - func(props.inputObject, props.inputObject); // TODO + func(props.inputObject, runContext.context.context); }; }); }, [props.inputObject, storage.settings]); @@ -212,6 +234,10 @@ export default function getExposeGlobalVariables( }); }, [props.fields, createFunctionTransformer]); + if (!props.visible) { + return <>; + } + return ( Promise; + + /** + * Function to query LLM (openai) + * Executed in host, so that the key can be stored in localstorage and + * cannot be accessed by user-scripts + */ + queryLLM: (parameters: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + messages: any[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + functions?: any[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + function_call?: any; + }) => Promise< + | { + status: "ok"; + result: string; + } + | { + status: "error"; + error: string; + } + >; };