@dyyz1993/create-agent 1.9.0

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 (358) hide show
  1. package/package.json +25 -0
  2. package/src/__tests__/cli.test.ts +85 -0
  3. package/src/__tests__/copy.test.ts +168 -0
  4. package/src/__tests__/templates.test.ts +87 -0
  5. package/src/cli.ts +65 -0
  6. package/src/commands/create.ts +49 -0
  7. package/src/commands/list.ts +12 -0
  8. package/src/commands/status.ts +85 -0
  9. package/src/commands/workspace.ts +129 -0
  10. package/src/lib/copy.ts +285 -0
  11. package/src/lib/templates.ts +57 -0
  12. package/src/lib/types.ts +6 -0
  13. package/templates/agent/.env.example +7 -0
  14. package/templates/agent/LICENSE +21 -0
  15. package/templates/agent/README.md +87 -0
  16. package/templates/agent/bun.lock +1279 -0
  17. package/templates/agent/electrobun.config.ts +27 -0
  18. package/templates/agent/eslint.config.mjs +63 -0
  19. package/templates/agent/llms.txt +24 -0
  20. package/templates/agent/package-lock.json +3670 -0
  21. package/templates/agent/package.json +59 -0
  22. package/templates/agent/postcss.config.js +6 -0
  23. package/templates/agent/scripts/dev.ts +138 -0
  24. package/templates/agent/src/__tests__/server-config.test.ts +59 -0
  25. package/templates/agent/src/bun/index.ts +73 -0
  26. package/templates/agent/src/gateway/__tests__/ws-handler.test.ts +48 -0
  27. package/templates/agent/src/gateway/http-routes.ts +1 -0
  28. package/templates/agent/src/gateway/ipc-transport.ts +68 -0
  29. package/templates/agent/src/gateway/ws-handler.ts +86 -0
  30. package/templates/agent/src/mainview/App.tsx +41 -0
  31. package/templates/agent/src/mainview/__tests__/components/ChatPanel.test.tsx +114 -0
  32. package/templates/agent/src/mainview/__tests__/components/ExplorerSidebar.test.tsx +141 -0
  33. package/templates/agent/src/mainview/__tests__/components/MessageBubble.test.tsx +72 -0
  34. package/templates/agent/src/mainview/__tests__/hooks/use-input-history.test.ts +125 -0
  35. package/templates/agent/src/mainview/__tests__/hooks/use-rpc-init.test.ts +217 -0
  36. package/templates/agent/src/mainview/__tests__/hooks/use-sidebar-resize.test.ts +127 -0
  37. package/templates/agent/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
  38. package/templates/agent/src/mainview/__tests__/setup.ts +37 -0
  39. package/templates/agent/src/mainview/__tests__/stores/use-chat-store.test.ts +83 -0
  40. package/templates/agent/src/mainview/__tests__/stores/use-sidebar-store.test.ts +85 -0
  41. package/templates/agent/src/mainview/components/activity-bar/ActivityBar.tsx +36 -0
  42. package/templates/agent/src/mainview/components/activity-bar/MobileTabBar.tsx +36 -0
  43. package/templates/agent/src/mainview/components/activity-bar/__tests__/ActivityBar.test.tsx +44 -0
  44. package/templates/agent/src/mainview/components/activity-bar/__tests__/MobileTabBar.test.tsx +44 -0
  45. package/templates/agent/src/mainview/components/bash/BashPanel.tsx +206 -0
  46. package/templates/agent/src/mainview/components/bash/__tests__/BashPanel.test.tsx +103 -0
  47. package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +102 -0
  48. package/templates/agent/src/mainview/components/chat/MessageBubble.tsx +86 -0
  49. package/templates/agent/src/mainview/components/common/ErrorBoundary.tsx +58 -0
  50. package/templates/agent/src/mainview/components/common/LanguageSwitcher.tsx +14 -0
  51. package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +24 -0
  52. package/templates/agent/src/mainview/components/common/__tests__/ErrorBoundary.test.tsx +74 -0
  53. package/templates/agent/src/mainview/components/common/__tests__/LanguageSwitcher.test.tsx +44 -0
  54. package/templates/agent/src/mainview/components/common/__tests__/ThemeToggle.test.tsx +41 -0
  55. package/templates/agent/src/mainview/components/debug/DebugPanel.tsx +120 -0
  56. package/templates/agent/src/mainview/components/debug/__tests__/DebugPanel.test.tsx +102 -0
  57. package/templates/agent/src/mainview/components/diff/DiffViewerPanel.tsx +106 -0
  58. package/templates/agent/src/mainview/components/diff/__tests__/DiffViewerPanel.test.tsx +73 -0
  59. package/templates/agent/src/mainview/components/explorer/ConfirmDialog.tsx +53 -0
  60. package/templates/agent/src/mainview/components/explorer/ContextMenu.tsx +82 -0
  61. package/templates/agent/src/mainview/components/explorer/ExplorerSidebar.tsx +221 -0
  62. package/templates/agent/src/mainview/components/explorer/InlineInput.tsx +55 -0
  63. package/templates/agent/src/mainview/components/explorer/TreeNodeItem.tsx +121 -0
  64. package/templates/agent/src/mainview/components/explorer/__tests__/ConfirmDialog.test.tsx +37 -0
  65. package/templates/agent/src/mainview/components/explorer/__tests__/ContextMenu.test.tsx +49 -0
  66. package/templates/agent/src/mainview/components/explorer/__tests__/InlineInput.test.tsx +48 -0
  67. package/templates/agent/src/mainview/components/explorer/__tests__/TreeNodeItem.test.tsx +146 -0
  68. package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +239 -0
  69. package/templates/agent/src/mainview/components/feed/__tests__/FeedPanel.test.tsx +108 -0
  70. package/templates/agent/src/mainview/components/file-preview/FilePreviewOverlay.tsx +56 -0
  71. package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +99 -0
  72. package/templates/agent/src/mainview/components/file-preview/__tests__/FilePreviewOverlay.test.tsx +96 -0
  73. package/templates/agent/src/mainview/components/file-preview/__tests__/VirtualizedCodeView.test.tsx +58 -0
  74. package/templates/agent/src/mainview/components/git/GitBranchSelector.tsx +98 -0
  75. package/templates/agent/src/mainview/components/git/GitCommitInput.tsx +60 -0
  76. package/templates/agent/src/mainview/components/git/GitPanel.tsx +528 -0
  77. package/templates/agent/src/mainview/components/git/__tests__/GitBranchSelector.test.tsx +76 -0
  78. package/templates/agent/src/mainview/components/git/__tests__/GitCommitInput.test.tsx +91 -0
  79. package/templates/agent/src/mainview/components/git/__tests__/GitPanel.test.tsx +174 -0
  80. package/templates/agent/src/mainview/components/layout/AppLayout.tsx +170 -0
  81. package/templates/agent/src/mainview/components/layout/__tests__/AppLayout.test.tsx +163 -0
  82. package/templates/agent/src/mainview/components/rules/RulesPanel.tsx +117 -0
  83. package/templates/agent/src/mainview/components/rules/__tests__/RulesPanel.test.tsx +91 -0
  84. package/templates/agent/src/mainview/components/search/SearchPanel.tsx +404 -0
  85. package/templates/agent/src/mainview/components/search/__tests__/SearchPanel.test.tsx +50 -0
  86. package/templates/agent/src/mainview/components/sidebar/PinButton.tsx +20 -0
  87. package/templates/agent/src/mainview/components/sidebar/__tests__/PinButton.test.tsx +48 -0
  88. package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +141 -0
  89. package/templates/agent/src/mainview/components/todo/__tests__/TodoPanel.test.tsx +91 -0
  90. package/templates/agent/src/mainview/global.d.ts +13 -0
  91. package/templates/agent/src/mainview/hooks/__tests__/use-breakpoint.test.ts +147 -0
  92. package/templates/agent/src/mainview/hooks/use-breakpoint.ts +34 -0
  93. package/templates/agent/src/mainview/hooks/use-input-history.ts +84 -0
  94. package/templates/agent/src/mainview/hooks/use-rpc-init.ts +61 -0
  95. package/templates/agent/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  96. package/templates/agent/src/mainview/index.css +61 -0
  97. package/templates/agent/src/mainview/index.html +12 -0
  98. package/templates/agent/src/mainview/lib/api-client.ts +192 -0
  99. package/templates/agent/src/mainview/lib/i18n/index.ts +30 -0
  100. package/templates/agent/src/mainview/lib/i18n/locales/en.json +157 -0
  101. package/templates/agent/src/mainview/lib/i18n/locales/zh.json +157 -0
  102. package/templates/agent/src/mainview/lib/rpc-cache.ts +94 -0
  103. package/templates/agent/src/mainview/main.tsx +24 -0
  104. package/templates/agent/src/mainview/stores/__tests__/use-app-store.test.ts +86 -0
  105. package/templates/agent/src/mainview/stores/__tests__/use-bash-store.test.ts +99 -0
  106. package/templates/agent/src/mainview/stores/__tests__/use-connection-store.test.ts +26 -0
  107. package/templates/agent/src/mainview/stores/__tests__/use-explorer-store.test.ts +130 -0
  108. package/templates/agent/src/mainview/stores/__tests__/use-feed-store.test.ts +132 -0
  109. package/templates/agent/src/mainview/stores/__tests__/use-git-store.test.ts +161 -0
  110. package/templates/agent/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  111. package/templates/agent/src/mainview/stores/__tests__/use-log-store.test.ts +28 -0
  112. package/templates/agent/src/mainview/stores/__tests__/use-notification-store.test.ts +102 -0
  113. package/templates/agent/src/mainview/stores/__tests__/use-rules-store.test.ts +75 -0
  114. package/templates/agent/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  115. package/templates/agent/src/mainview/stores/__tests__/use-todo-store.test.ts +75 -0
  116. package/templates/agent/src/mainview/stores/message-batcher.ts +25 -0
  117. package/templates/agent/src/mainview/stores/use-app-store.ts +99 -0
  118. package/templates/agent/src/mainview/stores/use-bash-store.ts +93 -0
  119. package/templates/agent/src/mainview/stores/use-chat-store.ts +37 -0
  120. package/templates/agent/src/mainview/stores/use-connection-store.ts +42 -0
  121. package/templates/agent/src/mainview/stores/use-explorer-store.ts +320 -0
  122. package/templates/agent/src/mainview/stores/use-feed-store.ts +129 -0
  123. package/templates/agent/src/mainview/stores/use-git-store.ts +291 -0
  124. package/templates/agent/src/mainview/stores/use-locale-store.ts +27 -0
  125. package/templates/agent/src/mainview/stores/use-log-store.ts +16 -0
  126. package/templates/agent/src/mainview/stores/use-notification-store.ts +82 -0
  127. package/templates/agent/src/mainview/stores/use-rules-store.ts +54 -0
  128. package/templates/agent/src/mainview/stores/use-sidebar-store.ts +99 -0
  129. package/templates/agent/src/mainview/stores/use-theme-store.ts +46 -0
  130. package/templates/agent/src/mainview/stores/use-todo-store.ts +54 -0
  131. package/templates/agent/src/mainview/types/index.ts +33 -0
  132. package/templates/agent/src/mainview/utils/constants.ts +1 -0
  133. package/templates/agent/src/mainview/utils/drop-handler.ts +150 -0
  134. package/templates/agent/src/mainview/utils/file-icon.tsx +24 -0
  135. package/templates/agent/src/mainview/utils/file-utils.ts +35 -0
  136. package/templates/agent/src/server-config.ts +43 -0
  137. package/templates/agent/src/server.ts +89 -0
  138. package/templates/agent/src/shared/handlers/__tests__/bash-handler.test.ts +135 -0
  139. package/templates/agent/src/shared/handlers/__tests__/chat-handler.test.ts +77 -0
  140. package/templates/agent/src/shared/handlers/__tests__/feed-handler.test.ts +108 -0
  141. package/templates/agent/src/shared/handlers/__tests__/file-handler.test.ts +100 -0
  142. package/templates/agent/src/shared/handlers/__tests__/system-handler.test.ts +55 -0
  143. package/templates/agent/src/shared/handlers/__tests__/timer-handler.test.ts +77 -0
  144. package/templates/agent/src/shared/handlers/bash.ts +89 -0
  145. package/templates/agent/src/shared/handlers/chat.ts +179 -0
  146. package/templates/agent/src/shared/handlers/feed.ts +53 -0
  147. package/templates/agent/src/shared/handlers/file.ts +99 -0
  148. package/templates/agent/src/shared/handlers/git.ts +263 -0
  149. package/templates/agent/src/shared/handlers/index.ts +10 -0
  150. package/templates/agent/src/shared/handlers/rules.ts +45 -0
  151. package/templates/agent/src/shared/handlers/system.ts +27 -0
  152. package/templates/agent/src/shared/handlers/timer.ts +34 -0
  153. package/templates/agent/src/shared/handlers/todo.ts +45 -0
  154. package/templates/agent/src/shared/lib/__tests__/bash-security.test.ts +125 -0
  155. package/templates/agent/src/shared/lib/__tests__/logger.test.ts +92 -0
  156. package/templates/agent/src/shared/lib/__tests__/path-security.test.ts +77 -0
  157. package/templates/agent/src/shared/lib/bash-security.ts +64 -0
  158. package/templates/agent/src/shared/lib/logger.ts +130 -0
  159. package/templates/agent/src/shared/lib/path-security.ts +38 -0
  160. package/templates/agent/src/shared/lib/port-registry.ts +103 -0
  161. package/templates/agent/src/shared/modules/bash.ts +19 -0
  162. package/templates/agent/src/shared/modules/chat.ts +20 -0
  163. package/templates/agent/src/shared/modules/feed.ts +30 -0
  164. package/templates/agent/src/shared/modules/file.ts +40 -0
  165. package/templates/agent/src/shared/modules/git.ts +81 -0
  166. package/templates/agent/src/shared/modules/rules.ts +25 -0
  167. package/templates/agent/src/shared/modules/system.ts +8 -0
  168. package/templates/agent/src/shared/modules/timer.ts +11 -0
  169. package/templates/agent/src/shared/modules/todo.ts +27 -0
  170. package/templates/agent/src/shared/register-all-handlers.ts +36 -0
  171. package/templates/agent/src/shared/rpc-schema.ts +35 -0
  172. package/templates/agent/tailwind.config.js +15 -0
  173. package/templates/agent/tsconfig.json +26 -0
  174. package/templates/agent/vite.config.ts +3 -0
  175. package/templates/agent/vitest.config.ts +3 -0
  176. package/templates/chat/.env.example +6 -0
  177. package/templates/chat/LICENSE +21 -0
  178. package/templates/chat/README.md +56 -0
  179. package/templates/chat/bun.lock +1203 -0
  180. package/templates/chat/electrobun.config.ts +27 -0
  181. package/templates/chat/eslint.config.mjs +56 -0
  182. package/templates/chat/llms.txt +24 -0
  183. package/templates/chat/package-lock.json +3670 -0
  184. package/templates/chat/package.json +58 -0
  185. package/templates/chat/postcss.config.js +6 -0
  186. package/templates/chat/scripts/dev.ts +138 -0
  187. package/templates/chat/src/__tests__/server-config.test.ts +59 -0
  188. package/templates/chat/src/bun/index.ts +72 -0
  189. package/templates/chat/src/gateway/__tests__/ws-handler.test.ts +48 -0
  190. package/templates/chat/src/gateway/http-routes.ts +1 -0
  191. package/templates/chat/src/gateway/ipc-transport.ts +68 -0
  192. package/templates/chat/src/gateway/ws-handler.ts +86 -0
  193. package/templates/chat/src/mainview/App.tsx +108 -0
  194. package/templates/chat/src/mainview/__tests__/components/ChatPanel.test.tsx +76 -0
  195. package/templates/chat/src/mainview/__tests__/components/MessageBubble.test.tsx +48 -0
  196. package/templates/chat/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
  197. package/templates/chat/src/mainview/__tests__/setup-verify.test.ts +8 -0
  198. package/templates/chat/src/mainview/__tests__/setup.ts +37 -0
  199. package/templates/chat/src/mainview/__tests__/stores/use-app-store.test.ts +46 -0
  200. package/templates/chat/src/mainview/__tests__/stores/use-chat-store.test.ts +104 -0
  201. package/templates/chat/src/mainview/__tests__/stores/use-connection-store.test.ts +35 -0
  202. package/templates/chat/src/mainview/__tests__/stores/use-log-store.test.ts +45 -0
  203. package/templates/chat/src/mainview/__tests__/stores/use-notification-store.test.ts +102 -0
  204. package/templates/chat/src/mainview/__tests__/stores/use-sidebar-store.test.ts +89 -0
  205. package/templates/chat/src/mainview/components/activity-bar/ActivityBar.tsx +26 -0
  206. package/templates/chat/src/mainview/components/activity-bar/MobileTabBar.tsx +20 -0
  207. package/templates/chat/src/mainview/components/chat/ChatPanel.tsx +66 -0
  208. package/templates/chat/src/mainview/components/chat/MessageBubble.tsx +85 -0
  209. package/templates/chat/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  210. package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +24 -0
  211. package/templates/chat/src/mainview/components/sidebar/PinButton.tsx +20 -0
  212. package/templates/chat/src/mainview/global.d.ts +13 -0
  213. package/templates/chat/src/mainview/hooks/use-breakpoint.ts +34 -0
  214. package/templates/chat/src/mainview/hooks/use-input-history.ts +84 -0
  215. package/templates/chat/src/mainview/index.css +61 -0
  216. package/templates/chat/src/mainview/index.html +12 -0
  217. package/templates/chat/src/mainview/lib/api-client.ts +175 -0
  218. package/templates/chat/src/mainview/lib/i18n/index.ts +30 -0
  219. package/templates/chat/src/mainview/lib/i18n/locales/en.json +157 -0
  220. package/templates/chat/src/mainview/lib/i18n/locales/zh.json +157 -0
  221. package/templates/chat/src/mainview/main.tsx +20 -0
  222. package/templates/chat/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  223. package/templates/chat/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  224. package/templates/chat/src/mainview/stores/message-batcher.ts +25 -0
  225. package/templates/chat/src/mainview/stores/use-app-store.ts +50 -0
  226. package/templates/chat/src/mainview/stores/use-chat-store.ts +37 -0
  227. package/templates/chat/src/mainview/stores/use-connection-store.ts +42 -0
  228. package/templates/chat/src/mainview/stores/use-locale-store.ts +27 -0
  229. package/templates/chat/src/mainview/stores/use-log-store.ts +16 -0
  230. package/templates/chat/src/mainview/stores/use-notification-store.ts +82 -0
  231. package/templates/chat/src/mainview/stores/use-sidebar-store.ts +98 -0
  232. package/templates/chat/src/mainview/stores/use-theme-store.ts +46 -0
  233. package/templates/chat/src/mainview/types/index.ts +8 -0
  234. package/templates/chat/src/mainview/utils/constants.ts +1 -0
  235. package/templates/chat/src/server-config.ts +42 -0
  236. package/templates/chat/src/server.ts +89 -0
  237. package/templates/chat/src/shared/__tests__/chat-handler.test.ts +71 -0
  238. package/templates/chat/src/shared/__tests__/system-handler.test.ts +70 -0
  239. package/templates/chat/src/shared/handlers/chat.ts +186 -0
  240. package/templates/chat/src/shared/handlers/index.ts +2 -0
  241. package/templates/chat/src/shared/handlers/system.ts +27 -0
  242. package/templates/chat/src/shared/lib/logger.ts +130 -0
  243. package/templates/chat/src/shared/lib/path-security.ts +38 -0
  244. package/templates/chat/src/shared/lib/port-registry.ts +103 -0
  245. package/templates/chat/src/shared/modules/chat.ts +20 -0
  246. package/templates/chat/src/shared/modules/system.ts +8 -0
  247. package/templates/chat/src/shared/register-all-handlers.ts +36 -0
  248. package/templates/chat/src/shared/rpc-schema.ts +11 -0
  249. package/templates/chat/tailwind.config.js +15 -0
  250. package/templates/chat/tsconfig.json +26 -0
  251. package/templates/chat/vite.config.ts +3 -0
  252. package/templates/chat/vitest.config.ts +3 -0
  253. package/templates/general/.env.example +7 -0
  254. package/templates/general/LICENSE +21 -0
  255. package/templates/general/README.md +113 -0
  256. package/templates/general/bun.lock +1266 -0
  257. package/templates/general/electrobun.config.ts +27 -0
  258. package/templates/general/eslint.config.mjs +56 -0
  259. package/templates/general/llms.txt +24 -0
  260. package/templates/general/package-lock.json +3670 -0
  261. package/templates/general/package.json +59 -0
  262. package/templates/general/postcss.config.js +6 -0
  263. package/templates/general/scripts/dev.ts +138 -0
  264. package/templates/general/src/__tests__/server-config.test.ts +59 -0
  265. package/templates/general/src/bun/index.ts +72 -0
  266. package/templates/general/src/gateway/http-routes.ts +1 -0
  267. package/templates/general/src/gateway/ipc-transport.ts +68 -0
  268. package/templates/general/src/gateway/ws-handler.ts +86 -0
  269. package/templates/general/src/mainview/App.tsx +40 -0
  270. package/templates/general/src/mainview/__tests__/components/ChatPanel.test.tsx +82 -0
  271. package/templates/general/src/mainview/__tests__/components/FeedPanel.test.tsx +109 -0
  272. package/templates/general/src/mainview/__tests__/components/GitPanel.test.tsx +151 -0
  273. package/templates/general/src/mainview/__tests__/i18n/i18n.test.ts +48 -0
  274. package/templates/general/src/mainview/__tests__/setup-verify.test.ts +8 -0
  275. package/templates/general/src/mainview/__tests__/setup.ts +37 -0
  276. package/templates/general/src/mainview/components/activity-bar/ActivityBar.tsx +40 -0
  277. package/templates/general/src/mainview/components/activity-bar/MobileTabBar.tsx +29 -0
  278. package/templates/general/src/mainview/components/chat/ChatPanel.tsx +66 -0
  279. package/templates/general/src/mainview/components/chat/MessageBubble.tsx +85 -0
  280. package/templates/general/src/mainview/components/common/LanguageSwitcher.tsx +15 -0
  281. package/templates/general/src/mainview/components/common/ThemeToggle.tsx +24 -0
  282. package/templates/general/src/mainview/components/debug/DebugPanel.tsx +176 -0
  283. package/templates/general/src/mainview/components/diff/DiffViewerPanel.tsx +108 -0
  284. package/templates/general/src/mainview/components/explorer/ConfirmDialog.tsx +52 -0
  285. package/templates/general/src/mainview/components/explorer/ContextMenu.tsx +82 -0
  286. package/templates/general/src/mainview/components/explorer/ExplorerSidebar.tsx +225 -0
  287. package/templates/general/src/mainview/components/explorer/InlineInput.tsx +55 -0
  288. package/templates/general/src/mainview/components/explorer/TreeNodeItem.tsx +121 -0
  289. package/templates/general/src/mainview/components/feed/FeedPanel.tsx +248 -0
  290. package/templates/general/src/mainview/components/file-preview/FilePreviewOverlay.tsx +55 -0
  291. package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +99 -0
  292. package/templates/general/src/mainview/components/git/GitBranchSelector.tsx +93 -0
  293. package/templates/general/src/mainview/components/git/GitCommitInput.tsx +60 -0
  294. package/templates/general/src/mainview/components/git/GitPanel.tsx +533 -0
  295. package/templates/general/src/mainview/components/layout/AppLayout.tsx +146 -0
  296. package/templates/general/src/mainview/components/search/SearchPanel.tsx +405 -0
  297. package/templates/general/src/mainview/components/sidebar/PinButton.tsx +20 -0
  298. package/templates/general/src/mainview/global.d.ts +13 -0
  299. package/templates/general/src/mainview/hooks/use-breakpoint.ts +34 -0
  300. package/templates/general/src/mainview/hooks/use-input-history.ts +84 -0
  301. package/templates/general/src/mainview/hooks/use-rpc-init.ts +61 -0
  302. package/templates/general/src/mainview/hooks/use-sidebar-resize.ts +40 -0
  303. package/templates/general/src/mainview/index.css +61 -0
  304. package/templates/general/src/mainview/index.html +12 -0
  305. package/templates/general/src/mainview/lib/api-client.ts +175 -0
  306. package/templates/general/src/mainview/lib/i18n/index.ts +30 -0
  307. package/templates/general/src/mainview/lib/i18n/locales/en.json +157 -0
  308. package/templates/general/src/mainview/lib/i18n/locales/zh.json +157 -0
  309. package/templates/general/src/mainview/main.tsx +20 -0
  310. package/templates/general/src/mainview/stores/__tests__/use-chat-store.test.ts +104 -0
  311. package/templates/general/src/mainview/stores/__tests__/use-connection-store.test.ts +35 -0
  312. package/templates/general/src/mainview/stores/__tests__/use-locale-store.test.ts +32 -0
  313. package/templates/general/src/mainview/stores/__tests__/use-log-store.test.ts +45 -0
  314. package/templates/general/src/mainview/stores/__tests__/use-theme-store.test.ts +59 -0
  315. package/templates/general/src/mainview/stores/message-batcher.ts +25 -0
  316. package/templates/general/src/mainview/stores/use-app-store.ts +104 -0
  317. package/templates/general/src/mainview/stores/use-chat-store.ts +37 -0
  318. package/templates/general/src/mainview/stores/use-connection-store.ts +42 -0
  319. package/templates/general/src/mainview/stores/use-explorer-store.ts +315 -0
  320. package/templates/general/src/mainview/stores/use-feed-store.ts +129 -0
  321. package/templates/general/src/mainview/stores/use-git-store.ts +284 -0
  322. package/templates/general/src/mainview/stores/use-locale-store.ts +27 -0
  323. package/templates/general/src/mainview/stores/use-log-store.ts +16 -0
  324. package/templates/general/src/mainview/stores/use-notification-store.ts +82 -0
  325. package/templates/general/src/mainview/stores/use-sidebar-store.ts +99 -0
  326. package/templates/general/src/mainview/stores/use-theme-store.ts +46 -0
  327. package/templates/general/src/mainview/types/index.ts +33 -0
  328. package/templates/general/src/mainview/utils/constants.ts +1 -0
  329. package/templates/general/src/mainview/utils/drop-handler.ts +150 -0
  330. package/templates/general/src/mainview/utils/file-icon.tsx +24 -0
  331. package/templates/general/src/mainview/utils/file-utils.ts +35 -0
  332. package/templates/general/src/server-config.ts +42 -0
  333. package/templates/general/src/server.ts +89 -0
  334. package/templates/general/src/shared/handlers/__tests__/chat.test.ts +183 -0
  335. package/templates/general/src/shared/handlers/__tests__/system.test.ts +94 -0
  336. package/templates/general/src/shared/handlers/__tests__/timer.test.ts +113 -0
  337. package/templates/general/src/shared/handlers/chat.ts +179 -0
  338. package/templates/general/src/shared/handlers/feed.ts +53 -0
  339. package/templates/general/src/shared/handlers/file.ts +99 -0
  340. package/templates/general/src/shared/handlers/git.ts +263 -0
  341. package/templates/general/src/shared/handlers/index.ts +7 -0
  342. package/templates/general/src/shared/handlers/system.ts +27 -0
  343. package/templates/general/src/shared/handlers/timer.ts +34 -0
  344. package/templates/general/src/shared/lib/logger.ts +130 -0
  345. package/templates/general/src/shared/lib/path-security.ts +38 -0
  346. package/templates/general/src/shared/lib/port-registry.ts +103 -0
  347. package/templates/general/src/shared/modules/chat.ts +20 -0
  348. package/templates/general/src/shared/modules/feed.ts +30 -0
  349. package/templates/general/src/shared/modules/file.ts +40 -0
  350. package/templates/general/src/shared/modules/git.ts +81 -0
  351. package/templates/general/src/shared/modules/system.ts +8 -0
  352. package/templates/general/src/shared/modules/timer.ts +11 -0
  353. package/templates/general/src/shared/register-all-handlers.ts +36 -0
  354. package/templates/general/src/shared/rpc-schema.ts +31 -0
  355. package/templates/general/tailwind.config.js +15 -0
  356. package/templates/general/tsconfig.json +26 -0
  357. package/templates/general/vite.config.ts +3 -0
  358. package/templates/general/vitest.config.ts +3 -0
