@rexvane/piweb 0.3.21 → 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 +10 -10
- package/.next/required-server-files.js +16 -1
- package/.next/required-server-files.json +16 -1
- 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 +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/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 -1
- 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 -2
- package/.next/server/chunks/686.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/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/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/package.json +2 -2
- package/src/app/api/files/route.ts +5 -2
- package/src/app/api/github/route.ts +26 -0
- package/src/app/api/models/defaults/route.ts +18 -0
- package/src/app/api/models/route.ts +24 -1
- package/src/app/globals.css +186 -0
- package/src/components/AppShell.tsx +82 -30
- package/src/components/ChatInput.tsx +244 -63
- package/src/components/ChatWindow.tsx +18 -27
- package/src/components/ContextMeter.tsx +193 -143
- package/src/components/GitPanel.tsx +110 -40
- 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 +243 -27
- package/src/components/TrajectoryView.tsx +4 -6
- package/src/components/composer/ComposerImages.tsx +306 -0
- package/src/hooks/useGrowth.ts +3 -0
- package/src/hooks/usePiWeb.ts +12 -2
- package/src/hooks/useWorkspaceFiles.ts +93 -0
- package/src/i18n.tsx +108 -2
- package/src/lib/agent-manager.ts +135 -104
- 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 +12 -1
- package/src/lib/workspace-index.ts +270 -0
- package/.next/server/chunks/741.js +0 -165
- package/.next/static/chunks/316-5ea443ee6b512aa9.js +0 -1
- package/.next/static/chunks/3794-3679f1c05160977c.js +0 -52
- package/.next/static/chunks/4673.8a5a9e6855350e34.js +0 -1
- package/.next/static/chunks/4686-882531aef1d2e250.js +0 -1
- package/.next/static/chunks/4778.305dc6abb9889627.js +0 -1
- package/.next/static/chunks/4bd1b696-93bf506b236d6248.js +0 -1
- package/.next/static/chunks/618.ee5d5bf1c647e483.js +0 -4
- package/.next/static/chunks/839.bfd2db36483faa3e.js +0 -1
- package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/app/page-c04af72e549695bd.js +0 -1
- package/.next/static/chunks/framework-c4e651fe89edfa91.js +0 -1
- package/.next/static/chunks/main-6159b22a8cb0b7c5.js +0 -5
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +0 -1
- package/.next/static/chunks/webpack-9b4bb574e5817654.js +0 -1
- package/.next/static/css/ea27b9e30f76303f.css +0 -1
- package/.next/static/sNX5GQkOWuK5YEID1hT-C/_buildManifest.js +0 -1
- /package/.next/static/{sNX5GQkOWuK5YEID1hT-C → 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 {
|
|
@@ -35,6 +36,7 @@ import { RemoteSection } from "@/components/RemoteSection";
|
|
|
35
36
|
import { CopyCommand } from "@/components/update/CopyCommand";
|
|
36
37
|
import { checkUpdate, loadUpdates, runUpdate, useUpdates, type UpdateTarget } from "@/components/update/update-store";
|
|
37
38
|
import { useI18n } from "@/i18n";
|
|
39
|
+
import { fmtTokens } from "@/lib/format";
|
|
38
40
|
import { applyPebrelTheme, loadPebrelTheme, loadThemeMode, type PebrelTheme, type ThemeMode } from "@/lib/theme";
|
|
39
41
|
import { applyFontChoice, CUSTOM_PREFIX, FONT_PRESETS, loadFontChoice } from "@/lib/font";
|
|
40
42
|
import {
|
|
@@ -47,7 +49,22 @@ import {
|
|
|
47
49
|
type StreamSettings,
|
|
48
50
|
type StreamStyle,
|
|
49
51
|
} from "@/lib/chat/stream-settings";
|
|
50
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
customModelGaps,
|
|
54
|
+
fillModelFields,
|
|
55
|
+
formatCapacity,
|
|
56
|
+
isInertProviderConfig,
|
|
57
|
+
modelDraftFromCatalog,
|
|
58
|
+
modelDraftFromConfig,
|
|
59
|
+
modelDraftsFromProviderConfig,
|
|
60
|
+
parseCapacity,
|
|
61
|
+
PI_MODEL_FALLBACKS,
|
|
62
|
+
serializeProviderDraft,
|
|
63
|
+
validateModelDrafts,
|
|
64
|
+
type CustomModelGap,
|
|
65
|
+
type ModelDraft,
|
|
66
|
+
type ModelFieldFill,
|
|
67
|
+
} from "@/lib/model-draft";
|
|
51
68
|
import { customApiOptions } from "@/lib/provider-display";
|
|
52
69
|
import { quotaWindowKind, supportsUsageProbe, type BalanceAmount, type BalanceDetailKey, type ProviderUsage, type QuotaWindow, type XaiUsage } from "@/lib/provider-usage";
|
|
53
70
|
import type { ProviderView } from "@/lib/models-service";
|
|
@@ -871,12 +888,163 @@ function FontFamilyPicker({ scope }: { scope: "ui" | "mono" }) {
|
|
|
871
888
|
|
|
872
889
|
// ---------- Models ----------
|
|
873
890
|
|
|
891
|
+
type AuditRow = { contextWindow: string; maxTokens: string; vision: "" | "image" | "text" };
|
|
892
|
+
const EMPTY_AUDIT_ROW: AuditRow = { contextWindow: "", maxTokens: "", vision: "" };
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* 模型配置体检:models[] 里有字段没写的模型,pi 会按 PI_MODEL_FALLBACKS / 纯文本处理。
|
|
896
|
+
* 不替用户猜——空着的格子由用户填(支持 256K / 1M 写法),保存时只补空字段,已写的值不改。
|
|
897
|
+
*/
|
|
898
|
+
function ModelFieldAudit({ gaps, providerLabel, busy, onSave }: {
|
|
899
|
+
gaps: CustomModelGap[];
|
|
900
|
+
providerLabel: (id: string) => string;
|
|
901
|
+
busy: boolean;
|
|
902
|
+
/** 保存成功返回 true(之后清空填写) */
|
|
903
|
+
onSave: (fills: ModelFieldFill[]) => Promise<boolean>;
|
|
904
|
+
}) {
|
|
905
|
+
const { t } = useI18n();
|
|
906
|
+
const [rows, setRows] = useState<Record<string, AuditRow>>({});
|
|
907
|
+
const keyOf = (gap: CustomModelGap) => `${gap.provider}\u0000${gap.index}`;
|
|
908
|
+
const rowOf = (gap: CustomModelGap) => rows[keyOf(gap)] ?? EMPTY_AUDIT_ROW;
|
|
909
|
+
const edit = (gap: CustomModelGap, patch: Partial<AuditRow>) =>
|
|
910
|
+
setRows((current) => ({ ...current, [keyOf(gap)]: { ...(current[keyOf(gap)] ?? EMPTY_AUDIT_ROW), ...patch } }));
|
|
911
|
+
const visionGaps = gaps.filter((gap) => gap.missing.includes("input"));
|
|
912
|
+
const markVision = (vision: "image" | "text") =>
|
|
913
|
+
setRows((current) => {
|
|
914
|
+
const next = { ...current };
|
|
915
|
+
for (const gap of visionGaps) next[keyOf(gap)] = { ...(next[keyOf(gap)] ?? EMPTY_AUDIT_ROW), vision };
|
|
916
|
+
return next;
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// 每行填的文本 → 要写的值;有一格格式不对就不让保存(那一格标红)
|
|
920
|
+
let invalid = false;
|
|
921
|
+
const fills: ModelFieldFill[] = [];
|
|
922
|
+
for (const gap of gaps) {
|
|
923
|
+
const row = rows[keyOf(gap)];
|
|
924
|
+
if (!row) continue;
|
|
925
|
+
const contextWindow = gap.missing.includes("contextWindow") ? parseCapacity(row.contextWindow) : undefined;
|
|
926
|
+
const maxTokens = gap.missing.includes("maxTokens") ? parseCapacity(row.maxTokens) : undefined;
|
|
927
|
+
if (Number.isNaN(contextWindow) || Number.isNaN(maxTokens)) {
|
|
928
|
+
invalid = true;
|
|
929
|
+
continue;
|
|
930
|
+
}
|
|
931
|
+
const vision = gap.missing.includes("input") && row.vision ? row.vision === "image" : undefined;
|
|
932
|
+
if (contextWindow === undefined && maxTokens === undefined && vision === undefined) continue;
|
|
933
|
+
fills.push({ provider: gap.provider, index: gap.index, id: gap.id, contextWindow, maxTokens, vision });
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
const save = async () => {
|
|
937
|
+
if (await onSave(fills)) setRows({});
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
const capacityCell = (gap: CustomModelGap, field: "contextWindow" | "maxTokens", label: string) => {
|
|
941
|
+
if (!gap.missing.includes(field)) {
|
|
942
|
+
return (
|
|
943
|
+
<span className="truncate" style={{ fontFamily: "var(--font-mono)", color: "var(--dsw-label-secondary)" }}>
|
|
944
|
+
<span className="sm:hidden" style={{ color: "var(--dsw-label-caption)" }}>{label} </span>
|
|
945
|
+
{formatCapacity(gap[field]) || "—"}
|
|
946
|
+
</span>
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
const text = rowOf(gap)[field];
|
|
950
|
+
return (
|
|
951
|
+
<input
|
|
952
|
+
className="pw-audit-field"
|
|
953
|
+
style={{ fontFamily: "var(--font-mono)" }}
|
|
954
|
+
value={text}
|
|
955
|
+
onChange={(event) => edit(gap, { [field]: event.target.value })}
|
|
956
|
+
placeholder={label}
|
|
957
|
+
aria-label={`${gap.id} ${label}`}
|
|
958
|
+
aria-invalid={Number.isNaN(parseCapacity(text)) || undefined}
|
|
959
|
+
disabled={busy}
|
|
960
|
+
/>
|
|
961
|
+
);
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
return (
|
|
965
|
+
<section className="flex flex-col gap-3 rounded-2xl p-4" style={{ border: "0.5px solid var(--dsw-border-l3)", background: "var(--dsw-hover)" }} data-testid="model-audit">
|
|
966
|
+
<div className="flex items-start gap-2.5">
|
|
967
|
+
<IconWarningOutline16 size={16} style={{ flex: "none", marginTop: 1, color: "var(--dsw-warn)" }} />
|
|
968
|
+
<div className="min-w-0 flex-1">
|
|
969
|
+
<div style={{ fontSize: 13.5, fontWeight: 600 }}>{t.modelAuditTitle.replace("{n}", String(gaps.length))}</div>
|
|
970
|
+
<div className="mt-1" style={{ fontSize: 12, color: "var(--dsw-label-caption)", lineHeight: 1.65 }}>
|
|
971
|
+
{t.modelAuditDesc
|
|
972
|
+
.replace("{ctx}", formatCapacity(PI_MODEL_FALLBACKS.contextWindow))
|
|
973
|
+
.replace("{out}", formatCapacity(PI_MODEL_FALLBACKS.maxTokens))}
|
|
974
|
+
</div>
|
|
975
|
+
</div>
|
|
976
|
+
</div>
|
|
977
|
+
|
|
978
|
+
<div style={{ fontSize: 12.5 }} role="table" aria-label={t.modelAuditTitle.replace("{n}", String(gaps.length))}>
|
|
979
|
+
<div role="row" className="hidden gap-x-2 px-1 pb-1.5 sm:grid sm:grid-cols-[minmax(0,1fr)_92px_92px_108px]" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
980
|
+
<span role="columnheader">{t.modelAuditColModel}</span>
|
|
981
|
+
<span role="columnheader">{t.modelAuditColContext}</span>
|
|
982
|
+
<span role="columnheader">{t.modelAuditColOutput}</span>
|
|
983
|
+
<span role="columnheader">{t.modelAuditColVision}</span>
|
|
984
|
+
</div>
|
|
985
|
+
<div className="max-h-[320px] overflow-y-auto" style={{ borderTop: "0.5px solid var(--dsw-border-l2)" }}>
|
|
986
|
+
{gaps.map((gap) => (
|
|
987
|
+
<div
|
|
988
|
+
key={keyOf(gap)}
|
|
989
|
+
role="row"
|
|
990
|
+
className="grid grid-cols-3 items-center gap-x-2 gap-y-1.5 px-1 py-2 sm:grid-cols-[minmax(0,1fr)_92px_92px_108px]"
|
|
991
|
+
style={{ borderBottom: "0.5px solid var(--dsw-border-l1)" }}
|
|
992
|
+
>
|
|
993
|
+
<div role="cell" className="col-span-3 min-w-0 sm:col-span-1" title={`${gap.provider} / ${gap.id}`}>
|
|
994
|
+
<div className="truncate" style={{ fontWeight: 500 }}>{gap.name ?? gap.id}</div>
|
|
995
|
+
<div className="truncate" style={{ fontSize: 11.5, fontFamily: "var(--font-mono)", color: "var(--dsw-label-caption)" }}>
|
|
996
|
+
{providerLabel(gap.provider)} / {gap.id}
|
|
997
|
+
</div>
|
|
998
|
+
</div>
|
|
999
|
+
<div role="cell" className="min-w-0">{capacityCell(gap, "contextWindow", t.modelAuditColContext)}</div>
|
|
1000
|
+
<div role="cell" className="min-w-0">{capacityCell(gap, "maxTokens", t.modelAuditColOutput)}</div>
|
|
1001
|
+
<div role="cell" className="min-w-0">
|
|
1002
|
+
{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
|
+
|
|
874
1040
|
function ModelsSection() {
|
|
875
1041
|
const { t } = useI18n();
|
|
876
1042
|
const [providers, setProviders] = useState<ProviderView[]>([]);
|
|
877
1043
|
const [custom, setCustom] = useState<{ providers: Record<string, any>; [key: string]: unknown }>({ providers: {} });
|
|
878
1044
|
const [customRevision, setCustomRevision] = useState<string | null>(null);
|
|
879
1045
|
const [secretProviderIds, setSecretProviderIds] = useState<Set<string>>(new Set());
|
|
1046
|
+
// 内置 provider → 目录里的模型 ID:编辑时同 ID 的行写 modelOverrides,「获取可用模型」里不再可选
|
|
1047
|
+
const [catalogModelIds, setCatalogModelIds] = useState<Record<string, string[]>>({});
|
|
880
1048
|
const [editing, setEditing] = useState<string | null>(null);
|
|
881
1049
|
const [keyDraft, setKeyDraft] = useState("");
|
|
882
1050
|
const [adding, setAdding] = useState<"builtin" | "custom" | null>(null);
|
|
@@ -955,6 +1123,7 @@ function ModelsSection() {
|
|
|
955
1123
|
throw new Error("invalid model configuration");
|
|
956
1124
|
}
|
|
957
1125
|
setProviders(j.data.providers);
|
|
1126
|
+
setCatalogModelIds(j.data.catalogModelIds ?? {});
|
|
958
1127
|
setSecretProviderIds(new Set(j.data.customProviders?.secretProviderIds ?? []));
|
|
959
1128
|
setCustom({ ...parsed, providers: parsed.providers ?? {} });
|
|
960
1129
|
setCustomRevision(typeof j.data.customProviders?.revision === "string" ? j.data.customProviders.revision : null);
|
|
@@ -1137,6 +1306,10 @@ function ModelsSection() {
|
|
|
1137
1306
|
}
|
|
1138
1307
|
};
|
|
1139
1308
|
|
|
1309
|
+
// 体检:直接从已载入的配置算(不另发请求),每次保存后 load() 重新载入就自动刷新
|
|
1310
|
+
const gaps = useMemo(() => customModelGaps(custom.providers ?? {}), [custom]);
|
|
1311
|
+
const [auditBusy, setAuditBusy] = useState(false);
|
|
1312
|
+
|
|
1140
1313
|
const call = async (body: Record<string, unknown>) => {
|
|
1141
1314
|
const j = await request(body);
|
|
1142
1315
|
notify(j.success, j.success && j.data?.requiresSessionReopen ? t.modelConfigReopen : j.error);
|
|
@@ -1181,6 +1354,18 @@ function ModelsSection() {
|
|
|
1181
1354
|
return call({ action: "saveCustomProviders", revision: customRevision, content: JSON.stringify({ ...custom, providers: content }, null, 2) });
|
|
1182
1355
|
};
|
|
1183
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
|
+
|
|
1184
1369
|
const providerName = (id: string) => providers.find((p) => p.id === id)?.name ?? id;
|
|
1185
1370
|
const credentialSource = (provider: ProviderView) => {
|
|
1186
1371
|
const source = provider.authSource;
|
|
@@ -1210,10 +1395,28 @@ function ModelsSection() {
|
|
|
1210
1395
|
setCName(typeof override?.name === "string" ? override.name : "");
|
|
1211
1396
|
setCModels((override?.models ?? []).map(modelDraftFromConfig));
|
|
1212
1397
|
} else {
|
|
1213
|
-
// 内置 provider:从 models.json
|
|
1398
|
+
// 内置 provider:从 models.json 覆盖层初始化(若有),models[] 定义和 modelOverrides 覆盖都作为行
|
|
1214
1399
|
setBBaseUrl(typeof override?.baseUrl === "string" ? override.baseUrl : "");
|
|
1215
|
-
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;
|
|
1216
1418
|
}
|
|
1419
|
+
return next;
|
|
1217
1420
|
};
|
|
1218
1421
|
|
|
1219
1422
|
const saveCustomEdit = async () => {
|
|
@@ -1247,12 +1450,12 @@ function ModelsSection() {
|
|
|
1247
1450
|
if (invalid) return;
|
|
1248
1451
|
setEditBusy(true);
|
|
1249
1452
|
try {
|
|
1250
|
-
const
|
|
1251
|
-
next[provider.id] = serializeProviderDraft(next[provider.id] ?? {}, {
|
|
1453
|
+
const config = serializeProviderDraft(custom.providers[provider.id] ?? {}, {
|
|
1252
1454
|
baseUrl: bBaseUrl,
|
|
1253
1455
|
models: bModels,
|
|
1254
|
-
}, provider.apis[0] ?? "openai-completions");
|
|
1255
|
-
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 };
|
|
1256
1459
|
if (j.success && keyDraft.trim()) {
|
|
1257
1460
|
void call({ action: "setKey", providerId: provider.id, apiKey: keyDraft.trim() });
|
|
1258
1461
|
setOauthDone(null);
|
|
@@ -1283,8 +1486,9 @@ function ModelsSection() {
|
|
|
1283
1486
|
};
|
|
1284
1487
|
|
|
1285
1488
|
const saveBuiltinSetup = async ({ providerId, apiKey, config }: BuiltinProviderSetup) => {
|
|
1286
|
-
const next =
|
|
1287
|
-
|
|
1489
|
+
const next = withBuiltinEntry(providerId, { ...custom.providers[providerId], ...config });
|
|
1490
|
+
// 只填了密钥:models.json 不需要条目,直接去存密钥
|
|
1491
|
+
const result = next ? await saveCustom(next) : { success: true };
|
|
1288
1492
|
if (result.success && apiKey) {
|
|
1289
1493
|
// 多步认证(Vertex/Bedrock/Cloudflare 等)的后续提示经登录弹窗应答;
|
|
1290
1494
|
// 不能 await setKey —— 它会挂起到全部提示答完。
|
|
@@ -1467,7 +1671,7 @@ function ModelsSection() {
|
|
|
1467
1671
|
});
|
|
1468
1672
|
const result = await response.json();
|
|
1469
1673
|
if (!result.success) throw new Error(result.error || t.toastError);
|
|
1470
|
-
return (result.data.models ?? []).map(
|
|
1674
|
+
return (result.data.models ?? []).map(modelDraftFromCatalog);
|
|
1471
1675
|
}}
|
|
1472
1676
|
discoverDisabled={!cBaseUrl.trim()}
|
|
1473
1677
|
/>
|
|
@@ -1514,6 +1718,7 @@ function ModelsSection() {
|
|
|
1514
1718
|
setModels={setBModels}
|
|
1515
1719
|
updateModel={(index, patch) => setBModels((current) => current.map((model, at) => (at === index ? { ...model, ...patch } : model)))}
|
|
1516
1720
|
inheritedCount={p.modelCount}
|
|
1721
|
+
catalogIds={catalogIdsOf(p.id)}
|
|
1517
1722
|
discover={async (allowPrivate) => {
|
|
1518
1723
|
const response = await fetch("/api/models", {
|
|
1519
1724
|
method: "POST",
|
|
@@ -1523,7 +1728,7 @@ function ModelsSection() {
|
|
|
1523
1728
|
});
|
|
1524
1729
|
const result = await response.json();
|
|
1525
1730
|
if (!result.success) throw new Error(result.error || t.toastError);
|
|
1526
|
-
return (result.data.models ?? []).map(
|
|
1731
|
+
return (result.data.models ?? []).map(modelDraftFromCatalog);
|
|
1527
1732
|
}}
|
|
1528
1733
|
discoverDisabled={!(bBaseUrl.trim() || p.baseUrl)}
|
|
1529
1734
|
/>
|
|
@@ -1566,6 +1771,11 @@ function ModelsSection() {
|
|
|
1566
1771
|
})}
|
|
1567
1772
|
</div>
|
|
1568
1773
|
|
|
1774
|
+
{/* 模型配置体检:没写的字段 pi 会按缺省处理;由用户填,不替他猜 */}
|
|
1775
|
+
{gaps.length > 0 ? (
|
|
1776
|
+
<ModelFieldAudit gaps={gaps} providerLabel={providerName} busy={auditBusy || customRevision === null} onSave={saveAuditFills} />
|
|
1777
|
+
) : null}
|
|
1778
|
+
|
|
1569
1779
|
{/* 两个添加按钮 */}
|
|
1570
1780
|
<div className="flex gap-3">
|
|
1571
1781
|
<button
|
|
@@ -1594,6 +1804,7 @@ function ModelsSection() {
|
|
|
1594
1804
|
<ProviderSetupModal
|
|
1595
1805
|
mode={adding}
|
|
1596
1806
|
providers={addable}
|
|
1807
|
+
catalogModelIds={catalogModelIds}
|
|
1597
1808
|
existingIds={providers.map((provider) => provider.id)}
|
|
1598
1809
|
onClose={() => setAdding(null)}
|
|
1599
1810
|
onSaveBuiltin={saveBuiltinSetup}
|
|
@@ -1720,12 +1931,13 @@ function ModelsSection() {
|
|
|
1720
1931
|
|
|
1721
1932
|
</div>
|
|
1722
1933
|
);
|
|
1723
|
-
}
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
const fieldCls = "w-full rounded-xl px-3 py-2";
|
|
1724
1937
|
|
|
1725
|
-
/** 配额展示:token
|
|
1938
|
+
/** 配额展示:token 数转换单位(53,000,000 → 53.00M),与统计条 / 上下文弹窗口径一致 */
|
|
1726
1939
|
function fmtQuotaTokens(value: number): string {
|
|
1727
|
-
|
|
1728
|
-
return value.toLocaleString();
|
|
1940
|
+
return fmtTokens(value);
|
|
1729
1941
|
}
|
|
1730
1942
|
|
|
1731
1943
|
/** 套餐值来自服务端声明(如 "pro"/"plus"),首字母大写后直接跟在标签后显示。 */
|
|
@@ -1994,7 +2206,8 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
|
|
|
1994
2206
|
|
|
1995
2207
|
// ---------- Plugins ----------
|
|
1996
2208
|
|
|
1997
|
-
|
|
2209
|
+
/** 导出仅供测试(其余 section 不导出) */
|
|
2210
|
+
export function PluginsSection({ cwd }: { cwd: string }) {
|
|
1998
2211
|
const { t, lang } = useI18n();
|
|
1999
2212
|
const confirmUninstall = (source: string) => window.confirm(t.confirmUninstallPackage.replace("{source}", source));
|
|
2000
2213
|
const [tab, setTab] = useState<"config" | "list">("config");
|
|
@@ -2111,13 +2324,19 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2111
2324
|
})),
|
|
2112
2325
|
].filter((x) => !query || x.name.toLowerCase().includes(query));
|
|
2113
2326
|
|
|
2114
|
-
|
|
2327
|
+
/** 扩展卡片的启用状态:每张卡只在头部显示这一处,展开区不重复 */
|
|
2328
|
+
const stateBadge = (disabled: boolean) => (
|
|
2115
2329
|
<span
|
|
2116
2330
|
className="flex flex-none items-center gap-1.5 rounded-md px-1.5 py-0.5"
|
|
2117
|
-
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
|
+
}}
|
|
2118
2337
|
>
|
|
2119
|
-
<span className="h-1.5 w-1.5 rounded-full" style={{ background: "var(--dsw-success)" }} />
|
|
2120
|
-
{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}
|
|
2121
2340
|
</span>
|
|
2122
2341
|
);
|
|
2123
2342
|
|
|
@@ -2234,9 +2453,7 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2234
2453
|
{e.error && (
|
|
2235
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>
|
|
2236
2455
|
)}
|
|
2237
|
-
{e.
|
|
2238
|
-
<span className="rounded px-1.5 py-0.5" style={{ fontSize: 10.5, color: "var(--dsw-label-tertiary)", background: "var(--dsw-selector)" }}>{t.disabled}</span>
|
|
2239
|
-
)}
|
|
2456
|
+
{!e.error && stateBadge(e.disabled === true)}
|
|
2240
2457
|
</div>
|
|
2241
2458
|
<div className="truncate" style={{ fontSize: 12, color: e.error ? "var(--dsw-danger)" : "var(--dsw-label-caption)" }} title={e.error ?? e.path}>
|
|
2242
2459
|
{e.error ?? e.path}
|
|
@@ -2258,7 +2475,6 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2258
2475
|
>
|
|
2259
2476
|
{scopeLabel(scope)}
|
|
2260
2477
|
</span>
|
|
2261
|
-
{badge}
|
|
2262
2478
|
</div>
|
|
2263
2479
|
<div className="break-all" style={{ fontFamily: "var(--font-mono)", fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
2264
2480
|
{e.path}
|
|
@@ -2341,7 +2557,7 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2341
2557
|
<span className="h-3 w-3 flex-none animate-spin rounded-full border border-[var(--dsw-label-tertiary)] border-t-transparent" />
|
|
2342
2558
|
) : !isDisabled ? (
|
|
2343
2559
|
<span
|
|
2344
|
-
|
|
2560
|
+
className="h-2 w-2 flex-none rounded-full"
|
|
2345
2561
|
style={{ background: "#22c55e", boxShadow: "0 0 5px rgba(34, 197, 94, 0.4)" }}
|
|
2346
2562
|
/>
|
|
2347
2563
|
) : null}
|
|
@@ -2355,8 +2571,8 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2355
2571
|
>
|
|
2356
2572
|
{isDisabled ? t.disabled : t.enabled}
|
|
2357
2573
|
</span>
|
|
2358
|
-
|
|
2359
|
-
|
|
2574
|
+
<IconChevronDown14
|
|
2575
|
+
size={13}
|
|
2360
2576
|
style={{
|
|
2361
2577
|
color: "var(--dsw-label-tertiary)",
|
|
2362
2578
|
transform: isMenuOpen ? "rotate(180deg)" : "none",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useMemo, useState } from "react";
|
|
4
4
|
import { IconCloseOutline14, IconSearchOutline16 } from "@/components/icons";
|
|
5
5
|
import { useI18n } from "@/i18n";
|
|
6
|
+
import { fmtTokens } from "@/lib/format";
|
|
6
7
|
import type { TrajEntry, TrajTokens } from "@/lib/types";
|
|
7
8
|
import styles from "./TrajectoryAnalyzer.module.css";
|
|
8
9
|
|
|
@@ -26,10 +27,7 @@ const KIND_NAME: Record<TrajEntry["kind"], string> = {
|
|
|
26
27
|
};
|
|
27
28
|
|
|
28
29
|
function fmtTok(value?: number): string {
|
|
29
|
-
|
|
30
|
-
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
|
|
31
|
-
if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`;
|
|
32
|
-
return String(value);
|
|
30
|
+
return value == null ? "—" : fmtTokens(value);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
function fmtTime(ts: number): string {
|
|
@@ -45,8 +43,8 @@ function durationOf(entry: TrajEntry): number | undefined {
|
|
|
45
43
|
|
|
46
44
|
function fmtDur(ms?: number): string {
|
|
47
45
|
if (ms == null) return "—";
|
|
48
|
-
if (ms >= 60_000) return `${(ms / 60_000).toFixed(
|
|
49
|
-
if (ms >= 1_000) return `${(ms / 1_000).toFixed(
|
|
46
|
+
if (ms >= 60_000) return `${(ms / 60_000).toFixed(2)}m`;
|
|
47
|
+
if (ms >= 1_000) return `${(ms / 1_000).toFixed(2)}s`;
|
|
50
48
|
return `${Math.round(ms)}ms`;
|
|
51
49
|
}
|
|
52
50
|
|