@rexvane/piweb 0.3.21 → 0.3.23

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 (343) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +9 -6
  3. package/.next/build-manifest.json +5 -5
  4. package/.next/next-minimal-server.js.nft.json +1 -1
  5. package/.next/next-server.js.nft.json +1 -1
  6. package/.next/prerender-manifest.json +7 -7
  7. package/.next/react-loadable-manifest.json +18 -11
  8. package/.next/required-server-files.js +16 -1
  9. package/.next/required-server-files.json +16 -1
  10. package/.next/routes-manifest.json +20 -0
  11. package/.next/server/app/_global-error/page.js +2 -2
  12. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  13. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_global-error.html +1 -1
  15. package/.next/server/app/_global-error.meta +8 -0
  16. package/.next/server/app/_global-error.rsc +15 -11
  17. package/.next/server/app/_global-error.segments/_full.segment.rsc +15 -11
  18. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +20 -16
  19. package/.next/server/app/_global-error.segments/_tree.segment.rsc +3 -1
  20. package/.next/server/app/_not-found/page.js +1 -1
  21. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/server/app/_not-found.html +1 -1
  24. package/.next/server/app/_not-found.meta +8 -0
  25. package/.next/server/app/_not-found.rsc +17 -13
  26. package/.next/server/app/_not-found.segments/_full.segment.rsc +17 -13
  27. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +24 -20
  28. package/.next/server/app/_not-found.segments/_tree.segment.rsc +4 -2
  29. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  30. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  31. package/.next/server/app/api/agent/[id]/route.js +1 -1
  32. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  33. package/.next/server/app/api/agent/[id]/side-chat/route.js +5 -0
  34. package/.next/server/app/api/agent/[id]/side-chat/route.js.nft.json +1 -0
  35. package/.next/server/app/api/agent/[id]/side-chat/route_client-reference-manifest.js +1 -0
  36. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  37. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  38. package/.next/server/app/api/agent/new/route.js +1 -1
  39. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  40. package/.next/server/app/api/config/route.js +1 -1
  41. package/.next/server/app/api/config/route.js.nft.json +1 -1
  42. package/.next/server/app/api/files/route.js +1 -1
  43. package/.next/server/app/api/files/route.js.nft.json +1 -1
  44. package/.next/server/app/api/fs/route.js +1 -1
  45. package/.next/server/app/api/fs/route.js.nft.json +1 -1
  46. package/.next/server/app/api/git/route.js +4 -4
  47. package/.next/server/app/api/git/route.js.nft.json +1 -1
  48. package/.next/server/app/api/github/route.js +5 -0
  49. package/.next/server/app/api/github/route.js.nft.json +1 -0
  50. package/.next/server/app/api/github/route_client-reference-manifest.js +1 -0
  51. package/.next/server/app/api/growth/route.js +1 -1
  52. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  53. package/.next/server/app/api/health/route.js +1 -1
  54. package/.next/server/app/api/health/route.js.nft.json +1 -1
  55. package/.next/server/app/api/models/defaults/route.js +1 -0
  56. package/.next/server/app/api/models/defaults/route.js.nft.json +1 -0
  57. package/.next/server/app/api/models/defaults/route_client-reference-manifest.js +1 -0
  58. package/.next/server/app/api/models/route.js +1 -1
  59. package/.next/server/app/api/models/route.js.nft.json +1 -1
  60. package/.next/server/app/api/pi-settings/route.js +1 -1
  61. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  62. package/.next/server/app/api/plugins/route.js +1 -1
  63. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  64. package/.next/server/app/api/prompts/route.js +1 -1
  65. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  66. package/.next/server/app/api/remote/route.js +4 -4
  67. package/.next/server/app/api/remote/route.js.nft.json +1 -1
  68. package/.next/server/app/api/security/route.js +1 -1
  69. package/.next/server/app/api/security/route.js.nft.json +1 -1
  70. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  71. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  72. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  73. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  74. package/.next/server/app/api/sessions/import/route.js +2 -2
  75. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  76. package/.next/server/app/api/sessions/route.js +1 -1
  77. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  78. package/.next/server/app/api/skills/route.js +2 -2
  79. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  80. package/.next/server/app/api/update/route.js +2 -2
  81. package/.next/server/app/api/update/route.js.nft.json +1 -1
  82. package/.next/server/app/api/version/route.js +1 -1
  83. package/.next/server/app/api/version/route.js.nft.json +1 -1
  84. package/.next/server/app/api/web-auth/route.js +2 -2
  85. package/.next/server/app/api/web-auth/route.js.nft.json +1 -1
  86. package/.next/server/app/api/workspaces/route.js +1 -1
  87. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  88. package/.next/server/app/icon.svg/route.js +1 -1
  89. package/.next/server/app/icon.svg/route.js.nft.json +1 -1
  90. package/.next/server/app/icon.svg.meta +1 -1
  91. package/.next/server/app/index.html +1 -1
  92. package/.next/server/app/index.meta +8 -0
  93. package/.next/server/app/index.rsc +20 -13
  94. package/.next/server/app/index.segments/__PAGE__.segment.rsc +26 -19
  95. package/.next/server/app/index.segments/_full.segment.rsc +20 -13
  96. package/.next/server/app/index.segments/_tree.segment.rsc +4 -2
  97. package/.next/server/app/login/page.js +1 -1
  98. package/.next/server/app/login/page.js.nft.json +1 -1
  99. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  100. package/.next/server/app/login.html +1 -1
  101. package/.next/server/app/login.meta +8 -0
  102. package/.next/server/app/login.rsc +23 -17
  103. package/.next/server/app/login.segments/_full.segment.rsc +23 -17
  104. package/.next/server/app/login.segments/_tree.segment.rsc +4 -2
  105. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +30 -24
  106. package/.next/server/app/page.js +1 -1
  107. package/.next/server/app/page.js.nft.json +1 -1
  108. package/.next/server/app/page_client-reference-manifest.js +1 -1
  109. package/.next/server/app-paths-manifest.json +9 -6
  110. package/.next/server/chunks/1813.js +23 -0
  111. package/.next/server/chunks/2430.js +377 -0
  112. package/.next/server/chunks/2970.js +305 -0
  113. package/.next/server/chunks/3421.js +1 -0
  114. package/.next/server/chunks/3924.js +6 -0
  115. package/.next/server/chunks/4807.js +1 -0
  116. package/.next/server/chunks/538.js +1 -1
  117. package/.next/server/chunks/5393.js +1 -0
  118. package/.next/server/chunks/5802.js +2 -0
  119. package/.next/server/chunks/6229.js +1 -0
  120. package/.next/server/chunks/7496.js +1 -0
  121. package/.next/server/chunks/7662.js +2 -0
  122. package/.next/server/chunks/8686.js +1 -0
  123. package/.next/server/chunks/{593.js → 974.js} +1 -1
  124. package/.next/server/chunks/{920.js → 9920.js} +2 -2
  125. package/.next/server/functions-config-manifest.json +2 -1
  126. package/.next/server/instrumentation.js +1 -1
  127. package/.next/server/instrumentation.js.nft.json +1 -1
  128. package/.next/server/middleware-build-manifest.js +1 -1
  129. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  130. package/.next/server/middleware.js +185 -23
  131. package/.next/server/middleware.js.nft.json +1 -1
  132. package/.next/server/pages/404.html +1 -1
  133. package/.next/server/pages/500.html +1 -1
  134. package/.next/server/preview-props.json +5 -0
  135. package/.next/server/server-reference-manifest.json +1 -1
  136. package/.next/server/webpack-runtime.js +1 -1
  137. package/.next/static/chunks/2716.44fdf1b21454b961.js +1 -0
  138. package/.next/static/chunks/316-698d10eb465bd8ef.js +1 -0
  139. package/.next/static/chunks/3794-0978865cda773008.js +52 -0
  140. package/.next/static/chunks/4673.e261707dce391be7.js +1 -0
  141. package/.next/static/chunks/4686-0cd87513f9ea3538.js +1 -0
  142. package/.next/static/chunks/4778.eddd3a5813f85824.js +1 -0
  143. package/.next/static/chunks/4bd1b696-e199e788e58acf79.js +1 -0
  144. package/.next/static/chunks/{5173.91ddb803785009f8.js → 5173.dfd1cbb795b9a21c.js} +1 -1
  145. package/.next/static/chunks/618.949981da99be7182.js +4 -0
  146. package/.next/static/chunks/7411.5f79671208209b13.js +1 -0
  147. package/.next/static/chunks/app/_global-error/page-cbaaee0d7aacb85a.js +1 -0
  148. package/.next/static/chunks/app/api/agent/[id]/events/route-cbaaee0d7aacb85a.js +1 -0
  149. package/.next/static/chunks/app/api/agent/[id]/route-cbaaee0d7aacb85a.js +1 -0
  150. package/.next/static/chunks/app/api/agent/[id]/side-chat/route-cbaaee0d7aacb85a.js +1 -0
  151. package/.next/static/chunks/app/api/agent/[id]/tree/route-cbaaee0d7aacb85a.js +1 -0
  152. package/.next/static/chunks/app/api/agent/new/route-cbaaee0d7aacb85a.js +1 -0
  153. package/.next/static/chunks/app/api/config/route-cbaaee0d7aacb85a.js +1 -0
  154. package/.next/static/chunks/app/api/files/route-cbaaee0d7aacb85a.js +1 -0
  155. package/.next/static/chunks/app/api/fs/route-cbaaee0d7aacb85a.js +1 -0
  156. package/.next/static/chunks/app/api/git/route-cbaaee0d7aacb85a.js +1 -0
  157. package/.next/static/chunks/app/api/github/route-cbaaee0d7aacb85a.js +1 -0
  158. package/.next/static/chunks/app/api/growth/route-cbaaee0d7aacb85a.js +1 -0
  159. package/.next/static/chunks/app/api/health/route-cbaaee0d7aacb85a.js +1 -0
  160. package/.next/static/chunks/app/api/models/defaults/route-cbaaee0d7aacb85a.js +1 -0
  161. package/.next/static/chunks/app/api/models/route-cbaaee0d7aacb85a.js +1 -0
  162. package/.next/static/chunks/app/api/pi-settings/route-cbaaee0d7aacb85a.js +1 -0
  163. package/.next/static/chunks/app/api/plugins/route-cbaaee0d7aacb85a.js +1 -0
  164. package/.next/static/chunks/app/api/prompts/route-cbaaee0d7aacb85a.js +1 -0
  165. package/.next/static/chunks/app/api/remote/route-cbaaee0d7aacb85a.js +1 -0
  166. package/.next/static/chunks/app/api/security/route-cbaaee0d7aacb85a.js +1 -0
  167. package/.next/static/chunks/app/api/sessions/[id]/export/route-cbaaee0d7aacb85a.js +1 -0
  168. package/.next/static/chunks/app/api/sessions/[id]/route-cbaaee0d7aacb85a.js +1 -0
  169. package/.next/static/chunks/app/api/sessions/import/route-cbaaee0d7aacb85a.js +1 -0
  170. package/.next/static/chunks/app/api/sessions/route-cbaaee0d7aacb85a.js +1 -0
  171. package/.next/static/chunks/app/api/skills/route-cbaaee0d7aacb85a.js +1 -0
  172. package/.next/static/chunks/app/api/update/route-cbaaee0d7aacb85a.js +1 -0
  173. package/.next/static/chunks/app/api/version/route-cbaaee0d7aacb85a.js +1 -0
  174. package/.next/static/chunks/app/api/web-auth/route-cbaaee0d7aacb85a.js +1 -0
  175. package/.next/static/chunks/app/api/workspaces/route-cbaaee0d7aacb85a.js +1 -0
  176. package/.next/static/chunks/app/page-dc3cb41d91c7432e.js +1 -0
  177. package/.next/static/chunks/framework-47b723d27f4a4963.js +1 -0
  178. package/.next/static/chunks/main-5403f0764579e5fe.js +5 -0
  179. package/.next/static/chunks/next/dist/client/components/builtin/app-error-cbaaee0d7aacb85a.js +1 -0
  180. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cbaaee0d7aacb85a.js +1 -0
  181. package/.next/static/chunks/next/dist/client/components/builtin/not-found-cbaaee0d7aacb85a.js +1 -0
  182. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cbaaee0d7aacb85a.js +1 -0
  183. package/.next/static/chunks/webpack-2ac3d76b81895f5b.js +1 -0
  184. package/.next/static/css/9fc559c2c4ad1d0f.css +1 -0
  185. package/.next/static/css/c6e2227c36ec2fd6.css +1 -0
  186. package/.next/static/css/{2959bd6b626b7ffe.css → f15a853af8b5127f.css} +1 -1
  187. package/.next/static/hYPL1-dmhKFjLS3f5yoqN/_buildManifest.js +1 -0
  188. package/.next/types/app/api/agent/[id]/events/route.ts +3 -1
  189. package/.next/types/app/api/agent/[id]/route.ts +3 -1
  190. package/.next/types/app/api/agent/[id]/side-chat/route.ts +355 -0
  191. package/.next/types/app/api/agent/[id]/tree/route.ts +3 -1
  192. package/.next/types/app/api/agent/new/route.ts +3 -1
  193. package/.next/types/app/api/config/route.ts +3 -1
  194. package/.next/types/app/api/files/route.ts +3 -1
  195. package/.next/types/app/api/fs/route.ts +3 -1
  196. package/.next/types/app/api/git/route.ts +3 -1
  197. package/.next/types/app/api/github/route.ts +355 -0
  198. package/.next/types/app/api/growth/route.ts +3 -1
  199. package/.next/types/app/api/health/route.ts +3 -1
  200. package/.next/types/app/api/models/defaults/route.ts +355 -0
  201. package/.next/types/app/api/models/route.ts +3 -1
  202. package/.next/types/app/api/pi-settings/route.ts +3 -1
  203. package/.next/types/app/api/plugins/route.ts +3 -1
  204. package/.next/types/app/api/prompts/route.ts +3 -1
  205. package/.next/types/app/api/remote/route.ts +3 -1
  206. package/.next/types/app/api/security/route.ts +3 -1
  207. package/.next/types/app/api/sessions/[id]/export/route.ts +3 -1
  208. package/.next/types/app/api/sessions/[id]/route.ts +3 -1
  209. package/.next/types/app/api/sessions/import/route.ts +3 -1
  210. package/.next/types/app/api/sessions/route.ts +3 -1
  211. package/.next/types/app/api/skills/route.ts +3 -1
  212. package/.next/types/app/api/update/route.ts +3 -1
  213. package/.next/types/app/api/version/route.ts +3 -1
  214. package/.next/types/app/api/web-auth/route.ts +3 -1
  215. package/.next/types/app/api/workspaces/route.ts +3 -1
  216. package/.next/types/app/layout.ts +4 -1
  217. package/.next/types/app/login/page.ts +4 -1
  218. package/.next/types/app/page.ts +4 -1
  219. package/.next/types/cache-life.d.ts +11 -43
  220. package/.next/types/routes.d.ts +4 -1
  221. package/.next/types/validator.ts +86 -2
  222. package/next.config.ts +2 -0
  223. package/package.json +8 -2
  224. package/scripts/release.mjs +3 -7
  225. package/scripts/runtime/check-installed.mjs +66 -0
  226. package/scripts/runtime/pi-packages.mjs +51 -0
  227. package/scripts/verify-package.mjs +29 -7
  228. package/src/app/api/agent/[id]/side-chat/route.ts +77 -0
  229. package/src/app/api/files/route.ts +5 -2
  230. package/src/app/api/github/route.ts +26 -0
  231. package/src/app/api/models/defaults/route.ts +18 -0
  232. package/src/app/api/models/route.ts +24 -1
  233. package/src/app/globals.css +1748 -0
  234. package/src/components/AppShell.tsx +145 -43
  235. package/src/components/ChatInput.tsx +264 -79
  236. package/src/components/ChatWindow.tsx +13 -23
  237. package/src/components/ContextMeter.tsx +213 -144
  238. package/src/components/DrawerPanel.tsx +1 -1
  239. package/src/components/FileViewer.tsx +89 -8
  240. package/src/components/GitPanel.tsx +110 -40
  241. package/src/components/GrowthTree.tsx +30 -5
  242. package/src/components/ModelSelector.tsx +21 -5
  243. package/src/components/ProjectPanel.tsx +55 -30
  244. package/src/components/PromptPanel.tsx +2 -4
  245. package/src/components/ProviderSetupModal.module.css +20 -0
  246. package/src/components/ProviderSetupModal.tsx +134 -29
  247. package/src/components/SettingsPanel.tsx +252 -27
  248. package/src/components/TrajectoryView.tsx +4 -6
  249. package/src/components/chat/reading-anchor.ts +81 -0
  250. package/src/components/composer/ComposerImages.tsx +326 -0
  251. package/src/components/side-chat/SideChatPanel.module.css +131 -0
  252. package/src/components/side-chat/SideChatPanel.tsx +189 -0
  253. package/src/components/side-chat/icons.tsx +8 -0
  254. package/src/components/side-chat/request-history.ts +29 -0
  255. package/src/components/side-chat/store.ts +248 -0
  256. package/src/hooks/useFileViewer.ts +6 -4
  257. package/src/hooks/useGrowth.ts +3 -0
  258. package/src/hooks/usePiWeb.ts +12 -2
  259. package/src/hooks/useWorkspaceFiles.ts +93 -0
  260. package/src/i18n.tsx +150 -2
  261. package/src/lib/agent-manager.ts +135 -104
  262. package/src/lib/files-service.ts +166 -3
  263. package/src/lib/format.ts +56 -0
  264. package/src/lib/git-service.ts +45 -13
  265. package/src/lib/github-identity.ts +245 -0
  266. package/src/lib/growth-service.ts +6 -2
  267. package/src/lib/hero-model-memory.ts +28 -0
  268. package/src/lib/model-defaults.ts +105 -0
  269. package/src/lib/model-draft.ts +257 -9
  270. package/src/lib/model-endpoint.ts +23 -0
  271. package/src/lib/model-files.ts +33 -0
  272. package/src/lib/models-service.ts +240 -78
  273. package/src/lib/path-security.ts +24 -32
  274. package/src/lib/pi.ts +27 -2
  275. package/src/lib/process-format.ts +3 -2
  276. package/src/lib/session/metrics.ts +31 -0
  277. package/src/lib/session-reader.ts +1 -1
  278. package/src/lib/side-chat/context.ts +123 -0
  279. package/src/lib/side-chat/service.ts +191 -0
  280. package/src/lib/side-chat/types.ts +61 -0
  281. package/src/lib/thinking-level.ts +39 -0
  282. package/src/lib/trajectory.ts +17 -3
  283. package/src/lib/types.ts +12 -1
  284. package/src/lib/update-service.ts +4 -6
  285. package/src/lib/workspace-index.ts +270 -0
  286. package/.next/server/chunks/229.js +0 -1
  287. package/.next/server/chunks/393.js +0 -1
  288. package/.next/server/chunks/421.js +0 -1
  289. package/.next/server/chunks/430.js +0 -193
  290. package/.next/server/chunks/496.js +0 -1
  291. package/.next/server/chunks/662.js +0 -2
  292. package/.next/server/chunks/686.js +0 -1
  293. package/.next/server/chunks/741.js +0 -165
  294. package/.next/server/chunks/802.js +0 -2
  295. package/.next/server/chunks/807.js +0 -1
  296. package/.next/server/chunks/813.js +0 -1
  297. package/.next/server/chunks/924.js +0 -6
  298. package/.next/static/chunks/2716.7ad073038e94a802.js +0 -1
  299. package/.next/static/chunks/316-5ea443ee6b512aa9.js +0 -1
  300. package/.next/static/chunks/3794-3679f1c05160977c.js +0 -52
  301. package/.next/static/chunks/4673.8a5a9e6855350e34.js +0 -1
  302. package/.next/static/chunks/4686-882531aef1d2e250.js +0 -1
  303. package/.next/static/chunks/4778.305dc6abb9889627.js +0 -1
  304. package/.next/static/chunks/4bd1b696-93bf506b236d6248.js +0 -1
  305. package/.next/static/chunks/618.ee5d5bf1c647e483.js +0 -4
  306. package/.next/static/chunks/839.bfd2db36483faa3e.js +0 -1
  307. package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +0 -1
  308. package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +0 -1
  309. package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +0 -1
  310. package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +0 -1
  311. package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +0 -1
  312. package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +0 -1
  313. package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +0 -1
  314. package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +0 -1
  315. package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +0 -1
  316. package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +0 -1
  317. package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +0 -1
  318. package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +0 -1
  319. package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +0 -1
  320. package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +0 -1
  321. package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +0 -1
  322. package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +0 -1
  323. package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +0 -1
  324. package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +0 -1
  325. package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +0 -1
  326. package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +0 -1
  327. package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +0 -1
  328. package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +0 -1
  329. package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +0 -1
  330. package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +0 -1
  331. package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +0 -1
  332. package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +0 -1
  333. package/.next/static/chunks/app/page-c04af72e549695bd.js +0 -1
  334. package/.next/static/chunks/framework-c4e651fe89edfa91.js +0 -1
  335. package/.next/static/chunks/main-6159b22a8cb0b7c5.js +0 -5
  336. package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +0 -1
  337. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +0 -1
  338. package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +0 -1
  339. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +0 -1
  340. package/.next/static/chunks/webpack-9b4bb574e5817654.js +0 -1
  341. package/.next/static/css/ea27b9e30f76303f.css +0 -1
  342. package/.next/static/sNX5GQkOWuK5YEID1hT-C/_buildManifest.js +0 -1
  343. /package/.next/static/{sNX5GQkOWuK5YEID1hT-C → hYPL1-dmhKFjLS3f5yoqN}/_ssgManifest.js +0 -0
