@rexvane/piweb 0.3.16 → 0.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +8 -11
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +6 -6
  5. package/.next/react-loadable-manifest.json +4 -16
  6. package/.next/required-server-files.js +1 -3
  7. package/.next/required-server-files.json +1 -3
  8. package/.next/routes-manifest.json +0 -20
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +4 -7
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -5
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
  23. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  25. package/.next/server/app/api/agent/[id]/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  28. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  29. package/.next/server/app/api/agent/new/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  31. package/.next/server/app/api/config/route.js +1 -1
  32. package/.next/server/app/api/config/route.js.nft.json +1 -1
  33. package/.next/server/app/api/files/route.js +1 -1
  34. package/.next/server/app/api/files/route.js.nft.json +1 -1
  35. package/.next/server/app/api/git/route.js +2 -2
  36. package/.next/server/app/api/git/route.js.nft.json +1 -1
  37. package/.next/server/app/api/growth/route.js +1 -1
  38. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  39. package/.next/server/app/api/health/route.js +1 -1
  40. package/.next/server/app/api/models/route.js +1 -1
  41. package/.next/server/app/api/models/route.js.nft.json +1 -1
  42. package/.next/server/app/api/pi-settings/route.js +1 -1
  43. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  44. package/.next/server/app/api/plugins/route.js +1 -1
  45. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  46. package/.next/server/app/api/prompts/route.js +1 -1
  47. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  48. package/.next/server/app/api/security/route.js +1 -1
  49. package/.next/server/app/api/security/route.js.nft.json +1 -1
  50. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  51. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  52. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  53. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  54. package/.next/server/app/api/sessions/import/route.js +3 -3
  55. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  56. package/.next/server/app/api/sessions/route.js +1 -1
  57. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  58. package/.next/server/app/api/skills/route.js +2 -2
  59. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  60. package/.next/server/app/api/update/route.js +2 -2
  61. package/.next/server/app/api/update/route.js.nft.json +1 -1
  62. package/.next/server/app/api/version/route.js +1 -1
  63. package/.next/server/app/api/version/route.js.nft.json +1 -1
  64. package/.next/server/app/api/web-auth/route.js +1 -1
  65. package/.next/server/app/api/workspaces/route.js +1 -1
  66. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  67. package/.next/server/app/icon.svg/route.js +1 -1
  68. package/.next/server/app/index.html +1 -1
  69. package/.next/server/app/index.rsc +6 -9
  70. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -7
  71. package/.next/server/app/index.segments/_full.segment.rsc +6 -9
  72. package/.next/server/app/index.segments/_tree.segment.rsc +2 -5
  73. package/.next/server/app/login/page.js +1 -1
  74. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/login.html +1 -1
  76. package/.next/server/app/login.rsc +5 -8
  77. package/.next/server/app/login.segments/_full.segment.rsc +5 -8
  78. package/.next/server/app/login.segments/_tree.segment.rsc +2 -5
  79. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -6
  80. package/.next/server/app/page.js +1 -1
  81. package/.next/server/app/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app-paths-manifest.json +8 -11
  83. package/.next/server/chunks/496.js +1 -1
  84. package/.next/server/chunks/532.js +1 -1
  85. package/.next/server/chunks/686.js +4 -8
  86. package/.next/server/middleware-build-manifest.js +1 -1
  87. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  88. package/.next/server/pages/404.html +1 -1
  89. package/.next/server/pages/500.html +1 -1
  90. package/.next/server/server-reference-manifest.json +1 -1
  91. package/.next/static/chunks/2608.ec8ff1f5767c9056.js +1 -0
  92. package/.next/static/chunks/2716.93be8ad5c970c516.js +1 -0
  93. package/.next/static/chunks/4632.112d2e893d48d188.js +1 -0
  94. package/.next/static/chunks/4673.c1fb1580efc6ace7.js +1 -0
  95. package/.next/static/chunks/4686-f4a037d609cea3f2.js +1 -0
  96. package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +1 -0
  97. package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +1 -0
  98. package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +1 -0
  99. package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +1 -0
  100. package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +1 -0
  101. package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +1 -0
  102. package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +1 -0
  103. package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +1 -0
  104. package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +1 -0
  105. package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +1 -0
  106. package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +1 -0
  107. package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +1 -0
  108. package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +1 -0
  109. package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +1 -0
  110. package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +1 -0
  111. package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +1 -0
  112. package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +1 -0
  113. package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +1 -0
  114. package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +1 -0
  115. package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +1 -0
  116. package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +1 -0
  117. package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +1 -0
  118. package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +1 -0
  119. package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +1 -0
  120. package/.next/static/chunks/app/page-b51087831582ee37.js +1 -0
  121. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +1 -0
  122. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +1 -0
  123. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +1 -0
  124. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +1 -0
  125. package/.next/static/chunks/webpack-c31263fd83d75ce6.js +1 -0
  126. package/.next/static/css/850c2fd130ffa2fc.css +1 -0
  127. package/.next/static/zsynuO_9Kx6nrXgkTUJ04/_buildManifest.js +1 -0
  128. package/.next/types/routes.d.ts +1 -4
  129. package/.next/types/validator.ts +0 -27
  130. package/README.md +17 -29
  131. package/assets/archive.jpg +0 -0
  132. package/assets/growth-1.jpg +0 -0
  133. package/assets/growth-2.jpg +0 -0
  134. package/assets/import.jpg +0 -0
  135. package/assets/main.jpg +0 -0
  136. package/assets/outline.jpg +0 -0
  137. package/assets/prompts-1.jpg +0 -0
  138. package/assets/prompts-2.jpg +0 -0
  139. package/assets/providers-1.jpg +0 -0
  140. package/assets/providers-2.jpg +0 -0
  141. package/assets/providers-3.jpg +0 -0
  142. package/assets/trace-1.jpg +0 -0
  143. package/assets/trace-2.png +0 -0
  144. package/assets/trace-3.png +0 -0
  145. package/assets/trace-4.png +0 -0
  146. package/assets/viewer-1.jpg +0 -0
  147. package/assets/viewer-2.jpg +0 -0
  148. package/bin/pi.js +50 -0
  149. package/next.config.ts +32 -7
  150. package/package.json +4 -9
  151. package/scripts/launcher.mjs +1 -1
  152. package/scripts/pick-folder.ps1 +2 -11
  153. package/scripts/release.mjs +31 -11
  154. package/scripts/verify-package.mjs +0 -53
  155. package/src/app/api/config/route.ts +1 -4
  156. package/src/app/api/growth/route.ts +14 -14
  157. package/src/app/api/models/route.ts +2 -1
  158. package/src/app/api/security/route.ts +3 -8
  159. package/src/app/api/update/route.ts +2 -8
  160. package/src/app/globals.css +90 -777
  161. package/src/app/layout.tsx +6 -13
  162. package/src/components/AppShell.tsx +229 -521
  163. package/src/components/ChatInput.tsx +26 -153
  164. package/src/components/ChatWindow.tsx +231 -51
  165. package/src/components/ContextMeter.tsx +17 -11
  166. package/src/components/FileViewer.tsx +11 -10
  167. package/src/components/GitPanel.tsx +11 -13
  168. package/src/components/ModelSelector.tsx +6 -19
  169. package/src/components/PiMark.tsx +38 -5
  170. package/src/components/ProjectPanel.tsx +111 -95
  171. package/src/components/PromptPanel.tsx +51 -132
  172. package/src/components/SessionImportSection.tsx +37 -53
  173. package/src/components/SessionSidebar.tsx +57 -159
  174. package/src/components/SettingsPanel.tsx +193 -330
  175. package/src/components/icons.tsx +0 -23
  176. package/src/hooks/useGrowth.ts +123 -156
  177. package/src/hooks/usePiWeb.ts +15 -122
  178. package/src/i18n.tsx +42 -98
  179. package/src/lib/agent-manager.ts +21 -187
  180. package/src/lib/command-validation.ts +1 -9
  181. package/src/lib/growth-service.ts +390 -166
  182. package/src/lib/growth-tracker.ts +64 -264
  183. package/src/lib/growth-turns.ts +31 -1
  184. package/src/lib/models/model-scope.ts +15 -0
  185. package/src/lib/models-service.ts +16 -0
  186. package/src/lib/pi-settings.ts +7 -0
  187. package/src/lib/pi.ts +2 -3
  188. package/src/lib/session-import/claude.ts +2 -13
  189. package/src/lib/session-import/codex.ts +8 -44
  190. package/src/lib/session-import/files.ts +0 -14
  191. package/src/lib/session-import/grok.ts +24 -54
  192. package/src/lib/session-import/index.ts +0 -5
  193. package/src/lib/session-import/types.ts +0 -12
  194. package/src/lib/session-import/writer.ts +3 -8
  195. package/src/lib/skills-service.ts +9 -16
  196. package/src/lib/theme.ts +1 -68
  197. package/src/lib/trajectory.ts +0 -1
  198. package/src/lib/types.ts +27 -36
  199. package/tsconfig.json +2 -1
  200. package/.next/server/app/api/swarm/[id]/route.js +0 -1
  201. package/.next/server/app/api/swarm/[id]/route.js.nft.json +0 -1
  202. package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +0 -1
  203. package/.next/server/app/api/swarm/route.js +0 -1
  204. package/.next/server/app/api/swarm/route.js.nft.json +0 -1
  205. package/.next/server/app/api/swarm/route_client-reference-manifest.js +0 -1
  206. package/.next/server/app/api/tests/route.js +0 -2
  207. package/.next/server/app/api/tests/route.js.nft.json +0 -1
  208. package/.next/server/app/api/tests/route_client-reference-manifest.js +0 -1
  209. package/.next/server/chunks/757.js +0 -1
  210. package/.next/server/chunks/775.js +0 -3
  211. package/.next/static/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +0 -1
  212. package/.next/static/chunks/2608.43c58236c9cce8e6.js +0 -1
  213. package/.next/static/chunks/2716.4ec42df1d51354c9.js +0 -1
  214. package/.next/static/chunks/4632.62139b565b657aac.js +0 -1
  215. package/.next/static/chunks/4673.7796104395deb8d7.js +0 -1
  216. package/.next/static/chunks/4686-17f21a006739a596.js +0 -1
  217. package/.next/static/chunks/7577.10af9bb661652da8.js +0 -1
  218. package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +0 -1
  219. package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +0 -1
  220. package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +0 -1
  221. package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +0 -1
  222. package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +0 -1
  223. package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +0 -1
  224. package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +0 -1
  225. package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +0 -1
  226. package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +0 -1
  227. package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +0 -1
  228. package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +0 -1
  229. package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +0 -1
  230. package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +0 -1
  231. package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +0 -1
  232. package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +0 -1
  233. package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +0 -1
  234. package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +0 -1
  235. package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +0 -1
  236. package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +0 -1
  237. package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +0 -1
  238. package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +0 -1
  239. package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +0 -1
  240. package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +0 -1
  241. package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +0 -1
  242. package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +0 -1
  243. package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +0 -1
  244. package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +0 -1
  245. package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +0 -1
  246. package/.next/static/chunks/app/page-14379b8060d5cf07.js +0 -1
  247. package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +0 -1
  248. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +0 -1
  249. package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +0 -1
  250. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +0 -1
  251. package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +0 -1
  252. package/.next/static/css/d0a0162cb481abe8.css +0 -1
  253. package/.next/types/app/api/swarm/[id]/route.ts +0 -353
  254. package/.next/types/app/api/swarm/route.ts +0 -353
  255. package/.next/types/app/api/tests/route.ts +0 -353
  256. package/assets/archive.png +0 -0
  257. package/assets/growth-1.png +0 -0
  258. package/assets/growth-2.png +0 -0
  259. package/assets/import.png +0 -0
  260. package/assets/main.png +0 -0
  261. package/assets/outline.png +0 -0
  262. package/assets/prompts-1.png +0 -0
  263. package/assets/prompts-2.png +0 -0
  264. package/assets/providers-1.png +0 -0
  265. package/assets/providers-2.png +0 -0
  266. package/assets/providers-3.png +0 -0
  267. package/assets/trace-1.png +0 -0
  268. package/assets/viewer-1.png +0 -0
  269. package/assets/viewer-2.png +0 -0
  270. package/scripts/managed-niubash-assets.mjs +0 -146
  271. package/scripts/prepare-niubash-packages.mjs +0 -182
  272. package/scripts/run-test-file.mjs +0 -34
  273. package/scripts/smoke-managed-niubash.mjs +0 -62
  274. package/scripts/verify-release-config.mjs +0 -24
  275. package/src/app/api/swarm/[id]/route.ts +0 -28
  276. package/src/app/api/swarm/route.ts +0 -24
  277. package/src/app/api/tests/route.ts +0 -27
  278. package/src/components/SwarmCoordinatorPod.tsx +0 -1021
  279. package/src/components/TestInspectorPod.tsx +0 -549
  280. package/src/lib/delegation.ts +0 -93
  281. package/src/lib/growth-rounds.ts +0 -84
  282. package/src/lib/managed-niubash.ts +0 -231
  283. package/src/lib/runtime-activity.ts +0 -27
  284. package/src/lib/session-import/lock.ts +0 -25
  285. package/src/lib/swarm-service.ts +0 -371
  286. package/src/lib/test-run-service.ts +0 -122
  287. package/src/lib/workflow.ts +0 -55
  288. /package/.next/static/{0t1jBUAuVaTFTXxRyIo3q → zsynuO_9Kx6nrXgkTUJ04}/_ssgManifest.js +0 -0
