@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.
Files changed (230) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +9 -6
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +6 -6
  5. package/.next/react-loadable-manifest.json +16 -4
  6. package/.next/routes-manifest.json +20 -0
  7. package/.next/server/app/_global-error/page.js +2 -2
  8. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_global-error.html +1 -1
  10. package/.next/server/app/_global-error.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page.js +1 -1
  15. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/_not-found.html +1 -1
  17. package/.next/server/app/_not-found.rsc +7 -4
  18. package/.next/server/app/_not-found.segments/_full.segment.rsc +7 -4
  19. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -2
  20. package/.next/server/app/_not-found.segments/_tree.segment.rsc +5 -2
  21. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  22. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  23. package/.next/server/app/api/agent/[id]/route.js +1 -1
  24. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  25. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/new/route.js +1 -1
  28. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  29. package/.next/server/app/api/config/route.js +1 -1
  30. package/.next/server/app/api/config/route.js.nft.json +1 -1
  31. package/.next/server/app/api/files/route.js +1 -1
  32. package/.next/server/app/api/files/route.js.nft.json +1 -1
  33. package/.next/server/app/api/git/route.js +2 -2
  34. package/.next/server/app/api/git/route.js.nft.json +1 -1
  35. package/.next/server/app/api/growth/route.js +1 -1
  36. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  37. package/.next/server/app/api/health/route.js +1 -1
  38. package/.next/server/app/api/models/route.js +1 -1
  39. package/.next/server/app/api/models/route.js.nft.json +1 -1
  40. package/.next/server/app/api/pi-settings/route.js +1 -1
  41. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  42. package/.next/server/app/api/plugins/route.js +1 -1
  43. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  44. package/.next/server/app/api/prompts/route.js +1 -1
  45. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  46. package/.next/server/app/api/security/route.js +1 -1
  47. package/.next/server/app/api/security/route.js.nft.json +1 -1
  48. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  49. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  50. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  51. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  52. package/.next/server/app/api/sessions/import/route.js +3 -3
  53. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  54. package/.next/server/app/api/sessions/route.js +1 -1
  55. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  56. package/.next/server/app/api/skills/route.js +2 -2
  57. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  58. package/.next/server/app/api/swarm/[id]/route.js +1 -0
  59. package/.next/server/app/api/swarm/[id]/route.js.nft.json +1 -0
  60. package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +1 -0
  61. package/.next/server/app/api/swarm/route.js +1 -0
  62. package/.next/server/app/api/swarm/route.js.nft.json +1 -0
  63. package/.next/server/app/api/swarm/route_client-reference-manifest.js +1 -0
  64. package/.next/server/app/api/tests/route.js +2 -0
  65. package/.next/server/app/api/tests/route.js.nft.json +1 -0
  66. package/.next/server/app/api/tests/route_client-reference-manifest.js +1 -0
  67. package/.next/server/app/api/update/route.js +2 -2
  68. package/.next/server/app/api/update/route.js.nft.json +1 -1
  69. package/.next/server/app/api/version/route.js +1 -1
  70. package/.next/server/app/api/version/route.js.nft.json +1 -1
  71. package/.next/server/app/api/web-auth/route.js +1 -1
  72. package/.next/server/app/api/workspaces/route.js +1 -1
  73. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  74. package/.next/server/app/icon.svg/route.js +1 -1
  75. package/.next/server/app/index.html +1 -1
  76. package/.next/server/app/index.rsc +9 -6
  77. package/.next/server/app/index.segments/__PAGE__.segment.rsc +7 -4
  78. package/.next/server/app/index.segments/_full.segment.rsc +9 -6
  79. package/.next/server/app/index.segments/_tree.segment.rsc +5 -2
  80. package/.next/server/app/login/page.js +1 -1
  81. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app/login.html +1 -1
  83. package/.next/server/app/login.rsc +8 -5
  84. package/.next/server/app/login.segments/_full.segment.rsc +8 -5
  85. package/.next/server/app/login.segments/_tree.segment.rsc +5 -2
  86. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +6 -3
  87. package/.next/server/app/page.js +1 -1
  88. package/.next/server/app/page_client-reference-manifest.js +1 -1
  89. package/.next/server/app-paths-manifest.json +9 -6
  90. package/.next/server/chunks/496.js +1 -1
  91. package/.next/server/chunks/532.js +1 -1
  92. package/.next/server/chunks/686.js +6 -2
  93. package/.next/server/chunks/757.js +1 -0
  94. package/.next/server/chunks/775.js +3 -0
  95. package/.next/server/middleware-build-manifest.js +1 -1
  96. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  97. package/.next/server/pages/404.html +1 -1
  98. package/.next/server/pages/500.html +1 -1
  99. package/.next/server/server-reference-manifest.json +1 -1
  100. package/.next/static/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +1 -0
  101. package/.next/static/chunks/2608.43c58236c9cce8e6.js +1 -0
  102. package/.next/static/chunks/{2716.a9e808190a4e2424.js → 2716.4ec42df1d51354c9.js} +1 -1
  103. package/.next/static/chunks/4632.62139b565b657aac.js +1 -0
  104. package/.next/static/chunks/{4673.d3d0a7f2599080fb.js → 4673.7796104395deb8d7.js} +1 -1
  105. package/.next/static/chunks/4686-17f21a006739a596.js +1 -0
  106. package/.next/static/chunks/7577.10af9bb661652da8.js +1 -0
  107. package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +1 -0
  108. package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +1 -0
  109. package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +1 -0
  110. package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +1 -0
  111. package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +1 -0
  112. package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +1 -0
  113. package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +1 -0
  114. package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +1 -0
  115. package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +1 -0
  116. package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +1 -0
  117. package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +1 -0
  118. package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +1 -0
  119. package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +1 -0
  120. package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +1 -0
  121. package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +1 -0
  122. package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +1 -0
  123. package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +1 -0
  124. package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +1 -0
  125. package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +1 -0
  126. package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +1 -0
  127. package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +1 -0
  128. package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +1 -0
  129. package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +1 -0
  130. package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +1 -0
  131. package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +1 -0
  132. package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +1 -0
  133. package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +1 -0
  134. package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +1 -0
  135. package/.next/static/chunks/app/page-14379b8060d5cf07.js +1 -0
  136. package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +1 -0
  137. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +1 -0
  138. package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +1 -0
  139. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +1 -0
  140. package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +1 -0
  141. package/.next/static/css/d0a0162cb481abe8.css +1 -0
  142. package/.next/types/app/api/swarm/[id]/route.ts +353 -0
  143. package/.next/types/app/api/swarm/route.ts +353 -0
  144. package/.next/types/app/api/tests/route.ts +353 -0
  145. package/.next/types/routes.d.ts +4 -1
  146. package/.next/types/validator.ts +27 -0
  147. package/README.md +10 -0
  148. package/package.json +4 -3
  149. package/scripts/pick-folder.ps1 +11 -2
  150. package/scripts/release.mjs +11 -31
  151. package/scripts/run-test-file.mjs +34 -0
  152. package/scripts/verify-release-config.mjs +24 -0
  153. package/src/app/api/config/route.ts +4 -1
  154. package/src/app/api/security/route.ts +8 -3
  155. package/src/app/api/swarm/[id]/route.ts +28 -0
  156. package/src/app/api/swarm/route.ts +24 -0
  157. package/src/app/api/tests/route.ts +27 -0
  158. package/src/app/api/update/route.ts +8 -2
  159. package/src/app/globals.css +777 -0
  160. package/src/app/layout.tsx +13 -6
  161. package/src/components/AppShell.tsx +505 -199
  162. package/src/components/ChatInput.tsx +153 -26
  163. package/src/components/ChatWindow.tsx +46 -40
  164. package/src/components/ContextMeter.tsx +11 -17
  165. package/src/components/FileViewer.tsx +1 -1
  166. package/src/components/GitPanel.tsx +13 -11
  167. package/src/components/ModelSelector.tsx +19 -6
  168. package/src/components/PiMark.tsx +5 -38
  169. package/src/components/ProjectPanel.tsx +1 -0
  170. package/src/components/PromptPanel.tsx +132 -51
  171. package/src/components/SessionImportSection.tsx +17 -17
  172. package/src/components/SessionSidebar.tsx +159 -57
  173. package/src/components/SettingsPanel.tsx +330 -193
  174. package/src/components/SwarmCoordinatorPod.tsx +1021 -0
  175. package/src/components/TestInspectorPod.tsx +549 -0
  176. package/src/components/icons.tsx +23 -0
  177. package/src/hooks/usePiWeb.ts +112 -8
  178. package/src/i18n.tsx +59 -3
  179. package/src/lib/agent-manager.ts +180 -12
  180. package/src/lib/command-validation.ts +9 -1
  181. package/src/lib/delegation.ts +93 -0
  182. package/src/lib/managed-niubash.ts +8 -4
  183. package/src/lib/pi.ts +3 -2
  184. package/src/lib/runtime-activity.ts +27 -0
  185. package/src/lib/session-import/index.ts +5 -0
  186. package/src/lib/session-import/lock.ts +25 -0
  187. package/src/lib/session-import/writer.ts +8 -3
  188. package/src/lib/skills-service.ts +16 -9
  189. package/src/lib/swarm-service.ts +371 -0
  190. package/src/lib/test-run-service.ts +122 -0
  191. package/src/lib/theme.ts +68 -1
  192. package/src/lib/trajectory.ts +1 -0
  193. package/src/lib/types.ts +14 -0
  194. package/src/lib/workflow.ts +55 -0
  195. package/.next/static/DdBwPYoHBKtaJ97QIr6Co/_buildManifest.js +0 -1
  196. package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
  197. package/.next/static/chunks/4632.0ed92ae42905ce40.js +0 -1
  198. package/.next/static/chunks/4686-7d694def920ff093.js +0 -1
  199. package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +0 -1
  200. package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +0 -1
  201. package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +0 -1
  202. package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +0 -1
  203. package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +0 -1
  204. package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +0 -1
  205. package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +0 -1
  206. package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +0 -1
  207. package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +0 -1
  208. package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +0 -1
  209. package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +0 -1
  210. package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +0 -1
  211. package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +0 -1
  212. package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +0 -1
  213. package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +0 -1
  214. package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +0 -1
  215. package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +0 -1
  216. package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +0 -1
  217. package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +0 -1
  218. package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +0 -1
  219. package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +0 -1
  220. package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +0 -1
  221. package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +0 -1
  222. package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +0 -1
  223. package/.next/static/chunks/app/page-9a5440047c5a415c.js +0 -1
  224. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +0 -1
  225. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +0 -1
  226. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +0 -1
  227. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +0 -1
  228. package/.next/static/chunks/webpack-42c5aae79b0e5d8c.js +0 -1
  229. package/.next/static/css/b2d35abc828d4713.css +0 -1
  230. /package/.next/static/{DdBwPYoHBKtaJ97QIr6Co → 0t1jBUAuVaTFTXxRyIo3q}/_ssgManifest.js +0 -0
