@rexvane/piweb 0.3.16 → 0.3.17

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.
Files changed (288) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +8 -11
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +6 -6
  5. package/.next/react-loadable-manifest.json +4 -16
  6. package/.next/required-server-files.js +1 -3
  7. package/.next/required-server-files.json +1 -3
  8. package/.next/routes-manifest.json +0 -20
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +4 -7
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -5
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
  23. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  25. package/.next/server/app/api/agent/[id]/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  28. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  29. package/.next/server/app/api/agent/new/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  31. package/.next/server/app/api/config/route.js +1 -1
  32. package/.next/server/app/api/config/route.js.nft.json +1 -1
  33. package/.next/server/app/api/files/route.js +1 -1
  34. package/.next/server/app/api/files/route.js.nft.json +1 -1
  35. package/.next/server/app/api/git/route.js +2 -2
  36. package/.next/server/app/api/git/route.js.nft.json +1 -1
  37. package/.next/server/app/api/growth/route.js +1 -1
  38. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  39. package/.next/server/app/api/health/route.js +1 -1
  40. package/.next/server/app/api/models/route.js +1 -1
  41. package/.next/server/app/api/models/route.js.nft.json +1 -1
  42. package/.next/server/app/api/pi-settings/route.js +1 -1
  43. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  44. package/.next/server/app/api/plugins/route.js +1 -1
  45. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  46. package/.next/server/app/api/prompts/route.js +1 -1
  47. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  48. package/.next/server/app/api/security/route.js +1 -1
  49. package/.next/server/app/api/security/route.js.nft.json +1 -1
  50. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  51. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  52. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  53. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  54. package/.next/server/app/api/sessions/import/route.js +3 -3
  55. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  56. package/.next/server/app/api/sessions/route.js +1 -1
  57. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  58. package/.next/server/app/api/skills/route.js +2 -2
  59. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  60. package/.next/server/app/api/update/route.js +2 -2
  61. package/.next/server/app/api/update/route.js.nft.json +1 -1
  62. package/.next/server/app/api/version/route.js +1 -1
  63. package/.next/server/app/api/version/route.js.nft.json +1 -1
  64. package/.next/server/app/api/web-auth/route.js +1 -1
  65. package/.next/server/app/api/workspaces/route.js +1 -1
  66. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  67. package/.next/server/app/icon.svg/route.js +1 -1
  68. package/.next/server/app/index.html +1 -1
  69. package/.next/server/app/index.rsc +6 -9
  70. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -7
  71. package/.next/server/app/index.segments/_full.segment.rsc +6 -9
  72. package/.next/server/app/index.segments/_tree.segment.rsc +2 -5
  73. package/.next/server/app/login/page.js +1 -1
  74. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/login.html +1 -1
  76. package/.next/server/app/login.rsc +5 -8
  77. package/.next/server/app/login.segments/_full.segment.rsc +5 -8
  78. package/.next/server/app/login.segments/_tree.segment.rsc +2 -5
  79. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -6
  80. package/.next/server/app/page.js +1 -1
  81. package/.next/server/app/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app-paths-manifest.json +8 -11
  83. package/.next/server/chunks/496.js +1 -1
  84. package/.next/server/chunks/532.js +1 -1
  85. package/.next/server/chunks/686.js +4 -8
  86. package/.next/server/middleware-build-manifest.js +1 -1
  87. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  88. package/.next/server/pages/404.html +1 -1
  89. package/.next/server/pages/500.html +1 -1
  90. package/.next/server/server-reference-manifest.json +1 -1
  91. package/.next/static/chunks/2608.ec8ff1f5767c9056.js +1 -0
  92. package/.next/static/chunks/2716.93be8ad5c970c516.js +1 -0
  93. package/.next/static/chunks/4632.112d2e893d48d188.js +1 -0
  94. package/.next/static/chunks/4673.c1fb1580efc6ace7.js +1 -0
  95. package/.next/static/chunks/4686-f4a037d609cea3f2.js +1 -0
  96. package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +1 -0
  97. package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +1 -0
  98. package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +1 -0
  99. package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +1 -0
  100. package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +1 -0
  101. package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +1 -0
  102. package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +1 -0
  103. package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +1 -0
  104. package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +1 -0
  105. package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +1 -0
  106. package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +1 -0
  107. package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +1 -0
  108. package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +1 -0
  109. package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +1 -0
  110. package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +1 -0
  111. package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +1 -0
  112. package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +1 -0
  113. package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +1 -0
  114. package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +1 -0
  115. package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +1 -0
  116. package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +1 -0
  117. package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +1 -0
  118. package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +1 -0
  119. package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +1 -0
  120. package/.next/static/chunks/app/page-b51087831582ee37.js +1 -0
  121. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +1 -0
  122. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +1 -0
  123. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +1 -0
  124. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +1 -0
  125. package/.next/static/chunks/webpack-c31263fd83d75ce6.js +1 -0
  126. package/.next/static/css/850c2fd130ffa2fc.css +1 -0
  127. package/.next/static/zsynuO_9Kx6nrXgkTUJ04/_buildManifest.js +1 -0
  128. package/.next/types/routes.d.ts +1 -4
  129. package/.next/types/validator.ts +0 -27
  130. package/README.md +17 -29
  131. package/assets/archive.jpg +0 -0
  132. package/assets/growth-1.jpg +0 -0
  133. package/assets/growth-2.jpg +0 -0
  134. package/assets/import.jpg +0 -0
  135. package/assets/main.jpg +0 -0
  136. package/assets/outline.jpg +0 -0
  137. package/assets/prompts-1.jpg +0 -0
  138. package/assets/prompts-2.jpg +0 -0
  139. package/assets/providers-1.jpg +0 -0
  140. package/assets/providers-2.jpg +0 -0
  141. package/assets/providers-3.jpg +0 -0
  142. package/assets/trace-1.jpg +0 -0
  143. package/assets/trace-2.png +0 -0
  144. package/assets/trace-3.png +0 -0
  145. package/assets/trace-4.png +0 -0
  146. package/assets/viewer-1.jpg +0 -0
  147. package/assets/viewer-2.jpg +0 -0
  148. package/bin/pi.js +50 -0
  149. package/next.config.ts +32 -7
  150. package/package.json +4 -9
  151. package/scripts/launcher.mjs +1 -1
  152. package/scripts/pick-folder.ps1 +2 -11
  153. package/scripts/release.mjs +31 -11
  154. package/scripts/verify-package.mjs +0 -53
  155. package/src/app/api/config/route.ts +1 -4
  156. package/src/app/api/growth/route.ts +14 -14
  157. package/src/app/api/models/route.ts +2 -1
  158. package/src/app/api/security/route.ts +3 -8
  159. package/src/app/api/update/route.ts +2 -8
  160. package/src/app/globals.css +90 -777
  161. package/src/app/layout.tsx +6 -13
  162. package/src/components/AppShell.tsx +229 -521
  163. package/src/components/ChatInput.tsx +26 -153
  164. package/src/components/ChatWindow.tsx +231 -51
  165. package/src/components/ContextMeter.tsx +17 -11
  166. package/src/components/FileViewer.tsx +11 -10
  167. package/src/components/GitPanel.tsx +11 -13
  168. package/src/components/ModelSelector.tsx +6 -19
  169. package/src/components/PiMark.tsx +38 -5
  170. package/src/components/ProjectPanel.tsx +111 -95
  171. package/src/components/PromptPanel.tsx +51 -132
  172. package/src/components/SessionImportSection.tsx +37 -53
  173. package/src/components/SessionSidebar.tsx +57 -159
  174. package/src/components/SettingsPanel.tsx +193 -330
  175. package/src/components/icons.tsx +0 -23
  176. package/src/hooks/useGrowth.ts +123 -156
  177. package/src/hooks/usePiWeb.ts +15 -122
  178. package/src/i18n.tsx +42 -98
  179. package/src/lib/agent-manager.ts +21 -187
  180. package/src/lib/command-validation.ts +1 -9
  181. package/src/lib/growth-service.ts +390 -166
  182. package/src/lib/growth-tracker.ts +64 -264
  183. package/src/lib/growth-turns.ts +31 -1
  184. package/src/lib/models/model-scope.ts +15 -0
  185. package/src/lib/models-service.ts +16 -0
  186. package/src/lib/pi-settings.ts +7 -0
  187. package/src/lib/pi.ts +2 -3
  188. package/src/lib/session-import/claude.ts +2 -13
  189. package/src/lib/session-import/codex.ts +8 -44
  190. package/src/lib/session-import/files.ts +0 -14
  191. package/src/lib/session-import/grok.ts +24 -54
  192. package/src/lib/session-import/index.ts +0 -5
  193. package/src/lib/session-import/types.ts +0 -12
  194. package/src/lib/session-import/writer.ts +3 -8
  195. package/src/lib/skills-service.ts +9 -16
  196. package/src/lib/theme.ts +1 -68
  197. package/src/lib/trajectory.ts +0 -1
  198. package/src/lib/types.ts +27 -36
  199. package/tsconfig.json +2 -1
  200. package/.next/server/app/api/swarm/[id]/route.js +0 -1
  201. package/.next/server/app/api/swarm/[id]/route.js.nft.json +0 -1
  202. package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +0 -1
  203. package/.next/server/app/api/swarm/route.js +0 -1
  204. package/.next/server/app/api/swarm/route.js.nft.json +0 -1
  205. package/.next/server/app/api/swarm/route_client-reference-manifest.js +0 -1
  206. package/.next/server/app/api/tests/route.js +0 -2
  207. package/.next/server/app/api/tests/route.js.nft.json +0 -1
  208. package/.next/server/app/api/tests/route_client-reference-manifest.js +0 -1
  209. package/.next/server/chunks/757.js +0 -1
  210. package/.next/server/chunks/775.js +0 -3
  211. package/.next/static/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +0 -1
  212. package/.next/static/chunks/2608.43c58236c9cce8e6.js +0 -1
  213. package/.next/static/chunks/2716.4ec42df1d51354c9.js +0 -1
  214. package/.next/static/chunks/4632.62139b565b657aac.js +0 -1
  215. package/.next/static/chunks/4673.7796104395deb8d7.js +0 -1
  216. package/.next/static/chunks/4686-17f21a006739a596.js +0 -1
  217. package/.next/static/chunks/7577.10af9bb661652da8.js +0 -1
  218. package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +0 -1
  219. package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +0 -1
  220. package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +0 -1
  221. package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +0 -1
  222. package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +0 -1
  223. package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +0 -1
  224. package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +0 -1
  225. package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +0 -1
  226. package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +0 -1
  227. package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +0 -1
  228. package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +0 -1
  229. package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +0 -1
  230. package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +0 -1
  231. package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +0 -1
  232. package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +0 -1
  233. package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +0 -1
  234. package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +0 -1
  235. package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +0 -1
  236. package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +0 -1
  237. package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +0 -1
  238. package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +0 -1
  239. package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +0 -1
  240. package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +0 -1
  241. package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +0 -1
  242. package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +0 -1
  243. package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +0 -1
  244. package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +0 -1
  245. package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +0 -1
  246. package/.next/static/chunks/app/page-14379b8060d5cf07.js +0 -1
  247. package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +0 -1
  248. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +0 -1
  249. package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +0 -1
  250. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +0 -1
  251. package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +0 -1
  252. package/.next/static/css/d0a0162cb481abe8.css +0 -1
  253. package/.next/types/app/api/swarm/[id]/route.ts +0 -353
  254. package/.next/types/app/api/swarm/route.ts +0 -353
  255. package/.next/types/app/api/tests/route.ts +0 -353
  256. package/assets/archive.png +0 -0
  257. package/assets/growth-1.png +0 -0
  258. package/assets/growth-2.png +0 -0
  259. package/assets/import.png +0 -0
  260. package/assets/main.png +0 -0
  261. package/assets/outline.png +0 -0
  262. package/assets/prompts-1.png +0 -0
  263. package/assets/prompts-2.png +0 -0
  264. package/assets/providers-1.png +0 -0
  265. package/assets/providers-2.png +0 -0
  266. package/assets/providers-3.png +0 -0
  267. package/assets/trace-1.png +0 -0
  268. package/assets/viewer-1.png +0 -0
  269. package/assets/viewer-2.png +0 -0
  270. package/scripts/managed-niubash-assets.mjs +0 -146
  271. package/scripts/prepare-niubash-packages.mjs +0 -182
  272. package/scripts/run-test-file.mjs +0 -34
  273. package/scripts/smoke-managed-niubash.mjs +0 -62
  274. package/scripts/verify-release-config.mjs +0 -24
  275. package/src/app/api/swarm/[id]/route.ts +0 -28
  276. package/src/app/api/swarm/route.ts +0 -24
  277. package/src/app/api/tests/route.ts +0 -27
  278. package/src/components/SwarmCoordinatorPod.tsx +0 -1021
  279. package/src/components/TestInspectorPod.tsx +0 -549
  280. package/src/lib/delegation.ts +0 -93
  281. package/src/lib/growth-rounds.ts +0 -84
  282. package/src/lib/managed-niubash.ts +0 -231
  283. package/src/lib/runtime-activity.ts +0 -27
  284. package/src/lib/session-import/lock.ts +0 -25
  285. package/src/lib/swarm-service.ts +0 -371
  286. package/src/lib/test-run-service.ts +0 -122
  287. package/src/lib/workflow.ts +0 -55
  288. /package/.next/static/{0t1jBUAuVaTFTXxRyIo3q → zsynuO_9Kx6nrXgkTUJ04}/_ssgManifest.js +0 -0