@@ -39,10 +39,19 @@ interface GitInfo {
39
39
  statusError?: string;
40
40
  }
41
41
 
42
+ interface GithubIdentity {
43
+ login: string;
44
+ name?: string;
45
+ avatar?: string;
46
+ url?: string;
47
+ repo?: string;
48
+ }
49
+
42
50
  interface DiffResult {
43
51
  patch: string;
44
52
  truncated: boolean;
45
53
  binary: boolean;
54
+ stats?: { add: number; del: number };
46
55
  title?: string;
47
56
  }
48
57
 
@@ -75,6 +84,7 @@ export function GitPanel({
75
84
  }) {
76
85
  const { t } = useI18n();
77
86
  const [info, setInfo] = useState<GitInfo | null>(null);
87
+ const [identity, setIdentity] = useState<GithubIdentity | null>(null);
78
88
  const [loading, setLoading] = useState(false);
79
89
  const [statusError, setStatusError] = useState("");
80
90
  const [diffError, setDiffError] = useState("");
@@ -105,6 +115,23 @@ export function GitPanel({
105
115
  }
106
116
  }, [cwd]);
107
117
 
118
+ // GitHub 身份单独拉:要走网络(gh / 头像),不能拖慢上面那条纯本地的状态读取
119
+ useEffect(() => {
120
+ setIdentity(null);
121
+ if (!cwd) return;
122
+ const controller = new AbortController();
123
+ void (async () => {
124
+ try {
125
+ const r = await fetch(`/api/github?cwd=${encodeURIComponent(cwd)}`, { signal: controller.signal });
126
+ const j = await r.json();
127
+ if (j?.success && j.data?.identity) setIdentity(j.data.identity as GithubIdentity);
128
+ } catch {
129
+ /* 取不到就只显示分支,不报错 */
130
+ }
131
+ })();
132
+ return () => controller.abort();
133
+ }, [cwd, refreshKey]);
134
+
108
135
  useEffect(() => {
109
136
  setInfo(null);
110
137
  setView(null);
@@ -165,13 +192,31 @@ export function GitPanel({
165
192
  const error = view ? diffError : statusError || info?.statusError || "";
166
193
 
167
194
  const parsed = useMemo(() => (diff?.patch ? parseUnifiedDiff(diff.patch) : null), [diff]);
168
- const stats = parsed ? diffStats(parsed) : null;
195
+ // 新服务端给完整 Git 统计;旧服务端只在 patch 完整时才允许本地计数。
196
+ const stats = diff?.binary ? null : diff?.stats ?? (parsed && !diff?.truncated ? diffStats(parsed) : null);
169
197
 
170
198
  const conflicts = info?.files.filter((f) => f.kind === "conflict") ?? [];
171
199
  const staged = info?.files.filter((f) => f.kind !== "conflict" && f.kind !== "untracked" && f.indexStatus !== " ") ?? [];
172
200
  const unstaged = info?.files.filter((f) => f.kind !== "conflict" && f.kind !== "untracked" && f.workStatus !== " ") ?? [];
173
201
  const untracked = info?.files.filter((f) => f.kind === "untracked") ?? [];
174
202
 
203
+ /**
204
+ * 按 Git 的三个区呈现(分支 / 上游那些是「版本库」侧,在别的行):
205
+ * - 暂存区:`git add` 过的,下次 commit 会带上;
206
+ * - 工作区:改了还没 add 的 + 未跟踪的新文件(都属于「还没进暂存区」);
207
+ * - 冲突单独置顶(不先解决,提交走不下去)。
208
+ * 未跟踪文件要整份当新增行看,模式逐文件给,不能按组统一。
209
+ */
210
+ const areas: Array<{ key: string; label: string; entries: Array<{ file: GitFileEntry; mode: "staged" | "worktree" | "untracked" }>; danger?: boolean }> = [
211
+ { key: "conflict", label: t.gitConflict, entries: conflicts.map((file) => ({ file, mode: "worktree" as const })), danger: true },
212
+ { key: "staged", label: t.gitStagingArea, entries: staged.map((file) => ({ file, mode: "staged" as const })) },
213
+ {
214
+ key: "worktree",
215
+ label: t.gitWorkingTree,
216
+ entries: [...unstaged.map((file) => ({ file, mode: "worktree" as const })), ...untracked.map((file) => ({ file, mode: "untracked" as const }))],
217
+ },
218
+ ].filter((area) => area.entries.length > 0);
219
+
175
220
  const openFile = (f: GitFileEntry, mode: "staged" | "worktree" | "untracked", label: string) => changeView({ kind: "file", path: f.path, mode, label });
176
221
 
177
222
  const headerTitle = view
@@ -247,6 +292,39 @@ export function GitPanel({
247
292
  )}
248
293
  {!view && info?.isRepo && (
249
294
  <div className="flex flex-col gap-4">
295
+ {/* GitHub 身份:头像 + 账号(取不到就整行不出现) */}
296
+ {identity && (
297
+ <div className="flex items-center gap-2">
298
+ {identity.avatar ? (
299
+ // eslint-disable-next-line @next/next/no-img-element
300
+ <img src={identity.avatar} alt="" width={22} height={22} className="flex-none rounded-full" />
301
+ ) : (
302
+ <span
303
+ className="grid flex-none place-items-center rounded-full"
304
+ style={{ width: 22, height: 22, background: "var(--dsw-selector)", fontSize: 11, fontWeight: 600, color: "var(--dsw-label-secondary)" }}
305
+ aria-hidden
306
+ >
307
+ {identity.login.slice(0, 1).toUpperCase()}
308
+ </span>
309
+ )}
310
+ <a
311
+ href={identity.url ?? `https://github.com/${identity.login}`}
312
+ target="_blank"
313
+ rel="noreferrer"
314
+ className="min-w-0 truncate"
315
+ style={{ fontSize: 12.5, color: "var(--dsw-label-secondary)", textDecoration: "none" }}
316
+ title={identity.name ? `${identity.name} (@${identity.login})` : `@${identity.login}`}
317
+ >
318
+ @{identity.login}
319
+ </a>
320
+ {identity.repo && (
321
+ <span className="min-w-0 truncate" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }} title={identity.repo}>
322
+ · {identity.repo}
323
+ </span>
324
+ )}
325
+ </div>
326
+ )}
327
+
250
328
  {/* 分支行 */}
251
329
  <div className="flex flex-wrap items-center gap-2" style={{ fontSize: 12.5 }}>
252
330
  <span
@@ -280,45 +358,37 @@ export function GitPanel({
280
358
  {error ? null : info.files.length === 0 ? (
281
359
  <div style={{ fontSize: 12.5, color: "var(--dsw-label-caption)" }}>{t.gitNoChanges}</div>
282
360
  ) : (
283
- [
284
- { label: t.gitConflict, list: conflicts, mode: "worktree" as const },
285
- { label: t.gitStaged, list: staged, mode: "staged" as const },
286
- { label: t.gitUnstaged, list: unstaged, mode: "worktree" as const },
287
- { label: t.gitUntracked, list: untracked, mode: "untracked" as const },
288
- ].map(
289
- (group) =>
290
- group.list.length > 0 && (
291
- <div key={group.label}>
292
- <div className="mb-1 flex items-center gap-2" style={{ fontSize: 11.5, color: group.mode === "worktree" && group.label === t.gitConflict ? "var(--dsw-danger)" : "var(--dsw-label-caption)" }}>
293
- <span style={{ fontWeight: 600 }}>{group.label}</span>
294
- <span>{group.list.length}</span>
295
- </div>
296
- <div className="flex flex-col">
297
- {group.list.map((f) => (
298
- <button
299
- key={`${group.label}:${f.path}`}
300
- type="button"
301
- className="flex w-full items-center gap-2 rounded-lg px-2 py-1 text-left transition-colors"
302
- title={f.path}
303
- onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
304
- onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
305
- onClick={() => openFile(f, group.mode, group.mode === "staged" ? t.gitStagedDiff : group.mode === "untracked" ? t.gitUntracked : t.gitWorktreeDiff)}
306
- >
307
- <span
308
- className="flex-none rounded px-1 text-center"
309
- style={{ minWidth: 16, fontSize: 10.5, fontFamily: "var(--font-mono)", fontWeight: 600, color: KIND_COLOR[f.kind], background: "var(--dsw-hover)" }}
310
- >
311
- {f.kind === "untracked" ? "?" : f.kind === "conflict" ? "!" : group.mode === "staged" ? f.indexStatus : f.workStatus !== " " ? f.workStatus : f.indexStatus}
312
- </span>
313
- <span className="min-w-0 flex-1 truncate" style={{ fontSize: 12, fontFamily: "var(--font-mono)", color: "var(--dsw-label-secondary)" }}>
314
- {f.path}
315
- </span>
316
- </button>
317
- ))}
318
- </div>
319
- </div>
320
- ),
321
- )
361
+ areas.map((area) => (
362
+ <div key={area.key}>
363
+ <div className="mb-1 flex items-center gap-2" style={{ fontSize: 11.5, color: area.danger ? "var(--dsw-danger)" : "var(--dsw-label-caption)" }}>
364
+ <span style={{ fontWeight: 600 }}>{area.label}</span>
365
+ <span>{area.entries.length}</span>
366
+ </div>
367
+ <div className="flex flex-col">
368
+ {area.entries.map(({ file: f, mode }) => (
369
+ <button
370
+ key={`${area.key}:${f.path}`}
371
+ type="button"
372
+ className="flex w-full items-center gap-2 rounded-lg px-2 py-1 text-left transition-colors"
373
+ title={f.path}
374
+ onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
375
+ onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
376
+ onClick={() => openFile(f, mode, mode === "staged" ? t.gitStagedDiff : mode === "untracked" ? t.gitUntracked : t.gitWorktreeDiff)}
377
+ >
378
+ <span
379
+ className="flex-none rounded px-1 text-center"
380
+ style={{ minWidth: 16, fontSize: 10.5, fontFamily: "var(--font-mono)", fontWeight: 600, color: KIND_COLOR[f.kind], background: "var(--dsw-hover)" }}
381
+ >
382
+ {f.kind === "untracked" ? "?" : f.kind === "conflict" ? "!" : mode === "staged" ? f.indexStatus : f.workStatus !== " " ? f.workStatus : f.indexStatus}
383
+ </span>
384
+ <span className="min-w-0 flex-1 truncate" style={{ fontSize: 12, fontFamily: "var(--font-mono)", color: "var(--dsw-label-secondary)" }}>
385
+ {f.path}
386
+ </span>
387
+ </button>
388
+ ))}
389
+ </div>
390
+ </div>
391
+ ))
322
392
  )}
323
393
 
324
394
  {/* 最近提交 */}
@@ -6,7 +6,7 @@
6
6
  * 生成中呼吸态、新步入场动画、窗口化渲染、键盘导航(↑↓ 移动,←→ 收展,Enter 打开)。
7
7
  */
8
8
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
9
- import { IconAtOutline16, IconFileOutline16, IconTriangleRightFill14 } from "@/components/icons";
9
+ import { IconAtOutline16, IconFileOutline16, IconFolderClose16, IconTriangleRightFill14 } from "@/components/icons";
10
10
  import { useI18n } from "@/i18n";
11
11
  import { fileIconSrc, folderIconSrc } from "@/lib/file-icons";
12
12
  import { flattenTree, treeNodeKey, type FlatRow, type TreeNode } from "@/lib/growth-tree";
@@ -182,14 +182,16 @@ export const GrowthTree = memo(function GrowthTree({
182
182
  selected={row.node.kind === "file" && selectedPath === row.node.path}
183
183
  focused={focused === treeNodeKey(row.node)}
184
184
  onActivate={activate}
185
+ onOpenFolder={onOpenFile}
185
186
  onFocusRow={setFocused}
186
187
  onReference={onReference}
187
188
  onOpenEditor={onOpenEditor}
188
189
  pendingLabel={t.growthPending}
189
- lazyLabel={t.growthLazyDir}
190
- deletedLabel={t.growthStatD}
191
- referenceLabel={t.fileReference}
190
+ lazyLabel={t.growthLazyDir}
191
+ deletedLabel={t.growthStatD}
192
+ referenceLabel={t.fileReference}
192
193
  editorLabel={t.fileOpenEditor}
194
+ openFolderLabel={t.viewerOpenFolder}
193
195
  />
194
196
  ))}
195
197
  </div>
@@ -205,6 +207,7 @@ const TreeRow = memo(function TreeRow({
205
207
  selected,
206
208
  focused,
207
209
  onActivate,
210
+ onOpenFolder,
208
211
  onFocusRow,
209
212
  onReference,
210
213
  onOpenEditor,
@@ -213,6 +216,7 @@ const TreeRow = memo(function TreeRow({
213
216
  deletedLabel,
214
217
  referenceLabel,
215
218
  editorLabel,
219
+ openFolderLabel,
216
220
  }: {
217
221
  row: FlatRow;
218
222
  top: number;
@@ -220,6 +224,7 @@ const TreeRow = memo(function TreeRow({
220
224
  selected: boolean;
221
225
  focused: boolean;
222
226
  onActivate: (row: FlatRow) => void;
227
+ onOpenFolder: (node: TreeNode) => void;
223
228
  onFocusRow: (path: string) => void;
224
229
  onReference?: (path: string) => void;
225
230
  onOpenEditor?: (path: string) => void;
@@ -228,6 +233,7 @@ const TreeRow = memo(function TreeRow({
228
233
  deletedLabel: string;
229
234
  referenceLabel: string;
230
235
  editorLabel: string;
236
+ openFolderLabel: string;
231
237
  }) {
232
238
  const { node, depth, expanded } = row;
233
239
  const isDir = node.kind === "dir";
@@ -253,6 +259,10 @@ const TreeRow = memo(function TreeRow({
253
259
  onFocusRow(treeNodeKey(node));
254
260
  onActivate(row);
255
261
  }}
262
+ onDoubleClick={() => {
263
+ // 目录双击 = 打开文件夹预览(单击仍是展开/收起,不动既有习惯)
264
+ if (isDir) onOpenFolder(node);
265
+ }}
256
266
  >
257
267
  <span className="pw-tree-chevron" aria-hidden>
258
268
  {isDir && <IconTriangleRightFill14 size={10} style={{ transform: expanded ? "rotate(90deg)" : undefined, transition: "transform var(--ds-duration-fast)" }} />}
@@ -264,8 +274,23 @@ const TreeRow = memo(function TreeRow({
264
274
  {node.status === "R" && node.from && <span className="pw-tree-from">← {node.from.split("/").pop()}</span>}
265
275
  </span>
266
276
  {node.pending && <span className="pw-tree-pulse" aria-label={pendingLabel} />}
267
- {!node.lazy && (onReference || (!isDir && onOpenEditor)) && (
277
+ {!node.lazy && (onReference || (!isDir && onOpenEditor) || isDir) && (
268
278
  <span className="pw-tree-actions">
279
+ {isDir && (
280
+ <button
281
+ type="button"
282
+ className="icon-btn"
283
+ style={{ width: 18, height: 18, borderRadius: 5 }}
284
+ title={openFolderLabel}
285
+ aria-label={`${openFolderLabel}: ${node.path}`}
286
+ onClick={(e) => {
287
+ e.stopPropagation();
288
+ onOpenFolder(node);
289
+ }}
290
+ >
291
+ <IconFolderClose16 size={11} />
292
+ </button>
293
+ )}
269
294
  {onReference && (
270
295
  <button
271
296
  type="button"
@@ -27,6 +27,9 @@ export function ModelSelector({
27
27
  onSelectLevel,
28
28
  open: openProp,
29
29
  onOpenChange,
30
+ modelOnly = false,
31
+ disabled = false,
32
+ defaultOption,
30
33
  }: {
31
34
  model?: { provider: string; id: string; name: string };
32
35
  thinkingLevel?: string;
@@ -38,6 +41,10 @@ export function ModelSelector({
38
41
  onSelectLevel: (level: string) => void;
39
42
  open?: boolean;
40
43
  onOpenChange?: (open: boolean) => void;
44
+ /** Compact consumers can show just the model list. */
45
+ modelOnly?: boolean;
46
+ disabled?: boolean;
47
+ defaultOption?: { label: string; selected: boolean; onSelect: () => void };
41
48
  }) {
42
49
  const [openState, setOpenState] = useState(false);
43
50
  const open = openProp ?? openState;
@@ -104,10 +111,10 @@ export function ModelSelector({
104
111
 
105
112
  const hasModel = Boolean(model?.id);
106
113
  const modelName = hasModel ? (model?.name || model?.id || "") : "";
107
- const effortLabel = hasModel && thinkingLevel && thinkingLevel !== "off" ? thinkingLevel : undefined;
108
- const triggerTitle = hasModel
114
+ const effortLabel = !modelOnly && hasModel && thinkingLevel && thinkingLevel !== "off" ? thinkingLevel : undefined;
115
+ const triggerTitle = (defaultOption?.selected ? `${defaultOption.label} · ` : "") + (hasModel
109
116
  ? `${providerNames[model?.provider ?? ""] ?? model?.provider}/${modelName}${effortLabel ? ` · ${effortLabel}` : ""}`
110
- : t.selectModel;
117
+ : t.selectModel);
111
118
 
112
119
  return (
113
120
  <div ref={ref} className="relative">
@@ -119,6 +126,7 @@ export function ModelSelector({
119
126
  aria-label={triggerTitle}
120
127
  aria-haspopup="menu"
121
128
  aria-expanded={open}
129
+ disabled={disabled}
122
130
  onClick={() => {
123
131
  setOpen(!open);
124
132
  setPane("model");
@@ -198,6 +206,14 @@ export function ModelSelector({
198
206
  {pane === "model" && (
199
207
  <div className="flex min-h-0 flex-col">
200
208
  <div ref={listRef} className="max-h-[340px] overflow-y-auto py-1">
209
+ {defaultOption && (
210
+ <button type="button" role="menuitemradio" aria-checked={defaultOption.selected} data-selected={defaultOption.selected}
211
+ className="flex min-h-10 w-full items-center justify-between gap-3 rounded-[10px] px-3 py-2 text-left"
212
+ style={{ fontSize: 13, background: defaultOption.selected ? "var(--dsw-active)" : undefined }}
213
+ onClick={() => { defaultOption.onSelect(); setOpen(false); }}>
214
+ {defaultOption.label}{defaultOption.selected && <IconCheckOutline14 size={14} />}
215
+ </button>
216
+ )}
201
217
  {groups.length === 0 && (
202
218
  <div className="px-3 py-3" style={{ fontSize: 13, color: "var(--dsw-label-caption)" }}>
203
219
  {t.emptyModels}
@@ -213,7 +229,7 @@ export function ModelSelector({
213
229
  {g.label}
214
230
  </div>
215
231
  {g.models.map((m) => {
216
- const selected = model?.provider === m.provider && model?.id === m.id;
232
+ const selected = !defaultOption?.selected && model?.provider === m.provider && model?.id === m.id;
217
233
  return (
218
234
  <button
219
235
  key={`${m.provider}/${m.id}`}
@@ -250,7 +266,7 @@ export function ModelSelector({
250
266
  </div>
251
267
  {/* 思考档位入口:模型只有一档(非推理模型 / models.json 没写 reasoning: true)时也显示,
252
268
  但置灰并说明是模型不支持,而不是让入口凭空消失 */}
253
- {hasModel && (thinkingLevels.length > 1 ? (
269
+ {hasModel && !modelOnly && (thinkingLevels.length > 1 ? (
254
270
  <button
255
271
  type="button"
256
272
  className="mt-1 flex w-full items-center justify-between gap-3 border-t px-3 pb-1 pt-2 text-left transition-colors"
@@ -84,6 +84,12 @@ function roundColor(round: GrowthEntry): string {
84
84
  return "var(--dsw-warn)";
85
85
  }
86
86
 
87
+ /** 这一轮有没有实际改动(轮次列表据此把没改动的轮降成次要色,改过的才显示 +/− 行数) */
88
+ function hasChanges(round: GrowthEntry): boolean {
89
+ const st = round.stats;
90
+ return Boolean(st.added || st.modified || st.deleted || st.renamed || st.add || st.del);
91
+ }
92
+
87
93
  function changedLines(round: GrowthEntry): number {
88
94
  return round.stats.add + round.stats.del;
89
95
  }
@@ -175,7 +181,13 @@ export function ProjectPanel({
175
181
  };
176
182
  }, [cwd, gitRefreshKey, latestCommit]);
177
183
 
178
- const totals = useMemo(() => sumChanges(growth.changes), [growth.changes]);
184
+ const totals = useMemo(() => {
185
+ if (selected && growth.scope === "round") {
186
+ const st = selected.stats;
187
+ return { files: st.added + st.modified + st.deleted + st.renamed, add: st.add, del: st.del };
188
+ }
189
+ return growth.changesReady === false ? null : sumChanges(growth.changes);
190
+ }, [selected, growth.scope, growth.changes, growth.changesReady]);
179
191
  const piRounds = useMemo(() => rounds.filter((round) => round.kind === "round").length, [rounds]);
180
192
  const maxLines = useMemo(() => Math.max(1, ...rounds.map(changedLines)), [rounds]);
181
193
  const railRef = useRef<HTMLDivElement>(null);
@@ -304,9 +316,12 @@ export function ProjectPanel({
304
316
  <div className="mt-0.5 flex items-center gap-2" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
305
317
  <span>{fmtTime(selected.ts)}</span>
306
318
  <span>·</span>
307
- <span>{t.growthRoundFiles.replace("{n}", String(totals.files))}</span>
308
- <span style={{ color: "var(--dsw-success)" }}>+{totals.add}</span>
309
- <span style={{ color: "var(--dsw-danger)" }}>−{totals.del}</span>
319
+ {growth.scope === "session" && <span>{t.growthScopeSession}</span>}
320
+ {totals ? <>
321
+ <span>{t.growthRoundFiles.replace("{n}", String(totals.files))}</span>
322
+ <span style={{ color: "var(--dsw-success)" }}>+{totals.add}</span>
323
+ <span style={{ color: "var(--dsw-danger)" }}>−{totals.del}</span>
324
+ </> : <span>—</span>}
310
325
  {statusText && <span style={{ color: "var(--dsw-warn)" }}>{statusText}</span>}
311
326
  <span className="flex-1" />
312
327
  {onJumpToChat && selected.promptIds[0] && (
@@ -397,7 +412,7 @@ export function ProjectPanel({
397
412
  data-kind={round.kind}
398
413
  data-active={selected?.commit === round.commit || undefined}
399
414
  style={{ height: h, background: roundColor(round) }}
400
- title={`${roundTitle(round, tt)} · ${fmtTime(round.ts)} · ${t.growthRoundFiles.replace("{n}", String(round.changes.length))} +${round.stats.add} −${round.stats.del}`}
415
+ title={`${roundTitle(round, tt)} · ${fmtTime(round.ts)} · ${t.growthRoundFiles.replace("{n}", String(round.stats.added + round.stats.modified + round.stats.deleted + round.stats.renamed))} +${round.stats.add} −${round.stats.del}`}
401
416
  onClick={() => growth.select(round.commit)}
402
417
  />
403
418
  );
@@ -478,31 +493,41 @@ export function ProjectPanel({
478
493
  )}
479
494
  {/* 轮次列表:当前轮高亮,打开时自动滚到它 */}
480
495
  <div ref={roundListRef} className="min-h-0 max-h-[260px] overflow-y-auto border-t pt-1" style={{ borderColor: "var(--dsw-border-l1)" }}>
481
- {rounds.map((round) => (
482
- <button
483
- key={round.commit}
484
- type="button"
485
- role="option"
486
- aria-selected={selected?.commit === round.commit}
487
- data-selected={selected?.commit === round.commit || undefined}
488
- className="flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left transition-colors"
489
- style={{ background: selected?.commit === round.commit ? "var(--dsw-active)" : "transparent" }}
490
- onMouseEnter={(e) => {
491
- if (selected?.commit !== round.commit) e.currentTarget.style.background = "var(--dsw-hover)";
492
- }}
493
- onMouseLeave={(e) => {
494
- if (selected?.commit !== round.commit) e.currentTarget.style.background = "transparent";
495
- }}
496
- title={`${roundTitle(round, tt)} · ${fmtTime(round.ts)}`}
497
- onClick={() => {
498
- growth.select(round.commit);
499
- setRoundPickerOpen(false);
500
- }}
501
- >
502
- <span className="min-w-0 flex-1 truncate" style={{ fontSize: 12.5 }}>{roundTitle(round, tt)}</span>
503
- <span className="flex-none" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }}>{fmtTime(round.ts)}</span>
504
- </button>
505
- ))}
496
+ {rounds.map((round) => {
497
+ const changed = hasChanges(round);
498
+ // 没改动的轮整行降对比度,扫视时自然被跳过;选中的那行不灰(否则像被禁用)
499
+ const dimmed = !changed && selected?.commit !== round.commit;
500
+ return (
501
+ <button
502
+ key={round.commit}
503
+ type="button"
504
+ role="option"
505
+ aria-selected={selected?.commit === round.commit}
506
+ data-selected={selected?.commit === round.commit || undefined}
507
+ className="flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left transition-colors"
508
+ style={{ background: selected?.commit === round.commit ? "var(--dsw-active)" : "transparent", ...(dimmed ? { opacity: 0.45 } : {}) }}
509
+ onMouseEnter={(e) => {
510
+ if (selected?.commit !== round.commit) e.currentTarget.style.background = "var(--dsw-hover)";
511
+ }}
512
+ onMouseLeave={(e) => {
513
+ if (selected?.commit !== round.commit) e.currentTarget.style.background = "transparent";
514
+ }}
515
+ title={`${roundTitle(round, tt)} · ${fmtTime(round.ts)}`}
516
+ onClick={() => {
517
+ growth.select(round.commit);
518
+ setRoundPickerOpen(false);
519
+ }}
520
+ >
521
+ <span className="min-w-0 flex-1 truncate" style={{ fontSize: 12.5 }}>{roundTitle(round, tt)}</span>
522
+ {changed && (round.stats.add > 0 || round.stats.del > 0) ? (
523
+ <span className="flex-none" style={{ fontSize: 11, fontFamily: "var(--font-mono)" }}>
524
+ <span style={{ color: "var(--dsw-success)" }}>+{round.stats.add}</span> <span style={{ color: "var(--dsw-danger)" }}>−{round.stats.del}</span>
525
+ </span>
526
+ ) : null}
527
+ <span className="flex-none" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }}>{fmtTime(round.ts)}</span>
528
+ </button>
529
+ );
530
+ })}
506
531
  </div>
507
532
  </div>
508
533
  )}
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { useCallback, useEffect, useState } from "react";
10
10
  import { useI18n } from "@/i18n";
11
+ import { fmtBytes } from "@/lib/format";
11
12
  import { IconRefreshOutline14, IconCloseOutline14 } from "@/components/icons";
12
13
 
13
14
  export type PromptSourceKind = "system" | "append" | "agents" | "skill" | "template" | "tool" | "assembled";
@@ -45,10 +46,7 @@ export interface PromptSourcesData {
45
46
  const GROUP_KINDS: PromptSourceKind[] = ["system", "append", "agents", "skill", "template", "tool", "assembled"];
46
47
 
47
48
  function formatBytes(bytes: number | undefined): string {
48
- if (!bytes) return "";
49
- if (bytes < 1024) return `${bytes} B`;
50
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
51
- return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
49
+ return bytes ? fmtBytes(bytes) : "";
52
50
  }
53
51
 
54
52
  export function PromptPanel({
@@ -489,6 +489,12 @@
489
489
  color: var(--dsw-danger);
490
490
  padding: 4px 0;
491
491
  }
492
+ .catalogHint {
493
+ margin-top: 8px;
494
+ font-size: 11.5px;
495
+ line-height: 1.5;
496
+ color: var(--dsw-label-caption);
497
+ }
492
498
 
493
499
  /* ---- 候选选择对话框 ---- */
494
500
  .candidateMask {
@@ -542,6 +548,20 @@
542
548
  .candidateRow:hover {
543
549
  background: var(--dsw-hover);
544
550
  }
551
+ /* 已在内置目录里的候选:不可勾选 */
552
+ .candidateRow[data-disabled] {
553
+ cursor: default;
554
+ color: var(--dsw-label-tertiary);
555
+ }
556
+ .candidateRow[data-disabled]:hover {
557
+ background: transparent;
558
+ }
559
+ .candidateTag {
560
+ flex: none;
561
+ margin-left: auto;
562
+ font-size: 11px;
563
+ color: var(--dsw-label-caption);
564
+ }
545
565
  .candidateId {
546
566
  font-family: var(--font-mono);
547
567
  font-size: 12.5px;