@rexvane/piweb 0.3.24 → 0.3.26

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 (285) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +11 -11
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +5 -5
  5. package/.next/react-loadable-manifest.json +13 -20
  6. package/.next/required-server-files.js +2 -1
  7. package/.next/required-server-files.json +2 -1
  8. package/.next/routes-manifest.json +6 -8
  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 +2 -2
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  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/agent/resources/route.js +1 -0
  32. package/.next/server/app/api/agent/resources/route.js.nft.json +1 -0
  33. package/.next/server/app/api/agent/resources/route_client-reference-manifest.js +1 -0
  34. package/.next/server/app/api/config/route.js +1 -1
  35. package/.next/server/app/api/config/route.js.nft.json +1 -1
  36. package/.next/server/app/api/files/route.js +1 -1
  37. package/.next/server/app/api/files/route.js.nft.json +1 -1
  38. package/.next/server/app/api/fs/route.js +1 -1
  39. package/.next/server/app/api/fs/route.js.nft.json +1 -1
  40. package/.next/server/app/api/git/route.js +4 -4
  41. package/.next/server/app/api/git/route.js.nft.json +1 -1
  42. package/.next/server/app/api/github/route.js +4 -4
  43. package/.next/server/app/api/github/route.js.nft.json +1 -1
  44. package/.next/server/app/api/growth/route.js +1 -1
  45. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  46. package/.next/server/app/api/health/route.js +1 -1
  47. package/.next/server/app/api/models/defaults/route.js +1 -1
  48. package/.next/server/app/api/models/defaults/route.js.nft.json +1 -1
  49. package/.next/server/app/api/models/route.js +1 -1
  50. package/.next/server/app/api/models/route.js.nft.json +1 -1
  51. package/.next/server/app/api/pi-settings/route.js +1 -1
  52. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  53. package/.next/server/app/api/plugins/route.js +1 -1
  54. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  55. package/.next/server/app/api/prompts/route.js +1 -1
  56. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  57. package/.next/server/app/api/remote/route.js +4 -3
  58. package/.next/server/app/api/security/route.js +1 -1
  59. package/.next/server/app/api/security/route.js.nft.json +1 -1
  60. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  61. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  62. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  63. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  64. package/.next/server/app/api/sessions/import/route.js +2 -2
  65. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  66. package/.next/server/app/api/sessions/route.js +1 -1
  67. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  68. package/.next/server/app/api/skills/route.js +2 -2
  69. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  70. package/.next/server/app/api/update/route.js +2 -2
  71. package/.next/server/app/api/update/route.js.nft.json +1 -1
  72. package/.next/server/app/api/version/route.js +1 -1
  73. package/.next/server/app/api/version/route.js.nft.json +1 -1
  74. package/.next/server/app/api/web-auth/route.js +2 -2
  75. package/.next/server/app/api/workspaces/route.js +1 -1
  76. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  77. package/.next/server/app/icon.svg/route.js +1 -1
  78. package/.next/server/app/index.html +1 -1
  79. package/.next/server/app/index.rsc +6 -7
  80. package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -7
  81. package/.next/server/app/index.segments/_full.segment.rsc +6 -7
  82. package/.next/server/app/index.segments/_tree.segment.rsc +2 -3
  83. package/.next/server/app/login/page.js +1 -1
  84. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/login.html +1 -1
  86. package/.next/server/app/login.rsc +3 -3
  87. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  88. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  89. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
  90. package/.next/server/app/page.js +1 -1
  91. package/.next/server/app/page.js.nft.json +1 -1
  92. package/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app-paths-manifest.json +11 -11
  94. package/.next/server/chunks/3924.js +3 -3
  95. package/.next/server/chunks/538.js +1 -1
  96. package/.next/server/chunks/620.js +1 -0
  97. package/.next/server/chunks/6229.js +1 -1
  98. package/.next/server/chunks/7496.js +1 -1
  99. package/.next/server/chunks/7662.js +2 -2
  100. package/.next/server/chunks/8686.js +1 -1
  101. package/.next/server/functions-config-manifest.json +1 -2
  102. package/.next/server/instrumentation.js.nft.json +1 -1
  103. package/.next/server/middleware-build-manifest.js +1 -1
  104. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  105. package/.next/server/pages/404.html +1 -1
  106. package/.next/server/pages/500.html +1 -1
  107. package/.next/server/preview-props.json +3 -3
  108. package/.next/server/server-reference-manifest.json +1 -1
  109. package/.next/static/QJCtqgDKzGS2BdKABd9ch/_buildManifest.js +1 -0
  110. package/.next/static/chunks/2577.86c8538fbf05a8b2.js +1 -0
  111. package/.next/static/chunks/2716.fa561519675a3ff1.js +1 -0
  112. package/.next/static/chunks/2906.8a0e09bb47605cf1.js +1 -0
  113. package/.next/static/chunks/4673.ea36e55005c0f82f.js +1 -0
  114. package/.next/static/chunks/4686-4dc565817a160351.js +1 -0
  115. package/.next/static/chunks/4778.0d2c5d75b791d352.js +1 -0
  116. package/.next/static/chunks/{618.949981da99be7182.js → 618.9a22656b863f242b.js} +2 -2
  117. package/.next/static/chunks/app/_global-error/page-f2d93969c0bcf406.js +1 -0
  118. package/.next/static/chunks/app/api/agent/[id]/events/route-f2d93969c0bcf406.js +1 -0
  119. package/.next/static/chunks/app/api/agent/[id]/route-f2d93969c0bcf406.js +1 -0
  120. package/.next/static/chunks/app/api/agent/[id]/tree/route-f2d93969c0bcf406.js +1 -0
  121. package/.next/static/chunks/app/api/agent/new/route-f2d93969c0bcf406.js +1 -0
  122. package/.next/static/chunks/app/api/agent/resources/route-f2d93969c0bcf406.js +1 -0
  123. package/.next/static/chunks/app/api/config/route-f2d93969c0bcf406.js +1 -0
  124. package/.next/static/chunks/app/api/files/route-f2d93969c0bcf406.js +1 -0
  125. package/.next/static/chunks/app/api/fs/route-f2d93969c0bcf406.js +1 -0
  126. package/.next/static/chunks/app/api/git/route-f2d93969c0bcf406.js +1 -0
  127. package/.next/static/chunks/app/api/github/route-f2d93969c0bcf406.js +1 -0
  128. package/.next/static/chunks/app/api/growth/route-f2d93969c0bcf406.js +1 -0
  129. package/.next/static/chunks/app/api/health/route-f2d93969c0bcf406.js +1 -0
  130. package/.next/static/chunks/app/api/models/defaults/route-f2d93969c0bcf406.js +1 -0
  131. package/.next/static/chunks/app/api/models/route-f2d93969c0bcf406.js +1 -0
  132. package/.next/static/chunks/app/api/pi-settings/route-f2d93969c0bcf406.js +1 -0
  133. package/.next/static/chunks/app/api/plugins/route-f2d93969c0bcf406.js +1 -0
  134. package/.next/static/chunks/app/api/prompts/route-f2d93969c0bcf406.js +1 -0
  135. package/.next/static/chunks/app/api/remote/route-f2d93969c0bcf406.js +1 -0
  136. package/.next/static/chunks/app/api/security/route-f2d93969c0bcf406.js +1 -0
  137. package/.next/static/chunks/app/api/sessions/[id]/export/route-f2d93969c0bcf406.js +1 -0
  138. package/.next/static/chunks/app/api/sessions/[id]/route-f2d93969c0bcf406.js +1 -0
  139. package/.next/static/chunks/app/api/sessions/import/route-f2d93969c0bcf406.js +1 -0
  140. package/.next/static/chunks/app/api/sessions/route-f2d93969c0bcf406.js +1 -0
  141. package/.next/static/chunks/app/api/skills/route-f2d93969c0bcf406.js +1 -0
  142. package/.next/static/chunks/app/api/update/route-f2d93969c0bcf406.js +1 -0
  143. package/.next/static/chunks/app/api/version/route-f2d93969c0bcf406.js +1 -0
  144. package/.next/static/chunks/app/api/web-auth/route-f2d93969c0bcf406.js +1 -0
  145. package/.next/static/chunks/app/api/workspaces/route-f2d93969c0bcf406.js +1 -0
  146. package/.next/static/chunks/app/page-71f2d3b3ed864b3d.js +1 -0
  147. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f2d93969c0bcf406.js +1 -0
  148. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f2d93969c0bcf406.js +1 -0
  149. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f2d93969c0bcf406.js +1 -0
  150. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f2d93969c0bcf406.js +1 -0
  151. package/.next/static/chunks/webpack-ffc09b5fcc8aaa6d.js +1 -0
  152. package/.next/static/css/014eb1f21414e615.css +1 -0
  153. package/.next/static/css/56b129a13637fcfd.css +1 -0
  154. package/.next/static/css/7e3caf797e4cb19a.css +1 -0
  155. package/.next/types/app/api/agent/{[id]/side-chat → resources}/route.ts +3 -3
  156. package/.next/types/routes.d.ts +2 -2
  157. package/.next/types/validator.ts +9 -9
  158. package/README.md +1 -1
  159. package/next.config.ts +1 -1
  160. package/package.json +9 -9
  161. package/scripts/apply-global.mjs +87 -0
  162. package/scripts/launcher.mjs +21 -0
  163. package/scripts/testing/prepare-sdk-tools.mjs +19 -0
  164. package/src/app/api/agent/new/route.ts +2 -1
  165. package/src/app/api/agent/resources/route.ts +18 -0
  166. package/src/app/api/models/route.ts +22 -1
  167. package/src/app/api/plugins/route.ts +3 -2
  168. package/src/app/api/prompts/route.ts +3 -2
  169. package/src/app/api/remote/route.ts +22 -4
  170. package/src/app/api/sessions/[id]/export/route.ts +3 -1
  171. package/src/app/api/skills/route.ts +3 -2
  172. package/src/app/globals.css +1002 -1765
  173. package/src/components/AppShell.tsx +128 -93
  174. package/src/components/ChatInput.tsx +177 -98
  175. package/src/components/ChatWindow.tsx +189 -56
  176. package/src/components/ContextMeter.tsx +33 -9
  177. package/src/components/CustomSelect.module.css +142 -0
  178. package/src/components/CustomSelect.tsx +157 -0
  179. package/src/components/DrawerPanel.tsx +1 -1
  180. package/src/components/ExtensionUI.tsx +9 -3
  181. package/src/components/FileViewer.tsx +24 -21
  182. package/src/components/GitPanel.tsx +35 -13
  183. package/src/components/GrowthTree.tsx +6 -2
  184. package/src/components/ImageViewer.tsx +72 -0
  185. package/src/components/MarkdownBlock.tsx +67 -96
  186. package/src/components/ModelSelector.tsx +36 -39
  187. package/src/components/PiMark.tsx +76 -41
  188. package/src/components/PreviewImage.tsx +33 -0
  189. package/src/components/ProjectPanel.tsx +8 -5
  190. package/src/components/PromptPanel.tsx +85 -20
  191. package/src/components/ProviderSetupModal.module.css +12 -11
  192. package/src/components/ProviderSetupModal.tsx +107 -36
  193. package/src/components/RemoteSection.tsx +53 -2
  194. package/src/components/SessionImportSection.tsx +30 -15
  195. package/src/components/SessionSidebar.tsx +72 -62
  196. package/src/components/SettingsPanel.tsx +49 -151
  197. package/src/components/TrajectoryAnalyzer.module.css +26 -11
  198. package/src/components/TrajectoryView.tsx +11 -3
  199. package/src/components/chat/markdown-actions.tsx +23 -0
  200. package/src/components/composer/ComposerFiles.tsx +224 -0
  201. package/src/components/composer/ComposerImages.tsx +59 -127
  202. package/src/hooks/usePiWeb.ts +47 -2
  203. package/src/i18n.tsx +62 -80
  204. package/src/lib/agent-manager.ts +365 -33
  205. package/src/lib/chat/file-path.ts +54 -0
  206. package/src/lib/chat/skill-expansion.ts +51 -0
  207. package/src/lib/extension-ui.ts +25 -1
  208. package/src/lib/font.ts +37 -39
  209. package/src/lib/git-service.ts +4 -2
  210. package/src/lib/growth-turns.ts +3 -1
  211. package/src/lib/model-draft.ts +69 -2
  212. package/src/lib/models-service.ts +53 -3
  213. package/src/lib/pi.ts +2 -2
  214. package/src/lib/remote/assets.json +1 -1
  215. package/src/lib/remote/supervisor.ts +48 -5
  216. package/src/lib/security-service.ts +2 -1
  217. package/src/lib/session-reader.ts +25 -13
  218. package/src/lib/session-retention.ts +11 -8
  219. package/src/lib/skills-service.ts +2 -1
  220. package/src/lib/slash-command-token.ts +14 -0
  221. package/src/lib/trajectory.ts +111 -15
  222. package/src/lib/types.ts +28 -3
  223. package/src/lib/update-service.ts +29 -9
  224. package/src/lib/workspace-store.ts +36 -1
  225. package/.next/server/app/api/agent/[id]/side-chat/route.js +0 -5
  226. package/.next/server/app/api/agent/[id]/side-chat/route.js.nft.json +0 -1
  227. package/.next/server/app/api/agent/[id]/side-chat/route_client-reference-manifest.js +0 -1
  228. package/.next/server/chunks/3421.js +0 -1
  229. package/.next/static/chunks/2716.44fdf1b21454b961.js +0 -1
  230. package/.next/static/chunks/4673.e261707dce391be7.js +0 -1
  231. package/.next/static/chunks/4686-0cd87513f9ea3538.js +0 -1
  232. package/.next/static/chunks/4778.eddd3a5813f85824.js +0 -1
  233. package/.next/static/chunks/7411.5f79671208209b13.js +0 -1
  234. package/.next/static/chunks/8552.25c39ef4c995eba0.js +0 -1
  235. package/.next/static/chunks/app/_global-error/page-cbaaee0d7aacb85a.js +0 -1
  236. package/.next/static/chunks/app/api/agent/[id]/events/route-cbaaee0d7aacb85a.js +0 -1
  237. package/.next/static/chunks/app/api/agent/[id]/route-cbaaee0d7aacb85a.js +0 -1
  238. package/.next/static/chunks/app/api/agent/[id]/side-chat/route-cbaaee0d7aacb85a.js +0 -1
  239. package/.next/static/chunks/app/api/agent/[id]/tree/route-cbaaee0d7aacb85a.js +0 -1
  240. package/.next/static/chunks/app/api/agent/new/route-cbaaee0d7aacb85a.js +0 -1
  241. package/.next/static/chunks/app/api/config/route-cbaaee0d7aacb85a.js +0 -1
  242. package/.next/static/chunks/app/api/files/route-cbaaee0d7aacb85a.js +0 -1
  243. package/.next/static/chunks/app/api/fs/route-cbaaee0d7aacb85a.js +0 -1
  244. package/.next/static/chunks/app/api/git/route-cbaaee0d7aacb85a.js +0 -1
  245. package/.next/static/chunks/app/api/github/route-cbaaee0d7aacb85a.js +0 -1
  246. package/.next/static/chunks/app/api/growth/route-cbaaee0d7aacb85a.js +0 -1
  247. package/.next/static/chunks/app/api/health/route-cbaaee0d7aacb85a.js +0 -1
  248. package/.next/static/chunks/app/api/models/defaults/route-cbaaee0d7aacb85a.js +0 -1
  249. package/.next/static/chunks/app/api/models/route-cbaaee0d7aacb85a.js +0 -1
  250. package/.next/static/chunks/app/api/pi-settings/route-cbaaee0d7aacb85a.js +0 -1
  251. package/.next/static/chunks/app/api/plugins/route-cbaaee0d7aacb85a.js +0 -1
  252. package/.next/static/chunks/app/api/prompts/route-cbaaee0d7aacb85a.js +0 -1
  253. package/.next/static/chunks/app/api/remote/route-cbaaee0d7aacb85a.js +0 -1
  254. package/.next/static/chunks/app/api/security/route-cbaaee0d7aacb85a.js +0 -1
  255. package/.next/static/chunks/app/api/sessions/[id]/export/route-cbaaee0d7aacb85a.js +0 -1
  256. package/.next/static/chunks/app/api/sessions/[id]/route-cbaaee0d7aacb85a.js +0 -1
  257. package/.next/static/chunks/app/api/sessions/import/route-cbaaee0d7aacb85a.js +0 -1
  258. package/.next/static/chunks/app/api/sessions/route-cbaaee0d7aacb85a.js +0 -1
  259. package/.next/static/chunks/app/api/skills/route-cbaaee0d7aacb85a.js +0 -1
  260. package/.next/static/chunks/app/api/update/route-cbaaee0d7aacb85a.js +0 -1
  261. package/.next/static/chunks/app/api/version/route-cbaaee0d7aacb85a.js +0 -1
  262. package/.next/static/chunks/app/api/web-auth/route-cbaaee0d7aacb85a.js +0 -1
  263. package/.next/static/chunks/app/api/workspaces/route-cbaaee0d7aacb85a.js +0 -1
  264. package/.next/static/chunks/app/page-dc3cb41d91c7432e.js +0 -1
  265. package/.next/static/chunks/next/dist/client/components/builtin/app-error-cbaaee0d7aacb85a.js +0 -1
  266. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cbaaee0d7aacb85a.js +0 -1
  267. package/.next/static/chunks/next/dist/client/components/builtin/not-found-cbaaee0d7aacb85a.js +0 -1
  268. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cbaaee0d7aacb85a.js +0 -1
  269. package/.next/static/chunks/webpack-2ac3d76b81895f5b.js +0 -1
  270. package/.next/static/css/08815507594e8b8d.css +0 -1
  271. package/.next/static/css/9fc559c2c4ad1d0f.css +0 -1
  272. package/.next/static/css/c6e2227c36ec2fd6.css +0 -1
  273. package/.next/static/css/f15a853af8b5127f.css +0 -1
  274. package/.next/static/lvKCJmjFj8LuqSojRVLRF/_buildManifest.js +0 -1
  275. package/src/app/api/agent/[id]/side-chat/route.ts +0 -77
  276. package/src/components/side-chat/SideChatPanel.module.css +0 -131
  277. package/src/components/side-chat/SideChatPanel.tsx +0 -189
  278. package/src/components/side-chat/icons.tsx +0 -8
  279. package/src/components/side-chat/request-history.ts +0 -29
  280. package/src/components/side-chat/store.ts +0 -248
  281. package/src/lib/chat/stream-settings.ts +0 -192
  282. package/src/lib/side-chat/context.ts +0 -123
  283. package/src/lib/side-chat/service.ts +0 -191
  284. package/src/lib/side-chat/types.ts +0 -61
  285. /package/.next/static/{lvKCJmjFj8LuqSojRVLRF → QJCtqgDKzGS2BdKABd9ch}/_ssgManifest.js +0 -0