@@ -95,80 +95,57 @@ export function SettingsPanel({
95
95
  }}
96
96
  >
97
97
  <div
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"
98
+ className="settings-dialog glass-modal flex overflow-hidden"
99
99
  style={{
100
- width: 960,
101
- maxWidth: "calc(100vw - 32px)",
102
- height: "min(840px, calc(100vh - 48px))",
103
- borderRadius: 36,
100
+ width: 800,
101
+ maxWidth: "calc(100vw - 48px)",
102
+ height: "min(800px, calc(100vh - 48px))",
103
+ borderRadius: 32,
104
+ boxShadow: "var(--dsw-elevation-prominent)",
104
105
  }}
105
106
  >
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>
107
+ {/* 左导航(dsh trigger:h42 r12,active 填充) */}
108
+ <div className="settings-nav flex w-52 flex-none flex-col gap-0.5 py-4 pl-4 pr-3">
109
+ <div className="settings-title px-2 pb-3" style={{ fontSize: 16, fontWeight: 500 }}>
110
+ {t.settings}
119
111
  </div>
120
- <div className="flex items-center gap-2">
112
+ {navItems.map((n) => (
121
113
  <button
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")}
125
- >
126
- <IconDownloadOutline16 size={14} className="text-sky-500" />
127
- <span>{t.openConfigFile}</span>
128
- </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}
114
+ key={n.id}
115
+ className="flex h-[42px] items-center gap-2 rounded-xl px-3 text-left transition-colors"
116
+ style={{
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)}
134
130
  >
135
- <IconCloseOutline14 size={15} />
131
+ {section === n.id ? <span style={{ color: "var(--dsw-accent)" }}>{n.icon}</span> : n.icon}
132
+ {n.label}
136
133
  </button>
137
- </div>
134
+ ))}
138
135
  </div>
