@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
|
@@ -84,6 +84,12 @@ function roundColor(round: GrowthEntry): string {
|
|
|
84
84
|
return "var(--dsw-warn)";
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/** 这一轮有没有实际改动(轮次列表据此把没改动的轮降成次要色,改过的才显示 +/− 行数) */
|
|
88
|
+
function hasChanges(round: GrowthEntry): boolean {
|
|
89
|
+
const st = round.stats;
|
|
90
|
+
return Boolean(st.added || st.modified || st.deleted || st.renamed || st.add || st.del);
|
|
91
|
+
}
|
|
92
|
+
|
|
87
93
|
function changedLines(round: GrowthEntry): number {
|
|
88
94
|
return round.stats.add + round.stats.del;
|
|
89
95
|
}
|
|
@@ -175,7 +181,13 @@ export function ProjectPanel({
|
|
|
175
181
|
};
|
|
176
182
|
}, [cwd, gitRefreshKey, latestCommit]);
|
|
177
183
|
|
|
178
|
-
const totals = useMemo(() =>
|
|
184
|
+
const totals = useMemo(() => {
|
|
185
|
+
if (selected && growth.scope === "round") {
|
|
186
|
+
const st = selected.stats;
|
|
187
|
+
return { files: st.added + st.modified + st.deleted + st.renamed, add: st.add, del: st.del };
|
|
188
|
+
}
|
|
189
|
+
return growth.changesReady === false ? null : sumChanges(growth.changes);
|
|
190
|
+
}, [selected, growth.scope, growth.changes, growth.changesReady]);
|
|
179
191
|
const piRounds = useMemo(() => rounds.filter((round) => round.kind === "round").length, [rounds]);
|
|
180
192
|
const maxLines = useMemo(() => Math.max(1, ...rounds.map(changedLines)), [rounds]);
|
|
181
193
|
const railRef = useRef<HTMLDivElement>(null);
|
|
@@ -304,9 +316,12 @@ export function ProjectPanel({
|
|
|
304
316
|
<div className="mt-0.5 flex items-center gap-2" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
305
317
|
<span>{fmtTime(selected.ts)}</span>
|
|
306
318
|
<span>·</span>
|
|
307
|
-
<span>{t.
|
|
308
|
-
|
|
309
|
-
|
|
319
|
+
{growth.scope === "session" && <span>{t.growthScopeSession}</span>}
|
|
320
|
+
{totals ? <>
|
|
321
|
+
<span>{t.growthRoundFiles.replace("{n}", String(totals.files))}</span>
|
|
322
|
+
<span style={{ color: "var(--dsw-success)" }}>+{totals.add}</span>
|
|
323
|
+
<span style={{ color: "var(--dsw-danger)" }}>−{totals.del}</span>
|
|
324
|
+
</> : <span>—</span>}
|
|
310
325
|
{statusText && <span style={{ color: "var(--dsw-warn)" }}>{statusText}</span>}
|
|
311
326
|
<span className="flex-1" />
|
|
312
327
|
{onJumpToChat && selected.promptIds[0] && (
|
|
@@ -397,7 +412,7 @@ export function ProjectPanel({
|
|
|
397
412
|
data-kind={round.kind}
|
|
398
413
|
data-active={selected?.commit === round.commit || undefined}
|
|
399
414
|
style={{ height: h, background: roundColor(round) }}
|
|
400
|
-
title={`${roundTitle(round, tt)} · ${fmtTime(round.ts)} · ${t.growthRoundFiles.replace("{n}", String(round.
|
|
415
|
+
title={`${roundTitle(round, tt)} · ${fmtTime(round.ts)} · ${t.growthRoundFiles.replace("{n}", String(round.stats.added + round.stats.modified + round.stats.deleted + round.stats.renamed))} +${round.stats.add} −${round.stats.del}`}
|
|
401
416
|
onClick={() => growth.select(round.commit)}
|
|
402
417
|
/>
|
|
403
418
|
);
|
|
@@ -478,31 +493,41 @@ export function ProjectPanel({
|
|
|
478
493
|
)}
|
|
479
494
|
{/* 轮次列表:当前轮高亮,打开时自动滚到它 */}
|
|
480
495
|
<div ref={roundListRef} className="min-h-0 max-h-[260px] overflow-y-auto border-t pt-1" style={{ borderColor: "var(--dsw-border-l1)" }}>
|
|
481
|
-
{rounds.map((round) =>
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
496
|
+
{rounds.map((round) => {
|
|
497
|
+
const changed = hasChanges(round);
|
|
498
|
+
// 没改动的轮整行降对比度,扫视时自然被跳过;选中的那行不灰(否则像被禁用)
|
|
499
|
+
const dimmed = !changed && selected?.commit !== round.commit;
|
|
500
|
+
return (
|
|
501
|
+
<button
|
|
502
|
+
key={round.commit}
|
|
503
|
+
type="button"
|
|
504
|
+
role="option"
|
|
505
|
+
aria-selected={selected?.commit === round.commit}
|
|
506
|
+
data-selected={selected?.commit === round.commit || undefined}
|
|
507
|
+
className="flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left transition-colors"
|
|
508
|
+
style={{ background: selected?.commit === round.commit ? "var(--dsw-active)" : "transparent", ...(dimmed ? { opacity: 0.45 } : {}) }}
|
|
509
|
+
onMouseEnter={(e) => {
|
|
510
|
+
if (selected?.commit !== round.commit) e.currentTarget.style.background = "var(--dsw-hover)";
|
|
511
|
+
}}
|
|
512
|
+
onMouseLeave={(e) => {
|
|
513
|
+
if (selected?.commit !== round.commit) e.currentTarget.style.background = "transparent";
|
|
514
|
+
}}
|
|
515
|
+
title={`${roundTitle(round, tt)} · ${fmtTime(round.ts)}`}
|
|
516
|
+
onClick={() => {
|
|
517
|
+
growth.select(round.commit);
|
|
518
|
+
setRoundPickerOpen(false);
|
|
519
|
+
}}
|
|
520
|
+
>
|
|
521
|
+
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 12.5 }}>{roundTitle(round, tt)}</span>
|
|
522
|
+
{changed && (round.stats.add > 0 || round.stats.del > 0) ? (
|
|
523
|
+
<span className="flex-none" style={{ fontSize: 11, fontFamily: "var(--font-mono)" }}>
|
|
524
|
+
<span style={{ color: "var(--dsw-success)" }}>+{round.stats.add}</span> <span style={{ color: "var(--dsw-danger)" }}>−{round.stats.del}</span>
|
|
525
|
+
</span>
|
|
526
|
+
) : null}
|
|
527
|
+
<span className="flex-none" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }}>{fmtTime(round.ts)}</span>
|
|
528
|
+
</button>
|
|
529
|
+
);
|
|
530
|
+
})}
|
|
506
531
|
</div>
|
|
507
532
|
</div>
|
|
508
533
|
)}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { useCallback, useEffect, useState } from "react";
|
|
10
10
|
import { useI18n } from "@/i18n";
|
|
11
|
+
import { fmtBytes } from "@/lib/format";
|
|
11
12
|
import { IconRefreshOutline14, IconCloseOutline14 } from "@/components/icons";
|
|
12
13
|
|
|
13
14
|
export type PromptSourceKind = "system" | "append" | "agents" | "skill" | "template" | "tool" | "assembled";
|
|
@@ -45,10 +46,7 @@ export interface PromptSourcesData {
|
|
|
45
46
|
const GROUP_KINDS: PromptSourceKind[] = ["system", "append", "agents", "skill", "template", "tool", "assembled"];
|
|
46
47
|
|
|
47
48
|
function formatBytes(bytes: number | undefined): string {
|
|
48
|
-
|
|
49
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
50
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
51
|
-
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
49
|
+
return bytes ? fmtBytes(bytes) : "";
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
export function PromptPanel({
|
|
@@ -489,6 +489,12 @@
|
|
|
489
489
|
color: var(--dsw-danger);
|
|
490
490
|
padding: 4px 0;
|
|
491
491
|
}
|
|
492
|
+
.catalogHint {
|
|
493
|
+
margin-top: 8px;
|
|
494
|
+
font-size: 11.5px;
|
|
495
|
+
line-height: 1.5;
|
|
496
|
+
color: var(--dsw-label-caption);
|
|
497
|
+
}
|
|
492
498
|
|
|
493
499
|
/* ---- 候选选择对话框 ---- */
|
|
494
500
|
.candidateMask {
|
|
@@ -542,6 +548,20 @@
|
|
|
542
548
|
.candidateRow:hover {
|
|
543
549
|
background: var(--dsw-hover);
|
|
544
550
|
}
|
|
551
|
+
/* 已在内置目录里的候选:不可勾选 */
|
|
552
|
+
.candidateRow[data-disabled] {
|
|
553
|
+
cursor: default;
|
|
554
|
+
color: var(--dsw-label-tertiary);
|
|
555
|
+
}
|
|
556
|
+
.candidateRow[data-disabled]:hover {
|
|
557
|
+
background: transparent;
|
|
558
|
+
}
|
|
559
|
+
.candidateTag {
|
|
560
|
+
flex: none;
|
|
561
|
+
margin-left: auto;
|
|
562
|
+
font-size: 11px;
|
|
563
|
+
color: var(--dsw-label-caption);
|
|
564
|
+
}
|
|
545
565
|
.candidateId {
|
|
546
566
|
font-family: var(--font-mono);
|
|
547
567
|
font-size: 12.5px;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import {
|
|
5
5
|
IconCheckOutline14,
|
|
6
6
|
IconChevronDown14,
|
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
} from "@/components/icons";
|
|
12
12
|
import { ProviderBrand } from "@/components/ProviderBrand";
|
|
13
13
|
import { useI18n } from "@/i18n";
|
|
14
|
+
import { looksPrivateEndpoint } from "@/lib/model-endpoint";
|
|
14
15
|
import { CUSTOM_PROVIDER_ID_PATTERN, customApiOptions } from "@/lib/provider-display";
|
|
15
|
-
import { formatCapacity, parseCapacity, serializeModelDraft, validateModelDrafts, type ModelDraft } from "@/lib/model-draft";
|
|
16
|
+
import { formatCapacity, modelDraftFromCatalog, parseCapacity, serializeModelDraft, serializeProviderDraft, validateModelDrafts, type ModelDraft } from "@/lib/model-draft";
|
|
16
17
|
import type { ProviderView } from "@/lib/models-service";
|
|
17
18
|
import styles from "./ProviderSetupModal.module.css";
|
|
18
19
|
|
|
@@ -34,6 +35,7 @@ type SubmitResult = { success: boolean; error?: string };
|
|
|
34
35
|
export function ProviderSetupModal({
|
|
35
36
|
mode,
|
|
36
37
|
providers,
|
|
38
|
+
catalogModelIds,
|
|
37
39
|
existingIds,
|
|
38
40
|
onClose,
|
|
39
41
|
onSaveBuiltin,
|
|
@@ -42,6 +44,8 @@ export function ProviderSetupModal({
|
|
|
42
44
|
}: {
|
|
43
45
|
mode: "builtin" | "custom";
|
|
44
46
|
providers: ProviderView[];
|
|
47
|
+
/** 内置 provider → 目录里的模型 ID:同 ID 的行写 modelOverrides,「获取可用模型」里不再可选 */
|
|
48
|
+
catalogModelIds?: Readonly<Record<string, readonly string[]>>;
|
|
45
49
|
existingIds: string[];
|
|
46
50
|
onClose: () => void;
|
|
47
51
|
onSaveBuiltin: (value: BuiltinProviderSetup) => Promise<SubmitResult>;
|
|
@@ -62,10 +66,18 @@ export function ProviderSetupModal({
|
|
|
62
66
|
const allApis = customApiOptions(providers.flatMap((provider) => provider.apis));
|
|
63
67
|
const [api, setApi] = useState(allApis.includes("openai-completions") ? "openai-completions" : (allApis[0] ?? "openai-completions"));
|
|
64
68
|
const [models, setModels] = useState<ModelDraft[]>([]);
|
|
69
|
+
// 自定义 provider 的自动识别只在地址 / 密钥输入完(失焦)或换协议时触发:
|
|
70
|
+
// 还没有任何模型行,且 URL(+ 密钥,本机网关可省)都填了,这次失焦才算数
|
|
71
|
+
const autoReady = models.length === 0 && Boolean(baseUrl.trim()) && Boolean(apiKey.trim() || looksPrivateEndpoint(baseUrl));
|
|
72
|
+
const [autoTrigger, setAutoTrigger] = useState(0);
|
|
73
|
+
const requestAutoDiscover = () => {
|
|
74
|
+
if (autoReady) setAutoTrigger((count) => count + 1);
|
|
75
|
+
};
|
|
65
76
|
const [saving, setSaving] = useState(false);
|
|
66
77
|
const [error, setError] = useState("");
|
|
67
78
|
const pickerRef = useRef<HTMLDivElement>(null);
|
|
68
79
|
const selected = providers.find((provider) => provider.id === selectedId);
|
|
80
|
+
const catalogIds = useMemo(() => new Set(catalogModelIds?.[selectedId] ?? []), [catalogModelIds, selectedId]);
|
|
69
81
|
const filtered = providers.filter((provider) => {
|
|
70
82
|
const needle = search.trim().toLocaleLowerCase();
|
|
71
83
|
return !needle || provider.name.toLocaleLowerCase().includes(needle) || provider.id.toLocaleLowerCase().includes(needle);
|
|
@@ -114,12 +126,8 @@ export function ProviderSetupModal({
|
|
|
114
126
|
if (authMethod === "oauth" && onOAuthLogin) {
|
|
115
127
|
result = await onOAuthLogin(selected.id);
|
|
116
128
|
} else {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (models.length) {
|
|
120
|
-
config.api = selected.apis[0] ?? "openai-completions";
|
|
121
|
-
config.models = models.map(serializeModelDraft);
|
|
122
|
-
}
|
|
129
|
+
// 与目录同 ID 的行进 modelOverrides,新 ID 才进 models[](并带上协议)
|
|
130
|
+
const config = serializeProviderDraft({}, { baseUrl, models }, selected.apis[0] ?? "openai-completions", catalogIds);
|
|
123
131
|
result = await onSaveBuiltin({ providerId: selected.id, apiKey: apiKey.trim(), config });
|
|
124
132
|
}
|
|
125
133
|
} else {
|
|
@@ -130,7 +138,7 @@ export function ProviderSetupModal({
|
|
|
130
138
|
baseUrl: baseUrl.trim(),
|
|
131
139
|
api,
|
|
132
140
|
...(apiKey.trim() ? { apiKey: apiKey.trim() } : {}),
|
|
133
|
-
models: models.map(serializeModelDraft),
|
|
141
|
+
models: models.map((model) => serializeModelDraft(model)),
|
|
134
142
|
},
|
|
135
143
|
});
|
|
136
144
|
}
|
|
@@ -155,7 +163,7 @@ export function ProviderSetupModal({
|
|
|
155
163
|
});
|
|
156
164
|
const result = await response.json();
|
|
157
165
|
if (!response.ok || !result.success) throw new Error(result.error || t.toastError);
|
|
158
|
-
return (result.data.models ?? []).map(
|
|
166
|
+
return (result.data.models ?? []).map(modelDraftFromCatalog);
|
|
159
167
|
};
|
|
160
168
|
|
|
161
169
|
return (
|
|
@@ -255,7 +263,7 @@ export function ProviderSetupModal({
|
|
|
255
263
|
<label className={styles.label}>{t.apiAddress}</label>
|
|
256
264
|
<input className={styles.input} value={baseUrl} onChange={(event) => setBaseUrl(event.target.value)} placeholder={selected?.baseUrl || t.providerDefault} />
|
|
257
265
|
</div>
|
|
258
|
-
<ModelCatalog models={models} setModels={setModels} updateModel={updateModel} inheritedCount={selected?.modelCount ?? 0} discover={discoverAvailableModels} discoverDisabled={!baseUrl.trim()} />
|
|
266
|
+
<ModelCatalog models={models} setModels={setModels} updateModel={updateModel} inheritedCount={selected?.modelCount ?? 0} catalogIds={catalogIds} discover={discoverAvailableModels} discoverDisabled={!baseUrl.trim()} />
|
|
259
267
|
</div>
|
|
260
268
|
) : null}
|
|
261
269
|
</div>
|
|
@@ -275,17 +283,17 @@ export function ProviderSetupModal({
|
|
|
275
283
|
</div>
|
|
276
284
|
<div className={styles.field}>
|
|
277
285
|
<label className={styles.label}>{t.apiAddress}</label>
|
|
278
|
-
<input className={styles.input} value={baseUrl} onChange={(event) => setBaseUrl(event.target.value)} placeholder="https://gateway.example/v1" />
|
|
286
|
+
<input className={styles.input} value={baseUrl} onChange={(event) => setBaseUrl(event.target.value)} onBlur={requestAutoDiscover} placeholder="https://gateway.example/v1" />
|
|
279
287
|
</div>
|
|
280
288
|
<div className={styles.field}>
|
|
281
289
|
<label className={styles.label}>{t.apiProtocol}</label>
|
|
282
|
-
<select className={styles.select} value={api} onChange={(event) => setApi(event.target.value)}>
|
|
290
|
+
<select className={styles.select} value={api} onChange={(event) => { setApi(event.target.value); requestAutoDiscover(); }}>
|
|
283
291
|
{allApis.map((value) => <option key={value} value={value}>{value}</option>)}
|
|
284
292
|
</select>
|
|
285
293
|
</div>
|
|
286
294
|
<div className={styles.field}>
|
|
287
295
|
<label className={styles.label}>{t.apiKey}</label>
|
|
288
|
-
<input className={styles.input} type="password" value={apiKey} onChange={(event) => setApiKey(event.target.value)} placeholder={t.apiKeyOptional} />
|
|
296
|
+
<input className={styles.input} type="password" value={apiKey} onChange={(event) => setApiKey(event.target.value)} onBlur={requestAutoDiscover} placeholder={t.apiKeyOptional} />
|
|
289
297
|
<div className={styles.hint}>{t.customApiKeyHint}</div>
|
|
290
298
|
</div>
|
|
291
299
|
<ModelCatalog
|
|
@@ -294,6 +302,7 @@ export function ProviderSetupModal({
|
|
|
294
302
|
updateModel={updateModel}
|
|
295
303
|
discover={discoverAvailableModels}
|
|
296
304
|
discoverDisabled={!baseUrl.trim()}
|
|
305
|
+
autoDiscover={autoReady ? { signature: `${baseUrl.trim()}|${api}|${apiKey.trim()}`, privateHost: looksPrivateEndpoint(baseUrl), trigger: autoTrigger } : undefined}
|
|
297
306
|
/>
|
|
298
307
|
</>
|
|
299
308
|
)}
|
|
@@ -325,16 +334,25 @@ export function ModelCatalog({
|
|
|
325
334
|
setModels,
|
|
326
335
|
updateModel,
|
|
327
336
|
inheritedCount,
|
|
337
|
+
catalogIds,
|
|
328
338
|
discover,
|
|
329
339
|
discoverDisabled,
|
|
340
|
+
autoDiscover,
|
|
330
341
|
}: {
|
|
331
342
|
models: ModelDraft[];
|
|
332
343
|
setModels: React.Dispatch<React.SetStateAction<ModelDraft[]>>;
|
|
333
344
|
updateModel: (index: number, patch: Partial<ModelDraft>) => void;
|
|
334
345
|
inheritedCount?: number;
|
|
346
|
+
/** 内置 provider 的目录模型 ID:候选里已在目录中的不能勾选(本来就能选),同 ID 的行只覆盖填写的字段 */
|
|
347
|
+
catalogIds?: ReadonlySet<string>;
|
|
335
348
|
/** 用表单当前值探测远端目录(allowPrivate:用户勾选了允许访问本机/私网地址);抛错时错误显示在目录内 */
|
|
336
349
|
discover?: (allowPrivate: boolean) => Promise<ModelDraft[]>;
|
|
337
350
|
discoverDisabled?: boolean;
|
|
351
|
+
/**
|
|
352
|
+
* 自动识别:trigger 变了(地址 / 密钥失焦、换协议)就按当前签名(URL + 协议 + 密钥)拉一次模型目录。
|
|
353
|
+
* 只在「还没有任何模型行」时给出;同一签名只跑一次,清空模型行后不会自己又加回来。
|
|
354
|
+
*/
|
|
355
|
+
autoDiscover?: { signature: string; privateHost: boolean; trigger: number };
|
|
338
356
|
}) {
|
|
339
357
|
const { t } = useI18n();
|
|
340
358
|
const [expanded, setExpanded] = useState<ReadonlySet<number>>(new Set());
|
|
@@ -381,8 +399,64 @@ export function ModelCatalog({
|
|
|
381
399
|
});
|
|
382
400
|
};
|
|
383
401
|
|
|
402
|
+
const inCatalog = (id: string) => catalogIds?.has(id.trim()) ?? false;
|
|
403
|
+
|
|
404
|
+
// 自动识别:地址 / 密钥输入框失焦时(父组件递增 trigger)自己去拉一次目录,不用再点按钮。
|
|
405
|
+
// 不按「停手」计时:那样会拿半截地址和半截密钥去发请求。同一签名只跑一次;
|
|
406
|
+
// 表单一改(签名变了)就作废还在路上的那次,迟到的结果不会把别的地址的模型塞进来。
|
|
407
|
+
const auto = autoDiscover;
|
|
408
|
+
const autoDone = useRef("");
|
|
409
|
+
const autoSeq = useRef(0);
|
|
410
|
+
const autoPending = useRef(false);
|
|
411
|
+
// 处理过的最后一次 trigger:条目从无到有(如清空模型行)时 trigger 没变,不算一次新的失焦
|
|
412
|
+
const autoTriggerSeen = useRef(0);
|
|
413
|
+
const [autoStatus, setAutoStatus] = useState<"" | "loading" | "done" | "empty" | "failed">("");
|
|
414
|
+
const [autoFound, setAutoFound] = useState(0);
|
|
415
|
+
useEffect(() => {
|
|
416
|
+
if (!autoPending.current) return;
|
|
417
|
+
autoPending.current = false;
|
|
418
|
+
autoSeq.current += 1;
|
|
419
|
+
// 作废的那次不算跑过:改回原值再失焦还能重试
|
|
420
|
+
autoDone.current = "";
|
|
421
|
+
setAutoStatus("");
|
|
422
|
+
}, [auto?.signature]);
|
|
423
|
+
useEffect(() => {
|
|
424
|
+
if (!auto || auto.trigger === autoTriggerSeen.current) return;
|
|
425
|
+
autoTriggerSeen.current = auto.trigger;
|
|
426
|
+
if (!discover || autoDone.current === auto.signature) return;
|
|
427
|
+
autoDone.current = auto.signature;
|
|
428
|
+
autoPending.current = true;
|
|
429
|
+
const seq = ++autoSeq.current;
|
|
430
|
+
setAutoStatus("loading");
|
|
431
|
+
const settle = (status: "done" | "empty" | "failed") => {
|
|
432
|
+
if (seq !== autoSeq.current) return false;
|
|
433
|
+
autoPending.current = false;
|
|
434
|
+
setAutoStatus(status);
|
|
435
|
+
return true;
|
|
436
|
+
};
|
|
437
|
+
// 本机/私网地址自动允许(服务端仍会自己再判一次)
|
|
438
|
+
discover(allowPrivate || auto.privateHost).then(
|
|
439
|
+
(found) => {
|
|
440
|
+
if (!settle(found.length ? "done" : "empty")) return;
|
|
441
|
+
setAutoFound(found.length);
|
|
442
|
+
setModels((current) => (current.length ? current : found));
|
|
443
|
+
},
|
|
444
|
+
// 自动跑失败不弹错:按钮还在,手动点能看到具体原因
|
|
445
|
+
() => void settle("failed"),
|
|
446
|
+
);
|
|
447
|
+
// 只由失焦触发;discover 每次渲染都是新函数,放进依赖会让任何重渲染都重跑
|
|
448
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
449
|
+
}, [auto?.trigger]);
|
|
450
|
+
|
|
384
451
|
const startDiscover = async () => {
|
|
385
452
|
if (!discover || discovering) return;
|
|
453
|
+
// 手动点了就以候选对话框为准:点按钮时输入框失焦触发的那次自动识别作废
|
|
454
|
+
if (autoPending.current) {
|
|
455
|
+
autoPending.current = false;
|
|
456
|
+
autoSeq.current += 1;
|
|
457
|
+
autoDone.current = "";
|
|
458
|
+
setAutoStatus("");
|
|
459
|
+
}
|
|
386
460
|
setDiscovering(true);
|
|
387
461
|
setDiscoverError("");
|
|
388
462
|
try {
|
|
@@ -391,10 +465,12 @@ export function ModelCatalog({
|
|
|
391
465
|
setDiscoverError(t.discoverEmpty);
|
|
392
466
|
return;
|
|
393
467
|
}
|
|
394
|
-
//
|
|
468
|
+
// 已配置的行默认不勾选:采纳永远不覆盖用户改过的内容。
|
|
469
|
+
// 内置目录里已有的模型本来就能选,加成行只会变成一条空覆盖:排到后面、不可勾选
|
|
395
470
|
const known = new Set(models.map((model) => model.id.trim()).filter(Boolean));
|
|
396
|
-
|
|
397
|
-
|
|
471
|
+
const ordered = [...found.filter((model) => !inCatalog(model.id)), ...found.filter((model) => inCatalog(model.id))];
|
|
472
|
+
setCandidates(ordered);
|
|
473
|
+
setPicked(new Set(ordered.filter((model) => !known.has(model.id.trim()) && !inCatalog(model.id)).map((model) => model.id)));
|
|
398
474
|
} catch (reason) {
|
|
399
475
|
setDiscoverError(reason instanceof Error ? reason.message : t.toastError);
|
|
400
476
|
} finally {
|
|
@@ -413,8 +489,8 @@ export function ModelCatalog({
|
|
|
413
489
|
const byId = new Map(current.map((model) => [model.id.trim(), model]));
|
|
414
490
|
for (const candidate of candidates) {
|
|
415
491
|
const id = candidate.id.trim();
|
|
416
|
-
if (!id || !picked.has(id) || byId.has(id)) continue;
|
|
417
|
-
byId.set(id,
|
|
492
|
+
if (!id || !picked.has(id) || byId.has(id) || inCatalog(id)) continue;
|
|
493
|
+
byId.set(id, candidate);
|
|
418
494
|
}
|
|
419
495
|
return [...byId.values()];
|
|
420
496
|
});
|
|
@@ -422,7 +498,9 @@ export function ModelCatalog({
|
|
|
422
498
|
};
|
|
423
499
|
|
|
424
500
|
const visibleCandidates = candidates ?? [];
|
|
425
|
-
|
|
501
|
+
// 「全选」只管能勾的那些
|
|
502
|
+
const selectableCandidates = visibleCandidates.filter((model) => !inCatalog(model.id));
|
|
503
|
+
const allVisiblePicked = selectableCandidates.length > 0 && selectableCandidates.every((model) => picked.has(model.id));
|
|
426
504
|
|
|
427
505
|
const togglePick = (id: string) =>
|
|
428
506
|
setPicked((current) => {
|
|
@@ -432,6 +510,7 @@ export function ModelCatalog({
|
|
|
432
510
|
});
|
|
433
511
|
|
|
434
512
|
const firstInvalid = validateModelDrafts(models);
|
|
513
|
+
const overridesBuiltin = models.some((model) => inCatalog(model.id));
|
|
435
514
|
|
|
436
515
|
return (
|
|
437
516
|
<div className={styles.catalog}>
|
|
@@ -440,6 +519,10 @@ export function ModelCatalog({
|
|
|
440
519
|
<div className={styles.label}>{t.modelCatalog}</div>
|
|
441
520
|
<div className={styles.catalogStatus}>
|
|
442
521
|
{inheritedCount ? t.usingDefaultModels.replace("{count}", String(inheritedCount)) : t.customModelsRequired}
|
|
522
|
+
{autoStatus === "loading" ? ` · ${t.autoDiscovering}` : null}
|
|
523
|
+
{autoStatus === "done" ? ` · ${t.autoDiscovered.replace("{n}", String(autoFound))}` : null}
|
|
524
|
+
{autoStatus === "empty" ? ` · ${t.discoverEmpty}` : null}
|
|
525
|
+
{autoStatus === "failed" ? ` · ${t.autoDiscoverFailed}` : null}
|
|
443
526
|
</div>
|
|
444
527
|
</div>
|
|
445
528
|
{discover ? (
|
|
@@ -509,6 +592,22 @@ export function ModelCatalog({
|
|
|
509
592
|
placeholder="16K"
|
|
510
593
|
/>
|
|
511
594
|
</label>
|
|
595
|
+
<label className={styles.capacityField} title={t.visionHint}>
|
|
596
|
+
<span>{t.visionLabel}</span>
|
|
597
|
+
<select
|
|
598
|
+
className={styles.input}
|
|
599
|
+
value={model.vision === undefined ? "unset" : model.vision ? "image" : "text"}
|
|
600
|
+
onChange={(event) => {
|
|
601
|
+
const choice = event.target.value;
|
|
602
|
+
// 未声明 = 不写 input:pi 按纯文本处理,体检卡片会列出来提醒
|
|
603
|
+
updateModel(index, { vision: choice === "unset" ? undefined : choice === "image" });
|
|
604
|
+
}}
|
|
605
|
+
>
|
|
606
|
+
<option value="unset">{t.visionUnset}</option>
|
|
607
|
+
<option value="image">{t.visionOn}</option>
|
|
608
|
+
<option value="text">{t.visionOff}</option>
|
|
609
|
+
</select>
|
|
610
|
+
</label>
|
|
512
611
|
<span className={styles.capacityHint}>{t.capacityHint}</span>
|
|
513
612
|
</div>
|
|
514
613
|
) : null}
|
|
@@ -520,6 +619,7 @@ export function ModelCatalog({
|
|
|
520
619
|
) : (
|
|
521
620
|
<div className={styles.modelEmpty}>{inheritedCount ? t.modelCatalogEmptyBuiltin : t.modelCatalogEmptyCustom}</div>
|
|
522
621
|
)}
|
|
622
|
+
{overridesBuiltin ? <div className={styles.catalogHint}>{t.catalogOverrideHint}</div> : null}
|
|
523
623
|
<button className={styles.addModel} onClick={() => setModels((current) => [...current, { id: "", name: "" }])}>
|
|
524
624
|
<IconPlusOutline16 size={14} />
|
|
525
625
|
{t.addModel}
|
|
@@ -532,13 +632,17 @@ export function ModelCatalog({
|
|
|
532
632
|
<span>{t.candidateTitle.replace("{count}", String(candidates.length))}</span>
|
|
533
633
|
</div>
|
|
534
634
|
<div className={styles.candidateList}>
|
|
535
|
-
{visibleCandidates.map((model) =>
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
<
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
635
|
+
{visibleCandidates.map((model) => {
|
|
636
|
+
const listed = inCatalog(model.id);
|
|
637
|
+
return (
|
|
638
|
+
<label className={styles.candidateRow} data-disabled={listed || undefined} key={model.id}>
|
|
639
|
+
<input type="checkbox" checked={!listed && picked.has(model.id)} disabled={listed} onChange={() => togglePick(model.id)} />
|
|
640
|
+
<span className={styles.candidateId}>{model.id}</span>
|
|
641
|
+
{model.name ? <span className={styles.candidateName}>{model.name}</span> : null}
|
|
642
|
+
{listed ? <span className={styles.candidateTag}>{t.candidateInCatalog}</span> : null}
|
|
643
|
+
</label>
|
|
644
|
+
);
|
|
645
|
+
})}
|
|
542
646
|
{visibleCandidates.length === 0 ? <div className={styles.modelEmpty}>—</div> : null}
|
|
543
647
|
</div>
|
|
544
648
|
<div className={styles.candidateActions}>
|
|
@@ -546,11 +650,12 @@ export function ModelCatalog({
|
|
|
546
650
|
<input
|
|
547
651
|
type="checkbox"
|
|
548
652
|
checked={allVisiblePicked}
|
|
653
|
+
disabled={selectableCandidates.length === 0}
|
|
549
654
|
onChange={() =>
|
|
550
655
|
setPicked((current) => {
|
|
551
656
|
const next = new Set(current);
|
|
552
|
-
if (
|
|
553
|
-
else
|
|
657
|
+
if (selectableCandidates.every((model) => next.has(model.id))) selectableCandidates.forEach((model) => next.delete(model.id));
|
|
658
|
+
else selectableCandidates.forEach((model) => next.add(model.id));
|
|
554
659
|
return next;
|
|
555
660
|
})
|
|
556
661
|
}
|