@@ -130,9 +130,9 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
130
130
  */
131
131
  const [activeSource, setActiveSource] = useState("");
132
132
  useEffect(() => {
133
- if (activeSource && groups.some((group) => group.source === activeSource)) return;
134
- setActiveSource(groups[0]?.source ?? "");
135
- }, [groups, activeSource]);
133
+ // 扫描完成的 effect 可能晚于用户点击;按执行时的最新选择决定是否回退。
134
+ setActiveSource((current) => groups.some((group) => group.source === current) ? current : groups[0]?.source ?? "");
135
+ }, [groups]);
136
136
  const activeGroup = groups.find((group) => group.source === activeSource);
137
137
 
138
138
  const toggle = (summary: Summary) => {
@@ -348,36 +348,51 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
348
348
  return (
349
349
  <label
350
350
  key={rowKey}
351
- className="flex cursor-pointer items-center gap-3 rounded-xl px-3 py-2 transition-colors"
351
+ className="flex cursor-pointer items-center gap-3 rounded-2xl px-3.5 py-2.5 transition-all"
352
352
  style={{
353
- border: `0.5px solid ${checked ? "var(--dsw-accent)" : "var(--dsw-border-l2)"}`,
354
- background: checked ? "var(--dsw-accent-soft)" : "transparent",
355
- opacity: done ? 0.5 : 1,
353
+ border: `1px solid ${checked ? "var(--dsw-accent)" : "var(--dsw-border-l2)"}`,
354
+ background: checked ? "var(--dsw-accent-soft)" : "var(--dsw-bg-elevated)",
355
+ opacity: done ? 0.45 : 1,
356
+ transition: "all 0.2s var(--isx-ease)",
357
+ }}
358
+ onMouseEnter={(e) => {
359
+ if (!checked && !done) {
360
+ e.currentTarget.style.borderColor = "var(--dsw-border-l3)";
361
+ e.currentTarget.style.background = "var(--dsw-hover)";
362
+ e.currentTarget.style.transform = "translateY(-1px)";
363
+ }
364
+ }}
365
+ onMouseLeave={(e) => {
366
+ if (!checked && !done) {
367
+ e.currentTarget.style.borderColor = "var(--dsw-border-l2)";
368
+ e.currentTarget.style.background = "var(--dsw-bg-elevated)";
369
+ e.currentTarget.style.transform = "none";
370
+ }
356
371
  }}
357
372
  >
358
- <input type="checkbox" checked={checked} disabled={done} onChange={() => toggle(row)} className="flex-none" />
373
+ <input type="checkbox" checked={checked} disabled={done} onChange={() => toggle(row)} className="flex-none accent-[var(--dsw-accent)]" />
359
374
  <div className="flex min-w-0 flex-1 flex-col gap-0.5">
360
- <div className="truncate" style={{ fontSize: 12.5, color: done ? "var(--dsw-label-tertiary)" : "var(--dsw-label-primary)" }} title={row.title}>
375
+ <div className="truncate font-medium" style={{ fontSize: 12.5, color: done ? "var(--dsw-label-tertiary)" : "var(--dsw-label-primary)" }} title={row.title}>
361
376
  {row.title || t.importUntitled}
362
377
  </div>
363
378
  <div className="flex min-w-0 items-center gap-1.5" title={row.projectPath}>
364
379
  <span
365
- className="flex-none rounded px-1 py-0.5"
366
- style={{ fontSize: 10, background: "var(--dsw-selector)", color: "var(--dsw-label-tertiary)" }}
380
+ className="flex-none rounded-full px-2 py-0.5"
381
+ style={{ fontSize: 10, background: "var(--dsw-selector)", color: "var(--dsw-label-secondary)", border: "1px solid var(--dsw-border-l1)" }}
367
382
  >
368
383
  {goesOriginal ? t.importGoesOriginal : t.importGoesFallback}
369
384
  </span>
370
- <span className="truncate" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }}>
385
+ <span className="truncate font-mono" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }}>
371
386
  {target || t.importNoProject}
