@dyyz1993/create-agent 2.1.0 → 2.1.2

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 (349) hide show
  1. package/package.json +5 -4
  2. package/src/__tests__/cli.test.ts +2 -0
  3. package/src/__tests__/copy.test.ts +110 -122
  4. package/src/__tests__/create-args.test.ts +113 -0
  5. package/src/__tests__/templates.test.ts +29 -3
  6. package/src/__tests__/update.test.ts +129 -0
  7. package/src/cli.ts +30 -32
  8. package/src/commands/create.ts +83 -24
  9. package/src/commands/update.ts +174 -0
  10. package/src/commands/workspace.ts +26 -6
  11. package/src/lib/copy.ts +233 -93
  12. package/src/lib/templates.ts +12 -0
  13. package/templates/agent/.husky/commit-msg +4 -0
  14. package/templates/agent/.husky/pre-commit +11 -0
  15. package/templates/agent/.husky/pre-push +6 -0
  16. package/templates/agent/.prettierignore +6 -0
  17. package/templates/agent/.prettierrc +9 -0
  18. package/templates/agent/CHANGELOG.md +15 -0
  19. package/templates/agent/commitlint.config.js +8 -0
  20. package/templates/agent/package.json +19 -4
  21. package/templates/agent/src/gateway/__tests__/ws-handler-token.test.ts +53 -39
  22. package/templates/agent/src/mainview/App.tsx +14 -12
  23. package/templates/agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +194 -190
  24. package/templates/agent/src/mainview/components/bash/BashPanel.tsx +210 -189
  25. package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +45 -20
  26. package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +41 -21
  27. package/templates/agent/src/mainview/components/debug/DebugPanel.tsx +181 -114
  28. package/templates/agent/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +80 -80
  29. package/templates/agent/src/mainview/components/diff/__tests__/DiffViewerPanel.test.tsx +61 -58
  30. package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +43 -41
  31. package/templates/agent/src/mainview/components/file-preview/FilePreviewOverlay.tsx +69 -50
  32. package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +31 -18
  33. package/templates/agent/src/mainview/components/git/GitPanel.tsx +731 -490
  34. package/templates/agent/src/mainview/components/git/__tests__/GitPanel.test.tsx +157 -139
  35. package/templates/agent/src/mainview/components/layout/AppLayout.tsx +210 -161
  36. package/templates/agent/src/mainview/components/layout/__tests__/AppLayout.test.tsx +130 -122
  37. package/templates/agent/src/mainview/components/rules/RulesPanel.tsx +119 -109
  38. package/templates/agent/src/mainview/components/search/SearchPanel.tsx +123 -117
  39. package/templates/agent/src/mainview/components/search/__tests__/SearchPanel.test.tsx +64 -44
  40. package/templates/agent/src/mainview/components/sidebar/__tests__/PinButton.test.tsx +38 -38
  41. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +40 -38
  42. package/templates/agent/src/mainview/components/todo/__tests__/TodoPanel.test.tsx +72 -72
  43. package/templates/agent/src/mainview/lib/i18n/locales/en.json +175 -155
  44. package/templates/agent/src/mainview/lib/i18n/locales/zh.json +173 -155
  45. package/templates/agent/src/mainview/stores/use-app-store.ts +96 -86
  46. package/templates/agent/src/mainview/stores/use-explorer-store.ts +292 -275
  47. package/templates/agent/src/mainview/types/index.ts +23 -22
  48. package/templates/agent/src/mainview/utils/file-icon.tsx +17 -21
  49. package/templates/agent/src/shared/handlers/chat.ts +53 -53
  50. package/templates/agent/src/shared/handlers/debug.ts +13 -3
  51. package/templates/browser-agent/.env.example +19 -0
  52. package/templates/browser-agent/.husky/commit-msg +4 -0
  53. package/templates/browser-agent/.husky/pre-commit +11 -0
  54. package/templates/browser-agent/.husky/pre-push +6 -0
  55. package/templates/browser-agent/.prettierignore +6 -0
  56. package/templates/browser-agent/.prettierrc +9 -0
  57. package/templates/browser-agent/AGENTS.md +396 -0
  58. package/templates/browser-agent/CHANGELOG.md +15 -0
  59. package/templates/browser-agent/LICENSE +21 -0
  60. package/templates/browser-agent/README.md +103 -0
  61. package/templates/browser-agent/commitlint.config.js +8 -0
  62. package/templates/browser-agent/electrobun.config.ts +27 -0
  63. package/templates/browser-agent/electron/main.js +46 -0
  64. package/templates/browser-agent/electron/preload.js +5 -0
  65. package/templates/browser-agent/electron-builder.json +40 -0
  66. package/templates/browser-agent/eslint.config.mjs +79 -0
  67. package/templates/browser-agent/llms.txt +24 -0
  68. package/templates/browser-agent/package.json +140 -0
  69. package/templates/browser-agent/postcss.config.js +6 -0
  70. package/templates/browser-agent/scripts/dev.ts +138 -0
  71. package/templates/browser-agent/src/__tests__/hybrid-mode.test.ts +126 -0
  72. package/templates/browser-agent/src/__tests__/server-config-security.test.ts +55 -0
  73. package/templates/browser-agent/src/__tests__/server-config.test.ts +59 -0
  74. package/templates/browser-agent/src/bun/index.ts +130 -0
  75. package/templates/browser-agent/src/bun/three.d.ts +1 -0
  76. package/templates/browser-agent/src/gateway/http-routes.ts +1 -0
  77. package/templates/browser-agent/src/gateway/ipc-transport.ts +68 -0
  78. package/templates/browser-agent/src/gateway/sse-transport.ts +221 -0
  79. package/templates/browser-agent/src/mainview/App.tsx +45 -0
  80. package/templates/browser-agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
  81. package/templates/browser-agent/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
  82. package/templates/browser-agent/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
  83. package/templates/browser-agent/src/mainview/__tests__/setup.ts +40 -0
  84. package/templates/browser-agent/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
  85. package/templates/browser-agent/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
  86. package/templates/browser-agent/src/mainview/__tests__/theme-variables.test.ts +36 -0
  87. package/templates/browser-agent/src/mainview/components/assets/AssetsPanel.tsx +139 -0
  88. package/templates/browser-agent/src/mainview/components/chat/ChatPanel.tsx +219 -0
  89. package/templates/browser-agent/src/mainview/components/chat/CommandBar.tsx +184 -0
  90. package/templates/browser-agent/src/mainview/components/chat/MessageBubble.tsx +249 -0
  91. package/templates/browser-agent/src/mainview/components/chat/ToolPicker.tsx +115 -0
  92. package/templates/browser-agent/src/mainview/components/common/ErrorBoundary.tsx +1 -0
  93. package/templates/browser-agent/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  94. package/templates/browser-agent/src/mainview/components/common/ThemeToggle.tsx +45 -0
  95. package/templates/browser-agent/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
  96. package/templates/browser-agent/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
  97. package/templates/browser-agent/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
  98. package/templates/browser-agent/src/mainview/components/dev/NetworkPanel.tsx +155 -0
  99. package/templates/browser-agent/src/mainview/components/layout/AppLayout.tsx +311 -0
  100. package/templates/browser-agent/src/mainview/components/onboarding/SetupWizard.tsx +310 -0
  101. package/templates/browser-agent/src/mainview/components/process/ProcessPanel.tsx +329 -0
  102. package/templates/browser-agent/src/mainview/components/sidebar/SessionSidebar.tsx +122 -0
  103. package/templates/browser-agent/src/mainview/components/sidebar/SkillSidebar.tsx +115 -0
  104. package/templates/browser-agent/src/mainview/components/topbar/TopBar.tsx +350 -0
  105. package/templates/browser-agent/src/mainview/global.d.ts +13 -0
  106. package/templates/browser-agent/src/mainview/hooks/__tests__/use-breakpoint.test.ts +151 -0
  107. package/templates/browser-agent/src/mainview/hooks/use-agent-chat.ts +157 -0
  108. package/templates/browser-agent/src/mainview/hooks/use-breakpoint.ts +68 -0
  109. package/templates/browser-agent/src/mainview/hooks/use-rpc-init.ts +71 -0
  110. package/templates/browser-agent/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  111. package/templates/browser-agent/src/mainview/index.css +71 -0
  112. package/templates/browser-agent/src/mainview/index.html +12 -0
  113. package/templates/browser-agent/src/mainview/lib/api-client.ts +397 -0
  114. package/templates/browser-agent/src/mainview/lib/i18n/index.ts +30 -0
  115. package/templates/browser-agent/src/mainview/lib/i18n/locales/en.json +130 -0
  116. package/templates/browser-agent/src/mainview/lib/i18n/locales/zh.json +128 -0
  117. package/templates/browser-agent/src/mainview/lib/network-bus.ts +92 -0
  118. package/templates/browser-agent/src/mainview/lib/rpc-cache.ts +94 -0
  119. package/templates/browser-agent/src/mainview/main.tsx +18 -0
  120. package/templates/browser-agent/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
  121. package/templates/browser-agent/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  122. package/templates/browser-agent/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
  123. package/templates/browser-agent/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  124. package/templates/browser-agent/src/mainview/stores/message-batcher.ts +25 -0
  125. package/templates/browser-agent/src/mainview/stores/use-asset-store.ts +65 -0
  126. package/templates/browser-agent/src/mainview/stores/use-chat-store.ts +200 -0
  127. package/templates/browser-agent/src/mainview/stores/use-connection-store.ts +173 -0
  128. package/templates/browser-agent/src/mainview/stores/use-locale-store.ts +27 -0
  129. package/templates/browser-agent/src/mainview/stores/use-log-store.ts +16 -0
  130. package/templates/browser-agent/src/mainview/stores/use-network-panel-store.ts +18 -0
  131. package/templates/browser-agent/src/mainview/stores/use-record-store.ts +147 -0
  132. package/templates/browser-agent/src/mainview/stores/use-session-store.ts +151 -0
  133. package/templates/browser-agent/src/mainview/stores/use-sidebar-store.ts +161 -0
  134. package/templates/browser-agent/src/mainview/stores/use-theme-store.ts +46 -0
  135. package/templates/browser-agent/src/mainview/stores/use-view-store.ts +20 -0
  136. package/templates/browser-agent/src/mainview/types/index.ts +6 -0
  137. package/templates/browser-agent/src/server-config.ts +45 -0
  138. package/templates/browser-agent/src/server.ts +78 -0
  139. package/templates/browser-agent/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
  140. package/templates/browser-agent/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
  141. package/templates/browser-agent/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
  142. package/templates/browser-agent/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
  143. package/templates/browser-agent/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
  144. package/templates/browser-agent/src/shared/handlers/browser.ts +596 -0
  145. package/templates/browser-agent/src/shared/handlers/chat.ts +213 -0
  146. package/templates/browser-agent/src/shared/handlers/file.ts +99 -0
  147. package/templates/browser-agent/src/shared/handlers/index.ts +7 -0
  148. package/templates/browser-agent/src/shared/handlers/session.ts +167 -0
  149. package/templates/browser-agent/src/shared/handlers/system.ts +27 -0
  150. package/templates/browser-agent/src/shared/handlers/timer.ts +34 -0
  151. package/templates/browser-agent/src/shared/http-routes.ts +437 -0
  152. package/templates/browser-agent/src/shared/lib/__tests__/logger.test.ts +92 -0
  153. package/templates/browser-agent/src/shared/lib/__tests__/path-security.test.ts +77 -0
  154. package/templates/browser-agent/src/shared/lib/__tests__/web-server.test.ts +203 -0
  155. package/templates/browser-agent/src/shared/lib/agent.ts +384 -0
  156. package/templates/browser-agent/src/shared/lib/cdp.ts +448 -0
  157. package/templates/browser-agent/src/shared/lib/generate.ts +111 -0
  158. package/templates/browser-agent/src/shared/lib/logger.ts +152 -0
  159. package/templates/browser-agent/src/shared/lib/mock-stream.ts +147 -0
  160. package/templates/browser-agent/src/shared/lib/path-security.ts +38 -0
  161. package/templates/browser-agent/src/shared/lib/port-registry.ts +103 -0
  162. package/templates/browser-agent/src/shared/lib/web-server.ts +127 -0
  163. package/templates/browser-agent/src/shared/lib/xhs-extract.ts +71 -0
  164. package/templates/browser-agent/src/shared/modules/browser.ts +86 -0
  165. package/templates/browser-agent/src/shared/modules/chat.ts +20 -0
  166. package/templates/browser-agent/src/shared/modules/file.ts +40 -0
  167. package/templates/browser-agent/src/shared/modules/session.ts +55 -0
  168. package/templates/browser-agent/src/shared/modules/system.ts +8 -0
  169. package/templates/browser-agent/src/shared/modules/timer.ts +11 -0
  170. package/templates/browser-agent/src/shared/register-all-handlers.ts +36 -0
  171. package/templates/browser-agent/src/shared/rpc-schema.ts +34 -0
  172. package/templates/browser-agent/tailwind.config.js +15 -0
  173. package/templates/browser-agent/tsconfig.ipc.json +14 -0
  174. package/templates/browser-agent/tsconfig.json +36 -0
  175. package/templates/browser-agent/vite.config.ts +3 -0
  176. package/templates/browser-agent/vitest.config.ts +3 -0
  177. package/templates/chat/.husky/commit-msg +4 -0
  178. package/templates/chat/.husky/pre-commit +11 -0
  179. package/templates/chat/.husky/pre-push +6 -0
  180. package/templates/chat/CHANGELOG.md +15 -0
  181. package/templates/chat/commitlint.config.js +8 -0
  182. package/templates/chat/package.json +19 -4
  183. package/templates/chat/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
  184. package/templates/chat/src/mainview/__tests__/setup.ts +32 -29
  185. package/templates/chat/src/mainview/components/chat/ChatPanel.tsx +80 -55
  186. package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +40 -20
  187. package/templates/chat/src/mainview/lib/i18n/locales/en.json +175 -155
  188. package/templates/chat/src/mainview/lib/i18n/locales/zh.json +173 -155
  189. package/templates/chat/src/shared/handlers/chat.ts +17 -19
  190. package/templates/chat/src/shared/handlers/debug.ts +12 -2
  191. package/templates/cowork/.env.example +8 -0
  192. package/templates/cowork/.prettierignore +6 -0
  193. package/templates/cowork/.prettierrc +9 -0
  194. package/templates/cowork/AGENTS.md +236 -0
  195. package/templates/cowork/CHANGELOG.md +15 -0
  196. package/templates/cowork/LICENSE +21 -0
  197. package/templates/cowork/README.md +103 -0
  198. package/templates/cowork/commitlint.config.js +8 -0
  199. package/templates/cowork/docs/CODE-VIEW-PLAN.md +637 -0
  200. package/templates/cowork/docs/CODE-VIEW-V2.md +196 -0
  201. package/templates/cowork/docs/CODE-VIEW-V4.md +150 -0
  202. package/templates/cowork/electrobun.config.ts +27 -0
  203. package/templates/cowork/eslint.config.mjs +79 -0
  204. package/templates/cowork/llms.txt +24 -0
  205. package/templates/cowork/package.json +87 -0
  206. package/templates/cowork/postcss.config.js +6 -0
  207. package/templates/cowork/scripts/dev.ts +138 -0
  208. package/templates/cowork/src/__tests__/hybrid-mode.test.ts +126 -0
  209. package/templates/cowork/src/__tests__/server-config-security.test.ts +55 -0
  210. package/templates/cowork/src/__tests__/server-config.test.ts +59 -0
  211. package/templates/cowork/src/bun/index.ts +130 -0
  212. package/templates/cowork/src/bun/three.d.ts +1 -0
  213. package/templates/cowork/src/gateway/http-routes.ts +1 -0
  214. package/templates/cowork/src/gateway/ipc-transport.ts +68 -0
  215. package/templates/cowork/src/gateway/sse-transport.ts +231 -0
  216. package/templates/cowork/src/mainview/App.tsx +45 -0
  217. package/templates/cowork/src/mainview/__tests__/hooks/use-rpc-init.test.ts +202 -0
  218. package/templates/cowork/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
  219. package/templates/cowork/src/mainview/__tests__/i18n/i18n.test.ts +49 -0
  220. package/templates/cowork/src/mainview/__tests__/setup.ts +40 -0
  221. package/templates/cowork/src/mainview/__tests__/stores/use-chat-store.test.ts +73 -0
  222. package/templates/cowork/src/mainview/__tests__/stores/use-sidebar-store.test.ts +61 -0
  223. package/templates/cowork/src/mainview/__tests__/theme-variables.test.ts +36 -0
  224. package/templates/cowork/src/mainview/components/chat/TaskChat.tsx +311 -0
  225. package/templates/cowork/src/mainview/components/chat/__tests__/localhost-links.test.ts +76 -0
  226. package/templates/cowork/src/mainview/components/common/ErrorBoundary.tsx +1 -0
  227. package/templates/cowork/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  228. package/templates/cowork/src/mainview/components/common/ThemeToggle.tsx +45 -0
  229. package/templates/cowork/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
  230. package/templates/cowork/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
  231. package/templates/cowork/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
  232. package/templates/cowork/src/mainview/components/dev/NetworkPanel.tsx +155 -0
  233. package/templates/cowork/src/mainview/components/layout/AppLayout.tsx +216 -0
  234. package/templates/cowork/src/mainview/components/right/ArtifactsPanel.tsx +54 -0
  235. package/templates/cowork/src/mainview/components/right/ContextPanel.tsx +133 -0
  236. package/templates/cowork/src/mainview/components/right/ElementPicker.tsx +206 -0
  237. package/templates/cowork/src/mainview/components/right/PreviewBlock.tsx +155 -0
  238. package/templates/cowork/src/mainview/components/right/ProgressPanel.tsx +85 -0
  239. package/templates/cowork/src/mainview/components/sidebar/TaskSidebar.tsx +211 -0
  240. package/templates/cowork/src/mainview/components/topbar/TopBar.tsx +86 -0
  241. package/templates/cowork/src/mainview/global.d.ts +13 -0
  242. package/templates/cowork/src/mainview/hooks/__tests__/use-breakpoint.test.ts +123 -0
  243. package/templates/cowork/src/mainview/hooks/use-breakpoint.ts +53 -0
  244. package/templates/cowork/src/mainview/hooks/use-rpc-init.ts +26 -0
  245. package/templates/cowork/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  246. package/templates/cowork/src/mainview/index.css +89 -0
  247. package/templates/cowork/src/mainview/index.html +12 -0
  248. package/templates/cowork/src/mainview/lib/api-client.ts +404 -0
  249. package/templates/cowork/src/mainview/lib/i18n/index.ts +30 -0
  250. package/templates/cowork/src/mainview/lib/i18n/locales/en.json +130 -0
  251. package/templates/cowork/src/mainview/lib/i18n/locales/zh.json +128 -0
  252. package/templates/cowork/src/mainview/lib/network-bus.ts +92 -0
  253. package/templates/cowork/src/mainview/lib/rpc-cache.ts +94 -0
  254. package/templates/cowork/src/mainview/main.tsx +18 -0
  255. package/templates/cowork/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
  256. package/templates/cowork/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  257. package/templates/cowork/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
  258. package/templates/cowork/src/mainview/stores/__tests__/use-preview-store.test.ts +193 -0
  259. package/templates/cowork/src/mainview/stores/__tests__/use-sidebar-store.test.ts +81 -0
  260. package/templates/cowork/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  261. package/templates/cowork/src/mainview/stores/message-batcher.ts +25 -0
  262. package/templates/cowork/src/mainview/stores/use-chat-store.ts +198 -0
  263. package/templates/cowork/src/mainview/stores/use-connection-store.ts +168 -0
  264. package/templates/cowork/src/mainview/stores/use-context-store.ts +68 -0
  265. package/templates/cowork/src/mainview/stores/use-locale-store.ts +27 -0
  266. package/templates/cowork/src/mainview/stores/use-log-store.ts +16 -0
  267. package/templates/cowork/src/mainview/stores/use-network-panel-store.ts +18 -0
  268. package/templates/cowork/src/mainview/stores/use-output-store.ts +47 -0
  269. package/templates/cowork/src/mainview/stores/use-preview-store.ts +97 -0
  270. package/templates/cowork/src/mainview/stores/use-sidebar-store.ts +272 -0
  271. package/templates/cowork/src/mainview/stores/use-task-store.ts +86 -0
  272. package/templates/cowork/src/mainview/stores/use-theme-store.ts +46 -0
  273. package/templates/cowork/src/mainview/stores/use-view-store.ts +29 -0
  274. package/templates/cowork/src/mainview/types/index.ts +6 -0
  275. package/templates/cowork/src/server-config.ts +44 -0
  276. package/templates/cowork/src/server.ts +78 -0
  277. package/templates/cowork/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
  278. package/templates/cowork/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
  279. package/templates/cowork/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
  280. package/templates/cowork/src/shared/handlers/__tests__/preview-handler.test.ts +172 -0
  281. package/templates/cowork/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
  282. package/templates/cowork/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
  283. package/templates/cowork/src/shared/handlers/chat.ts +213 -0
  284. package/templates/cowork/src/shared/handlers/context.ts +72 -0
  285. package/templates/cowork/src/shared/handlers/file.ts +99 -0
  286. package/templates/cowork/src/shared/handlers/index.ts +9 -0
  287. package/templates/cowork/src/shared/handlers/output.ts +59 -0
  288. package/templates/cowork/src/shared/handlers/preview.ts +81 -0
  289. package/templates/cowork/src/shared/handlers/system.ts +27 -0
  290. package/templates/cowork/src/shared/handlers/task.ts +101 -0
  291. package/templates/cowork/src/shared/handlers/timer.ts +34 -0
  292. package/templates/cowork/src/shared/http-routes.ts +444 -0
  293. package/templates/cowork/src/shared/lib/__tests__/logger.test.ts +92 -0
  294. package/templates/cowork/src/shared/lib/__tests__/path-security.test.ts +77 -0
  295. package/templates/cowork/src/shared/lib/__tests__/web-server.test.ts +203 -0
  296. package/templates/cowork/src/shared/lib/logger.ts +152 -0
  297. package/templates/cowork/src/shared/lib/path-security.ts +38 -0
  298. package/templates/cowork/src/shared/lib/port-registry.ts +103 -0
  299. package/templates/cowork/src/shared/lib/web-server.ts +127 -0
  300. package/templates/cowork/src/shared/modules/chat.ts +20 -0
  301. package/templates/cowork/src/shared/modules/context.ts +32 -0
  302. package/templates/cowork/src/shared/modules/file.ts +40 -0
  303. package/templates/cowork/src/shared/modules/output.ts +20 -0
  304. package/templates/cowork/src/shared/modules/preview.ts +44 -0
  305. package/templates/cowork/src/shared/modules/system.ts +8 -0
  306. package/templates/cowork/src/shared/modules/task.ts +31 -0
  307. package/templates/cowork/src/shared/modules/timer.ts +11 -0
  308. package/templates/cowork/src/shared/register-all-handlers.ts +36 -0
  309. package/templates/cowork/src/shared/rpc-schema.ts +38 -0
  310. package/templates/cowork/tailwind.config.js +15 -0
  311. package/templates/cowork/test-upload.txt +0 -0
  312. package/templates/cowork/tsconfig.ipc.json +14 -0
  313. package/templates/cowork/tsconfig.json +36 -0
  314. package/templates/cowork/vite.config.ts +3 -0
  315. package/templates/cowork/vitest.config.ts +3 -0
  316. package/templates/general/.husky/commit-msg +4 -0
  317. package/templates/general/.husky/pre-commit +11 -0
  318. package/templates/general/.husky/pre-push +6 -0
  319. package/templates/general/CHANGELOG.md +15 -0
  320. package/templates/general/commitlint.config.js +8 -0
  321. package/templates/general/package.json +19 -4
  322. package/templates/general/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
  323. package/templates/general/src/mainview/__tests__/setup.ts +32 -29
  324. package/templates/general/src/mainview/components/chat/ChatPanel.tsx +80 -55
  325. package/templates/general/src/mainview/components/common/ThemeToggle.tsx +40 -20
  326. package/templates/general/src/mainview/components/debug/DebugPanel.tsx +170 -156
  327. package/templates/general/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +169 -0
  328. package/templates/general/src/mainview/components/explorer/ConfirmDialog.tsx +42 -42
  329. package/templates/general/src/mainview/components/explorer/ContextMenu.tsx +72 -67
  330. package/templates/general/src/mainview/components/feed/FeedPanel.tsx +7 -5
  331. package/templates/general/src/mainview/components/file-preview/FilePreviewOverlay.tsx +64 -45
  332. package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +24 -7
  333. package/templates/general/src/mainview/components/git/GitPanel.tsx +700 -473
  334. package/templates/general/src/mainview/components/layout/AppLayout.tsx +123 -114
  335. package/templates/general/src/mainview/components/search/SearchPanel.tsx +15 -19
  336. package/templates/general/src/mainview/lib/i18n/locales/en.json +175 -155
  337. package/templates/general/src/mainview/lib/i18n/locales/zh.json +173 -155
  338. package/templates/general/src/mainview/stores/__tests__/use-explorer-store.test.ts +259 -0
  339. package/templates/general/src/mainview/stores/__tests__/use-feed-store.test.ts +160 -0
  340. package/templates/general/src/mainview/stores/__tests__/use-git-store.test.ts +313 -0
  341. package/templates/general/src/mainview/stores/__tests__/use-notification-store.test.ts +115 -0
  342. package/templates/general/src/mainview/stores/__tests__/use-sidebar-store.test.ts +120 -0
  343. package/templates/general/src/mainview/stores/use-explorer-store.ts +277 -260
  344. package/templates/general/src/mainview/types/index.ts +21 -20
  345. package/templates/general/src/shared/handlers/chat.ts +1 -1
  346. package/templates/general/src/shared/handlers/debug.ts +12 -2
  347. package/templates/shared/components/ErrorBoundary.tsx +0 -1
  348. package/templates/shared/vite-base.config.ts +8 -7
  349. /package/templates/{shared → browser-agent}/test-upload.txt +0 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * ChatPanel — Agent 流式对话面板
