@rexvane/piweb 0.3.21 → 0.3.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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +9 -6
- package/.next/build-manifest.json +5 -5
- package/.next/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +7 -7
- package/.next/react-loadable-manifest.json +18 -11
- package/.next/required-server-files.js +16 -1
- package/.next/required-server-files.json +16 -1
- package/.next/routes-manifest.json +20 -0
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.meta +8 -0
- package/.next/server/app/_global-error.rsc +15 -11
- package/.next/server/app/_global-error.segments/_full.segment.rsc +15 -11
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +20 -16
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +3 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.meta +8 -0
- package/.next/server/app/_not-found.rsc +17 -13
- package/.next/server/app/_not-found.segments/_full.segment.rsc +17 -13
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +24 -20
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +4 -2
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/side-chat/route.js +5 -0
- package/.next/server/app/api/agent/[id]/side-chat/route.js.nft.json +1 -0
- package/.next/server/app/api/agent/[id]/side-chat/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/files/route.js.nft.json +1 -1
- package/.next/server/app/api/fs/route.js +1 -1
- package/.next/server/app/api/fs/route.js.nft.json +1 -1
- package/.next/server/app/api/git/route.js +4 -4
- package/.next/server/app/api/git/route.js.nft.json +1 -1
- package/.next/server/app/api/github/route.js +5 -0
- package/.next/server/app/api/github/route.js.nft.json +1 -0
- package/.next/server/app/api/github/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +1 -1
- package/.next/server/app/api/health/route.js +1 -1
- package/.next/server/app/api/health/route.js.nft.json +1 -1
- package/.next/server/app/api/models/defaults/route.js +1 -0
- package/.next/server/app/api/models/defaults/route.js.nft.json +1 -0
- package/.next/server/app/api/models/defaults/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/pi-settings/route.js +1 -1
- package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/plugins/route.js.nft.json +1 -1
- package/.next/server/app/api/prompts/route.js +1 -1
- package/.next/server/app/api/prompts/route.js.nft.json +1 -1
- package/.next/server/app/api/remote/route.js +4 -4
- package/.next/server/app/api/remote/route.js.nft.json +1 -1
- package/.next/server/app/api/security/route.js +1 -1
- package/.next/server/app/api/security/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/import/route.js +2 -2
- package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/version/route.js.nft.json +1 -1
- package/.next/server/app/api/web-auth/route.js +2 -2
- package/.next/server/app/api/web-auth/route.js.nft.json +1 -1
- package/.next/server/app/api/workspaces/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/server/app/icon.svg/route.js +1 -1
- package/.next/server/app/icon.svg/route.js.nft.json +1 -1
- package/.next/server/app/icon.svg.meta +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.meta +8 -0
- package/.next/server/app/index.rsc +20 -13
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +26 -19
- package/.next/server/app/index.segments/_full.segment.rsc +20 -13
- package/.next/server/app/index.segments/_tree.segment.rsc +4 -2
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.meta +8 -0
- package/.next/server/app/login.rsc +23 -17
- package/.next/server/app/login.segments/_full.segment.rsc +23 -17
- package/.next/server/app/login.segments/_tree.segment.rsc +4 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +30 -24
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +9 -6
- package/.next/server/chunks/1813.js +23 -0
- package/.next/server/chunks/2430.js +377 -0
- package/.next/server/chunks/2970.js +305 -0
- package/.next/server/chunks/3421.js +1 -0
- package/.next/server/chunks/3924.js +6 -0
- package/.next/server/chunks/4807.js +1 -0
- package/.next/server/chunks/538.js +1 -1
- package/.next/server/chunks/5393.js +1 -0
- package/.next/server/chunks/5802.js +2 -0
- package/.next/server/chunks/6229.js +1 -0
- package/.next/server/chunks/7496.js +1 -0
- package/.next/server/chunks/7662.js +2 -0
- package/.next/server/chunks/8686.js +1 -0
- package/.next/server/chunks/{593.js → 974.js} +1 -1
- package/.next/server/chunks/{920.js → 9920.js} +2 -2
- package/.next/server/functions-config-manifest.json +2 -1
- package/.next/server/instrumentation.js +1 -1
- package/.next/server/instrumentation.js.nft.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/middleware.js +185 -23
- package/.next/server/middleware.js.nft.json +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/preview-props.json +5 -0
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/webpack-runtime.js +1 -1
- package/.next/static/chunks/2716.44fdf1b21454b961.js +1 -0
- package/.next/static/chunks/316-698d10eb465bd8ef.js +1 -0
- package/.next/static/chunks/3794-0978865cda773008.js +52 -0
- package/.next/static/chunks/4673.e261707dce391be7.js +1 -0
- package/.next/static/chunks/4686-0cd87513f9ea3538.js +1 -0
- package/.next/static/chunks/4778.eddd3a5813f85824.js +1 -0
- package/.next/static/chunks/4bd1b696-e199e788e58acf79.js +1 -0
- package/.next/static/chunks/{5173.91ddb803785009f8.js → 5173.dfd1cbb795b9a21c.js} +1 -1
- package/.next/static/chunks/618.949981da99be7182.js +4 -0
- package/.next/static/chunks/7411.5f79671208209b13.js +1 -0
- package/.next/static/chunks/app/_global-error/page-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/side-chat/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/config/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/files/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/fs/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/git/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/github/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/growth/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/health/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/models/defaults/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/models/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/remote/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/security/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/skills/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/update/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/version/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/app/page-dc3cb41d91c7432e.js +1 -0
- package/.next/static/chunks/framework-47b723d27f4a4963.js +1 -0
- package/.next/static/chunks/main-5403f0764579e5fe.js +5 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cbaaee0d7aacb85a.js +1 -0
- package/.next/static/chunks/webpack-2ac3d76b81895f5b.js +1 -0
- package/.next/static/css/9fc559c2c4ad1d0f.css +1 -0
- package/.next/static/css/c6e2227c36ec2fd6.css +1 -0
- package/.next/static/css/{2959bd6b626b7ffe.css → f15a853af8b5127f.css} +1 -1
- package/.next/static/hYPL1-dmhKFjLS3f5yoqN/_buildManifest.js +1 -0
- package/.next/types/app/api/agent/[id]/events/route.ts +3 -1
- package/.next/types/app/api/agent/[id]/route.ts +3 -1
- package/.next/types/app/api/agent/[id]/side-chat/route.ts +355 -0
- package/.next/types/app/api/agent/[id]/tree/route.ts +3 -1
- package/.next/types/app/api/agent/new/route.ts +3 -1
- package/.next/types/app/api/config/route.ts +3 -1
- package/.next/types/app/api/files/route.ts +3 -1
- package/.next/types/app/api/fs/route.ts +3 -1
- package/.next/types/app/api/git/route.ts +3 -1
- package/.next/types/app/api/github/route.ts +355 -0
- package/.next/types/app/api/growth/route.ts +3 -1
- package/.next/types/app/api/health/route.ts +3 -1
- package/.next/types/app/api/models/defaults/route.ts +355 -0
- package/.next/types/app/api/models/route.ts +3 -1
- package/.next/types/app/api/pi-settings/route.ts +3 -1
- package/.next/types/app/api/plugins/route.ts +3 -1
- package/.next/types/app/api/prompts/route.ts +3 -1
- package/.next/types/app/api/remote/route.ts +3 -1
- package/.next/types/app/api/security/route.ts +3 -1
- package/.next/types/app/api/sessions/[id]/export/route.ts +3 -1
- package/.next/types/app/api/sessions/[id]/route.ts +3 -1
- package/.next/types/app/api/sessions/import/route.ts +3 -1
- package/.next/types/app/api/sessions/route.ts +3 -1
- package/.next/types/app/api/skills/route.ts +3 -1
- package/.next/types/app/api/update/route.ts +3 -1
- package/.next/types/app/api/version/route.ts +3 -1
- package/.next/types/app/api/web-auth/route.ts +3 -1
- package/.next/types/app/api/workspaces/route.ts +3 -1
- package/.next/types/app/layout.ts +4 -1
- package/.next/types/app/login/page.ts +4 -1
- package/.next/types/app/page.ts +4 -1
- package/.next/types/cache-life.d.ts +11 -43
- package/.next/types/routes.d.ts +4 -1
- package/.next/types/validator.ts +86 -2
- package/next.config.ts +2 -0
- package/package.json +8 -2
- package/scripts/release.mjs +3 -7
- package/scripts/runtime/check-installed.mjs +66 -0
- package/scripts/runtime/pi-packages.mjs +51 -0
- package/scripts/verify-package.mjs +29 -7
- package/src/app/api/agent/[id]/side-chat/route.ts +77 -0
- package/src/app/api/files/route.ts +5 -2
- package/src/app/api/github/route.ts +26 -0
- package/src/app/api/models/defaults/route.ts +18 -0
- package/src/app/api/models/route.ts +24 -1
- package/src/app/globals.css +1748 -0
- package/src/components/AppShell.tsx +145 -43
- package/src/components/ChatInput.tsx +264 -79
- package/src/components/ChatWindow.tsx +13 -23
- package/src/components/ContextMeter.tsx +213 -144
- package/src/components/DrawerPanel.tsx +1 -1
- package/src/components/FileViewer.tsx +89 -8
- package/src/components/GitPanel.tsx +110 -40
- package/src/components/GrowthTree.tsx +30 -5
- package/src/components/ModelSelector.tsx +21 -5
- package/src/components/ProjectPanel.tsx +55 -30
- package/src/components/PromptPanel.tsx +2 -4
- package/src/components/ProviderSetupModal.module.css +20 -0
- package/src/components/ProviderSetupModal.tsx +134 -29
- package/src/components/SettingsPanel.tsx +252 -27
- package/src/components/TrajectoryView.tsx +4 -6
- package/src/components/chat/reading-anchor.ts +81 -0
- package/src/components/composer/ComposerImages.tsx +326 -0
- package/src/components/side-chat/SideChatPanel.module.css +131 -0
- package/src/components/side-chat/SideChatPanel.tsx +189 -0
- package/src/components/side-chat/icons.tsx +8 -0
- package/src/components/side-chat/request-history.ts +29 -0
- package/src/components/side-chat/store.ts +248 -0
- package/src/hooks/useFileViewer.ts +6 -4
- package/src/hooks/useGrowth.ts +3 -0
- package/src/hooks/usePiWeb.ts +12 -2
- package/src/hooks/useWorkspaceFiles.ts +93 -0
- package/src/i18n.tsx +150 -2
- package/src/lib/agent-manager.ts +135 -104
- package/src/lib/files-service.ts +166 -3
- package/src/lib/format.ts +56 -0
- package/src/lib/git-service.ts +45 -13
- package/src/lib/github-identity.ts +245 -0
- package/src/lib/growth-service.ts +6 -2
- package/src/lib/hero-model-memory.ts +28 -0
- package/src/lib/model-defaults.ts +105 -0
- package/src/lib/model-draft.ts +257 -9
- package/src/lib/model-endpoint.ts +23 -0
- package/src/lib/model-files.ts +33 -0
- package/src/lib/models-service.ts +240 -78
- package/src/lib/path-security.ts +24 -32
- package/src/lib/pi.ts +27 -2
- package/src/lib/process-format.ts +3 -2
- package/src/lib/session/metrics.ts +31 -0
- package/src/lib/session-reader.ts +1 -1
- package/src/lib/side-chat/context.ts +123 -0
- package/src/lib/side-chat/service.ts +191 -0
- package/src/lib/side-chat/types.ts +61 -0
- package/src/lib/thinking-level.ts +39 -0
- package/src/lib/trajectory.ts +17 -3
- package/src/lib/types.ts +12 -1
- package/src/lib/update-service.ts +4 -6
- package/src/lib/workspace-index.ts +270 -0
- package/.next/server/chunks/229.js +0 -1
- package/.next/server/chunks/393.js +0 -1
- package/.next/server/chunks/421.js +0 -1
- package/.next/server/chunks/430.js +0 -193
- package/.next/server/chunks/496.js +0 -1
- package/.next/server/chunks/662.js +0 -2
- package/.next/server/chunks/686.js +0 -1
- package/.next/server/chunks/741.js +0 -165
- package/.next/server/chunks/802.js +0 -2
- package/.next/server/chunks/807.js +0 -1
- package/.next/server/chunks/813.js +0 -1
- package/.next/server/chunks/924.js +0 -6
- package/.next/static/chunks/2716.7ad073038e94a802.js +0 -1
- package/.next/static/chunks/316-5ea443ee6b512aa9.js +0 -1
- package/.next/static/chunks/3794-3679f1c05160977c.js +0 -52
- package/.next/static/chunks/4673.8a5a9e6855350e34.js +0 -1
- package/.next/static/chunks/4686-882531aef1d2e250.js +0 -1
- package/.next/static/chunks/4778.305dc6abb9889627.js +0 -1
- package/.next/static/chunks/4bd1b696-93bf506b236d6248.js +0 -1
- package/.next/static/chunks/618.ee5d5bf1c647e483.js +0 -4
- package/.next/static/chunks/839.bfd2db36483faa3e.js +0 -1
- package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/page-c04af72e549695bd.js +0 -1
- package/.next/static/chunks/framework-c4e651fe89edfa91.js +0 -1
- package/.next/static/chunks/main-6159b22a8cb0b7c5.js +0 -5
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/webpack-9b4bb574e5817654.js +0 -1
- package/.next/static/css/ea27b9e30f76303f.css +0 -1
- package/.next/static/sNX5GQkOWuK5YEID1hT-C/_buildManifest.js +0 -1
- /package/.next/static/{sNX5GQkOWuK5YEID1hT-C → hYPL1-dmhKFjLS3f5yoqN}/_ssgManifest.js +0 -0
|
@@ -6,18 +6,23 @@
|
|
|
6
6
|
* 运行中:Enter 按设置插话 / 排队,Ctrl(⌘)+Enter 用另一种;停止按钮会把排队消息取回输入框(pi 终端同款);
|
|
7
7
|
* 排队中的消息列在输入卡上方,↑(空输入框时)取回编辑。Enter 发送 / Shift+Enter 换行。
|
|
8
8
|
*/
|
|
9
|
-
import { useEffect, useRef, useState } from "react";
|
|
9
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
10
10
|
import {
|
|
11
11
|
IconAtOutline16,
|
|
12
12
|
IconFileOutline16,
|
|
13
|
+
IconFolderClose16,
|
|
13
14
|
IconSendArrowUp14,
|
|
14
15
|
IconStopFill16,
|
|
15
16
|
IconTerminalOutline14,
|
|
16
17
|
} from "@/components/icons";
|
|
17
18
|
import { ContextMeter } from "@/components/ContextMeter";
|
|
19
|
+
import { COLLAPSE_AT, ComposerImages } from "@/components/composer/ComposerImages";
|
|
18
20
|
import { ModelSelector, type ModelChoice } from "@/components/ModelSelector";
|
|
19
21
|
import { useI18n } from "@/i18n";
|
|
22
|
+
import { useWorkspaceFiles } from "@/hooks/useWorkspaceFiles";
|
|
23
|
+
import { fmtBytes } from "@/lib/format";
|
|
20
24
|
import { otherBehavior, useEnterBehavior } from "@/lib/enter-behavior";
|
|
25
|
+
import { atToken, candidateText, suggestPaths, type PathCandidate } from "@/lib/workspace-index";
|
|
21
26
|
import type { ImageAttachment } from "@/lib/types";
|
|
22
27
|
|
|
23
28
|
export interface SlashCommand {
|
|
@@ -55,6 +60,7 @@ export function ChatInput({
|
|
|
55
60
|
contextWindow,
|
|
56
61
|
contextSource,
|
|
57
62
|
contextVisible = true,
|
|
63
|
+
cwd,
|
|
58
64
|
model,
|
|
59
65
|
thinkingLevel,
|
|
60
66
|
thinkingLevels,
|
|
@@ -89,12 +95,14 @@ export function ChatInput({
|
|
|
89
95
|
/** 上下文分段数据源:ContextMeter 弹窗打开时才做字符统计 */
|
|
90
96
|
contextSource?: {
|
|
91
97
|
systemChars: number;
|
|
92
|
-
messages: { role?: string; content: Array<{ type: string; text?: string; thinking?: string }
|
|
98
|
+
messages: { role?: string; content: Array<{ type: string; text?: string; thinking?: string; arguments?: unknown; name?: string }>; usage?: import("@/lib/types").TrajTokens; stopReason?: string }[];
|
|
93
99
|
breakdown?: import("@/lib/types").ContextBreakdown;
|
|
94
100
|
loadFull?: () => Promise<unknown> | unknown;
|
|
95
101
|
};
|
|
96
102
|
/** 还没有对话时隐藏上下文计量 */
|
|
97
103
|
contextVisible?: boolean;
|
|
104
|
+
/** 工作区根:@ 引用的候选清单从这里取(不传就不启用 @ 引用) */
|
|
105
|
+
cwd?: string;
|
|
98
106
|
model?: { provider: string; id: string; name: string };
|
|
99
107
|
thinkingLevel?: string;
|
|
100
108
|
thinkingLevels: string[];
|
|
@@ -149,10 +157,20 @@ export function ChatInput({
|
|
|
149
157
|
const uploadBusy = uploadCount > 0 || pendingUploadCount > 0;
|
|
150
158
|
const sendBusy = sending || pendingSend;
|
|
151
159
|
const [dragActive, setDragActive] = useState(false);
|
|
160
|
+
/** 引用 chip 超过 COLLAPSE_AT 就先折叠:手机上折行会把输入框顶掉(图片的折叠在 ComposerImages 里) */
|
|
161
|
+
const [refsExpanded, setRefsExpanded] = useState(false);
|
|
162
|
+
/** 上传文件超过 4 个时的展开状态(与引用同一套折叠规则) */
|
|
163
|
+
const [uploadsExpanded, setUploadsExpanded] = useState(false);
|
|
152
164
|
const dragDepth = useRef(0);
|
|
153
165
|
const [modelOpen, setModelOpen] = useState(false);
|
|
154
166
|
const [cmdIdx, setCmdIdx] = useState(0);
|
|
155
167
|
const [cmdDismissed, setCmdDismissed] = useState(false);
|
|
168
|
+
// @ 引用:打字触发候选,选中后落成引用 chip(发送时统一拼成 `@路径` 行)
|
|
169
|
+
const [mention, setMention] = useState<{ query: string; start: number; tokenEnd: number } | null>(null);
|
|
170
|
+
const [mentionIdx, setMentionIdx] = useState(0);
|
|
171
|
+
/** 被 Esc 关掉的那个 token(位置 + 原文);光标在它里面移动不再弹,改了字、或换成别的 token 重新弹 */
|
|
172
|
+
const mentionDismissed = useRef<string | null>(null);
|
|
173
|
+
const mentionMenuRef = useRef<HTMLDivElement>(null);
|
|
156
174
|
const taRef = useRef<HTMLTextAreaElement>(null);
|
|
157
175
|
const wrapRef = useRef<HTMLDivElement>(null);
|
|
158
176
|
const { t } = useI18n();
|
|
@@ -179,6 +197,79 @@ export function ChatInput({
|
|
|
179
197
|
const filteredCommands = (commands ?? []).filter((c) => c.name.toLowerCase().startsWith(slash.split(" ")[0].toLowerCase()));
|
|
180
198
|
const visibleCommands = filteredCommands;
|
|
181
199
|
const commandMode = typedCommandMode;
|
|
200
|
+
|
|
201
|
+
// ---------- @ 引用:输入 @ 弹候选,选中落成引用 chip ----------
|
|
202
|
+
// 候选清单一次拉全量(工作区 git 可见文件),之后每个字符都在本地匹配;规则与 pi 终端的 @ 补全一致(见 workspace-index)。
|
|
203
|
+
const mentionMode = mention !== null;
|
|
204
|
+
const mentionQuery = mention?.query ?? null;
|
|
205
|
+
const files = useWorkspaceFiles(cwd ?? "", mentionMode);
|
|
206
|
+
// 只在候选清单或查询变了时重算:光标移动、其它状态变化引起的重渲染不该每次扫一遍全量清单
|
|
207
|
+
const suggestions = useMemo(
|
|
208
|
+
() => (mentionQuery === null ? [] : suggestPaths(files.candidates, mentionQuery)),
|
|
209
|
+
[files.candidates, mentionQuery],
|
|
210
|
+
);
|
|
211
|
+
const effectiveMentionIdx = Math.min(mentionIdx, Math.max(0, suggestions.length - 1));
|
|
212
|
+
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
mentionMenuRef.current?.querySelector('[aria-selected="true"]')?.scrollIntoView({ block: "nearest" });
|
|
215
|
+
}, [mentionIdx, suggestions.length]);
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* 文本 / 光标变了就重新判是否在 @ token 里(Esc 关过的同一个 token 不再弹)。
|
|
219
|
+
* 没有工作区就不弹:没有候选来源,菜单只会空转还拦着方向键和 Esc。
|
|
220
|
+
*/
|
|
221
|
+
const syncMention = (value: string, caret: number | null) => {
|
|
222
|
+
const token = caret === null || !cwd ? null : atToken(value, caret);
|
|
223
|
+
if (!token) {
|
|
224
|
+
// 离开 token 就忘掉 Esc:之后在同一位置打新的 @ 照常弹
|
|
225
|
+
mentionDismissed.current = null;
|
|
226
|
+
setMention(null);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (mentionDismissed.current === `${token.start}:${value.slice(token.start, token.tokenEnd)}`) {
|
|
230
|
+
setMention(null);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!mention || mention.start !== token.start || mention.query !== token.query) setMentionIdx(0);
|
|
234
|
+
if (!mention || mention.start !== token.start || mention.query !== token.query || mention.tokenEnd !== token.tokenEnd) {
|
|
235
|
+
setMention({ query: token.query, start: token.start, tokenEnd: token.tokenEnd });
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const closeMention = (dismiss: boolean) => {
|
|
240
|
+
if (dismiss && mention) mentionDismissed.current = `${mention.start}:${text.slice(mention.start, mention.tokenEnd)}`;
|
|
241
|
+
setMention(null);
|
|
242
|
+
setMentionIdx(0);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/** 选中候选:目录引用文件夹本身(要往下钻就继续打 `@src/lib/`),token 从正文里播掉 */
|
|
246
|
+
const applyMention = (candidate: PathCandidate) => {
|
|
247
|
+
if (!mention) return;
|
|
248
|
+
const { start, tokenEnd } = mention;
|
|
249
|
+
// 目录存成 `src/lib/`:chip 靠尾斜杠判断文件夹(去重会把尾斜杠归一化掉)
|
|
250
|
+
const path = candidate.dir ? `${candidate.path}/` : candidate.path;
|
|
251
|
+
changeDraft((previous) => {
|
|
252
|
+
const refs = previous.references ?? [];
|
|
253
|
+
// 目录带尾斜杠、文件不带:去重按去掉尾斜杠比较,
|
|
254
|
+
// 否则「文件面板引用的 src/lib」与「@ 选的 src/lib/」会变成两个 chip
|
|
255
|
+
const samePath = (a: string, b: string) => a.replace(/[\\/]+$/, "") === b.replace(/[\\/]+$/, "");
|
|
256
|
+
return {
|
|
257
|
+
...previous,
|
|
258
|
+
text: previous.text.slice(0, start) + previous.text.slice(tokenEnd),
|
|
259
|
+
references: refs.some((existing) => samePath(existing, path)) ? refs : [...refs, path],
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
setMention(null);
|
|
263
|
+
setMentionIdx(0);
|
|
264
|
+
requestAnimationFrame(() => {
|
|
265
|
+
autoSize();
|
|
266
|
+
const ta = taRef.current;
|
|
267
|
+
if (ta) {
|
|
268
|
+
ta.focus();
|
|
269
|
+
ta.setSelectionRange(start, start);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
};
|
|
182
273
|
// 命令菜单滚动:.popover 的 overflow:hidden 会压掉 overflow-y-auto(unlayered CSS 优先),
|
|
183
274
|
// 内联 overflowY 强制可滚;键盘导航时把选中项滚进视野
|
|
184
275
|
const cmdMenuRef = useRef<HTMLDivElement>(null);
|
|
@@ -445,6 +536,63 @@ export function ChatInput({
|
|
|
445
536
|
{attachmentError}
|
|
446
537
|
</div>
|
|
447
538
|
)}
|
|
539
|
+
{/* @ 引用候选(与斜杠菜单同一处锚点:输入卡上方) */}
|
|
540
|
+
{mentionMode && (
|
|
541
|
+
<div
|
|
542
|
+
ref={mentionMenuRef}
|
|
543
|
+
data-testid="mention-menu"
|
|
544
|
+
className="popover absolute bottom-full left-0 right-0 z-50 mb-2 max-h-[340px] overflow-y-auto py-1.5"
|
|
545
|
+
role="listbox"
|
|
546
|
+
aria-label={t.mentionFiles}
|
|
547
|
+
style={{ overflowY: "auto" }}
|
|
548
|
+
>
|
|
549
|
+
<div className="flex items-center gap-2 px-4 pb-1 pt-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
550
|
+
<span>{t.mentionFiles}</span>
|
|
551
|
+
{files.truncated && <span style={{ color: "var(--dsw-warn)" }}>{t.mentionTruncated}</span>}
|
|
552
|
+
{files.source === "disk" && <span title={t.mentionDiskSourceHint}>{t.mentionDiskSource}</span>}
|
|
553
|
+
<span className="flex-1" />
|
|
554
|
+
<span>{t.mentionHint}</span>
|
|
555
|
+
</div>
|
|
556
|
+
{files.status === "loading" && (
|
|
557
|
+
<div className="px-4 py-2" style={{ fontSize: 12.5, color: "var(--dsw-label-caption)" }}>{t.panelLoading}</div>
|
|
558
|
+
)}
|
|
559
|
+
{files.status === "error" && (
|
|
560
|
+
<div className="px-4 py-2" style={{ fontSize: 12.5, color: "var(--dsw-danger)" }} role="alert">{files.error}</div>
|
|
561
|
+
)}
|
|
562
|
+
{files.status === "ready" && suggestions.length === 0 && (
|
|
563
|
+
<div className="px-4 py-2" style={{ fontSize: 12.5, color: "var(--dsw-label-caption)" }}>{t.mentionNoMatch}</div>
|
|
564
|
+
)}
|
|
565
|
+
{suggestions.map(({ candidate, label, description }, i) => (
|
|
566
|
+
<button
|
|
567
|
+
key={`${candidate.dir ? "d" : "f"}:${candidate.path}`}
|
|
568
|
+
type="button"
|
|
569
|
+
role="option"
|
|
570
|
+
aria-selected={i === effectiveMentionIdx}
|
|
571
|
+
className="flex w-full items-center gap-2 px-4 py-1.5 text-left transition-colors"
|
|
572
|
+
style={{ background: i === effectiveMentionIdx ? "var(--dsw-hover)" : "transparent" }}
|
|
573
|
+
onMouseEnter={() => setMentionIdx(i)}
|
|
574
|
+
onClick={() => applyMention(candidate)}
|
|
575
|
+
title={candidateText(candidate)}
|
|
576
|
+
>
|
|
577
|
+
{candidate.dir ? (
|
|
578
|
+
<IconFolderClose16 size={13} style={{ flex: "none", color: "var(--dsw-warn)" }} />
|
|
579
|
+
) : (
|
|
580
|
+
<IconFileOutline16 size={13} style={{ flex: "none", color: "var(--dsw-label-caption)" }} />
|
|
581
|
+
)}
|
|
582
|
+
{/* 和 pi 终端一样:名字在前,路径在后;根目录下的条目两者相同,不重复显示 */}
|
|
583
|
+
<span className="flex-none truncate" style={{ maxWidth: "55%", fontSize: 12.5, fontFamily: "var(--font-mono)", color: "var(--dsw-label-primary)" }}>
|
|
584
|
+
{label}
|
|
585
|
+
</span>
|
|
586
|
+
{description.replace(/\/$/, "") !== label.replace(/\/$/, "") && (
|
|
587
|
+
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 11.5, fontFamily: "var(--font-mono)", color: "var(--dsw-label-caption)" }}>
|
|
588
|
+
{description}
|
|
589
|
+
</span>
|
|
590
|
+
)}
|
|
591
|
+
</button>
|
|
592
|
+
))}
|
|
593
|
+
</div>
|
|
594
|
+
)}
|
|
595
|
+
|
|
448
596
|
{/* 斜杠命令菜单(dsh:悬浮于输入卡上方,同宽) */}
|
|
449
597
|
{commandMode && visibleCommands.length > 0 && (
|
|
450
598
|
<div ref={cmdMenuRef} className="popover absolute bottom-full left-0 right-0 z-50 mb-2 max-h-[340px] overflow-y-auto py-1.5" role="listbox" aria-label={t.slashCatalog} style={{ overflowY: "auto" }}>
|
|
@@ -478,91 +626,90 @@ export function ChatInput({
|
|
|
478
626
|
</div>
|
|
479
627
|
)}
|
|
480
628
|
|
|
481
|
-
{/*
|
|
629
|
+
{/*
|
|
630
|
+
* 附件轨(图片 / 引用 / 上传文件):绝对定位浮在输入卡上方,**不占布局高度** ——
|
|
631
|
+
* 输入框位置不动,多出来的附件直接盖住后面的聊天记录(用户要的就是这个)。
|
|
632
|
+
* 外层 pointer-events-none,只有附件本身接收点击,免得挡住聊天区的交互。
|
|
633
|
+
*/}
|
|
634
|
+
<div className="pointer-events-none absolute inset-x-0 bottom-full z-20 flex flex-col gap-1.5 pb-2" data-testid="composer-attachments">
|
|
635
|
+
{/* 附件轨:图片(超过 4 张折叠成牌堆,展开沿弧线扇开;点开可放大、切换、删除) */}
|
|
482
636
|
{images.length > 0 && (
|
|
483
|
-
<
|
|
484
|
-
{images
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
className="h-14 w-14 rounded-lg object-cover"
|
|
490
|
-
style={{ border: "0.5px solid var(--dsw-border-l2)" }}
|
|
491
|
-
/>
|
|
492
|
-
<button
|
|
493
|
-
className="absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full text-[10px]"
|
|
494
|
-
style={{ background: "var(--dsw-label-primary)", color: "var(--dsw-bg-base)" }}
|
|
495
|
-
disabled={sendBusy}
|
|
496
|
-
onClick={() => changeDraft((previous) => ({ ...previous, images: previous.images.filter((_, index) => index !== i) }))}
|
|
497
|
-
>
|
|
498
|
-
✕
|
|
499
|
-
</button>
|
|
500
|
-
</div>
|
|
501
|
-
))}
|
|
502
|
-
</div>
|
|
637
|
+
<ComposerImages
|
|
638
|
+
images={images}
|
|
639
|
+
disabled={sendBusy}
|
|
640
|
+
onRemove={(index) => changeDraft((previous) => ({ ...previous, images: previous.images.filter((_, at) => at !== index) }))}
|
|
641
|
+
onClear={() => changeDraft((previous) => ({ ...previous, images: [] }))}
|
|
642
|
+
/>
|
|
503
643
|
)}
|
|
504
644
|
|
|
505
|
-
{/* 附件轨:上传的普通文件(压缩包/文档等,路径引用) */}
|
|
506
645
|
{/* 路径引用(项目结构图/文件面板的「引用到输入框」):独立于正文,发送时拼成 @路径 */}
|
|
507
646
|
{references.length > 0 && (
|
|
508
|
-
<div className="
|
|
509
|
-
{references.map((path) =>
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
background: "var(--dsw-selector)",
|
|
519
|
-
border: "0.5px solid var(--dsw-border-l3)",
|
|
520
|
-
}}
|
|
521
|
-
title={path}
|
|
522
|
-
>
|
|
523
|
-
<IconAtOutline16 size={11} style={{ flex: "none", color: "var(--dsw-accent)" }} />
|
|
524
|
-
<span className="truncate">{referenceLabelOf(path)}</span>
|
|
525
|
-
<button
|
|
526
|
-
type="button"
|
|
527
|
-
className="flex h-4 w-4 flex-none items-center justify-center rounded-full text-[10px]"
|
|
528
|
-
style={{ color: "var(--dsw-label-caption)" }}
|
|
529
|
-
disabled={sendBusy}
|
|
530
|
-
onClick={() => changeDraft((previous) => ({ ...previous, references: (previous.references ?? []).filter((p) => p !== path) }))}
|
|
531
|
-
aria-label={`${t.delete} ${path}`}
|
|
647
|
+
<div className="pointer-events-auto flex flex-wrap gap-1 px-1" data-testid="composer-references">
|
|
648
|
+
{(refsExpanded ? references : references.slice(0, COLLAPSE_AT)).map((path, i) => {
|
|
649
|
+
const chip = referenceChipOf(path);
|
|
650
|
+
return (
|
|
651
|
+
<span
|
|
652
|
+
key={path}
|
|
653
|
+
className="pw-ref-chip group/ref"
|
|
654
|
+
title={path}
|
|
655
|
+
/* 与图片卡组同一套时序:从左往右错开浮上来,收起反向;不旋转(带文字转着读着累) */
|
|
656
|
+
style={{ animationDelay: `${Math.min(i * 14, 140)}ms` }}
|
|
532
657
|
>
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
658
|
+
{chip.dir ? (
|
|
659
|
+
<IconFolderClose16 size={12} style={{ flex: "none", color: "var(--dsw-warn)" }} />
|
|
660
|
+
) : (
|
|
661
|
+
<IconFileOutline16 size={12} style={{ flex: "none", color: "var(--dsw-accent)" }} />
|
|
662
|
+
)}
|
|
663
|
+
<span className="truncate">{chip.text}</span>
|
|
664
|
+
<button
|
|
665
|
+
type="button"
|
|
666
|
+
className="pw-ref-close"
|
|
667
|
+
disabled={sendBusy}
|
|
668
|
+
onClick={() => changeDraft((previous) => ({ ...previous, references: (previous.references ?? []).filter((p) => p !== path) }))}
|
|
669
|
+
aria-label={`${t.delete} ${path}`}
|
|
670
|
+
>
|
|
671
|
+
✕
|
|
672
|
+
</button>
|
|
673
|
+
</span>
|
|
674
|
+
);
|
|
675
|
+
})}
|
|
676
|
+
{references.length > COLLAPSE_AT ? (
|
|
677
|
+
<button type="button" className="pw-chip" data-testid="references-toggle" onClick={() => setRefsExpanded((open) => !open)}>
|
|
678
|
+
{refsExpanded ? t.chipCollapse : t.chipExpandMore.replace("{n}", String(references.length - COLLAPSE_AT))}
|
|
679
|
+
</button>
|
|
680
|
+
) : null}
|
|
537
681
|
</div>
|
|
538
682
|
)}
|
|
539
683
|
|
|
684
|
+
{/* 附件轨:上传的普通文件(压缩包/文档等,路径引用);超过 4 个折叠 */}
|
|
540
685
|
{uploads.length > 0 && (
|
|
541
|
-
<div className="
|
|
542
|
-
{uploads.map((f, i) => (
|
|
543
|
-
<
|
|
544
|
-
|
|
545
|
-
className="
|
|
546
|
-
style={{
|
|
547
|
-
title={f.path}
|
|
548
|
-
>
|
|
549
|
-
<IconFileOutline16 size={14} style={{ flex: "none", color: "var(--dsw-label-tertiary)" }} />
|
|
550
|
-
<span className="max-w-[180px] truncate" style={{ fontSize: 12.5, color: "var(--dsw-label-secondary)" }}>{f.name}</span>
|
|
551
|
-
<span style={{ fontSize: 11, color: "var(--dsw-label-caption)" }}>{fmtUploadSize(f.size)}</span>
|
|
686
|
+
<div className="pointer-events-auto flex flex-wrap items-center gap-1 px-1" data-testid="composer-uploads">
|
|
687
|
+
{(uploadsExpanded ? uploads : uploads.slice(0, COLLAPSE_AT)).map((f, i) => (
|
|
688
|
+
<span key={f.path} className="pw-ref-chip group/ref" title={f.path}>
|
|
689
|
+
<IconFileOutline16 size={12} style={{ flex: "none", color: "var(--dsw-label-tertiary)" }} />
|
|
690
|
+
<span className="max-w-[180px] truncate">{f.name}</span>
|
|
691
|
+
<span style={{ flex: "none", fontSize: 11, color: "var(--dsw-label-caption)" }}>{fmtUploadSize(f.size)}</span>
|
|
552
692
|
<button
|
|
553
|
-
|
|
554
|
-
|
|
693
|
+
type="button"
|
|
694
|
+
className="pw-ref-close"
|
|
555
695
|
disabled={sendBusy}
|
|
556
696
|
onClick={() => changeDraft((previous) => ({ ...previous, uploads: previous.uploads.filter((_, index) => index !== i) }))}
|
|
557
|
-
aria-label=
|
|
697
|
+
aria-label={`${t.delete} ${f.name}`}
|
|
558
698
|
>
|
|
559
699
|
✕
|
|
560
700
|
</button>
|
|
561
|
-
</
|
|
701
|
+
</span>
|
|
562
702
|
))}
|
|
703
|
+
{uploads.length > COLLAPSE_AT ? (
|
|
704
|
+
<button type="button" className="pw-chip" data-testid="uploads-toggle" onClick={() => setUploadsExpanded((open) => !open)}>
|
|
705
|
+
{uploadsExpanded ? t.chipCollapse : t.chipExpandMore.replace("{n}", String(uploads.length - COLLAPSE_AT))}
|
|
706
|
+
</button>
|
|
707
|
+
) : null}
|
|
563
708
|
</div>
|
|
564
709
|
)}
|
|
565
710
|
|
|
711
|
+
</div>
|
|
712
|
+
|
|
566
713
|
{/* 卡片(玻璃态)—— 拖放由 document 级监听统一接管 */}
|
|
567
714
|
<div
|
|
568
715
|
className="relative w-full"
|
|
@@ -605,12 +752,19 @@ export function ChatInput({
|
|
|
605
752
|
suppressHydrationWarning
|
|
606
753
|
className="min-w-0 flex-1 resize-none"
|
|
607
754
|
style={{ fontSize: "var(--piweb-chat-font-size, var(--dsh-content-font-size))", lineHeight: 1.55 }}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
755
|
+
onChange={(e) => {
|
|
756
|
+
const value = e.target.value;
|
|
757
|
+
setText(value);
|
|
758
|
+
setCmdDismissed(false);
|
|
611
759
|
setCmdIdx(0);
|
|
760
|
+
syncMention(value, e.target.selectionStart);
|
|
612
761
|
autoSize();
|
|
613
762
|
}}
|
|
763
|
+
onSelect={(e) => {
|
|
764
|
+
// 选中了一段文字时不弹:那时 Enter / Tab 应该照常工作
|
|
765
|
+
const ta = e.target as HTMLTextAreaElement;
|
|
766
|
+
syncMention(ta.value, ta.selectionStart === ta.selectionEnd ? ta.selectionStart : null);
|
|
767
|
+
}}
|
|
614
768
|
onPaste={(e) => {
|
|
615
769
|
const files = Array.from(e.clipboardData.files);
|
|
616
770
|
if (files.length) {
|
|
@@ -619,6 +773,34 @@ export function ChatInput({
|
|
|
619
773
|
}
|
|
620
774
|
}}
|
|
621
775
|
onKeyDown={(e) => {
|
|
776
|
+
// 输入法组字时的按键归输入法(Esc 取消组字、↑↓ 选字、Enter 上屏),一律不当候选操作
|
|
777
|
+
if (mentionMode && !e.nativeEvent.isComposing) {
|
|
778
|
+
// 候选开着时优先于发送:Esc 关掉、↑↓ 选、Enter / Tab 采纳。
|
|
779
|
+
// 没有候选时不拦 Enter——`@某人` 这种不成候选的文字要能直接发出去。
|
|
780
|
+
if (e.key === "Escape") {
|
|
781
|
+
e.preventDefault();
|
|
782
|
+
closeMention(true);
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (suggestions.length > 0) {
|
|
786
|
+
if (e.key === "ArrowDown") {
|
|
787
|
+
e.preventDefault();
|
|
788
|
+
setMentionIdx((i) => Math.min(i + 1, suggestions.length - 1));
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (e.key === "ArrowUp") {
|
|
792
|
+
e.preventDefault();
|
|
793
|
+
setMentionIdx((i) => Math.max(i - 1, 0));
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
if (e.key === "Enter" || e.key === "Tab") {
|
|
797
|
+
e.preventDefault();
|
|
798
|
+
const picked = suggestions[effectiveMentionIdx];
|
|
799
|
+
if (picked) applyMention(picked.candidate);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
622
804
|
if (adopted && text === "" && (e.key === "Backspace" || e.key === "Escape")) {
|
|
623
805
|
// 空参数时退格/Esc:摘掉命令胶囊
|
|
624
806
|
e.preventDefault();
|
|
@@ -711,6 +893,7 @@ export function ChatInput({
|
|
|
711
893
|
percent={contextPercent}
|
|
712
894
|
tokens={contextTokens}
|
|
713
895
|
contextWindow={contextWindow}
|
|
896
|
+
model={model}
|
|
714
897
|
source={contextSource}
|
|
715
898
|
/>
|
|
716
899
|
)}
|
|
@@ -773,17 +956,19 @@ export function ChatInput({
|
|
|
773
956
|
}
|
|
774
957
|
|
|
775
958
|
function fmtUploadSize(bytes: number): string {
|
|
776
|
-
|
|
777
|
-
if (bytes >= 1024) return `${Math.round(bytes / 1024)}KB`;
|
|
778
|
-
return `${bytes}B`;
|
|
959
|
+
return fmtBytes(bytes).replace(/ /g, "");
|
|
779
960
|
}
|
|
780
961
|
|
|
781
962
|
/**
|
|
782
|
-
* 引用 chip
|
|
783
|
-
*
|
|
963
|
+
* 引用 chip 上显示的短标签。只取 basename 会分不清两件事:目录与同名文件
|
|
964
|
+
* (`src/lib` 与 `src/lib.ts` 都显示 `lib`;`a/index.ts` 与 `b/index.ts` 都显示 `index.ts`)。
|
|
965
|
+
* 规则:目录带尾斜杠(`lib/`,配文件夹图标);其余最多保留**尾部两段**
|
|
966
|
+
* (`src/lib/index.ts` → `…/lib/index.ts`)。完整路径仍在 title 里。
|
|
784
967
|
*/
|
|
785
|
-
function
|
|
786
|
-
const
|
|
787
|
-
const
|
|
788
|
-
|
|
968
|
+
function referenceChipOf(path: string): { text: string; dir: boolean } {
|
|
969
|
+
const dir = /[\\/]$/.test(path);
|
|
970
|
+
const parts = path.replace(/[\\/]+$/, "").split(/[\\/]/).filter(Boolean);
|
|
971
|
+
if (!parts.length) return { text: path, dir };
|
|
972
|
+
const tail = parts.slice(-2).join("/");
|
|
973
|
+
return { text: dir ? `${tail}/` : parts.length > 2 ? `…/${tail}` : tail, dir };
|
|
789
974
|
}
|
|
@@ -29,7 +29,9 @@ import { languageForPath } from "@/lib/highlight";
|
|
|
29
29
|
import { copyText } from "@/lib/clipboard";
|
|
30
30
|
import { OutlineRail } from "@/components/OutlineRail";
|
|
31
31
|
import { ChatScrollContext, type ChatScroll } from "@/components/chat/chat-scroll";
|
|
32
|
+
import { useChatReadingAnchor } from "@/components/chat/reading-anchor";
|
|
32
33
|
import { useI18n } from "@/i18n";
|
|
34
|
+
import { fmtDuration, fmtPercent, fmtThroughput, fmtTokens } from "@/lib/format";
|
|
33
35
|
import type { ToolCardState } from "@/hooks/usePiWeb";
|
|
34
36
|
import type { ContextResource, TrajEntry, TrajTokens, WebContent, WebMessage, WebStats } from "@/lib/types";
|
|
35
37
|
import {
|
|
@@ -128,16 +130,16 @@ function TurnMetaPill({ durationMs, usage, t }: { durationMs: number; usage?: Tr
|
|
|
128
130
|
<>
|
|
129
131
|
<div className="flex items-center justify-between gap-2">
|
|
130
132
|
<span style={{ color: "var(--dsw-label-caption)" }}>{t.inputTokens}</span>
|
|
131
|
-
<span>{
|
|
133
|
+
<span>{fmtTokens(billed)}</span>
|
|
132
134
|
</div>
|
|
133
135
|
<div className="flex items-center justify-between gap-2">
|
|
134
136
|
<span style={{ color: "var(--dsw-label-caption)" }}>{t.outputTokens}</span>
|
|
135
|
-
<span>{
|
|
137
|
+
<span>{fmtTokens(usage.output ?? 0)}</span>
|
|
136
138
|
</div>
|
|
137
139
|
{(usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0) > 0 && (
|
|
138
140
|
<div className="flex items-center justify-between gap-2">
|
|
139
141
|
<span style={{ color: "var(--dsw-label-caption)" }}>{t.cacheHits}</span>
|
|
140
|
-
<span>{
|
|
142
|
+
<span>{fmtPercent(((usage.cacheRead ?? 0) / Math.max(billed, 1)) * 100)}</span>
|
|
141
143
|
</div>
|
|
142
144
|
)}
|
|
143
145
|
</>
|
|
@@ -710,7 +712,7 @@ function WorkingStep({ startedAt, outputTokens, workingMessage, hasLiveStep }: {
|
|
|
710
712
|
<div className="pw-line">
|
|
711
713
|
{label ? <span className="pw-shimmer">{label}</span> : <span className="pw-verb">{fillBold(WORKING_FOR, { d: fmtSpan(elapsed) })}</span>}
|
|
712
714
|
{label && <span className="pw-meta">{fmtSpan(elapsed)}</span>}
|
|
713
|
-
{outputTokens ? <span className="pw-meta">↓ {
|
|
715
|
+
{outputTokens ? <span className="pw-meta">↓ {fmtTokens(outputTokens)} tokens</span> : null}
|
|
714
716
|
</div>
|
|
715
717
|
</Step>
|
|
716
718
|
);
|
|
@@ -1243,22 +1245,6 @@ function TurnLite({ turn, messages }: { turn: Turn; messages: WebMessage[] }) {
|
|
|
1243
1245
|
);
|
|
1244
1246
|
}
|
|
1245
1247
|
|
|
1246
|
-
function fmtTok(n: number): string {
|
|
1247
|
-
const scaled = (value: number) => value >= 100 ? String(Math.round(value)) : String(Math.round(value * 10) / 10);
|
|
1248
|
-
return n >= 1_000_000 ? `${scaled(n / 1_000_000)}M` : n >= 1000 ? `${scaled(n / 1000)}K` : String(n);
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
function fmtDuration(ms: number): string {
|
|
1252
|
-
const seconds = ms / 1000;
|
|
1253
|
-
if (seconds < 60) return `${Math.round(seconds * 10) / 10}s`;
|
|
1254
|
-
const whole = Math.round(seconds);
|
|
1255
|
-
return `${Math.floor(whole / 60)}m${whole % 60}s`;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
function fmtThroughput(tokensPerSecond: number): string {
|
|
1259
|
-
return tokensPerSecond >= 10 ? String(Math.round(tokensPerSecond)) : String(Math.round(tokensPerSecond * 10) / 10);
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
1248
|
export function SessionStatsBar({
|
|
1263
1249
|
stats,
|
|
1264
1250
|
}: {
|
|
@@ -1281,15 +1267,18 @@ export function SessionStatsBar({
|
|
|
1281
1267
|
if (speeds.length) groups.push(speeds.join(" · "));
|
|
1282
1268
|
const billedInput = stats.tokens.input + stats.tokens.cacheRead + stats.tokens.cacheWrite;
|
|
1283
1269
|
if (billedInput > 0 || stats.tokens.output > 0) {
|
|
1284
|
-
if (billedInput > 0) groups.push(`${t.cacheHits} ${
|
|
1285
|
-
groups.push(`${t.inputTokens} ${
|
|
1270
|
+
if (billedInput > 0) groups.push(`${t.cacheHits} ${fmtPercent((stats.tokens.cacheRead / billedInput) * 100)}`);
|
|
1271
|
+
groups.push(`${t.inputTokens} ${fmtTokens(billedInput)} tok · ${t.outputTokens} ${fmtTokens(stats.tokens.output)} tok`);
|
|
1286
1272
|
}
|
|
1287
1273
|
const line = groups.join(" | ");
|
|
1288
1274
|
return (
|
|
1289
1275
|
// 悬停显示完整统计:自定义浮层固定在统计条上方(原生 title 位置不可控)
|
|
1276
|
+
// 容器只有 544px(内容窄轴下限)而整行实测 959px,以前一律省略号截断:
|
|
1277
|
+
// 提高精度后更长,「输入 / 输出 tokens」这类用户最关心的反而看不到。
|
|
1278
|
+
// 改成允许换行:窄屏折成两行完整显示,代价是纵向多占 20px。
|
|
1290
1279
|
<div className="group relative mx-auto mt-1 block w-full px-4 pt-1 text-center">
|
|
1291
1280
|
<div
|
|
1292
|
-
className="overflow-
|
|
1281
|
+
className="[overflow-wrap:anywhere]"
|
|
1293
1282
|
style={{ fontSize: "var(--dsh-content-font-size-secondary, 13px)", lineHeight: "20px", color: "var(--dsw-label-caption)" }}
|
|
1294
1283
|
>
|
|
1295
1284
|
{line}
|
|
@@ -1440,6 +1429,7 @@ export function ChatWindow({
|
|
|
1440
1429
|
if (node) node.scrollTop = node.scrollHeight;
|
|
1441
1430
|
},
|
|
1442
1431
|
}), []);
|
|
1432
|
+
useChatReadingAnchor(rootRef, scrollRef, chatScroll.shouldPin);
|
|
1443
1433
|
const { t } = useI18n();
|
|
1444
1434
|
useEffect(() => {
|
|
1445
1435
|
const root = rootRef.current;
|