@@ -34,46 +34,13 @@ export function PiMark({
34
34
  /** 品牌簇:对齐 dsh 品牌模板 [Logo] [Name] [BADGE] */
35
35
  export function BrandPi({ compact = false }: { compact?: boolean }) {
36
36
  if (compact) {
37
- return <PiMark size={18} />;
37
+ return <PiMark size={18} style={{ color: "var(--dsw-accent)" }} />;
38
38
  }
39
39
  return (
40
- <div
41
- className="inline-flex items-center select-none"
42
- style={{
43
- color: "var(--dsw-label-primary)",
44
- gap: 6,
45
- }}
46
- >
47
- <PiMark size={17} />
48
- <span
49
- className="inline-flex items-center"
50
- style={{
51
- fontSize: 16.5,
52
- fontWeight: 600,
53
- letterSpacing: "-0.02em",
54
- color: "var(--dsw-label-primary)",
55
- lineHeight: 1,
56
- }}
57
- >
58
- pi
59
- </span>
60
- <span
61
- className="inline-flex items-center justify-center font-bold"
62
- style={{
63
- fontSize: 9.5,
64
- letterSpacing: "0.06em",
65
- height: 15,
66
- paddingLeft: 4.5,
67
- paddingRight: 4.5,
68
- borderRadius: 4,
69
- background: "var(--dsw-label-primary)",
70
- color: "var(--dsw-bg-base)",
71
- lineHeight: 1,
72
- textTransform: "uppercase",
73
- }}
74
- >
75
- HARNESS
76
- </span>
40
+ <div className="pw-brand select-none">
41
+ <span className="pw-brand-symbol"><PiMark size={18} style={{ color: "var(--dsw-accent)" }} /></span>
42
+ <span className="pw-brand-name">piweb</span>
43
+ <span className="pw-brand-meta">CODE WORKBENCH</span>
77
44
  </div>
78
45
  );
79
46
  }
@@ -223,6 +223,7 @@ export function ProjectPanel({
223
223
  <input
224
224
  value={filter}
225
225
  onChange={(e) => setFilter(e.target.value)}
226
+ aria-label={t.growthFilter}
226
227
  placeholder={t.growthFilter}
227
228
  className="min-w-0 flex-1 bg-transparent"
228
229
  style={{ fontSize: 12 }}
@@ -6,7 +6,7 @@
6
6
  * 技能 / 提示模板,含扩展与 npm 包提供的那些);热会话额外给出"组装后的系统提示词"
7
7
  * 与每个工具的定义,它们不是文件、没有路径,直接在本面板内展开查看。
8
8
  */
9
- import { useCallback, useEffect, useState } from "react";
9
+ import { useCallback, useEffect, useRef, useState } from "react";
10
10
  import { useI18n } from "@/i18n";
11
11
  import { IconRefreshOutline14, IconCloseOutline14 } from "@/components/icons";
12
12
 
@@ -67,30 +67,46 @@ export function PromptPanel({
67
67
  onClose: () => void;
68
68
  }) {
69
69
  const { t } = useI18n();
70
- const [data, setData] = useState<PromptSourcesData | null>(null);
70
+ const owner = JSON.stringify([cwd, sessionId]);
71
+ const [catalog, setCatalog] = useState<{ owner: string; data: PromptSourcesData } | null>(null);
72
+ const data = catalog?.owner === owner ? catalog.data : null;
71
73
  const [error, setError] = useState<string | null>(null);
72
74
  const [busy, setBusy] = useState(false);
73
75
  const [opening, setOpening] = useState<string | null>(null);
76
+ const listRequest = useRef<AbortController | null>(null);
77
+ const readRequest = useRef<AbortController | null>(null);
74
78
 
75
79
  const load = useCallback(async () => {
80
+ listRequest.current?.abort();
81
+ readRequest.current?.abort();
82
+ const controller = new AbortController();
83
+ listRequest.current = controller;
84
+ const current = () => listRequest.current === controller && !controller.signal.aborted;
85
+ setCatalog(null);
86
+ setOpening(null);
76
87
  setBusy(true);
77
88
  setError(null);
78
89
  try {
79
90
  const query = new URLSearchParams({ cwd });
80
91
  if (sessionId) query.set("session", sessionId);
81
- const response = await fetch(`/api/prompts?${query.toString()}`);
92
+ const response = await fetch(`/api/prompts?${query.toString()}`, { signal: controller.signal });
82
93
  const body = await response.json();
94
+ if (!current()) return;
83
95
  if (!response.ok || !body.success) throw new Error(body.error || `request failed (${response.status})`);
84
- setData(body.data as PromptSourcesData);
96
+ setCatalog({ owner, data: body.data as PromptSourcesData });
85
97
  } catch (err) {
86
- setError(err instanceof Error ? err.message : String(err));
98
+ if (current()) setError(err instanceof Error ? err.message : String(err));
87
99
  } finally {
88
- setBusy(false);
100
+ if (current()) setBusy(false);
89
101
  }
90
- }, [cwd, sessionId]);
102
+ }, [cwd, sessionId, owner]);
91
103
 
92
104
  useEffect(() => {
93
105
  void load();
106
+ return () => {
107
+ listRequest.current?.abort();
108
+ readRequest.current?.abort();
109
+ };
94
110
  }, [load, refreshKey]);
95
111
 
96
112
  const originLabel = (origin: PromptSourceOrigin): string => {
@@ -115,21 +131,28 @@ export function PromptPanel({
115
131
  /** 文件类:按需读内容,交给外部查看器 */
116
132
  const openFile = async (source: PromptSource) => {
117
133
  if (!source.path || !onOpenContent) return;
134
+ readRequest.current?.abort();
135
+ const controller = new AbortController();
136
+ const catalogRequest = listRequest.current;
137
+ readRequest.current = controller;
138
+ const current = () => readRequest.current === controller && !controller.signal.aborted && listRequest.current === catalogRequest;
118
139
  setOpening(source.path);
119
140
  setError(null);
120
141
  try {
121
142
  const response = await fetch("/api/prompts", {
122
143
  method: "POST",
144
+ signal: controller.signal,
123
145
  headers: { "Content-Type": "application/json" },
124
146
  body: JSON.stringify({ action: "read", filePath: source.path, cwd }),
125
147
  });
126
148
  const body = await response.json();
149
+ if (!current()) return;
127
150
  if (!response.ok || !body.success) throw new Error(body.error || "read failed");
128
151
  onOpenContent(source.path, body.data.content as string);
129
152
  } catch (err) {
130
- setError(err instanceof Error ? err.message : String(err));
153
+ if (current()) setError(err instanceof Error ? err.message : String(err));
131
154
  } finally {
132
- setOpening(null);
155
+ if (current()) setOpening(null);
133
156
  }
134
157
  };
135
158
 
@@ -138,50 +161,94 @@ export function PromptPanel({
138
161
  );
139
162
  const hasTools = (data?.tools?.length ?? 0) > 0;
140
163
 
164
+ const fileExtBadge = (name: string): string => {
165
+ const ext = name.split(".").pop()?.toUpperCase() ?? "DOC";
166
+ return ext.length <= 4 ? ext : "DOC";
167
+ };
168
+
141
169
  return (
142
- <div className="flex min-h-0 flex-1 flex-col">
143
- <div className="hairline-b flex items-center gap-2 px-4 py-2.5">
144
- <span className="min-w-0 flex-1 truncate" style={{ fontSize: 13.5, fontWeight: 600 }}>{t.promptPanel}</span>
145
- <button type="button" className="icon-btn" style={{ width: 26, height: 26 }} title={t.promptRefresh} aria-label={t.promptRefresh} onClick={() => void load()}>
146
- <span className={busy ? "piweb-spin" : undefined} style={{ display: "inline-flex" }}>
147
- <IconRefreshOutline14 size={14} />
148
- </span>
149
- </button>
150
- <button type="button" className="icon-btn" style={{ width: 26, height: 26 }} title={t.close} aria-label={t.close} onClick={onClose}>
151
- <IconCloseOutline14 size={14} />
152
- </button>
170
+ <div className="flex min-h-0 flex-1 flex-col p-2 select-none">
171
+ {/* Right Header (Screen 2 Claymorphism) */}
172
+ <div className="flex items-center justify-between pb-3 mb-3 border-b border-slate-200 dark:border-slate-800 px-2">
173
+ <div className="flex items-center gap-2">
174
+ <div className="w-2.5 h-2.5 rounded-full bg-sky-500 animate-pulse" />
175
+ <h2 className="text-sm font-bold text-slate-900 dark:text-slate-100 tracking-tight">{t.promptPanel}</h2>
176
+ </div>
177
+ <div className="flex items-center gap-1.5 text-slate-500">
178
+ <button
179
+ type="button"
180
+ className="w-7 h-7 clay-btn bg-white dark:bg-slate-800 text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white transition-colors shadow-sm"
181
+ title={t.promptRefresh}
182
+ aria-label={t.promptRefresh}
183
+ onClick={() => void load()}
184
+ >
185
+ <span className={busy ? "piweb-spin" : undefined} style={{ display: "inline-flex" }}>
186
+ <IconRefreshOutline14 size={13} />
187
+ </span>
188
+ </button>
189
+ <button
190
+ type="button"
191
+ className="w-7 h-7 clay-btn bg-white dark:bg-slate-800 text-slate-700 hover:text-rose-600 dark:text-slate-300 transition-colors shadow-sm"
192
+ title={t.close}
193
+ aria-label={t.close}
194
+ onClick={onClose}
195
+ >
196
+ <IconCloseOutline14 size={13} />
197
+ </button>
198
+ </div>
153
199
  </div>
154
- <div className="min-h-0 flex-1 overflow-auto px-3 py-3" style={{ fontSize: 12.5 }}>
200
+
201
+ <div className="min-h-0 flex-1 overflow-y-auto px-1 space-y-4 text-xs">
155
202
  {error && (
156
- <div className="mb-2 rounded-lg px-2 py-1.5" style={{ background: "var(--dsw-hover)", color: "var(--dsw-danger)" }} role="alert">
203
+ <div className="rounded-xl px-3 py-2 bg-rose-50 text-rose-700 dark:bg-rose-950/40 dark:text-rose-300 font-semibold" role="alert">
157
204
  {error}
158
205
  </div>
159
206
  )}
160
- {!data && busy && <div style={{ color: "var(--dsw-label-tertiary)" }}>{t.promptLoading}</div>}
207
+
208
+ {!data && busy && <div className="text-slate-500 py-4 text-center">{t.promptLoading}</div>}
209
+
161
210
  {data && !data.sessionReady && (
162
- <div className="mb-2 rounded-lg px-2 py-1.5" style={{ background: "var(--dsw-hover)", color: "var(--dsw-label-caption)" }} role="status">
211
+ <div className="rounded-xl px-3 py-2.5 clay-inset text-slate-600 dark:text-slate-300 leading-relaxed text-xs" role="status">
163
212
  {t.promptColdHint}
164
213
  </div>
165
214
  )}
166
215
 
167
216
  {groups.map((group) => (
168
- <section key={group.kind} className="mb-3">
169
- <div className="mb-1 px-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{kindLabel(group.kind)}</div>
170
- <div className="flex flex-col gap-1">
217
+ <section key={group.kind} className="space-y-1.5">
218
+ <div className="flex items-center justify-between text-xs font-bold text-slate-800 dark:text-slate-200 px-1 mb-1">
219
+ <span>{kindLabel(group.kind)}</span>
220
+ <span className="text-[10px] text-slate-500 dark:text-slate-400 font-mono font-semibold">{group.items.length} 项</span>
221
+ </div>
222
+ <div className="flex flex-col gap-1.5">
171
223
  {group.items.map((source) => (
172
224
  <button
173
225
  key={`${source.kind}:${source.path ?? source.name}`}
174
226
  type="button"
175
- className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left"
176
- style={{ background: opening === source.path ? "var(--dsw-active)" : "transparent", border: "0.5px solid var(--dsw-border-l2)" }}
227
+ className={`clay-card p-3 flex items-center justify-between hover:scale-[1.01] transition-all cursor-pointer rounded-2xl text-left bg-white dark:bg-slate-900 shadow-sm border border-slate-200 dark:border-slate-800 ${
228
+ opening === source.path ? "ring-2 ring-emerald-500" : ""
229
+ }`}
177
230
  title={source.path ?? source.name}
178
231
  onClick={() => void openFile(source)}
179
232
  >
180
- <span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-primary)" }}>{source.name}</span>
181
- <span className="flex-none" style={{ fontSize: 10.5, color: "var(--dsw-label-tertiary)" }}>{formatBytes(source.bytes)}</span>
182
- <span className="flex-none rounded-md px-1.5 py-0.5" style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}>
183
- {originLabel(source.origin)}
184
- </span>
233
+ <div className="flex items-center gap-2.5 min-w-0 flex-1 mr-2">
234
+ <div className="w-7 h-7 clay-inset bg-emerald-50 text-emerald-700 dark:bg-emerald-950/70 dark:text-emerald-300 flex items-center justify-center rounded-xl font-mono font-bold text-[10px] flex-none">
235
+ {fileExtBadge(source.name)}
236
+ </div>
237
+ <div className="min-w-0 flex-1">
238
+ <div className="text-xs font-bold text-slate-900 dark:text-slate-100 truncate">{source.name}</div>
239
+ <div className="text-[11px] text-slate-600 dark:text-slate-400 truncate">{source.path ?? kindLabel(source.kind)}</div>
240
+ </div>
241
+ </div>
242
+ <div className="text-right flex-none flex flex-col items-end gap-0.5">
243
+ {source.bytes !== undefined && (
244
+ <span className="text-[11px] font-mono font-bold text-slate-800 dark:text-slate-200 block">
245
+ {formatBytes(source.bytes)}
246
+ </span>
247
+ )}
248
+ <span className="text-[10px] px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 font-semibold border border-slate-200 dark:border-slate-700">
249
+ {originLabel(source.origin)}
250
+ </span>
251
+ </div>
185
252
  </button>
186
253
  ))}
187
254
  </div>
@@ -189,15 +256,17 @@ export function PromptPanel({
189
256
  ))}
190
257
 
191
258
  {hasTools && (
192
- <section className="mb-3">
193
- <div className="mb-1 px-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{t.promptKindTool}</div>
194
- <div className="flex flex-col gap-1">
259
+ <section className="space-y-1.5">
260
+ <div className="flex items-center justify-between text-xs font-bold text-slate-800 dark:text-slate-200 px-1 mb-1">
261
+ <span>{t.promptKindTool}</span>
262
+ <span className="text-[10px] text-slate-500 dark:text-slate-400 font-mono font-semibold">{data!.tools!.length} 个工具</span>
263
+ </div>
264
+ <div className="flex flex-col gap-1.5">
195
265
  {data!.tools!.map((tool) => (
196
266
  <button
197
267
  key={`tool:${tool.name}`}
198
268
  type="button"
199
- className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-left"
200
- style={{ border: "0.5px solid var(--dsw-border-l2)" }}
269
+ className="clay-card p-3 flex items-center justify-between hover:scale-[1.01] transition-all cursor-pointer rounded-2xl text-left bg-white dark:bg-slate-900 shadow-sm border border-slate-200 dark:border-slate-800"
201
270
  title={tool.name}
202
271
  onClick={() =>
203
272
  onOpenContent?.(
@@ -214,8 +283,16 @@ export function PromptPanel({
214
283
  )
215
284
  }
216
285
  >
217
- <span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-primary)" }}>{tool.name}</span>
218
- <span className="flex-none rounded-md px-1.5 py-0.5" style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}>
286
+ <div className="flex items-center gap-2.5 min-w-0 flex-1 mr-2">
287
+ <div className="w-7 h-7 clay-inset bg-sky-50 text-sky-700 dark:bg-sky-950/70 dark:text-sky-300 flex items-center justify-center rounded-xl font-mono font-bold text-[10px] flex-none">
288
+ TOOL
289
+ </div>
290
+ <div className="min-w-0 flex-1">
291
+ <div className="text-xs font-bold text-slate-900 dark:text-slate-100 truncate">{tool.name}</div>
292
+ <div className="text-[11px] text-slate-600 dark:text-slate-400 truncate">{tool.description || t.promptKindTool}</div>
293
+ </div>
294
+ </div>
295
+ <span className="text-[10px] px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 font-semibold border border-slate-200 dark:border-slate-700 flex-none">
219
296
  {tool.source === "builtin" ? t.promptToolBuiltin : t.promptToolExtension}
220
297
  </span>
221
298
  </button>
@@ -225,31 +302,35 @@ export function PromptPanel({
225
302
  )}
226
303
 
227
304
  {data?.assembledSystemPrompt && (
228
- <section className="mb-3">
229
- <div className="mb-1 px-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{t.promptKindAssembled}</div>
305
+ <section className="space-y-1.5">
306
+ <div className="text-xs font-bold text-slate-800 dark:text-slate-200 px-1 mb-1">{t.promptKindAssembled}</div>
230
307
  <button
231
308
  type="button"
232
- className="w-full rounded-lg px-2 py-1.5 text-left"
233
- style={{ border: "0.5px solid var(--dsw-border-l2)" }}
309
+ className="w-full clay-card p-3 flex items-center gap-2.5 hover:scale-[1.01] transition-all cursor-pointer rounded-2xl text-left bg-white dark:bg-slate-900 shadow-sm border border-slate-200 dark:border-slate-800"
234
310
  title={t.promptAssembledKey}
235
311
  onClick={() => onOpenContent?.("system-prompt.md", data.assembledSystemPrompt ?? "")}
236
312
  >
237
- <span className="truncate" style={{ color: "var(--dsw-label-primary)" }}>{t.promptAssembledKey}</span>
313
+ <div className="w-7 h-7 clay-inset bg-emerald-50 text-emerald-700 dark:bg-emerald-950/70 dark:text-emerald-300 flex items-center justify-center rounded-xl font-bold text-sm flex-none">
314
+ π
315
+ </div>
316
+ <span className="truncate font-bold text-slate-900 dark:text-slate-100 text-xs">{t.promptAssembledKey}</span>
238
317
  </button>
239
318
  </section>
240
319
  )}
241
320
 
242
321
  {data?.compactedSummary && (
243
- <section className="mb-3">
244
- <div className="mb-1 px-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{t.promptKindCompacted}</div>
322
+ <section className="space-y-1.5">
323
+ <div className="text-xs font-bold text-slate-800 dark:text-slate-200 px-1 mb-1">{t.promptKindCompacted}</div>
245
324
  <button
246
325
  type="button"
247
- className="w-full rounded-lg px-2 py-1.5 text-left"
248
- style={{ border: "0.5px solid var(--dsw-border-l2)" }}
326
+ className="w-full clay-card p-3 flex items-center gap-2.5 hover:scale-[1.01] transition-all cursor-pointer rounded-2xl text-left bg-white dark:bg-slate-900 shadow-sm border border-slate-200 dark:border-slate-800"
249
327
  title={t.promptCompactedKey}
250
328
  onClick={() => onOpenContent?.("compacted-summary.md", data.compactedSummary ?? "")}
251
329
  >
252
- <span className="truncate" style={{ color: "var(--dsw-label-primary)" }}>{t.promptCompactedKey}</span>
330
+ <div className="w-7 h-7 clay-inset bg-amber-50 text-amber-700 dark:bg-amber-950/70 dark:text-amber-300 flex items-center justify-center rounded-xl font-bold text-xs flex-none">
331
+ SUM
332
+ </div>
333
+ <span className="truncate font-bold text-slate-900 dark:text-slate-100 text-xs">{t.promptCompactedKey}</span>
253
334
  </button>
254
335
  </section>
255
336
  )}
@@ -305,7 +305,7 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
305
305
 
306
306
  {/* 来源选择器:选中哪个就只列哪个工具的会话 */}
307
307
  {groups.length > 0 && (
308
- <div className="flex flex-wrap items-center gap-1.5" role="tablist" aria-label={t.importPickSource} data-testid="import-source-tabs">
308
+ <div className="flex flex-wrap items-center gap-2 p-1.5 rounded-2xl bg-slate-100/80 dark:bg-slate-900/60" role="tablist" aria-label={t.importPickSource} data-testid="import-source-tabs">
309
309
  {groups.map((group) => {
310
310
  const active = group.source === visibleSource;
311
311
  return (
@@ -314,18 +314,16 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
314
314
  type="button"
315
315
  role="tab"
316
316
  aria-selected={active}
317
- className="rounded-full px-3 py-1 transition-colors"
318
- style={{
319
- fontSize: 12,
320
- border: `0.5px solid ${active ? "var(--dsw-accent)" : "var(--dsw-border-l2)"}`,
321
- background: active ? "var(--dsw-accent)" : "transparent",
322
- color: active ? "#fff" : "var(--dsw-label-tertiary)",
323
- }}
317
+ className={`clay-btn rounded-xl px-3.5 py-1.5 transition-all text-xs font-medium flex items-center gap-1.5 ${
318
+ active
319
+ ? "clay-btn-peach text-white shadow-sm font-semibold"
320
+ : "clay-btn-soft text-slate-600 dark:text-slate-300"
321
+ }`}
324
322
  onClick={() => setActiveSource(group.source)}
325
323
  data-testid={`import-source-${group.source}`}
326
324
  >
327
- {sourceLabel(group.source)}
328
- <span style={{ marginLeft: 6, opacity: 0.75 }}>{group.rows.length}</span>
325
+ <span>{sourceLabel(group.source)}</span>
326
+ <span className={`text-[10px] px-1.5 py-0.2 rounded-full ${active ? "bg-white/30 text-white" : "bg-slate-200 dark:bg-slate-700 text-slate-500"}`}>{group.rows.length}</span>
329
327
  </button>
330
328
  );
331
329
  })}
@@ -339,10 +337,10 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
339
337
  {t.importGroupCount.replace("{n}", String(activeGroup.rows.length)).replace("{imported}", String(activeGroup.imported))}
340
338
  </span>
341
339
  <div className="flex-1" />
342
- <button className="rounded-lg px-2 py-0.5" style={{ fontSize: 11.5, color: "var(--dsw-label-tertiary)" }} onClick={() => selectGroup(activeGroup.rows, true)}>
340
+ <button className="clay-btn clay-btn-soft rounded-lg px-2.5 py-1 text-xs" onClick={() => selectGroup(activeGroup.rows, true)}>
343
341
  {t.importSelectAll}
344
342
  </button>
345
- <button className="rounded-lg px-2 py-0.5" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }} onClick={() => selectGroup(activeGroup.rows, false)}>
343
+ <button className="clay-btn clay-btn-soft rounded-lg px-2.5 py-1 text-xs text-slate-400" onClick={() => selectGroup(activeGroup.rows, false)}>
346
344
  {t.importSelectNone}
347
345
  </button>
348
346
  </div>
@@ -354,20 +352,22 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
354
352
  return (
355
353
  <label
356
354
  key={rowKey}
357
- className="flex cursor-pointer items-center gap-3 rounded-xl px-3 py-2"
358
- style={{ border: `0.5px solid ${checked ? "var(--dsw-accent)" : "var(--dsw-border-l2)"}`, opacity: done ? 0.55 : 1 }}
355
+ className={`clay-card flex cursor-pointer items-center gap-3 rounded-2xl px-3.5 py-2.5 transition-all hover:translate-x-0.5 ${
356
+ checked ? "ring-2 ring-emerald-500 bg-white dark:bg-slate-800" : "bg-white/70 dark:bg-slate-900/60"
357
+ }`}
358
+ style={{ opacity: done ? 0.55 : 1 }}
359
359
  >
360
360
  <input type="checkbox" checked={checked} disabled={done} onChange={() => toggle(row)} />
361
361
  <div className="min-w-0 flex-1">
362
- <div className="truncate" style={{ fontSize: 12.5 }} title={row.title}>
362
+ <div className="truncate text-xs font-medium text-slate-800 dark:text-slate-100" title={row.title}>
363
363
  {row.title || t.importUntitled}
364
364
  </div>
365
- <div className="truncate" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }} title={row.projectPath}>
365
+ <div className="truncate text-[11px] text-slate-400" title={row.projectPath}>
366
366
  {row.projectPath || t.importNoProject}
367
367
  </div>
368
368
  </div>
369
369
  {done && (
370
- <span className="rounded-full px-2 py-0.5" style={{ fontSize: 10, background: "var(--dsw-hover)", color: "var(--dsw-label-caption)" }}>
370
+ <span className="clay-badge rounded-full px-2 py-0.5 text-[10px] bg-slate-100 text-slate-400">
371
371
  {t.importAlready}
372
372
  </span>
373
373
  )}