3
+ *
4
+ * 对应 PRD §4.2-4.3:SSE 流式渲染、轮次展示、工具调用卡片
5
+ * 通过 RPC 事件订阅接收 Agent 的实时推送
6
+ */
7
+
8
+ import { useEffect, useRef, useCallback, useState } from "react";
9
+ import { useTranslation } from "react-i18next";
10
+ import { MessageSquare } from "lucide-react";
11
+ import { useChatStore } from "../../stores/use-chat-store";
12
+ import { useSessionStore } from "../../stores/use-session-store";
13
+ import { useConnectionStore } from "../../stores/use-connection-store";
14
+ import { MessageBubble } from "./MessageBubble";
15
+ import { CommandBar } from "./CommandBar";
16
+ import { ToolPicker } from "./ToolPicker";
17
+ import { useAgentChat } from "../../hooks/use-agent-chat";
18
+
19
+ export function ChatPanel() {
20
+ const { t } = useTranslation();
21
+ const messages = useChatStore((s) => s.messages);
22
+ const setMessages = useChatStore((s) => s.setMessages);
23
+
24
+ const currentSessionId = useSessionStore((s) => s.currentSessionId);
25
+ const running = useSessionStore((s) => s.running);
26
+
27
+ const activePlugins = useConnectionStore((s) => s.activePlugins);
28
+
29
+ const inputRef = useRef<HTMLInputElement>(null);
30
+ const messagesEndRef = useRef<HTMLDivElement>(null);
31
+ const [selectedTools, setSelectedTools] = useState<string[]>([]);
32
+
33
+ const toggleTool = (toolId: string): void => {
34
+ setSelectedTools((prev) =>
35
+ prev.includes(toolId) ? prev.filter((t) => t !== toolId) : [...prev, toolId],
36
+ );
37
+ };
38
+
39
+ // 自动滚底
40
+ useEffect(() => {
41
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
42
+ }, [messages]);
43
+
44
+ const currentSession = useSessionStore((s) => s.currentSession);
45
+
46
+ // 从 currentSession 同步 messages
47
+ useEffect(() => {
48
+ if (currentSession?.messages) {
49
+ setMessages(currentSession.messages);
50
+ }
51
+ }, [currentSession?.messages?.length]);
52
+
53
+ const { chat } = useAgentChat();
54
+
55
+ const sendMessage = useCallback(async () => {
56
+ const input = inputRef.current;
57
+ if (!input || !input.value.trim() || running || !currentSessionId) return;
58
+
59
+ const text = input.value.trim();
60
+ input.value = "";
61
+
62
+ // 组合选中工具 + 用户文本
63
+ const tools = [...activePlugins, ...selectedTools];
64
+ const allPlugins = [...new Set(tools)]; // 去重
65
+ await chat(text, currentSessionId, allPlugins);
66
+ setSelectedTools([]);
67
+ }, [currentSessionId, running, activePlugins, selectedTools, chat]);
68
+
69
+ const handleKeyDown = (e: React.KeyboardEvent) => {
70
+ if (e.key === "Enter" && !e.shiftKey) {
71
+ e.preventDefault();
72
+ sendMessage();
73
+ }
74
+ };
75
+
76
+ const sessionAssets = currentSession?.assets || [];
77
+
78
+ return (
79
+ <div className="flex-1 flex flex-col overflow-hidden">
80
+ {/* Header */}
81
+ <div className="px-4 py-2 bg-[var(--color-bg-secondary)] border-b border-[var(--color-border-primary)] flex items-center gap-2 flex-shrink-0">
82
+ <MessageSquare className="w-4 h-4 text-[var(--color-text-accent)]" />
83
+ <h2 className="text-sm font-semibold">{t("chat.title")}</h2>
84
+ {messages.length > 0 && (
85
+ <span className="ml-1 px-2 py-0.5 bg-[var(--color-accent)]/30 text-[var(--color-text-accent)] rounded text-[10px]">
86
+ {messages.length}
87
+ </span>
88
+ )}
89
+ </div>
90
+
91
+ {/* 直接命令栏(不经 Agent) */}
92
+ <CommandBar />
93
+
94
+ {/* 消息区 */}
95
+ <div className="flex-1 overflow-y-auto px-4 py-4" ref={messagesEndRef}>
96
+ {messages.length === 0 ? (
97
+ <div className="flex items-center justify-center h-full text-center">
98
+ <div>
99
+ <div className="text-4xl mb-4">◈</div>
100
+ <div className="text-lg font-semibold mb-2">
101
+ 开始你的第一次采集
102
+ </div>
103
+ <div className="text-sm text-[var(--color-text-tertiary)] max-w-md">
104
+ 在下方输入需求,或点击侧栏的技能快速开始。
105
+ </div>
106
+ </div>
107
+ </div>
108
+ ) : (
109
+ <>
110
+ {messages.map((m) => (
111
+ <MessageBubble key={m.id} message={m} />
112
+ ))}
113
+
114
+ {/* 内嵌资源 */}
115
+ {sessionAssets.length > 0 && (
116
+ <div style={{ marginTop: 16 }}>
117
+ <InlineAssets assets={sessionAssets} />
118
+ </div>
119
+ )}
120
+ </>
121
+ )}
122
+ </div>
123
+
124
+ {/* 输入区 */}
125
+ <div className="px-4 py-3 bg-[var(--color-bg-secondary)] border-t border-[var(--color-border-primary)] flex-shrink-0">
126
+ <div className="flex gap-2">
127
+ <input
128
+ ref={inputRef}
129
+ type="text"
130
+ placeholder={
131
+ running
132
+ ? "Agent 执行中..."
133
+ : "输入需求,如:打开百度、搜索美妆、采集小红书"
134
+ }
135
+ disabled={running || !currentSessionId}
136
+ onKeyDown={handleKeyDown}
137
+ className="flex-1 px-3 py-2 text-sm bg-[var(--color-bg-tertiary)] rounded-lg text-[var(--color-text-primary)] border border-[var(--color-border-secondary)] focus:border-[var(--color-accent)] focus:outline-none disabled:opacity-50"
138
+ />
139
+ <button
140
+ onClick={sendMessage}
141
+ disabled={running || !currentSessionId}
142
+ className="px-4 py-2 text-sm bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] rounded-lg transition-colors disabled:opacity-50"
143
+ >
144
+ {running ? "⏳" : t("chat.send")}
145
+ </button>
146
+ </div>
147
+
148
+ {/* 工具/插件选择 + 快捷输入 */}
149
+ <ToolPicker
150
+ selectedTools={selectedTools}
151
+ onToggle={toggleTool}
152
+ onClear={() => setSelectedTools([])}
153
+ />
154
+ </div>
155
+ </div>
156
+ );
157
+ }
158
+
159
+ function InlineAssets({ assets }: { assets: any[] }) {
160
+ return (
161
+ <div className="space-y-3">
162
+ {assets
163
+ .filter((a) => a.kind === "screenshot")
164
+ .slice(0, 1)
165
+ .map((s) => (
166
+ <img
167
+ key={s.id}
168
+ src={s.dataUrl || `/api/assets/${s.id}/download`}
169
+ alt="screenshot"
170
+ className="rounded-lg border border-[var(--color-border-primary)] max-w-full"
171
+ />
172
+ ))}
173
+
174
+ {assets.filter((a) => a.kind === "image" && a.path).length > 0 && (
175
+ <div className="grid grid-cols-4 gap-1">
176
+ {assets
177
+ .filter((a) => a.kind === "image" && a.path)
178
+ .slice(0, 8)
179
+ .map((img) => (
180
+ <img
181
+ key={img.id}
182
+ src={`/api/assets/${img.id}/download`}
183
+ alt={img.name}
184
+ className="rounded aspect-square object-cover border border-[var(--color-border-primary)]"
185
+ />
186
+ ))}
187
+ </div>
188
+ )}
189
+
190
+ {assets.filter((a) => ["csv", "json", "report"].includes(a.kind))
191
+ .length > 0 && (
192
+ <div className="space-y-1">
193
+ {assets
194
+ .filter((a) => ["csv", "json", "report"].includes(a.kind))
195
+ .map((f) => (
196
+ <a
197
+ key={f.id}
198
+ href={`/api/assets/${f.id}/download`}
199
+ download={f.name}
200
+ className="flex items-center gap-2 px-3 py-2 rounded hover:bg-[var(--color-bg-hover)] text-sm"
201
+ >
202
+ <span>
203
+ {f.kind === "csv"
204
+ ? "📊"
205
+ : f.kind === "json"
206
+ ? "📋"
207
+ : "📄"}
208
+ </span>
209
+ <span className="flex-1 truncate">{f.name}</span>
210
+ <span className="text-xs text-[var(--color-text-tertiary)]">
211
+
212
+ </span>
213
+ </a>
214
+ ))}
215
+ </div>
216
+ )}
217
+ </div>
218
+ );
219
+ }
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Command Bar — 直接执行 xbrowser 命令(不经 Agent)
3
+ *
4
+ * 用户可以输入 scrape/crawl/map/goto/title 等命令,直接调用
5
+ * browser.execXbrowser 执行。选中了标签页时自动注入 --tab 参数。
6
+ */
7
+
8
+ import { useState, useRef } from "react";
9
+ import { Terminal, Play, Loader2, AlertCircle, CheckCircle } from "lucide-react";
10
+ import { apiClient } from "../../lib/api-client";
11
+ import { useConnectionStore } from "../../stores/use-connection-store";
12
+ import { networkBus } from "../../lib/network-bus";
13
+
14
+ const QUICK_COMMANDS = [
15
+ { label: "title", cmd: "title", desc: "页面标题" },
16
+ { label: "url", cmd: "url", desc: "当前 URL" },
17
+ { label: "screenshot", cmd: "screenshot", desc: "截图" },
18
+ { label: "scrape", cmd: "scrape", desc: "采集页面" },
19
+ { label: "map", cmd: "map", desc: "网站结构" },
20
+ { label: "tab list", cmd: "tab list", desc: "标签页列表" },
21
+ ];
22
+
23
+ interface CommandResult {
24
+ success: boolean;
25
+ data: unknown;
26
+ raw: string;
27
+ elapsed: number;
28
+ }
29
+
30
+ export function CommandBar() {
31
+ const [input, setInput] = useState("");
32
+ const [running, setRunning] = useState(false);
33
+ const [result, setResult] = useState<CommandResult | null>(null);
34
+ const [showResult, setShowResult] = useState(false);
35
+ const inputRef = useRef<HTMLInputElement>(null);
36
+
37
+ const browserStatus = useConnectionStore((s) => s.browserStatus);
38
+ const selectedTabIndex = useConnectionStore((s) => s.selectedTabIndex);
39
+ const activeTabIndex = useConnectionStore((s) => s.activeTabIndex);
40
+
41
+ const isOnline = browserStatus === "online";
42
+ const effectiveTab = selectedTabIndex ?? activeTabIndex;
43
+
44
+ const execute = async (command: string): Promise<void> => {
45
+ const cmd = command.trim();
46
+ if (!cmd || running) return;
47
+
48
+ setRunning(true);
49
+ setShowResult(true);
50
+ setResult(null);
51
+ networkBus.emitRequest("browser.execXbrowser", { command: cmd, tabIndex: effectiveTab });
52
+
53
+ const t0 = performance.now();
54
+ try {
55
+ const res = await apiClient.call("browser.execXbrowser", {
56
+ command: cmd,
57
+ tabIndex: selectedTabIndex ?? undefined,
58
+ });
59
+ const elapsed = Math.round(performance.now() - t0);
60
+
61
+ const resultData = res.data;
62
+ const raw = JSON.stringify(resultData, null, 2);
63
+ setResult({
64
+ success: res.success,
65
+ data: resultData,
66
+ raw,
67
+ elapsed,
68
+ });
69
+ networkBus.emitResponse("browser.execXbrowser", elapsed);
70
+ } catch (err) {
71
+ const elapsed = Math.round(performance.now() - t0);
72
+ const errMsg = err instanceof Error ? err.message : String(err);
73
+ setResult({
74
+ success: false,
75
+ data: { error: errMsg },
76
+ raw: errMsg,
77
+ elapsed,
78
+ });
79
+ networkBus.emitResponse("browser.execXbrowser", elapsed);
80
+ } finally {
81
+ setRunning(false);
82
+ }
83
+ };
84
+
85
+ const handleSubmit = (e: React.FormEvent): void => {
86
+ e.preventDefault();
87
+ execute(input);
88
+ };
89
+
90
+ const handleQuickCmd = (cmd: string): void => {
91
+ setInput(cmd);
92
+ execute(cmd);
93
+ };
94
+
95
+ return (
96
+ <div className="border-b border-[var(--color-border-primary)] bg-[var(--color-bg-tertiary)]">
97
+ {/* 命令输入行 */}
98
+ <form onSubmit={handleSubmit} className="flex items-center gap-2 px-4 py-2">
99
+ <Terminal className="w-4 h-4 text-[var(--color-text-accent)] flex-shrink-0" />
100
+ <span className="text-[var(--color-text-tertiary)] text-xs font-mono flex-shrink-0">
101
+ xbrowser&gt;
102
+ </span>
103
+ <input
104
+ ref={inputRef}
105
+ type="text"
106
+ value={input}
107
+ onChange={(e) => setInput(e.target.value)}
108
+ placeholder={
109
+ isOnline
110
+ ? `输入命令,如: scrape https://example.com${
111
+ selectedTabIndex !== null ? ` (作用于 #${selectedTabIndex})` : ""
112
+ }`
113
+ : "浏览器未连接,先连接 Chrome"
114
+ }
115
+ disabled={!isOnline || running}
116
+ className="flex-1 px-2 py-1.5 text-xs font-mono bg-[var(--color-bg-secondary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)] focus:border-[var(--color-accent)] focus:outline-none disabled:opacity-50"
117
+ />
118
+ {selectedTabIndex !== null && (
119
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-[var(--color-accent)]/20 text-[var(--color-text-accent)] flex-shrink-0">
120
+ --tab {selectedTabIndex}
121
+ </span>
122
+ )}
123
+ <button
124
+ type="submit"
125
+ disabled={!isOnline || running || !input.trim()}
126
+ className="px-3 py-1.5 text-xs bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] rounded transition-colors disabled:opacity-50 flex items-center gap-1 flex-shrink-0"
127
+ >
128
+ {running ? (
129
+ <Loader2 className="w-3 h-3 animate-spin" />
130
+ ) : (
131
+ <Play className="w-3 h-3" />
132
+ )}
133
+ 执行
134
+ </button>
135
+ </form>
136
+
137
+ {/* 快捷命令 */}
138
+ <div className="flex gap-1 px-4 pb-2 flex-wrap">
139
+ {QUICK_COMMANDS.map((qc) => (
140
+ <button
141
+ key={qc.label}
142
+ onClick={() => handleQuickCmd(qc.cmd)}
143
+ disabled={!isOnline || running}
144
+ title={qc.desc}
145
+ className="px-2 py-0.5 text-[10px] font-mono bg-[var(--color-bg-secondary)] border border-[var(--color-border-secondary)] rounded hover:border-[var(--color-accent)] hover:text-[var(--color-text-accent)] transition-colors disabled:opacity-50"
146
+ >
147
+ {qc.label}
148
+ </button>
149
+ ))}
150
+ </div>
151
+
152
+ {/* 结果展示 */}
153
+ {showResult && result && (
154
+ <div className="px-4 pb-2">
155
+ <div className="rounded border border-[var(--color-border-secondary)] bg-[var(--color-bg-sidebar)] overflow-hidden">
156
+ {/* 结果头部 */}
157
+ <div className="flex items-center justify-between px-3 py-1.5 border-b border-[var(--color-border-secondary)] bg-[var(--color-bg-secondary)]">
158
+ <div className="flex items-center gap-2 text-xs">
159
+ {result.success ? (
160
+ <CheckCircle className="w-3 h-3 text-[var(--color-text-success)]" />
161
+ ) : (
162
+ <AlertCircle className="w-3 h-3 text-[var(--color-text-error)]" />
163
+ )}
164
+ <span className={result.success ? "text-[var(--color-text-success)]" : "text-[var(--color-text-error)]"}>
165
+ {result.success ? "成功" : "失败"} · {result.elapsed}ms
166
+ </span>
167
+ </div>
168
+ <button
169
+ onClick={() => setShowResult(false)}
170
+ className="text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] text-xs"
171
+ >
172
+ 关闭
173
+ </button>
174
+ </div>
175
+ {/* 结果内容 */}
176
+ <pre className="px-3 py-2 text-xs font-mono overflow-auto max-h-48 text-[var(--color-text-secondary)]">
177
+ {result.raw}
178
+ </pre>
179
+ </div>
180
+ </div>
181
+ )}
182
+ </div>
183
+ );
184
+ }
@@ -0,0 +1,249 @@
1
+ /**
2
+ * 消息气泡 — 用户消息右对齐,Agent 消息左对齐含轮次/工具/步骤
3
+ *
4
+ * 对应 PRD §4.3 流式渲染 + §5.2 消息气泡
5
+ */
6
+
7
+ import type { AgentMessage, ToolCall, Turn } from "../../stores/use-chat-store";
8
+
9
+ // ===== 工具元数据 =====
10
+ const TOOL_META: Record<string, { icon: string; label: string }> = {
11
+ xbrowser: { icon: "🌐", label: "xbrowser" },
12
+ web_search: { icon: "🔎", label: "搜索" },
13
+ fetch_content: { icon: "📄", label: "抓取" },
14
+ bash: { icon: "⚡", label: "命令" },
15
+ todo: { icon: "📝", label: "规划" },
16
+ };
17
+
18
+ function describeCommand(input: string): string {
19
+ try {
20
+ const parsed = JSON.parse(input);
21
+ const cmd = parsed.command || "";
22
+ const firstWord = cmd.split(/\s+/)[0];
23
+ const cmdMeta: Record<string, string> = {
24
+ goto: "🧭 打开",
25
+ open: "🧭 打开",
26
+ scrape: "📡 采集",
27
+ crawl: "🕷️ 爬取",
28
+ screenshot: "📸 截图",
29
+ eval: "⚙️ 执行JS",
30
+ click: "👆 点击",
31
+ fill: "⌨️ 输入",
32
+ scroll: "⬇️ 滚动",
33
+ tab: "📋 标签页",
34
+ search: "🔍 搜索",
35
+ };
36
+ return cmdMeta[firstWord] || firstWord;
37
+ } catch {
38
+ return "";
39
+ }
40
+ }
41
+
42
+ function ToolCallCard({ call }: { call: ToolCall }) {
43
+ const meta = TOOL_META[call.tool] || { icon: "🔧", label: call.tool };
44
+ const isRunning = call.status === "running";
45
+
46
+ let displayLabel = meta.label;
47
+ let displayInput = call.input;
48
+ if (call.tool === "xbrowser") {
49
+ const desc = describeCommand(call.input);
50
+ if (desc) displayLabel = desc;
51
+ try {
52
+ displayInput = JSON.parse(call.input).command || "";
53
+ } catch {}
54
+ }
55
+
56
+ return (
57
+ <div
58
+ className={`flex items-center gap-2 px-2.5 py-1.5 rounded-lg mb-1 text-sm ${
59
+ isRunning ? "bg-[var(--color-bg-active)]" : "bg-[var(--color-bg-tertiary)]"
60
+ }`}
61
+ >
62
+ <span>{meta.icon}</span>
63
+ <span className="font-medium">{displayLabel}</span>
64
+ <span className="text-[var(--color-text-tertiary)] text-xs">
65
+ {call.tool}
66
+ </span>
67
+ {displayInput && (
68
+ <span className="text-[var(--color-text-tertiary)] text-xs flex-1 truncate">
69
+ {displayInput.slice(0, 80)}
70
+ </span>
71
+ )}
72
+ <span className="ml-auto">{isRunning ? "⏳" : "✓"}</span>
73
+ </div>
74
+ );
75
+ }
76
+
77
+ function TurnBlock({ turn }: { turn: Turn }) {
78
+ const isRunning = !turn.done;
79
+ const toolCount = (turn.toolCalls || []).length;
80
+ const hasText = !!(turn.text && turn.text.trim());
81
+
82
+ return (
83
+ <details
84
+ open={isRunning}
85
+ className="mb-2 rounded-lg border border-[var(--color-border-primary)] overflow-hidden"
86
+ >
87
+ <summary
88
+ className={`px-2.5 py-1.5 cursor-pointer text-xs font-medium flex items-center gap-1.5 ${
89
+ isRunning
90
+ ? "bg-[var(--color-bg-active)]"
91
+ : "bg-[var(--color-bg-secondary)]"
92
+ }`}
93
+ >
94
+ <span>{isRunning ? "⚡" : "✓"}</span>
95
+ <span>第 {turn.turn} 轮</span>
96
+ {toolCount > 0 && (
97
+ <span className="text-[var(--color-text-tertiary)]">
98
+ · {toolCount} 个操作
99
+ </span>
100
+ )}
101
+ {hasText && (
102
+ <span className="text-[var(--color-text-tertiary)] ml-auto truncate max-w-[200px]">
103
+ {turn.text.slice(0, 40)}
104
+ </span>
105
+ )}
106
+ </summary>
107
+ <div className="p-2.5">
108
+ {turn.toolCalls.map((tc) => (
109
+ <ToolCallCard key={tc.id} call={tc} />
110
+ ))}
111
+ {hasText && (
112
+ <div className="mt-1 text-sm whitespace-pre-wrap">
113
+ {turn.text}
114
+ </div>
115
+ )}
116
+ </div>
117
+ </details>
118
+ );
119
+ }
120
+
121
+ function stepIcon(status: string): string {
122
+ switch (status) {
123
+ case "done":
124
+ return "✅";
125
+ case "running":
126
+ return "⏳";
127
+ case "error":
128
+ return "❌";
129
+ default:
130
+ return "⏸️";
131
+ }
132
+ }
133
+
134
+ export function MessageBubble({
135
+ message,
136
+ }: {
137
+ message: AgentMessage | { id: string; role: "user"; text: string; at: number };
138
+ }) {
139
+ if (message.role === "user") {
140
+ return (
141
+ <div className="flex justify-end mb-4">
142
+ <div
143
+ className="max-w-[70%] px-4 py-2.5 rounded-2xl text-sm leading-relaxed"
144
+ style={{
145
+ background: "linear-gradient(135deg, #8b5cf6, #3b82f6)",
146
+ color: "#fff",
147
+ }}
148
+ >
149
+ {message.text}
150
+ </div>
151
+ </div>
152
+ );
153
+ }
154
+
155
+ const m = message as AgentMessage;
156
+
157
+ return (
158
+ <div className="mb-4">
159
+ <div className="flex items-center gap-2 mb-2">
160
+ <span
161
+ className="w-7 h-7 rounded-full flex items-center justify-center text-sm text-white"
162
+ style={{ background: "var(--color-accent)" }}
163
+ >
164
+
165
+ </span>
166
+ <span className="font-semibold text-sm">Agent</span>
167
+ </div>
168
+
169
+ {/* 思考过程(默认折叠) */}
170
+ {m.thinking && (
171
+ <details className="mb-2 text-xs text-[var(--color-text-tertiary)]">
172
+ <summary className="cursor-pointer mb-1">💭 思考过程</summary>
173
+ <div className="p-2 bg-[var(--color-bg-tertiary)] rounded-lg whitespace-pre-wrap text-xs leading-relaxed">
174
+ {m.thinking.slice(-800)}
175
+ </div>
176
+ </details>
177
+ )}
178
+
179
+ {/* 轮次展示 */}
180
+ {m.turns?.map((turn) => (
181
+ <TurnBlock key={turn.turn} turn={turn} />
182
+ ))}
183
+
184
+ {/* 步骤清单 */}
185
+ {m.steps && m.steps.length > 0 && (
186
+ <div className="mb-2 p-2 bg-[var(--color-bg-tertiary)] rounded-lg text-xs">
187
+ {m.steps.map((step, i) => (
188
+ <div key={i} className="flex items-center gap-1.5 py-0.5">
189
+ <span>{stepIcon(step.status)}</span>
190
+ <span>{step.label}</span>
191
+ {step.detail && (
192
+ <span className="text-[var(--color-text-tertiary)]">
193
+ {step.detail}
194
+ </span>
195
+ )}
196
+ </div>
197
+ ))}
198
+ </div>
199
+ )}
200
+
201
+ {/* 摘要卡 */}
202
+ {m.summary && (
203
+ <div className="mb-2 p-3 rounded-lg border border-[var(--color-border-primary)] bg-[var(--color-bg-secondary)]">
204
+ <div className="font-semibold text-sm mb-2">✅ 采集完成摘要</div>
205
+ <div className="grid grid-cols-2 gap-2 text-xs">
206
+ <div>
207
+ <div className="text-lg font-bold">{m.summary.noteCount}</div>
208
+ <div className="text-[var(--color-text-tertiary)]">笔记</div>
209
+ </div>
210
+ <div>
211
+ <div className="text-lg font-bold">{m.summary.imageCount}</div>
212
+ <div className="text-[var(--color-text-tertiary)]">图片</div>
213
+ </div>
214
+ <div>
215
+ <div className="text-lg font-bold">{m.summary.successRate}</div>
216
+ <div className="text-[var(--color-text-tertiary)]">成功率</div>
217
+ </div>
218
+ <div>
219
+ <div className="text-lg font-bold">
220
+ {fmtDuration(m.summary.durationMs)}
221
+ </div>
222
+ <div className="text-[var(--color-text-tertiary)]">耗时</div>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ )}
227
+
228
+ {/* 最终文本 */}
229
+ {m.text && (
230
+ <div className="text-sm leading-relaxed whitespace-pre-wrap">
231
+ {m.text}
232
+ </div>
233
+ )}
234
+
235
+ {/* 错误 */}
236
+ {m.error && (
237
+ <div className="p-2 rounded-lg text-sm bg-red-500/10 text-[var(--color-text-error)]">
238
+ ❌ {m.error}
239
+ </div>
240
+ )}
241
+ </div>
242
+ );
243
+ }
244
+
245
+ function fmtDuration(ms: number): string {
246
+ if (ms < 1000) return `${ms}ms`;
247
+ if (ms < 60000) return `${Math.round(ms / 1000)}s`;
248
+ return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;
249
+ }