diff --git a/.gitignore b/.gitignore index a14702c..ce6d2f4 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store +.data \ No newline at end of file diff --git a/README.md b/README.md index 0bad03f..8163e17 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ bun install To run: ```bash -bun run index.ts +bun run index.tsx ``` This project was created using `bun init` in bun v1.3.13. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. diff --git a/bun.lock b/bun.lock index a1f7cd3..af77abb 100644 --- a/bun.lock +++ b/bun.lock @@ -5,13 +5,18 @@ "": { "name": "icoder", "dependencies": { - "@clack/prompts": "^1.3.0", "@vscode/ripgrep": "^1.17.1", "axios": "^1.15.2", + "dayjs": "^1.11.20", + "ink": "^7.0.2", + "ink-spinner": "^5.0.0", + "ink-text-input": "^6.0.0", "picocolors": "^1.1.1", + "react": "^19.2.6", }, "devDependencies": { "@types/bun": "latest", + "@types/react": "^19.2.14", }, "peerDependencies": { "typescript": "^5", @@ -19,20 +24,28 @@ }, }, "packages": { - "@clack/core": ["@clack/core@1.3.0", "https://registry.npmmirror.com/@clack/core/-/core-1.3.0.tgz", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-xJPHpAmEQUBrXSLx0gF+q5K/IyihXpsHZcha+jB+tyahsKRK3Dxo4D0coZDewHo12NhiuzC3dTtMPbm53GEAAA=="], - - "@clack/prompts": ["@clack/prompts@1.3.0", "https://registry.npmmirror.com/@clack/prompts/-/prompts-1.3.0.tgz", { "dependencies": { "@clack/core": "1.3.0", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-GgcWwRCs/xPtaqlMy8qRhPnZf9vlWcWZNHAitnVQ3yk7JmSralSiq5q07yaffYE8SogtDm7zFeKccx1QNVARpw=="], + "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.3.0", "https://registry.npmmirror.com/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.0.tgz", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA=="], "@types/bun": ["@types/bun@1.3.13", "https://registry.npmmirror.com/@types/bun/-/bun-1.3.13.tgz", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="], "@types/node": ["@types/node@25.6.0", "https://registry.npmmirror.com/@types/node/-/node-25.6.0.tgz", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + "@types/react": ["@types/react@19.2.14", "https://registry.npmmirror.com/@types/react/-/react-19.2.14.tgz", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + "@vscode/ripgrep": ["@vscode/ripgrep@1.17.1", "https://registry.npmmirror.com/@vscode/ripgrep/-/ripgrep-1.17.1.tgz", { "dependencies": { "https-proxy-agent": "^7.0.2", "proxy-from-env": "^1.1.0", "yauzl": "^2.9.2" } }, "sha512-xTs7DGyAO3IsJYOCTBP8LnTvPiYVKEuyv8s0xyJDBXfs8rhBfqnZPvb6xDT+RnwWzcXqW27xLS/aGrkjX7lNWw=="], "agent-base": ["agent-base@7.1.4", "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ansi-escapes": ["ansi-escapes@7.3.0", "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-7.3.0.tgz", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], + + "ansi-regex": ["ansi-regex@6.2.2", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@6.2.3", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "asynckit": ["asynckit@0.4.0", "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "auto-bind": ["auto-bind@5.0.1", "https://registry.npmmirror.com/auto-bind/-/auto-bind-5.0.1.tgz", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + "axios": ["axios@1.15.2", "https://registry.npmmirror.com/axios/-/axios-1.15.2.tgz", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A=="], "buffer-crc32": ["buffer-crc32@0.2.13", "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], @@ -41,14 +54,34 @@ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + "chalk": ["chalk@5.6.2", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + + "cli-boxes": ["cli-boxes@4.0.1", "https://registry.npmmirror.com/cli-boxes/-/cli-boxes-4.0.1.tgz", {}, "sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw=="], + + "cli-cursor": ["cli-cursor@4.0.0", "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-4.0.0.tgz", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], + + "cli-spinners": ["cli-spinners@2.9.2", "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + + "cli-truncate": ["cli-truncate@6.0.0", "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-6.0.0.tgz", { "dependencies": { "slice-ansi": "^9.0.0", "string-width": "^8.2.0" } }, "sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA=="], + + "code-excerpt": ["code-excerpt@4.0.0", "https://registry.npmmirror.com/code-excerpt/-/code-excerpt-4.0.0.tgz", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], + "combined-stream": ["combined-stream@1.0.8", "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + "convert-to-spaces": ["convert-to-spaces@2.0.1", "https://registry.npmmirror.com/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="], + + "csstype": ["csstype@3.2.3", "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "dayjs": ["dayjs@1.11.20", "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", {}, "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ=="], + "debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "delayed-stream": ["delayed-stream@1.0.0", "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "environment": ["environment@1.1.0", "https://registry.npmmirror.com/environment/-/environment-1.1.0.tgz", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + "es-define-property": ["es-define-property@1.0.1", "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], @@ -57,11 +90,9 @@ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "https://registry.npmmirror.com/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], + "es-toolkit": ["es-toolkit@1.46.1", "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.46.1.tgz", {}, "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ=="], - "fast-string-width": ["fast-string-width@3.0.2", "https://registry.npmmirror.com/fast-string-width/-/fast-string-width-3.0.2.tgz", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], - - "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "https://registry.npmmirror.com/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="], + "escape-string-regexp": ["escape-string-regexp@2.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], "fd-slicer": ["fd-slicer@1.1.0", "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], @@ -71,6 +102,8 @@ "function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "get-east-asian-width": ["get-east-asian-width@1.5.0", "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], "get-proto": ["get-proto@1.0.1", "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], @@ -85,28 +118,78 @@ "https-proxy-agent": ["https-proxy-agent@7.0.6", "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "indent-string": ["indent-string@5.0.0", "https://registry.npmmirror.com/indent-string/-/indent-string-5.0.0.tgz", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "ink": ["ink@7.0.2", "https://registry.npmmirror.com/ink/-/ink-7.0.2.tgz", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.3.0", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.3", "auto-bind": "^5.0.1", "chalk": "^5.6.2", "cli-boxes": "^4.0.1", "cli-cursor": "^4.0.0", "cli-truncate": "^6.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.45.1", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^9.0.0", "stack-utils": "^2.0.6", "string-width": "^8.2.0", "terminal-size": "^4.0.1", "type-fest": "^5.5.0", "widest-line": "^6.0.0", "wrap-ansi": "^10.0.0", "ws": "^8.20.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.2.0", "react": ">=19.2.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-cnkE2SsDC/gieJ+BD8+gWpXrZPMInv7agBYN5gcKVlQZYp+IKa/FKM5bp1OIuJFp3ZIuRK7ZNxY4MZR3tUzyfQ=="], + + "ink-spinner": ["ink-spinner@5.0.0", "https://registry.npmmirror.com/ink-spinner/-/ink-spinner-5.0.0.tgz", { "dependencies": { "cli-spinners": "^2.7.0" }, "peerDependencies": { "ink": ">=4.0.0", "react": ">=18.0.0" } }, "sha512-EYEasbEjkqLGyPOUc8hBJZNuC5GvXGMLu0w5gdTNskPc7Izc5vO3tdQEYnzvshucyGCBXc86ig0ujXPMWaQCdA=="], + + "ink-text-input": ["ink-text-input@6.0.0", "https://registry.npmmirror.com/ink-text-input/-/ink-text-input-6.0.0.tgz", { "dependencies": { "chalk": "^5.3.0", "type-fest": "^4.18.2" }, "peerDependencies": { "ink": ">=5", "react": ">=18" } }, "sha512-Fw64n7Yha5deb1rHY137zHTAbSTNelUKuB5Kkk2HACXEtwIHBCf9OH2tP/LQ9fRYTl1F0dZgbW0zPnZk6FA9Lw=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "is-in-ci": ["is-in-ci@2.0.0", "https://registry.npmmirror.com/is-in-ci/-/is-in-ci-2.0.0.tgz", { "bin": { "is-in-ci": "cli.js" } }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "mime-db": ["mime-db@1.52.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "mime-types": ["mime-types@2.1.35", "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "mimic-fn": ["mimic-fn@2.1.0", "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "onetime": ["onetime@5.1.2", "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "patch-console": ["patch-console@2.0.0", "https://registry.npmmirror.com/patch-console/-/patch-console-2.0.0.tgz", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], + "pend": ["pend@1.2.0", "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], "picocolors": ["picocolors@1.1.1", "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "proxy-from-env": ["proxy-from-env@1.1.0", "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - "sisteransi": ["sisteransi@1.0.5", "https://registry.npmmirror.com/sisteransi/-/sisteransi-1.0.5.tgz", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + "react": ["react@19.2.6", "https://registry.npmmirror.com/react/-/react-19.2.6.tgz", {}, "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q=="], + + "react-reconciler": ["react-reconciler@0.33.0", "https://registry.npmmirror.com/react-reconciler/-/react-reconciler-0.33.0.tgz", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], + + "restore-cursor": ["restore-cursor@4.0.0", "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-4.0.0.tgz", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], + + "scheduler": ["scheduler@0.27.0", "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "signal-exit": ["signal-exit@3.0.7", "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "slice-ansi": ["slice-ansi@9.0.0", "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-9.0.0.tgz", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA=="], + + "stack-utils": ["stack-utils@2.0.6", "https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + + "string-width": ["string-width@8.2.1", "https://registry.npmmirror.com/string-width/-/string-width-8.2.1.tgz", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA=="], + + "strip-ansi": ["strip-ansi@7.2.0", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "tagged-tag": ["tagged-tag@1.0.0", "https://registry.npmmirror.com/tagged-tag/-/tagged-tag-1.0.0.tgz", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="], + + "terminal-size": ["terminal-size@4.0.1", "https://registry.npmmirror.com/terminal-size/-/terminal-size-4.0.1.tgz", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="], + + "type-fest": ["type-fest@5.6.0", "https://registry.npmmirror.com/type-fest/-/type-fest-5.6.0.tgz", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="], "typescript": ["typescript@5.9.3", "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@7.19.2", "https://registry.npmmirror.com/undici-types/-/undici-types-7.19.2.tgz", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + "widest-line": ["widest-line@6.0.0", "https://registry.npmmirror.com/widest-line/-/widest-line-6.0.0.tgz", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="], + + "wrap-ansi": ["wrap-ansi@10.0.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-10.0.0.tgz", { "dependencies": { "ansi-styles": "^6.2.3", "string-width": "^8.2.0", "strip-ansi": "^7.1.2" } }, "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ=="], + + "ws": ["ws@8.20.0", "https://registry.npmmirror.com/ws/-/ws-8.20.0.tgz", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "yauzl": ["yauzl@2.10.0", "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + "yoga-layout": ["yoga-layout@3.2.1", "https://registry.npmmirror.com/yoga-layout/-/yoga-layout-3.2.1.tgz", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], + "axios/proxy-from-env": ["proxy-from-env@2.1.0", "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + + "ink-text-input/type-fest": ["type-fest@4.41.0", "https://registry.npmmirror.com/type-fest/-/type-fest-4.41.0.tgz", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], } } diff --git a/index.ts b/index.ts deleted file mode 100644 index 929e53d..0000000 --- a/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as p from '@clack/prompts'; -import {sendMessage} from "./src/llm.ts"; -import color from 'picocolors'; - -async function chat() { - console.clear(); - p.intro(color.bgCyan(color.black(' AI 助手已就绪 '))); - - while (true) { - // 1. 获取输入 - const prompt = await p.text({ - message: '你:', - placeholder: '说点什么... (输入 exit 退出)', - validate: (value) => value ? undefined : '请输入内容' - }); - - // 2. 退出判定 - if (p.isCancel(prompt) || prompt.toLowerCase() === 'exit') { - p.outro(color.dim('对话结束')); - break; - } - - // 3. 模拟回复逻辑 - const s = p.spinner(); - s.start('助手正在思考...'); - - // 这里替换成你真实的 AI 请求 - const response = await sendMessage(prompt); - - s.stop('助手回复:'); - - // 4. 使用 p.note 来承载回复内容,它会自动处理边框和宽度 - // p.note( - // response.choices[0].message.reasoning_content, - // '✨ ' - // ); - p.log.message(color.white( - response.choices[0].message.reasoning_content - )) - p.log.success(color.white( - response.choices[0].message.content - )); - - // 5. 打印一个空行,防止下一轮对话的 "你:" 贴得太近 - console.log(''); - } -} - -chat(); \ No newline at end of file diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..40353c2 --- /dev/null +++ b/index.tsx @@ -0,0 +1,153 @@ +import React, {useState} from 'react'; +import {Box, render, Text, useInput} from 'ink'; +import TextInput from 'ink-text-input'; +import Spinner from 'ink-spinner'; + +import {addMessage, callTools, type ChatMessage, findMessages, getMessages, requestLLMStream} from "./src/llm.ts"; +import dayjs from "dayjs"; + + +const App = () => { + const [input, setInput] = useState(''); // 当前输入框内容 + const [isChatting, setIsChatting] = useState(false); // 是否正在对话 + const [history, setHistory] = useState(getMessages()); // 对话历史 + const [currentResponse, setCurrentResponse] = useState(''); // 当前流式输出的内容 + const [currentReasoningResponse, setCurrentReasoningResponse] = useState(''); // 当前流式输出的内容 + + // 处理提交 + const handleSubmit = async (value: string) => { + if (!value) return; + setIsChatting(true); + await addMessage("now is " + dayjs().format("YYYY-MM-DD HH:mm:ss"), "system"); + await addMessage(value); + setHistory(getMessages()); + setInput(''); + + await dealRequest(); + }; + + const dealRequest = async () => { + let request_id = ""; + let fullText = ''; + let reasoningFullText = ''; + for await (const token of requestLLMStream(false)) { + fullText += token.content || ""; + reasoningFullText += token.reasoning_content || ""; + setCurrentResponse(fullText); + setCurrentReasoningResponse(reasoningFullText); + if (token.id) { + request_id = token.id; + } + } + const lastMsg = findMessages(request_id); + + if (lastMsg && lastMsg.finish_reason === "tool_calls") { + // 处理工具调用 + if (lastMsg.tool_call_id && lastMsg.tool_call_name) { + await callTools( + lastMsg.tool_call_id, + lastMsg.tool_call_name, + lastMsg.tool_call_arguments + ) + await dealRequest() + } + return; + } else { + // 流结束,归档到历史 + setHistory(getMessages()); + setCurrentResponse(''); + setCurrentReasoningResponse(''); + setIsChatting(false); + } + } + // 监听退出快捷键 (Ctrl+C) + useInput((input, key) => { + if (input === 'q' || (key.ctrl && input === 'c')) { + process.exit(); + } + }); + + return ( + + {/* 1. 标题栏 */} + + 🤖 iCoder + + + {/* 2. 渲染历史记录 */} + {history.map((msg) => ( + + + {msg.reasoning_content ?? ""} + + + {msg.content ?? ""} + + + {msg.finish_reason} + {msg.tool_call_id} + {msg.tool_call_name} + {msg.tool_call_arguments} + + + { + msg.role === "user" ? ( + + {msg.createdAt ?? ""} + + ) : null + } + + ))} + + {/* 3. 渲染当前正在生成的流 */} + {isChatting && currentResponse && ( + + + + 正在工作... + + + + {currentReasoningResponse} + + + {currentResponse} + + + )} + + {/* 4. 加载状态 */} + {isChatting && !currentResponse && ( + + + 正在思考... + + + )} + + {/* 5. 输入区 */} + {!isChatting && ( + + + 🎨 + + + + + + )} + + + 按 Ctrl+C 退出 + + + ); +}; + +render(); \ No newline at end of file diff --git a/package.json b/package.json index 026afd1..6b769fd 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,23 @@ { "name": "icoder", - "module": "index.ts", + "module": "index.tsx", "type": "module", "private": true, "devDependencies": { - "@types/bun": "latest" + "@types/bun": "latest", + "@types/react": "^19.2.14" }, "peerDependencies": { "typescript": "^5" }, "dependencies": { - "@clack/prompts": "^1.3.0", "@vscode/ripgrep": "^1.17.1", "axios": "^1.15.2", - "picocolors": "^1.1.1" + "dayjs": "^1.11.20", + "ink": "^7.0.2", + "ink-spinner": "^5.0.0", + "ink-text-input": "^6.0.0", + "picocolors": "^1.1.1", + "react": "^19.2.6" } } diff --git a/src/llm.ts b/src/llm.ts index e7eafd0..0a6b264 100644 --- a/src/llm.ts +++ b/src/llm.ts @@ -1,54 +1,253 @@ -import axios from "axios"; +import axios, {type AxiosRequestConfig} from "axios"; +import dayjs from "dayjs"; +import {mkdir, readdir} from "node:fs/promises"; -const apiKey = Bun.env.API_KEY; +export type Role = "user" | "assistant" | "system" | "tool"; -interface Message { +export interface ChatMessage { + id?: string; + role?: Role; content?: string; - role: "user" | "assistant" | "system"; + reasoning_content?: string; + extra?: string; + createdAt?: string; + tool_call_id?: string; + tool_call_name?: string; + tool_call_arguments?: string; + finish_reason?: string; } +await mkdir("./.data", {recursive: true}); +await mkdir("./.data/sessions", {recursive: true}); +await mkdir("./.data/requests", {recursive: true}); + + +const baseUrl = Bun.env.BASE_URL; +const apiKey = Bun.env.API_KEY; +const model = Bun.env.MODEL_NAME; + + const headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': 'Bearer ' + apiKey + "Content-Type": "application/json", + "Accept": "application/json", + "Authorization": "Bearer " + apiKey }; -const messageList: Message[] = [ +const messageList: ChatMessage[] = [ { content: "你是一个专业的代码助手", - role: "system" + role: "system", } ]; +const tools: object[] = [ + { + "type": "function", + "function": { + "name": "file_list", + "description": "查看给定目录下的所有文件列表", + "parameters": { + "type": "object", + "properties": { + "directory": { + "type": "string", + "description": "要查看文件列表的目录" + } + } + } + } + } +]; + +export function getMessages() { + return messageList.filter((message) => message.role === "user" || message.role === "assistant"); +} + + +export function findMessages(id?: string) { + return messageList.find((message) => message.id === id); +} + export async function sendMessage(content: string) { messageList.push({ - content: content, - role: "user" + content: "now time is " + Date(), + role: "system" + }); + messageList.push({ + content: content, + role: "user", + createdAt: dayjs().format('HH:mm:ss') }); - let config = { - method: 'post', - maxBodyLength: Infinity, - url: 'https://api.deepseek.com/chat/completions', - headers: headers, - data: JSON.stringify(getBody()) - }; - const response = await axios(config); + return await requestLLM() +} + +export async function requestLLM(): Promise { + const response = await generateRequest(false); if (response.status === 200) { - messageList.push({ - content: response.data.choices[0].message.content, - role: "assistant" - }); + const message = response.data?.choices?.[0]?.message; + let content = message?.content ?? ""; + let reasoning_content = message?.reasoning_content ?? ""; + await addMessage(content, "assistant"); + return {content, reasoning_content}; } - return response.data; + return {content: response.data?.choices?.[0]?.message?.content ?? "请求失败", reasoning_content: ""}; +} + +export async function addMessage(content: string, role: Role = "user") { + messageList.push({ + content: content, + role: role, + createdAt: dayjs().format('HH:mm:ss'), + id: Date.now() + Math.random().toString(), + }); } -function getBody() { +export async function* sendMessageStream(content: string) { + messageList.push({ + content: "now time is " + Date(), + role: "system" + }); + messageList.push({ + content: content, + role: "user", + createdAt: dayjs().format('HH:mm:ss') + }); + for await (const result of requestLLMStream(false)) { + yield result; + } + return {} +} + +export async function* requestLLMStream(logs: boolean = false): AsyncGenerator { + const response = await generateRequest(true) + if (response.status === 200) { + let request_id: string | undefined = undefined; + let result_data = "" + + // let content = "" + // let reasoning_content = "" + // let tool_calls_name = "" + // let tool_calls_arguments = "" + const messageData: ChatMessage = { + content: "", + reasoning_content: "", + tool_call_id: "", + tool_call_name: "", + tool_call_arguments: "", + finish_reason: "", + } + + for await (const chunk of response.data) { + const lines = chunk.toString().split("\n") + for (const line of lines) { + if (line.startsWith("data:")) { + if (result_data !== "") { + result_data += "\n" + } + result_data += line; + } + if (logs) console.log(line); + if (line.trim() === "") continue; + if (line.includes("[DONE]")) { + if (request_id != undefined && line.trim() !== "") { + await Bun.write("./.data/requests/" + request_id + ".log", result_data); + } + if (messageData.content !== "") { + messageList.push({ + content: messageData.content, + reasoning_content: messageData.reasoning_content, + role: "assistant", + id: request_id, + }); + // if (messageData?.tool_call_id && messageData?.tool_call_name && messageData?.tool_call_arguments) { + // await callTools(messageData.tool_call_id, messageData.tool_call_name, messageData.tool_call_arguments); + // return requestLLMStream(false); + // } + } + + return {}; + } + + if (line.startsWith("data: ")) { + try { + const data = JSON.parse(line.replace("data: ", "")); + if (request_id === undefined) { + request_id = data.id + } + const chunkDelta = data.choices[0].delta + if (chunkDelta.id) { + messageData.id = chunkDelta.id; + } + + messageData.content += chunkDelta.content ?? "" + messageData.reasoning_content += chunkDelta.reasoning_content ?? "" + + if (chunkDelta.tool_calls != null && chunkDelta.tool_calls.length > 0) { + const tool_call = chunkDelta.tool_calls[0]; + if (tool_call.id) { + messageData.tool_call_id = tool_call.id; + } + if (tool_call.name) { + messageData.tool_call_name += tool_call.name; + } + if (tool_call.arguments) { + messageData.tool_call_arguments += tool_call.arguments; + } + } + if (chunkDelta.finish_reason) { + messageData.finish_reason = chunkDelta.finish_reason; + } + + yield { + id: request_id, + content: chunkDelta?.content || "", + reasoning_content: chunkDelta?.reasoning_content || "" + }; + } catch (e) { + + } + } + } + } + } + return {}; +} + +export async function callTools(tool_call_id: string, tool_calls_name: string, tool_calls_arguments?: string) { + if (tool_calls_name === "file_list") { + const args = JSON.parse(tool_calls_arguments || "{}"); + const files = await readdir(args.directory); + await addMessage(files.join(","), "tool"); + messageList.push({ + tool_call_id: tool_call_id, + content: files.join(","), + role: "tool" + }); + return files.join(","); + } +} + + +async function generateRequest(stream: boolean) { + let config: AxiosRequestConfig = { + method: "post", + maxBodyLength: Infinity, + url: baseUrl + "/chat/completions", + headers: headers, + data: JSON.stringify(getBody(stream)), + responseType: stream ? "stream" : "json" + }; + return await axios(config); + +} + +function getBody(stream: boolean) { return { "messages": messageList, - "model": "deepseek-v4-flash", + "model": model ?? "deepseek-v4-flash", "thinking": { "type": "enabled" }, @@ -60,28 +259,13 @@ function getBody() { "type": "text" }, "stop": null, - "stream": false, - "stream_options": null, + "stream": stream, "temperature": 1, "top_p": 1, - "tools": null, - "tool_choice": "none", + "tools": tools, + "tool_choice": "auto", "logprobs": false, "top_logprobs": null } } -function getMessageList() { - if (!messageList) { - return "[]" - } - let messageListStr = "[" - for (let message of messageList) { - if (messageListStr == "[") { - messageListStr += "," - } - messageListStr += "{\"" + message.content + "\"," + "\"" + message.role + "\"}" - } - messageListStr = messageListStr.slice(0, -1) + "]" - return messageListStr; -} diff --git a/src/tools/list_files.ts b/src/tools/list_files.ts new file mode 100644 index 0000000..fdc0483 --- /dev/null +++ b/src/tools/list_files.ts @@ -0,0 +1,3 @@ +import { readdir } from "node:fs/promises"; +const files = await readdir("./src"); +console.log(files); diff --git a/src/types/message.ts b/src/types/message.ts new file mode 100644 index 0000000..ed7b3c8 --- /dev/null +++ b/src/types/message.ts @@ -0,0 +1,59 @@ + +export interface MessageResponse { + id?: string; + object?: string; + created?: number; + model?: string; + system_fingerprint?: string; + choices?: Choice[]; + usage?: Usage; +} + +export interface Choice { + index?: number; + delta?: Delta; + message?: Message; + finish_reason?: string; +} + +export interface Message { + content?: string; + reasoning_content?: string; +} + +export interface Delta { + content?: string; + reasoning_content?: string; + tool_calls?: ToolCall[]; +} + +export interface ToolCall { + index?: number; + id?: string; + type?: string; + function?: FunctionCall; +} + + +export interface FunctionCall { + name?: string; + arguments?: string; +} + +export interface Usage { + prompt_tokens?: number; + completion_tokens?: number; + total_tokens?: number; + prompt_tokens_details?: PromptTokensDetails; + completion_tokens_details?: CompletionTokensDetails; + prompt_cache_hit_tokens?: number; + prompt_cache_miss_tokens?: number; +} + +export interface PromptTokensDetails { + cached_tokens?: number; +} + +export interface CompletionTokensDetails { + reasoning_tokens?: number; +} diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..5a0f455 --- /dev/null +++ b/test.ts @@ -0,0 +1,4 @@ +import {sendMessageStream} from "./src/llm.ts"; + +for await (const result of sendMessageStream("查看当前目录下的所有文件列表")) { +}