@rexvane/piweb 0.3.25 → 0.3.26
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 +6 -5
- package/.next/build-manifest.json +4 -4
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +4 -4
- package/.next/react-loadable-manifest.json +14 -26
- package/.next/required-server-files.js +26 -26
- package/.next/required-server-files.json +26 -26
- package/.next/routes-manifest.json +6 -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.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -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.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -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]/events/route_client-reference-manifest.js +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]/route_client-reference-manifest.js +1 -1
- 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/[id]/tree/route_client-reference-manifest.js +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/agent/new/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/agent/resources/route.js +1 -0
- package/.next/server/app/api/agent/resources/route.js.nft.json +1 -0
- package/.next/server/app/api/agent/resources/route_client-reference-manifest.js +1 -0
- 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/config/route_client-reference-manifest.js +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/files/route_client-reference-manifest.js +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/fs/route_client-reference-manifest.js +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/git/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/github/route.js +4 -4
- package/.next/server/app/api/github/route.js.nft.json +1 -1
- package/.next/server/app/api/github/route_client-reference-manifest.js +1 -1
- 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/growth/route_client-reference-manifest.js +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/health/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/models/defaults/route.js +1 -1
- package/.next/server/app/api/models/defaults/route.js.nft.json +1 -1
- package/.next/server/app/api/models/defaults/route_client-reference-manifest.js +1 -1
- 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/models/route_client-reference-manifest.js +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/pi-settings/route_client-reference-manifest.js +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/plugins/route_client-reference-manifest.js +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/prompts/route_client-reference-manifest.js +1 -1
- package/.next/server/app/api/remote/route.js +2 -2
- package/.next/server/app/api/remote/route.js.nft.json +1 -1
- package/.next/server/app/api/remote/route_client-reference-manifest.js +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/security/route_client-reference-manifest.js +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]/export/route_client-reference-manifest.js +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/[id]/route_client-reference-manifest.js +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/import/route_client-reference-manifest.js +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/sessions/route_client-reference-manifest.js +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/skills/route_client-reference-manifest.js +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/update/route_client-reference-manifest.js +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/version/route_client-reference-manifest.js +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/web-auth/route_client-reference-manifest.js +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/api/workspaces/route_client-reference-manifest.js +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/index.html +1 -1
- package/.next/server/app/index.rsc +5 -5
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +5 -5
- package/.next/server/app/index.segments/_full.segment.rsc +5 -5
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -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.rsc +4 -4
- package/.next/server/app/login.segments/_full.segment.rsc +4 -4
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
- 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 +6 -5
- package/.next/server/chunks/{813.js → 1813.js} +1 -1
- package/.next/server/chunks/{430.js → 2430.js} +5 -5
- package/.next/server/chunks/{970.js → 2970.js} +1 -1
- package/.next/server/chunks/3924.js +6 -0
- package/.next/server/chunks/4807.js +1 -0
- package/.next/server/chunks/5393.js +1 -0
- package/.next/server/chunks/5802.js +2 -0
- package/.next/server/chunks/620.js +1 -1
- 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} +1 -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 +5 -5
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/preview-props.json +3 -3
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/server/webpack-runtime.js +1 -1
- package/.next/static/QJCtqgDKzGS2BdKABd9ch/_buildManifest.js +1 -0
- package/.next/static/chunks/2577.86c8538fbf05a8b2.js +1 -0
- package/.next/static/chunks/2716.fa561519675a3ff1.js +1 -0
- package/.next/static/chunks/2906.8a0e09bb47605cf1.js +1 -0
- package/.next/static/chunks/4673.ea36e55005c0f82f.js +1 -0
- package/.next/static/chunks/4686-4dc565817a160351.js +1 -0
- package/.next/static/chunks/4778.0d2c5d75b791d352.js +1 -0
- package/.next/static/chunks/app/_global-error/page-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/_not-found/page-2d40d1d7d916666a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/agent/resources/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/config/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/files/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/fs/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/git/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/github/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/growth/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/health/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/models/defaults/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/models/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/remote/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/security/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/skills/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/update/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/version/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/app/{layout-7f64fa67ea6ce980.js → layout-50d06e2078d6ce76.js} +1 -1
- package/.next/static/chunks/app/login/{page-00dbd2f395b364d5.js → page-5e276a1eeb8867df.js} +1 -1
- package/.next/static/chunks/app/page-71f2d3b3ed864b3d.js +1 -0
- package/.next/static/chunks/main-5403f0764579e5fe.js +5 -0
- package/.next/static/chunks/{main-app-56863ace24eb152f.js → main-app-e9b53f0e1c6591b2.js} +1 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-a7e260f8cc2582d7.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f2d93969c0bcf406.js +1 -0
- package/.next/static/chunks/webpack-ffc09b5fcc8aaa6d.js +1 -0
- package/.next/static/css/7e3caf797e4cb19a.css +1 -0
- package/.next/types/app/api/agent/[id]/events/route.ts +1 -1
- package/.next/types/app/api/agent/[id]/route.ts +1 -1
- package/.next/types/app/api/agent/[id]/tree/route.ts +1 -1
- package/.next/types/app/api/agent/new/route.ts +1 -1
- package/.next/types/app/api/agent/resources/route.ts +355 -0
- package/.next/types/app/api/config/route.ts +1 -1
- package/.next/types/app/api/files/route.ts +1 -1
- package/.next/types/app/api/fs/route.ts +1 -1
- package/.next/types/app/api/git/route.ts +1 -1
- package/.next/types/app/api/github/route.ts +1 -1
- package/.next/types/app/api/growth/route.ts +1 -1
- package/.next/types/app/api/health/route.ts +1 -1
- package/.next/types/app/api/models/defaults/route.ts +1 -1
- package/.next/types/app/api/models/route.ts +1 -1
- package/.next/types/app/api/pi-settings/route.ts +1 -1
- package/.next/types/app/api/plugins/route.ts +1 -1
- package/.next/types/app/api/prompts/route.ts +1 -1
- package/.next/types/app/api/remote/route.ts +1 -1
- package/.next/types/app/api/security/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/export/route.ts +1 -1
- package/.next/types/app/api/sessions/[id]/route.ts +1 -1
- package/.next/types/app/api/sessions/import/route.ts +1 -1
- package/.next/types/app/api/sessions/route.ts +1 -1
- package/.next/types/app/api/skills/route.ts +1 -1
- package/.next/types/app/api/update/route.ts +1 -1
- package/.next/types/app/api/version/route.ts +1 -1
- package/.next/types/app/api/web-auth/route.ts +1 -1
- package/.next/types/app/api/workspaces/route.ts +1 -1
- package/.next/types/app/layout.ts +1 -1
- package/.next/types/app/login/page.ts +1 -1
- package/.next/types/app/page.ts +1 -1
- package/.next/types/routes.d.ts +2 -1
- package/.next/types/validator.ts +10 -0
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/bin/pi.js +0 -0
- package/bin/piweb.js +0 -0
- package/package.json +1 -1
- package/scripts/build-output.mjs +0 -0
- package/scripts/dev.mjs +0 -0
- package/scripts/entrypoint.mjs +0 -0
- package/scripts/install-build.mjs +0 -0
- package/scripts/launcher.mjs +0 -0
- package/scripts/pick-folder.ps1 +0 -0
- package/scripts/postinstall.mjs +0 -0
- package/scripts/process-runner.mjs +0 -0
- package/scripts/release.mjs +0 -0
- package/scripts/sync-file-icons.mjs +0 -0
- package/scripts/sync-provider-icons.mjs +0 -0
- package/src/app/api/agent/resources/route.ts +18 -0
- package/src/app/api/remote/route.ts +5 -7
- package/src/app/api/sessions/[id]/export/route.ts +3 -1
- package/src/app/globals.css +16 -0
- package/src/components/AppShell.tsx +68 -21
- package/src/components/ChatInput.tsx +98 -27
- package/src/components/ChatWindow.tsx +68 -11
- package/src/components/ContextMeter.tsx +24 -4
- package/src/components/ExtensionUI.tsx +9 -3
- package/src/components/FileViewer.tsx +24 -21
- package/src/components/GitPanel.tsx +449 -449
- package/src/components/GrowthTree.tsx +3 -1
- package/src/components/ImageViewer.tsx +72 -0
- package/src/components/MarkdownBlock.tsx +6 -3
- package/src/components/PreviewImage.tsx +33 -0
- package/src/components/ProjectPanel.tsx +5 -3
- package/src/components/PromptPanel.tsx +322 -313
- package/src/components/RemoteSection.tsx +2 -1
- package/src/components/SessionSidebar.tsx +22 -4
- package/src/components/SettingsPanel.tsx +2654 -2639
- package/src/components/composer/ComposerImages.tsx +1 -79
- package/src/hooks/usePiWeb.ts +47 -2
- package/src/i18n.tsx +24 -0
- package/src/lib/agent-manager.ts +275 -18
- package/src/lib/chat/skill-expansion.ts +10 -0
- package/src/lib/extension-ui.ts +25 -1
- package/src/lib/git-service.ts +291 -289
- package/src/lib/github-identity.ts +245 -245
- package/src/lib/growth-turns.ts +3 -1
- package/src/lib/session-reader.ts +25 -13
- package/src/lib/session-retention.ts +11 -8
- package/src/lib/slash-command-token.ts +14 -0
- package/src/lib/trajectory.ts +17 -6
- package/src/lib/types.ts +24 -2
- package/src/lib/workspace-store.ts +36 -1
- package/.next/diagnostics/build-diagnostics.json +0 -6
- package/.next/diagnostics/framework.json +0 -1
- package/.next/server/chunks/229.js +0 -1
- package/.next/server/chunks/393.js +0 -1
- package/.next/server/chunks/612.js +0 -1
- package/.next/server/chunks/662.js +0 -2
- package/.next/server/chunks/686.js +0 -1
- package/.next/server/chunks/802.js +0 -2
- package/.next/server/chunks/807.js +0 -1
- package/.next/server/chunks/924.js +0 -6
- package/.next/static/4snUelZTEIiQYCkB5nTW7/_buildManifest.js +0 -1
- package/.next/static/chunks/1966.1560d0f43ac9a41a.js +0 -1
- package/.next/static/chunks/2577.eebf2cf291a811ad.js +0 -1
- package/.next/static/chunks/2716.a5228b0eeedbe4c8.js +0 -1
- package/.next/static/chunks/2906.546a64377e12dfbc.js +0 -1
- package/.next/static/chunks/3899.55a0943bf5e7fec7.js +0 -1
- package/.next/static/chunks/4673.8eff281cb63e2799.js +0 -1
- package/.next/static/chunks/4686-f03275819a719fd8.js +0 -1
- package/.next/static/chunks/4778.2dae0c6a9e396326.js +0 -1
- package/.next/static/chunks/app/_global-error/page-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/_not-found/page-851dc3883a1937aa.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/config/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/files/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/fs/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/git/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/github/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/growth/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/health/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/models/defaults/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/models/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/remote/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/security/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/skills/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/update/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/version/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/app/page-0159987118376f42.js +0 -1
- package/.next/static/chunks/main-22ccebc2da4d291e.js +0 -5
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-c37b7707077f50ce.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f977af7f858990a2.js +0 -1
- package/.next/static/chunks/webpack-c1b43c269867b64f.js +0 -1
- package/.next/static/css/93ed3daa6f3fbc35.css +0 -1
- package/.next/trace +0 -58
- package/.next/trace-build +0 -1
- /package/.next/static/{4snUelZTEIiQYCkB5nTW7 → QJCtqgDKzGS2BdKABd9ch}/_ssgManifest.js +0 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
10
10
|
import {
|
|
11
11
|
IconAtOutline16,
|
|
12
|
+
IconCloseOutline14,
|
|
12
13
|
IconFileOutline16,
|
|
13
14
|
IconFolderClose16,
|
|
14
15
|
IconSendArrowUp14,
|
|
@@ -24,6 +25,7 @@ import { useWorkspaceFiles } from "@/hooks/useWorkspaceFiles";
|
|
|
24
25
|
import { fmtBytes } from "@/lib/format";
|
|
25
26
|
import { otherBehavior, useEnterBehavior } from "@/lib/enter-behavior";
|
|
26
27
|
import { atToken, candidateText, suggestPaths, type PathCandidate } from "@/lib/workspace-index";
|
|
28
|
+
import { slashCommandToken } from "@/lib/slash-command-token";
|
|
27
29
|
import type { ImageAttachment } from "@/lib/types";
|
|
28
30
|
|
|
29
31
|
export interface SlashCommand {
|
|
@@ -87,6 +89,7 @@ export function ChatInput({
|
|
|
87
89
|
pendingUploadCount = 0,
|
|
88
90
|
pendingSend = false,
|
|
89
91
|
onSendPendingChange,
|
|
92
|
+
attachmentLayout = "overlay",
|
|
90
93
|
}: {
|
|
91
94
|
disabled?: boolean;
|
|
92
95
|
isStreaming: boolean;
|
|
@@ -132,6 +135,8 @@ export function ChatInput({
|
|
|
132
135
|
pendingUploadCount?: number;
|
|
133
136
|
pendingSend?: boolean;
|
|
134
137
|
onSendPendingChange?: (pending: boolean) => void;
|
|
138
|
+
/** Hero 在输入卡上方预留空间;聊天中仍让附件悬浮、不占布局高度 */
|
|
139
|
+
attachmentLayout?: "overlay" | "flow";
|
|
135
140
|
}) {
|
|
136
141
|
const [localDraft, setLocalDraft] = useState<ChatDraft>(initialDraft ?? EMPTY_CHAT_DRAFT);
|
|
137
142
|
const currentDraft = draft ?? localDraft;
|
|
@@ -164,6 +169,7 @@ export function ChatInput({
|
|
|
164
169
|
const [modelOpen, setModelOpen] = useState(false);
|
|
165
170
|
const [cmdIdx, setCmdIdx] = useState(0);
|
|
166
171
|
const [cmdDismissed, setCmdDismissed] = useState(false);
|
|
172
|
+
const [commandCaret, setCommandCaret] = useState<number | null>(currentDraft.text.length);
|
|
167
173
|
// @ 引用:打字触发候选,选中后落成引用 chip(发送时统一拼成 `@路径` 行)
|
|
168
174
|
const [mention, setMention] = useState<{ query: string; start: number; tokenEnd: number } | null>(null);
|
|
169
175
|
const [mentionIdx, setMentionIdx] = useState(0);
|
|
@@ -189,11 +195,11 @@ export function ChatInput({
|
|
|
189
195
|
autoSize();
|
|
190
196
|
}, [text]);
|
|
191
197
|
|
|
192
|
-
//
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
const typedCommandMode = !!commands?.length &&
|
|
196
|
-
const filteredCommands = (commands ?? []).filter((c) => c.name.toLowerCase().startsWith(slash.
|
|
198
|
+
// 补全光标处的 token,而不是只看草稿的第一个字符
|
|
199
|
+
const commandToken = slashCommandToken(text, commandCaret);
|
|
200
|
+
const slash = commandToken?.query ?? "";
|
|
201
|
+
const typedCommandMode = !adopted && !!commands?.length && !!commandToken && !cmdDismissed;
|
|
202
|
+
const filteredCommands = (commands ?? []).filter((c) => c.name.toLowerCase().startsWith(slash.toLowerCase()));
|
|
197
203
|
const visibleCommands = filteredCommands;
|
|
198
204
|
const commandMode = typedCommandMode;
|
|
199
205
|
|
|
@@ -277,7 +283,67 @@ export function ChatInput({
|
|
|
277
283
|
}, [cmdIdx, visibleCommands.length]);
|
|
278
284
|
const effectiveIdx = Math.min(cmdIdx, Math.max(0, visibleCommands.length - 1));
|
|
279
285
|
|
|
286
|
+
const withoutCommandToken = () => commandToken ? text.slice(0, commandToken.start) + text.slice(commandToken.tokenEnd) : text;
|
|
287
|
+
const adoptCommand = (entry: SlashCommand) => {
|
|
288
|
+
// SDK 只展开开头的一个命令:已经采纳时不再覆盖(先 Backspace 摘掉)
|
|
289
|
+
if (draftRef.current.adopted) return;
|
|
290
|
+
const remaining = withoutCommandToken();
|
|
291
|
+
const caret = commandToken?.start ?? remaining.length;
|
|
292
|
+
setAdopted(entry);
|
|
293
|
+
setText(remaining);
|
|
294
|
+
setCommandCaret(caret);
|
|
295
|
+
setCmdDismissed(true);
|
|
296
|
+
setCmdIdx(0);
|
|
297
|
+
requestAnimationFrame(() => {
|
|
298
|
+
autoSize();
|
|
299
|
+
taRef.current?.focus();
|
|
300
|
+
taRef.current?.setSelectionRange(caret, caret);
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// 行内扩展命令(如 /fast)自己执行:不能为了切换它把周围的草稿、图片或引用一起发出去
|
|
305
|
+
const runInlineExtension = async (entry: SlashCommand) => {
|
|
306
|
+
if (disabled || isBlocked || sendingRef.current || pendingSend || uploadBusy) return;
|
|
307
|
+
const original = text;
|
|
308
|
+
const remaining = withoutCommandToken();
|
|
309
|
+
sendingRef.current = true;
|
|
310
|
+
setSending(true);
|
|
311
|
+
onSendPendingChange?.(true);
|
|
312
|
+
setText(remaining);
|
|
313
|
+
setCommandCaret(remaining.length);
|
|
314
|
+
setCmdDismissed(true);
|
|
315
|
+
try {
|
|
316
|
+
const result = await onSend(`/${entry.name}`, []);
|
|
317
|
+
if (!result?.success) throw new Error(result?.error || "Command failed");
|
|
318
|
+
} catch (error) {
|
|
319
|
+
setText((previous) => previous === remaining ? original : previous);
|
|
320
|
+
if (mountedRef.current) {
|
|
321
|
+
setCommandCaret(commandToken?.tokenEnd ?? original.length);
|
|
322
|
+
setCmdDismissed(false);
|
|
323
|
+
setAttachmentError(error instanceof Error ? error.message : String(error));
|
|
324
|
+
}
|
|
325
|
+
} finally {
|
|
326
|
+
sendingRef.current = false;
|
|
327
|
+
if (mountedRef.current) setSending(false);
|
|
328
|
+
onSendPendingChange?.(false);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
280
332
|
const runEntry = (entry: SlashCommand) => {
|
|
333
|
+
if (draftRef.current.adopted) return;
|
|
334
|
+
// 命令出现在正文中间:只替换这一段 token,前后文字原样保留
|
|
335
|
+
if (commandToken && (commandToken.start > 0 || commandToken.tokenEnd < text.length)) {
|
|
336
|
+
if (entry.kind === "builtin") {
|
|
337
|
+
if (entry.name === "model") setModelOpen(true);
|
|
338
|
+
else onCommand?.(entry.name, "");
|
|
339
|
+
const remaining = withoutCommandToken();
|
|
340
|
+
setText(remaining);
|
|
341
|
+
setCommandCaret(remaining.length);
|
|
342
|
+
setCmdDismissed(true);
|
|
343
|
+
} else if (entry.kind === "extension") void runInlineExtension(entry);
|
|
344
|
+
else adoptCommand(entry);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
281
347
|
const args = slash.slice(entry.name.length).trim();
|
|
282
348
|
if (entry.kind === "builtin" && entry.name === "model") {
|
|
283
349
|
setModelOpen(true);
|
|
@@ -505,7 +571,7 @@ export function ChatInput({
|
|
|
505
571
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
506
572
|
}, []);
|
|
507
573
|
|
|
508
|
-
const hasDraft = text.trim().length > 0 || images.length > 0 || uploads.length > 0 || references.length > 0;
|
|
574
|
+
const hasDraft = adopted !== null || text.trim().length > 0 || images.length > 0 || uploads.length > 0 || references.length > 0;
|
|
509
575
|
|
|
510
576
|
return (
|
|
511
577
|
<div ref={wrapRef} className="relative w-full" data-testid="composer">
|
|
@@ -650,11 +716,12 @@ export function ChatInput({
|
|
|
650
716
|
)}
|
|
651
717
|
|
|
652
718
|
{/*
|
|
653
|
-
* 附件轨(图片 / 引用 /
|
|
719
|
+
* 附件轨(图片 / 引用 / 上传文件):聊天中悬浮于输入卡上方,不占布局高度 ——
|
|
654
720
|
* 输入框位置不动,多出来的附件直接盖住后面的聊天记录(用户要的就是这个)。
|
|
721
|
+
* 新对话页用 flow 占位,避免 @ 引用和图片盖住上方的工作区选择器。
|
|
655
722
|
* 外层 pointer-events-none,只有附件本身接收点击,免得挡住聊天区的交互。
|
|
656
723
|
*/}
|
|
657
|
-
<div className=
|
|
724
|
+
{(images.length > 0 || references.length > 0 || uploads.length > 0) && <div className={`pointer-events-none z-20 flex flex-col gap-1.5 pb-2 ${attachmentLayout === "flow" ? "relative" : "absolute inset-x-0 bottom-full"}`} data-testid="composer-attachments">
|
|
658
725
|
{/* 附件轨:图片(超过 4 张折叠成牌堆,展开沿弧线扇开;点开可放大、切换、删除) */}
|
|
659
726
|
{images.length > 0 && (
|
|
660
727
|
<ComposerImages
|
|
@@ -714,16 +781,18 @@ export function ChatInput({
|
|
|
714
781
|
/>
|
|
715
782
|
)}
|
|
716
783
|
|
|
717
|
-
</div>
|
|
784
|
+
</div>}
|
|
718
785
|
|
|
719
786
|
{/* 卡片(Iconsax .isx-composer:24px 圆角 + 聚焦高对比描边 + 底部流光短横条)—— 拖放由 document 级监听统一接管 */}
|
|
720
787
|
<div className="isx-composer relative w-full">
|
|
721
|
-
<div className="px-4 pt-3 flex items-
|
|
788
|
+
<div className="px-4 pt-3 flex flex-col items-stretch gap-1.5">
|
|
722
789
|
{adopted && (
|
|
723
790
|
<button
|
|
724
791
|
type="button"
|
|
725
|
-
className="flex-none flex items-center gap-1 self-start"
|
|
726
|
-
|
|
792
|
+
className="flex-none flex max-w-full items-center gap-1 self-start"
|
|
793
|
+
disabled={disabled || sendBusy}
|
|
794
|
+
aria-label={`${t.delete} /${adopted.name}`}
|
|
795
|
+
title={`${t.delete} /${adopted.name} (Backspace)`}
|
|
727
796
|
style={{
|
|
728
797
|
fontSize: "var(--piweb-chat-font-size, var(--dsh-content-font-size))",
|
|
729
798
|
lineHeight: "var(--piweb-chat-line-height, 1.55)",
|
|
@@ -738,7 +807,8 @@ export function ChatInput({
|
|
|
738
807
|
}}
|
|
739
808
|
>
|
|
740
809
|
<IconTerminalOutline14 size={14} style={{ color: "var(--dsw-accent)", flex: "none" }} />
|
|
741
|
-
<span style={{ color: "var(--dsw-accent)", fontWeight: 600 }}>/{adopted.name}</span>
|
|
810
|
+
<span className="min-w-0 truncate" style={{ color: "var(--dsw-accent)", fontWeight: 600 }}>/{adopted.name}</span>
|
|
811
|
+
<IconCloseOutline14 size={12} style={{ flex: "none", color: "var(--dsw-label-caption)" }} />
|
|
742
812
|
</button>
|
|
743
813
|
)}
|
|
744
814
|
<textarea
|
|
@@ -748,11 +818,12 @@ export function ChatInput({
|
|
|
748
818
|
rows={1}
|
|
749
819
|
placeholder={adopted ? (adopted.argumentHint ?? t.inputPlaceholder) : !disabled && isBlocked ? t.blockedComposer : t.inputPlaceholder}
|
|
750
820
|
suppressHydrationWarning
|
|
751
|
-
className="min-w-0
|
|
821
|
+
className="min-w-0 w-full resize-none"
|
|
752
822
|
style={{ fontSize: "var(--piweb-chat-font-size, var(--dsh-content-font-size))", lineHeight: 1.55 }}
|
|
753
823
|
onChange={(e) => {
|
|
754
824
|
const value = e.target.value;
|
|
755
825
|
setText(value);
|
|
826
|
+
setCommandCaret(e.target.selectionStart);
|
|
756
827
|
setCmdDismissed(false);
|
|
757
828
|
setCmdIdx(0);
|
|
758
829
|
syncMention(value, e.target.selectionStart);
|
|
@@ -761,7 +832,10 @@ export function ChatInput({
|
|
|
761
832
|
onSelect={(e) => {
|
|
762
833
|
// 选中了一段文字时不弹:那时 Enter / Tab 应该照常工作
|
|
763
834
|
const ta = e.target as HTMLTextAreaElement;
|
|
764
|
-
|
|
835
|
+
const caret = ta.selectionStart === ta.selectionEnd ? ta.selectionStart : null;
|
|
836
|
+
setCommandCaret(caret);
|
|
837
|
+
if (slashCommandToken(ta.value, caret)?.start !== commandToken?.start) setCmdDismissed(false);
|
|
838
|
+
syncMention(ta.value, caret);
|
|
765
839
|
}}
|
|
766
840
|
onPaste={(e) => {
|
|
767
841
|
const files = Array.from(e.clipboardData.files);
|
|
@@ -799,13 +873,16 @@ export function ChatInput({
|
|
|
799
873
|
}
|
|
800
874
|
}
|
|
801
875
|
}
|
|
802
|
-
if (adopted &&
|
|
803
|
-
|
|
876
|
+
if (adopted && !e.nativeEvent.isComposing && (
|
|
877
|
+
(e.key === "Backspace" && e.currentTarget.selectionStart === 0 && e.currentTarget.selectionEnd === 0) ||
|
|
878
|
+
(e.key === "Escape" && text === "")
|
|
879
|
+
)) {
|
|
880
|
+
// 光标在开头时退格 / 空参数时 Esc:只摘掉命令标记,不碰任何正文
|
|
804
881
|
e.preventDefault();
|
|
805
882
|
setAdopted(null);
|
|
806
883
|
return;
|
|
807
884
|
}
|
|
808
|
-
if (commandMode && visibleCommands.length > 0) {
|
|
885
|
+
if (commandMode && visibleCommands.length > 0 && !e.nativeEvent.isComposing) {
|
|
809
886
|
if (e.key === "ArrowDown") {
|
|
810
887
|
e.preventDefault();
|
|
811
888
|
setCmdIdx((i) => Math.min(i + 1, visibleCommands.length - 1));
|
|
@@ -818,10 +895,10 @@ export function ChatInput({
|
|
|
818
895
|
}
|
|
819
896
|
if (e.key === "Escape") {
|
|
820
897
|
e.preventDefault();
|
|
821
|
-
|
|
898
|
+
setCmdDismissed(true);
|
|
822
899
|
return;
|
|
823
900
|
}
|
|
824
|
-
if (e.key === "Enter"
|
|
901
|
+
if (e.key === "Enter") {
|
|
825
902
|
e.preventDefault();
|
|
826
903
|
runEntry(visibleCommands[effectiveIdx]);
|
|
827
904
|
return;
|
|
@@ -831,13 +908,7 @@ export function ChatInput({
|
|
|
831
908
|
e.preventDefault();
|
|
832
909
|
const entry = visibleCommands[effectiveIdx];
|
|
833
910
|
if (!entry) return;
|
|
834
|
-
|
|
835
|
-
setText("");
|
|
836
|
-
setCmdIdx(0);
|
|
837
|
-
requestAnimationFrame(() => {
|
|
838
|
-
autoSize();
|
|
839
|
-
taRef.current?.focus();
|
|
840
|
-
});
|
|
911
|
+
adoptCommand(entry);
|
|
841
912
|
return;
|
|
842
913
|
}
|
|
843
914
|
}
|
|
@@ -30,6 +30,7 @@ import { languageForPath } from "@/lib/highlight";
|
|
|
30
30
|
import { parseExpandedSkill } from "@/lib/chat/skill-expansion";
|
|
31
31
|
import { copyText } from "@/lib/clipboard";
|
|
32
32
|
import { OutlineRail } from "@/components/OutlineRail";
|
|
33
|
+
import { PreviewImage } from "@/components/PreviewImage";
|
|
33
34
|
import { ChatScrollContext, type ChatScroll } from "@/components/chat/chat-scroll";
|
|
34
35
|
import { MarkdownActionsProvider, type MarkdownActions } from "@/components/chat/markdown-actions";
|
|
35
36
|
import { useChatReadingAnchor } from "@/components/chat/reading-anchor";
|
|
@@ -879,7 +880,7 @@ function ItemView({ item, cwd, onInspectTool, onOpenFile }: { item: Item; cwd?:
|
|
|
879
880
|
case "image":
|
|
880
881
|
return (
|
|
881
882
|
<div className="pw-narr">
|
|
882
|
-
<
|
|
883
|
+
<PreviewImage image={{ type: "image", mimeType: item.mimeType, data: item.data }} className="my-1 max-h-96 max-w-full rounded-2xl object-contain" />
|
|
883
884
|
</div>
|
|
884
885
|
);
|
|
885
886
|
case "tool":
|
|
@@ -916,11 +917,12 @@ const FinalAnswer = memo(function FinalAnswer({
|
|
|
916
917
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
917
918
|
.map((c) => c.text)
|
|
918
919
|
.join("");
|
|
920
|
+
const images = message.content.filter((c) => c.type === "image");
|
|
919
921
|
return (
|
|
920
922
|
<div className="group w-full" style={{ marginTop: 14 }} data-role="assistant">
|
|
921
923
|
{message.content.map((c, i) => {
|
|
922
924
|
if (c.type === "text" && c.text.trim()) return <Markdown key={i} text={c.text} />;
|
|
923
|
-
if (c.type === "image") return <
|
|
925
|
+
if (c.type === "image") return <PreviewImage key={i} image={c} images={images} index={images.indexOf(c)} className="my-2 max-h-96 max-w-full rounded-2xl object-contain" />;
|
|
924
926
|
return null;
|
|
925
927
|
})}
|
|
926
928
|
{showActions && (
|
|
@@ -980,10 +982,11 @@ function UserImages({ images }: { images: Array<{ type: "image"; data: string; m
|
|
|
980
982
|
return (
|
|
981
983
|
<div className="mb-2 flex max-w-[85%] flex-wrap justify-end gap-2 self-end">
|
|
982
984
|
{images.map((image, index) => (
|
|
983
|
-
<
|
|
985
|
+
<PreviewImage
|
|
984
986
|
key={index}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
+
image={image}
|
|
988
|
+
images={images}
|
|
989
|
+
index={index}
|
|
987
990
|
className="max-h-72 max-w-full rounded-2xl object-contain"
|
|
988
991
|
style={{ border: "0.5px solid var(--dsw-border-l2)" }}
|
|
989
992
|
/>
|
|
@@ -1003,6 +1006,8 @@ const UserMessage = memo(function UserMessage({ message, onEditMessage }: { mess
|
|
|
1003
1006
|
if (editing) {
|
|
1004
1007
|
return (
|
|
1005
1008
|
<div className="group mt-7 flex w-full flex-col items-end first:mt-0" data-role="user" data-message-id={message.id}>
|
|
1009
|
+
{/* 编辑只改文字,图片由服务端从原消息里带进重发;照样显示,免得以为图被丢了 */}
|
|
1010
|
+
<UserImages images={images} />
|
|
1006
1011
|
<div className="msg-user-bubble w-full">
|
|
1007
1012
|
<textarea
|
|
1008
1013
|
className="w-full resize-none bg-transparent outline-none"
|
|
@@ -1064,7 +1069,8 @@ const UserMessage = memo(function UserMessage({ message, onEditMessage }: { mess
|
|
|
1064
1069
|
|
|
1065
1070
|
// ---------- 回合:用户消息 → 过程步骤 → 最终回答 ----------
|
|
1066
1071
|
|
|
1067
|
-
|
|
1072
|
+
/** summaryIndex:压缩分隔卡 / 分支摘要卡单独占一格,没有用户消息和过程 */
|
|
1073
|
+
type Turn = { key: string; userIndex: number; assistantIndexes: number[]; summaryIndex?: number };
|
|
1068
1074
|
|
|
1069
1075
|
function buildTurns(messages: WebMessage[]): Turn[] {
|
|
1070
1076
|
const out: Turn[] = [];
|
|
@@ -1073,6 +1079,10 @@ function buildTurns(messages: WebMessage[]): Turn[] {
|
|
|
1073
1079
|
if (m.role === "user") {
|
|
1074
1080
|
cur = { key: `u${i}`, userIndex: i, assistantIndexes: [] };
|
|
1075
1081
|
out.push(cur);
|
|
1082
|
+
} else if (m.role === "summary") {
|
|
1083
|
+
// 压缩可能切在回合中间:卡片后面的助手消息另起一个回合,不并回卡片上方的用户消息
|
|
1084
|
+
out.push({ key: `s${i}`, userIndex: -1, assistantIndexes: [], summaryIndex: i });
|
|
1085
|
+
cur = null;
|
|
1076
1086
|
} else if (m.role === "assistant") {
|
|
1077
1087
|
if (!cur) {
|
|
1078
1088
|
cur = { key: `a${i}`, userIndex: -1, assistantIndexes: [] };
|
|
@@ -1311,6 +1321,45 @@ function TurnLite({ turn, messages }: { turn: Turn; messages: WebMessage[] }) {
|
|
|
1311
1321
|
);
|
|
1312
1322
|
}
|
|
1313
1323
|
|
|
1324
|
+
/**
|
|
1325
|
+
* 压缩分隔卡:卡片以上的消息照常显示(可编辑、可分叉),但模型只看得到它们的摘要;摘要可展开。
|
|
1326
|
+
* 分支摘要卡:切换分支时离开的那条分支被总结成摘要发给模型。
|
|
1327
|
+
*/
|
|
1328
|
+
function SummaryCard({ message }: { message: WebMessage }) {
|
|
1329
|
+
const { t } = useI18n();
|
|
1330
|
+
const [open, setOpen] = useState(false);
|
|
1331
|
+
const branch = message.summaryKind === "branch";
|
|
1332
|
+
const summary = message.summary?.trim();
|
|
1333
|
+
const title = branch
|
|
1334
|
+
? t.branchSummaryTitle
|
|
1335
|
+
: message.tokensBefore
|
|
1336
|
+
? t.compactedTitleTokens.replace("{tokens}", fmtTokens(message.tokensBefore))
|
|
1337
|
+
: t.compactedTitle;
|
|
1338
|
+
const rule = <span aria-hidden="true" style={{ flex: 1, borderTop: "1px dashed var(--dsw-border-l3)" }} />;
|
|
1339
|
+
return (
|
|
1340
|
+
<div className="w-full" data-role="summary" data-message-id={message.id} style={{ fontSize: "var(--piweb-chat-font-s)", color: "var(--dsw-label-caption)" }}>
|
|
1341
|
+
<div className="flex items-center gap-3">
|
|
1342
|
+
{rule}
|
|
1343
|
+
<span style={{ flex: "none", color: "var(--dsw-label-secondary)" }}>{title}</span>
|
|
1344
|
+
{rule}
|
|
1345
|
+
</div>
|
|
1346
|
+
<div className="mt-1 flex flex-wrap items-center justify-center gap-x-2 text-center">
|
|
1347
|
+
<span>{branch ? t.branchSummaryHint : t.compactedHint}</span>
|
|
1348
|
+
{summary && (
|
|
1349
|
+
<button type="button" className="hover:underline" aria-expanded={open} onClick={() => setOpen((value) => !value)} style={{ color: "var(--dsw-label-secondary)" }}>
|
|
1350
|
+
{open ? t.hideSummary : t.showSummary}
|
|
1351
|
+
</button>
|
|
1352
|
+
)}
|
|
1353
|
+
</div>
|
|
1354
|
+
{open && summary && (
|
|
1355
|
+
<div className="mt-2 rounded-xl px-3 py-2" style={{ background: "var(--dsw-hover)", color: "var(--dsw-label-primary)" }}>
|
|
1356
|
+
<Markdown text={summary} />
|
|
1357
|
+
</div>
|
|
1358
|
+
)}
|
|
1359
|
+
</div>
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1314
1363
|
export function SessionStatsBar({
|
|
1315
1364
|
stats,
|
|
1316
1365
|
}: {
|
|
@@ -1455,6 +1504,7 @@ export function ChatWindow({
|
|
|
1455
1504
|
stats,
|
|
1456
1505
|
onOpenTrajectory,
|
|
1457
1506
|
onOpenFile,
|
|
1507
|
+
onOpenPath,
|
|
1458
1508
|
roundBadges,
|
|
1459
1509
|
onShowRound,
|
|
1460
1510
|
}: {
|
|
@@ -1483,6 +1533,8 @@ export function ChatWindow({
|
|
|
1483
1533
|
/** 在本机编辑器打开工具行涉及的文件 */
|
|
1484
1534
|
onEditMessage?: (entryId: string, text: string) => void;
|
|
1485
1535
|
onOpenFile?: (path: string) => void;
|
|
1536
|
+
/** 正文行内代码里的路径:用页内文件查看器打开;没给就退回 onOpenFile(本机编辑器) */
|
|
1537
|
+
onOpenPath?: (path: string) => void;
|
|
1486
1538
|
roundBadges?: ReadonlyMap<string, RoundBadge>;
|
|
1487
1539
|
onShowRound?: (commit: string) => void;
|
|
1488
1540
|
}) {
|
|
@@ -1502,8 +1554,9 @@ export function ChatWindow({
|
|
|
1502
1554
|
},
|
|
1503
1555
|
}), []);
|
|
1504
1556
|
useChatReadingAnchor(rootRef, scrollRef, chatScroll.shouldPin);
|
|
1505
|
-
// 正文里可点击的文件路径(见 chat/markdown-actions
|
|
1506
|
-
|
|
1557
|
+
// 正文里可点击的文件路径(见 chat/markdown-actions):只认当前工作区内的相对路径,
|
|
1558
|
+
// 点开走页内文件查看器(不离开页面,路径不存在时查看器自己会报「文件不存在」)。
|
|
1559
|
+
const markdownActions = useMemo<MarkdownActions>(() => ({ openFile: onOpenPath ?? onOpenFile }), [onOpenPath, onOpenFile]);
|
|
1507
1560
|
const { t } = useI18n();
|
|
1508
1561
|
useEffect(() => {
|
|
1509
1562
|
const root = rootRef.current;
|
|
@@ -1541,6 +1594,8 @@ export function ChatWindow({
|
|
|
1541
1594
|
return -1;
|
|
1542
1595
|
}, [messages]);
|
|
1543
1596
|
const turns = useMemo(() => buildTurns(messages), [messages]);
|
|
1597
|
+
// 工作指示 / 重试行挂在最后一个真正的回合上,压缩卡追加在末尾时也不抢走
|
|
1598
|
+
const lastContentTurn = useMemo(() => turns.findLastIndex((turn) => turn.summaryIndex === undefined), [turns]);
|
|
1544
1599
|
const firstUser = useMemo(() => messages.findIndex((m) => m.role === "user"), [messages]);
|
|
1545
1600
|
|
|
1546
1601
|
// ---------- 回合懒渲染 ----------
|
|
@@ -1742,14 +1797,16 @@ export function ChatWindow({
|
|
|
1742
1797
|
{turns.map((turn, ti) => (
|
|
1743
1798
|
// 回合间 28px 的分隔挂在槽上:用户消息现在总是槽里的第一个子元素,它自己的 mt-7 会被 first:mt-0 吃掉
|
|
1744
1799
|
<div key={turn.key} data-turn-slot={ti} ref={slotRef(ti)} className="mt-7 first:mt-0">
|
|
1745
|
-
{
|
|
1800
|
+
{turn.summaryIndex !== undefined ? (
|
|
1801
|
+
<SummaryCard message={messages[turn.summaryIndex]} />
|
|
1802
|
+
) : isNear(ti) ? (
|
|
1746
1803
|
<TurnBlock
|
|
1747
1804
|
turn={turn}
|
|
1748
1805
|
messages={messages}
|
|
1749
1806
|
tools={tools}
|
|
1750
1807
|
cwd={cwd}
|
|
1751
1808
|
isStreaming={isStreaming}
|
|
1752
|
-
isLast={ti ===
|
|
1809
|
+
isLast={ti === lastContentTurn}
|
|
1753
1810
|
lastAssistantIndex={lastAssistantIndex}
|
|
1754
1811
|
contextFiles={turn.userIndex >= 0 && turn.userIndex === firstUser ? contextFiles : undefined}
|
|
1755
1812
|
retryNotice={retryNotice}
|
|
@@ -1770,7 +1827,7 @@ export function ChatWindow({
|
|
|
1770
1827
|
</div>
|
|
1771
1828
|
))}
|
|
1772
1829
|
{/* 还没有任何消息就已在流式(极少见):单独给一条工作指示 */}
|
|
1773
|
-
{isStreaming && streamStartedAt &&
|
|
1830
|
+
{isStreaming && streamStartedAt && lastContentTurn < 0 && (
|
|
1774
1831
|
<div className="pw-turn">
|
|
1775
1832
|
<WorkingStep startedAt={streamStartedAt} outputTokens={stats?.tokens.output} workingMessage={workingMessage} hasLiveStep={Boolean(runningTool)} />
|
|
1776
1833
|
</div>
|
|
@@ -8,6 +8,24 @@ import { formatCapacity, parseCapacity } from "@/lib/model-draft";
|
|
|
8
8
|
import { classifyMessageChars } from "@/lib/process-format";
|
|
9
9
|
import type { ContextBreakdown, TrajTokens } from "@/lib/types";
|
|
10
10
|
|
|
11
|
+
type ContextMessage = {
|
|
12
|
+
role?: string;
|
|
13
|
+
content: Array<{ type: string; text?: string; thinking?: string; arguments?: unknown; name?: string }>;
|
|
14
|
+
usage?: TrajTokens;
|
|
15
|
+
stopReason?: string;
|
|
16
|
+
timestamp?: number;
|
|
17
|
+
summaryKind?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 消息列表会照常显示压缩前的历史,但模型上下文只从最近一张压缩卡往后算(卡片以上只剩摘要,已计入 compacted)。
|
|
22
|
+
* 保留下来的旧助手消息用量是压缩前那次请求的,也不能当「最近一次输入」。
|
|
23
|
+
*/
|
|
24
|
+
function inContext(messages: ContextMessage[]): { messages: ContextMessage[]; since?: number } {
|
|
25
|
+
const at = messages.findLastIndex((m) => m.role === "summary" && m.summaryKind === "compaction");
|
|
26
|
+
return at < 0 ? { messages } : { messages: messages.slice(at + 1), since: messages[at].timestamp };
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
export function ContextMeter({ percent, tokens, contextWindow, model, source, label }: {
|
|
12
30
|
percent: number | null;
|
|
13
31
|
tokens: number | null;
|
|
@@ -18,7 +36,7 @@ export function ContextMeter({ percent, tokens, contextWindow, model, source, la
|
|
|
18
36
|
label?: string;
|
|
19
37
|
source?: {
|
|
20
38
|
systemChars: number;
|
|
21
|
-
messages:
|
|
39
|
+
messages: ContextMessage[];
|
|
22
40
|
breakdown?: ContextBreakdown;
|
|
23
41
|
loadFull?: () => Promise<unknown> | unknown;
|
|
24
42
|
};
|
|
@@ -38,9 +56,11 @@ export function ContextMeter({ percent, tokens, contextWindow, model, source, la
|
|
|
38
56
|
}, [open]);
|
|
39
57
|
|
|
40
58
|
// 流式期间只在打开弹窗后扫描,分类是实际文本字符数,不是假设的 token 数。
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
59
|
+
const context = useMemo(() => inContext(open ? source?.messages ?? [] : []), [open, source]);
|
|
60
|
+
const chars = useMemo(() => classifyMessageChars(context.messages), [context]);
|
|
61
|
+
const lastUsage = useMemo(() => context.messages.findLast((m) => m.role === "assistant" && m.stopReason !== "error" && m.stopReason !== "aborted" && m.stopReason !== "pending" &&
|
|
62
|
+
(context.since === undefined || (m.timestamp ?? 0) >= context.since) &&
|
|
63
|
+
(m.usage?.input ?? 0) + (m.usage?.cacheRead ?? 0) + (m.usage?.cacheWrite ?? 0) + (m.usage?.output ?? 0) > 0)?.usage, [context]);
|
|
44
64
|
const measuredInput = lastUsage ? (lastUsage.input ?? 0) + (lastUsage.cacheRead ?? 0) + (lastUsage.cacheWrite ?? 0) : null;
|
|
45
65
|
|
|
46
66
|
// 刚改过窗口:服务端已落盘,但页面上的 contextWindow 还是改之前的值(冷会话要等下一次快照)。
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { IconShieldOutline16 } from "@/components/icons";
|
|
9
9
|
import { useI18n } from "@/i18n";
|
|
10
|
-
import type { ExtensionDialog } from "@/hooks/usePiWeb";
|
|
10
|
+
import type { ExtensionDialog, PiWebNotice } from "@/hooks/usePiWeb";
|
|
11
11
|
|
|
12
12
|
/** message 若全是 "key : value" 行(权限对话框的常见形态),解析成键值表渲染 */
|
|
13
13
|
function parseKeyValueRows(message: string): { key: string; value: string }[] | null {
|
|
@@ -154,11 +154,17 @@ export function ExtensionNotices({
|
|
|
154
154
|
onDismiss,
|
|
155
155
|
children,
|
|
156
156
|
}: {
|
|
157
|
-
notices:
|
|
157
|
+
notices: PiWebNotice[];
|
|
158
158
|
onDismiss: (id: string) => void;
|
|
159
159
|
/** 同一摞里的其它常驻提示(如更新提示条),排在扩展通知上面,免得两处浮层叠在一起 */
|
|
160
160
|
children?: React.ReactNode;
|
|
161
161
|
}) {
|
|
162
|
+
const { t } = useI18n();
|
|
163
|
+
const text = (n: PiWebNotice) => {
|
|
164
|
+
if (n.code === "external_reload") return t.externalSessionReloaded.replace("{n}", String(n.count ?? 0));
|
|
165
|
+
if (n.code === "external_write") return t.externalSessionWriting.replace("{n}", String(n.count ?? 0));
|
|
166
|
+
return n.message;
|
|
167
|
+
};
|
|
162
168
|
// 通知 6 秒后自动消失
|
|
163
169
|
useEffect(() => {
|
|
164
170
|
if (!notices.length) return;
|
|
@@ -179,7 +185,7 @@ export function ExtensionNotices({
|
|
|
179
185
|
style={{ fontSize: 12.5, background: "var(--dsw-glass-popover)", border: "0.5px solid var(--dsw-border-l2)", boxShadow: "var(--dsw-elevation-soft)", borderLeft: `3px solid ${color(n.type)}` }}
|
|
180
186
|
onClick={() => onDismiss(n.id)}
|
|
181
187
|
>
|
|
182
|
-
{n
|
|
188
|
+
{text(n)}
|
|
183
189
|
</button>
|
|
184
190
|
))}
|
|
185
191
|
</div>
|
|
@@ -17,6 +17,7 @@ import { IconAtOutline16, IconChevronDown14, IconChevronUp14, IconCloseOutline14
|
|
|
17
17
|
import type { GrowthApi } from "@/hooks/useGrowth";
|
|
18
18
|
import { useI18n } from "@/i18n";
|
|
19
19
|
import { fileIconSrc } from "@/lib/file-icons";
|
|
20
|
+
import { fmtBytes } from "@/lib/format";
|
|
20
21
|
import { fuzzyScore, type TreeNode } from "@/lib/growth-tree";
|
|
21
22
|
import type { ViewerState } from "@/hooks/useFileViewer";
|
|
22
23
|
import { highlightCode, languageForPath } from "@/lib/highlight";
|
|
@@ -113,8 +114,7 @@ export function FileViewer({
|
|
|
113
114
|
const [treeFilter, setTreeFilter] = useState("");
|
|
114
115
|
const [reveal, setReveal] = useState<{ path: string; key: number } | null>(null);
|
|
115
116
|
const [loaded, setLoaded] = useState<Loaded | null>(null);
|
|
116
|
-
const [dirPage, setDirPage] = useState<(DirPage & { key: string }) | null>(null);
|
|
117
|
-
const [dirMore, setDirMore] = useState<DirEntry[]>([]);
|
|
117
|
+
const [dirPage, setDirPage] = useState<(DirPage & { key: string; error?: string }) | null>(null);
|
|
118
118
|
const diffRef = useRef<FullDiffHandle>(null);
|
|
119
119
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
120
120
|
const quickInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -138,41 +138,44 @@ export function FileViewer({
|
|
|
138
138
|
const [dirFallback, setDirFallback] = useState<string | null>(null);
|
|
139
139
|
const dirPath = activeTab?.kind === "dir" ? activeTab.path : dirFallback;
|
|
140
140
|
const isDir = Boolean(dirPath);
|
|
141
|
+
// @src/ 这类带尾斜杠的引用:列目录时去掉
|
|
142
|
+
const dirRel = dirPath ? dirPath.replace(/\/+$/, "") : null;
|
|
143
|
+
const dirKey = dirRel !== null && cwd ? `dir|${cwd}|${dirRel}` : null;
|
|
144
|
+
// 只认当前目录的那一页:从一个文件夹页签切到另一个时,上一个目录的条目不能在新请求回来前冒充新目录
|
|
145
|
+
// (点上去会拼出不存在的路径),「加载更多」也不能接着上一个目录的偏移拉
|
|
146
|
+
const page = dirPage && dirPage.key === dirKey ? dirPage : null;
|
|
141
147
|
// 文件夹:列目录(分页,后端已按 .gitignore 过滤),不读文件内容
|
|
142
148
|
useEffect(() => {
|
|
143
|
-
if (
|
|
149
|
+
if (dirRel === null || !cwd || !dirKey) {
|
|
144
150
|
setDirPage(null);
|
|
145
|
-
setDirMore([]);
|
|
146
151
|
return;
|
|
147
152
|
}
|
|
148
|
-
// @src/ 这类带尾斜杠的引用:列目录时去掉
|
|
149
|
-
const path_ = dirPath.replace(/\/+$/, "");
|
|
150
|
-
const key = `dir|${path_}`;
|
|
151
153
|
let alive = true;
|
|
152
|
-
setDirMore([]);
|
|
153
154
|
void (async () => {
|
|
154
155
|
try {
|
|
155
|
-
const data = await requestJson<DirPage>(`/api/files?cwd=${encodeURIComponent(cwd)}&path=${encodeURIComponent(
|
|
156
|
-
if (alive) setDirPage({ key, ...data });
|
|
156
|
+
const data = await requestJson<DirPage>(`/api/files?cwd=${encodeURIComponent(cwd)}&path=${encodeURIComponent(dirRel)}&offset=0`);
|
|
157
|
+
if (alive) setDirPage({ key: dirKey, ...data });
|
|
157
158
|
} catch (error) {
|
|
158
|
-
if (alive) setDirPage({ key, dir:
|
|
159
|
+
if (alive) setDirPage({ key: dirKey, dir: dirRel, entries: [], nextOffset: null, truncated: false, error: error instanceof Error ? error.message : t.viewerLoadFailed });
|
|
159
160
|
}
|
|
160
161
|
})();
|
|
161
162
|
return () => {
|
|
162
163
|
alive = false;
|
|
163
164
|
};
|
|
164
|
-
}, [
|
|
165
|
+
}, [dirRel, dirKey, cwd, t.viewerLoadFailed]);
|
|
165
166
|
|
|
166
167
|
/** 目录还有下一页时接着拉(大目录:node_modules 这种已被忽略,剩下的也不会太多) */
|
|
167
168
|
const loadMore = useCallback(async () => {
|
|
168
|
-
if (!
|
|
169
|
+
if (!page?.nextOffset || dirRel === null || !cwd) return;
|
|
170
|
+
const base = page;
|
|
169
171
|
try {
|
|
170
|
-
const data = await requestJson<DirPage>(`/api/files?cwd=${encodeURIComponent(cwd)}&path=${encodeURIComponent(
|
|
171
|
-
|
|
172
|
+
const data = await requestJson<DirPage>(`/api/files?cwd=${encodeURIComponent(cwd)}&path=${encodeURIComponent(dirRel)}&offset=${base.nextOffset}`);
|
|
173
|
+
// 请求途中切走了(或别的请求先换了这一页)就丢掉结果
|
|
174
|
+
setDirPage((current) => (current === base ? { ...base, ...data, key: base.key, entries: [...base.entries, ...data.entries] } : current));
|
|
172
175
|
} catch {
|
|
173
176
|
// 下一页失败就留在当前这页,用户可以再点
|
|
174
177
|
}
|
|
175
|
-
}, [
|
|
178
|
+
}, [page, dirRel, cwd]);
|
|
176
179
|
|
|
177
180
|
// 取数据
|
|
178
181
|
useEffect(() => {
|
|
@@ -497,10 +500,10 @@ export function FileViewer({
|
|
|
497
500
|
{loaded?.truncated && <div className="pw-viewer-notice" data-kind="warn">{mode === "diff" ? t.viewerPatchTruncated : t.filePreviewTruncated}</div>}
|
|
498
501
|
{isDir ? (
|
|
499
502
|
<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(
|
|
503
|
+
<div className="px-3 py-2" style={{ fontSize: 11.5, color: page?.error ? "var(--dsw-danger)" : "var(--dsw-label-caption)" }}>
|
|
504
|
+
{!page ? t.panelLoading : page.error ? page.error : t.viewerDirHint.replace("{n}", String(page.entries.length))}
|
|
502
505
|
</div>
|
|
503
|
-
{
|
|
506
|
+
{page?.entries.map((entry) => (
|
|
504
507
|
<button
|
|
505
508
|
key={entry.name}
|
|
506
509
|
type="button"
|
|
@@ -512,11 +515,11 @@ export function FileViewer({
|
|
|
512
515
|
<img className="pw-tree-icon" src={entry.kind === "dir" ? "/file-icons/folder-close.svg" : fileIconSrc(entry.name)} alt="" />
|
|
513
516
|
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 12.5, fontFamily: entry.kind === "dir" ? "inherit" : "var(--font-mono)" }}>{entry.name}</span>
|
|
514
517
|
{entry.kind === "file" && entry.size !== undefined && (
|
|
515
|
-
<span style={{ fontSize: 11, color: "var(--dsw-label-caption)", fontVariantNumeric: "tabular-nums" }}>{entry.size}
|
|
518
|
+
<span style={{ fontSize: 11, color: "var(--dsw-label-caption)", fontVariantNumeric: "tabular-nums" }}>{fmtBytes(entry.size)}</span>
|
|
516
519
|
)}
|
|
517
520
|
</button>
|
|
518
521
|
))}
|
|
519
|
-
{
|
|
522
|
+
{page?.nextOffset ? (
|
|
520
523
|
<button type="button" className="pw-chip mx-3 my-2" onClick={() => void loadMore()}>{t.viewerDirMore}</button>
|
|
521
524
|
) : null}
|
|
522
525
|
</div>
|