139
136
 
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
-
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}
143
+ </button>
144
+ <button className="icon-btn" onClick={onClose}>
145
+ <IconCloseOutline14 size={15} />
146
+ </button>
168
147
  </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">
148
+ <div className="min-h-0 flex-1 overflow-y-auto px-6 py-5">
172
149
  {section === "general" && <GeneralSection lang={lang} setLang={setLang} />}
173
150
  {section === "models" && <ModelsSection />}
174
151
  {section === "skills" && <SkillsSection cwd={cwd} onOpenFileContent={onOpenFileContent} />}
@@ -184,16 +161,6 @@ export function SettingsPanel({
184
161
 
185
162
  // ---------- General ----------
186
163
 
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
-
197
164
  function GeneralSection({
198
165
  lang,
199
166
  setLang,
@@ -202,7 +169,7 @@ function GeneralSection({
202
169
  setLang: (l: "zh" | "en") => void;
203
170
  }) {
204
171
  const { t } = useI18n();
205
- const [theme, setTheme] = useState<PebrelTheme>("piweb");
172
+ const [theme, setTheme] = useState<PebrelTheme>("dsh");
206
173
  const [themeMode, setThemeMode] = useState<ThemeMode>("system");
207
174
  const [fontSize, setFontSize] = useState(14);
208
175
  const [chatFontSize, setChatFontSize] = useState(14);
@@ -212,8 +179,6 @@ function GeneralSection({
212
179
  const [piSettings, setPiSettings] = useState<{ compaction: { enabled: boolean; reserveTokens: number; keepRecentTokens: number }; retry: { enabled: boolean; maxRetries: number; baseDelayMs: number } } | null>(null);
213
180
  const [versions, setVersions] = useState<{ piWeb: string; piEngine: string } | null>(null);
214
181
  const [webAuth, setWebAuth] = useState<{ enabled: boolean; authenticated: boolean } | null>(null);
215
- const [logoutPending, setLogoutPending] = useState(false);
216
- const [logoutError, setLogoutError] = useState(false);
217
182
  const [settingsLoading, setSettingsLoading] = useState(true);
218
183
  const [settingsSaving, setSettingsSaving] = useState(false);
219
184
  const [settingsError, setSettingsError] = useState<"load" | "save" | null>(null);
@@ -368,17 +333,14 @@ function GeneralSection({
368
333
  }
369
334
  };
370
335
 
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
- );
336
+ const themes: { id: PebrelTheme; label: string; light: string; dark: string; accent: string }[] = [
337
+ { id: "dsh", label: t.themeDsh, light: "#F9FAFB", dark: "#151517", accent: "#4176E6" },
338
+ { id: "silver-steel", label: t.themeSilverSteel, light: "#F3F4F6", dark: "#1A1C24", accent: "#94A3B8" },
339
+ { id: "limestone-coal", label: t.themeLimestoneCoal, light: "#F0EFEB", dark: "#171717", accent: "#CEB27E" },
340
+ { id: "linen-moss", label: t.themeLinenMoss, light: "#F2F2EC", dark: "#1E211E", accent: "#A3B3A3" },
341
+ { id: "nord-paper", label: t.themeNordPaper, light: "#FCFBF9", dark: "#2E3440", accent: "#88C0D0" },
342
+ { id: "pebrel", label: t.themePebrel, light: "#F3F4F6", dark: "#0F111A", accent: "#52A8FF" },
343
+ ];
382
344
 
383
345
  return (
384
346
  <div className="flex flex-col gap-6">
@@ -399,34 +361,38 @@ function GeneralSection({
399
361
  />
400
362
  </Row>
401
363
  <RowColumn title={t.appearance}>
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
- })}
364
+ <div className="flex w-full flex-wrap gap-2">
365
+ {themes.map((th) => (
366
+ <button
367
+ key={th.id}
368
+ className="flex flex-col items-center justify-center gap-1 transition-colors"
369
+ style={{
370
+ flex: "1 1 150px",
371
+ padding: "20px 32px",
372
+ borderRadius: 20,
373
+ border: `0.5px solid ${theme === th.id ? "var(--dsw-border-strong)" : "var(--dsw-border-l4)"}`,
374
+ background: theme === th.id ? "var(--dsw-module-platform)" : "transparent",
375
+ color: "var(--dsw-label-primary)",
376
+ fontSize: 14,
377
+ lineHeight: "22px",
378
+ }}
379
+ aria-pressed={theme === th.id}
380
+ onClick={() => pickTheme(th.id)}
381
+ >
382
+ <span className="flex items-center overflow-hidden rounded-md" style={{ border: "0.5px solid var(--dsw-border-l3)", width: 42, height: 20 }}>
383
+ <span style={{ background: th.light, flex: 1, height: "100%" }} />
384
+ <span style={{ background: th.dark, flex: 1, height: "100%" }} />
385
+ </span>
386
+ <span className="flex items-center gap-1.5">
387
+ <span className="inline-block rounded-full" style={{ width: 8, height: 8, background: th.accent }} />
388
+ {th.label}
389
+ </span>
390
+ </button>
391
+ ))}
426
392
  </div>
427
393
  </RowColumn>
428
394
  <RowColumn title={t.appearanceMode}>
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">
395
+ <div className="flex w-full flex-wrap gap-2">
430
396
  {([
431
397
  { id: "light", label: t.themeLight },
432
398
  { id: "dark", label: t.themeDark },
@@ -434,12 +400,17 @@ function GeneralSection({
434
400
  ] as { id: ThemeMode; label: string }[]).map((mode) => (
435
401
  <button
436
402
  key={mode.id}
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
- }`}
403
+ className="flex flex-col items-center justify-center gap-1 transition-colors"
404
+ style={{
405
+ flex: "1 1 120px",
406
+ padding: "16px 24px",
407
+ borderRadius: 20,
408
+ border: `0.5px solid ${themeMode === mode.id ? "var(--dsw-border-strong)" : "var(--dsw-border-l4)"}`,
409
+ background: themeMode === mode.id ? "var(--dsw-module-platform)" : "transparent",
410
+ color: "var(--dsw-label-primary)",
411
+ fontSize: 14,
412
+ lineHeight: "22px",
413
+ }}
443
414
  aria-pressed={themeMode === mode.id}
444
415
  onClick={() => pickThemeMode(mode.id)}
445
416
  >
@@ -466,7 +437,7 @@ function GeneralSection({
466
437
  </Row>
467
438
  <Row title={t.autoCompact} desc={t.autoCompactDesc}>
468
439
  <div className="flex items-center gap-2">
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 } })}>
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 } })}>
470
441
  <span className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all" style={{ left: piSettings?.compaction.enabled ? 18 : 2 }} />
471
442
  </button>
472
443
  {numberInput("compaction", "reserveTokens", t.reserveTokens)}
@@ -475,7 +446,7 @@ function GeneralSection({
475
446
  </Row>
476
447
  <Row title={t.autoRetry} desc={t.autoRetryDesc}>
477
448
  <div className="flex items-center gap-2">
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 } })}>
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 } })}>
479
450
  <span className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all" style={{ left: piSettings?.retry.enabled ? 18 : 2 }} />
480
451
  </button>
481
452
  {numberInput("retry", "maxRetries", t.maxRetries)}
@@ -508,24 +479,12 @@ function GeneralSection({
508
479
  </Row>
509
480
  {webAuth?.enabled ? (
510
481
  <Row title={t.webAuth} desc={t.webAuthOn}>
511
- {logoutError && <span role="alert" style={{ color: "var(--dsw-danger)", fontSize: 12 }}>{t.logoutFailed}</span>}
512
482
  <button
513
483
  className="btn-outline"
514
484
  style={{ height: 30, padding: "0 14px", fontSize: 12.5 }}
515
- disabled={logoutPending}
516
485
  onClick={async () => {
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
- }
486
+ await fetch("/api/web-auth", { method: "DELETE" }).catch(() => {});
487
+ window.location.href = "/login";
529
488
  }}
530
489
  >
531
490
  {t.logout}
@@ -736,7 +695,7 @@ function SelectOption({
736
695
 
737
696
  function Row({ title, desc, children }: { title: string; desc?: string; children: React.ReactNode }) {
738
697
  return (
739
- <div className="settings-row hairline-b flex items-center justify-between gap-6 pb-5">
698
+ <div className="hairline-b flex items-center justify-between gap-6 pb-5">
740
699
  <div className="min-w-0">
741
700
  <div style={{ fontSize: 14, fontWeight: 400 }}>{title}</div>
742
701
  {desc && (
@@ -745,7 +704,7 @@ function Row({ title, desc, children }: { title: string; desc?: string; children
745
704
  </div>
746
705
  )}
747
706
  </div>
748
- <div className="settings-row-control flex-none">{children}</div>
707
+ <div className="flex-none">{children}</div>
749
708
  </div>
750
709
  );
751
710
  }
@@ -770,10 +729,10 @@ function RowColumn({ title, desc, children }: { title: string; desc?: string; ch
770
729
  /** 字号步进器:− 值 +(越界禁用;点中间数值回到 14 默认) */
771
730
  function FontSizeStepper({ value, onChange, max }: { value: number; onChange: (v: number) => void; max: number }) {
772
731
  return (
773
- <div className="clay-inset flex items-center gap-1.5 p-1 rounded-2xl bg-slate-100 dark:bg-slate-900">
732
+ <div className="flex items-center gap-2">
774
733
  <button
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"
734
+ className="icon-btn"
735
+ style={{ width: 26, height: 26, border: "0.5px solid var(--dsw-border-l3)", borderRadius: 8 }}
777
736
  disabled={value <= 12}
778
737
  onClick={() => onChange(value - 1)}
779
738
  aria-label="decrease font size"
@@ -781,16 +740,15 @@ function FontSizeStepper({ value, onChange, max }: { value: number; onChange: (v
781
740
  −
782
741
  </button>
783
742
  <button
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"
743
+ style={{ minWidth: 48, textAlign: "center", fontSize: 13, color: value === 14 ? "var(--dsw-label-tertiary)" : "var(--dsw-label-primary)" }}
744
+ title="reset"
787
745
  onClick={() => onChange(14)}
788
746
  >
789
747
  {value} px
790
748
  </button>
791
749
  <button
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"
750
+ className="icon-btn"
751
+ style={{ width: 26, height: 26, border: "0.5px solid var(--dsw-border-l3)", borderRadius: 8 }}
794
752
  disabled={value >= max}
795
753
  onClick={() => onChange(value + 1)}
796
754
  aria-label="increase font size"
@@ -1245,19 +1203,13 @@ function ModelsSection() {
1245
1203
 
1246
1204
  return (
1247
1205
  <div className="flex flex-col gap-5">
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} />
1206
+ <div>
1207
+ <div style={{ fontSize: 14, fontWeight: 400 }}>{t.modelsTitle}</div>
1208
+ <div className="mt-0.5" style={{ fontSize: 12.5, color: "var(--dsw-label-caption)" }}>
1209
+ {t.modelsDesc}
1252
1210
  </div>
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>
1211
+ <div className="mt-1" style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>
1212
+ {t.customModelIdentityNotice}
1261
1213
  </div>
1262
1214
  </div>
1263
1215
 
@@ -1281,8 +1233,8 @@ function ModelsSection() {
1281
1233
  const editingThis = editing === p.id;
1282
1234
  return (
1283
1235
  <div key={p.id}>
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)" }}>
1236
+ {/* 卡片:身份/操作行在上,额度显示放在同一个框内的第二段(分隔线区分) */}
1237
+ <div className="flex flex-col rounded-2xl px-4 py-3" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
1286
1238
  <div className="flex items-center gap-3">
1287
1239
  <ProviderBrand id={p.id} name={providerName(p.id)} size={34} />
1288
1240
  <div className="min-w-0 flex-1">
@@ -1500,39 +1452,30 @@ function ModelsSection() {
1500
1452
  })}
1501
1453
  </div>
1502
1454
 
1503
- {/* 两个添加按钮: Large Tactile Extruded Clay Buttons (Screen 6) */}
1504
- <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 pt-1">
1455
+ {/* 两个添加按钮 */}
1456
+ <div className="flex gap-3">
1505
1457
  <button
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"
1458
+ className="flex flex-1 items-center justify-center gap-2 rounded-2xl px-4 py-3.5 transition-colors"
1459
+ style={{ border: "0.5px solid var(--dsw-border-l3)", fontSize: 14 }}
1460
+ onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
1461
+ onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
1508
1462
  onClick={() => setAdding("builtin")}
1509
1463
  >
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>
1464
+ <IconPlusOutline16 size={15} />
1465
+ {t.addProvider}
1514
1466
  </button>
1515
1467
  <button
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"
1468
+ className="flex flex-1 items-center justify-center gap-2 rounded-2xl px-4 py-3.5 transition-colors"
1469
+ style={{ border: "0.5px solid var(--dsw-border-l3)", fontSize: 14 }}
1470
+ onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
1471
+ onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
1518
1472
  onClick={() => setAdding("custom")}
1519
1473
  >
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>
1474
+ <IconPlusOutline16 size={15} />
1475
+ {t.addCustomProvider}
1524
1476
  </button>
1525
1477
  </div>
1526
1478
 
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
-
1536
1479
  {adding ? (
1537
1480
  <ProviderSetupModal
1538
1481
  mode={adding}
@@ -1743,115 +1686,59 @@ function ToolsSection({
1743
1686
  return (
1744
1687
  <div className="flex flex-col gap-5">
1745
1688
  <div>
1746
- <div style={{ fontSize: 16, fontWeight: 600, color: "var(--dsw-label-primary)" }}>{t.toolsSection}</div>
1689
+ <div style={{ fontSize: 14, fontWeight: 400 }}>{t.toolsSection}</div>
1747
1690
  <div className="mt-0.5" style={{ fontSize: 12.5, color: "var(--dsw-label-caption)" }}>
1748
- 当前会话生效的工具与权限预设;主会话不提供系统级沙盒隔离
1749
- </div>
1750
- </div>
1751
-
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>
1691
+ {t.toolsSectionDesc}
1797
1692
  </div>
1798
1693
  </div>
1799
1694
 
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>
1695
+ <Row title={t.securityToolPreset} desc={t.securityToolPresetDesc}>
1696
+ <SelectOption
1697
+ value={toolPreset}
1698
+ options={[
1699
+ { value: "readonly", label: t.toolReadonly },
1700
+ { value: "standard", label: t.toolStandard },
1701
+ { value: "full", label: t.toolFull },
1702
+ ]}
1703
+ onChange={(value) => onToolPresetChange(value as ToolPreset)}
1704
+ />
1705
+ </Row>
1811
1706
 
1812
1707
  {all.length === 0 ? (
1813
- <div className="clay-card rounded-2xl p-6 text-center text-xs text-slate-400">
1708
+ <div className="rounded-2xl p-4" style={{ border: "0.5px dashed var(--dsw-border-l3)", fontSize: 12.5, color: "var(--dsw-label-caption)" }}>
1814
1709
  {t.toolsNoSession}
1815
1710
  </div>
1816
1711
  ) : (
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>
1712
+ <div className="flex flex-col gap-2.5">
1713
+ <div className="flex items-center gap-2" style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>
1714
+ <span style={{ fontWeight: 600 }}>{t.toolsCurrentSession}</span>
1820
1715
  <span>{t.toolsActiveCount.replace("{n}", String(active.size)).replace("{total}", String(all.length))}</span>
1821
1716
  </div>
1822
1717
  {all.map((tool) => {
1823
1718
  const on = active.has(tool.name);
1824
1719
  const desc = tool.description || describe(tool.name);
1825
1720
  return (
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>
1721
+ <div key={tool.name} className="flex items-center gap-3 rounded-2xl px-4 py-2.5" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
1722
+ <div className="min-w-0 flex-1">
1723
+ <div style={{ fontSize: 13.5, fontWeight: 500, fontFamily: "var(--font-mono)" }}>{tool.name}</div>
1833
1724
  {desc && (
1834
- <div className="truncate text-xs text-slate-500 mt-1">
1725
+ <div className="truncate" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
1835
1726
  {desc}
1836
1727
  </div>
1837
1728
  )}
1838
1729
  </div>
1839
1730
  <button
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
- }`}
1731
+ className="relative h-5 w-9 flex-none rounded-full transition-colors"
1732
+ style={{ background: on ? "var(--dsw-accent)" : "var(--dsw-border-l3)" }}
1844
1733
  role="switch"
1845
1734
  aria-checked={on}
1846
1735
  aria-label={tool.name}
1847
1736
  disabled={!onSetTools || (on && active.size <= 1)}
1848
1737
  onClick={() => toggle(tool.name)}
1849
1738
  >
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>
1739
+ <span className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all" style={{ left: on ? 18 : 2 }} />
1853
1740
  </button>
1854
- </div>
1741
+ </div>
1855
1742
  );
1856
1743
  })}
1857
1744
  </div>
@@ -1873,54 +1760,49 @@ interface SkillRow {
1873
1760
  function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileContent?: (path: string, content: string) => void }) {
1874
1761
  const { t } = useI18n();
1875
1762
  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);
1879
1763
 
1880
1764
  const load = useCallback(async () => {
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]);
1765
+ const r = await fetch(`/api/skills${cwd ? `?cwd=${encodeURIComponent(cwd)}` : ""}`);
1766
+ const j = await r.json();
1767
+ if (j.success) setSkills(j.data.skills);
1768
+ }, [cwd]);
1894
1769
 
1895
1770
  useEffect(() => {
1896
1771
  void load();
1897
1772
  }, [load]);
1898
1773
 
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
- }
1774
+ const toggle = async (s: SkillRow) => {
1775
+ const r = await fetch("/api/skills", {
1776
+ method: "POST",
1777
+ headers: { "Content-Type": "application/json" },
1778
+ body: JSON.stringify({ action: "toggle", filePath: s.filePath, disabled: !s.disabled, cwd }),
1779
+ });
1780
+ if ((await r.json()).success) void load();
1918
1781
  };
1919
1782
 
1920
1783
  // 查看技能文档:弹大窗口查看器(与项目文件查看器同款,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);
1921
1795
  const remove = async (s: SkillRow) => {
1922
1796
  if (!window.confirm(t.confirmDeleteSkill.replace("{name}", s.name))) return;
1923
- await action(s, "delete");
1797
+ setDeleteError(null);
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);
1924
1806
  };
