@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
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SIDE_CHAT_BODY_LIMIT, SIDE_CHAT_HISTORY_LIMIT, SIDE_CHAT_TEXT_LIMIT, type SideChatHistoryMessage } from "@/lib/side-chat/types";
|
|
2
|
+
|
|
3
|
+
/** Bound only the transmitted context; the browser retains the original transcript. */
|
|
4
|
+
export function requestHistory(messages: SideChatHistoryMessage[]): { history: SideChatHistoryMessage[]; truncated: boolean } {
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
const history: SideChatHistoryMessage[] = [];
|
|
7
|
+
// Reserve the other half for the question (including JSON escapes), model and envelope.
|
|
8
|
+
let remaining = Math.floor(SIDE_CHAT_BODY_LIMIT / 2) - 2;
|
|
9
|
+
let truncated = false;
|
|
10
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
11
|
+
if (history.length === SIDE_CHAT_HISTORY_LIMIT) { truncated = true; break; }
|
|
12
|
+
const message = messages[i];
|
|
13
|
+
let text = message.text;
|
|
14
|
+
if (text.length > SIDE_CHAT_TEXT_LIMIT) {
|
|
15
|
+
text = text.slice(-SIDE_CHAT_TEXT_LIMIT);
|
|
16
|
+
if (/^[\uDC00-\uDFFF]/.test(text)) text = text.slice(1);
|
|
17
|
+
truncated = true;
|
|
18
|
+
}
|
|
19
|
+
const entry = { role: message.role, text };
|
|
20
|
+
const size = encoder.encode(JSON.stringify(entry)).byteLength + 1;
|
|
21
|
+
if (size > remaining) { truncated = true; break; }
|
|
22
|
+
remaining -= size;
|
|
23
|
+
history.push(entry);
|
|
24
|
+
}
|
|
25
|
+
history.reverse();
|
|
26
|
+
// A retained transcript must begin with a question, not an orphaned answer.
|
|
27
|
+
while (history[0]?.role === "assistant") { history.shift(); truncated = true; }
|
|
28
|
+
return { history, truncated };
|
|
29
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { isSideChatId, isSideChatModel, type SideChatModel, type SideChatHistoryMessage, type SideChatProgress, type SideChatRequest, type SideChatUsage } from "@/lib/side-chat/types";
|
|
4
|
+
|
|
5
|
+
import { requestHistory } from "./request-history";
|
|
6
|
+
|
|
7
|
+
export interface SideChatMessage {
|
|
8
|
+
id: string;
|
|
9
|
+
role: "user" | "assistant";
|
|
10
|
+
text: string;
|
|
11
|
+
status?: "pending" | "done" | "error" | "aborted";
|
|
12
|
+
error?: string;
|
|
13
|
+
contextTruncated?: boolean;
|
|
14
|
+
lengthLimited?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface SideChatThread {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
messages: SideChatMessage[];
|
|
20
|
+
draft: string;
|
|
21
|
+
model?: SideChatModel;
|
|
22
|
+
request?: { id: string; revision: number };
|
|
23
|
+
/** 最近一次回答的真实用量与窗口:面板右下角的上下文环用它 */
|
|
24
|
+
usage?: SideChatUsage;
|
|
25
|
+
contextWindow?: number;
|
|
26
|
+
/** 这次上下文的真实字符分项(系统提示词 / 引用的主会话背景) */
|
|
27
|
+
characters?: { systemPrompt: number; background: number };
|
|
28
|
+
connectionLost?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface SideChatState {
|
|
31
|
+
currentThreadId: string;
|
|
32
|
+
threads: SideChatThread[];
|
|
33
|
+
storageFailed: boolean;
|
|
34
|
+
}
|
|
35
|
+
export const EMPTY_SIDE_CHAT: SideChatState = { currentThreadId: "", threads: [], storageFailed: false };
|
|
36
|
+
const PREFIX = "piweb.sidechat.v1.";
|
|
37
|
+
|
|
38
|
+
class ApiError extends Error {
|
|
39
|
+
constructor(message: string, readonly status: number) { super(message); }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pause(ms: number, signal: AbortSignal): Promise<void> {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
const finish = () => { clearTimeout(timer); signal.removeEventListener("abort", finish); resolve(); };
|
|
45
|
+
const timer = setTimeout(finish, ms);
|
|
46
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
47
|
+
if (signal.aborted) finish();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Browser-owned threads outlive panel unmounts. Only their own subscribers render on an answer update. */
|
|
52
|
+
export class SideChatStore {
|
|
53
|
+
private states = new Map<string, SideChatState>();
|
|
54
|
+
private listeners = new Map<string, Set<() => void>>();
|
|
55
|
+
private requests = new Map<string, AbortController>();
|
|
56
|
+
private saves = new Map<string, ReturnType<typeof setTimeout>>();
|
|
57
|
+
constructor(private options: { fetch: typeof fetch; storage: () => Storage; id: () => string } = {
|
|
58
|
+
fetch: (...args) => fetch(...args), storage: () => localStorage, id: () => crypto.randomUUID(),
|
|
59
|
+
}) {}
|
|
60
|
+
|
|
61
|
+
get = (sessionId: string | null): SideChatState => {
|
|
62
|
+
if (!sessionId) return EMPTY_SIDE_CHAT;
|
|
63
|
+
let state = this.states.get(sessionId);
|
|
64
|
+
if (state) return state;
|
|
65
|
+
try {
|
|
66
|
+
const raw = this.options.storage().getItem(PREFIX + sessionId);
|
|
67
|
+
if (raw) {
|
|
68
|
+
const saved = JSON.parse(raw) as SideChatState;
|
|
69
|
+
if (isSideChatId(saved.currentThreadId) && Array.isArray(saved.threads) && saved.threads.length && saved.threads.every((thread) =>
|
|
70
|
+
isSideChatId(thread.id) && typeof thread.title === "string" && typeof thread.draft === "string" && Array.isArray(thread.messages) && thread.messages.every((message) =>
|
|
71
|
+
isSideChatId(message.id) && (message.role === "user" || message.role === "assistant") && typeof message.text === "string") &&
|
|
72
|
+
(!thread.request || (isSideChatId(thread.request.id) && Number.isSafeInteger(thread.request.revision)))) && saved.threads.some((thread) => thread.id === saved.currentThreadId)) {
|
|
73
|
+
state = { ...saved, storageFailed: false, threads: saved.threads.map((thread) => ({ ...thread, model: isSideChatModel(thread.model) ? thread.model : undefined })) };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch { /* A unavailable or full browser store must not disable questions. */ }
|
|
77
|
+
if (!state) {
|
|
78
|
+
const thread = this.newThread();
|
|
79
|
+
state = { currentThreadId: thread.id, threads: [thread], storageFailed: false };
|
|
80
|
+
}
|
|
81
|
+
this.states.set(sessionId, state);
|
|
82
|
+
return state;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
flush = (): void => { for (const sessionId of this.states.keys()) this.save(sessionId); };
|
|
86
|
+
|
|
87
|
+
subscribe = (sessionId: string | null, listener: () => void): (() => void) => {
|
|
88
|
+
if (!sessionId) return () => {};
|
|
89
|
+
const listeners = this.listeners.get(sessionId) ?? new Set();
|
|
90
|
+
listeners.add(listener);
|
|
91
|
+
this.listeners.set(sessionId, listeners);
|
|
92
|
+
queueMicrotask(() => {
|
|
93
|
+
for (const thread of this.get(sessionId).threads) {
|
|
94
|
+
if (thread.request && !this.requests.has(thread.request.id)) void this.run(sessionId, thread.id, thread.request.id);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return () => { listeners.delete(listener); };
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
private newThread(model?: SideChatModel): SideChatThread { return { id: this.options.id(), title: "", messages: [], draft: "", ...(model ? { model: { ...model } } : {}) }; }
|
|
101
|
+
private notify(sessionId: string): void { for (const listener of this.listeners.get(sessionId) ?? []) listener(); }
|
|
102
|
+
private save(sessionId: string): void {
|
|
103
|
+
const timer = this.saves.get(sessionId);
|
|
104
|
+
if (timer) clearTimeout(timer);
|
|
105
|
+
this.saves.delete(sessionId);
|
|
106
|
+
const state = this.get(sessionId);
|
|
107
|
+
let failed = false;
|
|
108
|
+
try { this.options.storage().setItem(PREFIX + sessionId, JSON.stringify(state)); }
|
|
109
|
+
catch { failed = true; }
|
|
110
|
+
if (state.storageFailed !== failed) {
|
|
111
|
+
this.states.set(sessionId, { ...state, storageFailed: failed });
|
|
112
|
+
this.notify(sessionId);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
private update(sessionId: string, state: SideChatState, immediately = false): void {
|
|
116
|
+
this.states.set(sessionId, state);
|
|
117
|
+
this.notify(sessionId);
|
|
118
|
+
if (immediately) this.save(sessionId);
|
|
119
|
+
else if (!this.saves.has(sessionId)) this.saves.set(sessionId, setTimeout(() => this.save(sessionId), 300));
|
|
120
|
+
}
|
|
121
|
+
private updateThread(sessionId: string, threadId: string, change: (thread: SideChatThread) => SideChatThread, immediately = false): void {
|
|
122
|
+
const state = this.get(sessionId);
|
|
123
|
+
this.update(sessionId, { ...state, threads: state.threads.map((thread) => thread.id === threadId ? change(thread) : thread) }, immediately);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
setDraft(sessionId: string, text: string): void {
|
|
127
|
+
this.updateThread(sessionId, this.get(sessionId).currentThreadId, (thread) => ({ ...thread, draft: text }));
|
|
128
|
+
}
|
|
129
|
+
setModel(sessionId: string, model?: SideChatModel): void {
|
|
130
|
+
this.updateThread(sessionId, this.get(sessionId).currentThreadId, (thread) => ({ ...thread, model: model ? { ...model } : undefined }), true);
|
|
131
|
+
}
|
|
132
|
+
createThread(sessionId: string): void {
|
|
133
|
+
const state = this.get(sessionId);
|
|
134
|
+
const current = state.threads.find((thread) => thread.id === state.currentThreadId);
|
|
135
|
+
if (current && !current.messages.length && !current.draft) return;
|
|
136
|
+
const thread = this.newThread(current?.model);
|
|
137
|
+
this.update(sessionId, { ...state, currentThreadId: thread.id, threads: [...state.threads, thread] }, true);
|
|
138
|
+
}
|
|
139
|
+
selectThread(sessionId: string, threadId: string): void {
|
|
140
|
+
const state = this.get(sessionId);
|
|
141
|
+
if (state.threads.some((thread) => thread.id === threadId)) this.update(sessionId, { ...state, currentThreadId: threadId }, true);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
send(sessionId: string, retry = false): void {
|
|
145
|
+
const state = this.get(sessionId);
|
|
146
|
+
const thread = state.threads.find((item) => item.id === state.currentThreadId);
|
|
147
|
+
if (!thread || thread.request) return;
|
|
148
|
+
const previous = thread.messages.at(-1);
|
|
149
|
+
const retrying = retry && previous?.role === "assistant" && previous.status === "error" && thread.messages.at(-2)?.role === "user";
|
|
150
|
+
const text = (retrying ? thread.messages.at(-2)!.text : thread.draft).trim();
|
|
151
|
+
if (!text) return;
|
|
152
|
+
const requestId = this.options.id();
|
|
153
|
+
const historyMessages = retrying ? thread.messages.slice(0, -2) : thread.messages;
|
|
154
|
+
const availableHistory: SideChatHistoryMessage[] = historyMessages.filter((message) => message.text && (message.role === "user" || message.status === "done" || message.status === "aborted"))
|
|
155
|
+
.map(({ role, text }) => ({ role, text }));
|
|
156
|
+
const { history, truncated } = requestHistory(availableHistory);
|
|
157
|
+
const messages: SideChatMessage[] = retrying
|
|
158
|
+
? [...thread.messages.slice(0, -1), { id: previous!.id, role: "assistant", text: "", status: "pending", contextTruncated: truncated }]
|
|
159
|
+
: [...thread.messages, { id: this.options.id(), role: "user", text }, { id: this.options.id(), role: "assistant", text: "", status: "pending", contextTruncated: truncated }];
|
|
160
|
+
this.updateThread(sessionId, thread.id, (item) => ({ ...item, title: item.title || text.slice(0, 80), messages, draft: retrying ? item.draft : "", request: { id: requestId, revision: -1 }, connectionLost: false }), true);
|
|
161
|
+
void this.run(sessionId, thread.id, requestId, { requestId, threadId: thread.id, text, history, ...(thread.model ? { model: { ...thread.model } } : {}) });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private apply(sessionId: string, threadId: string, requestId: string, progress: SideChatProgress): void {
|
|
165
|
+
this.updateThread(sessionId, threadId, (thread) => {
|
|
166
|
+
if (thread.request?.id !== requestId) return thread;
|
|
167
|
+
const previousText = thread.messages.at(-1)?.text ?? "";
|
|
168
|
+
if (progress.textOffset !== undefined && progress.textOffset > previousText.length) throw new Error("side-chat answer cursor is out of sync");
|
|
169
|
+
const text = progress.textOffset === undefined ? progress.text : previousText.slice(0, progress.textOffset) + progress.text;
|
|
170
|
+
const status = !progress.done ? "pending" : progress.error ? "error" : progress.aborted ? "aborted" : "done";
|
|
171
|
+
return {
|
|
172
|
+
...thread, connectionLost: false, request: progress.done ? undefined : { id: requestId, revision: progress.revision },
|
|
173
|
+
// 只在这次响应真的报了就更新;没报就保持上一次的值(不回落成 0)
|
|
174
|
+
...(progress.usage ? { usage: progress.usage } : {}),
|
|
175
|
+
...(progress.contextWindow ? { contextWindow: progress.contextWindow } : {}),
|
|
176
|
+
...(progress.characters ? { characters: progress.characters } : {}),
|
|
177
|
+
messages: thread.messages.map((message, index) => index === thread.messages.length - 1 ? {
|
|
178
|
+
...message, text, status, error: progress.error, contextTruncated: message.contextTruncated || progress.contextTruncated, lengthLimited: progress.lengthLimited,
|
|
179
|
+
} : message),
|
|
180
|
+
};
|
|
181
|
+
}, progress.done);
|
|
182
|
+
}
|
|
183
|
+
private fail(sessionId: string, threadId: string, requestId: string, error: string): void {
|
|
184
|
+
const thread = this.get(sessionId).threads.find((item) => item.id === threadId);
|
|
185
|
+
this.apply(sessionId, threadId, requestId, { requestId, revision: 0, text: thread?.messages.at(-1)?.text ?? "", done: true, aborted: false, error });
|
|
186
|
+
}
|
|
187
|
+
private async response(response: Response): Promise<SideChatProgress> {
|
|
188
|
+
const json = await response.json();
|
|
189
|
+
if (!response.ok || !json.success) throw new ApiError(json.error || `side-chat request failed (${response.status})`, response.status);
|
|
190
|
+
const data = json.data as SideChatProgress;
|
|
191
|
+
if (!data || !isSideChatId(data.requestId) || !Number.isSafeInteger(data.revision) || typeof data.text !== "string" || typeof data.done !== "boolean" || (data.textOffset !== undefined && (!Number.isSafeInteger(data.textOffset) || data.textOffset < 0))) throw new Error("invalid side-chat response");
|
|
192
|
+
return data;
|
|
193
|
+
}
|
|
194
|
+
private async run(sessionId: string, threadId: string, requestId: string, initial?: SideChatRequest): Promise<void> {
|
|
195
|
+
if (this.requests.has(requestId)) return;
|
|
196
|
+
const controller = new AbortController();
|
|
197
|
+
this.requests.set(requestId, controller);
|
|
198
|
+
const url = `/api/agent/${sessionId}/side-chat`;
|
|
199
|
+
let revision = this.get(sessionId).threads.find((thread) => thread.id === threadId)?.request?.revision ?? -1;
|
|
200
|
+
try {
|
|
201
|
+
if (initial) {
|
|
202
|
+
const progress = await this.response(await this.options.fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(initial), signal: controller.signal }));
|
|
203
|
+
if (progress.requestId !== requestId) throw new Error("side-chat response belongs to a different request");
|
|
204
|
+
if (controller.signal.aborted) return;
|
|
205
|
+
this.apply(sessionId, threadId, requestId, progress);
|
|
206
|
+
if (progress.done) return;
|
|
207
|
+
revision = progress.revision;
|
|
208
|
+
}
|
|
209
|
+
let failures = 0;
|
|
210
|
+
while (!controller.signal.aborted) {
|
|
211
|
+
let progress: SideChatProgress;
|
|
212
|
+
try {
|
|
213
|
+
progress = await this.response(await this.options.fetch(`${url}?requestId=${encodeURIComponent(requestId)}&after=${revision}`, { cache: "no-store", signal: controller.signal }));
|
|
214
|
+
if (progress.requestId !== requestId) throw new Error("side-chat response belongs to a different request");
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (controller.signal.aborted) return;
|
|
217
|
+
if (error instanceof ApiError || ++failures >= 6) throw error;
|
|
218
|
+
this.updateThread(sessionId, threadId, (thread) => ({ ...thread, connectionLost: true }));
|
|
219
|
+
await pause(Math.min(500 * 2 ** (failures - 1), 5000), controller.signal);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (controller.signal.aborted) return;
|
|
223
|
+
failures = 0;
|
|
224
|
+
this.apply(sessionId, threadId, requestId, progress);
|
|
225
|
+
if (progress.done) return;
|
|
226
|
+
revision = progress.revision;
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (!controller.signal.aborted) this.fail(sessionId, threadId, requestId, error instanceof Error ? error.message : "side-chat request failed");
|
|
230
|
+
} finally {
|
|
231
|
+
if (this.requests.get(requestId) === controller) this.requests.delete(requestId);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
stop(sessionId: string): void {
|
|
236
|
+
const thread = this.get(sessionId).threads.find((item) => item.id === this.get(sessionId).currentThreadId);
|
|
237
|
+
const requestId = thread?.request?.id;
|
|
238
|
+
if (!thread || !requestId) return;
|
|
239
|
+
this.requests.get(requestId)?.abort();
|
|
240
|
+
this.apply(sessionId, thread.id, requestId, { requestId, revision: 0, text: thread.messages.at(-1)?.text ?? "", done: true, aborted: true });
|
|
241
|
+
void this.options.fetch(`/api/agent/${sessionId}/side-chat?requestId=${encodeURIComponent(requestId)}`, { method: "DELETE" })
|
|
242
|
+
.then(async (response) => { if (!response.ok) throw new Error((await response.json()).error || "failed to stop side-chat answer"); })
|
|
243
|
+
.catch((error) => this.updateThread(sessionId, thread.id, (current) => ({ ...current, messages: current.messages.map((message) => message.id === thread.messages.at(-1)?.id ? { ...message, error: error instanceof Error ? error.message : "failed to stop side-chat answer" } : message) }), true));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export const sideChatStore = new SideChatStore();
|
|
248
|
+
if (typeof window !== "undefined") window.addEventListener("pagehide", sideChatStore.flush);
|
|
@@ -8,11 +8,13 @@ import { useCallback, useState } from "react";
|
|
|
8
8
|
|
|
9
9
|
export interface ViewerTab {
|
|
10
10
|
path: string;
|
|
11
|
-
/**
|
|
11
|
+
/** 重命名前的路径(取 diff 用) */
|
|
12
12
|
from?: string;
|
|
13
|
-
/**
|
|
13
|
+
/** 快照之外的文件(node_modules 等):只能看磁盘上的当前内容 */
|
|
14
14
|
lazy?: boolean;
|
|
15
|
-
/**
|
|
15
|
+
/** dir = 打开的是文件夹:正文区列目录(可逐层进入),不是文件内容 */
|
|
16
|
+
kind?: "dir" | "file";
|
|
17
|
+
/** 内容由调用方直接提供(如技能文档):不取 diff、不读磁盘 */
|
|
16
18
|
staticContent?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -25,7 +27,7 @@ export interface ViewerState {
|
|
|
25
27
|
|
|
26
28
|
export function useFileViewer() {
|
|
27
29
|
const [state, setState] = useState<ViewerState>({ open: false, tabs: [], active: null });
|
|
28
|
-
const open = useCallback((path: string, opts: { from?: string; lazy?: boolean } = {}) => {
|
|
30
|
+
const open = useCallback((path: string, opts: { from?: string; lazy?: boolean; kind?: "dir" | "file" } = {}) => {
|
|
29
31
|
setState((s) => {
|
|
30
32
|
const exists = s.tabs.some((tab) => tab.path === path);
|
|
31
33
|
const tabs = exists ? s.tabs.map((tab) => (tab.path === path ? { ...tab, ...opts } : tab)) : [...s.tabs, { path, ...opts }];
|
package/src/hooks/useGrowth.ts
CHANGED
|
@@ -39,6 +39,8 @@ export interface GrowthApi {
|
|
|
39
39
|
range: { from: string; to: string } | null;
|
|
40
40
|
/** 所选范围的变更 */
|
|
41
41
|
changes: GrowthChange[];
|
|
42
|
+
/** 当前范围的完整 diff 已读取;加载或失败时不能将 [] 当成零改动。 */
|
|
43
|
+
changesReady?: boolean;
|
|
42
44
|
tree: TreeNode;
|
|
43
45
|
/** 所选轮的全部文件路径(快速打开用) */
|
|
44
46
|
filePaths: string[];
|
|
@@ -618,6 +620,7 @@ export function useGrowth({
|
|
|
618
620
|
scope,
|
|
619
621
|
range,
|
|
620
622
|
changes,
|
|
623
|
+
changesReady: inlineChanges !== null || Boolean(range && rangeChanges?.key === `${key}|${range.from}..${range.to}` && !rangeChanges.error),
|
|
621
624
|
tree,
|
|
622
625
|
filePaths,
|
|
623
626
|
expanded,
|
package/src/hooks/usePiWeb.ts
CHANGED
|
@@ -76,6 +76,10 @@ export const foldPiWebEvent = (state: PiWebState, evt: WebEvent): PiWebState =>
|
|
|
76
76
|
const s = { ...state };
|
|
77
77
|
switch (evt.type) {
|
|
78
78
|
case "delta": {
|
|
79
|
+
// 又开始出 token 了:不管有没有收到重试结束事件,重试行都收掉。
|
|
80
|
+
// 只靠 retry_end 不够——SDK 换了事件名、某条路径没发结束事件、旧服务端压根不发,
|
|
81
|
+
// 界面就会挂着「重试 1/5」而模型其实早在正常输出了(用户已反馈两次)。
|
|
82
|
+
s.retryNotice = null;
|
|
79
83
|
// 新服务端按稳定流 ID 定位;旧协议仍按最近 pending 消息兼容。
|
|
80
84
|
const msgs = [...s.messages];
|
|
81
85
|
// 从尾部找:增量几乎总是落在最后一条消息上,长会话里从头扫每个 token 都是一次全表遍历
|
|
@@ -251,11 +255,17 @@ export const foldPiWebEvent = (state: PiWebState, evt: WebEvent): PiWebState =>
|
|
|
251
255
|
s.growth = { ...s.growth, consent: evt.request };
|
|
252
256
|
return s;
|
|
253
257
|
case "retry":
|
|
254
|
-
//
|
|
258
|
+
// 只保留最后一次尝试:重试过程在界面上是「重试 3/5」这一条,而不是五行报错
|
|
255
259
|
s.retryNotice = evt.maxAttempts
|
|
256
260
|
? `重试 ${evt.attempt}/${evt.maxAttempts}${evt.message ? `:${evt.message}` : ""}`
|
|
257
261
|
: `重试 ${evt.attempt}${evt.message ? `:${evt.message}` : ""}`;
|
|
258
262
|
return s;
|
|
263
|
+
case "retry_end":
|
|
264
|
+
// 重试区间结束(SDK auto_retry_end):立刻收掉重试行。
|
|
265
|
+
// 重试常发生在下一轮 agent 开始之前,而那段间隙不会发 status:isStreaming=false,
|
|
266
|
+
// 所以不能只等 agent_settled —— 否则用户会一直看到「重试 1/5 terminated」。
|
|
267
|
+
s.retryNotice = null;
|
|
268
|
+
return s;
|
|
259
269
|
case "error":
|
|
260
270
|
// 旧服务端把自动重试塞在 error 里:同样按通知处理,不占错误条
|
|
261
271
|
if (/^(自动重试|重试)/.test(evt.message)) s.retryNotice = evt.message;
|
|
@@ -809,7 +819,7 @@ export function usePiWeb() {
|
|
|
809
819
|
const id = idOf(p);
|
|
810
820
|
// 工具预设取通用设置的持久化选择;模型/思考级别由 Hero 页显式下发
|
|
811
821
|
const setup: Record<string, unknown>[] = [{ cmd: "setToolPreset", preset: savedToolPreset() }];
|
|
812
|
-
if (overrides.provider && overrides.modelId) setup.push({ cmd: "setModel", provider: overrides.provider, modelId: overrides.modelId });
|
|
822
|
+
if (overrides.provider && overrides.modelId) setup.push({ cmd: "setModel", provider: overrides.provider, modelId: overrides.modelId, fresh: true });
|
|
813
823
|
if (overrides.thinking) setup.push({ cmd: "setThinkingLevel", level: overrides.thinking });
|
|
814
824
|
for (const command of setup) {
|
|
815
825
|
const configured = await sendCommand(command, id);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @ 引用的候选文件:打开候选菜单时拉一次全量清单,之后每个字符都在本地匹配。
|
|
5
|
+
* 按工作区缓存(含 in-flight 去重),过期后**先用手上的旧清单渲染、再后台刷新**——
|
|
6
|
+
* 打字过程中绝不能因为一次刷新把候选列表清空。
|
|
7
|
+
*/
|
|
8
|
+
import { useEffect, useRef, useState } from "react";
|
|
9
|
+
import { buildCandidates, dirsOfPaths, type PathCandidate } from "@/lib/workspace-index";
|
|
10
|
+
|
|
11
|
+
export interface WorkspaceFiles {
|
|
12
|
+
status: "idle" | "loading" | "ready" | "error";
|
|
13
|
+
candidates: PathCandidate[];
|
|
14
|
+
source: "git" | "disk" | null;
|
|
15
|
+
truncated: boolean;
|
|
16
|
+
error: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 清单多久算过期:agent 刚写出来的新文件也要能被引用到,所以不能一直用内存里那份 */
|
|
20
|
+
const TTL_MS = 30_000;
|
|
21
|
+
|
|
22
|
+
interface CachedIndex {
|
|
23
|
+
at: number;
|
|
24
|
+
candidates: PathCandidate[];
|
|
25
|
+
source: "git" | "disk";
|
|
26
|
+
truncated: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const cache = new Map<string, CachedIndex>();
|
|
30
|
+
const inflight = new Map<string, Promise<CachedIndex>>();
|
|
31
|
+
|
|
32
|
+
async function fetchIndex(cwd: string): Promise<CachedIndex> {
|
|
33
|
+
const response = await fetch(`/api/files?index=1&cwd=${encodeURIComponent(cwd)}`, { cache: "no-store" });
|
|
34
|
+
const payload = await response.json();
|
|
35
|
+
if (!response.ok || !payload.success) throw new Error(payload.error || `request failed (${response.status})`);
|
|
36
|
+
const data = payload.data as { files: string[]; source: "git" | "disk"; truncated: boolean };
|
|
37
|
+
const entry: CachedIndex = {
|
|
38
|
+
at: Date.now(),
|
|
39
|
+
candidates: buildCandidates(data.files, dirsOfPaths(data.files)),
|
|
40
|
+
source: data.source,
|
|
41
|
+
truncated: data.truncated,
|
|
42
|
+
};
|
|
43
|
+
cache.set(cwd, entry);
|
|
44
|
+
return entry;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 同一工作区并发只发一次请求;失败不进缓存(下次打开会重试)。
|
|
49
|
+
* 共享的请求不挂任何一方的 AbortSignal:先打开的输入框关了(中止),后来的还在等同一个结果,
|
|
50
|
+
* 不能让它跟着收到 AbortError。各方用完就不理结果,请求本身让它跑完、顺手填进缓存。
|
|
51
|
+
*/
|
|
52
|
+
function loadIndex(cwd: string): Promise<CachedIndex> {
|
|
53
|
+
const running = inflight.get(cwd);
|
|
54
|
+
if (running) return running;
|
|
55
|
+
const promise = fetchIndex(cwd).finally(() => inflight.delete(cwd));
|
|
56
|
+
inflight.set(cwd, promise);
|
|
57
|
+
return promise;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function useWorkspaceFiles(cwd: string, enabled: boolean): WorkspaceFiles {
|
|
61
|
+
const [state, setState] = useState<WorkspaceFiles>({ status: "idle", candidates: [], source: null, truncated: false, error: null });
|
|
62
|
+
const scope = useRef<{ cwd: string; controller: AbortController }>({ cwd: "", controller: new AbortController() });
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!enabled || !cwd) return;
|
|
66
|
+
const controller = new AbortController();
|
|
67
|
+
scope.current = { cwd, controller };
|
|
68
|
+
const isCurrent = () => scope.current.controller === controller && !controller.signal.aborted;
|
|
69
|
+
|
|
70
|
+
const cached = cache.get(cwd);
|
|
71
|
+
if (cached) {
|
|
72
|
+
// 有旧清单就先显示(旧的总比空白好),过期只在后台补
|
|
73
|
+
setState({ status: "ready", candidates: cached.candidates, source: cached.source, truncated: cached.truncated, error: null });
|
|
74
|
+
if (Date.now() - cached.at < TTL_MS) return () => controller.abort();
|
|
75
|
+
} else {
|
|
76
|
+
setState({ status: "loading", candidates: [], source: null, truncated: false, error: null });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
void loadIndex(cwd).then(
|
|
80
|
+
(entry) => {
|
|
81
|
+
if (isCurrent()) setState({ status: "ready", candidates: entry.candidates, source: entry.source, truncated: entry.truncated, error: null });
|
|
82
|
+
},
|
|
83
|
+
(error: unknown) => {
|
|
84
|
+
// 有旧清单时忽略这次失败:刷新失败不该把可用候选变成错误页
|
|
85
|
+
if (!isCurrent() || cache.has(cwd)) return;
|
|
86
|
+
setState({ status: "error", candidates: [], source: null, truncated: false, error: error instanceof Error ? error.message : String(error) });
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
return () => controller.abort();
|
|
90
|
+
}, [cwd, enabled]);
|
|
91
|
+
|
|
92
|
+
return state;
|
|
93
|
+
}
|