@@ -0,0 +1,176 @@
1
+ import { Send, Play, Square, File, ChevronRight, ChevronDown, Trash2 } from "lucide-react";
2
+ import { useState, useEffect } from "react";
3
+ import { useTranslation } from "react-i18next";
4
+ import { useAppStore } from "../../stores/use-app-store";
5
+ import { useLogStore } from "../../stores/use-log-store";
6
+ import type { DemoMethod } from "../../types";
7
+
8
+ const COLLAPSED_KEY = "debug-panel-collapsed";
9
+
10
+ function readCollapsed(): boolean {
11
+ try {
12
+ return localStorage.getItem(COLLAPSED_KEY) === "true";
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+
18
+ export function DebugPanel() {
19
+ const { t } = useTranslation();
20
+ const [collapsed, setCollapsed] = useState(readCollapsed);
21
+
22
+ useEffect(() => {
23
+ try {
24
+ localStorage.setItem(COLLAPSED_KEY, String(collapsed));
25
+ } catch { /* ignore */ }
26
+ }, [collapsed]);
27
+
28
+ const method = useAppStore((s) => s.method);
29
+ const result = useAppStore((s) => s.result);
30
+ const logs = useLogStore((s) => s.logs);
31
+ const tickEvents = useAppStore((s) => s.tickEvents);
32
+ const tickCount = useAppStore((s) => s.tickCount);
33
+ const subscriptionId = useAppStore((s) => s.subscriptionId);
34
+ const timerRunning = useAppStore((s) => s.timerRunning);
35
+ const setMethod = useAppStore((s) => s.setMethod);
36
+ const callRPC = useAppStore((s) => s.callRPC);
37
+ const handleSubscribe = useAppStore((s) => s.handleSubscribe);
38
+ const handleUnsubscribe = useAppStore((s) => s.handleUnsubscribe);
39
+ const clearDebug = useAppStore((s) => s.clearDebug);
40
+
41
+ if (collapsed) {
42
+ return (
43
+ <div className="w-12 bg-[var(--color-bg-primary)] border-l border-[var(--color-border-primary)] flex flex-col items-center justify-start pt-2 flex-shrink-0">
44
+ <button
45
+ onClick={() => setCollapsed(false)}
46
+ title="Expand Debug Panel"
47
+ className="w-10 h-6 flex items-center gap-1 text-xs text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] transition-colors"
48
+ >
49
+ <ChevronRight className="w-3 h-3" />
50
+ {t("tabs.debug")}
51
+ </button>
52
+ </div>
53
+ );
54
+ }
55
+
56
+ return (
57
+ <div className="w-72 bg-[var(--color-bg-primary)] border-l border-[var(--color-border-primary)] flex flex-col flex-shrink-0 overflow-y-auto">
58
+ <div className="flex items-center justify-between px-2 py-1.5 border-b border-[var(--color-border-primary)]">
59
+ <button
60
+ onClick={() => setCollapsed(true)}
61
+ title="Collapse Debug Panel"
62
+ className="flex items-center gap-1 text-[11px] text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)] transition-colors"
63
+ >
64
+ <ChevronDown className="w-3 h-3" />
65
+ {t("tabs.debug")}
66
+ </button>
67
+ <button
68
+ onClick={clearDebug}
69
+ title="Clear all debug data"
70
+ className="flex items-center gap-1 text-[11px] text-[var(--color-text-placeholder)] hover:text-red-400 transition-colors"
71
+ >
72
+ <Trash2 className="w-3 h-3" />
73
+ Clear
74
+ </button>
75
+ </div>
76
+ {/* RPC Calls */}
77
+ <div className="p-3 border-b border-[var(--color-border-primary)]">
78
+ <div className="flex items-center justify-between mb-2">
79
+ <h2 className="text-xs font-semibold flex items-center gap-1.5">
80
+ <Send className="w-3.5 h-3.5 text-[var(--color-text-accent)]" />
81
+ {t("debug.rpcCalls")}
82
+ </h2>
83
+ </div>
84
+ <div className="flex gap-2 mb-2">
85
+ <select
86
+ value={method}
87
+ onChange={(e) => setMethod(e.target.value as DemoMethod)}
88
+ className="flex-1 px-2 py-1 text-xs bg-[var(--color-bg-tertiary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)]"
89
+ >
90
+ <option value="system.ping">system.ping</option>
91
+ <option value="system.hello">system.hello</option>
92
+ <option value="system.echo">system.echo</option>
93
+ <option value="chat.send">chat.send</option>
94
+ </select>
95
+ <button
96
+ onClick={() => callRPC("")}
97
+ className="px-3 py-1 text-xs bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] rounded transition-colors flex items-center gap-1"
98
+ >
99
+ <Play className="w-3 h-3" />
100
+ {t("debug.call")}
101
+ </button>
102
+ </div>
103
+ {!!result && (
104
+ <div className="bg-[var(--color-bg-tertiary)] rounded p-2">
105
+ <pre className="text-[var(--color-text-success)] text-[11px] overflow-x-auto whitespace-pre-wrap">
106
+ {JSON.stringify(result, null, 2)}
107
+ </pre>
108
+ </div>
109
+ )}
110
+ </div>
111
+
112
+ {/* Subscriptions */}
113
+ <div className="p-3 border-b border-[var(--color-border-primary)]">
114
+ <div className="flex items-center justify-between mb-2">
115
+ <h2 className="text-xs font-semibold flex items-center gap-1.5">
116
+ {timerRunning ? (
117
+ <Square className="w-3.5 h-3.5 text-green-400 fill-green-400" />
118
+ ) : (
119
+ <Play className="w-3.5 h-3.5 text-[var(--color-text-tertiary)]" />
120
+ )}
121
+ {t("debug.subscriptions")}
122
+ {timerRunning && (
123
+ <span className="ml-1 px-1.5 py-0.5 bg-green-600/30 text-green-400 rounded text-[10px]">
124
+ {t("feed.live")}
125
+ </span>
126
+ )}
127
+ </h2>
128
+ <div className="flex gap-2 items-center">
129
+ <span className="text-[11px] text-[var(--color-text-tertiary)]">
130
+ {t("debug.events", { count: tickCount })}
131
+ </span>
132
+ {!subscriptionId ? (
133
+ <button
134
+ onClick={handleSubscribe}
135
+ className="px-2 py-0.5 text-xs bg-green-600 hover:bg-green-700 rounded transition-colors"
136
+ >
137
+ {t("debug.subscribe")}
138
+ </button>
139
+ ) : (
140
+ <button
141
+ onClick={handleUnsubscribe}
142
+ className="px-2 py-0.5 text-xs bg-red-600 hover:bg-red-700 rounded transition-colors"
143
+ >
144
+ {t("debug.unsubscribe")}
145
+ </button>
146
+ )}
147
+ </div>
148
+ </div>
149
+ <div className="bg-[var(--color-bg-tertiary)] rounded p-2 max-h-32 overflow-y-auto font-mono text-[11px]">
150
+ {tickEvents.length === 0 ? (
151
+ <div className="text-[var(--color-text-placeholder)] text-center py-1">{t("debug.noEvents")}</div>
152
+ ) : (
153
+ tickEvents.map((ev, i) => (
154
+ <div key={i} className="text-cyan-400">{ev}</div>
155
+ ))
156
+ )}
157
+ </div>
158
+ </div>
159
+
160
+ {/* Logs */}
161
+ <div className="p-3 flex flex-col flex-1 min-h-0">
162
+ <h2 className="text-xs font-semibold mb-2 flex-shrink-0 flex items-center gap-1.5">
163
+ <File className="w-3.5 h-3.5 text-[var(--color-text-tertiary)]" />
164
+ {t("debug.logs")}
165
+ </h2>
166
+ <div className="flex-1 bg-black rounded p-2 overflow-y-auto font-mono text-[11px]">
167
+ {logs.map((log, i) => (
168
+ <div key={i} className={log.includes("Error") ? "text-red-400" : "text-green-400"}>
169
+ {log}
170
+ </div>
171
+ ))}
172
+ </div>
173
+ </div>
174
+ </div>
175
+ );
176
+ }
@@ -0,0 +1,108 @@
1
+ import { useState } from "react";
2
+ import { X, Columns2, Rows3 } from "lucide-react";
3
+ import { useTranslation } from "react-i18next";
4
+ import ReactDiffViewer, { DiffMethod } from "react-diff-viewer-continued";
5
+ import { useGitStore } from "../../stores/use-git-store";
6
+
7
+ /* Module-level constant: avoids re-creating the styles object on every render */
8
+ const DIFF_STYLES = {
9
+ variables: {
10
+ light: {
11
+ diffViewerBackground: "#111827",
12
+ },
13
+ dark: {
14
+ diffViewerBackground: "#111827",
15
+ diffViewerColor: "#e5e7eb",
16
+ addedBackground: "#052e16",
17
+ addedColor: "#4ade80",
18
+ removedBackground: "#450a0a",
19
+ removedColor: "#fca5a5",
20
+ wordAddedBackground: "#065f46",
21
+ wordRemovedBackground: "#7f1d1d",
22
+ addedGutterBackground: "#052e16",
23
+ removedGutterBackground: "#450a0a",
24
+ gutterBackground: "#1f2937",
25
+ gutterColor: "#6b7280",
26
+ codeFoldGutterBackground: "#1f2937",
27
+ codeFoldBackground: "#1f2937",
28
+ emptyLineBackground: "#111827",
29
+ gutterBackgroundDark: "#1f2937",
30
+ highlightGutterBackground: "#374151",
31
+ highlightBackground: "#374151",
32
+ },
33
+ },
34
+ line: {
35
+ fontSize: "12px",
36
+ fontFamily: "ui-monospace, SFMono-Regular, monospace",
37
+ lineHeight: "1.6",
38
+ },
39
+ gutter: {
40
+ fontSize: "12px",
41
+ fontFamily: "ui-monospace, SFMono-Regular, monospace",
42
+ minWidth: "40px",
43
+ padding: "0 8px",
44
+ },
45
+ } as const;
46
+
47
+ export function DiffViewerPanel() {
48
+ const { t } = useTranslation();
49
+ const currentDiff = useGitStore((s) => s.currentDiff);
50
+ const loadingDiff = useGitStore((s) => s.loadingDiff);
51
+ const clearDiff = useGitStore((s) => s.clearDiff);
52
+ const [splitView, setSplitView] = useState(false);
53
+
54
+ if (!currentDiff && !loadingDiff) return null;
55
+
56
+ const fileName = currentDiff?.filePath.split("/").pop() || "";
57
+
58
+ return (
59
+ <div className="absolute inset-0 bg-[var(--color-bg-primary)] flex flex-col" style={{ zIndex: 40 }}>
60
+ <div className="h-9 bg-[var(--color-bg-secondary)] border-b border-[var(--color-border-primary)] flex items-center px-3 text-xs flex-shrink-0 gap-2">
61
+ <span className="text-[var(--color-text-secondary)] font-medium">{fileName}</span>
62
+ <span className="text-[var(--color-text-placeholder)] truncate text-[10px]">{currentDiff?.filePath}</span>
63
+ <div className="ml-auto flex items-center gap-1">
64
+ <button
65
+ onClick={() => setSplitView(false)}
66
+ className={`p-1 rounded transition-colors ${!splitView ? "bg-[var(--color-bg-active)] text-[var(--color-text-primary)]" : "text-[var(--color-text-placeholder)] hover:text-[var(--color-text-primary)]"}`}
67
+ title={t("diff.lineByLine")}
68
+ >
69
+ <Rows3 className="w-3.5 h-3.5" />
70
+ </button>
71
+ <button
72
+ onClick={() => setSplitView(true)}
73
+ className={`p-1 rounded transition-colors ${splitView ? "bg-[var(--color-bg-active)] text-[var(--color-text-primary)]" : "text-[var(--color-text-placeholder)] hover:text-[var(--color-text-primary)]"}`}
74
+ title={t("diff.sideBySide")}
75
+ >
76
+ <Columns2 className="w-3.5 h-3.5" />
77
+ </button>
78
+ <button
79
+ onClick={clearDiff}
80
+ className="ml-1 text-[var(--color-text-placeholder)] hover:text-[var(--color-text-primary)] transition-colors"
81
+ >
82
+ <X className="w-4 h-4" />
83
+ </button>
84
+ </div>
85
+ </div>
86
+
87
+ {/* Diff content */}
88
+ <div className="flex-1 overflow-auto">
89
+ {loadingDiff ? (
90
+ <div className="flex items-center justify-center h-full text-[var(--color-text-placeholder)]">
91
+ {t("diff.loading")}
92
+ </div>
93
+ ) : currentDiff ? (
94
+ <ReactDiffViewer
95
+ oldValue={currentDiff.oldContent}
96
+ newValue={currentDiff.newContent}
97
+ splitView={splitView}
98
+ compareMethod={DiffMethod.LINES}
99
+ useDarkTheme={true}
100
+ leftTitle={t("diff.before")}
101
+ rightTitle={t("diff.after")}
102
+ styles={DIFF_STYLES}
103
+ />
104
+ ) : null}
105
+ </div>
106
+ </div>
107
+ );
108
+ }
@@ -0,0 +1,52 @@
1
+ import { useEffect, useCallback } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+
4
+ interface ConfirmDialogProps {
5
+ title: string;
6
+ message: string;
7
+ onConfirm: () => void;
8
+ onCancel: () => void;
9
+ }
10
+
11
+ export function ConfirmDialog({ title, message, onConfirm, onCancel }: ConfirmDialogProps) {
12
+ const { t } = useTranslation();
13
+ const handleKeyDown = useCallback(
14
+ (e: KeyboardEvent) => {
15
+ if (e.key === "Escape") onCancel();
16
+ },
17
+ [onCancel],
18
+ );
19
+
20
+ useEffect(() => {
21
+ document.addEventListener("keydown", handleKeyDown);
22
+ return () => document.removeEventListener("keydown", handleKeyDown);
23
+ }, [handleKeyDown]);
24
+
25
+ return (
26
+ <div
27
+ className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
28
+ onClick={(e) => {
29
+ if (e.target === e.currentTarget) onCancel();
30
+ }}
31
+ >
32
+ <div className="bg-[var(--color-bg-secondary)] border border-[var(--color-border-secondary)] rounded-lg shadow-2xl p-4 min-w-[300px] max-w-[400px]">
33
+ <h3 className="text-sm font-semibold text-[var(--color-text-primary)] mb-2">{title}</h3>
34
+ <p className="text-xs text-[var(--color-text-secondary)] mb-4">{message}</p>
35
+ <div className="flex justify-end gap-2">
36
+ <button
37
+ className="px-3 py-1.5 text-xs bg-[var(--color-bg-tertiary)] hover:bg-[var(--color-bg-hover)] rounded transition-colors text-[var(--color-text-secondary)]"
38
+ onClick={onCancel}
39
+ >
40
+ {t("common.cancel")}
41
+ </button>
42
+ <button
43
+ className="px-3 py-1.5 text-xs bg-red-600 hover:bg-red-700 rounded transition-colors text-white"
44
+ onClick={onConfirm}
45
+ >
46
+ {t("common.delete")}
47
+ </button>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ );
52
+ }
@@ -0,0 +1,82 @@
1
+ import { useEffect, useRef, useCallback } from "react";
2
+
3
+ export interface MenuItem {
4
+ label: string;
5
+ icon?: React.ReactNode;
6
+ onClick: () => void;
7
+ danger?: boolean;
8
+ divider?: boolean;
9
+ }
10
+
11
+ interface ContextMenuProps {
12
+ x: number;
13
+ y: number;
14
+ items: MenuItem[];
15
+ onClose: () => void;
16
+ }
17
+
18
+ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
19
+ const ref = useRef<HTMLDivElement>(null);
20
+
21
+ // Adjust position to stay within viewport
22
+ const adjustedX = useRef(x);
23
+ const adjustedY = useRef(y);
24
+
25
+ useEffect(() => {
26
+ const el = ref.current;
27
+ if (!el) return;
28
+ const rect = el.getBoundingClientRect();
29
+ const vw = window.innerWidth;
30
+ const vh = window.innerHeight;
31
+ if (rect.right > vw) adjustedX.current = Math.max(0, x - rect.width);
32
+ if (rect.bottom > vh) adjustedY.current = Math.max(0, y - rect.height);
33
+ el.style.left = `${adjustedX.current}px`;
34
+ el.style.top = `${adjustedY.current}px`;
35
+ }, [x, y]);
36
+
37
+ const handleClickOutside = useCallback((e: MouseEvent) => {
38
+ if (ref.current && !ref.current.contains(e.target as Node)) {
39
+ onClose();
40
+ }
41
+ }, [onClose]);
42
+
43
+ useEffect(() => {
44
+ document.addEventListener("mousedown", handleClickOutside);
45
+ const handleKeyDown = (e: KeyboardEvent) => {
46
+ if (e.key === "Escape") onClose();
47
+ };
48
+ document.addEventListener("keydown", handleKeyDown);
49
+ return () => {
50
+ document.removeEventListener("mousedown", handleClickOutside);
51
+ document.removeEventListener("keydown", handleKeyDown);
52
+ };
53
+ }, [handleClickOutside, onClose]);
54
+
55
+ return (
56
+ <div
57
+ ref={ref}
58
+ className="fixed z-50 min-w-[160px] bg-[var(--color-bg-secondary)] border border-[var(--color-border-secondary)] rounded-md shadow-xl py-1"
59
+ style={{ left: adjustedX.current, top: adjustedY.current }}
60
+ >
61
+ {items.map((item, i) => (
62
+ <div key={i}>
63
+ {item.divider && i > 0 && <div className="border-t border-[var(--color-border-secondary)] my-1" />}
64
+ <button
65
+ className={`w-full text-left px-3 py-1.5 text-xs flex items-center gap-2 transition-colors ${
66
+ item.danger
67
+ ? "text-red-400 hover:bg-red-900/30"
68
+ : "text-[var(--color-text-secondary)] hover:bg-[var(--color-bg-hover)]"
69
+ }`}
70
+ onClick={() => {
71
+ item.onClick();
72
+ onClose();
73
+ }}
74
+ >
75
+ {item.icon && <span className="w-3.5 shrink-0">{item.icon}</span>}
76
+ {item.label}
77
+ </button>
78
+ </div>
79
+ ))}
80
+ </div>
81
+ );
82
+ }
@@ -0,0 +1,225 @@
1
+ import { useState, useCallback } from "react";
2
+ import { Folder, RefreshCw, File, FolderPlus, Pencil, Trash2, Copy } from "lucide-react";
3
+ import { useTranslation } from "react-i18next";
4
+ import { useExplorerStore } from "../../stores/use-explorer-store";
5
+ import { readDropItems } from "../../utils/drop-handler";
6
+ import { TreeNodeItem } from "./TreeNodeItem";
7
+ import { ContextMenu, type MenuItem } from "./ContextMenu";
8
+ import { ConfirmDialog } from "./ConfirmDialog";
9
+ import { InlineInput } from "./InlineInput";
10
+ import { PinButton } from "../sidebar/PinButton";
11
+
12
+ interface ContextMenuState {
13
+ x: number;
14
+ y: number;
15
+ node: import("../../types").TreeNode | null;
16
+ }
17
+
18
+ interface ExplorerSidebarProps {
19
+ hideOuterShell?: boolean;
20
+ }
21
+
22
+ export function ExplorerSidebar({ hideOuterShell }: ExplorerSidebarProps) {
23
+ const { t } = useTranslation();
24
+ const treeNodes = useExplorerStore((s) => s.treeNodes);
25
+ const currentPath = useExplorerStore((s) => s.currentPath);
26
+ const selectedPath = useExplorerStore((s) => s.selectedPath);
27
+ const editingNode = useExplorerStore((s) => s.editingNode);
28
+ const setCurrentPath = useExplorerStore((s) => s.setCurrentPath);
29
+ const listRootDir = useExplorerStore((s) => s.listRootDir);
30
+ const toggleNode = useExplorerStore((s) => s.toggleNode);
31
+ const openFile = useExplorerStore((s) => s.openFile);
32
+ const createFile = useExplorerStore((s) => s.createFile);
33
+ const createDir = useExplorerStore((s) => s.createDir);
34
+ const renameNode = useExplorerStore((s) => s.renameNode);
35
+ const deleteNode = useExplorerStore((s) => s.deleteNode);
36
+ const startEditing = useExplorerStore((s) => s.startEditing);
37
+ const cancelEditing = useExplorerStore((s) => s.cancelEditing);
38
+ const importFiles = useExplorerStore((s) => s.importFiles);
39
+
40
+ const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
41
+ const [pendingDelete, setPendingDelete] = useState<string | null>(null);
42
+ const [isDragOver, setIsDragOver] = useState(false);
43
+
44
+ const handleContextMenu = useCallback((e: React.MouseEvent, node: import("../../types").TreeNode | null) => {
45
+ e.preventDefault();
46
+ e.stopPropagation();
47
+ setContextMenu({ x: e.clientX, y: e.clientY, node });
48
+ }, []);
49
+
50
+ const handleBlankContextMenu = useCallback((e: React.MouseEvent) => {
51
+ e.preventDefault();
52
+ setContextMenu({ x: e.clientX, y: e.clientY, node: null });
53
+ }, []);
54
+
55
+ const handleDragOver = useCallback((e: React.DragEvent) => {
56
+ e.preventDefault();
57
+ e.stopPropagation();
58
+ setIsDragOver(true);
59
+ }, []);
60
+
61
+ const handleDragLeave = useCallback(() => {
62
+ setIsDragOver(false);
63
+ }, []);
64
+
65
+ const handleDrop = useCallback(async (e: React.DragEvent) => {
66
+ e.preventDefault();
67
+ e.stopPropagation();
68
+ setIsDragOver(false);
69
+ const entries = await readDropItems(e.dataTransfer);
70
+ if (entries.length > 0) {
71
+ try {
72
+ await importFiles(entries, currentPath);
73
+ } catch { /* error logged in store */ }
74
+ }
75
+ }, [importFiles, currentPath]);
76
+
77
+ const buildMenuItems = useCallback((): MenuItem[] => {
78
+ if (!contextMenu) return [];
79
+ const node = contextMenu.node;
80
+
81
+ if (!node) {
82
+ return [
83
+ { label: t("explorer.newFile"), icon: <File className="w-3 h-3" />, onClick: () => startEditing(currentPath, "newFile") },
84
+ { label: t("explorer.newFolder"), icon: <FolderPlus className="w-3 h-3" />, onClick: () => startEditing(currentPath, "newDir") },
85
+ { label: t("explorer.refresh"), icon: <RefreshCw className="w-3 h-3" />, onClick: () => listRootDir(), divider: true },
86
+ ];
87
+ }
88
+
89
+ const items: MenuItem[] = [];
90
+ if (node.type === "directory") {
91
+ items.push(
92
+ { label: t("explorer.newFile"), icon: <File className="w-3 h-3" />, onClick: () => startEditing(node.path, "newFile") },
93
+ { label: t("explorer.newFolder"), icon: <FolderPlus className="w-3 h-3" />, onClick: () => startEditing(node.path, "newDir") },
94
+ );
95
+ }
96
+ items.push(
97
+ { label: t("explorer.rename"), icon: <Pencil className="w-3 h-3" />, onClick: () => startEditing(node.path, "rename"), divider: items.length > 0 },
98
+ { label: t("explorer.delete"), icon: <Trash2 className="w-3 h-3" />, onClick: () => setPendingDelete(node.path), danger: true },
99
+ { label: t("explorer.copyPath"), icon: <Copy className="w-3 h-3" />, onClick: () => navigator.clipboard.writeText(node.path) },
100
+ );
101
+ return items;
102
+ }, [contextMenu, currentPath, listRootDir, startEditing]);
103
+
104
+ const handleSubmitEdit = useCallback(
105
+ (value: string) => {
106
+ if (!editingNode) return;
107
+ if (editingNode.type === "rename") {
108
+ renameNode(editingNode.path, value);
109
+ } else if (editingNode.type === "newFile") {
110
+ createFile(editingNode.path, value);
111
+ } else if (editingNode.type === "newDir") {
112
+ createDir(editingNode.path, value);
113
+ }
114
+ },
115
+ [editingNode, renameNode, createFile, createDir],
116
+ );
117
+
118
+ const isRootEditing =
119
+ editingNode &&
120
+ editingNode.path === currentPath &&
121
+ (editingNode.type === "newFile" || editingNode.type === "newDir");
122
+
123
+ const header = (
124
+ <div className="px-3 py-2 text-xs font-semibold text-[var(--color-text-tertiary)] uppercase tracking-wide border-b border-[var(--color-border-primary)] flex items-center justify-between">
125
+ <div className="flex items-center gap-1.5">
126
+ <Folder className="w-3.5 h-3.5" />
127
+ {t("explorer.title")}
128
+ </div>
129
+ <PinButton />
130
+ </div>
131
+ );
132
+
133
+ const content = (
134
+ <>
135
+ {header}
136
+ <div className="flex-1 overflow-hidden flex flex-col">
137
+ <div className="flex gap-2 p-2 border-b border-[var(--color-border-primary)]">
138
+ <input
139
+ type="text"
140
+ value={currentPath}
141
+ onChange={(e) => setCurrentPath(e.target.value)}
142
+ onKeyDown={(e) => e.key === "Enter" && listRootDir()}
143
+ placeholder={t("explorer.pathPlaceholder")}
144
+ className="flex-1 px-2 py-1 text-xs bg-[var(--color-bg-tertiary)] rounded text-[var(--color-text-primary)] border border-[var(--color-border-secondary)] focus:border-[var(--color-accent)] focus:outline-none"
145
+ />
146
+ <button
147
+ onClick={() => listRootDir()}
148
+ className="px-2 py-1 text-xs bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] rounded transition-colors"
149
+ title={t("explorer.listDirectory")}
150
+ >
151
+ <RefreshCw className="w-3 h-3" />
152
+ </button>
153
+ </div>
154
+ <div
155
+ className={`flex-1 overflow-y-auto p-1 transition-colors ${
156
+ isDragOver ? "bg-[var(--color-accent)]/30 ring-1 ring-inset ring-[var(--color-accent)]/50" : ""
157
+ }`}
158
+ onContextMenu={handleBlankContextMenu}
159
+ onDragOver={handleDragOver}
160
+ onDragLeave={handleDragLeave}
161
+ onDrop={handleDrop}
162
+ >
163
+ {treeNodes.length === 0 ? (
164
+ <div className="text-[var(--color-text-placeholder)] text-xs text-center py-4">{t("explorer.emptyHint")}</div>
165
+ ) : (
166
+ <ul className="space-y-0.5">
167
+ {treeNodes.map((node) => (
168
+ <TreeNodeItem
169
+ key={node.path}
170
+ node={node}
171
+ depth={0}
172
+ selectedPath={selectedPath}
173
+ editingNode={editingNode}
174
+ onToggle={(p) => toggleNode(p)}
175
+ onOpenFile={(n) => openFile(n)}
176
+ onContextMenu={handleContextMenu}
177
+ onSubmitEdit={handleSubmitEdit}
178
+ onCancelEdit={() => cancelEditing()}
179
+ />
180
+ ))}
181
+ {isRootEditing && (
182
+ <InlineInput
183
+ depth={0}
184
+ onSubmit={handleSubmitEdit}
185
+ onCancel={() => cancelEditing()}
186
+ />
187
+ )}
188
+ </ul>
189
+ )}
190
+ </div>
191
+ </div>
192
+
193
+ {contextMenu && (
194
+ <ContextMenu
195
+ x={contextMenu.x}
196
+ y={contextMenu.y}
197
+ items={buildMenuItems()}
198
+ onClose={() => setContextMenu(null)}
199
+ />
200
+ )}
201
+
202
+ {pendingDelete && (
203
+ <ConfirmDialog
204
+ title={t("explorer.confirmDelete")}
205
+ message={t("explorer.confirmDeleteMessage", { name: pendingDelete.split("/").pop() })}
206
+ onConfirm={() => {
207
+ deleteNode(pendingDelete);
208
+ setPendingDelete(null);
209
+ }}
210
+ onCancel={() => setPendingDelete(null)}
211
+ />
212
+ )}
213
+ </>
214
+ );
215
+
216
+ if (hideOuterShell) {
217
+ return <div className="flex flex-col flex-1 overflow-hidden">{content}</div>;
218
+ }
219
+
220
+ return (
221
+ <div className="w-60 bg-[var(--color-bg-primary)] border-r border-[var(--color-border-primary)] flex flex-col flex-shrink-0">
222
+ {content}
223
+ </div>
224
+ );
225
+ }