@rexvane/piweb 0.3.20 → 0.3.22
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 -4
- 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 +12 -12
- package/.next/required-server-files.js +18 -2
- package/.next/required-server-files.json +18 -2
- package/.next/routes-manifest.json +12 -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 +3 -3
- 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]/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 +3 -3
- 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 +1 -1
- 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 +19 -13
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +25 -19
- package/.next/server/app/index.segments/_full.segment.rsc +19 -13
- package/.next/server/app/index.segments/_tree.segment.rsc +3 -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 +6 -4
- package/.next/server/chunks/229.js +1 -1
- package/.next/server/chunks/421.js +1 -0
- package/.next/server/chunks/430.js +215 -31
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/538.js +1 -1
- package/.next/server/chunks/662.js +2 -0
- package/.next/server/chunks/686.js +1 -1
- package/.next/server/chunks/807.js +1 -1
- package/.next/server/chunks/813.js +23 -1
- package/.next/server/chunks/920.js +2 -2
- package/.next/server/chunks/924.js +1 -1
- package/.next/server/chunks/970.js +305 -0
- 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/static/WMazYvD-gbeLsCUfUxvvM/_buildManifest.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.4cf66cbdd8364c57.js +1 -0
- package/.next/static/chunks/4686-09fcd0028079006c.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.67366dc5032a141d.js +1 -0
- package/.next/static/chunks/8552.25c39ef4c995eba0.js +1 -0
- package/.next/static/chunks/app/_global-error/page-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/config/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/files/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/fs/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/git/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/github/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/growth/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/health/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/models/defaults/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/models/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/remote/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/security/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/skills/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/update/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/version/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/app/page-fc4ed2ff29b6235e.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-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f977af7f858990a2.js +1 -0
- package/.next/static/chunks/webpack-22ebffc20adb44ae.js +1 -0
- package/.next/static/css/6671d0d1589fdc0e.css +1 -0
- package/.next/static/css/{2959bd6b626b7ffe.css → f15a853af8b5127f.css} +1 -1
- 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]/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 +3 -1
- package/.next/types/validator.ts +76 -2
- package/README.md +14 -5
- package/next.config.ts +4 -2
- package/package.json +2 -2
- package/scripts/release.mjs +1 -1
- package/src/app/api/agent/[id]/events/route.ts +42 -13
- 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/api/update/route.ts +18 -2
- package/src/app/globals.css +275 -0
- package/src/components/AppShell.tsx +89 -33
- package/src/components/ChatInput.tsx +245 -63
- package/src/components/ChatWindow.tsx +123 -108
- package/src/components/ContextMeter.tsx +204 -123
- package/src/components/DrawerPanel.tsx +19 -26
- package/src/components/ExtensionUI.tsx +8 -2
- package/src/components/GitPanel.tsx +110 -40
- package/src/components/MarkdownBlock.tsx +333 -37
- 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 +365 -80
- package/src/components/TrajectoryView.tsx +4 -6
- package/src/components/chat/chat-scroll.ts +18 -0
- package/src/components/composer/ComposerImages.tsx +306 -0
- package/src/components/update/CopyCommand.tsx +34 -0
- package/src/components/update/UpdateNotice.tsx +130 -0
- package/src/components/update/update-store.ts +155 -0
- package/src/hooks/useGrowth.ts +3 -0
- package/src/hooks/usePiWeb.ts +54 -27
- package/src/hooks/useWorkspaceFiles.ts +93 -0
- package/src/i18n.tsx +182 -6
- package/src/instrumentation-node.ts +3 -0
- package/src/lib/agent-manager.ts +193 -119
- package/src/lib/chat/event-stream.ts +113 -0
- package/src/lib/chat/stream-settings.ts +192 -0
- package/src/lib/files-service.ts +100 -1
- 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/pi.ts +27 -2
- package/src/lib/process-format.ts +3 -2
- package/src/lib/session/metrics.ts +31 -0
- package/src/lib/thinking-level.ts +39 -0
- package/src/lib/trajectory.ts +17 -3
- package/src/lib/types.ts +19 -1
- package/src/lib/update/startup-check.ts +117 -0
- package/src/lib/update-service.ts +23 -3
- package/src/lib/workspace-index.ts +270 -0
- package/.next/server/chunks/131.js +0 -1
- package/.next/server/chunks/243.js +0 -1
- package/.next/server/chunks/741.js +0 -165
- package/.next/static/_Y3Lq0lI5RJ1tFSOnDi0L/_buildManifest.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-b1e9fa876d0ea671.js +0 -1
- package/.next/static/chunks/4778.e4c9ed093a413bc4.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.d3fe337a9117c325.js +0 -1
- package/.next/static/chunks/8552.6bfe3e101250b20f.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-2e425ef83fba0782.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-3614a8337843456b.js +0 -1
- package/.next/static/css/7391c42e1ae617f9.css +0 -1
- /package/.next/static/{_Y3Lq0lI5RJ1tFSOnDi0L → WMazYvD-gbeLsCUfUxvvM}/_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 {
|
|
@@ -32,10 +33,38 @@ import {
|
|
|
32
33
|
type CustomProviderSetup,
|
|
33
34
|
} from "@/components/ProviderSetupModal";
|
|
34
35
|
import { RemoteSection } from "@/components/RemoteSection";
|
|
36
|
+
import { CopyCommand } from "@/components/update/CopyCommand";
|
|
37
|
+
import { checkUpdate, loadUpdates, runUpdate, useUpdates, type UpdateTarget } from "@/components/update/update-store";
|
|
35
38
|
import { useI18n } from "@/i18n";
|
|
39
|
+
import { fmtTokens } from "@/lib/format";
|
|
36
40
|
import { applyPebrelTheme, loadPebrelTheme, loadThemeMode, type PebrelTheme, type ThemeMode } from "@/lib/theme";
|
|
37
41
|
import { applyFontChoice, CUSTOM_PREFIX, FONT_PRESETS, loadFontChoice } from "@/lib/font";
|
|
38
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
DEFAULT_STREAM_SETTINGS,
|
|
44
|
+
getStreamSettings,
|
|
45
|
+
setStreamSettings,
|
|
46
|
+
STREAM_SPEED_MAX,
|
|
47
|
+
STREAM_SPEED_MIN,
|
|
48
|
+
STREAM_STYLES,
|
|
49
|
+
type StreamSettings,
|
|
50
|
+
type StreamStyle,
|
|
51
|
+
} from "@/lib/chat/stream-settings";
|
|
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";
|
|
39
68
|
import { customApiOptions } from "@/lib/provider-display";
|
|
40
69
|
import { quotaWindowKind, supportsUsageProbe, type BalanceAmount, type BalanceDetailKey, type ProviderUsage, type QuotaWindow, type XaiUsage } from "@/lib/provider-usage";
|
|
41
70
|
import type { ProviderView } from "@/lib/models-service";
|
|
@@ -174,6 +203,7 @@ function GeneralSection({
|
|
|
174
203
|
const [themeMode, setThemeMode] = useState<ThemeMode>("system");
|
|
175
204
|
const [fontSize, setFontSize] = useState(14);
|
|
176
205
|
const [chatFontSize, setChatFontSize] = useState(14);
|
|
206
|
+
const [stream, setStream] = useState<StreamSettings>(DEFAULT_STREAM_SETTINGS);
|
|
177
207
|
const [enterBehavior, setEnterBehavior] = useState<EnterBehavior>("steer");
|
|
178
208
|
const [piSettings, setPiSettings] = useState<{ compaction: { enabled: boolean; reserveTokens: number; keepRecentTokens: number }; retry: { enabled: boolean; maxRetries: number; baseDelayMs: number } } | null>(null);
|
|
179
209
|
const [versions, setVersions] = useState<{ piWeb: string; piEngine: string } | null>(null);
|
|
@@ -222,6 +252,8 @@ function GeneralSection({
|
|
|
222
252
|
if (!Number.isNaN(fs)) setFontSize(fs);
|
|
223
253
|
const chatFs = parseInt(localStorage.getItem("piweb.chatFontSize") ?? "14", 10);
|
|
224
254
|
if (!Number.isNaN(chatFs)) setChatFontSize(chatFs);
|
|
255
|
+
// 流式输出偏好与主题/字体同源(localStorage),同样等挂载后再读,避免水合不一致
|
|
256
|
+
setStream(getStreamSettings());
|
|
225
257
|
setEnterBehavior(getEnterBehavior());
|
|
226
258
|
void loadRemoteSettings();
|
|
227
259
|
}, []);
|
|
@@ -254,6 +286,9 @@ function GeneralSection({
|
|
|
254
286
|
document.documentElement.style.setProperty("--piweb-chat-font-size", `${v}px`);
|
|
255
287
|
};
|
|
256
288
|
|
|
289
|
+
/** 流式输出:样式 / 速度 / 光标。改完即持久化,正在流出的回答下一帧就换(订阅在 MarkdownBlock 里) */
|
|
290
|
+
const pickStream = (patch: Partial<StreamSettings>) => setStream(setStreamSettings(patch));
|
|
291
|
+
|
|
257
292
|
const pickEnter = (v: EnterBehavior) => {
|
|
258
293
|
setEnterBehavior(v);
|
|
259
294
|
saveEnterBehavior(v);
|
|
@@ -316,6 +351,20 @@ function GeneralSection({
|
|
|
316
351
|
{ id: "pebrel", label: t.themePebrel, light: "#F3F4F6", dark: "#0F111A", accent: "#52A8FF" },
|
|
317
352
|
];
|
|
318
353
|
|
|
354
|
+
const streamStyleText: Record<StreamStyle, { label: string; desc: string }> = {
|
|
355
|
+
smooth: { label: t.streamStyleSmooth, desc: t.streamStyleSmoothDesc },
|
|
356
|
+
typewriter: { label: t.streamStyleTypewriter, desc: t.streamStyleTypewriterDesc },
|
|
357
|
+
fade: { label: t.streamStyleFade, desc: t.streamStyleFadeDesc },
|
|
358
|
+
instant: { label: t.streamStyleInstant, desc: t.streamStyleInstantDesc },
|
|
359
|
+
};
|
|
360
|
+
const streamSpeedText: Record<number, string> = {
|
|
361
|
+
1: t.streamSpeed1,
|
|
362
|
+
2: t.streamSpeed2,
|
|
363
|
+
3: t.streamSpeed3,
|
|
364
|
+
4: t.streamSpeed4,
|
|
365
|
+
5: t.streamSpeed5,
|
|
366
|
+
};
|
|
367
|
+
|
|
319
368
|
return (
|
|
320
369
|
<div className="flex flex-col gap-6">
|
|
321
370
|
{(settingsLoading || settingsError) && (
|
|
@@ -405,6 +454,79 @@ function GeneralSection({
|
|
|
405
454
|
<Row title={t.fontMonoFamily} desc={t.fontMonoFamilyDesc}>
|
|
406
455
|
<FontFamilyPicker scope="mono" />
|
|
407
456
|
</Row>
|
|
457
|
+
<RowColumn title={t.streamStyleTitle} desc={t.streamStyleDesc}>
|
|
458
|
+
<div className="flex w-full flex-wrap gap-2">
|
|
459
|
+
{STREAM_STYLES.map((id) => {
|
|
460
|
+
const active = stream.style === id;
|
|
461
|
+
return (
|
|
462
|
+
<button
|
|
463
|
+
key={id}
|
|
464
|
+
type="button"
|
|
465
|
+
className="flex flex-col items-start gap-1 text-left transition-colors"
|
|
466
|
+
style={{
|
|
467
|
+
flex: "1 1 150px",
|
|
468
|
+
padding: "12px 16px",
|
|
469
|
+
borderRadius: 14,
|
|
470
|
+
border: `0.5px solid ${active ? "var(--dsw-border-strong)" : "var(--dsw-border-l4)"}`,
|
|
471
|
+
background: active ? "var(--dsw-module-platform)" : "transparent",
|
|
472
|
+
color: "var(--dsw-label-primary)",
|
|
473
|
+
lineHeight: "18px",
|
|
474
|
+
}}
|
|
475
|
+
aria-pressed={active}
|
|
476
|
+
onClick={() => pickStream({ style: id })}
|
|
477
|
+
>
|
|
478
|
+
<span className="flex items-center gap-1.5" style={{ fontSize: 14 }}>
|
|
479
|
+
{active ? <IconCheckOutline14 size={13} /> : null}
|
|
480
|
+
{streamStyleText[id].label}
|
|
481
|
+
</span>
|
|
482
|
+
<span style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{streamStyleText[id].desc}</span>
|
|
483
|
+
</button>
|
|
484
|
+
);
|
|
485
|
+
})}
|
|
486
|
+
</div>
|
|
487
|
+
</RowColumn>
|
|
488
|
+
<RowColumn title={t.streamSpeedTitle} desc={t.streamSpeedDesc}>
|
|
489
|
+
<div className="flex w-full flex-wrap gap-2">
|
|
490
|
+
{Array.from({ length: STREAM_SPEED_MAX - STREAM_SPEED_MIN + 1 }, (_, i) => STREAM_SPEED_MIN + i).map((level) => {
|
|
491
|
+
const active = stream.speed === level;
|
|
492
|
+
return (
|
|
493
|
+
<button
|
|
494
|
+
key={level}
|
|
495
|
+
type="button"
|
|
496
|
+
className="transition-colors disabled:opacity-50"
|
|
497
|
+
// 即时样式不排队,速度对它没有意义:置灰,免得以为调了没反应
|
|
498
|
+
disabled={stream.style === "instant"}
|
|
499
|
+
style={{
|
|
500
|
+
flex: "1 1 84px",
|
|
501
|
+
padding: "10px 8px",
|
|
502
|
+
borderRadius: 12,
|
|
503
|
+
border: `0.5px solid ${active ? "var(--dsw-border-strong)" : "var(--dsw-border-l4)"}`,
|
|
504
|
+
background: active ? "var(--dsw-module-platform)" : "transparent",
|
|
505
|
+
color: active ? "var(--dsw-label-primary)" : "var(--dsw-label-secondary)",
|
|
506
|
+
fontSize: 13,
|
|
507
|
+
}}
|
|
508
|
+
aria-pressed={active}
|
|
509
|
+
onClick={() => pickStream({ speed: level })}
|
|
510
|
+
>
|
|
511
|
+
{streamSpeedText[level]}
|
|
512
|
+
</button>
|
|
513
|
+
);
|
|
514
|
+
})}
|
|
515
|
+
</div>
|
|
516
|
+
</RowColumn>
|
|
517
|
+
<Row title={t.streamCaretTitle} desc={t.streamCaretDesc}>
|
|
518
|
+
<button
|
|
519
|
+
type="button"
|
|
520
|
+
className="relative h-5 w-9 flex-none rounded-full transition-colors"
|
|
521
|
+
style={{ background: stream.caret ? "var(--dsw-accent)" : "var(--dsw-border-l3)" }}
|
|
522
|
+
role="switch"
|
|
523
|
+
aria-checked={stream.caret}
|
|
524
|
+
aria-label={t.streamCaretTitle}
|
|
525
|
+
onClick={() => pickStream({ caret: !stream.caret })}
|
|
526
|
+
>
|
|
527
|
+
<span className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all" style={{ left: stream.caret ? 18 : 2 }} />
|
|
528
|
+
</button>
|
|
529
|
+
</Row>
|
|
408
530
|
<Row title={t.enterBehavior} desc={t.enterBehaviorDesc}>
|
|
409
531
|
<SelectOption
|
|
410
532
|
value={enterBehavior}
|
|
@@ -479,61 +601,20 @@ function VersionLink({ href, value }: { href: string; value?: string }) {
|
|
|
479
601
|
);
|
|
480
602
|
}
|
|
481
603
|
|
|
482
|
-
type UpdatePhase = "idle" | "checking" | "available" | "updating" | "latest" | "updated" | "error";
|
|
483
|
-
|
|
484
604
|
/** 检查更新 / 更新控件:npm 安装的 piweb 走 npm install -g @rexvane/piweb@latest(服务端固定参数),
|
|
485
|
-
* Git 检出仍走 git pull + 本地构建;pi 走 npm install @latest
|
|
486
|
-
|
|
605
|
+
* Git 检出仍走 git pull + 本地构建;pi 走 npm install @latest(npm 安装只换依赖、不重建)。
|
|
606
|
+
* 状态和右下角提示条共用(update-store):启动检查的结果一到,这里直接显示「更新到 vX」或「已是最新」。 */
|
|
607
|
+
function UpdateControl({ target }: { target: UpdateTarget }) {
|
|
487
608
|
const { t } = useI18n();
|
|
488
|
-
const
|
|
489
|
-
const [latest, setLatest] = useState("");
|
|
490
|
-
const [command, setCommand] = useState("");
|
|
491
|
-
const [error, setError] = useState("");
|
|
609
|
+
const { phase, latest, command, error, busy: sessionBusy } = useUpdates().targets[target];
|
|
492
610
|
const busy = phase === "checking" || phase === "updating";
|
|
493
611
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
headers: { "Content-Type": "application/json" },
|
|
498
|
-
body: JSON.stringify({ target, action }),
|
|
499
|
-
});
|
|
500
|
-
return r.json();
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
const check = async () => {
|
|
504
|
-
setPhase("checking");
|
|
505
|
-
setError("");
|
|
506
|
-
try {
|
|
507
|
-
const j = await post("check");
|
|
508
|
-
if (!j.success) throw new Error(j.error);
|
|
509
|
-
if (j.data.canUpdate) {
|
|
510
|
-
setLatest(j.data.latest);
|
|
511
|
-
setCommand(j.data.command ?? "");
|
|
512
|
-
setPhase("available");
|
|
513
|
-
} else {
|
|
514
|
-
setPhase("latest");
|
|
515
|
-
}
|
|
516
|
-
} catch (e: any) {
|
|
517
|
-
setError(String(e?.message ?? e));
|
|
518
|
-
setPhase("error");
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
const run = async () => {
|
|
523
|
-
setPhase("updating");
|
|
524
|
-
try {
|
|
525
|
-
const j = await post("update");
|
|
526
|
-
if (!j.success) throw new Error(j.error);
|
|
527
|
-
setLatest(j.data.version ?? latest);
|
|
528
|
-
setPhase("updated");
|
|
529
|
-
} catch (e: any) {
|
|
530
|
-
setError(String(e?.message ?? e));
|
|
531
|
-
setPhase("error");
|
|
532
|
-
}
|
|
533
|
-
};
|
|
612
|
+
useEffect(() => {
|
|
613
|
+
void loadUpdates();
|
|
614
|
+
}, []);
|
|
534
615
|
|
|
535
616
|
if (phase === "idle" || phase === "latest" || phase === "error") {
|
|
536
|
-
const label = phase === "latest" ? t.upToDate : phase === "error" ? t.updateFailed : t.checkUpdate;
|
|
617
|
+
const label = phase === "latest" ? t.upToDate : phase === "error" ? (sessionBusy ? t.updateBusy : t.updateFailed) : t.checkUpdate;
|
|
537
618
|
const color = phase === "latest" ? "var(--dsw-label-tertiary)" : phase === "error" ? "var(--dsw-danger)" : "var(--dsw-label-primary)";
|
|
538
619
|
return (
|
|
539
620
|
<button
|
|
@@ -542,7 +623,7 @@ function UpdateControl({ target }: { target: "piweb" | "pi" }) {
|
|
|
542
623
|
style={{ height: 26, padding: "0 10px", fontSize: 12, color }}
|
|
543
624
|
title={error || undefined}
|
|
544
625
|
disabled={busy}
|
|
545
|
-
onClick={() => void
|
|
626
|
+
onClick={() => void checkUpdate(target)}
|
|
546
627
|
>
|
|
547
628
|
{label}
|
|
548
629
|
</button>
|
|
@@ -553,19 +634,24 @@ function UpdateControl({ target }: { target: "piweb" | "pi" }) {
|
|
|
553
634
|
if (command) {
|
|
554
635
|
return (
|
|
555
636
|
<span className="flex flex-col items-end gap-0.5" style={{ maxWidth: 300 }}>
|
|
556
|
-
<
|
|
637
|
+
<CopyCommand command={command} />
|
|
557
638
|
<span style={{ fontSize: 11, color: "var(--dsw-label-tertiary)", textAlign: "right" }}>{t.updateManualHint}</span>
|
|
558
639
|
</span>
|
|
559
640
|
);
|
|
560
641
|
}
|
|
561
642
|
return (
|
|
562
|
-
<button type="button" className="btn-primary-white" style={{ height: 26, padding: "0 12px", fontSize: 12 }} onClick={() => void
|
|
643
|
+
<button type="button" className="btn-primary-white" style={{ height: 26, padding: "0 12px", fontSize: 12 }} onClick={() => void runUpdate(target)}>
|
|
563
644
|
{t.updateTo.replace("{v}", latest)}
|
|
564
645
|
</button>
|
|
565
646
|
);
|
|
566
647
|
}
|
|
567
648
|
if (phase === "updated") {
|
|
568
|
-
return
|
|
649
|
+
return (
|
|
650
|
+
<span className="flex flex-col items-end gap-0.5" style={{ maxWidth: 280, textAlign: "right" }}>
|
|
651
|
+
<span style={{ fontSize: 12, color: "var(--dsw-success)" }}>{t.updatedTo.replace("{v}", latest)}</span>
|
|
652
|
+
<span style={{ fontSize: 11, color: "var(--dsw-label-tertiary)" }}>{t.updateRestartHint}</span>
|
|
653
|
+
</span>
|
|
654
|
+
);
|
|
569
655
|
}
|
|
570
656
|
return (
|
|
571
657
|
<span className="flex items-center gap-1.5" style={{ fontSize: 12, color: "var(--dsw-label-tertiary)" }}>
|
|
@@ -802,12 +888,163 @@ function FontFamilyPicker({ scope }: { scope: "ui" | "mono" }) {
|
|
|
802
888
|
|
|
803
889
|
// ---------- Models ----------
|
|
804
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
|
+
{gap.missing.includes("input") ? (
|
|
1003
|
+
<select
|
|
1004
|
+
className="pw-audit-field"
|
|
1005
|
+
value={rowOf(gap).vision}
|
|
1006
|
+
onChange={(event) => edit(gap, { vision: event.target.value as AuditRow["vision"] })}
|
|
1007
|
+
aria-label={`${gap.id} ${t.modelAuditColVision}`}
|
|
1008
|
+
disabled={busy}
|
|
1009
|
+
>
|
|
1010
|
+
<option value="">{t.visionUnset}</option>
|
|
1011
|
+
<option value="image">{t.visionOn}</option>
|
|
1012
|
+
<option value="text">{t.visionOff}</option>
|
|
1013
|
+
</select>
|
|
1014
|
+
) : (
|
|
1015
|
+
<span style={{ color: "var(--dsw-label-secondary)" }}>{gap.vision ? t.visionOn : t.visionOff}</span>
|
|
1016
|
+
)}
|
|
1017
|
+
</div>
|
|
1018
|
+
</div>
|
|
1019
|
+
))}
|
|
1020
|
+
</div>
|
|
1021
|
+
</div>
|
|
1022
|
+
|
|
1023
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
1024
|
+
{visionGaps.length > 0 ? (
|
|
1025
|
+
<>
|
|
1026
|
+
<span style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>{t.modelAuditVisionQuick}</span>
|
|
1027
|
+
<button className="btn-outline" style={{ height: 28, padding: "0 11px", fontSize: 12.5 }} disabled={busy} onClick={() => markVision("image")}>{t.modelAuditVisionOn}</button>
|
|
1028
|
+
<button className="btn-outline" style={{ height: 28, padding: "0 11px", fontSize: 12.5 }} disabled={busy} onClick={() => markVision("text")}>{t.modelAuditVisionOff}</button>
|
|
1029
|
+
</>
|
|
1030
|
+
) : null}
|
|
1031
|
+
<span className="flex-1" />
|
|
1032
|
+
<button className="btn-primary-white" style={{ height: 32 }} disabled={busy || invalid || fills.length === 0} onClick={() => void save()}>
|
|
1033
|
+
{busy ? t.saving : fills.length ? t.modelAuditSave.replace("{n}", String(fills.length)) : t.modelAuditSaveIdle}
|
|
1034
|
+
</button>
|
|
1035
|
+
</div>
|
|
1036
|
+
</section>
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
805
1040
|
function ModelsSection() {
|
|
806
1041
|
const { t } = useI18n();
|
|
807
1042
|
const [providers, setProviders] = useState<ProviderView[]>([]);
|
|
808
1043
|
const [custom, setCustom] = useState<{ providers: Record<string, any>; [key: string]: unknown }>({ providers: {} });
|
|
809
1044
|
const [customRevision, setCustomRevision] = useState<string | null>(null);
|
|
810
1045
|
const [secretProviderIds, setSecretProviderIds] = useState<Set<string>>(new Set());
|
|
1046
|
+
// 内置 provider → 目录里的模型 ID:编辑时同 ID 的行写 modelOverrides,「获取可用模型」里不再可选
|
|
1047
|
+
const [catalogModelIds, setCatalogModelIds] = useState<Record<string, string[]>>({});
|
|
811
1048
|
const [editing, setEditing] = useState<string | null>(null);
|
|
812
1049
|
const [keyDraft, setKeyDraft] = useState("");
|
|
813
1050
|
const [adding, setAdding] = useState<"builtin" | "custom" | null>(null);
|
|
@@ -886,6 +1123,7 @@ function ModelsSection() {
|
|
|
886
1123
|
throw new Error("invalid model configuration");
|
|
887
1124
|
}
|
|
888
1125
|
setProviders(j.data.providers);
|
|
1126
|
+
setCatalogModelIds(j.data.catalogModelIds ?? {});
|
|
889
1127
|
setSecretProviderIds(new Set(j.data.customProviders?.secretProviderIds ?? []));
|
|
890
1128
|
setCustom({ ...parsed, providers: parsed.providers ?? {} });
|
|
891
1129
|
setCustomRevision(typeof j.data.customProviders?.revision === "string" ? j.data.customProviders.revision : null);
|
|
@@ -1068,6 +1306,10 @@ function ModelsSection() {
|
|
|
1068
1306
|
}
|
|
1069
1307
|
};
|
|
1070
1308
|
|
|
1309
|
+
// 体检:直接从已载入的配置算(不另发请求),每次保存后 load() 重新载入就自动刷新
|
|
1310
|
+
const gaps = useMemo(() => customModelGaps(custom.providers ?? {}), [custom]);
|
|
1311
|
+
const [auditBusy, setAuditBusy] = useState(false);
|
|
1312
|
+
|
|
1071
1313
|
const call = async (body: Record<string, unknown>) => {
|
|
1072
1314
|
const j = await request(body);
|
|
1073
1315
|
notify(j.success, j.success && j.data?.requiresSessionReopen ? t.modelConfigReopen : j.error);
|
|
@@ -1112,6 +1354,18 @@ function ModelsSection() {
|
|
|
1112
1354
|
return call({ action: "saveCustomProviders", revision: customRevision, content: JSON.stringify({ ...custom, providers: content }, null, 2) });
|
|
1113
1355
|
};
|
|
1114
1356
|
|
|
1357
|
+
/** 体检卡片的保存:只补空字段,走与编辑 provider 同一条写入(revision 乐观锁、密钥由服务端从原文件恢复) */
|
|
1358
|
+
const saveAuditFills = async (fills: ModelFieldFill[]): Promise<boolean> => {
|
|
1359
|
+
const { providers: next, filled } = fillModelFields(custom.providers ?? {}, fills);
|
|
1360
|
+
if (!filled) return true;
|
|
1361
|
+
setAuditBusy(true);
|
|
1362
|
+
try {
|
|
1363
|
+
return Boolean((await saveCustom(next)).success);
|
|
1364
|
+
} finally {
|
|
1365
|
+
setAuditBusy(false);
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1115
1369
|
const providerName = (id: string) => providers.find((p) => p.id === id)?.name ?? id;
|
|
1116
1370
|
const credentialSource = (provider: ProviderView) => {
|
|
1117
1371
|
const source = provider.authSource;
|
|
@@ -1141,10 +1395,28 @@ function ModelsSection() {
|
|
|
1141
1395
|
setCName(typeof override?.name === "string" ? override.name : "");
|
|
1142
1396
|
setCModels((override?.models ?? []).map(modelDraftFromConfig));
|
|
1143
1397
|
} else {
|
|
1144
|
-
// 内置 provider:从 models.json
|
|
1398
|
+
// 内置 provider:从 models.json 覆盖层初始化(若有),models[] 定义和 modelOverrides 覆盖都作为行
|
|
1145
1399
|
setBBaseUrl(typeof override?.baseUrl === "string" ? override.baseUrl : "");
|
|
1146
|
-
setBModels((override
|
|
1400
|
+
setBModels(modelDraftsFromProviderConfig(override));
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
|
|
1404
|
+
const catalogIdsOf = (providerId: string): ReadonlySet<string> => new Set(catalogModelIds[providerId] ?? []);
|
|
1405
|
+
|
|
1406
|
+
/**
|
|
1407
|
+
* 内置 provider 的条目写回:什么都没覆盖时删掉条目(SDK 不认空条目,会让整份 models.json 存不进去)。
|
|
1408
|
+
* 前端拿到的配置不含 apiKey,存了密钥的条目不能当成空的删掉。
|
|
1409
|
+
* 返回 null = models.json 不用动(原本就没有条目、这次也没覆盖任何东西,比如只填了密钥)。
|
|
1410
|
+
*/
|
|
1411
|
+
const withBuiltinEntry = (providerId: string, config: Record<string, unknown>): Record<string, any> | null => {
|
|
1412
|
+
const next = { ...custom.providers };
|
|
1413
|
+
if (isInertProviderConfig(config) && !secretProviderIds.has(providerId)) {
|
|
1414
|
+
if (!Object.prototype.hasOwnProperty.call(next, providerId)) return null;
|
|
1415
|
+
delete next[providerId];
|
|
1416
|
+
} else {
|
|
1417
|
+
next[providerId] = config;
|
|
1147
1418
|
}
|
|
1419
|
+
return next;
|
|
1148
1420
|
};
|
|
1149
1421
|
|
|
1150
1422
|
const saveCustomEdit = async () => {
|
|
@@ -1178,12 +1450,12 @@ function ModelsSection() {
|
|
|
1178
1450
|
if (invalid) return;
|
|
1179
1451
|
setEditBusy(true);
|
|
1180
1452
|
try {
|
|
1181
|
-
const
|
|
1182
|
-
next[provider.id] = serializeProviderDraft(next[provider.id] ?? {}, {
|
|
1453
|
+
const config = serializeProviderDraft(custom.providers[provider.id] ?? {}, {
|
|
1183
1454
|
baseUrl: bBaseUrl,
|
|
1184
1455
|
models: bModels,
|
|
1185
|
-
}, provider.apis[0] ?? "openai-completions");
|
|
1186
|
-
const
|
|
1456
|
+
}, provider.apis[0] ?? "openai-completions", catalogIdsOf(provider.id));
|
|
1457
|
+
const next = withBuiltinEntry(provider.id, config);
|
|
1458
|
+
const j = next ? await saveCustom(next) : { success: true };
|
|
1187
1459
|
if (j.success && keyDraft.trim()) {
|
|
1188
1460
|
void call({ action: "setKey", providerId: provider.id, apiKey: keyDraft.trim() });
|
|
1189
1461
|
setOauthDone(null);
|
|
@@ -1214,8 +1486,9 @@ function ModelsSection() {
|
|
|
1214
1486
|
};
|
|
1215
1487
|
|
|
1216
1488
|
const saveBuiltinSetup = async ({ providerId, apiKey, config }: BuiltinProviderSetup) => {
|
|
1217
|
-
const next =
|
|
1218
|
-
|
|
1489
|
+
const next = withBuiltinEntry(providerId, { ...custom.providers[providerId], ...config });
|
|
1490
|
+
// 只填了密钥:models.json 不需要条目,直接去存密钥
|
|
1491
|
+
const result = next ? await saveCustom(next) : { success: true };
|
|
1219
1492
|
if (result.success && apiKey) {
|
|
1220
1493
|
// 多步认证(Vertex/Bedrock/Cloudflare 等)的后续提示经登录弹窗应答;
|
|
1221
1494
|
// 不能 await setKey —— 它会挂起到全部提示答完。
|
|
@@ -1398,7 +1671,7 @@ function ModelsSection() {
|
|
|
1398
1671
|
});
|
|
1399
1672
|
const result = await response.json();
|
|
1400
1673
|
if (!result.success) throw new Error(result.error || t.toastError);
|
|
1401
|
-
return (result.data.models ?? []).map(
|
|
1674
|
+
return (result.data.models ?? []).map(modelDraftFromCatalog);
|
|
1402
1675
|
}}
|
|
1403
1676
|
discoverDisabled={!cBaseUrl.trim()}
|
|
1404
1677
|
/>
|
|
@@ -1445,6 +1718,7 @@ function ModelsSection() {
|
|
|
1445
1718
|
setModels={setBModels}
|
|
1446
1719
|
updateModel={(index, patch) => setBModels((current) => current.map((model, at) => (at === index ? { ...model, ...patch } : model)))}
|
|
1447
1720
|
inheritedCount={p.modelCount}
|
|
1721
|
+
catalogIds={catalogIdsOf(p.id)}
|
|
1448
1722
|
discover={async (allowPrivate) => {
|
|
1449
1723
|
const response = await fetch("/api/models", {
|
|
1450
1724
|
method: "POST",
|
|
@@ -1454,7 +1728,7 @@ function ModelsSection() {
|
|
|
1454
1728
|
});
|
|
1455
1729
|
const result = await response.json();
|
|
1456
1730
|
if (!result.success) throw new Error(result.error || t.toastError);
|
|
1457
|
-
return (result.data.models ?? []).map(
|
|
1731
|
+
return (result.data.models ?? []).map(modelDraftFromCatalog);
|
|
1458
1732
|
}}
|
|
1459
1733
|
discoverDisabled={!(bBaseUrl.trim() || p.baseUrl)}
|
|
1460
1734
|
/>
|
|
@@ -1497,6 +1771,11 @@ function ModelsSection() {
|
|
|
1497
1771
|
})}
|
|
1498
1772
|
</div>
|
|
1499
1773
|
|
|
1774
|
+
{/* 模型配置体检:没写的字段 pi 会按缺省处理;由用户填,不替他猜 */}
|
|
1775
|
+
{gaps.length > 0 ? (
|
|
1776
|
+
<ModelFieldAudit gaps={gaps} providerLabel={providerName} busy={auditBusy || customRevision === null} onSave={saveAuditFills} />
|
|
1777
|
+
) : null}
|
|
1778
|
+
|
|
1500
1779
|
{/* 两个添加按钮 */}
|
|
1501
1780
|
<div className="flex gap-3">
|
|
1502
1781
|
<button
|
|
@@ -1525,6 +1804,7 @@ function ModelsSection() {
|
|
|
1525
1804
|
<ProviderSetupModal
|
|
1526
1805
|
mode={adding}
|
|
1527
1806
|
providers={addable}
|
|
1807
|
+
catalogModelIds={catalogModelIds}
|
|
1528
1808
|
existingIds={providers.map((provider) => provider.id)}
|
|
1529
1809
|
onClose={() => setAdding(null)}
|
|
1530
1810
|
onSaveBuiltin={saveBuiltinSetup}
|
|
@@ -1651,12 +1931,13 @@ function ModelsSection() {
|
|
|
1651
1931
|
|
|
1652
1932
|
</div>
|
|
1653
1933
|
);
|
|
1654
|
-
}
|
|
1934
|
+
}
|
|
1655
1935
|
|
|
1656
|
-
|
|
1936
|
+
const fieldCls = "w-full rounded-xl px-3 py-2";
|
|
1937
|
+
|
|
1938
|
+
/** 配额展示:token 数转换单位(53,000,000 → 53.00M),与统计条 / 上下文弹窗口径一致 */
|
|
1657
1939
|
function fmtQuotaTokens(value: number): string {
|
|
1658
|
-
|
|
1659
|
-
return value.toLocaleString();
|
|
1940
|
+
return fmtTokens(value);
|
|
1660
1941
|
}
|
|
1661
1942
|
|
|
1662
1943
|
/** 套餐值来自服务端声明(如 "pro"/"plus"),首字母大写后直接跟在标签后显示。 */
|
|
@@ -1925,7 +2206,8 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
|
|
|
1925
2206
|
|
|
1926
2207
|
// ---------- Plugins ----------
|
|
1927
2208
|
|
|
1928
|
-
|
|
2209
|
+
/** 导出仅供测试(其余 section 不导出) */
|
|
2210
|
+
export function PluginsSection({ cwd }: { cwd: string }) {
|
|
1929
2211
|
const { t, lang } = useI18n();
|
|
1930
2212
|
const confirmUninstall = (source: string) => window.confirm(t.confirmUninstallPackage.replace("{source}", source));
|
|
1931
2213
|
const [tab, setTab] = useState<"config" | "list">("config");
|
|
@@ -2042,13 +2324,19 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2042
2324
|
})),
|
|
2043
2325
|
].filter((x) => !query || x.name.toLowerCase().includes(query));
|
|
2044
2326
|
|
|
2045
|
-
|
|
2327
|
+
/** 扩展卡片的启用状态:每张卡只在头部显示这一处,展开区不重复 */
|
|
2328
|
+
const stateBadge = (disabled: boolean) => (
|
|
2046
2329
|
<span
|
|
2047
2330
|
className="flex flex-none items-center gap-1.5 rounded-md px-1.5 py-0.5"
|
|
2048
|
-
style={{
|
|
2331
|
+
style={{
|
|
2332
|
+
fontSize: 10.5,
|
|
2333
|
+
...(disabled
|
|
2334
|
+
? { background: "var(--dsw-selector)", color: "var(--dsw-label-tertiary)" }
|
|
2335
|
+
: { background: "rgba(34,197,94,.12)", color: "var(--dsw-success)" }),
|
|
2336
|
+
}}
|
|
2049
2337
|
>
|
|
2050
|
-
<span className="h-1.5 w-1.5 rounded-full" style={{ background: "var(--dsw-success)" }} />
|
|
2051
|
-
{t.enabled}
|
|
2338
|
+
<span className="h-1.5 w-1.5 rounded-full" style={{ background: disabled ? "var(--dsw-label-tertiary)" : "var(--dsw-success)" }} />
|
|
2339
|
+
{disabled ? t.disabled : t.enabled}
|
|
2052
2340
|
</span>
|
|
2053
2341
|
);
|
|
2054
2342
|
|
|
@@ -2165,9 +2453,7 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2165
2453
|
{e.error && (
|
|
2166
2454
|
<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>
|
|
2167
2455
|
)}
|
|
2168
|
-
{e.
|
|
2169
|
-
<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>
|
|
2170
|
-
)}
|
|
2456
|
+
{!e.error && stateBadge(e.disabled === true)}
|
|
2171
2457
|
</div>
|
|
2172
2458
|
<div className="truncate" style={{ fontSize: 12, color: e.error ? "var(--dsw-danger)" : "var(--dsw-label-caption)" }} title={e.error ?? e.path}>
|
|
2173
2459
|
{e.error ?? e.path}
|
|
@@ -2189,7 +2475,6 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2189
2475
|
>
|
|
2190
2476
|
{scopeLabel(scope)}
|
|
2191
2477
|
</span>
|
|
2192
|
-
{badge}
|
|
2193
2478
|
</div>
|
|
2194
2479
|
<div className="break-all" style={{ fontFamily: "var(--font-mono)", fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
2195
2480
|
{e.path}
|
|
@@ -2272,7 +2557,7 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2272
2557
|
<span className="h-3 w-3 flex-none animate-spin rounded-full border border-[var(--dsw-label-tertiary)] border-t-transparent" />
|
|
2273
2558
|
) : !isDisabled ? (
|
|
2274
2559
|
<span
|
|
2275
|
-
|
|
2560
|
+
className="h-2 w-2 flex-none rounded-full"
|
|
2276
2561
|
style={{ background: "#22c55e", boxShadow: "0 0 5px rgba(34, 197, 94, 0.4)" }}
|
|
2277
2562
|
/>
|
|
2278
2563
|
) : null}
|
|
@@ -2286,8 +2571,8 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2286
2571
|
>
|
|
2287
2572
|
{isDisabled ? t.disabled : t.enabled}
|
|
2288
2573
|
</span>
|
|
2289
|
-
|
|
2290
|
-
|
|
2574
|
+
<IconChevronDown14
|
|
2575
|
+
size={13}
|
|
2291
2576
|
style={{
|
|
2292
2577
|
color: "var(--dsw-label-tertiary)",
|
|
2293
2578
|
transform: isMenuOpen ? "rotate(180deg)" : "none",
|