@rexvane/piweb 0.3.16 → 0.3.18

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/0cd968b9b62035b5.css +1 -0
  127. package/.next/static/en03hnPiuro0rnf8QkFEe/_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 +92 -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 → en03hnPiuro0rnf8QkFEe}/_ssgManifest.js +0 -0
@@ -7,7 +7,7 @@
7
7
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
8
8
  import { PiMark } from "@/components/PiMark";
9
9
  import { ChatInput, EMPTY_CHAT_DRAFT, type ChatDraft, type ChatDraftUpdate } from "@/components/ChatInput";
10
- import { ChatWindow, SessionStatsBar } from "@/components/ChatWindow";
10
+ import { ChatWindow, SessionStatsBar, type RoundBadge } from "@/components/ChatWindow";
11
11
  import { ExtensionDialogHost, ExtensionNotices } from "@/components/ExtensionUI";
12
12
  import dynamic from "next/dynamic";
13
13
  import { SessionSidebar } from "@/components/SessionSidebar";
@@ -23,21 +23,14 @@ const samePath = (a: string, b: string): boolean =>
23
23
  const ProjectPanel = dynamic(() => import("@/components/ProjectPanel").then((m) => m.ProjectPanel), { ssr: false });
24
24
  const FileViewer = dynamic(() => import("@/components/FileViewer").then((m) => m.FileViewer), { ssr: false });
25
25
  const GitPanel = dynamic(() => import("@/components/GitPanel").then((m) => m.GitPanel), { ssr: false });
26
- const TestInspectorPod = dynamic(() => import("@/components/TestInspectorPod").then((m) => m.TestInspectorPod), { ssr: false });
27
- const SwarmCoordinatorPod = dynamic(() => import("@/components/SwarmCoordinatorPod").then((m) => m.SwarmCoordinatorPod), { ssr: false });
28
26
  import {
29
27
  IconAgentPresetOutline16,
30
- IconArchiveOutline20,
31
- IconBranchOutline16,
32
28
  IconCheckOutline14,
33
29
  IconChevronDown14,
34
30
  IconFolderClose16,
35
31
  IconFolderOpenOutline16,
36
32
  IconPanelLeftOutline16,
37
33
  IconProjectAddOutline16,
38
- IconSettingsOutline16,
39
- IconShieldOutline16,
40
- IconWorkflowAgent16,
41
34
  } from "@/components/icons";
42
35
  import { useI18n } from "@/i18n";
43
36
  import { usePiWeb } from "@/hooks/usePiWeb";
@@ -46,7 +39,7 @@ import { useFileViewer } from "@/hooks/useFileViewer";
46
39
  import type { TreeNode } from "@/lib/growth-tree";
47
40
  import { syncPebrelTheme } from "@/lib/theme";
48
41
  import type { ModelChoice } from "@/components/ModelSelector";
49
- import type { ImageAttachment, TrajEntry, WorkflowMode } from "@/lib/types";
42
+ import type { ImageAttachment, TrajEntry } from "@/lib/types";
50
43
 
51
44
  // dsh ui-layout columns.ts 几何常量
52
45
  const SIDEBAR_MIN = 264;