1925
1807
 
1926
1808
  const scopeLabel = (s: SkillRow["scope"]) => (s === "global" ? t.scopeGlobal : s === "project" ? t.scopeProject : t.scopePackage);
@@ -1928,15 +1810,13 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
1928
1810
  return (
1929
1811
  <div className="flex flex-col gap-4">
1930
1812
  <div style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>{t.skillsDesc}</div>
1931
- {error && (
1813
+ {deleteError && (
1932
1814
  <div role="alert" className="rounded-lg px-3 py-2" style={{ fontSize: 12.5, background: "var(--dsw-hover)", color: "var(--dsw-danger)" }}>
1933
- {error} <button type="button" className="underline" onClick={() => void load()}>{t.settingsRetry}</button>
1815
+ {deleteError}
1934
1816
  </div>
1935
1817
  )}
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>
1818
+ {skills.length === 0 && (
1819
+ <div style={{ fontSize: 13, color: "var(--dsw-label-caption)" }}>—</div>
1940
1820
  )}
1941
1821
  <div className="flex flex-col gap-2">
1942
1822
  {skills.map((s) => (
@@ -1959,8 +1839,7 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
1959
1839
  <button
1960
1840
  className="rounded-lg px-2.5 py-1"
1961
1841
  style={{ fontSize: 11.5, color: "var(--dsw-label-tertiary)" }}
1962
- disabled={busyFile !== null}
1963
- onClick={() => void action(s, "read")}
1842
+ onClick={() => view(s)}
1964
1843
  >
1965
1844
  {t.viewSkillFile}
1966
1845
  </button>
@@ -1969,7 +1848,6 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
1969
1848
  className="rounded-lg px-2.5 py-1"
1970
1849
  style={{ fontSize: 11.5, color: "var(--dsw-danger)" }}
1971
1850
  title={t.deleteSkillHint}
1972
- disabled={busyFile !== null}
1973
1851
  onClick={() => void remove(s)}
1974
1852
  >
1975
1853
  {t.delete}
@@ -1984,10 +1862,8 @@ function SkillsSection({ cwd, onOpenFileContent }: { cwd: string; onOpenFileCont
1984
1862
  className="relative h-5 w-9 flex-none rounded-full transition-colors"
1985
1863
  style={{ background: s.disabled ? "var(--dsw-border-l3)" : "var(--dsw-accent)" }}
1986
1864
  role="switch"
1987
- aria-label={s.name}
1988
1865
  aria-checked={!s.disabled}
1989
- disabled={busyFile !== null}
1990
- onClick={() => void action(s, "toggle")}
1866
+ onClick={() => toggle(s)}
1991
1867
  >
1992
1868
  <span
1993
1869
  className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all"
@@ -2017,7 +1893,6 @@ function PluginsSection({ cwd }: { cwd: string }) {
2017
1893
  const [busy, setBusy] = useState(false);
2018
1894
  const [busyKey, setBusyKey] = useState<string | null>(null);
2019
1895
  const [toast, setToast] = useState<{ ok: boolean; msg: string; loading?: boolean } | null>(null);
2020
- const [loadError, setLoadError] = useState<string | null>(null);
2021
1896
  const [menuKey, setMenuKey] = useState<string | null>(null);
2022
1897
  const menuRef = useRef<HTMLDivElement | null>(null);
2023
1898
 
@@ -2031,17 +1906,13 @@ function PluginsSection({ cwd }: { cwd: string }) {
2031
1906
  }, []);
2032
1907
 
2033
1908
  const load = useCallback(async () => {
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);
1909
+ const r = await fetch(`/api/plugins${cwd ? `?cwd=${encodeURIComponent(cwd)}` : ""}`);
1910
+ const j = await r.json();
1911
+ if (j.success) {
2038
1912
  setPackages(j.data.packages);
2039
1913
  setExtensions(j.data.extensions);
2040
- setLoadError(null);
2041
- } catch (error) {
2042
- setLoadError(error instanceof Error ? error.message : t.toastError);
2043
1914
  }
2044
- }, [cwd, t.toastError]);
1915
+ }, [cwd]);
2045
1916
 
2046
1917
  useEffect(() => {
2047
1918
  void load();
@@ -2141,11 +2012,6 @@ function PluginsSection({ cwd }: { cwd: string }) {
2141
2012
  <div className="mt-1" style={{ fontSize: 13, color: "var(--dsw-label-tertiary)" }}>
2142
2013
  {t.pluginPageDesc}
2143
2014
  </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
- )}
2149
2015
  </div>
2150
2016
 
2151
2017
  {toast && (
@@ -2226,11 +2092,8 @@ function PluginsSection({ cwd }: { cwd: string }) {
2226
2092
  try {
2227
2093
  const r = await fetch("/api/plugins", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "reload", cwd }) });
2228
2094
  const j = await r.json();
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);
2095
+ notify(j.success, j.success ? t.reloadedSessions.replace("{loaders}", String(j.data?.loaders ?? 0)).replace("{sessions}", String(j.data?.sessions ?? 0)) : j.error);
2096
+ if (j.success) await load();
2234
2097
  } finally {
2235
2098
  setBusy(false);
2236
2099
  }