diff --git a/.eslintrc.json b/.eslintrc.json index 2d7aa60..cee92b6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,5 +12,15 @@ "plugin:import/electron", "plugin:import/typescript" ], - "parser": "@typescript-eslint/parser" + "parser": "@typescript-eslint/parser", + "settings": { + "import/resolver": { + "typescript": { + "alwaysTryTypes": true + }, + "node": { + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + } + } } diff --git a/package-lock.json b/package-lock.json index 088d57e..17da096 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "framer-motion": "^12.4.7", "highlight.js": "^11.11.0", "lucide-react": "^0.468.0", + "openai": "^4.87.3", "posthog-js": "^1.116.6", "posthog-node": "^4.6.0", "react": "^19.0.0", @@ -71,6 +72,7 @@ "css-loader": "^6.11.0", "electron": "33.2.1", "eslint": "^8.57.1", + "eslint-import-resolver-typescript": "^4.2.2", "eslint-plugin-import": "^2.31.0", "fork-ts-checker-webpack-plugin": "^7.3.0", "node-loader": "^2.1.0", @@ -1002,6 +1004,40 @@ "node": ">=14.14" } }, + "node_modules/@emnapi/core": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", + "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", + "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/is-prop-valid": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", @@ -2072,6 +2108,19 @@ "node": ">= 0.6" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz", + "integrity": "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.3.1", + "@emnapi/runtime": "^1.3.1", + "@tybys/wasm-util": "^0.9.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3181,6 +3230,17 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/better-sqlite3": { "version": "7.6.12", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.12.tgz", @@ -3799,6 +3859,163 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@unrs/rspack-resolver-binding-darwin-arm64": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-arm64/-/rspack-resolver-binding-darwin-arm64-1.2.2.tgz", + "integrity": "sha512-i7z0B+C0P8Q63O/5PXJAzeFtA1ttY3OR2VSJgGv18S+PFNwD98xHgAgPOT1H5HIV6jlQP8Avzbp09qxJUdpPNw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-darwin-x64": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-x64/-/rspack-resolver-binding-darwin-x64-1.2.2.tgz", + "integrity": "sha512-YEdFzPjIbDUCfmehC6eS+AdJYtFWY35YYgWUnqqTM2oe/N58GhNy5yRllxYhxwJ9GcfHoNc6Ubze1yjkNv+9Qg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-freebsd-x64": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-freebsd-x64/-/rspack-resolver-binding-freebsd-x64-1.2.2.tgz", + "integrity": "sha512-TU4ntNXDgPN2giQyyzSnGWf/dVCem5lvwxg0XYvsvz35h5H19WrhTmHgbrULMuypCB3aHe1enYUC9rPLDw45mA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-arm-gnueabihf": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm-gnueabihf/-/rspack-resolver-binding-linux-arm-gnueabihf-1.2.2.tgz", + "integrity": "sha512-ik3w4/rU6RujBvNWiDnKdXi1smBhqxEDhccNi/j2rHaMjm0Fk49KkJ6XKsoUnD2kZ5xaMJf9JjailW/okfUPIw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-arm64-gnu": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-gnu/-/rspack-resolver-binding-linux-arm64-gnu-1.2.2.tgz", + "integrity": "sha512-fp4Azi8kHz6TX8SFmKfyScZrMLfp++uRm2srpqRjsRZIIBzH74NtSkdEUHImR4G7f7XJ+sVZjCc6KDDK04YEpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-arm64-musl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-musl/-/rspack-resolver-binding-linux-arm64-musl-1.2.2.tgz", + "integrity": "sha512-gMiG3DCFioJxdGBzhlL86KcFgt9HGz0iDhw0YVYPsShItpN5pqIkNrI+L/Q/0gfDiGrfcE0X3VANSYIPmqEAlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-x64-gnu": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-gnu/-/rspack-resolver-binding-linux-x64-gnu-1.2.2.tgz", + "integrity": "sha512-n/4n2CxaUF9tcaJxEaZm+lqvaw2gflfWQ1R9I7WQgYkKEKbRKbpG/R3hopYdUmLSRI4xaW1Cy0Bz40eS2Yi4Sw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-linux-x64-musl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-musl/-/rspack-resolver-binding-linux-x64-musl-1.2.2.tgz", + "integrity": "sha512-cHyhAr6rlYYbon1L2Ag449YCj3p6XMfcYTP0AQX+KkQo025d1y/VFtPWvjMhuEsE2lLvtHm7GdJozj6BOMtzVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-wasm32-wasi": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-wasm32-wasi/-/rspack-resolver-binding-wasm32-wasi-1.2.2.tgz", + "integrity": "sha512-eogDKuICghDLGc32FtP+WniG38IB1RcGOGz0G3z8406dUdjJvxfHGuGs/dSlM9YEp/v0lEqhJ4mBu6X2nL9pog==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/rspack-resolver-binding-win32-arm64-msvc": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-arm64-msvc/-/rspack-resolver-binding-win32-arm64-msvc-1.2.2.tgz", + "integrity": "sha512-7sWRJumhpXSi2lccX8aQpfFXHsSVASdWndLv8AmD8nDRA/5PBi8IplQVZNx2mYRx6+Bp91Z00kuVqpXO9NfCTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/rspack-resolver-binding-win32-x64-msvc": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-x64-msvc/-/rspack-resolver-binding-win32-x64-msvc-1.2.2.tgz", + "integrity": "sha512-hewo/UMGP1a7O6FG/ThcPzSJdm/WwrYDNkdGgWl6M18H6K6MSitklomWpT9MUtT5KGj++QJb06va/14QBC4pvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@vercel/webpack-asset-relocator-loader": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@vercel/webpack-asset-relocator-loader/-/webpack-asset-relocator-loader-1.7.3.tgz", @@ -4506,9 +4723,9 @@ } }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", + "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -7335,6 +7552,43 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.2.2.tgz", + "integrity": "sha512-Rg1YEsb9UKLQ8BOv27cS3TZ6LhEAKQVgVOXArcE/sQrlnX8+FjmJRSC29ij1qrn+eurFuMsCFUcs7/+27T0vqQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "rspack-resolver": "^1.2.2", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*", + "is-bun-module": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + }, + "is-bun-module": { + "optional": true + } + } + }, "node_modules/eslint-module-utils": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", @@ -7832,6 +8086,28 @@ "express": "^4.0.0 || ^5.0.0-alpha.1" } }, + "node_modules/express-ws/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -12162,6 +12438,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openai": { + "version": "4.87.3", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.87.3.tgz", + "integrity": "sha512-d2D54fzMuBYTxMW8wcNmhT1rYKcTfMJ8t+4KjH2KtvYenygITiGBgHoIrzHwnDQWW+C5oCA+ikIR2jgPCFqcKQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.80", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.80.tgz", + "integrity": "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -13964,6 +14285,29 @@ "postcss": "^8.4.38" } }, + "node_modules/rspack-resolver": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/rspack-resolver/-/rspack-resolver-1.2.2.tgz", + "integrity": "sha512-Fwc19jMBA3g+fxDJH2B4WxwZjE0VaaOL7OX/A4Wn5Zv7bOD/vyPZhzXfaO73Xc2GAlfi96g5fGUa378WbIGfFw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/JounQin" + }, + "optionalDependencies": { + "@unrs/rspack-resolver-binding-darwin-arm64": "1.2.2", + "@unrs/rspack-resolver-binding-darwin-x64": "1.2.2", + "@unrs/rspack-resolver-binding-freebsd-x64": "1.2.2", + "@unrs/rspack-resolver-binding-linux-arm-gnueabihf": "1.2.2", + "@unrs/rspack-resolver-binding-linux-arm64-gnu": "1.2.2", + "@unrs/rspack-resolver-binding-linux-arm64-musl": "1.2.2", + "@unrs/rspack-resolver-binding-linux-x64-gnu": "1.2.2", + "@unrs/rspack-resolver-binding-linux-x64-musl": "1.2.2", + "@unrs/rspack-resolver-binding-wasm32-wasi": "1.2.2", + "@unrs/rspack-resolver-binding-win32-arm64-msvc": "1.2.2", + "@unrs/rspack-resolver-binding-win32-x64-msvc": "1.2.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -14819,6 +15163,13 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -15520,6 +15871,51 @@ "license": "MIT", "optional": true }, + "node_modules/tinyglobby": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.12.tgz", + "integrity": "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.3", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", + "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -16510,28 +16906,6 @@ "node": ">= 10" } }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/webpack-merge": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", @@ -16822,17 +17196,17 @@ "license": "ISC" }, "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index 387605e..a77884a 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "css-loader": "^6.11.0", "electron": "33.2.1", "eslint": "^8.57.1", + "eslint-import-resolver-typescript": "^4.2.2", "eslint-plugin-import": "^2.31.0", "fork-ts-checker-webpack-plugin": "^7.3.0", "node-loader": "^2.1.0", @@ -78,6 +79,7 @@ "framer-motion": "^12.4.7", "highlight.js": "^11.11.0", "lucide-react": "^0.468.0", + "openai": "^4.87.3", "posthog-js": "^1.116.6", "posthog-node": "^4.6.0", "react": "^19.0.0", diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index dcb9e34..abd875d 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -1,15 +1,15 @@ -import { useState, useEffect } from "react"; -import { Conversation, newConversation, Project } from "@/types"; -import { Message, newUserMessage } from "@/types/message"; -import { ChatArea } from "@/components/ChatArea"; import { AppSidebar } from "@/components/AppSidebar"; +import { ChatArea } from "@/components/ChatArea"; +import { SettingsDialog } from "@/components/SettingsDialog"; +import { TitleBar } from "@/components/TitleBar"; import { SidebarProvider } from "@/components/ui/sidebar"; +import { Toaster } from "@/components/ui/toaster"; import { H2 } from "@/components/ui/typography"; -import { systemPrompt } from "@/lib/prompts"; import { WelcomeFlow } from "@/components/WelcomeFlow"; -import { SettingsDialog } from "@/components/SettingsDialog"; -import { Toaster } from "@/components/ui/toaster"; -import { TitleBar } from "@/components/TitleBar"; +import { systemPrompt } from "@/lib/prompts"; +import { Conversation, newConversation, Project } from "@/types"; +import { Message, newUserMessage } from "@/types/message"; +import { useEffect, useState } from "react"; export function Chat() { const [conversations, setConversations] = useState([]); @@ -52,7 +52,7 @@ export function Chat() { const loadConversationsForProject = async () => { try { const loadedConversations = await window.conversations.getAll( - selectedProject.id + selectedProject.id, ); setConversations(loadedConversations); } catch (err) { @@ -83,8 +83,8 @@ export function Chat() { messages: [...conversation.messages, message], updatedAt: Date.now(), } - : conversation - ) + : conversation, + ), ); // Update current conversation if it's the active one @@ -122,7 +122,7 @@ export function Chat() { setConversations((prev) => prev .map((c) => (c.id === conversation.id ? conversation : c)) - .sort((a, b) => b.updatedAt - a.updatedAt) + .sort((a, b) => b.updatedAt - a.updatedAt), ); }; @@ -149,7 +149,11 @@ export function Chat() { return cleanup; }, []); - const handleNewConversation = async (message: string): Promise => { + const handleNewConversation = async ( + message: string, + model: string, + thinking?: boolean, + ): Promise => { if ( !selectedProject || !window.conversations?.create || @@ -166,23 +170,27 @@ export function Chat() { setCurrentConversation(newConv); setConversations(await window.conversations.getAll(selectedProject.id)); - window.chat.generateTitle(newConv.id, message).catch((err) => { + window.chat.generateTitle(newConv.id, message, model).catch((err) => { console.error("Error generating title:", err); setError( err instanceof Error ? err.message - : "Title generation failed. See logs or console for details." + : "Title generation failed. See logs or console for details.", ); }); window.chat - .start(newConv.id, systemPrompt(selectedProject)) + .start(newConv.id, { + model, + thinking, + systemPrompt: systemPrompt(selectedProject), + }) .catch((err) => { console.error("Error starting chat:", err); setError( err instanceof Error ? err.message - : "Chat failed. See logs or console for details." + : "Chat failed. See logs or console for details.", ); }); }; @@ -205,7 +213,9 @@ export function Chat() { const handleNewMessage = async ( conversationId: string, - message: string + message: string, + model: string, + thinking?: boolean, ): Promise => { if (!window.conversations?.update || !window.chat?.start) { console.warn("Required APIs are not available"); @@ -225,7 +235,7 @@ export function Chat() { setError( err instanceof Error ? err.message - : "Error saving conversation. See logs or console for details." + : "Error saving conversation. See logs or console for details.", ); }); return updatedConversation; @@ -235,13 +245,17 @@ export function Chat() { // Start chat processing window.chat - .start(conversationId, systemPrompt(selectedProject)) + .start(conversationId, { + model, + thinking, + systemPrompt: systemPrompt(selectedProject), + }) .catch((err) => { console.error("Error starting chat:", err); setError( err instanceof Error ? err.message - : "Chat failed. See logs or console for details." + : "Chat failed. See logs or console for details.", ); }); }; @@ -292,7 +306,7 @@ export function Chat() { if (selectedProject) { const conversations = await window.conversations.getAll( - selectedProject.id + selectedProject.id, ); setConversations(conversations); } @@ -316,7 +330,7 @@ export function Chat() { if (selectedProject) { const conversations = await window.conversations.getAll( - selectedProject.id + selectedProject.id, ); setConversations(conversations); } @@ -378,10 +392,7 @@ export function Chat() { }} onSelectProject={onSelectProject} /> - + diff --git a/src/components/ChatArea.tsx b/src/components/ChatArea.tsx index 887809c..d2bea72 100644 --- a/src/components/ChatArea.tsx +++ b/src/components/ChatArea.tsx @@ -8,7 +8,6 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { H2 } from "@/components/ui/typography"; import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; import { useMessageConversion } from "@/hooks/useMessageConversion"; -import { SidebarTrigger } from "./ui/sidebar"; import { Breadcrumb, BreadcrumbItem, @@ -16,12 +15,11 @@ import { BreadcrumbPage, BreadcrumbSeparator, } from "./ui/breadcrumb"; -import { Separator } from "@radix-ui/react-separator"; interface ChatAreaProps { conversation: Conversation | null; - onNewConversation: (message: string) => Promise; - onNewMessage: (conversationId: string, message: string) => Promise; + onNewConversation: (message: string, model?: string, thinking?: boolean) => Promise; + onNewMessage: (conversationId: string, message: string, model?: string, thinking?: boolean) => Promise; onStop: () => Promise; isLoading: boolean; project: Project | null; @@ -39,6 +37,7 @@ export const ChatArea = ({ }: ChatAreaProps) => { const messagesEndRef = useRef(null); const [isStopping, setIsStopping] = useState(false); + const [selectedModel, setSelectedModel] = useState(""); // Scroll to the bottom of the chat when new messages are added useEffect(() => { @@ -79,15 +78,15 @@ export const ChatArea = ({ await onStop(); }; - const handleMessage = async (input: string) => { + const handleMessage = async (input: string, model?: string, thinking?: boolean) => { if (!input.trim()) return; if (!conversation) { - await onNewConversation(input); + await onNewConversation(input, model, thinking); return; } - await onNewMessage(conversation.id, input); + await onNewMessage(conversation.id, input, model, thinking); }; return ( @@ -190,6 +189,8 @@ export const ChatArea = ({ isLoading={isLoading} isStopping={isStopping} className="py-4 max-w-3xl mx-auto w-full px-4" + selectedModelId={selectedModel} + onModelChange={setSelectedModel} /> @@ -205,6 +206,8 @@ export const ChatArea = ({ isLoading={isLoading} isStopping={isStopping} className="max-w-2xl mx-auto w-full" + selectedModelId={selectedModel} + onModelChange={setSelectedModel} /> )} diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 7e548c9..ccf239f 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -1,11 +1,35 @@ -import React, { useState, useRef, useEffect, KeyboardEvent, ChangeEvent, FormEvent } from 'react'; -import { Send, Square } from 'lucide-react'; - -import { Button } from '@/components/ui/button' -import { Textarea } from '@/components/ui/textarea' +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectSeparator, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import { Model } from "@/types/model"; +import { Brain, Send, Square } from "lucide-react"; +import React, { + ChangeEvent, + FormEvent, + KeyboardEvent, + useEffect, + useRef, + useState, +} from "react"; interface ChatInputProps { - onSendMessage?: (message: string) => void; + onSendMessage?: (message: string, model?: string, thinking?: boolean) => void; onStop?: () => void; placeholder?: string; maxHeight?: number; @@ -13,26 +37,60 @@ interface ChatInputProps { className?: string; isLoading?: boolean; isStopping?: boolean; + selectedModelId?: string; + onModelChange?: (modelId: string) => void; } export const ChatInput: React.FC = ({ - onSendMessage = (message: string) => console.log('Sending message:', message), - onStop = () => console.log('Stopping chat...'), - placeholder = 'Type a message...', + onSendMessage = (message: string) => console.log("Sending message:", message), + onStop = () => console.log("Stopping chat..."), + placeholder = "Type a message...", maxHeight = 200, disabled = false, - className = '', + className = "", isLoading = false, isStopping = false, + selectedModelId = "", + onModelChange = () => {}, }) => { - const [message, setMessage] = useState(''); + const [message, setMessage] = useState(""); + const [models, setModels] = useState([]); + const [isLoadingModels, setIsLoadingModels] = useState(true); + const [thinking, setThinking] = useState(false); const textareaRef = useRef(null); + // Get current model object + const selectedModel = models.find((m) => m.id === selectedModelId); + + // Fetch available models when component mounts + useEffect(() => { + const fetchModels = async () => { + setIsLoadingModels(true); + try { + const availableModels = await window.models.getAll(); + setModels(availableModels); + } catch (error) { + console.error("Failed to fetch models:", error); + } finally { + setIsLoadingModels(false); + } + }; + + fetchModels(); + }, []); + + // Disable thinking toggle if model doesn't support it + useEffect(() => { + if (selectedModel && !selectedModel?.capabilities?.thinking) { + setThinking(false); + } + }, [selectedModelId, selectedModel]); + // Auto-resize textarea as content grows useEffect(() => { const textarea = textareaRef.current; if (textarea) { - textarea.style.height = 'auto'; + textarea.style.height = "auto"; textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`; } }, [message, maxHeight]); @@ -40,17 +98,17 @@ export const ChatInput: React.FC = ({ const handleSubmit = (e: FormEvent) => { e.preventDefault(); if (message.trim() && !disabled) { - onSendMessage(message.trim()); - setMessage(''); + onSendMessage(message.trim(), selectedModelId, thinking); + setMessage(""); } }; const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { + if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); if (message.trim() && !disabled) { - onSendMessage(message.trim()); - setMessage(''); + onSendMessage(message.trim(), selectedModelId, thinking); + setMessage(""); } } }; @@ -59,22 +117,128 @@ export const ChatInput: React.FC = ({ setMessage(e.target.value); }; + // Group models by provider + const groupedModels = models.reduce( + (acc, model) => { + if (!acc[model.provider]) { + acc[model.provider] = []; + } + acc[model.provider].push(model); + return acc; + }, + {} as Record, + ); + return (
-
+