@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
|
@@ -1,48 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* 上下文占用:
|
|
5
|
-
* 圆环常驻按钮 + 弹窗显示「百分比 / ~已用 / 窗口」与 13 类分段占用条
|
|
6
|
-
* (mac 磁盘存储条式的多色分段,含 Free Space 段)。
|
|
7
|
-
* 分段口径:全部为字符/4 启发式估算(与 SDK estimateTokens 一致),
|
|
8
|
-
* 服务端分项(System Prompt / Tools / Memory / Skills / Compacted / Buffer)来自快照,
|
|
9
|
-
* 消息分项(User / Agent Text / Thinking / Tool Call / Tool Output)弹窗打开时前端统计。
|
|
10
|
-
* 冷会话(只浏览历史、agent 没启动)拿不到组装后的 System Prompt 和工具定义:
|
|
11
|
-
* 其余分项照常显示,另给「现在统计」按需启动会话,分项随后经 context_breakdown 事件刷新。
|
|
12
|
-
* 无对话时不渲染(由父组件经 contextVisible 控制)。
|
|
13
|
-
*/
|
|
3
|
+
/** 上下文总量是 SDK 估算;API 输入用量与内容字符分类分别展示,不能按比例分摊 token。 */
|
|
14
4
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
15
5
|
import { useI18n } from "@/i18n";
|
|
6
|
+
import { fmtPercent, fmtTokens } from "@/lib/format";
|
|
7
|
+
import { formatCapacity, parseCapacity } from "@/lib/model-draft";
|
|
16
8
|
import { classifyMessageChars } from "@/lib/process-format";
|
|
17
|
-
import type { ContextBreakdown } from "@/lib/types";
|
|
18
|
-
|
|
19
|
-
function fmtTok(n: number): string {
|
|
20
|
-
return n >= 1000 ? `${(n / 1000).toFixed(1)}K` : String(Math.round(n));
|
|
21
|
-
}
|
|
9
|
+
import type { ContextBreakdown, TrajTokens } from "@/lib/types";
|
|
22
10
|
|
|
23
|
-
|
|
24
|
-
interface Segment {
|
|
25
|
-
key: string;
|
|
26
|
-
label: string;
|
|
27
|
-
tokens: number;
|
|
28
|
-
color: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function ContextMeter({
|
|
32
|
-
percent,
|
|
33
|
-
tokens,
|
|
34
|
-
contextWindow,
|
|
35
|
-
source,
|
|
36
|
-
}: {
|
|
11
|
+
export function ContextMeter({ percent, tokens, contextWindow, model, source, label }: {
|
|
37
12
|
percent: number | null;
|
|
38
13
|
tokens: number | null;
|
|
39
14
|
contextWindow: number | null;
|
|
40
|
-
/**
|
|
15
|
+
/** 当前会话的模型:有它才能在弹窗里改上下文窗口 */
|
|
16
|
+
model?: { provider: string; id: string } | null;
|
|
17
|
+
/** 覆盖「上下文估算」这个措辞:真实用量(如旁聊)不该叫估算 */
|
|
18
|
+
label?: string;
|
|
41
19
|
source?: {
|
|
42
20
|
systemChars: number;
|
|
43
|
-
messages: { role?: string; content: Array<{ type: string; text?: string; thinking?: string; arguments?: unknown }
|
|
21
|
+
messages: Array<{ role?: string; content: Array<{ type: string; text?: string; thinking?: string; arguments?: unknown; name?: string }>; usage?: TrajTokens; stopReason?: string }>;
|
|
44
22
|
breakdown?: ContextBreakdown;
|
|
45
|
-
/** 冷会话里点「现在统计」:启动会话(prepare),System Prompt 与工具定义才算得出来 */
|
|
46
23
|
loadFull?: () => Promise<unknown> | unknown;
|
|
47
24
|
};
|
|
48
25
|
}) {
|
|
@@ -52,154 +29,246 @@ export function ContextMeter({
|
|
|
52
29
|
const { t } = useI18n();
|
|
53
30
|
const cold = Boolean(source?.breakdown?.cold);
|
|
54
31
|
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
if (!cold) setCounting(false);
|
|
58
|
-
}, [cold]);
|
|
59
|
-
|
|
32
|
+
useEffect(() => { if (!cold) setCounting(false); }, [cold]);
|
|
60
33
|
useEffect(() => {
|
|
61
34
|
if (!open) return;
|
|
62
|
-
const h = (e: MouseEvent) => {
|
|
63
|
-
if (!ref.current?.contains(e.target as Node)) setOpen(false);
|
|
64
|
-
};
|
|
35
|
+
const h = (e: MouseEvent) => { if (!ref.current?.contains(e.target as Node)) setOpen(false); };
|
|
65
36
|
document.addEventListener("mousedown", h);
|
|
66
37
|
return () => document.removeEventListener("mousedown", h);
|
|
67
38
|
}, [open]);
|
|
68
39
|
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
const
|
|
40
|
+
// 流式期间只在打开弹窗后扫描,分类是实际文本字符数,不是假设的 token 数。
|
|
41
|
+
const chars = useMemo(() => classifyMessageChars(open ? source?.messages ?? [] : []), [open, source]);
|
|
42
|
+
const lastUsage = useMemo(() => open ? source?.messages.findLast((m) => m.role === "assistant" && m.stopReason !== "error" && m.stopReason !== "aborted" && m.stopReason !== "pending" &&
|
|
43
|
+
(m.usage?.input ?? 0) + (m.usage?.cacheRead ?? 0) + (m.usage?.cacheWrite ?? 0) + (m.usage?.output ?? 0) > 0)?.usage : undefined, [open, source]);
|
|
44
|
+
const measuredInput = lastUsage ? (lastUsage.input ?? 0) + (lastUsage.cacheRead ?? 0) + (lastUsage.cacheWrite ?? 0) : null;
|
|
72
45
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
agentToolCall: Math.max(0, Math.round(chars.agentToolCall / 4)),
|
|
82
|
-
toolOutput: Math.max(0, Math.round(chars.toolOutput / 4)),
|
|
83
|
-
};
|
|
84
|
-
}, [open, source]);
|
|
46
|
+
// 刚改过窗口:服务端已落盘,但页面上的 contextWindow 还是改之前的值(冷会话要等下一次快照)。
|
|
47
|
+
// 在它变之前先显示刚保存的值;contextWindow 一变(快照 / 用量追上来,或别处改了)就以 props 为准。
|
|
48
|
+
const modelKey = model ? `${model.provider}/${model.id}` : "";
|
|
49
|
+
const [applied, setApplied] = useState<{ key: string; from: number; value: number } | null>(null);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (applied && (applied.key !== modelKey || contextWindow !== applied.from)) setApplied(null);
|
|
52
|
+
}, [applied, modelKey, contextWindow]);
|
|
53
|
+
const override = applied && applied.key === modelKey && contextWindow === applied.from ? applied.value : null;
|
|
85
54
|
|
|
86
|
-
const
|
|
87
|
-
const
|
|
55
|
+
const known = tokens !== null && Number.isFinite(tokens) && tokens >= 0;
|
|
56
|
+
const used = known ? tokens : 0;
|
|
57
|
+
const window_ = override ?? contextWindow ?? 0;
|
|
58
|
+
const recomputed = override !== null && known;
|
|
59
|
+
const p = recomputed ? used / override * 100 : percent ?? 0;
|
|
60
|
+
/** 圆环与悬停提示仍用 SDK 估算(那是「占窗口多少」),弹窗正文只留 API 出来的真实数字 */
|
|
61
|
+
const usageLabel = percent === null && !recomputed ? t.contextUnknown : `${fmtPercent(p)} ${label ?? t.contextEstimated}`;
|
|
88
62
|
const r = 5.5;
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
63
|
+
const circumference = 2 * Math.PI * r;
|
|
64
|
+
const reserved = Math.max(0, source?.breakdown?.autoCompactBuffer ?? 0);
|
|
65
|
+
/**
|
|
66
|
+
* 进度条与 Free Space 的「已用」基准:优先最近一次请求的真实输入(API 报的),
|
|
67
|
+
* 没测到才退回 SDK 估算;两者都没有就不画(未知不能当空闲)。
|
|
68
|
+
*/
|
|
69
|
+
const anchor = window_ > 0 ? (measuredInput !== null ? Math.min(measuredInput, window_) : known ? Math.min(used, window_) : null) : null;
|
|
70
|
+
// 超过窗口时预留不再画到窗口外;用量未知不能将整扇窗口显示为 Free Space。
|
|
71
|
+
const visibleReserved = anchor === null ? 0 : Math.min(reserved, Math.max(0, window_ - anchor));
|
|
72
|
+
const free = anchor === null ? null : Math.max(0, window_ - anchor - reserved);
|
|
73
|
+
const b = source?.breakdown?.characters;
|
|
74
|
+
const rows = [
|
|
75
|
+
{ key: "system", label: t.contextSystemPrompt, value: b?.systemPrompt, color: "var(--dsw-accent)" },
|
|
76
|
+
{ key: "builtin", label: t.contextSystemTools, value: b?.systemTools, color: "#8B7EC8" },
|
|
77
|
+
{ key: "custom", label: t.contextCustomTools, value: b?.customTools, color: "#B39DDB" },
|
|
78
|
+
{ key: "background", label: t.contextBackground, value: b?.background, color: "#5B8DEF" },
|
|
79
|
+
{ key: "memory", label: t.contextMemory, value: b?.memory, color: "#5FA8D3" },
|
|
80
|
+
{ key: "skills", label: t.contextSkills, value: b?.skills, color: "#4FB286" },
|
|
81
|
+
{ key: "user", label: t.contextUserMessages, value: chars.user, color: "var(--dsw-success)" },
|
|
82
|
+
{ key: "text", label: t.contextAgentText, value: chars.agentText, color: "#7BC96F" },
|
|
83
|
+
{ key: "thinking", label: t.contextAgentThinking, value: chars.agentThinking, color: "#C9A66B" },
|
|
84
|
+
{ key: "call", label: t.contextAgentToolCall, value: chars.agentToolCall, color: "#D3A15F" },
|
|
85
|
+
{ key: "output", label: t.contextToolOutput, value: chars.toolOutput, color: "var(--dsw-warn)" },
|
|
86
|
+
{ key: "compacted", label: t.contextCompacted, value: b?.compacted, color: "#A05A78" },
|
|
87
|
+
].filter((row) => row.value !== undefined && row.value > 0);
|
|
88
|
+
const charTotal = rows.reduce((sum, row) => sum + (row.value ?? 0), 0);
|
|
89
|
+
/**
|
|
90
|
+
* 进度条整条 = 全部上下文窗口:已用那一截按各类**字符数比例**分段(API 不给分类 token,
|
|
91
|
+
* 所以只摊比例、不报每类的 token 数),剩下的部分就是背景色的 Free Space。
|
|
92
|
+
*/
|
|
93
|
+
const segments = anchor === null || charTotal === 0 ? [] : rows.map((row) => ({
|
|
94
|
+
key: row.key,
|
|
95
|
+
label: row.label,
|
|
96
|
+
value: row.value as number,
|
|
97
|
+
color: row.color,
|
|
98
|
+
width: anchor / window_ * 100 * ((row.value as number) / charTotal),
|
|
99
|
+
}));
|
|
119
100
|
|
|
120
101
|
return (
|
|
121
102
|
<div ref={ref} className="relative">
|
|
122
|
-
<button
|
|
123
|
-
className="icon-btn"
|
|
124
|
-
style={{ width: 28, height: 28 }}
|
|
125
|
-
title={usageLabel}
|
|
126
|
-
onClick={() => setOpen((o) => !o)}
|
|
127
|
-
>
|
|
103
|
+
<button className="icon-btn" style={{ width: 28, height: 28 }} title={usageLabel} aria-label={usageLabel} onClick={() => setOpen((o) => !o)}>
|
|
128
104
|
<svg width="14" height="14" viewBox="0 0 14 14">
|
|
129
105
|
<circle cx="7" cy="7" r={r} fill="none" stroke="var(--dsw-border-l3)" strokeWidth="2" />
|
|
130
|
-
<circle
|
|
131
|
-
|
|
132
|
-
fill="none"
|
|
133
|
-
stroke={p != null && p > 85 ? "var(--dsw-warn)" : "var(--dsw-accent)"}
|
|
134
|
-
strokeWidth="2"
|
|
135
|
-
strokeDasharray={`${(c * shown) / 100} ${c}`}
|
|
136
|
-
transform="rotate(-90 7 7)"
|
|
137
|
-
strokeLinecap="round"
|
|
138
|
-
/>
|
|
106
|
+
<circle cx="7" cy="7" r={r} fill="none" stroke={p > 85 ? "var(--dsw-warn)" : "var(--dsw-accent)"} strokeWidth="2"
|
|
107
|
+
strokeDasharray={`${circumference * Math.max(0, Math.min(100, p)) / 100} ${circumference}`} transform="rotate(-90 7 7)" strokeLinecap="round" />
|
|
139
108
|
</svg>
|
|
140
109
|
</button>
|
|
141
110
|
{open && (
|
|
142
|
-
<div
|
|
143
|
-
className="
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
<div style={{ color: "var(--dsw-label-tertiary)" }} className="mt-1">
|
|
150
|
-
{tokens === null ? "—" : `~${fmtTok(used)}`} / {window_ > 0 ? fmtTok(window_) : "—"}
|
|
151
|
-
</div>
|
|
152
|
-
|
|
153
|
-
{/* 分段占用条(mac 磁盘存储条式多色分段,含 Free Space) */}
|
|
154
|
-
{usedSum > 0 && (
|
|
155
|
-
<div className="mt-3 flex h-2 w-full overflow-hidden rounded-full" style={{ background: "var(--dsw-selector)" }} aria-hidden>
|
|
156
|
-
{segments.filter((s) => s.tokens > 0).map((s) => (
|
|
157
|
-
<span key={s.key} style={{ width: bar(s.tokens), background: s.color }} />
|
|
111
|
+
<div className="popover absolute bottom-9 right-0 z-50 w-72 p-4" style={{ fontSize: "var(--dsh-content-font-size-secondary)" }}>
|
|
112
|
+
{measuredInput !== null && <div className="mt-2 flex items-center justify-between gap-2"><span>{t.contextApiInput}</span><span title={`${measuredInput} tok`}>{fmtTokens(measuredInput)} tok</span></div>}
|
|
113
|
+
{window_ > 0 && (
|
|
114
|
+
<div className="mt-3 flex h-2 w-full overflow-hidden rounded-full" style={{ background: "var(--dsw-selector)" }} data-testid="context-bar" aria-hidden>
|
|
115
|
+
{segments.map((segment) => (
|
|
116
|
+
<span key={segment.key} data-segment={segment.key} className="flex-none" title={`${segment.label}:${fmtTokens(segment.value)} ${t.contextChars}`}
|
|
117
|
+
style={{ width: `${segment.width}%`, background: segment.color }} />
|
|
158
118
|
))}
|
|
159
|
-
{
|
|
119
|
+
{visibleReserved > 0 && <span data-segment="reserved" className="flex-none" style={{ width: `${visibleReserved / window_ * 100}%`, background: "var(--dsw-border-l3)" }} />}
|
|
160
120
|
</div>
|
|
161
121
|
)}
|
|
162
122
|
<div className="mt-2 flex flex-col gap-1" style={{ color: "var(--dsw-label-tertiary)" }}>
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
))}
|
|
166
|
-
{window_ > 0 && <SegmentRow label={t.contextFreeSpace} tokens={free} color="var(--dsw-selector)" />}
|
|
123
|
+
{reserved > 0 && <DataRow label={t.contextAutoCompactBuffer} value={`${fmtTokens(reserved)} tok`} exact={`${reserved} tok`} color="var(--dsw-border-l3)" />}
|
|
124
|
+
{window_ > 0 && <DataRow label={t.contextFreeSpace} value={free === null ? "—" : `~${fmtTokens(free)} tok`} color="var(--dsw-selector)" />}
|
|
167
125
|
</div>
|
|
168
|
-
{
|
|
126
|
+
{/* 进度条的图例:颜色跟上面的分段一一对应,数字是真实字符数 */}
|
|
127
|
+
<div className="mt-3 flex flex-col gap-1" style={{ color: "var(--dsw-label-tertiary)" }}>
|
|
128
|
+
{rows.map((row) => <DataRow key={row.key} label={row.label} value={`${fmtTokens(row.value!)} ${t.contextChars}`} exact={`${row.value} ${t.contextChars}`} color={row.color} />)}
|
|
129
|
+
</div>
|
|
130
|
+
{cold && source?.loadFull && (
|
|
169
131
|
<div className="mt-3 flex items-center justify-between gap-2" style={{ color: "var(--dsw-label-tertiary)" }}>
|
|
170
132
|
<span className="min-w-0" style={{ fontSize: 11.5 }}>{t.contextColdHint}</span>
|
|
171
|
-
<button
|
|
172
|
-
type="button"
|
|
173
|
-
className="btn-outline flex-none"
|
|
174
|
-
style={{ height: 24, padding: "0 10px", fontSize: 12 }}
|
|
175
|
-
disabled={counting}
|
|
133
|
+
<button type="button" className="btn-outline flex-none" style={{ height: 24, padding: "0 10px", fontSize: 12 }} disabled={counting}
|
|
176
134
|
onClick={() => {
|
|
177
135
|
setCounting(true);
|
|
178
|
-
// 启动失败(例如没配模型)就把按钮还回来;成功时等 context_breakdown 事件把冷标记去掉
|
|
179
136
|
void Promise.resolve(source.loadFull?.()).then((result) => {
|
|
180
137
|
if ((result as { success?: boolean } | undefined)?.success === false) setCounting(false);
|
|
181
138
|
}, () => setCounting(false));
|
|
182
|
-
}}
|
|
183
|
-
>
|
|
184
|
-
{counting ? t.contextCounting : t.contextCountNow}
|
|
185
|
-
</button>
|
|
139
|
+
}}>{counting ? t.contextCounting : t.contextCountNow}</button>
|
|
186
140
|
</div>
|
|
187
|
-
)
|
|
141
|
+
)}
|
|
142
|
+
{model && window_ > 0 && (
|
|
143
|
+
<WindowEditor key={modelKey} model={model} value={window_} onSaved={(value) => setApplied({ key: modelKey, from: contextWindow ?? 0, value })} />
|
|
144
|
+
)}
|
|
188
145
|
</div>
|
|
189
146
|
)}
|
|
190
147
|
</div>
|
|
191
148
|
);
|
|
192
149
|
}
|
|
193
150
|
|
|
194
|
-
function
|
|
195
|
-
|
|
151
|
+
async function postModels<T>(body: Record<string, unknown>): Promise<T> {
|
|
152
|
+
const response = await fetch("/api/models", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
153
|
+
const payload = (await response.json().catch(() => null)) as { success?: boolean; data?: T; error?: string } | null;
|
|
154
|
+
if (!response.ok || !payload?.success) throw new Error(payload?.error || `request failed (${response.status})`);
|
|
155
|
+
return payload.data as T;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 弹窗底部的上下文窗口:平时显示精确值,「修改」后填数字保存。
|
|
160
|
+
* 不用滑块:模型的真实上限是 128000、200000 这类精确值,拖出来的近似值多一点上游就 400。
|
|
161
|
+
* 内置目录有原值就并排给出、填大了提醒;改的是该模型的全局配置,这一点写在输入框下面。
|
|
162
|
+
*/
|
|
163
|
+
function WindowEditor({ model, value, onSaved }: { model: { provider: string; id: string }; value: number; onSaved: (value: number) => void }) {
|
|
164
|
+
const { t } = useI18n();
|
|
165
|
+
const [editing, setEditing] = useState(false);
|
|
166
|
+
const [text, setText] = useState("");
|
|
167
|
+
/** undefined = 还在查(不显示上限提示,免得先闪一句「没有内置值」) */
|
|
168
|
+
const [builtin, setBuiltin] = useState<number | null | undefined>(undefined);
|
|
169
|
+
const [saving, setSaving] = useState(false);
|
|
170
|
+
const [error, setError] = useState<string | null>(null);
|
|
171
|
+
const infoSeq = useRef(0);
|
|
172
|
+
const parsed = parseCapacity(text);
|
|
173
|
+
const target = parsed !== undefined && !Number.isNaN(parsed) ? parsed : null;
|
|
174
|
+
const valid = target !== null;
|
|
175
|
+
const over = target !== null && typeof builtin === "number" && target > builtin;
|
|
176
|
+
const exact = (n: number) => n.toLocaleString("en-US");
|
|
177
|
+
|
|
178
|
+
const begin = () => {
|
|
179
|
+
setEditing(true);
|
|
180
|
+
setText(formatCapacity(value));
|
|
181
|
+
setError(null);
|
|
182
|
+
setBuiltin(undefined);
|
|
183
|
+
const seq = ++infoSeq.current;
|
|
184
|
+
void postModels<{ builtin: number | null }>({ action: "contextWindowInfo", providerId: model.provider, modelId: model.id }).then(
|
|
185
|
+
(info) => { if (seq === infoSeq.current) setBuiltin(info.builtin); },
|
|
186
|
+
() => { if (seq === infoSeq.current) setBuiltin(null); },
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
const cancel = () => {
|
|
190
|
+
infoSeq.current += 1;
|
|
191
|
+
setEditing(false);
|
|
192
|
+
};
|
|
193
|
+
const save = async () => {
|
|
194
|
+
if (target === null || saving) return;
|
|
195
|
+
setSaving(true);
|
|
196
|
+
setError(null);
|
|
197
|
+
try {
|
|
198
|
+
const info = await postModels<{ contextWindow: number }>({ action: "setContextWindow", providerId: model.provider, modelId: model.id, contextWindow: target });
|
|
199
|
+
onSaved(info.contextWindow);
|
|
200
|
+
setEditing(false);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
203
|
+
} finally {
|
|
204
|
+
setSaving(false);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if (!editing) {
|
|
209
|
+
return (
|
|
210
|
+
<div className="hairline-t mt-3 flex items-center justify-between gap-2 pt-3" style={{ color: "var(--dsw-label-tertiary)" }}>
|
|
211
|
+
<span>{t.contextWindowLabel}</span>
|
|
212
|
+
<span className="flex items-center gap-2">
|
|
213
|
+
<span style={{ color: "var(--dsw-label-secondary)", fontFamily: "var(--font-mono)" }}>{exact(value)}</span>
|
|
214
|
+
<button type="button" className="btn-outline" style={{ height: 22, padding: "0 8px", fontSize: 11.5 }} onClick={begin}>{t.contextWindowEdit}</button>
|
|
215
|
+
</span>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
196
219
|
return (
|
|
197
|
-
<div className="
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
220
|
+
<div className="hairline-t mt-3 pt-3" style={{ color: "var(--dsw-label-tertiary)" }}>
|
|
221
|
+
<div>{t.contextWindowLabel}</div>
|
|
222
|
+
<div className="mt-1.5 flex items-center gap-1.5">
|
|
223
|
+
<input
|
|
224
|
+
autoFocus
|
|
225
|
+
value={text}
|
|
226
|
+
onChange={(e) => setText(e.target.value)}
|
|
227
|
+
onKeyDown={(e) => {
|
|
228
|
+
if (e.key === "Enter") {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
void save();
|
|
231
|
+
} else if (e.key === "Escape") {
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
e.stopPropagation();
|
|
234
|
+
cancel();
|
|
235
|
+
}
|
|
236
|
+
}}
|
|
237
|
+
aria-label={t.contextWindowLabel}
|
|
238
|
+
aria-invalid={!valid}
|
|
239
|
+
spellCheck={false}
|
|
240
|
+
className="min-w-0 flex-1 rounded-md px-2"
|
|
241
|
+
style={{ height: 26, fontSize: 12.5, fontFamily: "var(--font-mono)", color: "var(--dsw-label-primary)", background: "var(--dsw-hover)", border: `0.5px solid ${valid ? "var(--dsw-border-l2)" : "var(--dsw-danger)"}` }}
|
|
242
|
+
/>
|
|
243
|
+
<button type="button" className="btn-primary-white flex-none" style={{ height: 26, padding: "0 10px", fontSize: 12 }} disabled={!valid || saving} onClick={() => void save()}>
|
|
244
|
+
{saving ? t.saving : t.save}
|
|
245
|
+
</button>
|
|
246
|
+
<button type="button" className="btn-outline flex-none" style={{ height: 26, padding: "0 10px", fontSize: 12 }} disabled={saving} onClick={cancel}>{t.cancel}</button>
|
|
247
|
+
</div>
|
|
248
|
+
{typeof builtin === "number" && (
|
|
249
|
+
<div className="mt-1.5 flex items-center gap-1.5" style={{ fontSize: 11.5 }}>
|
|
250
|
+
<span>{t.contextWindowBuiltin.replace("{value}", exact(builtin))}</span>
|
|
251
|
+
{target !== builtin && (
|
|
252
|
+
<button type="button" className="underline" style={{ color: "var(--dsw-label-secondary)" }} onClick={() => setText(formatCapacity(builtin))}>{t.contextWindowUseBuiltin}</button>
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
)}
|
|
256
|
+
{!valid ? (
|
|
257
|
+
<div className="mt-1" style={{ fontSize: 11.5, color: "var(--dsw-danger)" }}>{t.contextWindowInvalid}</div>
|
|
258
|
+
) : over ? (
|
|
259
|
+
<div className="mt-1" style={{ fontSize: 11.5, color: "var(--dsw-warn)" }}>{t.contextWindowOverBuiltin}</div>
|
|
260
|
+
) : builtin === null ? (
|
|
261
|
+
<div className="mt-1" style={{ fontSize: 11.5 }}>{t.contextWindowNoBuiltin}</div>
|
|
262
|
+
) : null}
|
|
263
|
+
<div className="mt-1" style={{ fontSize: 11.5 }}>{t.contextWindowScope}</div>
|
|
264
|
+
{error && <div className="mt-1" role="alert" style={{ fontSize: 11.5, color: "var(--dsw-danger)" }}>{error}</div>}
|
|
203
265
|
</div>
|
|
204
266
|
);
|
|
205
267
|
}
|
|
268
|
+
|
|
269
|
+
function DataRow({ label, value, exact, color }: { label: string; value: string; exact?: string; color: string }) {
|
|
270
|
+
return <div className="flex items-center justify-between gap-2">
|
|
271
|
+
<span className="flex items-center gap-1.5"><span className="inline-block rounded-full" style={{ width: 7, height: 7, background: color }} />{label}</span>
|
|
272
|
+
<span title={exact}>{value}</span>
|
|
273
|
+
</div>;
|
|
274
|
+
}
|
|
@@ -18,7 +18,7 @@ import type { GrowthApi } from "@/hooks/useGrowth";
|
|
|
18
18
|
import { useI18n } from "@/i18n";
|
|
19
19
|
import type { TreeNode } from "@/lib/growth-tree";
|
|
20
20
|
|
|
21
|
-
/**
|
|
21
|
+
/** 抽屉入口:项目 → 提示词 → Git 详情 → 会话导入(旁聊已独立成右侧面板,不再共用这一列)。 */
|
|
22
22
|
export type DrawerSection = "project" | "prompts" | "git" | "import";
|
|
23
23
|
|
|
24
24
|
export function DrawerPanel({
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 文件页签:面包屑 + 模式(变更 / 内容 / 渲染)+ 折叠未改动 + 上一处 / 下一处 + 引用 / 编辑器打开。
|
|
7
7
|
* 「变更」= 整文件行内 diff(FullDiff);「内容」= 所选步的全文(被删文件显示删除前);「渲染」= Markdown。
|
|
8
8
|
*/
|
|
9
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
9
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
10
10
|
import ReactMarkdown from "react-markdown";
|
|
11
11
|
import rehypeHighlight from "rehype-highlight";
|
|
12
12
|
import remarkGfm from "remark-gfm";
|
|
@@ -46,6 +46,10 @@ function cachedJson<T>(url: string): Promise<T> {
|
|
|
46
46
|
return p;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/** 文件夹预览:一页目录条目(分页;后端已按 .gitignore 过滤) */
|
|
50
|
+
interface DirEntry { name: string; kind: "dir" | "file"; size?: number }
|
|
51
|
+
interface DirPage { dir: string; entries: DirEntry[]; nextOffset: number | null; truncated: boolean }
|
|
52
|
+
|
|
49
53
|
interface Loaded {
|
|
50
54
|
key: string;
|
|
51
55
|
diff: DiffLine[] | null;
|
|
@@ -94,7 +98,7 @@ export function FileViewer({
|
|
|
94
98
|
onClose: () => void;
|
|
95
99
|
onSelectTab: (path: string | null) => void;
|
|
96
100
|
onCloseTab: (path: string) => void;
|
|
97
|
-
onOpen: (path: string, opts?: { from?: string; lazy?: boolean }) => void;
|
|
101
|
+
onOpen: (path: string, opts?: { from?: string; lazy?: boolean; kind?: "dir" | "file" }) => void;
|
|
98
102
|
onReference?: (path: string) => void;
|
|
99
103
|
onOpenEditor?: (path: string) => void;
|
|
100
104
|
}) {
|
|
@@ -109,6 +113,8 @@ export function FileViewer({
|
|
|
109
113
|
const [treeFilter, setTreeFilter] = useState("");
|
|
110
114
|
const [reveal, setReveal] = useState<{ path: string; key: number } | null>(null);
|
|
111
115
|
const [loaded, setLoaded] = useState<Loaded | null>(null);
|
|
116
|
+
const [dirPage, setDirPage] = useState<(DirPage & { key: string }) | null>(null);
|
|
117
|
+
const [dirMore, setDirMore] = useState<DirEntry[]>([]);
|
|
112
118
|
const diffRef = useRef<FullDiffHandle>(null);
|
|
113
119
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
114
120
|
const quickInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -121,15 +127,56 @@ export function FileViewer({
|
|
|
121
127
|
// 切页签:模式按是否有改动自动选,用户手动切过才保留
|
|
122
128
|
useEffect(() => {
|
|
123
129
|
setModeTouched(false);
|
|
130
|
+
setDirFallback(null);
|
|
124
131
|
}, [state.active]);
|
|
125
132
|
useEffect(() => {
|
|
126
133
|
if (modeTouched || !activeTab) return;
|
|
127
134
|
setMode(change && !activeTab.lazy ? "diff" : isMarkdown ? "render" : "content");
|
|
128
135
|
}, [activeTab, change, isMarkdown, modeTouched]);
|
|
129
136
|
|
|
137
|
+
/** 目录兜底:文件读取报 400(不存在 / 不是文件)时探一下目录接口,成功就当文件夹显示 */
|
|
138
|
+
const [dirFallback, setDirFallback] = useState<string | null>(null);
|
|
139
|
+
const dirPath = activeTab?.kind === "dir" ? activeTab.path : dirFallback;
|
|
140
|
+
const isDir = Boolean(dirPath);
|
|
141
|
+
// 文件夹:列目录(分页,后端已按 .gitignore 过滤),不读文件内容
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!dirPath || !cwd || !activeTab) {
|
|
144
|
+
setDirPage(null);
|
|
145
|
+
setDirMore([]);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
// @src/ 这类带尾斜杠的引用:列目录时去掉
|
|
149
|
+
const path_ = dirPath.replace(/\/+$/, "");
|
|
150
|
+
const key = `dir|${path_}`;
|
|
151
|
+
let alive = true;
|
|
152
|
+
setDirMore([]);
|
|
153
|
+
void (async () => {
|
|
154
|
+
try {
|
|
155
|
+
const data = await requestJson<DirPage>(`/api/files?cwd=${encodeURIComponent(cwd)}&path=${encodeURIComponent(path_)}&offset=0`);
|
|
156
|
+
if (alive) setDirPage({ key, ...data });
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (alive) setDirPage({ key, dir: path_, entries: [], nextOffset: null, truncated: false, error: error instanceof Error ? error.message : t.viewerLoadFailed } as DirPage & { key: string; error?: string });
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
return () => {
|
|
162
|
+
alive = false;
|
|
163
|
+
};
|
|
164
|
+
}, [activeTab, dirPath, cwd, t.viewerLoadFailed]);
|
|
165
|
+
|
|
166
|
+
/** 目录还有下一页时接着拉(大目录:node_modules 这种已被忽略,剩下的也不会太多) */
|
|
167
|
+
const loadMore = useCallback(async () => {
|
|
168
|
+
if (!activeTab || !cwd || !dirPage?.nextOffset) return;
|
|
169
|
+
try {
|
|
170
|
+
const data = await requestJson<DirPage>(`/api/files?cwd=${encodeURIComponent(cwd)}&path=${encodeURIComponent(activeTab.path)}&offset=${dirPage.nextOffset}`);
|
|
171
|
+
setDirPage({ key: `dir|${activeTab.path}`, ...data, entries: [...dirPage.entries, ...data.entries] });
|
|
172
|
+
} catch {
|
|
173
|
+
// 下一页失败就留在当前这页,用户可以再点
|
|
174
|
+
}
|
|
175
|
+
}, [activeTab, cwd, dirPage]);
|
|
176
|
+
|
|
130
177
|
// 取数据
|
|
131
178
|
useEffect(() => {
|
|
132
|
-
if (!activeTab) {
|
|
179
|
+
if (!activeTab || activeTab.kind === "dir") {
|
|
133
180
|
setLoaded(null);
|
|
134
181
|
return;
|
|
135
182
|
}
|
|
@@ -157,6 +204,15 @@ export function FileViewer({
|
|
|
157
204
|
done({ content: data.content, binary: data.binary, truncated: data.truncated, notice });
|
|
158
205
|
} catch (error) {
|
|
159
206
|
if ((error as { status?: number }).status !== 400) throw error;
|
|
207
|
+
// 400 有两种:文件不存在 / 路径其实是目录。探一下目录接口,能列出条目就当文件夹打开
|
|
208
|
+
try {
|
|
209
|
+
await requestJson<DirPage>(`/api/files?${q}&path=${encodeURIComponent(tab.path)}&offset=0`);
|
|
210
|
+
done({ notice: null });
|
|
211
|
+
setDirFallback(tab.path);
|
|
212
|
+
return;
|
|
213
|
+
} catch {
|
|
214
|
+
// 目录也列不出来:照旧报「文件不存在」
|
|
215
|
+
}
|
|
160
216
|
done({ notice: "missing" });
|
|
161
217
|
}
|
|
162
218
|
};
|
|
@@ -379,7 +435,7 @@ export function FileViewer({
|
|
|
379
435
|
{stepBadge}
|
|
380
436
|
</span>
|
|
381
437
|
)}
|
|
382
|
-
{!activeTab.lazy && (
|
|
438
|
+
{!activeTab.lazy && !isDir && (
|
|
383
439
|
<span className="pw-seg" role="radiogroup">
|
|
384
440
|
{(["diff", "content", ...(isMarkdown ? ["render" as const] : [])] as Mode[]).map((m) => (
|
|
385
441
|
<button
|
|
@@ -398,7 +454,7 @@ export function FileViewer({
|
|
|
398
454
|
))}
|
|
399
455
|
</span>
|
|
400
456
|
)}
|
|
401
|
-
{activeTab.lazy && isMarkdown && (
|
|
457
|
+
{!isDir && activeTab.lazy && isMarkdown && (
|
|
402
458
|
<span className="pw-seg" role="radiogroup">
|
|
403
459
|
{(["content", "render"] as Mode[]).map((m) => (
|
|
404
460
|
<button key={m} type="button" role="radio" aria-checked={mode === m} data-active={mode === m} onClick={() => { setModeTouched(true); setMode(m); }}>
|
|
@@ -407,7 +463,7 @@ export function FileViewer({
|
|
|
407
463
|
))}
|
|
408
464
|
</span>
|
|
409
465
|
)}
|
|
410
|
-
{isDiffMode && (
|
|
466
|
+
{isDiffMode && !isDir && (
|
|
411
467
|
<>
|
|
412
468
|
<button type="button" className="pw-chip" data-active={folded} onClick={() => setFolded((f) => !f)}>
|
|
413
469
|
{folded ? t.viewerFold : t.viewerUnfold}
|
|
@@ -439,7 +495,32 @@ export function FileViewer({
|
|
|
439
495
|
</div>
|
|
440
496
|
)}
|
|
441
497
|
{loaded?.truncated && <div className="pw-viewer-notice" data-kind="warn">{mode === "diff" ? t.viewerPatchTruncated : t.filePreviewTruncated}</div>}
|
|
442
|
-
{
|
|
498
|
+
{isDir ? (
|
|
499
|
+
<div className="min-h-0 flex-1 overflow-auto" data-testid="viewer-dir">
|
|
500
|
+
<div className="px-3 py-2" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
501
|
+
{t.viewerDirHint.replace("{n}", String(dirPage?.entries.length ?? 0))}
|
|
502
|
+
</div>
|
|
503
|
+
{dirPage?.entries.map((entry) => (
|
|
504
|
+
<button
|
|
505
|
+
key={entry.name}
|
|
506
|
+
type="button"
|
|
507
|
+
className="flex w-full items-center gap-2 px-3 py-1.5 text-left hover:bg-[var(--dsw-hover)]"
|
|
508
|
+
onClick={() => (entry.kind === "dir"
|
|
509
|
+
? onOpen(`${dirPath!.replace(/\/+$/, "")}/${entry.name}`, { kind: "dir", lazy: true })
|
|
510
|
+
: onOpen(`${dirPath!.replace(/\/+$/, "")}/${entry.name}`, { lazy: true }))}
|
|
511
|
+
>
|
|
512
|
+
<img className="pw-tree-icon" src={entry.kind === "dir" ? "/file-icons/folder-close.svg" : fileIconSrc(entry.name)} alt="" />
|
|
513
|
+
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 12.5, fontFamily: entry.kind === "dir" ? "inherit" : "var(--font-mono)" }}>{entry.name}</span>
|
|
514
|
+
{entry.kind === "file" && entry.size !== undefined && (
|
|
515
|
+
<span style={{ fontSize: 11, color: "var(--dsw-label-caption)", fontVariantNumeric: "tabular-nums" }}>{entry.size} B</span>
|
|
516
|
+
)}
|
|
517
|
+
</button>
|
|
518
|
+
))}
|
|
519
|
+
{dirPage?.nextOffset ? (
|
|
520
|
+
<button type="button" className="pw-chip mx-3 my-2" onClick={() => void loadMore()}>{t.viewerDirMore}</button>
|
|
521
|
+
) : null}
|
|
522
|
+
</div>
|
|
523
|
+
) : !loaded || loaded.key !== `${activeTab.path}|${mode}|${range?.from ?? ""}|${range?.to ?? ""}|${change?.status ?? ""}` ? (
|
|
443
524
|
<div className="flex min-h-0 flex-1 items-center justify-center" style={{ fontSize: 12.5, color: "var(--dsw-label-caption)" }}>{t.panelLoading}</div>
|
|
444
525
|
) : loaded.error ? (
|
|
445
526
|
<div className="flex min-h-0 flex-1 items-center justify-center" style={{ fontSize: 12.5, color: "var(--dsw-danger)" }}>{loaded.error}</div>
|
|
@@ -480,7 +561,7 @@ export function FileViewer({
|
|
|
480
561
|
selectedPath={null}
|
|
481
562
|
onToggleDir={growth.toggleDir}
|
|
482
563
|
onExpandLazy={growth.expandLazy}
|
|
483
|
-
onOpenFile={(node: TreeNode) => onOpen(node.path, { from: node.from, lazy: node.lazy })}
|
|
564
|
+
onOpenFile={(node: TreeNode) => onOpen(node.path, { from: node.from, lazy: node.lazy, kind: node.kind })}
|
|
484
565
|
onReference={onReference}
|
|
485
566
|
onOpenEditor={onOpenEditor}
|
|
486
567
|
revealPath={reveal?.path ?? null}
|