372
387
  </span>
373
388
  </div>
374
389
  </div>
375
- <span className="flex-none" style={{ fontSize: 11, color: "var(--dsw-label-caption)", whiteSpace: "nowrap" }}>
390
+ <span className="flex-none font-mono" style={{ fontSize: 11, color: "var(--dsw-label-caption)", whiteSpace: "nowrap" }}>
376
391
  {typeof row.messageCount === "number" ? t.importMessages.replace("{n}", String(row.messageCount)) : "—"}
377
392
  </span>
378
- <span className="flex-none" style={{ fontSize: 11, color: "var(--dsw-label-caption)", whiteSpace: "nowrap" }}>{dayOf(row.updatedAt ?? row.createdAt)}</span>
393
+ <span className="flex-none font-mono" style={{ fontSize: 11, color: "var(--dsw-label-caption)", whiteSpace: "nowrap" }}>{dayOf(row.updatedAt ?? row.createdAt)}</span>
379
394
  {done && (
380
- <span className="flex-none rounded-full px-2 py-0.5" style={{ fontSize: 10, background: "var(--dsw-selector)", color: "var(--dsw-label-caption)" }}>
395
+ <span className="flex-none rounded-full px-2 py-0.5 font-medium" style={{ fontSize: 10, background: "var(--dsw-selector)", color: "var(--dsw-label-caption)", border: "1px solid var(--dsw-border-l1)" }}>
381
396
  {t.importAlready}
382
397
  </span>
383
398
  )}
