@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
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
IconShieldOutline16,
|
|
24
24
|
IconSkillOutline16,
|
|
25
25
|
IconTrashOutline16,
|
|
26
|
+
IconWarningOutline16,
|
|
26
27
|
} from "@/components/icons";
|
|
27
28
|
import { ProviderBrand } from "@/components/ProviderBrand";
|
|
28
29
|
import {
|
|
@@ -35,6 +36,7 @@ import { RemoteSection } from "@/components/RemoteSection";
|
|
|
35
36
|
import { CopyCommand } from "@/components/update/CopyCommand";
|
|
36
37
|
import { checkUpdate, loadUpdates, runUpdate, useUpdates, type UpdateTarget } from "@/components/update/update-store";
|
|
37
38
|
import { useI18n } from "@/i18n";
|
|
39
|
+
import { fmtTokens } from "@/lib/format";
|
|
38
40
|
import { applyPebrelTheme, loadPebrelTheme, loadThemeMode, type PebrelTheme, type ThemeMode } from "@/lib/theme";
|
|
39
41
|
import { applyFontChoice, CUSTOM_PREFIX, FONT_PRESETS, loadFontChoice } from "@/lib/font";
|
|
40
42
|
import {
|
|
@@ -47,7 +49,22 @@ import {
|
|
|
47
49
|
type StreamSettings,
|
|
48
50
|
type StreamStyle,
|
|
49
51
|
} from "@/lib/chat/stream-settings";
|
|
50
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
customModelGaps,
|
|
54
|
+
fillModelFields,
|
|
55
|
+
formatCapacity,
|
|
56
|
+
isInertProviderConfig,
|
|
57
|
+
modelDraftFromCatalog,
|
|
58
|
+
modelDraftFromConfig,
|
|
59
|
+
modelDraftsFromProviderConfig,
|
|
60
|
+
parseCapacity,
|
|
61
|
+
PI_MODEL_FALLBACKS,
|
|
62
|
+
serializeProviderDraft,
|
|
63
|
+
validateModelDrafts,
|
|
64
|
+
type CustomModelGap,
|
|
65
|
+
type ModelDraft,
|
|
66
|
+
type ModelFieldFill,
|
|
67
|
+
} from "@/lib/model-draft";
|
|
51
68
|
import { customApiOptions } from "@/lib/provider-display";
|
|
52
69
|
import { quotaWindowKind, supportsUsageProbe, type BalanceAmount, type BalanceDetailKey, type ProviderUsage, type QuotaWindow, type XaiUsage } from "@/lib/provider-usage";
|
|
53
70
|
import type { ProviderView } from "@/lib/models-service";
|
|
@@ -871,12 +888,167 @@ function FontFamilyPicker({ scope }: { scope: "ui" | "mono" }) {
|
|
|
871
888
|
|
|
872
889
|
// ---------- Models ----------
|
|
873
890
|
|
|
891
|
+
type AuditRow = { contextWindow: string; maxTokens: string; vision: "" | "image" | "text" };
|
|
892
|
+
const EMPTY_AUDIT_ROW: AuditRow = { contextWindow: "", maxTokens: "", vision: "" };
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* 模型配置体检:models[] 里有字段没写的模型,pi 会按 PI_MODEL_FALLBACKS / 纯文本处理。
|
|
896
|
+
* 不替用户猜——空着的格子由用户填(支持 256K / 1M 写法),保存时只补空字段,已写的值不改。
|
|
897
|
+
*/
|
|
898
|
+
function ModelFieldAudit({ gaps, providerLabel, busy, onSave }: {
|
|
899
|
+
gaps: CustomModelGap[];
|
|
900
|
+
providerLabel: (id: string) => string;
|
|
901
|
+
busy: boolean;
|
|
902
|
+
/** 保存成功返回 true(之后清空填写) */
|
|
903
|
+
onSave: (fills: ModelFieldFill[]) => Promise<boolean>;
|
|
904
|
+
}) {
|
|
905
|
+
const { t } = useI18n();
|
|
906
|
+
const [rows, setRows] = useState<Record<string, AuditRow>>({});
|
|
907
|
+
const keyOf = (gap: CustomModelGap) => `${gap.provider}\u0000${gap.index}`;
|
|
908
|
+
const rowOf = (gap: CustomModelGap) => rows[keyOf(gap)] ?? EMPTY_AUDIT_ROW;
|
|
909
|
+
const edit = (gap: CustomModelGap, patch: Partial<AuditRow>) =>
|
|
910
|
+
setRows((current) => ({ ...current, [keyOf(gap)]: { ...(current[keyOf(gap)] ?? EMPTY_AUDIT_ROW), ...patch } }));
|
|
911
|
+
const visionGaps = gaps.filter((gap) => gap.missing.includes("input"));
|
|
912
|
+
const markVision = (vision: "image" | "text") =>
|
|
913
|
+
setRows((current) => {
|
|
914
|
+
const next = { ...current };
|
|
915
|
+
for (const gap of visionGaps) next[keyOf(gap)] = { ...(next[keyOf(gap)] ?? EMPTY_AUDIT_ROW), vision };
|
|
916
|
+
return next;
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// 每行填的文本 → 要写的值;有一格格式不对就不让保存(那一格标红)
|
|
920
|
+
let invalid = false;
|
|
921
|
+
const fills: ModelFieldFill[] = [];
|
|
922
|
+
for (const gap of gaps) {
|
|
923
|
+
const row = rows[keyOf(gap)];
|
|
924
|
+
if (!row) continue;
|
|
925
|
+
const contextWindow = gap.missing.includes("contextWindow") ? parseCapacity(row.contextWindow) : undefined;
|
|
926
|
+
const maxTokens = gap.missing.includes("maxTokens") ? parseCapacity(row.maxTokens) : undefined;
|
|
927
|
+
if (Number.isNaN(contextWindow) || Number.isNaN(maxTokens)) {
|
|
928
|
+
invalid = true;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
const vision = gap.missing.includes("input") && row.vision ? row.vision === "image" : undefined;
|
|
932
|
+
if (contextWindow === undefined && maxTokens === undefined && vision === undefined) continue;
|
|
933
|
+
fills.push({ provider: gap.provider, index: gap.index, id: gap.id, contextWindow, maxTokens, vision });
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const save = async () => {
|
|
937
|
+
if (await onSave(fills)) setRows({});
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
const capacityCell = (gap: CustomModelGap, field: "contextWindow" | "maxTokens", label: string) => {
|
|
941
|
+
if (!gap.missing.includes(field)) {
|
|
942
|
+
return (
|
|
943
|
+
<span className="truncate" style={{ fontFamily: "var(--font-mono)", color: "var(--dsw-label-secondary)" }}>
|
|
944
|
+
<span className="sm:hidden" style={{ color: "var(--dsw-label-caption)" }}>{label} </span>
|
|
945
|
+
{formatCapacity(gap[field]) || "—"}
|
|
946
|
+
</span>
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
const text = rowOf(gap)[field];
|
|
950
|
+
return (
|
|
951
|
+
<input
|
|
952
|
+
className="pw-audit-field"
|
|
953
|
+
style={{ fontFamily: "var(--font-mono)" }}
|
|
954
|
+
value={text}
|
|
955
|
+
onChange={(event) => edit(gap, { [field]: event.target.value })}
|
|
956
|
+
placeholder={label}
|
|
957
|
+
aria-label={`${gap.id} ${label}`}
|
|
958
|
+
aria-invalid={Number.isNaN(parseCapacity(text)) || undefined}
|
|
959
|
+
disabled={busy}
|
|
960
|
+
/>
|
|
961
|
+
);
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
return (
|
|
965
|
+
<section className="flex flex-col gap-3 rounded-2xl p-4" style={{ border: "0.5px solid var(--dsw-border-l3)", background: "var(--dsw-hover)" }} data-testid="model-audit">
|
|
966
|
+
<div className="flex items-start gap-2.5">
|
|
967
|
+
<IconWarningOutline16 size={16} style={{ flex: "none", marginTop: 1, color: "var(--dsw-warn)" }} />
|
|
968
|
+
<div className="min-w-0 flex-1">
|
|
969
|
+
<div style={{ fontSize: 13.5, fontWeight: 600 }}>{t.modelAuditTitle.replace("{n}", String(gaps.length))}</div>
|
|
970
|
+
<div className="mt-1" style={{ fontSize: 12, color: "var(--dsw-label-caption)", lineHeight: 1.65 }}>
|
|
971
|
+
{t.modelAuditDesc
|
|
972
|
+
.replace("{ctx}", formatCapacity(PI_MODEL_FALLBACKS.contextWindow))
|
|
973
|
+
.replace("{out}", formatCapacity(PI_MODEL_FALLBACKS.maxTokens))}
|
|
974
|
+
</div>
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
|
|
978
|
+
<div style={{ fontSize: 12.5 }} role="table" aria-label={t.modelAuditTitle.replace("{n}", String(gaps.length))}>
|
|
979
|
+
<div role="row" className="hidden gap-x-2 px-1 pb-1.5 sm:grid sm:grid-cols-[minmax(0,1fr)_92px_92px_108px]" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
980
|
+
<span role="columnheader">{t.modelAuditColModel}</span>
|
|
981
|
+
<span role="columnheader">{t.modelAuditColContext}</span>
|
|
982
|
+
<span role="columnheader">{t.modelAuditColOutput}</span>
|
|
983
|
+
<span role="columnheader">{t.modelAuditColVision}</span>
|
|
984
|
+
</div>
|
|
985
|
+
<div className="max-h-[320px] overflow-y-auto" style={{ borderTop: "0.5px solid var(--dsw-border-l2)" }}>
|
|
986
|
+
{gaps.map((gap) => (
|
|
987
|
+
<div
|
|
988
|
+
key={keyOf(gap)}
|
|
989
|
+
role="row"
|
|
990
|
+
className="grid grid-cols-3 items-center gap-x-2 gap-y-1.5 px-1 py-2 sm:grid-cols-[minmax(0,1fr)_92px_92px_108px]"
|
|
991
|
+
style={{ borderBottom: "0.5px solid var(--dsw-border-l1)" }}
|
|
992
|
+
>
|
|
993
|
+
<div role="cell" className="col-span-3 min-w-0 sm:col-span-1" title={`${gap.provider} / ${gap.id}`}>
|
|
994
|
+
<div className="truncate" style={{ fontWeight: 500 }}>{gap.name ?? gap.id}</div>
|
|
995
|
+
<div className="truncate" style={{ fontSize: 11.5, fontFamily: "var(--font-mono)", color: "var(--dsw-label-caption)" }}>
|
|
996
|
+
{providerLabel(gap.provider)} / {gap.id}
|
|
997
|
+
</div>
|
|
998
|
+
</div>
|
|
999
|
+
<div role="cell" className="min-w-0">{capacityCell(gap, "contextWindow", t.modelAuditColContext)}</div>
|
|
1000
|
+
<div role="cell" className="min-w-0">{capacityCell(gap, "maxTokens", t.modelAuditColOutput)}</div>
|
|
1001
|
+
<div role="cell" className="min-w-0">
|
|
1002
|
+
{/* 输入类型:文本 pi 总是接受(勾上不可点),图片这一勾就是 models.json 的 input 里加 image */}
|
|
1003
|
+
{gap.missing.includes("input") || true ? (
|
|
1004
|
+
<div className="flex items-center gap-3" role="group" aria-label={`${gap.id} ${t.modelAuditColVision}`}>
|
|
1005
|
+
<label className="flex cursor-default items-center gap-1" style={{ color: "var(--dsw-label-tertiary)" }} title={t.visionInputTextHint}>
|
|
1006
|
+
<input type="checkbox" checked disabled readOnly />
|
|
1007
|
+
{t.visionInputText}
|
|
1008
|
+
</label>
|
|
1009
|
+
<label className="flex items-center gap-1" style={{ color: "var(--dsw-label-secondary)" }} title={t.visionInputImageHint}>
|
|
1010
|
+
<input
|
|
1011
|
+
type="checkbox"
|
|
1012
|
+
checked={gap.missing.includes("input") ? rowOf(gap).vision === "image" : gap.vision === true}
|
|
1013
|
+
disabled={busy || !gap.missing.includes("input")}
|
|
1014
|
+
onChange={(event) => edit(gap, { vision: event.target.checked ? "image" : "text" })}
|
|
1015
|
+
aria-label={`${gap.id} ${t.visionInputImage}`}
|
|
1016
|
+
/>
|
|
1017
|
+
{t.visionInputImage}
|
|
1018
|
+
</label>
|
|
1019
|
+
</div>
|
|
1020
|
+
) : null}
|
|
1021
|
+
</div>
|
|
1022
|
+
</div>
|
|
1023
|
+
))}
|
|
1024
|
+
</div>
|
|
1025
|
+
</div>
|
|
1026
|
+
|
|
1027
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
1028
|
+
{visionGaps.length > 0 ? (
|
|
1029
|
+
<>
|
|
1030
|
+
<span style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>{t.modelAuditVisionQuick}</span>
|
|
1031
|
+
<button className="btn-outline" style={{ height: 28, padding: "0 11px", fontSize: 12.5 }} disabled={busy} onClick={() => markVision("image")}>{t.modelAuditVisionOn}</button>
|
|
1032
|
+
<button className="btn-outline" style={{ height: 28, padding: "0 11px", fontSize: 12.5 }} disabled={busy} onClick={() => markVision("text")}>{t.modelAuditVisionOff}</button>
|
|
1033
|
+
</>
|
|
1034
|
+
) : null}
|
|
1035
|
+
<span className="flex-1" />
|
|
1036
|
+
<button className="btn-primary-white" style={{ height: 32 }} disabled={busy || invalid || fills.length === 0} onClick={() => void save()}>
|
|
1037
|
+
{busy ? t.saving : fills.length ? t.modelAuditSave.replace("{n}", String(fills.length)) : t.modelAuditSaveIdle}
|
|
1038
|
+
</button>
|
|
1039
|
+
</div>
|
|
1040
|
+
</section>
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
874
1044
|
function ModelsSection() {
|
|
875
1045
|
const { t } = useI18n();
|
|
876
1046
|
const [providers, setProviders] = useState<ProviderView[]>([]);
|
|
877
1047
|
const [custom, setCustom] = useState<{ providers: Record<string, any>; [key: string]: unknown }>({ providers: {} });
|
|
878
1048
|
const [customRevision, setCustomRevision] = useState<string | null>(null);
|
|
879
1049
|
const [secretProviderIds, setSecretProviderIds] = useState<Set<string>>(new Set());
|
|
1050
|
+
// 内置 provider → 目录里的模型 ID:编辑时同 ID 的行写 modelOverrides,「获取可用模型」里不再可选
|
|
1051
|
+
const [catalogModelIds, setCatalogModelIds] = useState<Record<string, string[]>>({});
|
|
880
1052
|
const [editing, setEditing] = useState<string | null>(null);
|
|
881
1053
|
const [keyDraft, setKeyDraft] = useState("");
|
|
882
1054
|
const [adding, setAdding] = useState<"builtin" | "custom" | null>(null);
|
|
@@ -955,6 +1127,7 @@ function ModelsSection() {
|
|
|
955
1127
|
throw new Error("invalid model configuration");
|
|
956
1128
|
}
|
|
957
1129
|
setProviders(j.data.providers);
|
|
1130
|
+
setCatalogModelIds(j.data.catalogModelIds ?? {});
|
|
958
1131
|
setSecretProviderIds(new Set(j.data.customProviders?.secretProviderIds ?? []));
|
|
959
1132
|
setCustom({ ...parsed, providers: parsed.providers ?? {} });
|
|
960
1133
|
setCustomRevision(typeof j.data.customProviders?.revision === "string" ? j.data.customProviders.revision : null);
|
|
@@ -1137,6 +1310,10 @@ function ModelsSection() {
|
|
|
1137
1310
|
}
|
|
1138
1311
|
};
|
|
1139
1312
|
|
|
1313
|
+
// 体检:直接从已载入的配置算(不另发请求),每次保存后 load() 重新载入就自动刷新
|
|
1314
|
+
const gaps = useMemo(() => customModelGaps(custom.providers ?? {}), [custom]);
|
|
1315
|
+
const [auditBusy, setAuditBusy] = useState(false);
|
|
1316
|
+
|
|
1140
1317
|
const call = async (body: Record<string, unknown>) => {
|
|
1141
1318
|
const j = await request(body);
|
|
1142
1319
|
notify(j.success, j.success && j.data?.requiresSessionReopen ? t.modelConfigReopen : j.error);
|
|
@@ -1181,6 +1358,23 @@ function ModelsSection() {
|
|
|
1181
1358
|
return call({ action: "saveCustomProviders", revision: customRevision, content: JSON.stringify({ ...custom, providers: content }, null, 2) });
|
|
1182
1359
|
};
|
|
1183
1360
|
|
|
1361
|
+
/** 体检卡片的保存:只补空字段,走与编辑 provider 同一条写入(revision 乐观锁、密钥由服务端从原文件恢复) */
|
|
1362
|
+
const saveAuditFills = async (fills: ModelFieldFill[]): Promise<boolean> => {
|
|
1363
|
+
const { providers: next, filled } = fillModelFields(custom.providers ?? {}, fills);
|
|
1364
|
+
if (!filled) return true;
|
|
1365
|
+
setAuditBusy(true);
|
|
1366
|
+
try {
|
|
1367
|
+
const result = await saveCustom(next);
|
|
1368
|
+
// 失败(典型是 revision 冲突:写入期间 pi 自己动过 models.json)也必须刷新本地副本与 revision。
|
|
1369
|
+
// 否则本地一直拿着旧 revision,之后每次保存都撞同一个 409——用户看到的就是「反复填、永远不成」。
|
|
1370
|
+
// call() 只在成功时 load(),这里把失败那一半补上。
|
|
1371
|
+
if (!result.success) await load();
|
|
1372
|
+
return Boolean(result.success);
|
|
1373
|
+
} finally {
|
|
1374
|
+
setAuditBusy(false);
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1184
1378
|
const providerName = (id: string) => providers.find((p) => p.id === id)?.name ?? id;
|
|
1185
1379
|
const credentialSource = (provider: ProviderView) => {
|
|
1186
1380
|
const source = provider.authSource;
|
|
@@ -1210,10 +1404,28 @@ function ModelsSection() {
|
|
|
1210
1404
|
setCName(typeof override?.name === "string" ? override.name : "");
|
|
1211
1405
|
setCModels((override?.models ?? []).map(modelDraftFromConfig));
|
|
1212
1406
|
} else {
|
|
1213
|
-
// 内置 provider:从 models.json
|
|
1407
|
+
// 内置 provider:从 models.json 覆盖层初始化(若有),models[] 定义和 modelOverrides 覆盖都作为行
|
|
1214
1408
|
setBBaseUrl(typeof override?.baseUrl === "string" ? override.baseUrl : "");
|
|
1215
|
-
setBModels((override
|
|
1409
|
+
setBModels(modelDraftsFromProviderConfig(override));
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
const catalogIdsOf = (providerId: string): ReadonlySet<string> => new Set(catalogModelIds[providerId] ?? []);
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* 内置 provider 的条目写回:什么都没覆盖时删掉条目(SDK 不认空条目,会让整份 models.json 存不进去)。
|
|
1417
|
+
* 前端拿到的配置不含 apiKey,存了密钥的条目不能当成空的删掉。
|
|
1418
|
+
* 返回 null = models.json 不用动(原本就没有条目、这次也没覆盖任何东西,比如只填了密钥)。
|
|
1419
|
+
*/
|
|
1420
|
+
const withBuiltinEntry = (providerId: string, config: Record<string, unknown>): Record<string, any> | null => {
|
|
1421
|
+
const next = { ...custom.providers };
|
|
1422
|
+
if (isInertProviderConfig(config) && !secretProviderIds.has(providerId)) {
|
|
1423
|
+
if (!Object.prototype.hasOwnProperty.call(next, providerId)) return null;
|
|
1424
|
+
delete next[providerId];
|
|
1425
|
+
} else {
|
|
1426
|
+
next[providerId] = config;
|
|
1216
1427
|
}
|
|
1428
|
+
return next;
|
|
1217
1429
|
};
|
|
1218
1430
|
|
|
1219
1431
|
const saveCustomEdit = async () => {
|
|
@@ -1247,12 +1459,12 @@ function ModelsSection() {
|
|
|
1247
1459
|
if (invalid) return;
|
|
1248
1460
|
setEditBusy(true);
|
|
1249
1461
|
try {
|
|
1250
|
-
const
|
|
1251
|
-
next[provider.id] = serializeProviderDraft(next[provider.id] ?? {}, {
|
|
1462
|
+
const config = serializeProviderDraft(custom.providers[provider.id] ?? {}, {
|
|
1252
1463
|
baseUrl: bBaseUrl,
|
|
1253
1464
|
models: bModels,
|
|
1254
|
-
}, provider.apis[0] ?? "openai-completions");
|
|
1255
|
-
const
|
|
1465
|
+
}, provider.apis[0] ?? "openai-completions", catalogIdsOf(provider.id));
|
|
1466
|
+
const next = withBuiltinEntry(provider.id, config);
|
|
1467
|
+
const j = next ? await saveCustom(next) : { success: true };
|
|
1256
1468
|
if (j.success && keyDraft.trim()) {
|
|
1257
1469
|
void call({ action: "setKey", providerId: provider.id, apiKey: keyDraft.trim() });
|
|
1258
1470
|
setOauthDone(null);
|
|
@@ -1283,8 +1495,9 @@ function ModelsSection() {
|
|
|
1283
1495
|
};
|
|
1284
1496
|
|
|
1285
1497
|
const saveBuiltinSetup = async ({ providerId, apiKey, config }: BuiltinProviderSetup) => {
|
|
1286
|
-
const next =
|
|
1287
|
-
|
|
1498
|
+
const next = withBuiltinEntry(providerId, { ...custom.providers[providerId], ...config });
|
|
1499
|
+
// 只填了密钥:models.json 不需要条目,直接去存密钥
|
|
1500
|
+
const result = next ? await saveCustom(next) : { success: true };
|
|
1288
1501
|
if (result.success && apiKey) {
|
|
1289
1502
|
// 多步认证(Vertex/Bedrock/Cloudflare 等)的后续提示经登录弹窗应答;
|
|
1290
1503
|
// 不能 await setKey —— 它会挂起到全部提示答完。
|
|
@@ -1467,7 +1680,7 @@ function ModelsSection() {
|
|
|
1467
1680
|
});
|
|
1468
1681
|
const result = await response.json();
|
|
1469
1682
|
if (!result.success) throw new Error(result.error || t.toastError);
|
|
1470
|
-
return (result.data.models ?? []).map(
|
|
1683
|
+
return (result.data.models ?? []).map(modelDraftFromCatalog);
|
|
1471
1684
|
}}
|
|
1472
1685
|
discoverDisabled={!cBaseUrl.trim()}
|
|
1473
1686
|
/>
|
|
@@ -1514,6 +1727,7 @@ function ModelsSection() {
|
|
|
1514
1727
|
setModels={setBModels}
|
|
1515
1728
|
updateModel={(index, patch) => setBModels((current) => current.map((model, at) => (at === index ? { ...model, ...patch } : model)))}
|
|
1516
1729
|
inheritedCount={p.modelCount}
|
|
1730
|
+
catalogIds={catalogIdsOf(p.id)}
|
|
1517
1731
|
discover={async (allowPrivate) => {
|
|
1518
1732
|
const response = await fetch("/api/models", {
|
|
1519
1733
|
method: "POST",
|
|
@@ -1523,7 +1737,7 @@ function ModelsSection() {
|
|
|
1523
1737
|
});
|
|
1524
1738
|
const result = await response.json();
|
|
1525
1739
|
if (!result.success) throw new Error(result.error || t.toastError);
|
|
1526
|
-
return (result.data.models ?? []).map(
|
|
1740
|
+
return (result.data.models ?? []).map(modelDraftFromCatalog);
|
|
1527
1741
|
}}
|
|
1528
1742
|
discoverDisabled={!(bBaseUrl.trim() || p.baseUrl)}
|
|
1529
1743
|
/>
|
|
@@ -1566,6 +1780,11 @@ function ModelsSection() {
|
|
|
1566
1780
|
})}
|
|
1567
1781
|
</div>
|
|
1568
1782
|
|
|
1783
|
+
{/* 模型配置体检:没写的字段 pi 会按缺省处理;由用户填,不替他猜 */}
|
|
1784
|
+
{gaps.length > 0 ? (
|
|
1785
|
+
<ModelFieldAudit gaps={gaps} providerLabel={providerName} busy={auditBusy || customRevision === null} onSave={saveAuditFills} />
|
|
1786
|
+
) : null}
|
|
1787
|
+
|
|
1569
1788
|
{/* 两个添加按钮 */}
|
|
1570
1789
|
<div className="flex gap-3">
|
|
1571
1790
|
<button
|
|
@@ -1594,6 +1813,7 @@ function ModelsSection() {
|
|
|
1594
1813
|
<ProviderSetupModal
|
|
1595
1814
|
mode={adding}
|
|
1596
1815
|
providers={addable}
|
|
1816
|
+
catalogModelIds={catalogModelIds}
|
|
1597
1817
|
existingIds={providers.map((provider) => provider.id)}
|
|
1598
1818
|
onClose={() => setAdding(null)}
|
|
1599
1819
|
onSaveBuiltin={saveBuiltinSetup}
|
|
@@ -1720,12 +1940,13 @@ function ModelsSection() {
|
|
|
1720
1940
|
|
|
1721
1941
|
</div>
|
|
1722
1942
|
);
|
|
1723
|
-
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
const fieldCls = "w-full rounded-xl px-3 py-2";
|
|
1724
1946
|
|
|
1725
|
-
/** 配额展示:token
|
|
1947
|
+
/** 配额展示:token 数转换单位(53,000,000 → 53.00M),与统计条 / 上下文弹窗口径一致 */
|
|
1726
1948
|
function fmtQuotaTokens(value: number): string {
|
|
1727
|
-
|
|
1728
|
-
return value.toLocaleString();
|
|
1949
|
+
return fmtTokens(value);
|
|
1729
1950
|
}
|
|
1730
1951
|
|
|
1731
1952
|
/** 套餐值来自服务端声明(如 "pro"/"plus"),首字母大写后直接跟在标签后显示。 */
|
|
@@ -1994,7 +2215,8 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
|
|
|
1994
2215
|
|
|
1995
2216
|
// ---------- Plugins ----------
|
|
1996
2217
|
|
|
1997
|
-
|
|
2218
|
+
/** 导出仅供测试(其余 section 不导出) */
|
|
2219
|
+
export function PluginsSection({ cwd }: { cwd: string }) {
|
|
1998
2220
|
const { t, lang } = useI18n();
|
|
1999
2221
|
const confirmUninstall = (source: string) => window.confirm(t.confirmUninstallPackage.replace("{source}", source));
|
|
2000
2222
|
const [tab, setTab] = useState<"config" | "list">("config");
|
|
@@ -2111,13 +2333,19 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2111
2333
|
})),
|
|
2112
2334
|
].filter((x) => !query || x.name.toLowerCase().includes(query));
|
|
2113
2335
|
|
|
2114
|
-
|
|
2336
|
+
/** 扩展卡片的启用状态:每张卡只在头部显示这一处,展开区不重复 */
|
|
2337
|
+
const stateBadge = (disabled: boolean) => (
|
|
2115
2338
|
<span
|
|
2116
2339
|
className="flex flex-none items-center gap-1.5 rounded-md px-1.5 py-0.5"
|
|
2117
|
-
style={{
|
|
2340
|
+
style={{
|
|
2341
|
+
fontSize: 10.5,
|
|
2342
|
+
...(disabled
|
|
2343
|
+
? { background: "var(--dsw-selector)", color: "var(--dsw-label-tertiary)" }
|
|
2344
|
+
: { background: "rgba(34,197,94,.12)", color: "var(--dsw-success)" }),
|
|
2345
|
+
}}
|
|
2118
2346
|
>
|
|
2119
|
-
<span className="h-1.5 w-1.5 rounded-full" style={{ background: "var(--dsw-success)" }} />
|
|
2120
|
-
{t.enabled}
|
|
2347
|
+
<span className="h-1.5 w-1.5 rounded-full" style={{ background: disabled ? "var(--dsw-label-tertiary)" : "var(--dsw-success)" }} />
|
|
2348
|
+
{disabled ? t.disabled : t.enabled}
|
|
2121
2349
|
</span>
|
|
2122
2350
|
);
|
|
2123
2351
|
|
|
@@ -2234,9 +2462,7 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2234
2462
|
{e.error && (
|
|
2235
2463
|
<span className="rounded px-1.5 py-0.5" style={{ fontSize: 10.5, color: "var(--dsw-danger)", background: "var(--pw-danger-soft, rgba(236,19,19,.12))" }}>{t.extensionLoadError}</span>
|
|
2236
2464
|
)}
|
|
2237
|
-
{e.
|
|
2238
|
-
<span className="rounded px-1.5 py-0.5" style={{ fontSize: 10.5, color: "var(--dsw-label-tertiary)", background: "var(--dsw-selector)" }}>{t.disabled}</span>
|
|
2239
|
-
)}
|
|
2465
|
+
{!e.error && stateBadge(e.disabled === true)}
|
|
2240
2466
|
</div>
|
|
2241
2467
|
<div className="truncate" style={{ fontSize: 12, color: e.error ? "var(--dsw-danger)" : "var(--dsw-label-caption)" }} title={e.error ?? e.path}>
|
|
2242
2468
|
{e.error ?? e.path}
|
|
@@ -2258,7 +2484,6 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2258
2484
|
>
|
|
2259
2485
|
{scopeLabel(scope)}
|
|
2260
2486
|
</span>
|
|
2261
|
-
{badge}
|
|
2262
2487
|
</div>
|
|
2263
2488
|
<div className="break-all" style={{ fontFamily: "var(--font-mono)", fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
2264
2489
|
{e.path}
|
|
@@ -2341,7 +2566,7 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2341
2566
|
<span className="h-3 w-3 flex-none animate-spin rounded-full border border-[var(--dsw-label-tertiary)] border-t-transparent" />
|
|
2342
2567
|
) : !isDisabled ? (
|
|
2343
2568
|
<span
|
|
2344
|
-
|
|
2569
|
+
className="h-2 w-2 flex-none rounded-full"
|
|
2345
2570
|
style={{ background: "#22c55e", boxShadow: "0 0 5px rgba(34, 197, 94, 0.4)" }}
|
|
2346
2571
|
/>
|
|
2347
2572
|
) : null}
|
|
@@ -2355,8 +2580,8 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2355
2580
|
>
|
|
2356
2581
|
{isDisabled ? t.disabled : t.enabled}
|
|
2357
2582
|
</span>
|
|
2358
|
-
|
|
2359
|
-
|
|
2583
|
+
<IconChevronDown14
|
|
2584
|
+
size={13}
|
|
2360
2585
|
style={{
|
|
2361
2586
|
color: "var(--dsw-label-tertiary)",
|
|
2362
2587
|
transform: isMenuOpen ? "rotate(180deg)" : "none",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
4
|
import { IconCloseOutline14, IconSearchOutline16 } from "@/components/icons";
|
|
5
5
|
import { useI18n } from "@/i18n";
|
|
6
|
+
import { fmtTokens } from "@/lib/format";
|
|
6
7
|
import type { TrajEntry, TrajTokens } from "@/lib/types";
|
|
7
8
|
import styles from "./TrajectoryAnalyzer.module.css";
|
|
8
9
|
|
|
@@ -26,10 +27,7 @@ const KIND_NAME: Record<TrajEntry["kind"], string> = {
|
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
function fmtTok(value?: number): string {
|
|
29
|
-
|
|
30
|
-
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
|
|
31
|
-
if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`;
|
|
32
|
-
return String(value);
|
|
30
|
+
return value == null ? "—" : fmtTokens(value);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
function fmtTime(ts: number): string {
|
|
@@ -45,8 +43,8 @@ function durationOf(entry: TrajEntry): number | undefined {
|
|
|
45
43
|
|
|
46
44
|
function fmtDur(ms?: number): string {
|
|
47
45
|
if (ms == null) return "—";
|
|
48
|
-
if (ms >= 60_000) return `${(ms / 60_000).toFixed(
|
|
49
|
-
if (ms >= 1_000) return `${(ms / 1_000).toFixed(
|
|
46
|
+
if (ms >= 60_000) return `${(ms / 60_000).toFixed(2)}m`;
|
|
47
|
+
if (ms >= 1_000) return `${(ms / 1_000).toFixed(2)}s`;
|
|
50
48
|
return `${Math.round(ms)}ms`;
|
|
51
49
|
}
|
|
52
50
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, type RefObject } from "react";
|
|
4
|
+
|
|
5
|
+
const BEFORE_LAYOUT = "piweb:before-chat-layout";
|
|
6
|
+
|
|
7
|
+
/** Call before changing the columns, while the currently visible text still has its old geometry. */
|
|
8
|
+
export function preserveChatReadingPosition(): void {
|
|
9
|
+
window.dispatchEvent(new Event(BEFORE_LAYOUT));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ReadingAnchor {
|
|
13
|
+
element?: Element;
|
|
14
|
+
range?: Range;
|
|
15
|
+
top: number;
|
|
16
|
+
bottom: boolean;
|
|
17
|
+
width: number;
|
|
18
|
+
capturedAt: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function useChatReadingAnchor(rootRef: RefObject<HTMLElement | null>, scrollRef: RefObject<HTMLElement | null>, shouldPin: () => boolean): void {
|
|
22
|
+
const anchor = useRef<ReadingAnchor | null>(null);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const root = rootRef.current;
|
|
25
|
+
const scroller = scrollRef.current;
|
|
26
|
+
if (!root || !scroller) return;
|
|
27
|
+
let frame = 0;
|
|
28
|
+
const restore = () => {
|
|
29
|
+
const saved = anchor.current;
|
|
30
|
+
anchor.current = null;
|
|
31
|
+
if (!saved || Date.now() - saved.capturedAt > 500 || root.getBoundingClientRect().width === saved.width) return;
|
|
32
|
+
if (saved.bottom) {
|
|
33
|
+
scroller.scrollTop = scroller.scrollHeight;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const rect = saved.range?.startContainer.isConnected ? saved.range.getBoundingClientRect() : saved.element?.isConnected ? saved.element.getBoundingClientRect() : null;
|
|
37
|
+
if (rect) scroller.scrollTop += rect.top - scroller.getBoundingClientRect().top - saved.top;
|
|
38
|
+
};
|
|
39
|
+
const capture = () => {
|
|
40
|
+
if (frame) cancelAnimationFrame(frame);
|
|
41
|
+
// A new drag frame may arrive before the previous restore frame. Finish it first.
|
|
42
|
+
restore();
|
|
43
|
+
const geometry = { width: root.getBoundingClientRect().width, capturedAt: Date.now() };
|
|
44
|
+
if (shouldPin()) {
|
|
45
|
+
anchor.current = { top: 0, bottom: true, ...geometry };
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const viewport = scroller.getBoundingClientRect();
|
|
49
|
+
const element = [...scroller.querySelectorAll("p, pre, li, h1, h2, h3, h4, table, [data-role='user']")].find((node) => {
|
|
50
|
+
const rect = node.getBoundingClientRect();
|
|
51
|
+
return rect.bottom > viewport.top + 8 && rect.top < viewport.bottom;
|
|
52
|
+
});
|
|
53
|
+
if (!element) return;
|
|
54
|
+
const rect = element.getBoundingClientRect();
|
|
55
|
+
const x = Math.max(viewport.left + 8, rect.left + 8);
|
|
56
|
+
const y = Math.max(viewport.top + 8, rect.top + 4);
|
|
57
|
+
const range = document.caretRangeFromPoint?.(x, y);
|
|
58
|
+
if (range && scroller.contains(range.startContainer) && range.startContainer.nodeType === Node.TEXT_NODE && range.startOffset < (range.startContainer.textContent?.length ?? 0)) {
|
|
59
|
+
range.setEnd(range.startContainer, range.startOffset + 1);
|
|
60
|
+
anchor.current = { element, range, top: range.getBoundingClientRect().top - viewport.top, bottom: false, ...geometry };
|
|
61
|
+
} else {
|
|
62
|
+
anchor.current = { element, top: rect.top - viewport.top, bottom: false, ...geometry };
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const observer = new ResizeObserver(() => {
|
|
66
|
+
if (!anchor.current) return;
|
|
67
|
+
if (frame) cancelAnimationFrame(frame);
|
|
68
|
+
frame = requestAnimationFrame(() => {
|
|
69
|
+
frame = 0;
|
|
70
|
+
restore();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
observer.observe(root);
|
|
74
|
+
window.addEventListener(BEFORE_LAYOUT, capture);
|
|
75
|
+
return () => {
|
|
76
|
+
observer.disconnect();
|
|
77
|
+
window.removeEventListener(BEFORE_LAYOUT, capture);
|
|
78
|
+
if (frame) cancelAnimationFrame(frame);
|
|
79
|
+
};
|
|
80
|
+
}, [rootRef, scrollRef, shouldPin]);
|
|
81
|
+
}
|