@rexvane/piweb 0.3.15 → 0.3.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +9 -6
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +6 -6
- package/.next/react-loadable-manifest.json +16 -4
- package/.next/routes-manifest.json +20 -0
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +7 -4
- package/.next/server/app/_not-found.segments/_full.segment.rsc +7 -4
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +5 -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/git/route.js +2 -2
- package/.next/server/app/api/git/route.js.nft.json +1 -1
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +1 -1
- package/.next/server/app/api/health/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/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/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 +3 -3
- 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/swarm/[id]/route.js +1 -0
- package/.next/server/app/api/swarm/[id]/route.js.nft.json +1 -0
- package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/swarm/route.js +1 -0
- package/.next/server/app/api/swarm/route.js.nft.json +1 -0
- package/.next/server/app/api/swarm/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/tests/route.js +2 -0
- package/.next/server/app/api/tests/route.js.nft.json +1 -0
- package/.next/server/app/api/tests/route_client-reference-manifest.js +1 -0
- 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/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/index.html +1 -1
- package/.next/server/app/index.rsc +9 -6
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +7 -4
- package/.next/server/app/index.segments/_full.segment.rsc +9 -6
- package/.next/server/app/index.segments/_tree.segment.rsc +5 -2
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +8 -5
- package/.next/server/app/login.segments/_full.segment.rsc +8 -5
- package/.next/server/app/login.segments/_tree.segment.rsc +5 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +6 -3
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +9 -6
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/532.js +1 -1
- package/.next/server/chunks/686.js +6 -2
- package/.next/server/chunks/757.js +1 -0
- package/.next/server/chunks/775.js +3 -0
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +1 -0
- package/.next/static/chunks/2608.43c58236c9cce8e6.js +1 -0
- package/.next/static/chunks/{2716.a9e808190a4e2424.js → 2716.4ec42df1d51354c9.js} +1 -1
- package/.next/static/chunks/4632.62139b565b657aac.js +1 -0
- package/.next/static/chunks/{4673.d3d0a7f2599080fb.js → 4673.7796104395deb8d7.js} +1 -1
- package/.next/static/chunks/4686-17f21a006739a596.js +1 -0
- package/.next/static/chunks/7577.10af9bb661652da8.js +1 -0
- package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +1 -0
- package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/page-14379b8060d5cf07.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +1 -0
- package/.next/static/css/d0a0162cb481abe8.css +1 -0
- package/.next/types/app/api/swarm/[id]/route.ts +353 -0
- package/.next/types/app/api/swarm/route.ts +353 -0
- package/.next/types/app/api/tests/route.ts +353 -0
- package/.next/types/routes.d.ts +4 -1
- package/.next/types/validator.ts +27 -0
- package/README.md +10 -0
- package/package.json +4 -3
- package/scripts/pick-folder.ps1 +11 -2
- package/scripts/release.mjs +11 -31
- package/scripts/run-test-file.mjs +34 -0
- package/scripts/verify-release-config.mjs +24 -0
- package/src/app/api/config/route.ts +4 -1
- package/src/app/api/security/route.ts +8 -3
- package/src/app/api/swarm/[id]/route.ts +28 -0
- package/src/app/api/swarm/route.ts +24 -0
- package/src/app/api/tests/route.ts +27 -0
- package/src/app/api/update/route.ts +8 -2
- package/src/app/globals.css +777 -0
- package/src/app/layout.tsx +13 -6
- package/src/components/AppShell.tsx +505 -199
- package/src/components/ChatInput.tsx +153 -26
- package/src/components/ChatWindow.tsx +46 -40
- package/src/components/ContextMeter.tsx +11 -17
- package/src/components/FileViewer.tsx +1 -1
- package/src/components/GitPanel.tsx +13 -11
- package/src/components/ModelSelector.tsx +19 -6
- package/src/components/PiMark.tsx +5 -38
- package/src/components/ProjectPanel.tsx +1 -0
- package/src/components/PromptPanel.tsx +132 -51
- package/src/components/SessionImportSection.tsx +17 -17
- package/src/components/SessionSidebar.tsx +159 -57
- package/src/components/SettingsPanel.tsx +330 -193
- package/src/components/SwarmCoordinatorPod.tsx +1021 -0
- package/src/components/TestInspectorPod.tsx +549 -0
- package/src/components/icons.tsx +23 -0
- package/src/hooks/usePiWeb.ts +112 -8
- package/src/i18n.tsx +59 -3
- package/src/lib/agent-manager.ts +180 -12
- package/src/lib/command-validation.ts +9 -1
- package/src/lib/delegation.ts +93 -0
- package/src/lib/managed-niubash.ts +8 -4
- package/src/lib/pi.ts +3 -2
- package/src/lib/runtime-activity.ts +27 -0
- package/src/lib/session-import/index.ts +5 -0
- package/src/lib/session-import/lock.ts +25 -0
- package/src/lib/session-import/writer.ts +8 -3
- package/src/lib/skills-service.ts +16 -9
- package/src/lib/swarm-service.ts +371 -0
- package/src/lib/test-run-service.ts +122 -0
- package/src/lib/theme.ts +68 -1
- package/src/lib/trajectory.ts +1 -0
- package/src/lib/types.ts +14 -0
- package/src/lib/workflow.ts +55 -0
- package/.next/static/DdBwPYoHBKtaJ97QIr6Co/_buildManifest.js +0 -1
- package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
- package/.next/static/chunks/4632.0ed92ae42905ce40.js +0 -1
- package/.next/static/chunks/4686-7d694def920ff093.js +0 -1
- package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/page-9a5440047c5a415c.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/webpack-42c5aae79b0e5d8c.js +0 -1
- package/.next/static/css/b2d35abc828d4713.css +0 -1
- /package/.next/static/{DdBwPYoHBKtaJ97QIr6Co → 0t1jBUAuVaTFTXxRyIo3q}/_ssgManifest.js +0 -0
|
@@ -95,57 +95,80 @@ export function SettingsPanel({
|
|
|
95
95
|
}}
|
|
96
96
|
>
|
|
97
97
|
<div
|
|
98
|
-
className="settings-dialog
|
|
98
|
+
className="settings-dialog clay-card relative flex flex-col gap-4 overflow-hidden bg-white/95 dark:bg-slate-900/95 p-6 shadow-2xl backdrop-blur-md"
|
|
99
99
|
style={{
|
|
100
|
-
width:
|
|
101
|
-
maxWidth: "calc(100vw -
|
|
102
|
-
height: "min(
|
|
103
|
-
borderRadius:
|
|
104
|
-
boxShadow: "var(--dsw-elevation-prominent)",
|
|
100
|
+
width: 960,
|
|
101
|
+
maxWidth: "calc(100vw - 32px)",
|
|
102
|
+
height: "min(840px, calc(100vh - 48px))",
|
|
103
|
+
borderRadius: 36,
|
|
105
104
|
}}
|
|
106
105
|
>
|
|
107
|
-
{/*
|
|
108
|
-
<div className="
|
|
109
|
-
<div className="
|
|
110
|
-
|
|
106
|
+
{/* Top Header Bar (Screen 4-7) */}
|
|
107
|
+
<div className="flex items-center justify-between pb-3 border-b border-slate-200/60 dark:border-slate-800/60 shrink-0">
|
|
108
|
+
<div className="flex items-center gap-3">
|
|
109
|
+
<div className="w-10 h-10 rounded-2xl clay-inset flex items-center justify-center text-emerald-600 dark:text-emerald-400 flex-none">
|
|
110
|
+
<IconSettingsOutline16 size={20} />
|
|
111
|
+
</div>
|
|
112
|
+
<div className="flex flex-col">
|
|
113
|
+
<div className="flex items-center gap-2">
|
|
114
|
+
<h1 className="text-base sm:text-lg font-extrabold text-slate-800 dark:text-slate-100 tracking-tight">全局设置中心</h1>
|
|
115
|
+
<span className="px-2.5 py-0.5 rounded-full clay-inset text-emerald-600 dark:text-emerald-400 font-mono text-[11px] font-semibold">config.json</span>
|
|
116
|
+
</div>
|
|
117
|
+
<p className="text-xs text-slate-400 hidden sm:block">管理工作台外观基调、语言偏好、模型调度与集成插件环境</p>
|
|
118
|
+
</div>
|
|
111
119
|
</div>
|
|
112
|
-
|
|
120
|
+
<div className="flex items-center gap-2">
|
|
113
121
|
<button
|
|
114
|
-
|
|
115
|
-
className="
|
|
116
|
-
|
|
117
|
-
paddingLeft: 8,
|
|
118
|
-
paddingRight: 10,
|
|
119
|
-
fontSize: 14,
|
|
120
|
-
background: section === n.id ? "var(--dsw-active)" : "transparent",
|
|
121
|
-
color: section === n.id ? "var(--dsw-label-primary)" : "var(--dsw-label-secondary)",
|
|
122
|
-
}}
|
|
123
|
-
onMouseEnter={(e) => {
|
|
124
|
-
if (section !== n.id) e.currentTarget.style.background = "var(--dsw-hover)";
|
|
125
|
-
}}
|
|
126
|
-
onMouseLeave={(e) => {
|
|
127
|
-
if (section !== n.id) e.currentTarget.style.background = "transparent";
|
|
128
|
-
}}
|
|
129
|
-
onClick={() => setSection(n.id)}
|
|
122
|
+
type="button"
|
|
123
|
+
className="clay-btn clay-btn-soft px-3 py-1.5 text-xs text-slate-700 dark:text-slate-200 flex items-center gap-1.5"
|
|
124
|
+
onClick={() => window.open("/api/config", "_blank")}
|
|
130
125
|
>
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
</button>
|
|
134
|
-
))}
|
|
135
|
-
</div>
|
|
136
|
-
|
|
137
|
-
{/* 右侧内容 */}
|
|
138
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
139
|
-
<div className="flex items-center gap-2 px-6 py-3.5">
|
|
140
|
-
<div className="flex-1" />
|
|
141
|
-
<button className="btn-outline" style={{ height: 32, fontSize: 13 }} onClick={() => window.open("/api/config", "_blank")}>
|
|
142
|
-
<IconDownloadOutline16 size={14} /> {t.openConfigFile}
|
|
126
|
+
<IconDownloadOutline16 size={14} className="text-sky-500" />
|
|
127
|
+
<span>{t.openConfigFile}</span>
|
|
143
128
|
</button>
|
|
144
|
-
<button
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
className="clay-btn clay-btn-soft w-8 h-8 rounded-full text-slate-500 hover:text-rose-500 transition-colors"
|
|
132
|
+
aria-label={t.close}
|
|
133
|
+
onClick={onClose}
|
|
134
|
+
>
|
|
145
135
|
<IconCloseOutline14 size={15} />
|
|
146
136
|
</button>
|
|
147
137
|
</div>
|
|
148
|
-
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{/* 12-Column Grid Body */}
|
|
141
|
+
<div className="grid grid-cols-12 gap-5 min-h-0 flex-1 overflow-hidden">
|
|
142
|
+
{/* Left Sub-Navigation Menu (3 cols) */}
|
|
143
|
+
<div className="col-span-12 md:col-span-3 flex flex-col gap-3 min-h-0 overflow-y-auto">
|
|
144
|
+
<div className="clay-inset p-2 rounded-2xl flex flex-col gap-1.5 shrink-0">
|
|
145
|
+
{navItems.map((n) => {
|
|
146
|
+
const isSelected = section === n.id;
|
|
147
|
+
return (
|
|
148
|
+
<button
|
|
149
|
+
key={n.id}
|
|
150
|
+
type="button"
|
|
151
|
+
className={`flex items-center justify-between px-3.5 py-2.5 rounded-xl text-xs font-semibold transition-all ${
|
|
152
|
+
isSelected
|
|
153
|
+
? "clay-card text-emerald-700 dark:text-emerald-300 font-bold bg-white dark:bg-slate-800 shadow-sm"
|
|
154
|
+
: "text-slate-600 dark:text-slate-300 hover:bg-white/60 dark:hover:bg-slate-800/60"
|
|
155
|
+
}`}
|
|
156
|
+
onClick={() => setSection(n.id)}
|
|
157
|
+
>
|
|
158
|
+
<div className="flex items-center gap-2.5">
|
|
159
|
+
<span className={isSelected ? "text-emerald-500" : "text-slate-400"}>{n.icon}</span>
|
|
160
|
+
<span>{n.label}</span>
|
|
161
|
+
</div>
|
|
162
|
+
{isSelected && <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />}
|
|
163
|
+
</button>
|
|
164
|
+
);
|
|
165
|
+
})}
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
{/* Right Configuration Content Area (9 cols) */}
|
|
171
|
+
<div className="col-span-12 md:col-span-9 min-h-0 flex-1 overflow-y-auto pr-2">
|
|
149
172
|
{section === "general" && <GeneralSection lang={lang} setLang={setLang} />}
|
|
150
173
|
{section === "models" && <ModelsSection />}
|
|
151
174
|
{section === "skills" && <SkillsSection cwd={cwd} onOpenFileContent={onOpenFileContent} />}
|
|
@@ -161,6 +184,16 @@ export function SettingsPanel({
|
|
|
161
184
|
|
|
162
185
|
// ---------- General ----------
|
|
163
186
|
|
|
187
|
+
const THEME_OPTIONS: Array<{ id: PebrelTheme; labelKey: string; nameZh: string; nameEn: string; light: string; dark: string; accent: string }> = [
|
|
188
|
+
{ id: "piweb", labelKey: "themePiWeb", nameZh: "PiWeb · 工作台", nameEn: "PiWeb · Workbench", light: "#34D399", dark: "#A7F3D0", accent: "#34D399" },
|
|
189
|
+
{ id: "ocean-ink", labelKey: "themeOceanInk", nameZh: "海与墨", nameEn: "Ocean Ink", light: "#38BDF8", dark: "#0284C7", accent: "#38BDF8" },
|
|
190
|
+
{ id: "silver-steel", labelKey: "themeSilverSteel", nameZh: "银钢", nameEn: "Silver Steel", light: "#E2E8F0", dark: "#475569", accent: "#94A3B8" },
|
|
191
|
+
{ id: "limestone-coal", labelKey: "themeLimestoneCoal", nameZh: "石灰与煤炭", nameEn: "Limestone & Coal", light: "#FB923C", dark: "#1E293B", accent: "#FB923C" },
|
|
192
|
+
{ id: "linen-moss", labelKey: "themeLinenMoss", nameZh: "亚麻与苔绿", nameEn: "Linen & Moss", light: "#FFDCC5", dark: "#006C4B", accent: "#006C4B" },
|
|
193
|
+
{ id: "nord-paper", labelKey: "themeNordPaper", nameZh: "北欧纸墨", nameEn: "Nordic Paper", light: "#F6FAFE", dark: "#00668A", accent: "#00668A" },
|
|
194
|
+
{ id: "pebrel", labelKey: "themePebrel", nameZh: "Pebrel", nameEn: "Pebrel", light: "#818CF8", dark: "#EEF2FF", accent: "#818CF8" },
|
|
195
|
+
];
|
|
196
|
+
|
|
164
197
|
function GeneralSection({
|
|
165
198
|
lang,
|
|
166
199
|
setLang,
|
|
@@ -169,7 +202,7 @@ function GeneralSection({
|
|
|
169
202
|
setLang: (l: "zh" | "en") => void;
|
|
170
203
|
}) {
|
|
171
204
|
const { t } = useI18n();
|
|
172
|
-
const [theme, setTheme] = useState<PebrelTheme>("
|
|
205
|
+
const [theme, setTheme] = useState<PebrelTheme>("piweb");
|
|
173
206
|
const [themeMode, setThemeMode] = useState<ThemeMode>("system");
|
|
174
207
|
const [fontSize, setFontSize] = useState(14);
|
|
175
208
|
const [chatFontSize, setChatFontSize] = useState(14);
|
|
@@ -179,6 +212,8 @@ function GeneralSection({
|
|
|
179
212
|
const [piSettings, setPiSettings] = useState<{ compaction: { enabled: boolean; reserveTokens: number; keepRecentTokens: number }; retry: { enabled: boolean; maxRetries: number; baseDelayMs: number } } | null>(null);
|
|
180
213
|
const [versions, setVersions] = useState<{ piWeb: string; piEngine: string } | null>(null);
|
|
181
214
|
const [webAuth, setWebAuth] = useState<{ enabled: boolean; authenticated: boolean } | null>(null);
|
|
215
|
+
const [logoutPending, setLogoutPending] = useState(false);
|
|
216
|
+
const [logoutError, setLogoutError] = useState(false);
|
|
182
217
|
const [settingsLoading, setSettingsLoading] = useState(true);
|
|
183
218
|
const [settingsSaving, setSettingsSaving] = useState(false);
|
|
184
219
|
const [settingsError, setSettingsError] = useState<"load" | "save" | null>(null);
|
|
@@ -333,14 +368,17 @@ function GeneralSection({
|
|
|
333
368
|
}
|
|
334
369
|
};
|
|
335
370
|
|
|
336
|
-
const themes
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
371
|
+
const themes = useMemo(
|
|
372
|
+
() =>
|
|
373
|
+
THEME_OPTIONS.map((th) => ({
|
|
374
|
+
id: th.id,
|
|
375
|
+
label: (t as Record<string, string>)[th.labelKey] || (lang === "zh" ? th.nameZh : th.nameEn),
|
|
376
|
+
light: th.light,
|
|
377
|
+
dark: th.dark,
|
|
378
|
+
accent: th.accent,
|
|
379
|
+
})),
|
|
380
|
+
[lang, t],
|
|
381
|
+
);
|
|
344
382
|
|
|
345
383
|
return (
|
|
346
384
|
<div className="flex flex-col gap-6">
|
|
@@ -361,38 +399,34 @@ function GeneralSection({
|
|
|
361
399
|
/>
|
|
362
400
|
</Row>
|
|
363
401
|
<RowColumn title={t.appearance}>
|
|
364
|
-
<div className="
|
|
365
|
-
{themes.map((th) =>
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
</
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
{th.label}
|
|
389
|
-
</span>
|
|
390
|
-
</button>
|
|
391
|
-
))}
|
|
402
|
+
<div className="settings-theme-grid grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2.5 w-full">
|
|
403
|
+
{themes.map((th) => {
|
|
404
|
+
const isSelected = theme === th.id;
|
|
405
|
+
return (
|
|
406
|
+
<button
|
|
407
|
+
key={th.id}
|
|
408
|
+
type="button"
|
|
409
|
+
className={`clay-card flex flex-col items-center justify-center p-3.5 rounded-2xl transition-all hover:-translate-y-0.5 ${
|
|
410
|
+
isSelected ? "ring-2 ring-emerald-500 shadow-md bg-white dark:bg-slate-800" : "bg-white/70 dark:bg-slate-900/60 opacity-80 hover:opacity-100"
|
|
411
|
+
}`}
|
|
412
|
+
aria-pressed={isSelected}
|
|
413
|
+
onClick={() => pickTheme(th.id)}
|
|
414
|
+
>
|
|
415
|
+
<div className="flex items-center overflow-hidden rounded-lg mb-2 shadow-inner border border-slate-200/50 dark:border-slate-700/50" style={{ width: 48, height: 22 }}>
|
|
416
|
+
<span style={{ background: th.light, flex: 1, height: "100%" }} />
|
|
417
|
+
<span style={{ background: th.dark, flex: 1, height: "100%" }} />
|
|
418
|
+
</div>
|
|
419
|
+
<div className="flex items-center gap-1.5 text-xs font-medium text-slate-800 dark:text-slate-100">
|
|
420
|
+
<span className="w-2 h-2 rounded-full inline-block flex-none" style={{ background: th.accent }} />
|
|
421
|
+
<span className="truncate">{th.label}</span>
|
|
422
|
+
</div>
|
|
423
|
+
</button>
|
|
424
|
+
);
|
|
425
|
+
})}
|
|
392
426
|
</div>
|
|
393
427
|
</RowColumn>
|
|
394
428
|
<RowColumn title={t.appearanceMode}>
|
|
395
|
-
<div className="flex
|
|
429
|
+
<div className="clay-inset flex p-1 rounded-2xl bg-slate-100/90 dark:bg-slate-900/80 gap-1 w-full max-w-sm">
|
|
396
430
|
{([
|
|
397
431
|
{ id: "light", label: t.themeLight },
|
|
398
432
|
{ id: "dark", label: t.themeDark },
|
|
@@ -400,17 +434,12 @@ function GeneralSection({
|
|
|
400
434
|
] as { id: ThemeMode; label: string }[]).map((mode) => (
|
|
401
435
|
<button
|
|
402
436
|
key={mode.id}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
background: themeMode === mode.id ? "var(--dsw-module-platform)" : "transparent",
|
|
410
|
-
color: "var(--dsw-label-primary)",
|
|
411
|
-
fontSize: 14,
|
|
412
|
-
lineHeight: "22px",
|
|
413
|
-
}}
|
|
437
|
+
type="button"
|
|
438
|
+
className={`flex-1 py-1.5 px-3 rounded-xl text-xs font-semibold transition-all ${
|
|
439
|
+
themeMode === mode.id
|
|
440
|
+
? "clay-btn-soft text-slate-800 dark:text-slate-100 shadow-sm"
|
|
441
|
+
: "text-slate-500 hover:text-slate-800 dark:hover:text-slate-200"
|
|
442
|
+
}`}
|
|
414
443
|
aria-pressed={themeMode === mode.id}
|
|
415
444
|
onClick={() => pickThemeMode(mode.id)}
|
|
416
445
|
>
|
|
@@ -437,7 +466,7 @@ function GeneralSection({
|
|
|
437
466
|
</Row>
|
|
438
467
|
<Row title={t.autoCompact} desc={t.autoCompactDesc}>
|
|
439
468
|
<div className="flex items-center gap-2">
|
|
440
|
-
<button disabled={!piSettings || settingsSaving} className="relative h-5 w-9 flex-none rounded-full transition-colors" style={{ background: piSettings?.compaction.enabled ? "var(--dsw-accent)" : "var(--dsw-border-l3)" }} role="switch" aria-checked={piSettings?.compaction.enabled ?? false} onClick={() => void patchPiSettings({ compaction: { enabled: !piSettings?.compaction.enabled } })}>
|
|
469
|
+
<button disabled={!piSettings || settingsSaving} className="relative h-5 w-9 flex-none rounded-full transition-colors" style={{ background: piSettings?.compaction.enabled ? "var(--dsw-accent)" : "var(--dsw-border-l3)" }} role="switch" aria-label={t.autoCompact} aria-checked={piSettings?.compaction.enabled ?? false} onClick={() => void patchPiSettings({ compaction: { enabled: !piSettings?.compaction.enabled } })}>
|
|
441
470
|
<span className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all" style={{ left: piSettings?.compaction.enabled ? 18 : 2 }} />
|
|
442
471
|
</button>
|
|
443
472
|
{numberInput("compaction", "reserveTokens", t.reserveTokens)}
|
|
@@ -446,7 +475,7 @@ function GeneralSection({
|
|
|
446
475
|
</Row>
|
|
447
476
|
<Row title={t.autoRetry} desc={t.autoRetryDesc}>
|
|
448
477
|
<div className="flex items-center gap-2">
|
|
449
|
-
<button disabled={!piSettings || settingsSaving} className="relative h-5 w-9 flex-none rounded-full transition-colors" style={{ background: piSettings?.retry.enabled ? "var(--dsw-accent)" : "var(--dsw-border-l3)" }} role="switch" aria-checked={piSettings?.retry.enabled ?? false} onClick={() => void patchPiSettings({ retry: { enabled: !piSettings?.retry.enabled } })}>
|
|
478
|
+
<button disabled={!piSettings || settingsSaving} className="relative h-5 w-9 flex-none rounded-full transition-colors" style={{ background: piSettings?.retry.enabled ? "var(--dsw-accent)" : "var(--dsw-border-l3)" }} role="switch" aria-label={t.autoRetry} aria-checked={piSettings?.retry.enabled ?? false} onClick={() => void patchPiSettings({ retry: { enabled: !piSettings?.retry.enabled } })}>
|
|
450
479
|
<span className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all" style={{ left: piSettings?.retry.enabled ? 18 : 2 }} />
|
|
451
480
|
</button>
|
|
452
481
|
{numberInput("retry", "maxRetries", t.maxRetries)}
|
|
@@ -479,12 +508,24 @@ function GeneralSection({
|
|
|
479
508
|
</Row>
|
|
480
509
|
{webAuth?.enabled ? (
|
|
481
510
|
<Row title={t.webAuth} desc={t.webAuthOn}>
|
|
511
|
+
{logoutError && <span role="alert" style={{ color: "var(--dsw-danger)", fontSize: 12 }}>{t.logoutFailed}</span>}
|
|
482
512
|
<button
|
|
483
513
|
className="btn-outline"
|
|
484
514
|
style={{ height: 30, padding: "0 14px", fontSize: 12.5 }}
|
|
515
|
+
disabled={logoutPending}
|
|
485
516
|
onClick={async () => {
|
|
486
|
-
|
|
487
|
-
|
|
517
|
+
if (logoutPending) return;
|
|
518
|
+
setLogoutPending(true);
|
|
519
|
+
setLogoutError(false);
|
|
520
|
+
try {
|
|
521
|
+
const response = await fetch("/api/web-auth", { method: "DELETE" });
|
|
522
|
+
const result = await response.json();
|
|
523
|
+
if (!response.ok || !result.success) throw new Error("logout failed");
|
|
524
|
+
window.location.href = "/login";
|
|
525
|
+
} catch {
|
|
526
|
+
setLogoutError(true);
|
|
527
|
+
setLogoutPending(false);
|
|
528
|
+
}
|
|
488
529
|
}}
|
|
489
530
|
>
|
|
490
531
|
{t.logout}
|
|
@@ -695,7 +736,7 @@ function SelectOption({
|
|
|
695
736
|
|
|
696
737
|
function Row({ title, desc, children }: { title: string; desc?: string; children: React.ReactNode }) {
|
|
697
738
|
return (
|
|
698
|
-
<div className="hairline-b flex items-center justify-between gap-6 pb-5">
|
|
739
|
+
<div className="settings-row hairline-b flex items-center justify-between gap-6 pb-5">
|
|
699
740
|
<div className="min-w-0">
|
|
700
741
|
<div style={{ fontSize: 14, fontWeight: 400 }}>{title}</div>
|
|
701
742
|
{desc && (
|
|
@@ -704,7 +745,7 @@ function Row({ title, desc, children }: { title: string; desc?: string; children
|
|
|
704
745
|
</div>
|
|
705
746
|
)}
|
|
706
747
|
</div>
|
|
707
|
-
<div className="flex-none">{children}</div>
|
|
748
|
+
<div className="settings-row-control flex-none">{children}</div>
|
|
708
749
|
</div>
|
|
709
750
|
);
|
|
710
751
|
}
|
|
@@ -729,10 +770,10 @@ function RowColumn({ title, desc, children }: { title: string; desc?: string; ch
|
|
|
729
770
|
/** 字号步进器:− 值 +(越界禁用;点中间数值回到 14 默认) */
|
|
730
771
|
function FontSizeStepper({ value, onChange, max }: { value: number; onChange: (v: number) => void; max: number }) {
|
|
731
772
|
return (
|
|
732
|
-
<div className="flex items-center gap-
|
|
773
|
+
<div className="clay-inset flex items-center gap-1.5 p-1 rounded-2xl bg-slate-100 dark:bg-slate-900">
|
|
733
774
|
<button
|
|
734
|
-
|
|
735
|
-
|
|
775
|
+
type="button"
|
|
776
|
+
className="clay-btn clay-btn-soft w-7 h-7 rounded-xl text-xs font-bold text-slate-700 dark:text-slate-200 disabled:opacity-40"
|
|
736
777
|
disabled={value <= 12}
|
|
737
778
|
onClick={() => onChange(value - 1)}
|
|
738
779
|
aria-label="decrease font size"
|
|
@@ -740,15 +781,16 @@ function FontSizeStepper({ value, onChange, max }: { value: number; onChange: (v
|
|
|
740
781
|
−
|
|
741
782
|
</button>
|
|
742
783
|
<button
|
|
743
|
-
|
|
744
|
-
|
|
784
|
+
type="button"
|
|
785
|
+
className="px-2 text-center text-xs font-semibold text-slate-700 dark:text-slate-200 hover:text-emerald-500 transition-colors"
|
|
786
|
+
title="reset to 14px"
|
|
745
787
|
onClick={() => onChange(14)}
|
|
746
788
|
>
|
|
747
789
|
{value} px
|
|
748
790
|
</button>
|
|
749
791
|
<button
|
|
750
|
-
|
|
751
|
-
|
|
792
|
+
type="button"
|
|
793
|
+
className="clay-btn clay-btn-soft w-7 h-7 rounded-xl text-xs font-bold text-slate-700 dark:text-slate-200 disabled:opacity-40"
|
|
752
794
|
disabled={value >= max}
|
|
753
795
|
onClick={() => onChange(value + 1)}
|
|
754
796
|
aria-label="increase font size"
|
|
@@ -1203,13 +1245,19 @@ function ModelsSection() {
|
|
|
1203
1245
|
|
|
1204
1246
|
return (
|
|
1205
1247
|
<div className="flex flex-col gap-5">
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
<div className="
|
|
1209
|
-
{
|
|
1248
|
+
{/* Notice Banner (Screen 6 Soft Claymorphism) */}
|
|
1249
|
+
<div className="p-4 rounded-2xl bg-emerald-50/80 dark:bg-emerald-950/30 border border-emerald-200/50 dark:border-emerald-800/40 flex items-start gap-3">
|
|
1250
|
+
<div className="w-8 h-8 rounded-full bg-white dark:bg-slate-800 text-emerald-600 clay-card flex-shrink-0 flex items-center justify-center mt-0.5 shadow-sm">
|
|
1251
|
+
<IconModelOutline16 size={16} />
|
|
1210
1252
|
</div>
|
|
1211
|
-
<div className="
|
|
1212
|
-
{t.
|
|
1253
|
+
<div className="flex flex-col gap-1 min-w-0">
|
|
1254
|
+
<span className="font-bold text-slate-800 dark:text-slate-100 text-sm">{t.modelsTitle}</span>
|
|
1255
|
+
<p className="text-xs text-slate-600 dark:text-slate-300 leading-relaxed">
|
|
1256
|
+
{t.modelsDesc}
|
|
1257
|
+
</p>
|
|
1258
|
+
<p className="text-[11px] text-slate-400 dark:text-slate-400">
|
|
1259
|
+
{t.customModelIdentityNotice}
|
|
1260
|
+
</p>
|
|
1213
1261
|
</div>
|
|
1214
1262
|
</div>
|
|
1215
1263
|
|
|
@@ -1233,8 +1281,8 @@ function ModelsSection() {
|
|
|
1233
1281
|
const editingThis = editing === p.id;
|
|
1234
1282
|
return (
|
|
1235
1283
|
<div key={p.id}>
|
|
1236
|
-
{/*
|
|
1237
|
-
<div className="flex flex-col rounded-2xl px-4 py-3" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
|
|
1284
|
+
{/* 卡片:身份/操作行在上,额度显示放在同一个框内的第二段(柔和粘土拟物风格) */}
|
|
1285
|
+
<div className="clay-card flex flex-col rounded-2xl px-4 py-3 bg-white/80 dark:bg-slate-900/80 shadow-sm transition-all hover:shadow-md" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
|
|
1238
1286
|
<div className="flex items-center gap-3">
|
|
1239
1287
|
<ProviderBrand id={p.id} name={providerName(p.id)} size={34} />
|
|
1240
1288
|
<div className="min-w-0 flex-1">
|
|
@@ -1452,30 +1500,39 @@ function ModelsSection() {
|
|
|
1452
1500
|
})}
|
|
1453
1501
|
</div>
|
|
1454
1502
|
|
|
1455
|
-
{/*
|
|
1456
|
-
<div className="
|
|
1503
|
+
{/* 两个添加按钮: Large Tactile Extruded Clay Buttons (Screen 6) */}
|
|
1504
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-1">
|
|
1457
1505
|
<button
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
|
|
1461
|
-
onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
|
|
1506
|
+
type="button"
|
|
1507
|
+
className="py-3.5 px-5 rounded-2xl clay-card text-slate-800 dark:text-slate-100 font-bold flex items-center justify-center gap-2 hover:-translate-y-0.5 active:translate-y-0.5 transition-all cursor-pointer group shadow-sm hover:shadow"
|
|
1462
1508
|
onClick={() => setAdding("builtin")}
|
|
1463
1509
|
>
|
|
1464
|
-
<
|
|
1465
|
-
|
|
1510
|
+
<div className="w-7 h-7 rounded-full clay-inset flex items-center justify-center text-emerald-600 group-hover:scale-110 transition-transform">
|
|
1511
|
+
<IconPlusOutline16 size={15} />
|
|
1512
|
+
</div>
|
|
1513
|
+
<span>{t.addProvider}</span>
|
|
1466
1514
|
</button>
|
|
1467
1515
|
<button
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
|
|
1471
|
-
onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
|
|
1516
|
+
type="button"
|
|
1517
|
+
className="py-3.5 px-5 rounded-2xl clay-card text-slate-800 dark:text-slate-100 font-bold flex items-center justify-center gap-2 hover:-translate-y-0.5 active:translate-y-0.5 transition-all cursor-pointer group shadow-sm hover:shadow"
|
|
1472
1518
|
onClick={() => setAdding("custom")}
|
|
1473
1519
|
>
|
|
1474
|
-
<
|
|
1475
|
-
|
|
1520
|
+
<div className="w-7 h-7 rounded-full clay-inset flex items-center justify-center text-orange-600 group-hover:scale-110 transition-transform">
|
|
1521
|
+
<IconPlusOutline16 size={15} />
|
|
1522
|
+
</div>
|
|
1523
|
+
<span>{t.addCustomProvider}</span>
|
|
1476
1524
|
</button>
|
|
1477
1525
|
</div>
|
|
1478
1526
|
|
|
1527
|
+
{/* 路由状态提示 */}
|
|
1528
|
+
<div className="clay-card p-4 rounded-2xl flex items-center gap-3 bg-gradient-to-r from-emerald-50/50 to-sky-50/50 dark:from-emerald-950/20 dark:to-sky-950/20 text-xs text-slate-600 dark:text-slate-300 border border-emerald-100/60 dark:border-emerald-950/40">
|
|
1529
|
+
<span className="text-emerald-500 font-bold flex items-center gap-1.5 flex-none">
|
|
1530
|
+
<span className="w-2 h-2 rounded-full bg-emerald-500 inline-block" />
|
|
1531
|
+
路由提示:
|
|
1532
|
+
</span>
|
|
1533
|
+
<span>请求使用当前选定的模型与供应商;此处未配置自动故障转移。</span>
|
|
1534
|
+
</div>
|
|
1535
|
+
|
|
1479
1536
|
{adding ? (
|
|
1480
1537
|
<ProviderSetupModal
|
|
1481
1538
|
mode={adding}
|
|
@@ -1686,59 +1743,115 @@ function ToolsSection({
|
|
|
1686
1743
|
return (
|
|
1687
1744
|
<div className="flex flex-col gap-5">
|
|
1688
1745
|
<div>
|
|
1689
|
-
<div style={{ fontSize:
|
|
1746
|
+
<div style={{ fontSize: 16, fontWeight: 600, color: "var(--dsw-label-primary)" }}>{t.toolsSection}</div>
|
|
1690
1747
|
<div className="mt-0.5" style={{ fontSize: 12.5, color: "var(--dsw-label-caption)" }}>
|
|
1691
|
-
|
|
1748
|
+
当前会话生效的工具与权限预设;主会话不提供系统级沙盒隔离
|
|
1692
1749
|
</div>
|
|
1693
1750
|
</div>
|
|
1694
1751
|
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
{
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1752
|
+
{/* 3-tier permission modes (Screen 5) */}
|
|
1753
|
+
<div className="flex flex-col gap-2">
|
|
1754
|
+
<div className="text-xs font-medium text-slate-500">运行权限预设</div>
|
|
1755
|
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-2.5">
|
|
1756
|
+
<button
|
|
1757
|
+
type="button"
|
|
1758
|
+
className={`clay-card p-3 rounded-2xl text-left transition-all ${
|
|
1759
|
+
toolPreset === "readonly" ? "ring-2 ring-emerald-500 bg-white dark:bg-slate-800 shadow-md" : "bg-white/70 dark:bg-slate-900/60 opacity-85 hover:opacity-100"
|
|
1760
|
+
}`}
|
|
1761
|
+
onClick={() => onToolPresetChange("readonly")}
|
|
1762
|
+
>
|
|
1763
|
+
<div className="font-semibold text-xs text-slate-800 dark:text-slate-100 flex items-center justify-between">
|
|
1764
|
+
<span>{t.toolReadonly}</span>
|
|
1765
|
+
{toolPreset === "readonly" && <IconCheckOutline14 className="text-emerald-500" size={13} />}
|
|
1766
|
+
</div>
|
|
1767
|
+
<div className="text-[11px] text-slate-500 mt-1 leading-relaxed">仅读取文件与搜索,禁止修改系统或执行命令</div>
|
|
1768
|
+
</button>
|
|
1769
|
+
|
|
1770
|
+
<button
|
|
1771
|
+
type="button"
|
|
1772
|
+
className={`clay-card p-3 rounded-2xl text-left transition-all ${
|
|
1773
|
+
toolPreset === "standard" ? "ring-2 ring-emerald-500 bg-white dark:bg-slate-800 shadow-md" : "bg-white/70 dark:bg-slate-900/60 opacity-85 hover:opacity-100"
|
|
1774
|
+
}`}
|
|
1775
|
+
onClick={() => onToolPresetChange("standard")}
|
|
1776
|
+
>
|
|
1777
|
+
<div className="font-semibold text-xs text-slate-800 dark:text-slate-100 flex items-center justify-between">
|
|
1778
|
+
<span>{t.toolStandard}</span>
|
|
1779
|
+
{toolPreset === "standard" && <IconCheckOutline14 className="text-emerald-500" size={13} />}
|
|
1780
|
+
</div>
|
|
1781
|
+
<div className="text-[11px] text-slate-500 mt-1 leading-relaxed">常规开发工具集,终端执行与高危修改需确认</div>
|
|
1782
|
+
</button>
|
|
1783
|
+
|
|
1784
|
+
<button
|
|
1785
|
+
type="button"
|
|
1786
|
+
className={`clay-card p-3 rounded-2xl text-left transition-all ${
|
|
1787
|
+
toolPreset === "full" ? "ring-2 ring-emerald-500 bg-white dark:bg-slate-800 shadow-md" : "bg-white/70 dark:bg-slate-900/60 opacity-85 hover:opacity-100"
|
|
1788
|
+
}`}
|
|
1789
|
+
onClick={() => onToolPresetChange("full")}
|
|
1790
|
+
>
|
|
1791
|
+
<div className="font-semibold text-xs text-slate-800 dark:text-slate-100 flex items-center justify-between">
|
|
1792
|
+
<span>{t.toolFull}</span>
|
|
1793
|
+
{toolPreset === "full" && <IconCheckOutline14 className="text-emerald-500" size={13} />}
|
|
1794
|
+
</div>
|
|
1795
|
+
<div className="text-[11px] text-slate-500 mt-1 leading-relaxed">允许所有内置与扩展工具无障碍全自动调用</div>
|
|
1796
|
+
</button>
|
|
1797
|
+
</div>
|
|
1798
|
+
</div>
|
|
1799
|
+
|
|
1800
|
+
{/* Telemetry card / Context caching widget */}
|
|
1801
|
+
<div className="clay-card p-3 rounded-2xl bg-emerald-50/40 dark:bg-emerald-950/20 border border-emerald-200/50 dark:border-emerald-900/40 flex items-center justify-between text-xs">
|
|
1802
|
+
<div className="flex items-center gap-2">
|
|
1803
|
+
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
|
1804
|
+
<span className="font-medium text-slate-700 dark:text-slate-200">会话工具:</span>
|
|
1805
|
+
<span className="text-emerald-600 dark:text-emerald-400 font-semibold">{active.size}/{all.length} 已启用</span>
|
|
1806
|
+
</div>
|
|
1807
|
+
<div className="text-slate-400 text-[11px] font-mono">
|
|
1808
|
+
按当前会话工具清单统计
|
|
1809
|
+
</div>
|
|
1810
|
+
</div>
|
|
1706
1811
|
|
|
1707
1812
|
{all.length === 0 ? (
|
|
1708
|
-
<div className="rounded-2xl p-
|
|
1813
|
+
<div className="clay-card rounded-2xl p-6 text-center text-xs text-slate-400">
|
|
1709
1814
|
{t.toolsNoSession}
|
|
1710
1815
|
</div>
|
|
1711
1816
|
) : (
|
|
1712
|
-
<div className="flex flex-col gap-2
|
|
1713
|
-
<div className="flex items-center
|
|
1714
|
-
<span
|
|
1817
|
+
<div className="flex flex-col gap-2">
|
|
1818
|
+
<div className="flex items-center justify-between text-xs text-slate-500 px-1">
|
|
1819
|
+
<span className="font-medium">{t.toolsCurrentSession}</span>
|
|
1715
1820
|
<span>{t.toolsActiveCount.replace("{n}", String(active.size)).replace("{total}", String(all.length))}</span>
|
|
1716
1821
|
</div>
|
|
1717
1822
|
{all.map((tool) => {
|
|
1718
1823
|
const on = active.has(tool.name);
|
|
1719
1824
|
const desc = tool.description || describe(tool.name);
|
|
1720
1825
|
return (
|
|
1721
|
-
<div key={tool.name} className="flex items-center
|
|
1722
|
-
<div className="min-w-0 flex-1">
|
|
1723
|
-
<div
|
|
1826
|
+
<div key={tool.name} className="clay-card flex items-center justify-between p-3 rounded-2xl transition-all hover:translate-x-0.5">
|
|
1827
|
+
<div className="min-w-0 flex-1 pr-3">
|
|
1828
|
+
<div className="flex items-center gap-2">
|
|
1829
|
+
<span className="clay-code-pill px-2.5 py-0.5 text-xs font-mono font-bold text-slate-800 dark:text-slate-200">
|
|
1830
|
+
{tool.name}
|
|
1831
|
+
</span>
|
|
1832
|
+
</div>
|
|
1724
1833
|
{desc && (
|
|
1725
|
-
<div className="truncate
|
|
1834
|
+
<div className="truncate text-xs text-slate-500 mt-1">
|
|
1726
1835
|
{desc}
|
|
1727
1836
|
</div>
|
|
1728
1837
|
)}
|
|
1729
1838
|
</div>
|
|
1730
1839
|
<button
|
|
1731
|
-
|
|
1732
|
-
|
|
1840
|
+
type="button"
|
|
1841
|
+
className={`clay-btn w-12 h-6 rounded-full transition-all duration-200 p-0.5 flex items-center ${
|
|
1842
|
+
on ? "clay-btn-mint justify-end" : "bg-slate-200 dark:bg-slate-700 text-slate-400 justify-start"
|
|
1843
|
+
}`}
|
|
1733
1844
|
role="switch"
|
|
1734
1845
|
aria-checked={on}
|
|
1735
1846
|
aria-label={tool.name}
|
|
1736
1847
|
disabled={!onSetTools || (on && active.size <= 1)}
|
|
1737
1848
|
onClick={() => toggle(tool.name)}
|
|
1738
1849
|
>
|
|
1739
|
-
<span className="
|
|
1850
|
+
<span className="w-5 h-5 rounded-full bg-white shadow-md flex items-center justify-center text-[10px] font-bold text-emerald-600">
|
|
1851
|
+
{on ? "✓" : "−"}
|
|
1852
|
+
</span>
|
|
1740
1853
|
</button>
|
|
1741
|
-
|
|
1854
|
+
</div>
|
|
1742
1855
|
);
|
|
1743
1856
|
})}
|
|
1744
1857
|
</div>
|
|
@@ -1760,49 +1873,54 @@ interface SkillRow {
|
|
|
1760
1873
|
function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileContent?: (path: string, content: string) => void }) {
|
|
1761
1874
|
const { t } = useI18n();
|
|
1762
1875
|
const [skills, setSkills] = useState<SkillRow[]>([]);
|
|
1876
|
+
const [error, setError] = useState<string | null>(null);
|
|
1877
|
+
const [loading, setLoading] = useState(true);
|
|
1878
|
+
const [busyFile, setBusyFile] = useState<string | null>(null);
|
|
1763
1879
|
|
|
1764
1880
|
const load = useCallback(async () => {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1881
|
+
setLoading(true);
|
|
1882
|
+
try {
|
|
1883
|
+
const r = await fetch(`/api/skills${cwd ? `?cwd=${encodeURIComponent(cwd)}` : ""}`);
|
|
1884
|
+
const j = await r.json();
|
|
1885
|
+
if (!r.ok || !j.success) throw new Error(j.error || t.toastError);
|
|
1886
|
+
setSkills(j.data.skills);
|
|
1887
|
+
setError(null);
|
|
1888
|
+
} catch (cause) {
|
|
1889
|
+
setError(cause instanceof Error ? cause.message : t.toastError);
|
|
1890
|
+
} finally {
|
|
1891
|
+
setLoading(false);
|
|
1892
|
+
}
|
|
1893
|
+
}, [cwd, t.toastError]);
|
|
1769
1894
|
|
|
1770
1895
|
useEffect(() => {
|
|
1771
1896
|
void load();
|
|
1772
1897
|
}, [load]);
|
|
1773
1898
|
|
|
1774
|
-
const
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1899
|
+
const action = async (s: SkillRow, kind: "toggle" | "read" | "delete") => {
|
|
1900
|
+
if (busyFile) return;
|
|
1901
|
+
setBusyFile(s.filePath);
|
|
1902
|
+
setError(null);
|
|
1903
|
+
try {
|
|
1904
|
+
const r = await fetch("/api/skills", {
|
|
1905
|
+
method: "POST",
|
|
1906
|
+
headers: { "Content-Type": "application/json" },
|
|
1907
|
+
body: JSON.stringify({ action: kind, filePath: s.filePath, ...(kind === "toggle" ? { disabled: !s.disabled } : {}), cwd }),
|
|
1908
|
+
});
|
|
1909
|
+
const j = await r.json();
|
|
1910
|
+
if (!r.ok || !j.success) throw new Error(j.error || t.toastError);
|
|
1911
|
+
if (kind === "read") onOpenFileContent?.(s.filePath, j.data.content);
|
|
1912
|
+
else await load();
|
|
1913
|
+
} catch (cause) {
|
|
1914
|
+
setError(cause instanceof Error ? cause.message : t.toastError);
|
|
1915
|
+
} finally {
|
|
1916
|
+
setBusyFile(null);
|
|
1917
|
+
}
|
|
1781
1918
|
};
|
|
1782
1919
|
|
|
1783
1920
|
// 查看技能文档:弹大窗口查看器(与项目文件查看器同款,Markdown 可切渲染/源码)
|
|
1784
|
-
const view = async (s: SkillRow) => {
|
|
1785
|
-
const r = await fetch("/api/skills", {
|
|
1786
|
-
method: "POST",
|
|
1787
|
-
headers: { "Content-Type": "application/json" },
|
|
1788
|
-
body: JSON.stringify({ action: "read", filePath: s.filePath, cwd }),
|
|
1789
|
-
});
|
|
1790
|
-
const j = await r.json();
|
|
1791
|
-
if (j.success) onOpenFileContent?.(s.filePath, j.data.content);
|
|
1792
|
-
};
|
|
1793
|
-
|
|
1794
|
-
const [deleteError, setDeleteError] = useState<string | null>(null);
|
|
1795
1921
|
const remove = async (s: SkillRow) => {
|
|
1796
1922
|
if (!window.confirm(t.confirmDeleteSkill.replace("{name}", s.name))) return;
|
|
1797
|
-
|
|
1798
|
-
const r = await fetch("/api/skills", {
|
|
1799
|
-
method: "POST",
|
|
1800
|
-
headers: { "Content-Type": "application/json" },
|
|
1801
|
-
body: JSON.stringify({ action: "delete", filePath: s.filePath, cwd }),
|
|
1802
|
-
});
|
|
1803
|
-
const j = await r.json();
|
|
1804
|
-
if (j.success) void load();
|
|
1805
|
-
else setDeleteError(j.error || t.toastError);
|
|
1923
|
+
await action(s, "delete");
|
|
1806
1924
|
};
|
|
1807
1925
|
|
|
1808
1926
|
const scopeLabel = (s: SkillRow["scope"]) => (s === "global" ? t.scopeGlobal : s === "project" ? t.scopeProject : t.scopePackage);
|
|
@@ -1810,13 +1928,15 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
|
|
|
1810
1928
|
return (
|
|
1811
1929
|
<div className="flex flex-col gap-4">
|
|
1812
1930
|
<div style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>{t.skillsDesc}</div>
|
|
1813
|
-
{
|
|
1931
|
+
{error && (
|
|
1814
1932
|
<div role="alert" className="rounded-lg px-3 py-2" style={{ fontSize: 12.5, background: "var(--dsw-hover)", color: "var(--dsw-danger)" }}>
|
|
1815
|
-
{
|
|
1933
|
+
{error} <button type="button" className="underline" onClick={() => void load()}>{t.settingsRetry}</button>
|
|
1816
1934
|
</div>
|
|
1817
1935
|
)}
|
|
1818
|
-
{skills.length === 0 && (
|
|
1819
|
-
<div style={{ fontSize: 13, color: "var(--dsw-label-caption)" }}
|
|
1936
|
+
{!error && skills.length === 0 && (
|
|
1937
|
+
<div role="status" style={{ fontSize: 13, color: "var(--dsw-label-caption)" }}>
|
|
1938
|
+
{loading ? t.skillsLoading : t.skillsEmpty}
|
|
1939
|
+
</div>
|
|
1820
1940
|
)}
|
|
1821
1941
|
<div className="flex flex-col gap-2">
|
|
1822
1942
|
{skills.map((s) => (
|
|
@@ -1839,7 +1959,8 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
|
|
|
1839
1959
|
<button
|
|
1840
1960
|
className="rounded-lg px-2.5 py-1"
|
|
1841
1961
|
style={{ fontSize: 11.5, color: "var(--dsw-label-tertiary)" }}
|
|
1842
|
-
|
|
1962
|
+
disabled={busyFile !== null}
|
|
1963
|
+
onClick={() => void action(s, "read")}
|
|
1843
1964
|
>
|
|
1844
1965
|
{t.viewSkillFile}
|
|
1845
1966
|
</button>
|
|
@@ -1848,6 +1969,7 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
|
|
|
1848
1969
|
className="rounded-lg px-2.5 py-1"
|
|
1849
1970
|
style={{ fontSize: 11.5, color: "var(--dsw-danger)" }}
|
|
1850
1971
|
title={t.deleteSkillHint}
|
|
1972
|
+
disabled={busyFile !== null}
|
|
1851
1973
|
onClick={() => void remove(s)}
|
|
1852
1974
|
>
|
|
1853
1975
|
{t.delete}
|
|
@@ -1862,8 +1984,10 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
|
|
|
1862
1984
|
className="relative h-5 w-9 flex-none rounded-full transition-colors"
|
|
1863
1985
|
style={{ background: s.disabled ? "var(--dsw-border-l3)" : "var(--dsw-accent)" }}
|
|
1864
1986
|
role="switch"
|
|
1987
|
+
aria-label={s.name}
|
|
1865
1988
|
aria-checked={!s.disabled}
|
|
1866
|
-
|
|
1989
|
+
disabled={busyFile !== null}
|
|
1990
|
+
onClick={() => void action(s, "toggle")}
|
|
1867
1991
|
>
|
|
1868
1992
|
<span
|
|
1869
1993
|
className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all"
|
|
@@ -1893,6 +2017,7 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
1893
2017
|
const [busy, setBusy] = useState(false);
|
|
1894
2018
|
const [busyKey, setBusyKey] = useState<string | null>(null);
|
|
1895
2019
|
const [toast, setToast] = useState<{ ok: boolean; msg: string; loading?: boolean } | null>(null);
|
|
2020
|
+
const [loadError, setLoadError] = useState<string | null>(null);
|
|
1896
2021
|
const [menuKey, setMenuKey] = useState<string | null>(null);
|
|
1897
2022
|
const menuRef = useRef<HTMLDivElement | null>(null);
|
|
1898
2023
|
|
|
@@ -1906,13 +2031,17 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
1906
2031
|
}, []);
|
|
1907
2032
|
|
|
1908
2033
|
const load = useCallback(async () => {
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
2034
|
+
try {
|
|
2035
|
+
const r = await fetch(`/api/plugins${cwd ? `?cwd=${encodeURIComponent(cwd)}` : ""}`);
|
|
2036
|
+
const j = await r.json();
|
|
2037
|
+
if (!r.ok || !j.success) throw new Error(j.error || t.toastError);
|
|
1912
2038
|
setPackages(j.data.packages);
|
|
1913
2039
|
setExtensions(j.data.extensions);
|
|
2040
|
+
setLoadError(null);
|
|
2041
|
+
} catch (error) {
|
|
2042
|
+
setLoadError(error instanceof Error ? error.message : t.toastError);
|
|
1914
2043
|
}
|
|
1915
|
-
}, [cwd]);
|
|
2044
|
+
}, [cwd, t.toastError]);
|
|
1916
2045
|
|
|
1917
2046
|
useEffect(() => {
|
|
1918
2047
|
void load();
|
|
@@ -2012,6 +2141,11 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2012
2141
|
<div className="mt-1" style={{ fontSize: 13, color: "var(--dsw-label-tertiary)" }}>
|
|
2013
2142
|
{t.pluginPageDesc}
|
|
2014
2143
|
</div>
|
|
2144
|
+
{loadError && (
|
|
2145
|
+
<div role="alert" className="mt-3 rounded-xl px-3 py-2" style={{ fontSize: 12.5, color: "var(--dsw-danger)", background: "var(--dsw-hover)" }}>
|
|
2146
|
+
{loadError} <button type="button" className="underline" onClick={() => void load()}>{t.settingsRetry}</button>
|
|
2147
|
+
</div>
|
|
2148
|
+
)}
|
|
2015
2149
|
</div>
|
|
2016
2150
|
|
|
2017
2151
|
{toast && (
|
|
@@ -2092,8 +2226,11 @@ function PluginsSection({ cwd }: { cwd: string }) {
|
|
|
2092
2226
|
try {
|
|
2093
2227
|
const r = await fetch("/api/plugins", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "reload", cwd }) });
|
|
2094
2228
|
const j = await r.json();
|
|
2095
|
-
|
|
2096
|
-
|
|
2229
|
+
if (!r.ok || !j.success) throw new Error(j.error || t.toastError);
|
|
2230
|
+
notify(true, t.reloadedSessions.replace("{loaders}", String(j.data?.loaders ?? 0)).replace("{sessions}", String(j.data?.sessions ?? 0)));
|
|
2231
|
+
await load();
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
notify(false, error instanceof Error ? error.message : t.toastError);
|
|
2097
2234
|
} finally {
|
|
2098
2235
|
setBusy(false);
|
|
2099
2236
|
}
|