@@ -8,7 +8,7 @@
8
8
  * 搜索模式(输入框替身标题行,空查询=工作区平铺,输入=会话平铺),
9
9
  * 滑杆菜单(分组方式:按工作区/单列表;排序方式:手动排序/最近更新)。
10
10
  */
11
- import { useEffect, useMemo, useRef, useState } from "react";
11
+ import { memo, useEffect, useMemo, useRef, useState } from "react";
12
12
  import { createPortal } from "react-dom";
13
13
  import { BrandPi, PiMark } from "@/components/PiMark";
14
14
  import {
@@ -68,7 +68,7 @@ interface WorkspaceGroup {
68
68
  lastModified: string;
69
69
  }
70
70
 
71
- export function SessionSidebar({
71
+ function SessionSidebarImpl({
72
72
  sessions,
73
73
  sessionListError,
74
74
  onRetrySessionList,
@@ -221,11 +221,17 @@ export function SessionSidebar({
221
221
  }
222
222
  };
223
223
 
224
- const currentCwd = sessions.find((s) => s.path === currentPath)?.cwd;
224
+ // 路径一律按 normPath 比:注册表里的工作区经过 path.resolve,会话头里的 cwd 是原样记下的,
225
+ // Windows 上盘符大小写、分隔符、尾斜杠都可能不同
226
+ const currentKey = currentPath ? normPath(currentPath) : "";
227
+ const currentCwd = currentKey ? sessions.find((s) => normPath(s.path) === currentKey)?.cwd : undefined;
225
228
 
226
229
  useEffect(() => {
227
230
  if (!viewMenu && !rowMenu && !sessionMenu) return;
228
231
  const h = (e: MouseEvent) => {
232
+ // 按在行菜单的「…」按钮上:交给按钮的 onClick 切换(它也会关掉另一种菜单)。
233
+ // 这里先关的话,紧跟着的 click 又把菜单打开,再点一下「…」永远关不掉
234
+ if ((e.target as Element | null)?.closest?.("[data-menu-trigger]")) return;
229
235
  if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
230
236
  setViewMenu(false);
231
237
  setRowMenu(null);
@@ -325,7 +331,7 @@ export function SessionSidebar({
325
331
  } catch {
326
332
  /* ignore */
327
333
  }
328
- const s = sessions.find((item) => item.path === currentPath);
334
+ const s = sessions.find((item) => normPath(item.path) === normPath(currentPath));
329
335
  if (!s) return; // 会话列表尚未加载,等轮询后重试
330
336
  lastExpandedFor.current = currentPath;
331
337
  const matchedGroup = workspaceGroups.find((g) => normPath(g.cwd) === normPath(s.cwd));
@@ -400,46 +406,40 @@ export function SessionSidebar({
400
406
  }
401
407
 
402
408
  const sessionRow = (s: SessionListItem, indented: boolean, archived = false) => {
403
- const selected = s.path === currentPath;
409
+ const selected = !!currentKey && normPath(s.path) === currentKey;
404
410
  // dsh 合同:被归档的当前会话仍显示在主列表,菜单按真实归档态切换
405
411
  const reallyArchived = archived || archivedPaths.some((p) => normPath(p) === normPath(s.path));
406
412
  return (
407
413
  <div
408
414
  key={s.path}
409
- className="group relative flex items-center rounded-xl transition-colors"
415
+ data-active={selected ? "true" : undefined}
416
+ className="isx-menu-item group relative flex items-center rounded-xl"
410
417
  style={{
411
- background: selected ? "var(--dsw-active)" : "transparent",
412
418
  paddingLeft: indented ? 38 : 10,
413
- paddingRight: 8,
419
+ paddingRight: 14,
414
420
  paddingTop: 6,
415
421
  paddingBottom: 6,
416
422
  // 选中/悬停框之间留缝,不互相粘连
417
423
  marginTop: 2,
418
424
  marginBottom: 2,
419
425
  }}
420
- onMouseEnter={(e) => {
421
- if (!selected) e.currentTarget.style.background = "var(--dsw-hover)";
422
- }}
423
- onMouseLeave={(e) => {
424
- if (!selected) e.currentTarget.style.background = "transparent";
425
- }}
426
426
  >
427
427
  {s.streaming && <span className="state-dot running" style={{ position: "absolute", left: indented ? 26 : 6, width: 6, height: 6 }} />}
428
428
  <button className="flex min-w-0 flex-1 flex-col items-start text-left" onClick={() => onOpen(s.path)}>
429
429
  <span
430
- className="w-full truncate"
431
- style={{ fontSize: 13, color: selected ? "var(--dsw-accent)" : "var(--dsw-label-primary)" }}
430
+ className="isx-menu-shift w-full truncate"
431
+ style={{ fontSize: 13, fontWeight: selected ? 500 : 400 }}
432
432
  >
433
433
  {s.name || s.firstMessage || "(untitled)"}
434
434
  </span>
435
435
  {s.streaming && s.lastStep && (
436
- <span className="w-full truncate" style={{ fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--dsw-label-caption)", lineHeight: "16px" }} title={s.lastStep}>
436
+ <span className="isx-menu-sub w-full truncate" style={{ fontSize: 11, fontFamily: "var(--font-mono)", color: "var(--dsw-label-caption)", lineHeight: "16px" }} title={s.lastStep}>
437
437
  {s.lastStep}
438
438
  </span>
439
439
  )}
440
440
  </button>
441
441
  <span
442
- className={`flex-none pl-1 ${sessionMenu === s.path ? "hidden" : "group-hover:hidden group-focus-within:hidden"}`}
442
+ className={`isx-menu-sub flex-none pl-1 ${sessionMenu === s.path ? "hidden" : "group-hover:hidden group-focus-within:hidden"}`}
443
443
  style={{ fontSize: 10.5, color: "var(--dsw-label-caption)" }}
444
444
  >
445
445
  {relTime(s.modified, lang)}
@@ -450,6 +450,7 @@ export function SessionSidebar({
450
450
  style={{ width: 22, height: 22 }}
451
451
  title="..."
452
452
  aria-label="session-actions"
453
+ data-menu-trigger="session"
453
454
  onClick={(e) => {
454
455
  e.stopPropagation();
455
456
  setSessionMenu(sessionMenu === s.path ? null : s.path);
@@ -495,6 +496,7 @@ export function SessionSidebar({
495
496
  <MenuItem
496
497
  icon={<IconArchiveOutline20 size={14} />}
497
498
  label={reallyArchived ? t.unarchiveSession : t.archiveSession}
499
+ title={reallyArchived ? undefined : t.archiveRetentionHint}
498
500
  onClick={(e) => {
499
501
  e.stopPropagation();
500
502
  setSessionMenu(null);
@@ -511,7 +513,7 @@ export function SessionSidebar({
511
513
  const workspaceRow = (g: WorkspaceGroup) => {
512
514
  const isUngrouped = g.cwd === "__ungrouped__";
513
515
  const open = openSet.has(g.cwd);
514
- const isCurrent = !isUngrouped && g.cwd === currentCwd;
516
+ const isCurrent = !isUngrouped && !!currentCwd && normPath(g.cwd) === normPath(currentCwd);
515
517
  // 草稿新会话属于哪个分组:路径匹配的工作区,否则归未分组
516
518
  const draftInThisGroup = draftCwd
517
519
  ? (isUngrouped
@@ -525,42 +527,37 @@ export function SessionSidebar({
525
527
  return (
526
528
  <div key={g.cwd}>
527
529
  <div
528
- className="group relative flex items-center rounded-xl transition-colors"
530
+ className="isx-menu-item group relative flex items-center rounded-xl"
529
531
  style={{
530
532
  background: open || isCurrent ? "var(--dsw-hover)" : "transparent",
531
533
  paddingLeft: 10,
532
- paddingRight: 6,
534
+ paddingRight: 12,
533
535
  paddingTop: 7,
534
536
  paddingBottom: 7,
535
537
  // 悬停/展开框之间留缝,不互相粘连(与会话行一致)
536
538
  marginTop: 2,
537
539
  marginBottom: 2,
538
540
  }}
539
- onMouseEnter={(e) => {
540
- if (!(open || isCurrent)) e.currentTarget.style.background = "var(--dsw-hover)";
541
- }}
542
- onMouseLeave={(e) => {
543
- if (!(open || isCurrent)) e.currentTarget.style.background = "transparent";
544
- }}
545
541
  >
546
542
  <button className="flex min-w-0 flex-1 items-center gap-2 text-left" onClick={() => toggleOpen(g.cwd)} title={displayName}>
547
543
  <span
544
+ className="isx-menu-sub"
548
545
  style={{
549
546
  color: "var(--dsw-label-tertiary)",
550
547
  display: "inline-flex",
551
548
  transform: open ? "rotate(90deg)" : "none",
552
- transition: "transform 120ms var(--ds-ease-in-out)",
549
+ transition: "transform 180ms var(--isx-ease)",
553
550
  flex: "none",
554
551
  }}
555
552
  >
556
553
  <IconTriangleRightFill14 size={11} />
557
554
  </span>
558
- <span style={{ color: isCurrent ? "var(--dsw-accent)" : "var(--dsw-label-tertiary)", display: "inline-flex", flex: "none" }}>
555
+ <span className="isx-menu-icon" style={{ color: isCurrent ? "var(--dsw-accent)" : "var(--dsw-label-tertiary)", display: "inline-flex", flex: "none" }}>
559
556
  {open ? <IconFolderOpenOutline16 size={15} /> : <IconFolderClose16 size={15} />}
560
557
  </span>
561
558
  <span
562
- className="truncate"
563
- style={{ fontSize: 13, color: isCurrent ? "var(--dsw-accent)" : "var(--dsw-label-primary)" }}
559
+ className="isx-menu-shift truncate"
560
+ style={{ fontSize: 13, fontWeight: isCurrent ? 500 : 400 }}
564
561
  >
565
562
  {displayName}
566
563
  </span>
@@ -572,6 +569,7 @@ export function SessionSidebar({
572
569
  style={{ width: 22, height: 22 }}
573
570
  title="..."
574
571
  aria-label="workspace-actions"
572
+ data-menu-trigger="workspace"
575
573
  onClick={() => {
576
574
  setRowMenu(rowMenu === g.cwd ? null : g.cwd);
577
575
  setSessionMenu(null);
@@ -661,7 +659,7 @@ export function SessionSidebar({
661
659
  </button>
662
660
  </div>
663
661
 
664
- {/* 新会话(dsh .newSession) */}
662
+ {/* 新会话(Iconsax .button-gradient 光晕胶囊) */}
665
663
  <div className="px-3 pt-1">
666
664
  <button className="btn-new-session w-full" onClick={onNew}>
667
665
  <IconNewChatOutline16 size={14} />
@@ -669,11 +667,11 @@ export function SessionSidebar({
669
667
  </button>
670
668
  </div>
671
669
 
672
- {/* 工作区标题行 / 搜索框(互替,照 dsh) */}
670
+ {/* 工作区标题行 / 搜索框(互替,照 Iconsax .con-search) */}
673
671
  {searchOpen ? (
674
672
  <div className="px-3 pb-1 pt-3">
675
- <div className="flex items-center gap-2 rounded-xl px-2.5 py-2" style={{ background: "var(--dsw-hover)" }}>
676
- <IconSearchOutline16 size={14} style={{ color: "var(--dsw-label-caption)" }} />
673
+ <div className="isx-search flex items-center gap-2 px-3 py-2">
674
+ <IconSearchOutline16 size={14} className="isx-search-icon" style={{ color: "var(--dsw-label-caption)", flex: "none" }} />
677
675
  <input
678
676
  autoFocus
679
677
  value={q}
@@ -717,7 +715,7 @@ export function SessionSidebar({
717
715
  </div>
718
716
  <MenuCheck label={t.byWorkspace} checked={groupBy === "workspace"} onClick={() => { setGroupBy("workspace"); setViewMenu(false); }} />
719
717
  <MenuCheck label={t.flatList} checked={groupBy === "flat"} onClick={() => { setGroupBy("flat"); setViewMenu(false); }} />
720
- <div className="my-1.5" style={{ borderTop: "0.5px solid var(--dsw-border-l2)" }} />
718
+ <div className="my-1.5" style={{ borderTop: "1px solid var(--dsw-border-l2)" }} />
721
719
  <div className="px-3 pb-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
722
720
  {t.sortBy}
723
721
  </div>
@@ -766,19 +764,17 @@ export function SessionSidebar({
766
764
  [...workspaceGroups, ...(ungroupedGroup ? [ungroupedGroup] : [])].map((g) => (
767
765
  <div
768
766
  key={g.cwd}
769
- className="flex items-center gap-2.5 rounded-xl px-2.5 py-2 transition-colors"
770
- onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
771
- onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
767
+ className="isx-menu-item flex cursor-pointer items-center gap-2.5 rounded-xl px-2.5 py-2"
772
768
  onClick={() => {
773
769
  setSearchOpen(false);
774
770
  toggleOpen(g.cwd);
775
771
  }}
776
772
  title={g.cwd === "__ungrouped__" ? (t.ungrouped || "未分组") : g.cwd}
777
773
  >
778
- <span style={{ color: "var(--dsw-label-tertiary)", display: "inline-flex" }}>
774
+ <span className="isx-menu-icon" style={{ color: "var(--dsw-label-tertiary)", display: "inline-flex" }}>
779
775
  <IconFolderClose16 size={15} />
780
776
  </span>
781
- <span className="truncate" style={{ fontSize: 13 }}>
777
+ <span className="isx-menu-shift truncate" style={{ fontSize: 13 }}>
782
778
  {g.cwd === "__ungrouped__"
783
779
  ? (t.ungrouped || "未分组")
784
780
  : (getWorkspaceName ? getWorkspaceName(g.cwd) : basename(g.cwd))}
@@ -839,26 +835,33 @@ export function SessionSidebar({
839
835
  <div className="mt-2 border-t pt-2" style={{ borderColor: "var(--dsw-border-l2)" }}>
840
836
  <button
841
837
  type="button"
842
- className="flex w-full items-center gap-2 rounded-xl px-2.5 py-2 text-left transition-colors"
838
+ className="isx-menu-item flex w-full items-center gap-2 rounded-xl px-2.5 py-2 text-left"
843
839
  aria-expanded={archiveOpen}
840
+ title={t.archiveRetentionDetail}
844
841
  onClick={() => setArchiveOpen((open) => !open)}
845
- onMouseEnter={(event) => (event.currentTarget.style.background = "var(--dsw-hover)")}
846
- onMouseLeave={(event) => (event.currentTarget.style.background = "transparent")}
847
842
  >
848
843
  <span
844
+ className="isx-menu-sub"
849
845
  style={{
850
846
  display: "inline-flex",
851
847
  transform: archiveOpen ? "rotate(90deg)" : "none",
852
- transition: "transform 120ms var(--ds-ease-in-out)",
848
+ transition: "transform 180ms var(--isx-ease)",
853
849
  color: "var(--dsw-label-tertiary)",
854
850
  }}
855
851
  >
856
852
  <IconTriangleRightFill14 size={11} />
857
853
  </span>
858
- <IconArchiveOutline20 size={15} style={{ color: "var(--dsw-label-tertiary)" }} />
859
- <span className="min-w-0 flex-1 truncate" style={{ fontSize: 13 }}>{t.archivedSessions}</span>
860
- <span style={{ fontSize: 11, color: "var(--dsw-label-caption)" }}>{archivedSessions.length}</span>
854
+ <span className="isx-menu-icon" style={{ display: "inline-flex", color: "var(--dsw-label-tertiary)" }}>
855
+ <IconArchiveOutline20 size={15} />
856
+ </span>
857
+ <span className="isx-menu-shift min-w-0 flex-1 truncate" style={{ fontSize: 13 }}>{t.archivedSessions}</span>
858
+ <span className="isx-menu-sub pr-2" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }}>{archivedSessions.length}</span>
861
859
  </button>
860
+ {archiveOpen && (
861
+ <div className="px-2.5 pb-1" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }} title={t.archiveRetentionDetail}>
862
+ {t.archiveRetentionHint}
863
+ </div>
864
+ )}
862
865
  {archiveOpen && archivedSessions.map((session) => sessionRow(session, true, true))}
863
866
  </div>
864
867
  )}
@@ -867,14 +870,14 @@ export function SessionSidebar({
867
870
  {/* 设置固定左下角 */}
868
871
  <div className="px-3 py-2.5">
869
872
  <button
870
- className="flex w-full items-center gap-2.5 rounded-xl px-4 py-2 text-left transition-colors"
873
+ className="isx-menu-item flex w-full items-center gap-2.5 rounded-xl px-4 py-2 text-left"
871
874
  style={{ color: "var(--dsw-label-secondary)" }}
872
- onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
873
- onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
874
875
  onClick={onOpenSettings}
875
876
  >
876
- <IconSettingsOutline16 size={16} />
877
- <span style={{ fontSize: "var(--dsh-content-font-size)" }}>{t.settings}</span>
877
+ <span className="isx-menu-icon" style={{ display: "inline-flex" }}>
878
+ <IconSettingsOutline16 size={16} />
879
+ </span>
880
+ <span className="isx-menu-shift" style={{ fontSize: "var(--dsh-content-font-size)" }}>{t.settings}</span>
878
881
  </button>
879
882
  </div>
880
883
 
@@ -1025,27 +1028,35 @@ export function SessionSidebar({
1025
1028
  );
1026
1029
  }
1027
1030
 
1031
+ /**
1032
+ * memo:流式期间 AppShell 每 16ms 重渲染一次(state.messages 换引用),侧栏的 props
1033
+ * (会话列表、回调)在此期间全部不变,几百行会话树的 reconciliation 是纯浪费。
1034
+ */
1035
+ export const SessionSidebar = memo(SessionSidebarImpl);
1036
+
1028
1037
  function MenuItem({
1029
1038
  icon,
1030
1039
  label,
1040
+ title,
1031
1041
  danger,
1032
1042
  onClick,
1033
1043
  }: {
1034
1044
  icon: React.ReactNode;
1035
1045
  label: string;
1046
+ title?: string;
1036
1047
  danger?: boolean;
1037
1048
  onClick: (e: React.MouseEvent) => void;
1038
1049
  }) {
1039
1050
  return (
1040
1051
  <button
1041
- className="mx-1 flex w-[calc(100%-8px)] items-center gap-2.5 rounded-lg px-3 py-2 text-left transition-colors"
1052
+ title={title}
1053
+ data-danger={danger ? "true" : undefined}
1054
+ className="isx-menu-item mx-1 flex w-[calc(100%-8px)] items-center gap-2.5 rounded-lg px-3 py-2 text-left"
1042
1055
  style={{ fontSize: 13, color: danger ? "var(--dsw-danger)" : "var(--dsw-label-primary)" }}
1043
- onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
1044
- onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
1045
1056
  onClick={onClick}
1046
1057
  >
1047
- {icon}
1048
- {label}
1058
+ <span className="isx-menu-icon" style={{ display: "inline-flex" }}>{icon}</span>
1059
+ <span className="isx-menu-shift">{label}</span>
1049
1060
  </button>
1050
1061
  );
1051
1062
  }
@@ -1053,13 +1064,12 @@ function MenuItem({
1053
1064
  function MenuCheck({ label, checked, onClick }: { label: string; checked: boolean; onClick: () => void }) {
1054
1065
  return (
1055
1066
  <button
1056
- className="mx-1 flex w-[calc(100%-8px)] items-center justify-between rounded-lg pl-5 pr-3 py-2 text-left transition-colors"
1067
+ data-active={checked ? "true" : undefined}
1068
+ className="isx-menu-item mx-1 flex w-[calc(100%-8px)] items-center justify-between rounded-lg pl-4 pr-4 py-2 text-left"
1057
1069
  style={{ fontSize: 13, color: "var(--dsw-label-primary)" }}
1058
- onMouseEnter={(e) => (e.currentTarget.style.background = "var(--dsw-hover)")}
1059
- onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")}
1060
1070
  onClick={onClick}
1061
1071
  >
1062
- {label}
1072
+ <span className="isx-menu-shift">{label}</span>
1063
1073
  {checked && <IconCheckOutline14 size={13} />}
1064
1074
  </button>
1065
1075
  );