@@ -79,8 +72,6 @@ export function AppShell() {
79
72
  currentPath,
80
73
  state,
81
74
  models,
82
- modelLoading,
83
- modelLoadError,
84
75
  addedWorkspaces,
85
76
  removedWorkspaces,
86
77
  workspaceAliases,
@@ -95,11 +86,9 @@ export function AppShell() {
95
86
  setGroupBy,
96
87
  setOrderBy,
97
88
  addWorkspaceByPicker,
98
- addWorkspaceByPath,
99
89
  removeWorkspace,
100
90
  refreshModels,
101
91
  sendCommand,
102
- editAndResend,
103
92
  newSession,
104
93
  openSession,
105
94
  closeSession,
@@ -155,8 +144,6 @@ export function AppShell() {
155
144
  const [heroCwd, setHeroCwd] = useState("");
156
145
  const [heroModel, setHeroModel] = useState<{ provider: string; id: string } | null>(null);
157
146
  const [heroThinking, setHeroThinking] = useState("");
158
- const [heroMode, setHeroMode] = useState<WorkflowMode>("agent");
159
- const [activeTopView, setActiveTopView] = useState<"workbench" | "diff" | "tests" | "swarm" | "archive">("workbench");
160
147
  const [isNarrow, setIsNarrow] = useState(false);
161
148
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
162
149
  useEffect(() => {
@@ -370,9 +357,15 @@ export function AppShell() {
370
357
  const doEditMessage = useCallback(
371
358
  async (entryId: string, text: string) => {
372
359
  if (!currentId) return;
373
- await editAndResend(entryId, text);
360
+ const done = await sendCommand({ cmd: "editAndResend", entryId, text });
361
+ if (!done?.success) {
362
+ setError(done?.error ?? t.editMessageFailed);
363
+ return;
364
+ }
365
+ // 被编辑消息之后的尾部要从视图里消失(服务端已切断该分支)
366
+ resync();
374
367
  },
375
- [currentId, editAndResend],
368
+ [currentId, resync, sendCommand, setError, t.editMessageFailed],
376
369
  );
377
370
 
378
371
  const doArchiveSession = useCallback(
@@ -381,7 +374,6 @@ export function AppShell() {
381
374
  await archiveSession(path);
382
375
  } catch {
383
376
  // usePiWeb exposes the request failure in the shared error banner.
384
- return;
385
377
  }
386
378
  // 归档后会话立刻离开工作区列表(服务端同时让它收工)。正在看的会话被归档时
387
379
  // 必须切走:先取同一工作区的另一个可见会话,没有就回到空态(工作区仍留在侧栏)。
@@ -440,7 +432,6 @@ export function AppShell() {
440
432
  provider: effective?.provider,
441
433
  modelId: effective?.id,
442
434
  thinking: heroThinking || undefined,
443
- mode: heroMode,
444
435
  });
445
436
  if (!p) return { success: false };
446
437
  // Bind before the new-session render. In-flight ChatInput callbacks still own
@@ -530,8 +521,6 @@ export function AppShell() {
530
521
 
531
522
  // 项目栏/文件查看器的工作区:当前会话的工作区优先,快照未到时回落到会话列表里的 cwd
532
523
  const panelCwd = snapshot?.cwd || currentSession?.cwd || heroCwd || knownCwds[0] || "";
533
- const activeSnapshot = currentPath && snapshot?.sessionPath && samePath(snapshot.sessionPath, currentPath) ? snapshot : null;
534
- const activeWorkspaceCwd = currentPath ? (activeSnapshot?.cwd || currentSession?.cwd || "") : heroCwd;
535
524
 
536
525
  // Git 面板自动刷新:pi 每完成一个会改动文件的工具,或一轮结束时,静默重拉
537
526
  const mutatingDone = useMemo(
@@ -539,8 +528,6 @@ export function AppShell() {
539
528
  [state.tools],
540
529
  );
541
530
  const [panelRefreshKey, setPanelRefreshKey] = useState(0);
542
- const [gitSummary, setGitSummary] = useState<{ available: boolean; isRepo: boolean; branch: string | null; detached: boolean; files: unknown[]; statusError?: string } | null>(null);
543
- const [gitSummaryError, setGitSummaryError] = useState(false);
544
531
  const prevMutatingRef = useRef(mutatingDone);
545
532
  const prevStreamingRef = useRef(isStreaming);
546
533
  useEffect(() => {
@@ -553,31 +540,6 @@ export function AppShell() {
553
540
  if (prevStreamingRef.current && !isStreaming) setPanelRefreshKey((k) => k + 1);
554
541
  prevStreamingRef.current = isStreaming;
555
542
  }, [isStreaming]);
556
- useEffect(() => {
557
- const controller = new AbortController();
558
- setGitSummary(null);
559
- setGitSummaryError(false);
560
- if (!activeWorkspaceCwd) return () => controller.abort();
561
- void (async () => {
562
- try {
563
- const response = await fetch(`/api/git?cwd=${encodeURIComponent(activeWorkspaceCwd)}`, { signal: controller.signal });
564
- const body = await response.json();
565
- if (!response.ok || !body.success) throw new Error(body.error || "Git 状态读取失败");
566
- if (!controller.signal.aborted) setGitSummary(body.data);
567
- } catch {
568
- if (!controller.signal.aborted) setGitSummaryError(true);
569
- }
570
- })();
571
- return () => controller.abort();
572
- }, [activeWorkspaceCwd, panelRefreshKey]);
573
- const gitBranchLabel = gitSummary?.isRepo ? (gitSummary.detached ? "HEAD" : gitSummary.branch || "—") : "—";
574
- const gitStatusLabel = !activeWorkspaceCwd ? "未选择"
575
- : gitSummaryError || gitSummary?.statusError ? "状态未知"
576
- : !gitSummary ? "读取中"
577
- : !gitSummary.available ? "Git 不可用"
578
- : !gitSummary.isRepo ? "非仓库"
579
- : gitSummary.files.length ? `${gitSummary.files.length} 改动` : "干净";
580
- const gitCanCommit = Boolean(gitSummary?.isRepo && !gitSummary.statusError && gitSummary.files.length);
581
543
 
582
544
  const insertIntoComposer = useCallback((text: string) => {
583
545
  const textarea = document.querySelector<HTMLTextAreaElement>('[data-testid="composer"] textarea');
@@ -591,12 +553,12 @@ export function AppShell() {
591
553
  requestAnimationFrame(() => textarea?.focus());
592
554
  }, [draftKey, updateDraft]);
593
555
  const openInEditor = useCallback(
594
- async (filePath: string, cwdOverride?: string) => {
556
+ async (filePath: string) => {
595
557
  try {
596
558
  const r = await fetch("/api/files", {
597
559
  method: "POST",
598
560
  headers: { "Content-Type": "application/json" },
599
- body: JSON.stringify({ action: "open", cwd: cwdOverride || panelCwd, path: filePath }),
561
+ body: JSON.stringify({ action: "open", cwd: panelCwd, path: filePath }),
600
562
  });
601
563
  const j = await r.json();
602
564
  if (!j.success) throw new Error(j.error || "failed to open file");
@@ -612,29 +574,39 @@ export function AppShell() {
612
574
  useEffect(() => {
613
575
  resetViewer();
614
576
  }, [panelCwd, resetViewer]);
615
- const growthTurnStarts = useMemo(
616
- () => {
617
- const persisted = state.snapshot?.userTurns ?? [];
618
- const ids = new Set(persisted.map((turn) => turn.id));
619
- const lastTs = persisted[persisted.length - 1]?.ts ?? -Infinity;
620
- const live = state.messages
621
- .filter((message) => message.role === "user" && (!message.id || !ids.has(message.id)) && (message.timestamp ?? Date.now()) >= lastTs - 1000)
622
- .map((message) => message.timestamp ?? Date.now());
623
- return [...persisted.map((turn) => turn.ts), ...live];
624
- },
625
- [state.snapshot?.userTurns, state.messages],
626
- );
627
577
  const growth = useGrowth({
628
578
  cwd: panelCwd,
629
579
  sessionPath: currentPath,
630
- liveSteps: state.growth.steps,
631
- pending: state.growth.pending,
580
+ liveRounds: state.growth.rounds,
632
581
  runtimeError: state.growth.error,
633
582
  active: projectOpen || viewer.state.open,
583
+ watchRounds: Boolean(currentPath),
634
584
  connected: state.connected,
635
- turnStarts: growthTurnStarts,
636
585
  });
637
586
  const openFileNode = useCallback((node: TreeNode) => viewer.open(node.path, { from: node.from, lazy: node.lazy }), [viewer]);
587
+ const roundBadges = useMemo(() => {
588
+ const map = new Map<string, RoundBadge>();
589
+ for (const round of growth.rounds) {
590
+ if (round.kind !== "round" || !round.promptIds[0]) continue;
591
+ const st = round.stats;
592
+ map.set(round.promptIds[0], { commit: round.commit, files: st.added + st.modified + st.deleted + st.renamed, add: st.add, del: st.del });
593
+ }
594
+ return map;
595
+ }, [growth.rounds]);
596
+ const growthSelect = growth.select;
597
+ const showRound = useCallback((commit: string) => {
598
+ toggleProject(true);
599
+ growthSelect(commit);
600
+ }, [toggleProject, growthSelect]);
601
+ const jumpToChat = useCallback((messageId: string) => {
602
+ requestAnimationFrame(() => {
603
+ const node = document.querySelector<HTMLElement>(`[data-role="user"][data-message-id="${CSS.escape(messageId)}"]`);
604
+ if (!node) return;
605
+ node.scrollIntoView({ block: "center", behavior: "smooth" });
606
+ node.dataset.flash = "";
607
+ setTimeout(() => delete node.dataset.flash, 1600);
608
+ });
609
+ }, []);
638
610
 
639
611
  useEffect(() => {
640
612
  document.title = currentId && title && title !== "pi" ? `${title} · pi` : "pi";
@@ -660,6 +632,8 @@ export function AppShell() {
660
632
  workspaceName={panelCwd ? getWorkspaceName(panelCwd) : ""}
661
633
  cwd={panelCwd}
662
634
  hasSession={Boolean(currentPath)}
635
+ running={isStreaming}
636
+ onJumpToChat={jumpToChat}
663
637
  onOpenFile={openFileNode}
664
638
  onReference={insertIntoComposer}
665
639
  onOpenEditor={openInEditor}
@@ -673,159 +647,28 @@ export function AppShell() {
673
647
  );
674
648
 
675
649
  return (
676
- <div className="flex flex-col h-screen w-screen overflow-hidden bg-clay-bg text-clay-slate-text antialiased">
677
- {/* 全局粘土顶部导航栏 (Screen 1, 2, 3, 8) */}
678
- <header className="pw-top-navbar h-14 px-6 flex items-center justify-between border-b border-white/60 bg-white/70 backdrop-blur-md z-30 shrink-0 select-none">
679
- {/* Left: Engine brand badge + Code Workbench capsule */}
680
- <div className="flex items-center gap-3 min-w-0">
681
- <div className="w-9 h-9 clay-badge bg-gradient-to-tr from-emerald-500 to-teal-400 flex items-center justify-center text-white font-black text-lg shadow-sm">
682
- π
683
- </div>
684
- <div className="flex items-baseline gap-2">
685
- <span className="font-extrabold text-lg tracking-tight text-slate-900 dark:text-slate-100">piweb</span>
686
- <span className="text-xs font-bold px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-800 dark:bg-emerald-950/70 dark:text-emerald-300 tracking-wide uppercase border border-emerald-300 dark:border-emerald-800">Code Workbench</span>
687
- </div>
688
- </div>
689
-
690
- {/* Center: Session Breadcrumb Status Pill */}
691
- <div className="hidden lg:flex items-center gap-2 px-3 py-1.5 clay-inset bg-white dark:bg-slate-900 rounded-full text-xs font-medium border border-slate-200 dark:border-slate-800 min-w-0">
692
- <span className={`w-2.5 h-2.5 rounded-full flex-none ${state.connected ? "bg-emerald-500 animate-pulse" : "bg-slate-400"}`} />
693
- <span className="font-bold text-slate-800 dark:text-slate-200">PiWeb</span>
694
- <span className="text-slate-400">/</span>
695
- <span className="font-semibold text-slate-600 dark:text-slate-400">SESSION</span>
696
- <span className="text-slate-400">/</span>
697
- <span className="text-slate-900 dark:text-slate-100 max-w-xs truncate font-bold">
698
- {currentId ? title : (heroCwd ? (getWorkspaceName ? getWorkspaceName(heroCwd) : basename(heroCwd)) : t.newChat)}
699
- </span>
700
- </div>
701
-
702
- {/* Center/Nav: View Switcher Pill Tabs */}
703
- <nav aria-label="视图切换" className="clay-inset flex min-w-0 items-center overflow-x-auto p-1 rounded-full bg-slate-100 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 gap-1 text-xs font-semibold">
704
- <button
705
- type="button"
706
- className={`px-3.5 py-1 rounded-full transition-all duration-150 ${
707
- activeTopView === "workbench"
708
- ? "clay-btn-peach text-white font-bold shadow-sm"
709
- : "text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white"
710
- }`}
711
- onClick={() => setActiveTopView("workbench")}
712
- >
713
- 工作台
714
- </button>
715
- <button
716
- type="button"
717
- className={`flex items-center gap-1.5 px-3.5 py-1 rounded-full transition-all duration-150 ${
718
- activeTopView === "diff"
719
- ? "clay-btn-peach text-white font-bold shadow-sm"
720
- : "text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white"
721
- }`}
722
- onClick={() => { setPanelRefreshKey((key) => key + 1); setActiveTopView("diff"); }}
723
- >
724
- <span>差异对比</span>
725
- <span className={`text-[10px] px-1.5 py-0.2 rounded-full font-bold ${
726
- activeTopView === "diff" ? "bg-white/30 text-white" : "bg-emerald-100 text-emerald-800 dark:bg-emerald-950/70 dark:text-emerald-300"
727
- }`}>
728
- {gitSummary?.isRepo ? gitSummary.files.length : "—"}
729
- </span>
730
- </button>
731
- <button
732
- type="button"
733
- className={`shrink-0 px-3.5 py-1 rounded-full transition-all duration-150 ${
734
- activeTopView === "tests"
735
- ? "clay-btn-peach text-white font-bold shadow-sm"
736
- : "text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white"
737
- }`}
738
- onClick={() => setActiveTopView("tests")}
739
- >
740
- 测试检查器
741
- </button>
742
- <button
743
- type="button"
744
- className={`flex items-center gap-1.5 px-3.5 py-1 rounded-full transition-all duration-150 ${
745
- activeTopView === "swarm"
746
- ? "clay-btn-peach text-white font-bold shadow-sm"
747
- : "text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white"
748
- }`}
749
- onClick={() => setActiveTopView("swarm")}
750
- >
751
- <span>智能体群</span>
752
- </button>
753
- </nav>
754
-
755
- {/* Right: Actions */}
756
- <div className="flex items-center gap-2">
757
- <button
758
- type="button"
759
- className="clay-badge hidden md:flex items-center gap-1.5 px-3 py-1 text-xs font-mono text-slate-800 dark:text-slate-200 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 shadow-sm font-semibold hover:border-slate-300 transition-all cursor-pointer"
760
- title="Git 工作区状态(点击进入差异对比)"
761
- onClick={() => {
762
- setPanelRefreshKey((key) => key + 1);
763
- setActiveTopView("diff");
764
- }}
765
- >
766
- <IconBranchOutline16 size={13} className="text-emerald-500" />
767
- <span>{gitBranchLabel}</span>
768
- <span className="text-slate-400">·</span>
769
- <span className="text-amber-600 dark:text-amber-400 font-bold">{gitStatusLabel}</span>
770
- </button>
771
- <button
772
- type="button"
773
- className="w-8 h-8 clay-btn bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white transition-colors"
774
- title={t.projectPanel}
775
- aria-label={t.projectPanel}
776
- data-testid="project-toggle"
777
- onClick={() => toggleProject()}
778
- >
779
- <IconFolderOpenOutline16 size={15} />
780
- </button>
781
- <button
782
- type="button"
783
- className="w-8 h-8 clay-btn bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white transition-colors"
784
- title={t.promptPanel}
785
- aria-label={t.promptPanel}
786
- data-testid="prompt-panel-toggle"
787
- onClick={() => {
788
- setPromptsOpen((open) => !open);
789
- setSelected(null);
790
- setGitDetailsOpen(false);
791
- }}
792
- >
793
- <IconAgentPresetOutline16 size={15} />
794
- </button>
795
- <button
796
- type="button"
797
- className="w-8 h-8 clay-btn bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white transition-colors"
798
- title={t.settings}
799
- aria-label={t.settings}
800
- data-testid="settings-toggle"
801
- onClick={() => setSettingsOpen(true)}
802
- >
803
- <IconSettingsOutline16 size={15} />
804
- </button>
805
- </div>
806
- </header>
807
-
808
- {/* 三栏/四栏悬浮粘土卡片主体容器 */}
809
- <main
810
- className="pw-shell grid flex-1 min-h-0 overflow-hidden p-3 gap-3 bg-clay-bg"
811
- style={{
812
- gridTemplateColumns: gridCols,
813
- transition: dragging ? "none" : "grid-template-columns var(--ds-duration-slow) var(--ds-ease-in-out)",
814
- }}
650
+ <div
651
+ className="grid h-screen w-screen overflow-hidden"
652
+ style={{
653
+ gridTemplateColumns: gridCols,
654
+ transition: dragging ? "none" : "grid-template-columns var(--ds-duration-slow) var(--ds-ease-in-out)",
655
+ background: "var(--dsw-bg-base)",
656
+ }}
657
+ >
658
+ {/* 侧栏 */}
659
+ <div
660
+ className="min-h-0 overflow-hidden"
661
+ style={isNarrow ? {
662
+ position: "fixed",
663
+ inset: "0 auto 0 0",
664
+ width: "min(86vw, 340px)",
665
+ zIndex: 90,
666
+ background: "var(--dsw-sidebar-fill)",
667
+ transform: mobileSidebarOpen ? "translateX(0)" : "translateX(-105%)",
668
+ transition: "transform var(--ds-duration-normal) var(--ds-ease-in-out)",
669
+ boxShadow: mobileSidebarOpen ? "var(--dsw-elevation-prominent)" : "none",
670
+ } : { background: "var(--dsw-sidebar-fill)" }}
815
671
  >
816
- {/* 侧栏卡片 */}
817
- <div
818
- className="pw-sidebar-surface clay-surface rounded-3xl p-3 shrink-0 flex flex-col justify-between overflow-hidden shadow-md"
819
- style={isNarrow ? {
820
- position: "fixed",
821
- inset: "0 auto 0 0",
822
- width: "min(86vw, 340px)",
823
- zIndex: 90,
824
- transform: mobileSidebarOpen ? "translateX(0)" : "translateX(-105%)",
825
- transition: "transform var(--ds-duration-normal) var(--ds-ease-in-out)",
826
- boxShadow: mobileSidebarOpen ? "var(--dsw-elevation-prominent)" : "none",
827
- } : undefined}
828
- >
829
672
  <SessionSidebar
830
673
  sessions={sessions}
831
674
  sessionListError={sessionListError}
@@ -870,104 +713,15 @@ export function AppShell() {
870
713
  }}
871
714
  onAddWorkspace={() => void addWorkspaceByPicker()}
872
715
  draftCwd={!currentId ? heroCwd || null : null}
873
- gitBranch={gitBranchLabel}
874
- gitStatus={gitStatusLabel}
875
- canAskCommit={gitCanCommit}
876
- onAskCommit={() => { setActiveTopView("workbench"); insertIntoComposer(t.gitAskCommitPrompt); }}
877
- activeTopView={activeTopView}
878
- onSelectTopView={(v) => { setActiveTopView(v); setMobileSidebarOpen(false); }}
879
716
  />
880
717
  </div>
881
718
 
882
- {/* 四列网格中始终保留项目列 */}
883
- {!isNarrow && (projectColumn ? (
884
- <div className="clay-surface rounded-3xl p-3 shrink-0 flex flex-col overflow-hidden shadow-md">
885
- {projectColumn}
886
- </div>
887
- ) : <div aria-hidden="true" />)}
719
+ {/* 四列网格中始终保留项目列,否则关闭项目栏时会话区会落进 0px 列。 */}
720
+ {!isNarrow && (projectColumn || <div aria-hidden="true" />)}
888
721
 
889
- {/* 会话区与多视图卡片 (Screen 1 & Screen 3) */}
890
- <section className={`pi-main pw-main flex flex-col clay-card rounded-3xl overflow-hidden min-h-0 min-w-0 relative bg-white shadow-md${currentId ? "" : " pw-main-hero"}`}>
891
-
892
-
893
- {/* 视图内容切换 */}
894
- {activeTopView === "diff" ? (
895
- <div className="flex-1 min-h-0 overflow-hidden p-4 sm:p-6 bg-slate-50/50 dark:bg-slate-950/30">
896
- <div className="clay-card mx-auto h-full max-w-6xl overflow-hidden rounded-3xl bg-white/85 dark:bg-slate-900/80">
897
- <GitPanel cwd={activeWorkspaceCwd} refreshKey={panelRefreshKey} onClose={() => setActiveTopView("workbench")} onOpenFile={(filePath) => void openInEditor(filePath, activeWorkspaceCwd)} />
898
- </div>
899
- </div>
900
- ) : activeTopView === "tests" ? (
901
- <div className="flex-1 min-h-0 overflow-y-auto p-4 sm:p-6 bg-slate-50/50 dark:bg-slate-950/30">
902
- <TestInspectorPod
903
- cwd={activeWorkspaceCwd}
904
- onClose={() => setActiveTopView("workbench")}
905
- onSwitchToWorkbench={() => setActiveTopView("workbench")}
906
- onSwitchToDiff={() => setActiveTopView("diff")}
907
- />
908
- </div>
909
- ) : activeTopView === "swarm" ? (
910
- <div className="flex-1 min-h-0 overflow-hidden p-3 sm:p-4 bg-slate-50/50 dark:bg-slate-950/30">
911
- <SwarmCoordinatorPod
912
- cwd={activeWorkspaceCwd}
913
- onClose={() => setActiveTopView("workbench")}
914
- onSwitchToWorkbench={() => setActiveTopView("workbench")}
915
- projectTrust={activeSnapshot?.projectTrust}
916
- onWorkspaceChanged={() => setPanelRefreshKey((key) => key + 1)}
917
- />
918
- </div>
919
- ) : activeTopView === "archive" ? (
920
- <div className="flex-1 min-h-0 overflow-y-auto p-6 flex flex-col items-center bg-slate-50/40 dark:bg-slate-950/20">
921
- <div className="clay-card max-w-2xl w-full p-6 rounded-3xl space-y-4">
922
- <div className="flex items-center justify-between pb-3 border-b border-slate-200/60 dark:border-slate-800/60">
923
- <h2 className="text-base font-bold text-slate-800 dark:text-slate-100 flex items-center gap-2">
924
- <IconArchiveOutline20 size={18} className="text-slate-400" />
925
- 已归档会话 ({archivedSessions.length})
926
- </h2>
927
- <span className="text-xs text-slate-400">保留历史记录,不占用主工作台</span>
928
- </div>
929
- {archivedSessions.length === 0 ? (
930
- <div className="text-center py-12 text-sm text-slate-400">暂无归档会话</div>
931
- ) : (
932
- <div className="space-y-2">
933
- {archivedSessions.map((session) => (
934
- <div
935
- key={session.path}
936
- className="clay-card flex items-center justify-between p-3.5 rounded-2xl hover:scale-[1.01] transition-transform"
937
- >
938
- <div className="min-w-0 flex-1">
939
- <div className="font-semibold text-sm text-slate-800 dark:text-slate-200 truncate">{session.name || session.firstMessage || basename(session.path)}</div>
940
- <div className="text-xs text-slate-400 truncate mt-0.5">{session.cwd}</div>
941
- </div>
942
- <div className="flex items-center gap-2 ml-4">
943
- <button
944
- type="button"
945
- className="clay-btn clay-btn-soft px-3 py-1.5 text-xs text-slate-700 dark:text-slate-200"
946
- onClick={() => {
947
- doUnarchiveSession(session.path);
948
- setActiveTopView("workbench");
949
- }}
950
- >
951
- 恢复会话
952
- </button>
953
- <button
954
- type="button"
955
- className="clay-btn clay-btn-peach px-3 py-1.5 text-xs text-white"
956
- onClick={() => {
957
- openSession(session.path);
958
- setActiveTopView("workbench");
959
- }}
960
- >
961
- 查看
962
- </button>
963
- </div>
964
- </div>
965
- ))}
966
- </div>
967
- )}
968
- </div>
969
- </div>
970
- ) : !currentId ? (
722
+ {/* 会话区 */}
723
+ <div className="pi-main flex min-h-0 min-w-0 flex-col">
724
+ {!currentId ? (
971
725
  <div className="flex min-h-0 flex-1 flex-col">
972
726
  <Hero
973
727
  draft={composerDrafts.current.get(heroDraftKey) ?? EMPTY_CHAT_DRAFT}
@@ -986,9 +740,6 @@ export function AppShell() {
986
740
  getWorkspaceName={getWorkspaceName}
987
741
  onSend={heroSend}
988
742
  models={modelChoices}
989
- modelLoading={modelLoading}
990
- modelLoadError={modelLoadError}
991
- onRetryModels={() => void refreshModels()}
992
743
  providerNames={providerNames}
993
744
  authByProvider={authByProvider}
994
745
  addWorkspaceByPicker={addWorkspaceByPicker}
@@ -1000,11 +751,8 @@ export function AppShell() {
1000
751
  }}
1001
752
  defaultModel={defaultModel}
1002
753
  heroModelLevels={heroModelLevels}
1003
- heroThinking={heroThinking}
1004
- onSelectHeroThinking={setHeroThinking}
1005
- heroMode={heroMode}
1006
- onSelectHeroMode={setHeroMode}
1007
- onOpenSwarm={() => setActiveTopView("swarm")}
754
+ heroThinking={heroThinking}
755
+ onSelectHeroThinking={setHeroThinking}
1008
756
  />
1009
757
  {/* 草稿阶段的错误行内显示(不再弹右下角) */}
1010
758
  {state.error && (
@@ -1022,9 +770,43 @@ export function AppShell() {
1022
770
  </div>
1023
771
  ) : (
1024
772
  <>
1025
- {/* 会话信任横幅 */}
773
+ {/* 头部:标题行 + 页签行(dsh 两行式) */}
774
+ <div className="hairline-b px-5 pb-0 pt-3">
775
+ <div className="flex items-center gap-3">
776
+ <span className="min-w-0 flex-1 truncate" style={{ fontSize: 14.5, fontWeight: 600 }}>
777
+ {title}
778
+ </span>
779
+ {/* 项目生长入口;轨迹详情由对话内工具行点击唤起 */}
780
+ <button
781
+ type="button"
782
+ className="icon-btn"
783
+ style={{ width: 30, height: 30, background: projectOpen ? "var(--dsw-active)" : undefined }}
784
+ title={`${t.projectPanel} (Ctrl/⌘+Shift+E)`}
785
+ aria-label={t.projectPanel}
786
+ data-testid="project-toggle"
787
+ onClick={() => toggleProject()}
788
+ >
789
+ <IconFolderOpenOutline16 size={15} />
790
+ </button>
791
+ {/* 提示词来源:系统提示词 / 记忆 / 技能 / 模板 / 工具提示词集中列出,点开即看 */}
792
+ <button
793
+ type="button"
794
+ className="icon-btn"
795
+ style={{ width: 30, height: 30, background: promptsOpen ? "var(--dsw-active)" : undefined }}
796
+ title={t.promptPanel}
797
+ aria-label={t.promptPanel}
798
+ data-testid="prompt-panel-toggle"
799
+ onClick={() => {
800
+ setPromptsOpen((open) => !open);
801
+ setSelected(null);
802
+ setGitDetailsOpen(false);
803
+ }}
804
+ >
805
+ <IconAgentPresetOutline16 size={15} />
806
+ </button>
807
+ </div>
1026
808
  {projectTrust?.required && !projectTrust.trusted && (
1027
- <div className="mx-5 mt-2 flex flex-wrap items-center gap-2 rounded-xl px-3 py-2" style={{ fontSize: 12.5, background: "var(--dsw-hover)", border: "0.5px solid var(--dsw-border-l2)" }} role="status">
809
+ <div className="mt-2 flex flex-wrap items-center gap-2 rounded-xl px-3 py-2" style={{ fontSize: 12.5, background: "var(--dsw-hover)", border: "0.5px solid var(--dsw-border-l2)" }} role="status">
1028
810
  <span style={{ color: "var(--dsw-warn)", fontWeight: 600 }}>⚠</span>
1029
811
  <span className="min-w-0 flex-1">{projectTrust.source === "default-never" || projectTrust.source === "remembered" ? t.projectTrustBannerNever : t.projectTrustBanner}</span>
1030
812
  <button className="btn-primary-white" style={{ height: 26, padding: "0 10px", fontSize: 12 }} onClick={() => void setProjectTrust(true)}>{t.projectTrustAllow}</button>
@@ -1033,6 +815,8 @@ export function AppShell() {
1033
815
  )}
1034
816
  </div>
1035
817
  )}
818
+ {/* 轨迹页签已移除:轨迹按需查看(对话里的工具行 → 轨迹详情),不再占用主区域 */}
819
+ </div>
1036
820
 
1037
821
  {/* 内容:只有对话区,轨迹按需在详情栏查看 */}
1038
822
  {!snapshot ? (
@@ -1073,20 +857,22 @@ export function AppShell() {
1073
857
  onOpenFile={openInEditor}
1074
858
  onFork={handleFork}
1075
859
  onEditMessage={doEditMessage}
860
+ roundBadges={roundBadges}
861
+ onShowRound={showRound}
1076
862
  />
1077
- <div className="pw-session-composer px-4 pb-3 pt-2">
863
+ <div className="px-4 pb-3 pt-2">
1078
864
  <div className="mx-auto w-full" style={{ maxWidth: "var(--dsh-composer-card-max-width)" }}>
1079
865
  {/* 运行状态指示由 ChatWindow 内的 WorkingIndicator 承担(含工具/输出 token 信息) */}
1080
866
  <ChatInput
1081
867
  key={draftKey}
1082
- draft={composerDrafts.current.get(draftKey) ?? EMPTY_CHAT_DRAFT}
1083
- onDraftChange={saveSessionDraft}
1084
- onUploadError={(message) => failUpload(draftKey, message)}
1085
- onUploadProgress={(delta) => trackUpload(draftKey, delta)}
1086
- uploadFailure={uploadFailures[draftKey]}
1087
- pendingUploadCount={uploadCounts[draftKey] ?? 0}
1088
- pendingSend={pendingSends[draftKey] ?? false}
1089
- onSendPendingChange={(pending) => trackSend(draftKey, pending)}
868
+ draft={composerDrafts.current.get(draftKey) ?? EMPTY_CHAT_DRAFT}
869
+ onDraftChange={saveSessionDraft}
870
+ onUploadError={(message) => failUpload(draftKey, message)}
871
+ onUploadProgress={(delta) => trackUpload(draftKey, delta)}
872
+ uploadFailure={uploadFailures[draftKey]}
873
+ pendingUploadCount={uploadCounts[draftKey] ?? 0}
874
+ pendingSend={pendingSends[draftKey] ?? false}
875
+ onSendPendingChange={(pending) => trackSend(draftKey, pending)}
1090
876
  isStreaming={isStreaming}
1091
877
  contextPercent={snapshot?.contextUsage?.percent ?? null}
1092
878
  contextTokens={snapshot?.contextUsage?.tokens ?? null}
@@ -1097,15 +883,9 @@ export function AppShell() {
1097
883
  thinkingLevel={snapshot?.thinkingLevel}
1098
884
  thinkingLevels={snapshot?.thinkingLevels ?? ["off", "minimal", "low", "medium", "high", "xhigh", "max"]}
1099
885
  models={modelChoices}
1100
- modelLoading={modelLoading}
1101
- modelLoadError={modelLoadError}
1102
- onRetryModels={() => void refreshModels()}
1103
886
  providerNames={providerNames}
1104
887
  authByProvider={authByProvider}
1105
888
  queue={snapshot?.queue ?? { steering: [], followUp: [] }}
1106
- workflow={snapshot?.workflow}
1107
- onWorkflowModeChange={(mode) => sendCommand({ cmd: "setWorkflowMode", mode })}
1108
- onApprovePlan={() => sendCommand({ cmd: "approvePlan" })}
1109
889
  commands={slashCommands}
1110
890
  onCommand={runSlashCommand}
1111
891
  onSend={sendPrompt}
@@ -1127,46 +907,45 @@ export function AppShell() {
1127
907
  {Object.entries(state.extensionStatuses).map(([k, v]) => <span key={k}>{v}</span>)}
1128
908
  </div>
1129
909
  )}
1130
- </div>
910
+ </div>
1131
911
  </div>
1132
912
  </div>
1133
913
  )
1134
914
  }
1135
915
  </>
1136
916
  )}
1137
- </section>
917
+ </div>
1138
918
 
1139
- {/* 提示词来源 / 轨迹 / Git 详情栏卡片 */}
1140
- {(selected || gitDetailsOpen || promptsOpen) && (
1141
- <aside
1142
- className="pw-details-surface clay-surface rounded-3xl p-4 shrink-0 flex flex-col overflow-hidden shadow-md"
1143
- data-purpose="inspector-panel"
1144
- style={isNarrow ? {
1145
- position: "fixed",
1146
- inset: "0 0 0 auto",
1147
- width: "min(92vw, 640px)",
1148
- zIndex: 90,
1149
- boxShadow: "var(--dsw-elevation-prominent)",
1150
- } : undefined}
1151
- >
1152
- <div className="min-h-0 flex-1 flex flex-col">
1153
- {promptsOpen ? (
1154
- <PromptPanel
1155
- cwd={panelCwd}
1156
- sessionId={currentId}
1157
- refreshKey={currentId}
1158
- onOpenContent={(path, content) => viewer.openStatic(path, content)}
1159
- onClose={() => setPromptsOpen(false)}
1160
- />
1161
- ) : selected ? (
1162
- <TrajInspector entry={selected} onClose={() => setSelected(null)} />
1163
- ) : (
1164
- <GitPanel cwd={panelCwd} refreshKey={panelRefreshKey} onClose={() => setGitDetailsOpen(false)} onAskCommit={() => insertIntoComposer(t.gitAskCommitPrompt)} onOpenFile={openInEditor} />
1165
- )}
1166
- </div>
1167
- </aside>
1168
- )}
1169
- </main>
919
+ {/* 提示词来源 / 轨迹 / Git 详情栏 */}
920
+ {(selected || gitDetailsOpen || promptsOpen) && (
921
+ <div
922
+ className="flex min-h-0 flex-col overflow-hidden"
923
+ style={isNarrow ? {
924
+ position: "fixed",
925
+ inset: "0 0 0 auto",
926
+ width: "min(92vw, 640px)",
927
+ zIndex: 90,
928
+ background: "var(--dsw-sidebar-fill)",
929
+ boxShadow: "var(--dsw-elevation-prominent)",
930
+ } : { background: "var(--dsw-sidebar-fill)", borderLeft: "0.5px solid var(--dsw-border-l2)" }}
931
+ >
932
+ <div className="min-h-0 flex-1">
933
+ {promptsOpen ? (
934
+ <PromptPanel
935
+ cwd={panelCwd}
936
+ sessionId={currentId}
937
+ refreshKey={currentId}
938
+ onOpenContent={(path, content) => viewer.openStatic(path, content)}
939
+ onClose={() => setPromptsOpen(false)}
940
+ />
941
+ ) : selected ? (
942
+ <TrajInspector entry={selected} onClose={() => setSelected(null)} />
943
+ ) : (
944
+ <GitPanel cwd={panelCwd} refreshKey={panelRefreshKey} onClose={() => setGitDetailsOpen(false)} onAskCommit={() => insertIntoComposer(t.gitAskCommitPrompt)} onOpenFile={openInEditor} />
945
+ )}
946
+ </div>
947
+ </div>
948
+ )}
1170
949
 
1171
950
  {/* 拖拽手柄 */}
1172
951
  {!isNarrow && !sidebarCollapsed && (
@@ -1280,9 +1059,6 @@ function Hero({
1280
1059
  getWorkspaceName,
1281
1060
  onSend,
1282
1061
  models,
1283
- modelLoading,
1284
- modelLoadError,
1285
- onRetryModels,
1286
1062
  providerNames,
1287
1063
  authByProvider,
1288
1064
  addWorkspaceByPicker,
@@ -1292,9 +1068,6 @@ function Hero({
1292
1068
  heroModelLevels,
1293
1069
  heroThinking,
1294
1070
  onSelectHeroThinking,
1295
- heroMode,
1296
- onSelectHeroMode,
1297
- onOpenSwarm,
1298
1071
  }: {
1299
1072
  draft: ChatDraft;
1300
1073
  onDraftChange: (update: ChatDraftUpdate) => void;
@@ -1312,9 +1085,6 @@ function Hero({
1312
1085
  getWorkspaceName?: (cwd: string) => string;
1313
1086
  onSend: (text: string, images: ImageAttachment[]) => Promise<{ success: boolean }>;
1314
1087
  models: ModelChoice[];
1315
- modelLoading: boolean;
1316
- modelLoadError: string | null;
1317
- onRetryModels: () => void;
1318
1088
  providerNames: Record<string, string>;
1319
1089
  authByProvider: Record<string, boolean>;
1320
1090
  addWorkspaceByPicker: () => Promise<string | null>;
@@ -1324,9 +1094,6 @@ function Hero({
1324
1094
  heroModelLevels: string[];
1325
1095
  heroThinking: string;
1326
1096
  onSelectHeroThinking: (level: string) => void;
1327
- heroMode: WorkflowMode;
1328
- onSelectHeroMode: (mode: WorkflowMode) => void;
1329
- onOpenSwarm?: () => void;
1330
1097
  }) {
1331
1098
  const { t } = useI18n();
1332
1099
  const [wsMenu, setWsMenu] = useState(false);
@@ -1344,106 +1111,81 @@ function Hero({
1344
1111
  }, [wsMenu]);
1345
1112
 
1346
1113
  const pickWorkspace = async () => {
1347
- setWsMenu(false);
1348
1114
  const p = await addWorkspaceByPicker();
1349
1115
  if (p) {
1350
1116
  setCwd(p);
1117
+ setWsMenu(false);
1351
1118
  }
1352
1119
  };
1353
1120
 
1354
1121
  const currentLabel = cwd ? (getWorkspaceName ? getWorkspaceName(cwd) : basename(cwd)) : t.startWith;
1355
1122
 
1356
1123
  return (
1357
- <div className="pw-hero flex h-full min-h-0 flex-col items-center justify-center px-6 py-8 overflow-y-auto relative">
1358
- {/* Ambient orbs */}
1359
- <div className="absolute -top-10 left-12 w-64 h-64 bg-emerald-200/40 dark:bg-emerald-950/20 rounded-full filter blur-3xl opacity-60 pointer-events-none" />
1360
- <div className="absolute top-1/3 -right-16 w-80 h-80 bg-sky-200/40 dark:bg-sky-950/20 rounded-full filter blur-3xl opacity-60 pointer-events-none" />
1361
- <div className="absolute bottom-6 left-1/4 w-72 h-72 bg-orange-200/30 dark:bg-orange-950/20 rounded-full filter blur-3xl opacity-50 pointer-events-none" />
1362
-
1363
- {/* 工作区芯片行 + 输入卡 同宽容器 */}
1364
- <div className="pw-hero-content w-full flex flex-col items-stretch mx-auto my-auto z-10" style={{ maxWidth: "var(--dsh-composer-card-max-width)" }}>
1365
- <div className="pw-hero-intro mb-4 text-center">
1366
- <div className="flex items-center justify-center gap-4 px-4 py-2 rounded-full clay-card mb-3 mx-auto w-fit animate-bounce duration-1000 border border-slate-200 dark:border-slate-800">
1367
- <div className="w-8 h-8 rounded-full bg-emerald-600 flex items-center justify-center shadow-[inset_1px_1px_2px_rgba(255,255,255,0.7)] text-white font-mono font-bold text-sm">
1368
- π
1369
- </div>
1370
- <div className="flex items-center gap-2">
1371
- <span className="font-extrabold tracking-wider text-slate-900 dark:text-slate-100 text-xs sm:text-sm">PIWEB</span>
1372
- <span className="text-slate-400">/</span>
1373
- <span className="text-[10px] tracking-widest text-slate-600 dark:text-slate-400 font-bold uppercase">WORKBENCH</span>
1374
- </div>
1375
- <span className="w-1.5 h-1.5 rounded-full bg-slate-300 dark:bg-slate-600" />
1376
- <div className="flex items-center gap-1.5 px-2.5 py-0.5 rounded-full clay-inset border border-slate-200 dark:border-slate-800">
1377
- <span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
1378
- <span className="font-mono text-[11px] text-emerald-700 dark:text-emerald-300 font-bold tracking-wider">01 / READY</span>
1379
- </div>
1380
- </div>
1381
- <h1 className="text-3xl sm:text-4xl font-extrabold tracking-tight text-slate-900 dark:text-slate-50 mb-2 drop-shadow-sm">
1382
- 让想法落到代码。
1383
- </h1>
1384
- <p className="text-sm text-slate-600 dark:text-slate-300 max-w-lg mx-auto leading-relaxed font-medium">
1385
- 规划、执行、验证。一个专注于交付的轻量工作台。
1386
- </p>
1124
+ <div className="flex h-full min-h-0 flex-col items-center justify-center px-6">
1125
+ {/* 品牌行:仅 π 标 */}
1126
+ <div className="mb-5 flex items-center justify-center gap-3">
1127
+ <PiMark size={40} />
1128
+ </div>
1387
1129
 
1388
- {/* 工作区选择器 */}
1389
- <div ref={menuRef} className="pw-hero-workspace mt-4 mb-3 flex items-center justify-center gap-2.5">
1390
- <div className="relative">
1391
- <button
1392
- type="button"
1393
- className="clay-btn clay-btn-soft flex items-center gap-2 px-3.5 py-1.5 rounded-full text-xs font-bold text-slate-800 dark:text-slate-100 border border-slate-200 dark:border-slate-700 shadow-sm"
1394
- data-open={wsMenu}
1395
- onClick={() => setWsMenu((v) => !v)}
1396
- >
1397
- <IconFolderClose16 className="text-amber-500 flex-none" size={15} />
1398
- <span className="max-w-[240px] truncate" suppressHydrationWarning>{currentLabel}</span>
1399
- <IconChevronDown14 size={13} className="text-slate-500 dark:text-slate-400" />
1400
- </button>
1401
- {wsMenu && (
1402
- <div className="clay-card popover absolute top-full left-1/2 -translate-x-1/2 z-50 w-64 mt-2 p-1.5 rounded-2xl shadow-xl border border-slate-200 dark:border-slate-800">
1403
- <div className="px-3 py-1.5 text-[11px] font-bold text-slate-500 dark:text-slate-400">选择工作区</div>
1404
- {knownCwds.map((c) => {
1405
- const itemLabel = getWorkspaceName ? getWorkspaceName(c) : basename(c);
1406
- return (
1407
- <button
1408
- key={c}
1409
- className="flex w-full items-center gap-2.5 px-3 py-2 text-left rounded-xl transition-colors text-xs font-medium"
1410
- style={{
1411
- background: cwd === c ? "var(--dsw-accent-soft)" : "transparent",
1412
- color: cwd === c ? "var(--dsw-accent)" : "inherit",
1413
- }}
1414
- onClick={() => {
1415
- setCwd(c);
1416
- setWsMenu(false);
1417
- }}
1418
- >
1419
- <IconFolderClose16 size={14} style={{ flex: "none", color: cwd === c ? "var(--dsw-accent)" : "var(--dsw-label-tertiary)" }} />
1420
- <span className="min-w-0 flex-1 truncate font-medium">{itemLabel}</span>
1421
- {cwd === c && <IconCheckOutline14 size={13} style={{ flex: "none" }} />}
1422
- </button>
1423
- );
1424
- })}
1425
- <div className="my-1 border-t border-slate-200/50 dark:border-slate-800/50" />
1426
- <button
1427
- type="button"
1428
- className="flex w-full items-center gap-2.5 px-3 py-2 text-left rounded-xl transition-colors text-xs text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 font-semibold"
1429
- onClick={pickWorkspace}
1430
- >
1431
- <IconProjectAddOutline16 size={14} style={{ flex: "none", color: "var(--dsw-label-tertiary)" }} />
1432
- {t.addWorkspace}
1433
- </button>
1434
- </div>
1435
- )}
1436
- </div>
1437
- <div className="px-3 py-1.5 rounded-full clay-inset text-slate-700 dark:text-slate-300 text-xs font-mono font-semibold flex items-center gap-1.5 border border-slate-200 dark:border-slate-800">
1438
- <span className="text-amber-500">⚡</span>
1439
- <span>在当前工作区执行</span>
1440
- </div>
1130
+ {/* 工作区芯片行 + 输入卡 同宽容器(芯片行与卡片左对齐) */}
1131
+ <div className="w-full flex flex-col items-stretch mx-auto" style={{ maxWidth: "var(--dsh-composer-card-max-width)" }}>
1132
+ <div ref={menuRef} className="mb-3 flex items-center gap-3" style={{ paddingLeft: 7 }}>
1133
+ <div className="relative">
1134
+ <button className="hero-chip" data-open={wsMenu} onClick={() => setWsMenu((v) => !v)}>
1135
+ <IconFolderClose16 className="hero-chip-icon" size={16} />
1136
+ <span className="max-w-[220px] truncate" suppressHydrationWarning>{currentLabel}</span>
1137
+ <span className="chevron">
1138
+ <IconChevronDown14 size={14} />
1139
+ </span>
1140
+ </button>
1141
+ {wsMenu && (
1142
+ <div className="popover absolute bottom-9 left-0 z-50 w-56 py-1">
1143
+ {knownCwds.map((c) => {
1144
+ const itemLabel = getWorkspaceName ? getWorkspaceName(c) : basename(c);
1145
+ return (
1146
+ <button
1147
+ key={c}
1148
+ className="flex w-full items-center gap-2.5 px-3.5 py-2 text-left transition-colors"
1149
+ style={{
1150
+ fontSize: 13,
1151
+ background: cwd === c ? "var(--dsw-accent-soft)" : "transparent",
1152
+ color: cwd === c ? "var(--dsw-accent)" : "inherit",
1153
+ }}
1154
+ onMouseEnter={(e) => {
1155
+ if (cwd !== c) e.currentTarget.style.background = "var(--dsw-hover)";
1156
+ }}
1157
+ onMouseLeave={(e) => {
1158
+ if (cwd !== c) e.currentTarget.style.background = "transparent";
1159
+ }}
1160
+ onClick={() => {
1161
+ setCwd(c);
1162
+ setWsMenu(false);
1163
+ }}
1164
+ >
1165
+ <IconFolderClose16 size={14} style={{ flex: "none", color: cwd === c ? "var(--dsw-accent)" : "var(--dsw-label-tertiary)" }} />
1166
+ <span className="min-w-0 flex-1 truncate">{itemLabel}</span>
1167
+ {cwd === c && <IconCheckOutline14 size={13} style={{ flex: "none" }} />}
1168
+ </button>
1169
+ );
1170
+ })}
1171
+ <div className="my-1" style={{ borderTop: "0.5px solid var(--dsw-border-l2)" }} />
1172
+ <button
1173
+ className="flex w-full items-center gap-2.5 px-3.5 py-2 text-left transition-colors"
1174
+ style={{ fontSize: 13 }}
1175
+ onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
1176
+ onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
1177
+ onClick={pickWorkspace}
1178
+ >
1179
+ <IconProjectAddOutline16 size={14} style={{ flex: "none", color: "var(--dsw-label-tertiary)" }} />
1180
+ {t.addWorkspace}
1181
+ </button>
1182
+ </div>
1183
+ )}
1441
1184
  </div>
1442
1185
  </div>
1443
1186
 
1444
1187
  {/* 输入卡 */}
1445
- <div className="w-full mb-4">
1446
- <ChatInput
1188
+ <ChatInput
1447
1189
  draft={draft}
1448
1190
  onDraftChange={onDraftChange}
1449
1191
  onUploadError={onUploadError}
@@ -1452,60 +1194,26 @@ function Hero({
1452
1194
  pendingUploadCount={pendingUploadCount}
1453
1195
  pendingSend={pendingSend}
1454
1196
  onSendPendingChange={onSendPendingChange}
1455
- commands={commands}
1456
- onCommand={onCommand}
1457
- isStreaming={false}
1458
- contextPercent={null}
1459
- contextTokens={null}
1460
- contextWindow={null}
1461
- contextVisible={false}
1462
- model={heroModel ? models.find((m) => m.provider === heroModel.provider && m.id === heroModel.id) ?? { provider: heroModel.provider, id: heroModel.id, name: heroModel.id, reasoning: false, contextWindow: 0 } : defaultModel}
1463
- thinkingLevel={heroThinking || undefined}
1464
- thinkingLevels={heroModelLevels}
1465
- models={models}
1466
- modelLoading={modelLoading}
1467
- modelLoadError={modelLoadError}
1468
- onRetryModels={onRetryModels}
1469
- providerNames={providerNames}
1470
- authByProvider={authByProvider}
1471
- queue={{ steering: [], followUp: [] }}
1472
- workflow={{ mode: heroMode, planStatus: "idle", goal: "" }}
1473
- onWorkflowModeChange={onSelectHeroMode}
1474
- onSend={onSend}
1475
- onSteer={() => ({ success: false })}
1476
- onAbort={() => {}}
1477
- onSelectModel={onSelectHeroModel}
1478
- onSelectLevel={onSelectHeroThinking}
1479
- />
1480
- <div className="flex items-center justify-between px-2 pt-2 text-slate-500 dark:text-slate-400 font-mono text-xs font-medium">
1481
- <div className="flex items-center gap-3">
1482
- <span className="flex items-center gap-1"><kbd className="px-1.5 py-0.5 rounded clay-inset font-bold text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-700">⏎</kbd> 发送</span>
1483
- <span className="flex items-center gap-1"><kbd className="px-1.5 py-0.5 rounded clay-inset font-bold text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-700">⇧ ⏎</kbd> 换行</span>
1484
- </div>
1485
- <div className="flex items-center gap-1.5 text-emerald-700 dark:text-emerald-400 font-semibold">
1486
- <IconShieldOutline16 size={13} />
1487
- <span>主会话无系统级沙盒隔离</span>
1488
- </div>
1489
- </div>
1490
- </div>
1491
-
1492
- {/* Telemetry bottom bar */}
1493
- <div className="flex items-center justify-center gap-4 text-slate-600 dark:text-slate-300 text-xs font-semibold tracking-wider">
1494
- <div className="flex items-center gap-1.5">
1495
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
1496
- <span>极速热加载</span>
1497
- </div>
1498
- <span>•</span>
1499
- <div className="flex items-center gap-1.5">
1500
- <span className="w-1.5 h-1.5 rounded-full bg-sky-500" />
1501
- <span>MCP 扩展全兼容</span>
1502
- </div>
1503
- <span>•</span>
1504
- <div className="flex items-center gap-1.5">
1505
- <span className="w-1.5 h-1.5 rounded-full bg-orange-500" />
1506
- <span>端侧安全加固</span>
1507
- </div>
1508
- </div>
1197
+ commands={commands}
1198
+ onCommand={onCommand}
1199
+ isStreaming={false}
1200
+ contextPercent={null}
1201
+ contextTokens={null}
1202
+ contextWindow={null}
1203
+ contextVisible={false}
1204
+ model={heroModel ? models.find((m) => m.provider === heroModel.provider && m.id === heroModel.id) ?? { provider: heroModel.provider, id: heroModel.id, name: heroModel.id, reasoning: false, contextWindow: 0 } : defaultModel}
1205
+ thinkingLevel={heroThinking || undefined}
1206
+ thinkingLevels={heroModelLevels}
1207
+ models={models}
1208
+ providerNames={providerNames}
1209
+ authByProvider={authByProvider}
1210
+ queue={{ steering: [], followUp: [] }}
1211
+ onSend={onSend}
1212
+ onSteer={() => ({ success: false })}
1213
+ onAbort={() => {}}
1214
+ onSelectModel={onSelectHeroModel}
1215
+ onSelectLevel={onSelectHeroThinking}
1216
+ />
1509
1217
  </div>
1510
1218
  </div>
1511
1219
  );