sparkecoder 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +1308 -212
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +1933 -454
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +20 -3
- package/dist/db/index.js +97 -0
- package/dist/db/index.js.map +1 -1
- package/dist/{index-CNwLFGiZ.d.ts → index-BblbmG_0.d.ts} +19 -4
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1913 -434
- package/dist/index.js.map +1 -1
- package/dist/{schema-Df7MU3nM.d.ts → schema-D_8A4k01.d.ts} +246 -2
- package/dist/search-ybREg7F_.d.ts +254 -0
- package/dist/server/index.js +1918 -439
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +7 -56
- package/dist/tools/index.js +894 -27
- package/dist/tools/index.js.map +1 -1
- package/package.json +5 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/app-path-routes-manifest.json +4 -0
- package/web/.next/standalone/web/.next/build-manifest.json +7 -6
- package/web/.next/standalone/web/.next/prerender-manifest.json +99 -3
- package/web/.next/standalone/web/.next/required-server-files.json +28 -4
- package/web/.next/standalone/web/.next/routes-manifest.json +24 -0
- package/web/.next/standalone/web/.next/server/app/(main)/page/build-manifest.json +5 -4
- package/web/.next/standalone/web/.next/server/app/(main)/page.js +2 -2
- package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page/build-manifest.json +5 -4
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js +2 -2
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error/page/build-manifest.json +5 -4
- package/web/.next/standalone/web/.next/server/app/_global-error/page.js +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page/build-manifest.json +5 -4
- package/web/.next/standalone/web/.next/server/app/_not-found/page.js +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/build-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page.js +21 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +86 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.meta +16 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +36 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +36 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +22 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/build-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/page.js +21 -0
- package/web/.next/standalone/web/.next/server/app/docs/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/build-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page.js +21 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +268 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.meta +16 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +82 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +82 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +66 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/build-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page.js +21 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +242 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.meta +16 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +87 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +87 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +72 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/docs.html +74 -0
- package/web/.next/standalone/web/.next/server/app/docs.meta +15 -0
- package/web/.next/standalone/web/.next/server/app/docs.rsc +34 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +34 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +3 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +20 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app-paths-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5f58fd73._.js → 2374f_244589df._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_84859a94._.js → 2374f_41a27541._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_65fcfd95._.js → 2374f_47c9e2d5._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f1038f7c._.js → 2374f_4bf2df9d._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_663d1038._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_954e49c0._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_387a1437._.js → 2374f_c33b095a._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_cfd0137a._.js → 2374f_fa61fbb2._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_741f6b67._.js → 2374f_fb82ac0d._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_96bca05b._.js → 2374f_next_dist_bbe64674._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__7f04455b._.js → [root-of-the-server]__1e06ddf7._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__2b151e1c._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__2dbf511a._.js +9 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__397fadd4._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__44bd8bd1._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__70cecda8._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9fdf9974._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__f18f92f4._.js → [root-of-the-server]__b050bb8f._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d3034cd2._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__c3a1e22c._.js → [root-of-the-server]__ef2713cf._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__f764bebe._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_046bf7db._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_656c1e45._.js +7 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_c7618534._.js → web_76ccf09f._.js} +4 -4
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_installation_page_actions_52cc0648.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_page_actions_4fe77da8.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_skills_page_actions_251df2e1.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_tools_page_actions_3e6382b0.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_a565dc94._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_b1cce0b7._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_b42ed1be._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_c0c2bee4._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_eea9c122._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_ff00a5c3._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_layout_tsx_453f6492._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +3 -0
- package/web/.next/standalone/web/.next/server/middleware-build-manifest.js +5 -4
- package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/next-font-manifest.json +16 -0
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/0cc382a66266188e.js +7 -0
- package/web/.next/standalone/web/.next/static/chunks/0fda34e553582102.js +1 -0
- package/web/.next/standalone/web/.next/static/{static/chunks/5e5b485d77ac0d8f.js → chunks/6407c045dfc908fe.js} +3 -3
- package/web/.next/standalone/web/.next/static/chunks/651e187cc15d66de.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/862ced58ce21a270.js +4 -0
- package/web/.next/standalone/web/.next/static/chunks/89bc21c0443670f4.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/8f4edf22ededc29b.js +7 -0
- package/web/.next/standalone/web/.next/static/chunks/ad6b9dbb257d62cc.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/af22745850132107.css +1 -0
- package/web/.next/standalone/web/.next/static/chunks/b9ad1584d4e11d12.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/db9b22c844a35e20.js +5 -0
- package/web/.next/standalone/web/.next/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
- package/web/.next/standalone/web/.next/static/static/chunks/0cc382a66266188e.js +7 -0
- package/web/.next/standalone/web/.next/static/static/chunks/0fda34e553582102.js +1 -0
- package/web/.next/{static/chunks/5e5b485d77ac0d8f.js → standalone/web/.next/static/static/chunks/6407c045dfc908fe.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/651e187cc15d66de.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/862ced58ce21a270.js +4 -0
- package/web/.next/standalone/web/.next/static/static/chunks/89bc21c0443670f4.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/8f4edf22ededc29b.js +7 -0
- package/web/.next/standalone/web/.next/static/static/chunks/ad6b9dbb257d62cc.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/af22745850132107.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/b9ad1584d4e11d12.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/db9b22c844a35e20.js +5 -0
- package/web/.next/standalone/web/.next/static/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
- package/web/.next/standalone/web/mdx-components.tsx +119 -0
- package/web/.next/standalone/web/next.config.ts +15 -1
- package/web/.next/standalone/web/package-lock.json +559 -4
- package/web/.next/standalone/web/package.json +4 -0
- package/web/.next/standalone/web/runtime-config.json +1 -1
- package/web/.next/standalone/web/server.js +1 -1
- package/web/.next/standalone/web/src/app/(main)/page.tsx +127 -5
- package/web/.next/standalone/web/src/app/docs/installation/page.mdx +128 -0
- package/web/.next/standalone/web/src/app/docs/layout.tsx +74 -0
- package/web/.next/standalone/web/src/app/docs/page.mdx +90 -0
- package/web/.next/standalone/web/src/app/docs/skills/page.mdx +334 -0
- package/web/.next/standalone/web/src/app/docs/tools/page.mdx +300 -0
- package/web/.next/standalone/web/src/components/ai-elements/mention-input.tsx +809 -0
- package/web/.next/standalone/web/src/components/ai-elements/search-tool.tsx +400 -0
- package/web/.next/standalone/web/src/components/ai-elements/subagent-modal.tsx +275 -0
- package/web/.next/standalone/web/src/components/ai-elements/write-file-tool.tsx +19 -5
- package/web/.next/standalone/web/src/components/chat-interface.tsx +525 -71
- package/web/.next/standalone/web/src/hooks/use-workspace-files.ts +108 -0
- package/web/.next/standalone/web/src/lib/api.ts +90 -4
- package/web/.next/static/chunks/0cc382a66266188e.js +7 -0
- package/web/.next/static/chunks/0fda34e553582102.js +1 -0
- package/web/.next/{standalone/web/.next/static/chunks/5e5b485d77ac0d8f.js → static/chunks/6407c045dfc908fe.js} +3 -3
- package/web/.next/static/chunks/651e187cc15d66de.js +1 -0
- package/web/.next/static/chunks/862ced58ce21a270.js +4 -0
- package/web/.next/static/chunks/89bc21c0443670f4.js +1 -0
- package/web/.next/static/chunks/8f4edf22ededc29b.js +7 -0
- package/web/.next/static/chunks/ad6b9dbb257d62cc.js +1 -0
- package/web/.next/static/chunks/af22745850132107.css +1 -0
- package/web/.next/static/chunks/b9ad1584d4e11d12.js +1 -0
- package/web/.next/static/chunks/db9b22c844a35e20.js +5 -0
- package/web/.next/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
- package/web/package.json +4 -0
- package/dist/bash-CGAqW7HR.d.ts +0 -80
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_814be2c9._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__0f6b5fa7._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__3ec22171._.js +0 -9
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__513c6b45._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de58a952._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_d7d3e40d._.js +0 -7
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_e6034803._.js +0 -3
- package/web/.next/standalone/web/.next/static/chunks/03d4169891280e04.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/2d5da0cfc011b8d9.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/3bb454ca848ec78e.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/634fd97fab9ed4e4.js +0 -4
- package/web/.next/standalone/web/.next/static/chunks/77e4bf0421481629.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/beb9625c4a470042.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/c2244168e74b8c78.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/c81c1aec4369c77f.js +0 -5
- package/web/.next/standalone/web/.next/static/chunks/cb355fac10c6ad11.css +0 -1
- package/web/.next/standalone/web/.next/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
- package/web/.next/standalone/web/.next/static/static/chunks/03d4169891280e04.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/2d5da0cfc011b8d9.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/3bb454ca848ec78e.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/634fd97fab9ed4e4.js +0 -4
- package/web/.next/standalone/web/.next/static/static/chunks/77e4bf0421481629.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/beb9625c4a470042.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/c2244168e74b8c78.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/c81c1aec4369c77f.js +0 -5
- package/web/.next/standalone/web/.next/static/static/chunks/cb355fac10c6ad11.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
- package/web/.next/static/chunks/03d4169891280e04.js +0 -7
- package/web/.next/static/chunks/2d5da0cfc011b8d9.js +0 -1
- package/web/.next/static/chunks/3bb454ca848ec78e.js +0 -7
- package/web/.next/static/chunks/634fd97fab9ed4e4.js +0 -4
- package/web/.next/static/chunks/77e4bf0421481629.js +0 -1
- package/web/.next/static/chunks/beb9625c4a470042.js +0 -1
- package/web/.next/static/chunks/c2244168e74b8c78.js +0 -1
- package/web/.next/static/chunks/c81c1aec4369c77f.js +0 -5
- package/web/.next/static/chunks/cb355fac10c6ad11.css +0 -1
- package/web/.next/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
- /package/web/.next/standalone/web/.next/static/{n86r6x1RoUipFp6nLIk-R → static/uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{n86r6x1RoUipFp6nLIk-R → static/uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{n86r6x1RoUipFp6nLIk-R → static/uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
- /package/web/.next/static/{n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
- /package/web/.next/static/{n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useState,
|
|
5
|
+
useRef,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
type ChangeEvent,
|
|
10
|
+
createContext,
|
|
11
|
+
useContext,
|
|
12
|
+
useMemo,
|
|
13
|
+
type ReactNode,
|
|
14
|
+
type ComponentProps,
|
|
15
|
+
type ClipboardEventHandler,
|
|
16
|
+
} from "react";
|
|
17
|
+
import { cn } from "@/lib/utils";
|
|
18
|
+
import {
|
|
19
|
+
Command,
|
|
20
|
+
CommandEmpty,
|
|
21
|
+
CommandGroup,
|
|
22
|
+
CommandItem,
|
|
23
|
+
CommandList,
|
|
24
|
+
} from "@/components/ui/command";
|
|
25
|
+
import {
|
|
26
|
+
Popover,
|
|
27
|
+
PopoverContent,
|
|
28
|
+
PopoverAnchor,
|
|
29
|
+
} from "@/components/ui/popover";
|
|
30
|
+
import { useWorkspaceFiles } from "@/hooks/use-workspace-files";
|
|
31
|
+
import { usePromptInputAttachments } from "@/components/ai-elements/prompt-input";
|
|
32
|
+
import {
|
|
33
|
+
FileIcon,
|
|
34
|
+
FolderIcon,
|
|
35
|
+
HashIcon,
|
|
36
|
+
HelpCircleIcon,
|
|
37
|
+
TerminalIcon,
|
|
38
|
+
} from "lucide-react";
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Types
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
export interface FileMention {
|
|
45
|
+
id: string;
|
|
46
|
+
type: "file" | "folder";
|
|
47
|
+
path: string;
|
|
48
|
+
name: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface SlashCommand {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
description: string;
|
|
55
|
+
icon?: ReactNode;
|
|
56
|
+
/** Text to insert when this command is selected */
|
|
57
|
+
prompt?: string;
|
|
58
|
+
/** Optional action to run (for special commands like clear) */
|
|
59
|
+
action?: () => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface TriggerState {
|
|
63
|
+
type: "file" | "command";
|
|
64
|
+
query: string;
|
|
65
|
+
startIndex: number; // Where the trigger character is
|
|
66
|
+
endIndex: number; // Current cursor position
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Context - tracks mentions found in the text (for extraction on submit)
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
interface MentionInputContextValue {
|
|
74
|
+
/** Extract mentions from text (parses @path patterns) */
|
|
75
|
+
extractMentions: (text: string) => FileMention[];
|
|
76
|
+
/** Clear any internal state */
|
|
77
|
+
clear: () => void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const MentionInputContext = createContext<MentionInputContextValue | null>(null);
|
|
81
|
+
|
|
82
|
+
export function useMentionInput() {
|
|
83
|
+
const ctx = useContext(MentionInputContext);
|
|
84
|
+
if (!ctx) {
|
|
85
|
+
throw new Error("useMentionInput must be used within MentionInputProvider");
|
|
86
|
+
}
|
|
87
|
+
return ctx;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Regex to match @mentions in text (matches @path/to/file or @folder/)
|
|
91
|
+
const MENTION_REGEX = /@([\w./-]+\/?)/g;
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Default Slash Commands - Each inserts a prompt template
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
export const DEFAULT_SLASH_COMMANDS: SlashCommand[] = [
|
|
98
|
+
{
|
|
99
|
+
id: "help",
|
|
100
|
+
name: "help",
|
|
101
|
+
description: "Ask about available features",
|
|
102
|
+
icon: <HelpCircleIcon className="size-4" />,
|
|
103
|
+
prompt: "What commands and features are available? How can you help me?",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "terminal",
|
|
107
|
+
name: "terminal",
|
|
108
|
+
description: "Open a new terminal",
|
|
109
|
+
icon: <TerminalIcon className="size-4" />,
|
|
110
|
+
prompt: "Please open a new terminal session for me.",
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "explain",
|
|
114
|
+
name: "explain",
|
|
115
|
+
description: "Explain a file or code",
|
|
116
|
+
icon: <FileIcon className="size-4" />,
|
|
117
|
+
prompt: "Please explain this code: ",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: "fix",
|
|
121
|
+
name: "fix",
|
|
122
|
+
description: "Fix a bug or issue",
|
|
123
|
+
icon: <HashIcon className="size-4" />,
|
|
124
|
+
prompt: "Please help me fix this issue: ",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "refactor",
|
|
128
|
+
name: "refactor",
|
|
129
|
+
description: "Refactor code",
|
|
130
|
+
icon: <HashIcon className="size-4" />,
|
|
131
|
+
prompt: "Please refactor this code to be cleaner and more maintainable: ",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "test",
|
|
135
|
+
name: "test",
|
|
136
|
+
description: "Write tests",
|
|
137
|
+
icon: <HashIcon className="size-4" />,
|
|
138
|
+
prompt: "Please write tests for: ",
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// Provider - Simplified to extract mentions from text on demand
|
|
144
|
+
// ============================================================================
|
|
145
|
+
|
|
146
|
+
interface MentionInputProviderProps {
|
|
147
|
+
children: ReactNode;
|
|
148
|
+
onChange?: (mentions: FileMention[]) => void;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function MentionInputProvider({
|
|
152
|
+
children,
|
|
153
|
+
}: MentionInputProviderProps) {
|
|
154
|
+
// Extract mentions from text by parsing @path patterns
|
|
155
|
+
const extractMentions = useCallback((text: string): FileMention[] => {
|
|
156
|
+
const mentions: FileMention[] = [];
|
|
157
|
+
const regex = new RegExp(MENTION_REGEX.source, 'g');
|
|
158
|
+
let match;
|
|
159
|
+
|
|
160
|
+
while ((match = regex.exec(text)) !== null) {
|
|
161
|
+
const path = match[1];
|
|
162
|
+
const isFolder = path.endsWith('/');
|
|
163
|
+
mentions.push({
|
|
164
|
+
id: `mention-${match.index}`,
|
|
165
|
+
type: isFolder ? 'folder' : 'file',
|
|
166
|
+
path: isFolder ? path.slice(0, -1) : path,
|
|
167
|
+
name: path.split('/').pop()?.replace(/\/$/, '') || path,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return mentions;
|
|
172
|
+
}, []);
|
|
173
|
+
|
|
174
|
+
const clear = useCallback(() => {
|
|
175
|
+
// No-op for now, mentions are extracted from text
|
|
176
|
+
}, []);
|
|
177
|
+
|
|
178
|
+
const value = useMemo(
|
|
179
|
+
() => ({
|
|
180
|
+
extractMentions,
|
|
181
|
+
clear,
|
|
182
|
+
}),
|
|
183
|
+
[extractMentions, clear]
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<MentionInputContext.Provider value={value}>
|
|
188
|
+
{children}
|
|
189
|
+
</MentionInputContext.Provider>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ============================================================================
|
|
194
|
+
// Highlight Backdrop - renders behind textarea to show styled mentions
|
|
195
|
+
// ============================================================================
|
|
196
|
+
|
|
197
|
+
interface HighlightBackdropProps {
|
|
198
|
+
value: string;
|
|
199
|
+
className?: string;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function HighlightBackdrop({ value, className }: HighlightBackdropProps) {
|
|
203
|
+
// Parse text and highlight @mentions
|
|
204
|
+
const renderHighlightedText = () => {
|
|
205
|
+
const parts: ReactNode[] = [];
|
|
206
|
+
const regex = new RegExp(MENTION_REGEX.source, 'g');
|
|
207
|
+
let lastIndex = 0;
|
|
208
|
+
let match;
|
|
209
|
+
|
|
210
|
+
while ((match = regex.exec(value)) !== null) {
|
|
211
|
+
// Add text before the match
|
|
212
|
+
if (match.index > lastIndex) {
|
|
213
|
+
parts.push(
|
|
214
|
+
<span key={`text-${lastIndex}`}>
|
|
215
|
+
{value.slice(lastIndex, match.index)}
|
|
216
|
+
</span>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Add the highlighted mention
|
|
221
|
+
parts.push(
|
|
222
|
+
<span
|
|
223
|
+
key={`mention-${match.index}`}
|
|
224
|
+
className="bg-primary/20 text-primary rounded px-0.5 -mx-0.5"
|
|
225
|
+
>
|
|
226
|
+
{match[0]}
|
|
227
|
+
</span>
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
lastIndex = match.index + match[0].length;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Add remaining text
|
|
234
|
+
if (lastIndex < value.length) {
|
|
235
|
+
parts.push(
|
|
236
|
+
<span key={`text-${lastIndex}`}>
|
|
237
|
+
{value.slice(lastIndex)}
|
|
238
|
+
</span>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Add a trailing space to match textarea behavior
|
|
243
|
+
parts.push(<span key="trailing"> </span>);
|
|
244
|
+
|
|
245
|
+
return parts;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<div
|
|
250
|
+
className={cn(
|
|
251
|
+
"absolute inset-0 pointer-events-none whitespace-pre-wrap break-words overflow-hidden",
|
|
252
|
+
"px-3 py-2 text-sm text-transparent",
|
|
253
|
+
className
|
|
254
|
+
)}
|
|
255
|
+
aria-hidden="true"
|
|
256
|
+
>
|
|
257
|
+
{renderHighlightedText()}
|
|
258
|
+
</div>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Legacy export for backwards compatibility - no longer shows chips
|
|
263
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
264
|
+
export function MentionsDisplay(_props: { className?: string }) {
|
|
265
|
+
// Mentions are now shown inline, this component is no longer needed
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ============================================================================
|
|
270
|
+
// Mention Textarea with Popover
|
|
271
|
+
// ============================================================================
|
|
272
|
+
|
|
273
|
+
interface MentionTextareaProps
|
|
274
|
+
extends Omit<ComponentProps<"textarea">, "onChange"> {
|
|
275
|
+
sessionId: string;
|
|
276
|
+
value: string;
|
|
277
|
+
onChange: (value: string) => void;
|
|
278
|
+
onSlashCommand?: (command: SlashCommand) => void;
|
|
279
|
+
slashCommands?: SlashCommand[];
|
|
280
|
+
/** Show inline highlighting for @mentions (default: true) */
|
|
281
|
+
showHighlights?: boolean;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function MentionTextarea({
|
|
285
|
+
sessionId,
|
|
286
|
+
value,
|
|
287
|
+
onChange,
|
|
288
|
+
onSlashCommand,
|
|
289
|
+
slashCommands = DEFAULT_SLASH_COMMANDS,
|
|
290
|
+
showHighlights = true,
|
|
291
|
+
className,
|
|
292
|
+
onKeyDown,
|
|
293
|
+
disabled,
|
|
294
|
+
...props
|
|
295
|
+
}: MentionTextareaProps) {
|
|
296
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
297
|
+
const backdropRef = useRef<HTMLDivElement>(null);
|
|
298
|
+
const [trigger, setTrigger] = useState<TriggerState | null>(null);
|
|
299
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
300
|
+
const [popoverPosition, setPopoverPosition] = useState({ top: 0, left: 0 });
|
|
301
|
+
const [isComposing, setIsComposing] = useState(false); // For IME input
|
|
302
|
+
|
|
303
|
+
// Get attachments context for paste handling
|
|
304
|
+
const attachments = usePromptInputAttachments();
|
|
305
|
+
|
|
306
|
+
// Fetch workspace files when @ is triggered
|
|
307
|
+
const { files, isLoading } = useWorkspaceFiles({
|
|
308
|
+
sessionId,
|
|
309
|
+
query: trigger?.type === "file" ? trigger.query : "",
|
|
310
|
+
enabled: trigger?.type === "file",
|
|
311
|
+
limit: 15,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Filter slash commands based on query
|
|
315
|
+
const filteredCommands = useMemo(() => {
|
|
316
|
+
if (trigger?.type !== "command") return [];
|
|
317
|
+
const query = trigger.query.toLowerCase();
|
|
318
|
+
return slashCommands.filter(
|
|
319
|
+
(cmd) =>
|
|
320
|
+
cmd.name.toLowerCase().includes(query) ||
|
|
321
|
+
cmd.description.toLowerCase().includes(query)
|
|
322
|
+
);
|
|
323
|
+
}, [trigger, slashCommands]);
|
|
324
|
+
|
|
325
|
+
// Current items to display
|
|
326
|
+
const items = trigger?.type === "file" ? files : filteredCommands;
|
|
327
|
+
const showPopover = trigger !== null && (items.length > 0 || isLoading);
|
|
328
|
+
|
|
329
|
+
// Calculate popover position based on textarea caret
|
|
330
|
+
const updatePopoverPosition = useCallback(() => {
|
|
331
|
+
const textarea = textareaRef.current;
|
|
332
|
+
if (!textarea || !trigger) return;
|
|
333
|
+
|
|
334
|
+
// Create a hidden div to measure text up to caret
|
|
335
|
+
const mirror = document.createElement("div");
|
|
336
|
+
const style = window.getComputedStyle(textarea);
|
|
337
|
+
|
|
338
|
+
// Copy relevant styles
|
|
339
|
+
const stylesToCopy = [
|
|
340
|
+
"fontFamily",
|
|
341
|
+
"fontSize",
|
|
342
|
+
"fontWeight",
|
|
343
|
+
"lineHeight",
|
|
344
|
+
"padding",
|
|
345
|
+
"paddingLeft",
|
|
346
|
+
"paddingTop",
|
|
347
|
+
"border",
|
|
348
|
+
"borderWidth",
|
|
349
|
+
"boxSizing",
|
|
350
|
+
"whiteSpace",
|
|
351
|
+
"wordWrap",
|
|
352
|
+
"wordBreak",
|
|
353
|
+
];
|
|
354
|
+
|
|
355
|
+
stylesToCopy.forEach((prop) => {
|
|
356
|
+
// @ts-expect-error - style props
|
|
357
|
+
mirror.style[prop] = style[prop];
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
mirror.style.position = "absolute";
|
|
361
|
+
mirror.style.visibility = "hidden";
|
|
362
|
+
mirror.style.height = "auto";
|
|
363
|
+
mirror.style.width = `${textarea.clientWidth}px`;
|
|
364
|
+
mirror.style.overflow = "hidden";
|
|
365
|
+
|
|
366
|
+
// Get text up to trigger position
|
|
367
|
+
const textBeforeTrigger = value.substring(0, trigger.startIndex);
|
|
368
|
+
mirror.textContent = textBeforeTrigger;
|
|
369
|
+
|
|
370
|
+
// Add a span for the trigger character to measure position
|
|
371
|
+
const marker = document.createElement("span");
|
|
372
|
+
marker.textContent = "|";
|
|
373
|
+
mirror.appendChild(marker);
|
|
374
|
+
|
|
375
|
+
document.body.appendChild(mirror);
|
|
376
|
+
|
|
377
|
+
// Get position relative to textarea
|
|
378
|
+
const textareaRect = textarea.getBoundingClientRect();
|
|
379
|
+
const markerRect = marker.getBoundingClientRect();
|
|
380
|
+
const mirrorRect = mirror.getBoundingClientRect();
|
|
381
|
+
|
|
382
|
+
// Calculate offset from textarea
|
|
383
|
+
const left = markerRect.left - mirrorRect.left;
|
|
384
|
+
const top = markerRect.top - mirrorRect.top + parseInt(style.lineHeight);
|
|
385
|
+
|
|
386
|
+
// Adjust for scroll
|
|
387
|
+
const scrollTop = textarea.scrollTop;
|
|
388
|
+
|
|
389
|
+
document.body.removeChild(mirror);
|
|
390
|
+
|
|
391
|
+
setPopoverPosition({
|
|
392
|
+
left: Math.min(left, textareaRect.width - 250), // Keep popover in view
|
|
393
|
+
top: top - scrollTop + 4,
|
|
394
|
+
});
|
|
395
|
+
}, [trigger, value]);
|
|
396
|
+
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
updatePopoverPosition();
|
|
399
|
+
}, [updatePopoverPosition]);
|
|
400
|
+
|
|
401
|
+
// Detect trigger characters
|
|
402
|
+
const detectTrigger = useCallback(
|
|
403
|
+
(text: string, cursorPos: number) => {
|
|
404
|
+
if (cursorPos === 0) {
|
|
405
|
+
setTrigger(null);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const beforeCursor = text.slice(0, cursorPos);
|
|
410
|
+
|
|
411
|
+
// Check for @ trigger (file mention)
|
|
412
|
+
const atMatch = beforeCursor.match(/@([^\s@]*)$/);
|
|
413
|
+
if (atMatch) {
|
|
414
|
+
setTrigger({
|
|
415
|
+
type: "file",
|
|
416
|
+
query: atMatch[1],
|
|
417
|
+
startIndex: cursorPos - atMatch[0].length,
|
|
418
|
+
endIndex: cursorPos,
|
|
419
|
+
});
|
|
420
|
+
setSelectedIndex(0);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Check for / trigger at start of line or after whitespace (slash command)
|
|
425
|
+
const slashMatch = beforeCursor.match(/(?:^|\s)\/([^\s]*)$/);
|
|
426
|
+
if (slashMatch) {
|
|
427
|
+
setTrigger({
|
|
428
|
+
type: "command",
|
|
429
|
+
query: slashMatch[1],
|
|
430
|
+
startIndex: cursorPos - slashMatch[1].length - 1,
|
|
431
|
+
endIndex: cursorPos,
|
|
432
|
+
});
|
|
433
|
+
setSelectedIndex(0);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
setTrigger(null);
|
|
438
|
+
},
|
|
439
|
+
[]
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
// Handle input change
|
|
443
|
+
const handleChange = useCallback(
|
|
444
|
+
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
445
|
+
const newValue = e.target.value;
|
|
446
|
+
const cursorPos = e.target.selectionStart ?? 0;
|
|
447
|
+
|
|
448
|
+
onChange(newValue);
|
|
449
|
+
detectTrigger(newValue, cursorPos);
|
|
450
|
+
},
|
|
451
|
+
[onChange, detectTrigger]
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
// Handle selection from popover
|
|
455
|
+
const handleSelect = useCallback(
|
|
456
|
+
(item: (typeof files)[0] | SlashCommand) => {
|
|
457
|
+
if (!trigger || !textareaRef.current) return;
|
|
458
|
+
|
|
459
|
+
if (trigger.type === "file" && "path" in item) {
|
|
460
|
+
// Insert @path inline (replacing @query with @fullpath)
|
|
461
|
+
const before = value.slice(0, trigger.startIndex);
|
|
462
|
+
const after = value.slice(trigger.endIndex);
|
|
463
|
+
// Add trailing slash for folders, space after for usability
|
|
464
|
+
const mentionText = `@${item.path}${item.type === 'folder' ? '/' : ''} `;
|
|
465
|
+
const newValue = before + mentionText + after;
|
|
466
|
+
|
|
467
|
+
onChange(newValue);
|
|
468
|
+
|
|
469
|
+
// Set cursor after the inserted mention
|
|
470
|
+
setTimeout(() => {
|
|
471
|
+
if (textareaRef.current) {
|
|
472
|
+
const newPos = trigger.startIndex + mentionText.length;
|
|
473
|
+
textareaRef.current.selectionStart = newPos;
|
|
474
|
+
textareaRef.current.selectionEnd = newPos;
|
|
475
|
+
textareaRef.current.focus();
|
|
476
|
+
}
|
|
477
|
+
}, 0);
|
|
478
|
+
} else if (trigger.type === "command" && "description" in item) {
|
|
479
|
+
const cmd = item as SlashCommand;
|
|
480
|
+
|
|
481
|
+
// If command has an action, call it (for special commands)
|
|
482
|
+
if (cmd.action) {
|
|
483
|
+
cmd.action();
|
|
484
|
+
onSlashCommand?.(cmd);
|
|
485
|
+
|
|
486
|
+
// Remove the /command from input
|
|
487
|
+
const before = value.slice(0, trigger.startIndex);
|
|
488
|
+
const after = value.slice(trigger.endIndex);
|
|
489
|
+
onChange((before + after).trim());
|
|
490
|
+
} else if (cmd.prompt) {
|
|
491
|
+
// Insert the prompt text (replacing /command with the prompt)
|
|
492
|
+
const before = value.slice(0, trigger.startIndex);
|
|
493
|
+
const after = value.slice(trigger.endIndex);
|
|
494
|
+
const newValue = before + cmd.prompt + after;
|
|
495
|
+
|
|
496
|
+
onChange(newValue);
|
|
497
|
+
|
|
498
|
+
// Set cursor at end of inserted prompt
|
|
499
|
+
setTimeout(() => {
|
|
500
|
+
if (textareaRef.current) {
|
|
501
|
+
const newPos = trigger.startIndex + cmd.prompt!.length;
|
|
502
|
+
textareaRef.current.selectionStart = newPos;
|
|
503
|
+
textareaRef.current.selectionEnd = newPos;
|
|
504
|
+
textareaRef.current.focus();
|
|
505
|
+
}
|
|
506
|
+
}, 0);
|
|
507
|
+
} else {
|
|
508
|
+
// Fallback: just notify via callback
|
|
509
|
+
onSlashCommand?.(cmd);
|
|
510
|
+
|
|
511
|
+
// Remove the /command from input
|
|
512
|
+
const before = value.slice(0, trigger.startIndex);
|
|
513
|
+
const after = value.slice(trigger.endIndex);
|
|
514
|
+
onChange((before + after).trim());
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
setTimeout(() => {
|
|
518
|
+
textareaRef.current?.focus();
|
|
519
|
+
}, 0);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
setTrigger(null);
|
|
523
|
+
},
|
|
524
|
+
[trigger, value, onChange, onSlashCommand]
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
// Handle keyboard navigation and form submission
|
|
528
|
+
const handleKeyDown = useCallback(
|
|
529
|
+
(e: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
530
|
+
// Call external handler first
|
|
531
|
+
onKeyDown?.(e);
|
|
532
|
+
if (e.defaultPrevented) return;
|
|
533
|
+
|
|
534
|
+
// When popover is showing, handle navigation
|
|
535
|
+
if (showPopover) {
|
|
536
|
+
switch (e.key) {
|
|
537
|
+
case "ArrowDown":
|
|
538
|
+
e.preventDefault();
|
|
539
|
+
setSelectedIndex((prev) => (prev + 1) % items.length);
|
|
540
|
+
return;
|
|
541
|
+
case "ArrowUp":
|
|
542
|
+
e.preventDefault();
|
|
543
|
+
setSelectedIndex((prev) => (prev - 1 + items.length) % items.length);
|
|
544
|
+
return;
|
|
545
|
+
case "Tab":
|
|
546
|
+
case "Enter":
|
|
547
|
+
if (items.length > 0) {
|
|
548
|
+
e.preventDefault();
|
|
549
|
+
handleSelect(items[selectedIndex]);
|
|
550
|
+
}
|
|
551
|
+
return;
|
|
552
|
+
case "Escape":
|
|
553
|
+
e.preventDefault();
|
|
554
|
+
setTrigger(null);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// When popover is NOT showing, handle Enter to submit form
|
|
560
|
+
if (e.key === "Enter") {
|
|
561
|
+
// Don't submit during IME composition
|
|
562
|
+
if (isComposing || e.nativeEvent.isComposing) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
// Shift+Enter for newline
|
|
566
|
+
if (e.shiftKey) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
e.preventDefault();
|
|
571
|
+
|
|
572
|
+
// Find the form - use closest() as fallback if .form is null
|
|
573
|
+
const textarea = e.currentTarget;
|
|
574
|
+
const form = textarea.form || textarea.closest('form') as HTMLFormElement | null;
|
|
575
|
+
|
|
576
|
+
if (form) {
|
|
577
|
+
// Find and check submit button
|
|
578
|
+
const submitButton = form.querySelector(
|
|
579
|
+
'button[type="submit"]'
|
|
580
|
+
) as HTMLButtonElement | null;
|
|
581
|
+
|
|
582
|
+
if (submitButton?.disabled) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Click the submit button directly (more reliable than requestSubmit)
|
|
587
|
+
if (submitButton) {
|
|
588
|
+
submitButton.click();
|
|
589
|
+
} else {
|
|
590
|
+
// Fallback to requestSubmit
|
|
591
|
+
form.requestSubmit();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// Backspace to remove last attachment when textarea is empty
|
|
596
|
+
if (e.key === "Backspace" && value === "" && attachments.files.length > 0) {
|
|
597
|
+
e.preventDefault();
|
|
598
|
+
const lastAttachment = attachments.files.at(-1);
|
|
599
|
+
if (lastAttachment) {
|
|
600
|
+
attachments.remove(lastAttachment.id);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
[showPopover, items, selectedIndex, handleSelect, onKeyDown, isComposing, value, attachments]
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
// Handle paste for file attachments
|
|
608
|
+
const handlePaste: ClipboardEventHandler<HTMLTextAreaElement> = useCallback((event) => {
|
|
609
|
+
const items = event.clipboardData?.items;
|
|
610
|
+
if (!items) return;
|
|
611
|
+
|
|
612
|
+
const files: File[] = [];
|
|
613
|
+
for (const item of items) {
|
|
614
|
+
if (item.kind === "file") {
|
|
615
|
+
const file = item.getAsFile();
|
|
616
|
+
if (file) files.push(file);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (files.length > 0) {
|
|
621
|
+
event.preventDefault();
|
|
622
|
+
attachments.add(files);
|
|
623
|
+
}
|
|
624
|
+
}, [attachments]);
|
|
625
|
+
|
|
626
|
+
// Handle click to update cursor position
|
|
627
|
+
const handleClick = useCallback(() => {
|
|
628
|
+
const textarea = textareaRef.current;
|
|
629
|
+
if (!textarea) return;
|
|
630
|
+
detectTrigger(value, textarea.selectionStart ?? 0);
|
|
631
|
+
}, [value, detectTrigger]);
|
|
632
|
+
|
|
633
|
+
// Sync backdrop scroll with textarea
|
|
634
|
+
const handleScroll = useCallback(() => {
|
|
635
|
+
if (textareaRef.current && backdropRef.current) {
|
|
636
|
+
backdropRef.current.scrollTop = textareaRef.current.scrollTop;
|
|
637
|
+
}
|
|
638
|
+
}, []);
|
|
639
|
+
|
|
640
|
+
// Parse text and render with highlighted mentions
|
|
641
|
+
const renderHighlightedText = () => {
|
|
642
|
+
const parts: ReactNode[] = [];
|
|
643
|
+
const regex = new RegExp(MENTION_REGEX.source, 'g');
|
|
644
|
+
let lastIndex = 0;
|
|
645
|
+
let match;
|
|
646
|
+
|
|
647
|
+
while ((match = regex.exec(value)) !== null) {
|
|
648
|
+
// Add text before the match
|
|
649
|
+
if (match.index > lastIndex) {
|
|
650
|
+
parts.push(
|
|
651
|
+
<span key={`text-${lastIndex}`}>
|
|
652
|
+
{value.slice(lastIndex, match.index)}
|
|
653
|
+
</span>
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Add the highlighted mention
|
|
658
|
+
parts.push(
|
|
659
|
+
<mark
|
|
660
|
+
key={`mention-${match.index}`}
|
|
661
|
+
className="bg-primary/20 text-transparent rounded-sm"
|
|
662
|
+
>
|
|
663
|
+
{match[0]}
|
|
664
|
+
</mark>
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
lastIndex = match.index + match[0].length;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Add remaining text
|
|
671
|
+
if (lastIndex < value.length) {
|
|
672
|
+
parts.push(
|
|
673
|
+
<span key={`text-${lastIndex}`}>
|
|
674
|
+
{value.slice(lastIndex)}
|
|
675
|
+
</span>
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Add trailing character to match textarea sizing
|
|
680
|
+
parts.push(<span key="trailing">{'\n'}</span>);
|
|
681
|
+
|
|
682
|
+
return parts;
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
return (
|
|
686
|
+
<div className="relative w-full">
|
|
687
|
+
<Popover open={showPopover} onOpenChange={() => setTrigger(null)}>
|
|
688
|
+
<PopoverAnchor
|
|
689
|
+
style={{
|
|
690
|
+
position: "absolute",
|
|
691
|
+
left: popoverPosition.left,
|
|
692
|
+
top: popoverPosition.top,
|
|
693
|
+
width: 0,
|
|
694
|
+
height: 0,
|
|
695
|
+
}}
|
|
696
|
+
/>
|
|
697
|
+
<PopoverContent
|
|
698
|
+
align="start"
|
|
699
|
+
side="top"
|
|
700
|
+
className="w-64 p-0"
|
|
701
|
+
onOpenAutoFocus={(e) => e.preventDefault()}
|
|
702
|
+
>
|
|
703
|
+
<Command>
|
|
704
|
+
<CommandList>
|
|
705
|
+
{isLoading && trigger?.type === "file" && (
|
|
706
|
+
<div className="p-2 text-sm text-muted-foreground text-center">
|
|
707
|
+
Loading...
|
|
708
|
+
</div>
|
|
709
|
+
)}
|
|
710
|
+
{!isLoading && items.length === 0 && (
|
|
711
|
+
<CommandEmpty>
|
|
712
|
+
{trigger?.type === "file"
|
|
713
|
+
? "No files found"
|
|
714
|
+
: "No commands found"}
|
|
715
|
+
</CommandEmpty>
|
|
716
|
+
)}
|
|
717
|
+
{trigger?.type === "file" && files.length > 0 && (
|
|
718
|
+
<CommandGroup heading="Files & Folders">
|
|
719
|
+
{files.map((file, index) => (
|
|
720
|
+
<CommandItem
|
|
721
|
+
key={file.path}
|
|
722
|
+
onSelect={() => handleSelect(file)}
|
|
723
|
+
className={cn(
|
|
724
|
+
"cursor-pointer",
|
|
725
|
+
index === selectedIndex && "bg-accent"
|
|
726
|
+
)}
|
|
727
|
+
>
|
|
728
|
+
{file.type === "folder" ? (
|
|
729
|
+
<FolderIcon className="mr-2 size-4 text-blue-500" />
|
|
730
|
+
) : (
|
|
731
|
+
<FileIcon className="mr-2 size-4 text-muted-foreground" />
|
|
732
|
+
)}
|
|
733
|
+
<span className="truncate">{file.path}</span>
|
|
734
|
+
</CommandItem>
|
|
735
|
+
))}
|
|
736
|
+
</CommandGroup>
|
|
737
|
+
)}
|
|
738
|
+
{trigger?.type === "command" && filteredCommands.length > 0 && (
|
|
739
|
+
<CommandGroup heading="Commands">
|
|
740
|
+
{filteredCommands.map((cmd, index) => (
|
|
741
|
+
<CommandItem
|
|
742
|
+
key={cmd.id}
|
|
743
|
+
onSelect={() => handleSelect(cmd)}
|
|
744
|
+
className={cn(
|
|
745
|
+
"cursor-pointer",
|
|
746
|
+
index === selectedIndex && "bg-accent"
|
|
747
|
+
)}
|
|
748
|
+
>
|
|
749
|
+
{cmd.icon || <HashIcon className="mr-2 size-4" />}
|
|
750
|
+
<div className="flex flex-col ml-2">
|
|
751
|
+
<span className="font-medium">/{cmd.name}</span>
|
|
752
|
+
<span className="text-xs text-muted-foreground">
|
|
753
|
+
{cmd.description}
|
|
754
|
+
</span>
|
|
755
|
+
</div>
|
|
756
|
+
</CommandItem>
|
|
757
|
+
))}
|
|
758
|
+
</CommandGroup>
|
|
759
|
+
)}
|
|
760
|
+
</CommandList>
|
|
761
|
+
</Command>
|
|
762
|
+
</PopoverContent>
|
|
763
|
+
</Popover>
|
|
764
|
+
|
|
765
|
+
{/* Highlight backdrop - renders behind textarea to show colored mentions */}
|
|
766
|
+
{showHighlights && (
|
|
767
|
+
<div
|
|
768
|
+
ref={backdropRef}
|
|
769
|
+
aria-hidden="true"
|
|
770
|
+
className="absolute inset-0 pointer-events-none overflow-hidden whitespace-pre-wrap break-words px-3 py-3 text-sm"
|
|
771
|
+
style={{ color: 'transparent' }}
|
|
772
|
+
>
|
|
773
|
+
{renderHighlightedText()}
|
|
774
|
+
</div>
|
|
775
|
+
)}
|
|
776
|
+
|
|
777
|
+
<textarea
|
|
778
|
+
ref={textareaRef}
|
|
779
|
+
name="message"
|
|
780
|
+
data-slot="input-group-control"
|
|
781
|
+
value={value}
|
|
782
|
+
onChange={handleChange}
|
|
783
|
+
onKeyDown={handleKeyDown}
|
|
784
|
+
onClick={handleClick}
|
|
785
|
+
onScroll={handleScroll}
|
|
786
|
+
onPaste={handlePaste}
|
|
787
|
+
onCompositionStart={() => setIsComposing(true)}
|
|
788
|
+
onCompositionEnd={() => setIsComposing(false)}
|
|
789
|
+
disabled={disabled}
|
|
790
|
+
className={cn(
|
|
791
|
+
// Match InputGroupTextarea styling - no border since InputGroup provides it
|
|
792
|
+
"flex-1 w-full resize-none rounded-none border-0 py-3 px-3 text-sm shadow-none",
|
|
793
|
+
"placeholder:text-muted-foreground focus-visible:ring-0 focus-visible:outline-none",
|
|
794
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
795
|
+
// Make background transparent so highlights show through
|
|
796
|
+
"bg-transparent dark:bg-transparent",
|
|
797
|
+
className
|
|
798
|
+
)}
|
|
799
|
+
{...props}
|
|
800
|
+
/>
|
|
801
|
+
</div>
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// ============================================================================
|
|
806
|
+
// Exports
|
|
807
|
+
// ============================================================================
|
|
808
|
+
|
|
809
|
+
export type { TriggerState };
|