@@ -21,9 +21,6 @@ export function ModelSelector({
21
21
  thinkingLevel,
22
22
  thinkingLevels,
23
23
  models,
24
- modelLoading = false,
25
- modelLoadError,
26
- onRetryModels,
27
24
  providerNames,
28
25
  authByProvider,
29
26
  onSelectModel,
@@ -35,9 +32,6 @@ export function ModelSelector({
35
32
  thinkingLevel?: string;
36
33
  thinkingLevels: string[];
37
34
  models: ModelChoice[];
38
- modelLoading?: boolean;
39
- modelLoadError?: string | null;
40
- onRetryModels?: () => void;
41
35
  providerNames: Record<string, string>;
42
36
  authByProvider: Record<string, boolean>;
43
37
  onSelectModel: (provider: string, id: string) => void;
@@ -108,8 +102,7 @@ export function ModelSelector({
108
102
  });
109
103
  }, [open, pane]);
110
104
 
111
- // Pi uses this sentinel when no usable model can be restored.
112
- const hasModel = Boolean(model?.id && !(model.provider === "unknown" && model.id === "unknown"));
105
+ const hasModel = Boolean(model?.id);
113
106
  const modelName = hasModel ? (model?.name || model?.id || "") : "";
114
107
  const effortLabel = hasModel && thinkingLevel && thinkingLevel !== "off" ? thinkingLevel : undefined;
115
108
  const triggerTitle = hasModel
@@ -117,10 +110,10 @@ export function ModelSelector({
117
110
  : t.selectModel;
118
111
 
119
112
  return (
120
- <div ref={ref} className="pw-model-selector relative min-w-0">
113
+ <div ref={ref} className="relative">
121
114
  <button
122
115
  type="button"
123
- className="chip min-w-0 max-w-full"
116
+ className="chip"
124
117
  data-open={open}
125
118
  title={triggerTitle}
126
119
  aria-label={triggerTitle}
@@ -131,7 +124,7 @@ export function ModelSelector({
131
124
  setPane("model");
132
125
  }}
133
126
  >
134
- <span className="min-w-0 max-w-[220px] truncate" suppressHydrationWarning>{hasModel ? modelName : t.selectModel}</span>
127
+ <span className="max-w-[220px] truncate" suppressHydrationWarning>{hasModel ? modelName : t.selectModel}</span>
135
128
  {effortLabel && (
136
129
  <span style={{ color: "var(--dsw-label-caption)", flex: "none", fontSize: 13 }}>
137
130
  {effortLabel}
@@ -205,15 +198,9 @@ export function ModelSelector({
205
198
  {pane === "model" && (
206
199
  <div className="flex min-h-0 flex-col">
207
200
  <div ref={listRef} className="max-h-[340px] overflow-y-auto py-1">
208
- {modelLoadError && (
209
- <div role="alert" className="px-3 py-2" style={{ fontSize: 12, color: "var(--dsw-danger)" }}>
210
- <span title={modelLoadError}>{t.modelsLoadFailed}</span>{" "}
211
- {onRetryModels && <button type="button" className="underline" onClick={onRetryModels}>{t.settingsRetry}</button>}
212
- </div>
213
- )}
214
- {groups.length === 0 && (modelLoading || !modelLoadError) && (
201
+ {groups.length === 0 && (
215
202
  <div className="px-3 py-3" style={{ fontSize: 13, color: "var(--dsw-label-caption)" }}>
216
- {modelLoading ? t.modelsLoading : t.emptyModels}
203
+ {t.emptyModels}
217
204
  </div>
218
205
  )}
219
206
  {groups.map((g) => (
@@ -34,13 +34,46 @@ 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} style={{ color: "var(--dsw-accent)" }} />;
37
+ return <PiMark size={18} />;
38
38
  }
39
39
  return (
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>
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>
44
77
  </div>
45
78
  );
46
79
  }
@@ -1,40 +1,54 @@
1
1
  "use client";
2
2
 
3
3
  /**
4
- * 项目栏(侧栏与对话之间的第四列):项目生长可视化。
5
- * 头部:工作区名 + 本会话总计 + 立即快照 / 收起;工具行:范围(本步 / 本会话)、只看变更、筛选;
6
- * 中间:项目树(GrowthTree);底部:按轮浏览历史,默认跟随最新一轮。
4
+ * 项目栏(侧栏与对话之间的第四列):项目生长 = 每轮一个 git commit。
5
+ * 头部:工作区名 + 立即记录 / 收起;所选轮:提问首行、时间、文件数与行数、在对话里看;
6
+ * 工具行:范围(本轮 / 本会话累计)、只看改动、筛选;中间:项目树(GrowthTree);
7
+ * 底部:时间轴每轮一根柱(高度 = 改动行数,灰色细柱 = 你在两轮之间自己的修改),默认跟随最新一轮。
7
8
  */
8
9
  import { useEffect, useMemo, useRef, useState } from "react";
9
10
  import { GrowthTree } from "@/components/GrowthTree";
10
11
  import { IconChevronLeft14, IconChevronRight14, IconCloseOutline14, IconGitOutline16, IconRefreshOutline14, IconSearchOutline16 } from "@/components/icons";
11
- import type { GrowthApi } from "@/hooks/useGrowth";
12
+ import type { GrowthApi, GrowthEntry } from "@/hooks/useGrowth";
12
13
  import { useI18n } from "@/i18n";
13
14
  import type { GitInfo } from "@/lib/git-service";
14
15
  import type { TreeNode } from "@/lib/growth-tree";
15
- import type { GrowthStep } from "@/lib/types";
16
+ import type { GrowthChange } from "@/lib/types";
16
17
 
17
-
18
- export function stepTitle(step: GrowthStep, t: Record<string, string>): string {
19
- if (step.kind === "baseline") return t.growthKindBaseline;
20
- if (step.kind === "turn") return t.growthKindTurn;
21
- if (step.kind === "external") return t.growthKindExternal;
22
- if (step.kind === "manual") return step.label || t.growthKindManual;
23
- return step.label || step.toolName || "";
18
+ /** 「第 7 轮 · 把保存按钮改成红色」/「你的修改」 */
19
+ export function roundTitle(round: GrowthEntry, t: Record<string, string>): string {
20
+ if (round.kind !== "round") return t.growthKindUser;
21
+ const label = t.growthRoundLabel.replace("{n}", String(round.n ?? ""));
22
+ return round.title ? `${label} · ${round.title}` : label;
24
23
  }
25
24
 
26
- function stepColor(step: GrowthStep): string {
27
- if (step.kind === "baseline") return "var(--dsw-label-caption)";
28
- if (step.kind === "external") return "var(--dsw-accent)";
29
- const st = step.stats;
25
+ /** 柱的颜色:你的修改为灰;pi 的一轮按主要改动类型(删除为主红、新增为主绿、否则黄) */
26
+ function roundColor(round: GrowthEntry): string {
27
+ if (round.kind !== "round") return "var(--dsw-label-caption)";
28
+ const st = round.stats;
29
+ if (!st.added && !st.modified && !st.deleted && !st.renamed) return "var(--dsw-border-l3)";
30
30
  if (st.deleted && st.deleted >= st.added && st.deleted >= st.modified) return "var(--dsw-danger)";
31
31
  if (st.added >= st.modified) return "var(--dsw-success)";
32
32
  return "var(--dsw-warn)";
33
33
  }
34
34
 
35
+ function changedLines(round: GrowthEntry): number {
36
+ return round.stats.add + round.stats.del;
37
+ }
38
+
35
39
  function fmtTime(ts: number): string {
36
40
  const d = new Date(ts);
37
- return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
41
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
42
+ }
43
+
44
+ function sumChanges(changes: GrowthChange[]): { files: number; add: number; del: number } {
45
+ let add = 0;
46
+ let del = 0;
47
+ for (const c of changes) {
48
+ add += c.add ?? 0;
49
+ del += c.del ?? 0;
50
+ }
51
+ return { files: changes.length, add, del };
38
52
  }
39
53
 
40
54
  export function ProjectPanel({
@@ -42,9 +56,11 @@ export function ProjectPanel({
42
56
  workspaceName,
43
57
  cwd,
44
58
  hasSession,
59
+ running = false,
45
60
  onOpenFile,
46
61
  onReference,
47
62
  onOpenEditor,
63
+ onJumpToChat,
48
64
  gitRefreshKey = 0,
49
65
  onAskCommit,
50
66
  onOpenGit,
@@ -55,9 +71,13 @@ export function ProjectPanel({
55
71
  workspaceName: string;
56
72
  cwd: string;
57
73
  hasSession: boolean;
74
+ /** 本会话正在运行:时间轴末尾显示「本轮进行中」 */
75
+ running?: boolean;
58
76
  onOpenFile: (node: TreeNode) => void;
59
77
  onReference?: (path: string) => void;
60
78
  onOpenEditor?: (path: string) => void;
79
+ /** 跳到对话里这一轮的提问(用户消息 entryId) */
80
+ onJumpToChat?: (messageId: string) => void;
61
81
  gitRefreshKey?: number;
62
82
  onAskCommit?: () => void;
63
83
  onOpenGit?: () => void;
@@ -67,14 +87,15 @@ export function ProjectPanel({
67
87
  const { t } = useI18n();
68
88
  const tt = t as unknown as Record<string, string>;
69
89
  const [filter, setFilter] = useState("");
70
- const [onlyChanges, setOnlyChanges] = useState(false);
71
- const [snapshotting, setSnapshotting] = useState(false);
90
+ const [onlyChanges, setOnlyChanges] = useState(true);
91
+ const [recording, setRecording] = useState(false);
72
92
  const [notice, setNotice] = useState("");
73
93
  const [gitState, setGitState] = useState<{ cwd: string; info: GitInfo | null; error?: string } | null>(null);
74
94
  const git = gitState?.cwd === cwd ? gitState.info : null;
75
95
  const gitError = gitState?.cwd === cwd ? gitState.error ?? git?.statusError : undefined;
76
- useEffect(() => setOnlyChanges(false), [cwd]);
77
- const latestSeq = growth.steps[growth.steps.length - 1]?.seq ?? 0;
96
+ useEffect(() => setOnlyChanges(true), [cwd]);
97
+ const { rounds, selected } = growth;
98
+ const latestCommit = rounds[rounds.length - 1]?.commit ?? "";
78
99
  useEffect(() => {
79
100
  if (!cwd) return;
80
101
  const controller = new AbortController();
@@ -93,28 +114,17 @@ export function ProjectPanel({
93
114
  clearTimeout(timer);
94
115
  controller.abort();
95
116
  };
96
- }, [cwd, gitRefreshKey, latestSeq]);
97
-
98
- const totals = useMemo(() => {
99
- const out = { added: 0, modified: 0, deleted: 0, renamed: 0 };
100
- for (const c of growth.scope === "step" ? growth.changes : growth.sessionChanges) {
101
- if (c.status === "A") out.added += 1;
102
- else if (c.status === "M") out.modified += 1;
103
- else if (c.status === "D") out.deleted += 1;
104
- else out.renamed += 1;
105
- }
106
- return out;
107
- }, [growth.scope, growth.changes, growth.sessionChanges]);
117
+ }, [cwd, gitRefreshKey, latestCommit]);
108
118
 
109
- const { steps, selectedRound, rounds } = growth;
119
+ const totals = useMemo(() => sumChanges(growth.changes), [growth.changes]);
120
+ const piRounds = useMemo(() => rounds.filter((round) => round.kind === "round").length, [rounds]);
121
+ const maxLines = useMemo(() => Math.max(1, ...rounds.map(changedLines)), [rounds]);
110
122
  const railRef = useRef<HTMLDivElement>(null);
111
- const maxSteps = useMemo(() => Math.max(1, ...rounds.map((r) => r.steps.filter((step) => step.kind !== "turn").length)), [rounds]);
112
123
  useEffect(() => {
113
- if (!selectedRound) return;
114
- railRef.current?.querySelector<HTMLElement>(`[data-round="${selectedRound.id}"]`)?.scrollIntoView({ block: "nearest", inline: "nearest" });
115
- }, [selectedRound]);
116
- const selectedIdx = selectedRound ? rounds.findIndex((round) => round.id === selectedRound.id) : -1;
117
- const roundLabel = selectedRound ? !selectedRound.recorded ? t.growthUnrecorded : selectedRound.steps.every((step) => step.kind === "turn") ? t.growthNoChangesRound : stepTitle(selectedRound.steps.find((step) => step.kind !== "turn") ?? selectedRound.last!, tt) : "";
124
+ if (!selected) return;
125
+ railRef.current?.querySelector<HTMLElement>(`[data-commit="${selected.commit}"]`)?.scrollIntoView({ block: "nearest", inline: "nearest" });
126
+ }, [selected]);
127
+ const selectedIdx = selected ? rounds.findIndex((round) => round.commit === selected.commit) : -1;
118
128
  const conflicts = git?.files.filter((file) => file.kind === "conflict").length ?? 0;
119
129
  const staged = git?.files.filter((file) => file.kind !== "conflict" && file.indexStatus !== " ").length ?? 0;
120
130
  const unstaged = git?.files.filter((file) => file.kind !== "conflict" && file.workStatus !== " " && file.kind !== "untracked").length ?? 0;
@@ -126,24 +136,21 @@ export function ProjectPanel({
126
136
  return () => clearTimeout(timer);
127
137
  }, [notice]);
128
138
 
129
- const toggleOnly = () => {
130
- setOnlyChanges((value) => !value);
131
- };
132
-
133
- const snapshotNow = async () => {
134
- if (snapshotting) return;
135
- setSnapshotting(true);
139
+ const recordNow = async () => {
140
+ if (recording) return;
141
+ setRecording(true);
136
142
  try {
137
- const step = await growth.snapshotNow();
138
- if (!step) setNotice(t.growthSnapshotNone);
143
+ const round = await growth.recordNow();
144
+ if (!round) setNotice(t.growthRecordNone);
139
145
  } catch (e) {
140
146
  onError(e instanceof Error ? e.message : "failed");
141
147
  } finally {
142
- setSnapshotting(false);
148
+ setRecording(false);
143
149
  }
144
150
  };
145
151
 
146
- const emptyText = !cwd || !hasSession ? t.growthNoSession : growth.loading && !steps.length ? t.growthLoading : !growth.available ? t.growthUnavailable : steps.length ? undefined : t.growthNoSteps;
152
+ const emptyText = !cwd || !hasSession ? t.growthNoSession : growth.loading && !rounds.length ? t.growthLoading : !growth.available ? t.growthUnavailable : rounds.length ? undefined : t.growthNoRounds;
153
+ const statusText = selected?.status === "aborted" ? t.growthStatusAborted : selected?.status === "error" ? t.growthStatusError : "";
147
154
 
148
155
  return (
149
156
  <div className="pw-project flex h-full min-h-0 flex-col" data-testid="project-panel">
@@ -153,15 +160,15 @@ export function ProjectPanel({
153
160
  {workspaceName || t.projectPanel}
154
161
  </span>
155
162
  {hasSession && (
156
- <button className="icon-btn" style={{ width: 22, height: 22 }} title={t.growthSnapshotNow} onClick={() => void snapshotNow()} disabled={snapshotting}>
157
- <IconRefreshOutline14 size={13} className={snapshotting ? "piweb-spin" : undefined} />
163
+ <button className="icon-btn" style={{ width: 22, height: 22 }} title={t.growthRecordNow} onClick={() => void recordNow()} disabled={recording}>
164
+ <IconRefreshOutline14 size={13} className={recording ? "piweb-spin" : undefined} />
158
165
  </button>
159
166
  )}
160
167
  <button className="icon-btn" style={{ width: 22, height: 22 }} title={t.close} onClick={onClose}>
161
168
  <IconCloseOutline14 size={13} />
162
169
  </button>
163
170
  </div>
164
- {/* 生长快照不可用(找不到 git 等):明确提示 + 已退化为当前目录结构 */}
171
+ {/* 生长记录不可用(找不到 git 等):明确提示 + 已退化为当前目录结构 */}
165
172
  {cwd && hasSession && !growth.available && !growth.loading && (
166
173
  <div className="mx-3 mb-2 rounded-lg px-2 py-1" style={{ fontSize: 11.5, color: "var(--dsw-warn)", background: "var(--dsw-hover)" }} role="status">
167
174
  {t.growthUnavailableFallback}
@@ -190,30 +197,39 @@ export function ProjectPanel({
190
197
  {onOpenGit && <button type="button" className="pw-chip" onClick={onOpenGit}>{t.gitDetails}</button>}
191
198
  </div>
192
199
  )}
193
- {/* 总计 + 范围 */}
200
+ {/* 所选这一轮:提问首行 / 时间 · 文件数 · 行数 / 在对话里看 */}
201
+ {selected && (
202
+ <div className="px-3 pb-2" data-testid="growth-round-summary">
203
+ <div className="truncate" style={{ fontSize: 12.5, fontWeight: 600, color: "var(--dsw-label-primary)" }} title={roundTitle(selected, tt)}>
204
+ {roundTitle(selected, tt)}
205
+ </div>
206
+ <div className="mt-0.5 flex items-center gap-2" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
207
+ <span>{fmtTime(selected.ts)}</span>
208
+ <span>·</span>
209
+ <span>{t.growthRoundFiles.replace("{n}", String(totals.files))}</span>
210
+ <span style={{ color: "var(--dsw-success)" }}>+{totals.add}</span>
211
+ <span style={{ color: "var(--dsw-danger)" }}>−{totals.del}</span>
212
+ {statusText && <span style={{ color: "var(--dsw-warn)" }}>{statusText}</span>}
213
+ <span className="flex-1" />
214
+ {onJumpToChat && selected.promptIds[0] && (
215
+ <button type="button" className="pw-chip" onClick={() => onJumpToChat(selected.promptIds[0])}>
216
+ {t.growthJumpToChat}
217
+ </button>
218
+ )}
219
+ </div>
220
+ </div>
221
+ )}
222
+ {/* 范围 + 只看改动 */}
194
223
  <div className="flex items-center gap-2 px-3 pb-2" style={{ fontSize: 11.5 }}>
195
224
  <span className="pw-seg" role="radiogroup">
196
- {(["step", "session"] as const).map((s) => (
225
+ {(["round", "session"] as const).map((s) => (
197
226
  <button key={s} type="button" role="radio" aria-checked={growth.scope === s} data-active={growth.scope === s} onClick={() => growth.setScope(s)}>
198
- {s === "step" ? t.growthScopeStep : t.growthScopeSession}
227
+ {s === "round" ? t.growthScopeRound : t.growthScopeSession}
199
228
  </button>
200
229
  ))}
201
230
  </span>
202
- <span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-caption)" }} title={`${t.growthStatA} ${totals.added} · ${t.growthStatM} ${totals.modified} · ${t.growthStatD} ${totals.deleted} · ${t.growthStatR} ${totals.renamed}`}>
203
- {notice ? (
204
- <span style={{ color: "var(--dsw-label-secondary)" }}>{notice}</span>
205
- ) : (
206
- <>
207
- <span style={{ color: "var(--dsw-success)" }}>+{totals.added}</span>
208
- {" "}
209
- <span style={{ color: "var(--dsw-warn)" }}>~{totals.modified}</span>
210
- {" "}
211
- <span style={{ color: "var(--dsw-danger)" }}>−{totals.deleted}</span>
212
- {totals.renamed ? <span style={{ color: "var(--dsw-accent)" }}> ⇄{totals.renamed}</span> : null}
213
- </>
214
- )}
215
- </span>
216
- <button type="button" className="pw-chip" data-active={onlyChanges} aria-pressed={onlyChanges} onClick={toggleOnly}>
231
+ <span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-secondary)" }}>{notice}</span>
232
+ <button type="button" className="pw-chip" data-active={onlyChanges} aria-pressed={onlyChanges} onClick={() => setOnlyChanges((value) => !value)}>
217
233
  {t.growthOnlyChanges}
218
234
  </button>
219
235
  </div>
@@ -223,7 +239,6 @@ export function ProjectPanel({
223
239
  <input
224
240
  value={filter}
225
241
  onChange={(e) => setFilter(e.target.value)}
226
- aria-label={t.growthFilter}
227
242
  placeholder={t.growthFilter}
228
243
  className="min-w-0 flex-1 bg-transparent"
229
244
  style={{ fontSize: 12 }}
@@ -242,14 +257,14 @@ export function ProjectPanel({
242
257
  {growth.error}
243
258
  </div>
244
259
  )}
245
- {growth.selected?.truncated && growth.scope === "step" && (
260
+ {selected?.truncated && growth.scope === "round" && (
246
261
  <div className="mx-3 mb-1 rounded-lg px-2 py-1" style={{ fontSize: 11, color: "var(--dsw-warn)", background: "var(--dsw-hover)" }}>
247
262
  {t.growthTruncated}
248
263
  </div>
249
264
  )}
250
- {selectedRound && !selectedRound.recorded && (
265
+ {selected && growth.scope === "round" && !growth.changes.length && !emptyText && (
251
266
  <div className="mx-3 mb-1 rounded-lg px-2 py-1" style={{ fontSize: 11, color: "var(--dsw-label-caption)", background: "var(--dsw-hover)" }} role="status">
252
- {t.growthUnrecorded}
267
+ {t.growthNoChangesRound}
253
268
  </div>
254
269
  )}
255
270
  {/* 树 */}
@@ -258,7 +273,7 @@ export function ProjectPanel({
258
273
  expanded={growth.expanded}
259
274
  fresh={growth.fresh}
260
275
  filter={filter}
261
- onlyChanges={onlyChanges && steps.length > 1}
276
+ onlyChanges={onlyChanges && growth.changes.length > 0}
262
277
  onToggleDir={growth.toggleDir}
263
278
  onExpandLazy={growth.expandLazy}
264
279
  onOpenFile={onOpenFile}
@@ -266,29 +281,30 @@ export function ProjectPanel({
266
281
  onOpenEditor={onOpenEditor}
267
282
  emptyText={emptyText}
268
283
  />
269
- {/* 轮次导航 */}
284
+ {/* 时间轴 */}
270
285
  {hasSession && (
271
286
  <div className="pw-timeline hairline-t" data-testid="growth-timeline">
272
- {/* 时间轴:每轮一根柱,高度 = 本轮快照步数,颜色 = 首步的主要动作;点柱选轮 */}
273
- {rounds.length > 0 && (
287
+ {(rounds.length > 0 || running) && (
274
288
  <div ref={railRef} className="pw-tl-rail">
275
289
  {rounds.map((round) => {
276
- const activity = round.steps.filter((step) => step.kind !== "turn").length;
277
- const h = Math.max(4, Math.round((activity / maxSteps) * 24));
278
- const head = round.steps.find((step) => step.kind !== "turn") ?? round.last;
290
+ // 对数缩放:几十行和几千行的轮都看得出来,又不会一根柱子压扁其余
291
+ const lines = changedLines(round);
292
+ const h = round.changes.length || lines ? Math.max(5, Math.round(4 + (Math.log1p(lines) / Math.log1p(maxLines)) * 20)) : 3;
279
293
  return (
280
294
  <button
281
- key={round.id}
295
+ key={round.commit}
282
296
  type="button"
283
297
  className="pw-tl-bar"
284
- data-round={round.id}
285
- data-active={selectedRound?.id === round.id || undefined}
286
- style={{ height: h, background: head && round.recorded ? stepColor(head) : "var(--dsw-label-caption)" }}
287
- title={`${t.growthRoundLabel.replace("{n}", String(round.id))} · ${round.recorded ? activity ? stepTitle(head!, tt) : t.growthNoChangesRound : t.growthUnrecorded} · ${fmtTime(round.last?.ts ?? round.startedAt)} · ${t.growthStepsCount.replace("{n}", String(activity))}`}
288
- onClick={() => growth.selectRound(round.id)}
298
+ data-commit={round.commit}
299
+ data-kind={round.kind}
300
+ data-active={selected?.commit === round.commit || undefined}
301
+ style={{ height: h, background: roundColor(round) }}
302
+ title={`${roundTitle(round, tt)} · ${fmtTime(round.ts)} · ${t.growthRoundFiles.replace("{n}", String(round.changes.length))} +${round.stats.add} −${round.stats.del}`}
303
+ onClick={() => growth.select(round.commit)}
289
304
  />
290
305
  );
291
306
  })}
307
+ {running && <span className="pw-tl-bar" data-running="" title={t.growthRunning} aria-label={t.growthRunning} />}
292
308
  </div>
293
309
  )}
294
310
  <div className="flex items-center gap-1 px-2 pb-2 pt-1" style={{ fontSize: 11.5 }}>
@@ -298,12 +314,12 @@ export function ProjectPanel({
298
314
  <button className="icon-btn" style={{ width: 20, height: 20 }} title={t.growthNext} onClick={growth.next} disabled={selectedIdx < 0 || selectedIdx >= rounds.length - 1}>
299
315
  <IconChevronRight14 size={12} />
300
316
  </button>
301
- <span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-secondary)" }} title={selectedRound ? `${roundLabel} · ${fmtTime(selectedRound.last?.ts ?? selectedRound.startedAt)}` : undefined}>
302
- {selectedRound ? (
303
- <>
304
- <span style={{ color: "var(--dsw-label-caption)" }}>{t.growthRoundPosition.replace("{current}", String(selectedIdx + 1)).replace("{total}", String(rounds.length))}</span> {roundLabel}
305
- </>
306
- ) : t.growthRoundPosition.replace("{current}", "0").replace("{total}", "0")}
317
+ <span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-secondary)" }}>
318
+ {selected?.n
319
+ ? t.growthRoundPosition.replace("{current}", String(selected.n)).replace("{total}", String(piRounds))
320
+ : selected
321
+ ? t.growthKindUser
322
+ : t.growthRoundPosition.replace("{current}", "0").replace("{total}", String(piRounds))}
307
323
  </span>
308
324
  <button type="button" className="pw-chip" data-active={growth.following} onClick={growth.follow} title={t.growthFollow}>
309
325
  {growth.following ? t.growthFollowing : t.growthFollow}