@seqyuan/annodex 0.1.10 → 0.1.12

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 (519) hide show
  1. package/app/api/agent/[id]/events/route.ts +94 -0
  2. package/app/api/agent/[id]/route.ts +83 -0
  3. package/app/api/agent/new/route.ts +53 -0
  4. package/app/api/auth/all-providers/route.ts +21 -0
  5. package/app/api/auth/api-key/[provider]/route.ts +7 -0
  6. package/app/api/auth/login/[provider]/route.ts +7 -0
  7. package/app/api/auth/login/route.ts +22 -0
  8. package/app/api/auth/logout/[provider]/route.ts +7 -0
  9. package/app/api/auth/providers/route.ts +15 -0
  10. package/app/api/auth/status/route.ts +6 -0
  11. package/app/api/default-cwd/route.ts +22 -0
  12. package/app/api/files/[...path]/route.ts +621 -0
  13. package/app/api/harness/route.ts +47 -0
  14. package/app/api/home/route.ts +6 -0
  15. package/app/api/internal/runtime/route.ts +26 -0
  16. package/app/api/models/route.ts +67 -0
  17. package/app/api/models-config/discover/route.ts +42 -0
  18. package/app/api/models-config/route.ts +152 -0
  19. package/app/api/models-config/test/route.ts +154 -0
  20. package/app/api/projects/browse/route.ts +51 -0
  21. package/app/api/projects/route.ts +83 -0
  22. package/app/api/reports/[id]/route.ts +108 -0
  23. package/app/api/search/route.ts +122 -0
  24. package/app/api/sessions/[id]/context/route.ts +23 -0
  25. package/app/api/sessions/[id]/route.ts +124 -0
  26. package/app/api/sessions/new/route.ts +5 -0
  27. package/app/api/sessions/route.ts +16 -0
  28. package/app/api/settings/route.ts +51 -0
  29. package/app/api/skills/install/route.ts +249 -0
  30. package/app/api/skills/route.ts +161 -0
  31. package/app/api/skills/search/route.ts +121 -0
  32. package/app/api/soul/route.ts +47 -0
  33. package/app/api/version/route.ts +55 -0
  34. package/app/globals.css +736 -0
  35. package/app/layout.tsx +40 -0
  36. package/app/login/page.tsx +133 -0
  37. package/app/page.tsx +10 -0
  38. package/components/AppShell.tsx +1058 -0
  39. package/components/ChatInput.tsx +1103 -0
  40. package/components/ChatMinimap.tsx +381 -0
  41. package/components/ChatWindow.tsx +576 -0
  42. package/components/CodeMirrorEditor.tsx +137 -0
  43. package/components/ConversationSearch.tsx +369 -0
  44. package/components/DataTableViewer.tsx +248 -0
  45. package/components/FileExplorer.tsx +758 -0
  46. package/components/FileIcons.tsx +241 -0
  47. package/components/FileViewer.tsx +1273 -0
  48. package/components/GlobalFileEditor.tsx +98 -0
  49. package/components/MarkdownRenderer.tsx +331 -0
  50. package/components/MermaidDiagram.tsx +80 -0
  51. package/components/MessageView.tsx +1141 -0
  52. package/components/ModelsConfig.tsx +1991 -0
  53. package/components/ProjectContext.tsx +252 -0
  54. package/components/ProjectFolderPicker.tsx +202 -0
  55. package/components/ProjectsConfig.tsx +288 -0
  56. package/components/ProviderIcons.tsx +91 -0
  57. package/components/ReportPanel.tsx +237 -0
  58. package/components/ResizeHandle.tsx +105 -0
  59. package/components/SessionSidebar.tsx +1464 -0
  60. package/components/SettingsDialog.tsx +287 -0
  61. package/components/SkillsConfig.tsx +1093 -0
  62. package/components/SubagentPanel.tsx +191 -0
  63. package/components/TabBar.tsx +115 -0
  64. package/components/ToolPanel.tsx +131 -0
  65. package/components/WidgetRenderer.tsx +505 -0
  66. package/components/viewers/DocumentToolbar.tsx +78 -0
  67. package/components/viewers/DocxViewer.tsx +97 -0
  68. package/components/viewers/PdfViewer.tsx +206 -0
  69. package/components/viewers/PptxViewer.tsx +240 -0
  70. package/components/viewers/XlsxViewer.tsx +143 -0
  71. package/hooks/useAgentSession.ts +710 -0
  72. package/hooks/useAudio.ts +50 -0
  73. package/hooks/useDragDrop.ts +52 -0
  74. package/hooks/useResizable.ts +60 -0
  75. package/hooks/useTheme.ts +85 -0
  76. package/lib/agent-client.ts +39 -0
  77. package/lib/annodex-config.ts +556 -0
  78. package/lib/auth-token.ts +74 -0
  79. package/lib/auth.ts +90 -0
  80. package/lib/brand.ts +5 -0
  81. package/lib/code-theme.ts +32 -0
  82. package/lib/codex-compat-proxy.ts +1603 -0
  83. package/lib/codex-home.ts +6 -0
  84. package/lib/codex-server.ts +796 -0
  85. package/lib/codex-session.ts +590 -0
  86. package/lib/codex-usage.ts +213 -0
  87. package/lib/file-paths.ts +34 -0
  88. package/lib/model-discovery.ts +379 -0
  89. package/lib/normalize.ts +30 -0
  90. package/lib/npx.ts +87 -0
  91. package/lib/pi-types.ts +49 -0
  92. package/lib/projects.ts +269 -0
  93. package/lib/provider-api.ts +88 -0
  94. package/lib/report-prompt.ts +61 -0
  95. package/lib/report-store.ts +597 -0
  96. package/lib/report-update-parser.ts +66 -0
  97. package/lib/rpc-manager.ts +668 -0
  98. package/lib/runtime-state.ts +117 -0
  99. package/lib/session-reader.ts +903 -0
  100. package/lib/session-runtime.ts +105 -0
  101. package/lib/subagent-progress.ts +279 -0
  102. package/lib/types.ts +241 -0
  103. package/lib/widget-export.ts +318 -0
  104. package/lib/widget-guidelines.ts +288 -0
  105. package/lib/widget-prompt.ts +76 -0
  106. package/lib/widget-utils.ts +523 -0
  107. package/package.json +23 -18
  108. package/postcss.config.mjs +8 -0
  109. package/proxy.ts +64 -0
  110. package/scripts/postinstall.cjs +25 -0
  111. package/tsconfig.json +41 -0
  112. package/.next/BUILD_ID +0 -1
  113. package/.next/app-path-routes-manifest.json +0 -39
  114. package/.next/build-manifest.json +0 -20
  115. package/.next/diagnostics/build-diagnostics.json +0 -6
  116. package/.next/diagnostics/framework.json +0 -1
  117. package/.next/export-marker.json +0 -6
  118. package/.next/images-manifest.json +0 -68
  119. package/.next/next-minimal-server.js.nft.json +0 -1
  120. package/.next/next-server.js.nft.json +0 -1
  121. package/.next/package.json +0 -1
  122. package/.next/prerender-manifest.json +0 -109
  123. package/.next/react-loadable-manifest.json +0 -2320
  124. package/.next/required-server-files.js +0 -343
  125. package/.next/required-server-files.json +0 -343
  126. package/.next/routes-manifest.json +0 -286
  127. package/.next/server/app/_global-error/page.js +0 -32
  128. package/.next/server/app/_global-error/page.js.nft.json +0 -1
  129. package/.next/server/app/_global-error/page_client-reference-manifest.js +0 -1
  130. package/.next/server/app/_global-error.html +0 -1
  131. package/.next/server/app/_global-error.meta +0 -16
  132. package/.next/server/app/_global-error.rsc +0 -14
  133. package/.next/server/app/_global-error.segments/_full.segment.rsc +0 -14
  134. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +0 -5
  135. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +0 -5
  136. package/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
  137. package/.next/server/app/_global-error.segments/_index.segment.rsc +0 -5
  138. package/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
  139. package/.next/server/app/_not-found/page.js +0 -2
  140. package/.next/server/app/_not-found/page.js.nft.json +0 -1
  141. package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  142. package/.next/server/app/_not-found.html +0 -1
  143. package/.next/server/app/_not-found.meta +0 -16
  144. package/.next/server/app/_not-found.rsc +0 -18
  145. package/.next/server/app/_not-found.segments/_full.segment.rsc +0 -18
  146. package/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
  147. package/.next/server/app/_not-found.segments/_index.segment.rsc +0 -5
  148. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
  149. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -5
  150. package/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -4
  151. package/.next/server/app/api/agent/[id]/events/route.js +0 -3
  152. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +0 -1
  153. package/.next/server/app/api/agent/[id]/events/route_client-reference-manifest.js +0 -1
  154. package/.next/server/app/api/agent/[id]/route.js +0 -1
  155. package/.next/server/app/api/agent/[id]/route.js.nft.json +0 -1
  156. package/.next/server/app/api/agent/[id]/route_client-reference-manifest.js +0 -1
  157. package/.next/server/app/api/agent/new/route.js +0 -1
  158. package/.next/server/app/api/agent/new/route.js.nft.json +0 -1
  159. package/.next/server/app/api/agent/new/route_client-reference-manifest.js +0 -1
  160. package/.next/server/app/api/auth/all-providers/route.js +0 -1
  161. package/.next/server/app/api/auth/all-providers/route.js.nft.json +0 -1
  162. package/.next/server/app/api/auth/all-providers/route_client-reference-manifest.js +0 -1
  163. package/.next/server/app/api/auth/api-key/[provider]/route.js +0 -1
  164. package/.next/server/app/api/auth/api-key/[provider]/route.js.nft.json +0 -1
  165. package/.next/server/app/api/auth/api-key/[provider]/route_client-reference-manifest.js +0 -1
  166. package/.next/server/app/api/auth/login/[provider]/route.js +0 -1
  167. package/.next/server/app/api/auth/login/[provider]/route.js.nft.json +0 -1
  168. package/.next/server/app/api/auth/login/[provider]/route_client-reference-manifest.js +0 -1
  169. package/.next/server/app/api/auth/login/route.js +0 -1
  170. package/.next/server/app/api/auth/login/route.js.nft.json +0 -1
  171. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +0 -1
  172. package/.next/server/app/api/auth/logout/[provider]/route.js +0 -1
  173. package/.next/server/app/api/auth/logout/[provider]/route.js.nft.json +0 -1
  174. package/.next/server/app/api/auth/logout/[provider]/route_client-reference-manifest.js +0 -1
  175. package/.next/server/app/api/auth/providers/route.js +0 -1
  176. package/.next/server/app/api/auth/providers/route.js.nft.json +0 -1
  177. package/.next/server/app/api/auth/providers/route_client-reference-manifest.js +0 -1
  178. package/.next/server/app/api/auth/status/route.js +0 -1
  179. package/.next/server/app/api/auth/status/route.js.nft.json +0 -1
  180. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +0 -1
  181. package/.next/server/app/api/default-cwd/route.js +0 -1
  182. package/.next/server/app/api/default-cwd/route.js.nft.json +0 -1
  183. package/.next/server/app/api/default-cwd/route_client-reference-manifest.js +0 -1
  184. package/.next/server/app/api/files/[...path]/route.js +0 -4
  185. package/.next/server/app/api/files/[...path]/route.js.nft.json +0 -1
  186. package/.next/server/app/api/files/[...path]/route_client-reference-manifest.js +0 -1
  187. package/.next/server/app/api/harness/route.js +0 -1
  188. package/.next/server/app/api/harness/route.js.nft.json +0 -1
  189. package/.next/server/app/api/harness/route_client-reference-manifest.js +0 -1
  190. package/.next/server/app/api/home/route.js +0 -1
  191. package/.next/server/app/api/home/route.js.nft.json +0 -1
  192. package/.next/server/app/api/home/route_client-reference-manifest.js +0 -1
  193. package/.next/server/app/api/internal/runtime/route.js +0 -1
  194. package/.next/server/app/api/internal/runtime/route.js.nft.json +0 -1
  195. package/.next/server/app/api/internal/runtime/route_client-reference-manifest.js +0 -1
  196. package/.next/server/app/api/models/route.js +0 -1
  197. package/.next/server/app/api/models/route.js.nft.json +0 -1
  198. package/.next/server/app/api/models/route_client-reference-manifest.js +0 -1
  199. package/.next/server/app/api/models-config/discover/route.js +0 -1
  200. package/.next/server/app/api/models-config/discover/route.js.nft.json +0 -1
  201. package/.next/server/app/api/models-config/discover/route_client-reference-manifest.js +0 -1
  202. package/.next/server/app/api/models-config/route.js +0 -1
  203. package/.next/server/app/api/models-config/route.js.nft.json +0 -1
  204. package/.next/server/app/api/models-config/route_client-reference-manifest.js +0 -1
  205. package/.next/server/app/api/models-config/test/route.js +0 -1
  206. package/.next/server/app/api/models-config/test/route.js.nft.json +0 -1
  207. package/.next/server/app/api/models-config/test/route_client-reference-manifest.js +0 -1
  208. package/.next/server/app/api/projects/browse/route.js +0 -1
  209. package/.next/server/app/api/projects/browse/route.js.nft.json +0 -1
  210. package/.next/server/app/api/projects/browse/route_client-reference-manifest.js +0 -1
  211. package/.next/server/app/api/projects/route.js +0 -1
  212. package/.next/server/app/api/projects/route.js.nft.json +0 -1
  213. package/.next/server/app/api/projects/route_client-reference-manifest.js +0 -1
  214. package/.next/server/app/api/reports/[id]/route.js +0 -10
  215. package/.next/server/app/api/reports/[id]/route.js.nft.json +0 -1
  216. package/.next/server/app/api/reports/[id]/route_client-reference-manifest.js +0 -1
  217. package/.next/server/app/api/search/route.js +0 -1
  218. package/.next/server/app/api/search/route.js.nft.json +0 -1
  219. package/.next/server/app/api/search/route_client-reference-manifest.js +0 -1
  220. package/.next/server/app/api/sessions/[id]/context/route.js +0 -1
  221. package/.next/server/app/api/sessions/[id]/context/route.js.nft.json +0 -1
  222. package/.next/server/app/api/sessions/[id]/context/route_client-reference-manifest.js +0 -1
  223. package/.next/server/app/api/sessions/[id]/route.js +0 -1
  224. package/.next/server/app/api/sessions/[id]/route.js.nft.json +0 -1
  225. package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +0 -1
  226. package/.next/server/app/api/sessions/new/route.js +0 -1
  227. package/.next/server/app/api/sessions/new/route.js.nft.json +0 -1
  228. package/.next/server/app/api/sessions/new/route_client-reference-manifest.js +0 -1
  229. package/.next/server/app/api/sessions/route.js +0 -1
  230. package/.next/server/app/api/sessions/route.js.nft.json +0 -1
  231. package/.next/server/app/api/sessions/route_client-reference-manifest.js +0 -1
  232. package/.next/server/app/api/settings/route.js +0 -1
  233. package/.next/server/app/api/settings/route.js.nft.json +0 -1
  234. package/.next/server/app/api/settings/route_client-reference-manifest.js +0 -1
  235. package/.next/server/app/api/skills/install/route.js +0 -5
  236. package/.next/server/app/api/skills/install/route.js.nft.json +0 -1
  237. package/.next/server/app/api/skills/install/route_client-reference-manifest.js +0 -1
  238. package/.next/server/app/api/skills/route.js +0 -6
  239. package/.next/server/app/api/skills/route.js.nft.json +0 -1
  240. package/.next/server/app/api/skills/route_client-reference-manifest.js +0 -1
  241. package/.next/server/app/api/skills/search/route.js +0 -1
  242. package/.next/server/app/api/skills/search/route.js.nft.json +0 -1
  243. package/.next/server/app/api/skills/search/route_client-reference-manifest.js +0 -1
  244. package/.next/server/app/api/soul/route.js +0 -1
  245. package/.next/server/app/api/soul/route.js.nft.json +0 -1
  246. package/.next/server/app/api/soul/route_client-reference-manifest.js +0 -1
  247. package/.next/server/app/api/version/route.js +0 -1
  248. package/.next/server/app/api/version/route.js.nft.json +0 -1
  249. package/.next/server/app/api/version/route_client-reference-manifest.js +0 -1
  250. package/.next/server/app/index.html +0 -1
  251. package/.next/server/app/index.meta +0 -14
  252. package/.next/server/app/index.rsc +0 -17
  253. package/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -6
  254. package/.next/server/app/index.segments/_full.segment.rsc +0 -17
  255. package/.next/server/app/index.segments/_head.segment.rsc +0 -6
  256. package/.next/server/app/index.segments/_index.segment.rsc +0 -5
  257. package/.next/server/app/index.segments/_tree.segment.rsc +0 -4
  258. package/.next/server/app/login/page.js +0 -2
  259. package/.next/server/app/login/page.js.nft.json +0 -1
  260. package/.next/server/app/login/page_client-reference-manifest.js +0 -1
  261. package/.next/server/app/login.html +0 -1
  262. package/.next/server/app/login.meta +0 -15
  263. package/.next/server/app/login.rsc +0 -22
  264. package/.next/server/app/login.segments/_full.segment.rsc +0 -22
  265. package/.next/server/app/login.segments/_head.segment.rsc +0 -6
  266. package/.next/server/app/login.segments/_index.segment.rsc +0 -5
  267. package/.next/server/app/login.segments/_tree.segment.rsc +0 -4
  268. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +0 -9
  269. package/.next/server/app/login.segments/login.segment.rsc +0 -5
  270. package/.next/server/app/page.js +0 -261
  271. package/.next/server/app/page.js.nft.json +0 -1
  272. package/.next/server/app/page_client-reference-manifest.js +0 -1
  273. package/.next/server/app-paths-manifest.json +0 -39
  274. package/.next/server/chunks/1048.js +0 -1
  275. package/.next/server/chunks/1367.js +0 -77
  276. package/.next/server/chunks/1381.js +0 -1
  277. package/.next/server/chunks/165.js +0 -1
  278. package/.next/server/chunks/1681.js +0 -215
  279. package/.next/server/chunks/1688.js +0 -45
  280. package/.next/server/chunks/1703.js +0 -79
  281. package/.next/server/chunks/1712.js +0 -43
  282. package/.next/server/chunks/1813.js +0 -1
  283. package/.next/server/chunks/2325.js +0 -80
  284. package/.next/server/chunks/258.js +0 -1
  285. package/.next/server/chunks/2671.js +0 -287
  286. package/.next/server/chunks/2778.js +0 -1
  287. package/.next/server/chunks/2943.js +0 -1
  288. package/.next/server/chunks/3031.js +0 -226
  289. package/.next/server/chunks/3181.js +0 -1
  290. package/.next/server/chunks/3493.js +0 -1
  291. package/.next/server/chunks/3672.js +0 -1
  292. package/.next/server/chunks/3701.js +0 -104
  293. package/.next/server/chunks/4013.js +0 -1
  294. package/.next/server/chunks/402.js +0 -2
  295. package/.next/server/chunks/4035.js +0 -80
  296. package/.next/server/chunks/4248.js +0 -153
  297. package/.next/server/chunks/4367.js +0 -1
  298. package/.next/server/chunks/4406.js +0 -141
  299. package/.next/server/chunks/4741.js +0 -18
  300. package/.next/server/chunks/4768.js +0 -1
  301. package/.next/server/chunks/4858.js +0 -148
  302. package/.next/server/chunks/4980.js +0 -1
  303. package/.next/server/chunks/5155.js +0 -5
  304. package/.next/server/chunks/5293.js +0 -166
  305. package/.next/server/chunks/5399.js +0 -8
  306. package/.next/server/chunks/5409.js +0 -1
  307. package/.next/server/chunks/5797.js +0 -93
  308. package/.next/server/chunks/5851.js +0 -36
  309. package/.next/server/chunks/6206.js +0 -1
  310. package/.next/server/chunks/6296.js +0 -1
  311. package/.next/server/chunks/63.js +0 -45
  312. package/.next/server/chunks/6346.js +0 -1
  313. package/.next/server/chunks/6406.js +0 -23
  314. package/.next/server/chunks/642.js +0 -1
  315. package/.next/server/chunks/6429.js +0 -50
  316. package/.next/server/chunks/6729.js +0 -64
  317. package/.next/server/chunks/6907.js +0 -115
  318. package/.next/server/chunks/6980.js +0 -1
  319. package/.next/server/chunks/7073.js +0 -24
  320. package/.next/server/chunks/7233.js +0 -24
  321. package/.next/server/chunks/7307.js +0 -1
  322. package/.next/server/chunks/7362.js +0 -9
  323. package/.next/server/chunks/7567.js +0 -29
  324. package/.next/server/chunks/7765.js +0 -1
  325. package/.next/server/chunks/7890.js +0 -1
  326. package/.next/server/chunks/8065.js +0 -1
  327. package/.next/server/chunks/8238.js +0 -34
  328. package/.next/server/chunks/8276.js +0 -1
  329. package/.next/server/chunks/8336.js +0 -1
  330. package/.next/server/chunks/8477.js +0 -3
  331. package/.next/server/chunks/8490.js +0 -1
  332. package/.next/server/chunks/8916.js +0 -1
  333. package/.next/server/chunks/9280.js +0 -252
  334. package/.next/server/chunks/9315.js +0 -1
  335. package/.next/server/chunks/9537.js +0 -90
  336. package/.next/server/chunks/966.js +0 -1
  337. package/.next/server/chunks/9818.js +0 -21
  338. package/.next/server/chunks/static/media/pdf.worker.min.c476e1a0.mjs +0 -6
  339. package/.next/server/functions-config-manifest.json +0 -16
  340. package/.next/server/interception-route-rewrite-manifest.js +0 -1
  341. package/.next/server/middleware-build-manifest.js +0 -1
  342. package/.next/server/middleware-manifest.json +0 -6
  343. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  344. package/.next/server/middleware.js +0 -18
  345. package/.next/server/middleware.js.nft.json +0 -1
  346. package/.next/server/next-font-manifest.js +0 -1
  347. package/.next/server/next-font-manifest.json +0 -1
  348. package/.next/server/pages/404.html +0 -1
  349. package/.next/server/pages/500.html +0 -1
  350. package/.next/server/pages-manifest.json +0 -4
  351. package/.next/server/prefetch-hints.json +0 -1
  352. package/.next/server/server-reference-manifest.js +0 -1
  353. package/.next/server/server-reference-manifest.json +0 -1
  354. package/.next/server/webpack-runtime.js +0 -1
  355. package/.next/static/6cuMSvcr0FVO-GiK5RJZh/_buildManifest.js +0 -1
  356. package/.next/static/6cuMSvcr0FVO-GiK5RJZh/_ssgManifest.js +0 -1
  357. package/.next/static/chunks/0b9a0da7.9075af772487e743.js +0 -62
  358. package/.next/static/chunks/1413.922d232de90c0c41.js +0 -115
  359. package/.next/static/chunks/1643.467a526a1f24f54d.js +0 -24
  360. package/.next/static/chunks/1852.5543122f11aa7fed.js +0 -1
  361. package/.next/static/chunks/1960.b1e26436d7a5f586.js +0 -1
  362. package/.next/static/chunks/2170a4aa.4213bb2183c9cdf9.js +0 -1
  363. package/.next/static/chunks/2274.6cd173f80a1405a2.js +0 -21
  364. package/.next/static/chunks/2419.347fdfe3c170854d.js +0 -166
  365. package/.next/static/chunks/2619.9aac8983f30c7c8a.js +0 -1
  366. package/.next/static/chunks/2623.d20fabd8e18197c6.js +0 -287
  367. package/.next/static/chunks/2729.f5365061a849d659.js +0 -34
  368. package/.next/static/chunks/2821.934bcf60fbdc28c6.js +0 -1
  369. package/.next/static/chunks/2918becc.abff2ece1de37bc1.js +0 -153
  370. package/.next/static/chunks/2947.114e51cb06d1c01a.js +0 -23
  371. package/.next/static/chunks/3079.4c511fa1144e3adf.js +0 -79
  372. package/.next/static/chunks/3274.208ca44844cd7d95.js +0 -148
  373. package/.next/static/chunks/3308.465a94263d04bfea.js +0 -73
  374. package/.next/static/chunks/3325.e4bfe1ca657f3b5b.js +0 -80
  375. package/.next/static/chunks/3506.2a7eaa08b9f55337.js +0 -90
  376. package/.next/static/chunks/363642f4-043c1475ab9af70e.js +0 -1
  377. package/.next/static/chunks/3794-123fdf632563f469.js +0 -32
  378. package/.next/static/chunks/3837.a755ccfe6f9c1c1c.js +0 -5
  379. package/.next/static/chunks/394.91597771688df6d0.js +0 -1
  380. package/.next/static/chunks/3997.1009c06025691712.js +0 -1
  381. package/.next/static/chunks/4453.91a357dc43c21745.js +0 -1
  382. package/.next/static/chunks/4491.44fdf20580ac72bd.js +0 -24
  383. package/.next/static/chunks/4829.cf1d50e43e6d9db5.js +0 -1
  384. package/.next/static/chunks/498.fe1d9da9ecad6c36.js +0 -1
  385. package/.next/static/chunks/4bd1b696-e356ca5ba0218e27.js +0 -1
  386. package/.next/static/chunks/5019.b5a1a2b8daf17525.js +0 -1
  387. package/.next/static/chunks/5034.8f16c3fa3ce75411.js +0 -1
  388. package/.next/static/chunks/5074.d16651da01ec4e02.js +0 -1
  389. package/.next/static/chunks/51fb665c.0950e1b79671348d.js +0 -45
  390. package/.next/static/chunks/532.5956ed631aff722b.js +0 -9
  391. package/.next/static/chunks/5326.69460442bdcd6cd3.js +0 -1
  392. package/.next/static/chunks/5403.ff110bf5bf600758.js +0 -64
  393. package/.next/static/chunks/547.902a733488cfe3f7.js +0 -77
  394. package/.next/static/chunks/5567.540d7fc108ad6ee5.js +0 -215
  395. package/.next/static/chunks/5590.ef62922166d308b4.js +0 -1
  396. package/.next/static/chunks/5690.9d6eb1edb1399995.js +0 -1
  397. package/.next/static/chunks/5749.25faee4a1e55b854.js +0 -226
  398. package/.next/static/chunks/58bb9007.1ccb6bba34b4c635.js +0 -80
  399. package/.next/static/chunks/6121.f3f43f1896ea0cd9.js +0 -1
  400. package/.next/static/chunks/6600.583c88eef37aa524.js +0 -1
  401. package/.next/static/chunks/6696.a41aec266e657d54.js +0 -141
  402. package/.next/static/chunks/6922.42148793782d2fe7.js +0 -1
  403. package/.next/static/chunks/7006.e191611ffc2b9528.js +0 -43
  404. package/.next/static/chunks/7343.9fbb58204d8ac681.js +0 -1
  405. package/.next/static/chunks/73972abe.25a4cffa03b2bcef.js +0 -119
  406. package/.next/static/chunks/7547.58bda8a2aabba0d4.js +0 -93
  407. package/.next/static/chunks/7648.4ae2f183b4db0353.js +0 -1
  408. package/.next/static/chunks/7874.8db6929b94cdf697.js +0 -1
  409. package/.next/static/chunks/7959.1f20a35df316216a.js +0 -104
  410. package/.next/static/chunks/83.85d62d7fc9850b75.js +0 -29
  411. package/.next/static/chunks/8436.cab94b59cca0a8ff.js +0 -1
  412. package/.next/static/chunks/8451.ff6ff72b57dc52e1.js +0 -1
  413. package/.next/static/chunks/8489.45f22859734f514f.js +0 -36
  414. package/.next/static/chunks/8568.f85d8b36fc9a9037.js +0 -1
  415. package/.next/static/chunks/8771-3e14b6810486df1f.js +0 -1
  416. package/.next/static/chunks/8863.be51033a67436277.js +0 -1
  417. package/.next/static/chunks/90542734.dc1a2723e4f6affb.js +0 -1
  418. package/.next/static/chunks/9500.1488aec06ee78127.js +0 -1
  419. package/.next/static/chunks/9633.155548b5fca6e580.js +0 -1
  420. package/.next/static/chunks/9779.673004a62d70e36a.js +0 -1
  421. package/.next/static/chunks/app/_global-error/page-cc518af6b1ffb191.js +0 -1
  422. package/.next/static/chunks/app/_not-found/page-c72daab99269beff.js +0 -1
  423. package/.next/static/chunks/app/api/agent/[id]/events/route-cc518af6b1ffb191.js +0 -1
  424. package/.next/static/chunks/app/api/agent/[id]/route-cc518af6b1ffb191.js +0 -1
  425. package/.next/static/chunks/app/api/agent/new/route-cc518af6b1ffb191.js +0 -1
  426. package/.next/static/chunks/app/api/auth/all-providers/route-cc518af6b1ffb191.js +0 -1
  427. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-cc518af6b1ffb191.js +0 -1
  428. package/.next/static/chunks/app/api/auth/login/[provider]/route-cc518af6b1ffb191.js +0 -1
  429. package/.next/static/chunks/app/api/auth/login/route-cc518af6b1ffb191.js +0 -1
  430. package/.next/static/chunks/app/api/auth/logout/[provider]/route-cc518af6b1ffb191.js +0 -1
  431. package/.next/static/chunks/app/api/auth/providers/route-cc518af6b1ffb191.js +0 -1
  432. package/.next/static/chunks/app/api/auth/status/route-cc518af6b1ffb191.js +0 -1
  433. package/.next/static/chunks/app/api/default-cwd/route-cc518af6b1ffb191.js +0 -1
  434. package/.next/static/chunks/app/api/files/[...path]/route-cc518af6b1ffb191.js +0 -1
  435. package/.next/static/chunks/app/api/harness/route-cc518af6b1ffb191.js +0 -1
  436. package/.next/static/chunks/app/api/home/route-cc518af6b1ffb191.js +0 -1
  437. package/.next/static/chunks/app/api/internal/runtime/route-cc518af6b1ffb191.js +0 -1
  438. package/.next/static/chunks/app/api/models/route-cc518af6b1ffb191.js +0 -1
  439. package/.next/static/chunks/app/api/models-config/discover/route-cc518af6b1ffb191.js +0 -1
  440. package/.next/static/chunks/app/api/models-config/route-cc518af6b1ffb191.js +0 -1
  441. package/.next/static/chunks/app/api/models-config/test/route-cc518af6b1ffb191.js +0 -1
  442. package/.next/static/chunks/app/api/projects/browse/route-cc518af6b1ffb191.js +0 -1
  443. package/.next/static/chunks/app/api/projects/route-cc518af6b1ffb191.js +0 -1
  444. package/.next/static/chunks/app/api/reports/[id]/route-cc518af6b1ffb191.js +0 -1
  445. package/.next/static/chunks/app/api/search/route-cc518af6b1ffb191.js +0 -1
  446. package/.next/static/chunks/app/api/sessions/[id]/context/route-cc518af6b1ffb191.js +0 -1
  447. package/.next/static/chunks/app/api/sessions/[id]/route-cc518af6b1ffb191.js +0 -1
  448. package/.next/static/chunks/app/api/sessions/new/route-cc518af6b1ffb191.js +0 -1
  449. package/.next/static/chunks/app/api/sessions/route-cc518af6b1ffb191.js +0 -1
  450. package/.next/static/chunks/app/api/settings/route-cc518af6b1ffb191.js +0 -1
  451. package/.next/static/chunks/app/api/skills/install/route-cc518af6b1ffb191.js +0 -1
  452. package/.next/static/chunks/app/api/skills/route-cc518af6b1ffb191.js +0 -1
  453. package/.next/static/chunks/app/api/skills/search/route-cc518af6b1ffb191.js +0 -1
  454. package/.next/static/chunks/app/api/soul/route-cc518af6b1ffb191.js +0 -1
  455. package/.next/static/chunks/app/api/version/route-cc518af6b1ffb191.js +0 -1
  456. package/.next/static/chunks/app/layout-be148b7ae915b22a.js +0 -1
  457. package/.next/static/chunks/app/login/page-ebf0e6de99062783.js +0 -1
  458. package/.next/static/chunks/app/page-c45d98ea81c548ca.js +0 -260
  459. package/.next/static/chunks/d3ac728e.7964f816a1ca64e5.js +0 -1
  460. package/.next/static/chunks/framework-711ef29bc66f648c.js +0 -1
  461. package/.next/static/chunks/main-app-45a0f19af99d61b6.js +0 -1
  462. package/.next/static/chunks/main-f74964b7ae52493e.js +0 -5
  463. package/.next/static/chunks/next/dist/client/components/builtin/app-error-cc518af6b1ffb191.js +0 -1
  464. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cc518af6b1ffb191.js +0 -1
  465. package/.next/static/chunks/next/dist/client/components/builtin/global-error-9bfa08b9491621f2.js +0 -1
  466. package/.next/static/chunks/next/dist/client/components/builtin/not-found-cc518af6b1ffb191.js +0 -1
  467. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cc518af6b1ffb191.js +0 -1
  468. package/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  469. package/.next/static/chunks/webpack-fcf4a889ecbd753c.js +0 -1
  470. package/.next/static/css/45029451a1d7255d.css +0 -3
  471. package/.next/static/media/15605e25b523335c-s.woff2 +0 -0
  472. package/.next/static/media/1a3dce5cfb5f7760-s.woff2 +0 -0
  473. package/.next/static/media/1cdd02902f937a18-s.woff2 +0 -0
  474. package/.next/static/media/4c4b3b30b6bcb2be-s.woff2 +0 -0
  475. package/.next/static/media/641a7b8a5800ee0e-s.woff2 +0 -0
  476. package/.next/static/media/7deddc85b7ffd1dc-s.p.woff2 +0 -0
  477. package/.next/static/media/ec14413c594b3356-s.p.woff2 +0 -0
  478. package/.next/static/media/pdf.worker.min.29aaf158.mjs +0 -6
  479. package/.next/trace +0 -74
  480. package/.next/trace-build +0 -1
  481. package/.next/types/app/api/agent/[id]/events/route.ts +0 -351
  482. package/.next/types/app/api/agent/[id]/route.ts +0 -351
  483. package/.next/types/app/api/agent/new/route.ts +0 -351
  484. package/.next/types/app/api/auth/all-providers/route.ts +0 -351
  485. package/.next/types/app/api/auth/api-key/[provider]/route.ts +0 -351
  486. package/.next/types/app/api/auth/login/[provider]/route.ts +0 -351
  487. package/.next/types/app/api/auth/login/route.ts +0 -351
  488. package/.next/types/app/api/auth/logout/[provider]/route.ts +0 -351
  489. package/.next/types/app/api/auth/providers/route.ts +0 -351
  490. package/.next/types/app/api/auth/status/route.ts +0 -351
  491. package/.next/types/app/api/default-cwd/route.ts +0 -351
  492. package/.next/types/app/api/files/[...path]/route.ts +0 -351
  493. package/.next/types/app/api/harness/route.ts +0 -351
  494. package/.next/types/app/api/home/route.ts +0 -351
  495. package/.next/types/app/api/internal/runtime/route.ts +0 -351
  496. package/.next/types/app/api/models/route.ts +0 -351
  497. package/.next/types/app/api/models-config/discover/route.ts +0 -351
  498. package/.next/types/app/api/models-config/route.ts +0 -351
  499. package/.next/types/app/api/models-config/test/route.ts +0 -351
  500. package/.next/types/app/api/projects/browse/route.ts +0 -351
  501. package/.next/types/app/api/projects/route.ts +0 -351
  502. package/.next/types/app/api/reports/[id]/route.ts +0 -351
  503. package/.next/types/app/api/search/route.ts +0 -351
  504. package/.next/types/app/api/sessions/[id]/context/route.ts +0 -351
  505. package/.next/types/app/api/sessions/[id]/route.ts +0 -351
  506. package/.next/types/app/api/sessions/new/route.ts +0 -351
  507. package/.next/types/app/api/sessions/route.ts +0 -351
  508. package/.next/types/app/api/settings/route.ts +0 -351
  509. package/.next/types/app/api/skills/install/route.ts +0 -351
  510. package/.next/types/app/api/skills/route.ts +0 -351
  511. package/.next/types/app/api/skills/search/route.ts +0 -351
  512. package/.next/types/app/api/soul/route.ts +0 -351
  513. package/.next/types/app/api/version/route.ts +0 -351
  514. package/.next/types/app/layout.ts +0 -87
  515. package/.next/types/app/login/page.ts +0 -87
  516. package/.next/types/app/page.ts +0 -87
  517. package/.next/types/package.json +0 -1
  518. package/.next/types/routes.d.ts +0 -106
  519. package/.next/types/validator.ts +0 -376
@@ -0,0 +1,1058 @@
1
+ "use client";
2
+
3
+ import { useState, useCallback, useRef, useEffect } from "react";
4
+ import { useRouter, useSearchParams } from "next/navigation";
5
+ import { SessionSidebar } from "./SessionSidebar";
6
+ import { ChatWindow } from "./ChatWindow";
7
+ import { FileViewer } from "./FileViewer";
8
+ import { GlobalFileEditor } from "./GlobalFileEditor";
9
+ import { ReportPanel } from "./ReportPanel";
10
+ import { ResizeHandle } from "./ResizeHandle";
11
+ import { TabBar, type Tab } from "./TabBar";
12
+ import { SettingsDialog, type SettingsTab } from "./SettingsDialog";
13
+ import { useTheme } from "@/hooks/useTheme";
14
+ import { useResizable } from "@/hooks/useResizable";
15
+ import type { SessionInfo } from "@/lib/types";
16
+ import type { ChatInputHandle } from "./ChatInput";
17
+
18
+ interface AppSettings {
19
+ generativeUI: boolean;
20
+ }
21
+
22
+ interface RuntimeState {
23
+ running: boolean;
24
+ status: "running" | "orphaned" | "stopped";
25
+ version: string | null;
26
+ url: string | null;
27
+ restartPending: boolean;
28
+ installedVersion: string | null;
29
+ restartDetectedAt: string | null;
30
+ restartStartedAt: string | null;
31
+ }
32
+
33
+ interface VersionStatus {
34
+ current: string;
35
+ latest: string | null;
36
+ updateAvailable: boolean;
37
+ updateCommand: string;
38
+ runtime: RuntimeState | null;
39
+ }
40
+
41
+ export function AppShell() {
42
+ const router = useRouter();
43
+ const searchParams = useSearchParams();
44
+ const { isDark, toggleTheme } = useTheme();
45
+ const [selectedSession, setSelectedSession] = useState<SessionInfo | null>(null);
46
+ // When user clicks +, we only store the cwd — no fake session id
47
+ const [newSessionCwd, setNewSessionCwd] = useState<string | null>(null);
48
+ const [refreshKey, setRefreshKey] = useState(0);
49
+ const [sessionKey, setSessionKey] = useState(0);
50
+ const [explorerRefreshKey, setExplorerRefreshKey] = useState(0);
51
+ const [modelsRefreshKey, setModelsRefreshKey] = useState(0);
52
+ const [settingsOpen, setSettingsOpen] = useState(false);
53
+ const [settingsInitialTab, setSettingsInitialTab] = useState<SettingsTab>("projects");
54
+ const [settings, setSettings] = useState<AppSettings>({ generativeUI: true });
55
+ const [versionStatus, setVersionStatus] = useState<VersionStatus | null>(null);
56
+ const [sidebarOpen, setSidebarOpen] = useState(true);
57
+
58
+ // Resizable panels
59
+ const sidebarResize = useResizable({ initialWidth: 260, minWidth: 180, maxWidth: 500, storageKey: "--annodex-sidebar-width" });
60
+ const rightPanelResize = useResizable({ initialWidth: 500, minWidth: 300, maxWidth: 1200, storageKey: "--annodex-right-panel-width", direction: "left" });
61
+ const chatInputRef = useRef<ChatInputHandle | null>(null);
62
+ const topBarRef = useRef<HTMLDivElement>(null);
63
+
64
+ const [systemPrompt, setSystemPrompt] = useState<string | null>(null);
65
+ const systemBtnRef = useRef<HTMLButtonElement>(null);
66
+ const [editingTitle, setEditingTitle] = useState(false);
67
+ const [titleDraft, setTitleDraft] = useState("");
68
+ const titleInputRef = useRef<HTMLInputElement>(null);
69
+ const titleEditCanceledRef = useRef(false);
70
+
71
+ const handleSystemPromptChange = useCallback((prompt: string | null) => {
72
+ setSystemPrompt(prompt);
73
+ }, []);
74
+
75
+ useEffect(() => {
76
+ setEditingTitle(false);
77
+ }, [selectedSession?.id]);
78
+
79
+ const getSessionTitle = useCallback((session: SessionInfo | null) => {
80
+ if (!session) return "";
81
+ return session.name || session.firstMessage.slice(0, 80) || session.id.slice(0, 12);
82
+ }, []);
83
+
84
+ const handleStartTitleEdit = useCallback(() => {
85
+ if (!selectedSession) return;
86
+ titleEditCanceledRef.current = false;
87
+ setTitleDraft(selectedSession.name || getSessionTitle(selectedSession));
88
+ setEditingTitle(true);
89
+ setTimeout(() => titleInputRef.current?.select(), 0);
90
+ }, [getSessionTitle, selectedSession]);
91
+
92
+ const handleSaveTitle = useCallback(async () => {
93
+ if (titleEditCanceledRef.current) {
94
+ titleEditCanceledRef.current = false;
95
+ setEditingTitle(false);
96
+ return;
97
+ }
98
+ if (!selectedSession) {
99
+ setEditingTitle(false);
100
+ return;
101
+ }
102
+ const name = titleDraft.trim();
103
+ const fallbackTitle = getSessionTitle(selectedSession);
104
+ setEditingTitle(false);
105
+ if (name === (selectedSession.name ?? "") || (!selectedSession.name && name === fallbackTitle)) return;
106
+ try {
107
+ const res = await fetch(`/api/sessions/${encodeURIComponent(selectedSession.id)}`, {
108
+ method: "PATCH",
109
+ headers: { "Content-Type": "application/json" },
110
+ body: JSON.stringify({ name }),
111
+ });
112
+ if (res.ok) {
113
+ setSelectedSession((prev) => prev && prev.id === selectedSession.id ? { ...prev, name } : prev);
114
+ setRefreshKey((k) => k + 1);
115
+ }
116
+ } catch {
117
+ // keep the local title unchanged if saving fails
118
+ }
119
+ }, [getSessionTitle, selectedSession, titleDraft]);
120
+
121
+ const handleTitleKeyDown = useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
122
+ if (event.key === "Enter") {
123
+ event.preventDefault();
124
+ void handleSaveTitle();
125
+ } else if (event.key === "Escape") {
126
+ titleEditCanceledRef.current = true;
127
+ setEditingTitle(false);
128
+ }
129
+ }, [handleSaveTitle]);
130
+
131
+ useEffect(() => {
132
+ let cancelled = false;
133
+ fetch("/api/settings")
134
+ .then((res) => res.ok ? res.json() : null)
135
+ .then((data: { settings?: Partial<AppSettings> } | null) => {
136
+ if (cancelled) return;
137
+ setSettings((cur) => ({
138
+ generativeUI: typeof data?.settings?.generativeUI === "boolean" ? data.settings.generativeUI : cur.generativeUI,
139
+ }));
140
+ })
141
+ .catch(() => {});
142
+ return () => { cancelled = true; };
143
+ }, []);
144
+
145
+ useEffect(() => {
146
+ let cancelled = false;
147
+ let inFlight = false;
148
+ const load = () => {
149
+ if (inFlight) return;
150
+ inFlight = true;
151
+ fetch("/api/version")
152
+ .then((res) => res.ok ? res.json() : null)
153
+ .then((data: VersionStatus | null) => {
154
+ if (!cancelled) setVersionStatus(data);
155
+ })
156
+ .catch(() => {})
157
+ .finally(() => { inFlight = false; });
158
+ };
159
+ load();
160
+ const timer = window.setInterval(load, 60_000);
161
+ return () => {
162
+ cancelled = true;
163
+ window.clearInterval(timer);
164
+ };
165
+ }, []);
166
+
167
+ const handleGenerativeUIToggle = useCallback(() => {
168
+ const next = !settings.generativeUI;
169
+ setSettings((cur) => ({ ...cur, generativeUI: next }));
170
+ fetch("/api/settings", {
171
+ method: "PUT",
172
+ headers: { "Content-Type": "application/json" },
173
+ body: JSON.stringify({ settings: { generativeUI: next } }),
174
+ })
175
+ .then((res) => res.ok ? res.json() : Promise.reject(new Error(`HTTP ${res.status}`)))
176
+ .then((data: { settings?: Partial<AppSettings> }) => {
177
+ setSettings((cur) => ({
178
+ ...cur,
179
+ ...(typeof data.settings?.generativeUI === "boolean" ? { generativeUI: data.settings.generativeUI } : {}),
180
+ }));
181
+ })
182
+ .catch(() => setSettings((cur) => ({ ...cur, generativeUI: !cur.generativeUI })));
183
+ }, [settings.generativeUI]);
184
+
185
+ // Session stats (tokens + cost) — populated by ChatWindow, displayed in top bar
186
+ const [sessionStats, setSessionStats] = useState<{ tokens: { input: number; output: number; cacheRead: number; cacheWrite: number }; cost?: number } | null>(null);
187
+ const handleSessionStatsChange = useCallback((stats: { tokens: { input: number; output: number; cacheRead: number; cacheWrite: number }; cost?: number } | null) => {
188
+ setSessionStats(stats);
189
+ }, []);
190
+
191
+ // Context usage — populated by ChatWindow, displayed in top bar
192
+ const [contextUsage, setContextUsage] = useState<{ percent: number | null; contextWindow: number; tokens: number | null } | null>(null);
193
+ const handleContextUsageChange = useCallback((usage: { percent: number | null; contextWindow: number; tokens: number | null } | null) => {
194
+ setContextUsage(usage);
195
+ }, []);
196
+
197
+ // Top-bar system prompt dropdown
198
+ const [activeTopPanel, setActiveTopPanel] = useState<"system" | null>(null);
199
+ const [topPanelPos, setTopPanelPos] = useState<{ top: number; left: number; width: number } | null>(null);
200
+
201
+ const toggleTopPanel = useCallback((panel: "system") => {
202
+ setActiveTopPanel((cur) => cur === panel ? null : panel);
203
+ }, []);
204
+
205
+ useEffect(() => {
206
+ if (!activeTopPanel || !topBarRef.current) return;
207
+ const update = () => {
208
+ const rect = topBarRef.current!.getBoundingClientRect();
209
+ setTopPanelPos({ top: rect.bottom, left: rect.left, width: rect.width });
210
+ };
211
+ update();
212
+ const ro = new ResizeObserver(update);
213
+ ro.observe(topBarRef.current);
214
+ return () => ro.disconnect();
215
+ }, [activeTopPanel]);
216
+
217
+ // Single active panel — only one dropdown open at a time
218
+ // Right panel — pinned report tab plus file tabs
219
+ const [fileTabs, setFileTabs] = useState<Tab[]>([]);
220
+ const [activeRightTabId, setActiveRightTabId] = useState<string>("report");
221
+ const [rightPanelOpen, setRightPanelOpen] = useState(false);
222
+ const [reportRefreshKey, setReportRefreshKey] = useState(0);
223
+
224
+ const handleAtMention = useCallback((relativePath: string) => {
225
+ chatInputRef.current?.insertText("`" + relativePath + "`");
226
+ }, []);
227
+
228
+ const [initialSessionId] = useState<string | null>(() => searchParams.get("session"));
229
+ const [activeCwd, setActiveCwd] = useState<string | null>(null);
230
+ // True once the initial ?session= URL param has been resolved (or confirmed absent)
231
+ const [initialSessionRestored, setInitialSessionRestored] = useState<boolean>(() => !searchParams.get("session"));
232
+ // Suppresses sessionKey bump in handleCwdChange during the initial URL restore
233
+ const suppressCwdBumpRef = useRef(false);
234
+
235
+ const handleCwdChange = useCallback((cwd: string | null) => {
236
+ setActiveCwd(cwd);
237
+ // Skip if cwd is null (initial mount) or during the initial URL restore.
238
+ if (!cwd || suppressCwdBumpRef.current) return;
239
+ // Close any session that belongs to a different cwd — it no longer
240
+ // matches the selected project directory.
241
+ setSelectedSession((prev) => {
242
+ if (prev && prev.cwd !== cwd) return null;
243
+ return prev;
244
+ });
245
+ setNewSessionCwd((prev) => {
246
+ if (prev && prev !== cwd) return null;
247
+ return prev;
248
+ });
249
+ setSessionKey((k) => k + 1);
250
+ setExplorerRefreshKey((k) => k + 1);
251
+ setSystemPrompt(null);
252
+ setActiveTopPanel(null);
253
+ router.replace("/", { scroll: false });
254
+ }, [router]);
255
+
256
+ const handleProjectSelect = useCallback((cwd: string) => {
257
+ handleCwdChange(cwd);
258
+ setSettingsOpen(false);
259
+ setRefreshKey((k) => k + 1);
260
+ }, [handleCwdChange]);
261
+
262
+ const handleSelectSession = useCallback((session: SessionInfo, isRestore = false) => {
263
+ setNewSessionCwd(null);
264
+ setActiveCwd(session.cwd);
265
+ setSelectedSession(session);
266
+ setSessionKey((k) => k + 1);
267
+ setSystemPrompt(null);
268
+ setInitialSessionRestored(true);
269
+ if (isRestore) {
270
+ // Suppress the redundant sessionKey bump that would come from the
271
+ // onCwdChange effect firing after setSelectedCwd in the sidebar
272
+ suppressCwdBumpRef.current = true;
273
+ setTimeout(() => { suppressCwdBumpRef.current = false; }, 0);
274
+ }
275
+ // Skip router.replace when restoring from URL — the param is already correct
276
+ // and calling replace in production Next.js triggers a Suspense remount loop
277
+ if (!isRestore) {
278
+ router.replace(`?session=${encodeURIComponent(session.id)}`, { scroll: false });
279
+ }
280
+ }, [router]);
281
+
282
+ const handleNewSession = useCallback((_sessionId: string, cwd: string) => {
283
+ setSelectedSession(null);
284
+ setNewSessionCwd(cwd);
285
+ setActiveCwd(cwd);
286
+ setSessionKey((k) => k + 1);
287
+ setSystemPrompt(null);
288
+ setActiveTopPanel(null);
289
+ router.replace("/", { scroll: false });
290
+ }, [router]);
291
+
292
+ // Called by ChatWindow when a new session gets its real id
293
+ const handleSessionCreated = useCallback((session: SessionInfo) => {
294
+ setNewSessionCwd(null);
295
+ setActiveCwd(session.cwd);
296
+ setSelectedSession(session);
297
+ setRefreshKey((k) => k + 1);
298
+ router.replace(`?session=${encodeURIComponent(session.id)}`, { scroll: false });
299
+ }, [router]);
300
+
301
+ const handleAgentEnd = useCallback(() => {
302
+ setRefreshKey((k) => k + 1);
303
+ setExplorerRefreshKey((k) => k + 1);
304
+ }, []);
305
+
306
+ const handleSessionForked = useCallback((newSessionId: string) => {
307
+ setRefreshKey((k) => k + 1);
308
+ setNewSessionCwd(null);
309
+ setSelectedSession((prev) => ({
310
+ ...(prev ?? { path: "", cwd: "", created: "", modified: "", messageCount: 0, firstMessage: "" }),
311
+ id: newSessionId,
312
+ }));
313
+ router.replace(`?session=${encodeURIComponent(newSessionId)}`, { scroll: false });
314
+ }, [router]);
315
+
316
+ const handleInitialRestoreDone = useCallback(() => {
317
+ setInitialSessionRestored(true);
318
+ }, []);
319
+
320
+ const handleSessionDeleted = useCallback((sessionId: string) => {
321
+ setRefreshKey((k) => k + 1);
322
+ if (selectedSession?.id === sessionId) {
323
+ const cwd = selectedSession.cwd;
324
+ setSelectedSession(null);
325
+ setNewSessionCwd(cwd ?? null);
326
+ setSessionKey((k) => k + 1);
327
+ setSystemPrompt(null);
328
+ setActiveTopPanel(null);
329
+ router.replace("/", { scroll: false });
330
+ }
331
+ }, [selectedSession, router]);
332
+
333
+ const handleOpenFile = useCallback((filePath: string, fileName: string) => {
334
+ const tabId = `file:${filePath}`;
335
+ setFileTabs((prev) => {
336
+ if (prev.find((t) => t.id === tabId)) return prev;
337
+ return [...prev, { id: tabId, label: fileName, filePath }];
338
+ });
339
+ setActiveRightTabId(tabId);
340
+ setRightPanelOpen(true);
341
+ }, []);
342
+
343
+ const handleCloseFileTab = useCallback((tabId: string) => {
344
+ if (tabId === "report") return;
345
+ setFileTabs((prev) => {
346
+ const next = prev.filter((t) => t.id !== tabId);
347
+ return next;
348
+ });
349
+ setActiveRightTabId((cur) => {
350
+ if (cur !== tabId) return cur;
351
+ const remaining = fileTabs.filter((t) => t.id !== tabId);
352
+ return remaining.length > 0 ? remaining[remaining.length - 1].id : "report";
353
+ });
354
+ }, [fileTabs]);
355
+
356
+ // Show chat area if a session is selected, or if we have a cwd to start a new session in
357
+ const effectiveNewSessionCwd = newSessionCwd ?? (selectedSession === null && activeCwd ? activeCwd : null);
358
+ const showChat = selectedSession !== null || effectiveNewSessionCwd !== null;
359
+ // While restoring initial session from URL, don't show the placeholder
360
+ const showPlaceholder = initialSessionRestored && !showChat;
361
+
362
+ const rightTabs: Tab[] = [{ id: "report", label: "Report", closable: false, icon: "report" }, ...fileTabs];
363
+ const activeFileTab = fileTabs.find((t) => t.id === activeRightTabId) ?? null;
364
+ const topBarSessionTitle = selectedSession ? getSessionTitle(selectedSession) : effectiveNewSessionCwd ? "New chat" : "No session";
365
+ const topBarCwd = selectedSession?.cwd ?? effectiveNewSessionCwd ?? activeCwd ?? "";
366
+ const topBarProjectName = topBarCwd ? topBarCwd.replace(/\/+$/, "").split(/[\\/]/).filter(Boolean).pop() || topBarCwd : "";
367
+ const settingsCwd = activeCwd ?? selectedSession?.cwd ?? newSessionCwd ?? null;
368
+ const updateStatus = (() => {
369
+ const runtime = versionStatus?.runtime;
370
+ const target = runtime?.installedVersion ?? versionStatus?.latest;
371
+ if (runtime?.restartPending && target) {
372
+ const restarting = Boolean(runtime.restartStartedAt);
373
+ return {
374
+ label: `${restarting ? "Restarting" : "Restart queued"} ${target}`,
375
+ title: [
376
+ `Current runtime: v${runtime.version ?? versionStatus?.current ?? "unknown"}`,
377
+ `Installed: v${target}`,
378
+ restarting ? "A replacement server is starting." : "annodex will restart after active sessions become idle.",
379
+ ].join("\n"),
380
+ active: true,
381
+ };
382
+ }
383
+ if (versionStatus?.updateAvailable && versionStatus.latest) {
384
+ return {
385
+ label: `Update ${versionStatus.latest}`,
386
+ title: `Current: v${versionStatus.current}\nLatest: v${versionStatus.latest}\nRun: ${versionStatus.updateCommand}`,
387
+ active: false,
388
+ };
389
+ }
390
+ return null;
391
+ })();
392
+ const openSettings = useCallback((tab: SettingsTab = "projects") => {
393
+ setSettingsInitialTab(tab);
394
+ setSettingsOpen(true);
395
+ }, []);
396
+
397
+ const sidebarContent = (
398
+ <>
399
+ <SessionSidebar
400
+ selectedSessionId={selectedSession?.id ?? null}
401
+ onSelectSession={handleSelectSession}
402
+ onNewSession={handleNewSession}
403
+ initialSessionId={initialSessionId}
404
+ onInitialRestoreDone={handleInitialRestoreDone}
405
+ refreshKey={refreshKey}
406
+ onSessionDeleted={handleSessionDeleted}
407
+ selectedCwd={selectedSession?.cwd ?? newSessionCwd ?? activeCwd ?? null}
408
+ onCwdChange={handleCwdChange}
409
+ onOpenFile={handleOpenFile}
410
+ explorerRefreshKey={explorerRefreshKey}
411
+ onAtMention={handleAtMention}
412
+ />
413
+ <div style={{ padding: "8px", flexShrink: 0 }}>
414
+ <button
415
+ type="button"
416
+ onClick={() => openSettings("projects")}
417
+ title="Settings"
418
+ aria-label="Settings"
419
+ style={{
420
+ width: 34,
421
+ display: "flex",
422
+ alignItems: "center",
423
+ justifyContent: "center",
424
+ height: 34,
425
+ padding: 0,
426
+ background: "none",
427
+ border: "none",
428
+ borderRadius: 9,
429
+ color: "var(--text-muted)",
430
+ cursor: "pointer",
431
+ fontSize: 12,
432
+ transition: "background 0.12s, color 0.12s",
433
+ }}
434
+ onMouseEnter={(e) => { e.currentTarget.style.background = "var(--bg-hover)"; e.currentTarget.style.color = "var(--text)"; }}
435
+ onMouseLeave={(e) => { e.currentTarget.style.background = "none"; e.currentTarget.style.color = "var(--text-muted)"; }}
436
+ >
437
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
438
+ <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.51a2 2 0 0 1 1-1.72l.15-.1a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2Z" />
439
+ <circle cx="12" cy="12" r="3" />
440
+ </svg>
441
+ </button>
442
+ </div>
443
+ </>
444
+ );
445
+
446
+ return (
447
+ <>
448
+ <div style={{ display: "flex", height: "100dvh", overflow: "hidden", background: "var(--bg)" }}>
449
+ {/* Mobile overlay backdrop */}
450
+ <div
451
+ className="sidebar-overlay-backdrop"
452
+ onClick={() => setSidebarOpen(false)}
453
+ style={{
454
+ position: "fixed",
455
+ inset: 0,
456
+ zIndex: 199,
457
+ background: "rgba(0,0,0,0.4)",
458
+ opacity: sidebarOpen ? 1 : 0,
459
+ pointerEvents: sidebarOpen ? "auto" : "none",
460
+ transition: "opacity 0.25s ease",
461
+ }}
462
+ />
463
+
464
+ {/* Left sidebar */}
465
+ <div
466
+ className={`sidebar-container${sidebarOpen ? " sidebar-open" : " sidebar-closed"}`}
467
+ style={{
468
+ background: "var(--bg-panel)",
469
+ borderRight: "1px solid var(--border)",
470
+ display: "flex",
471
+ flexDirection: "column",
472
+ flexShrink: 0,
473
+ zIndex: 200,
474
+ width: sidebarResize.width,
475
+ minWidth: sidebarResize.width,
476
+ transition: sidebarOpen || sidebarResize.isResizing ? "none" : undefined,
477
+ }}
478
+ >
479
+ {sidebarContent}
480
+ </div>
481
+
482
+ {/* Sidebar drag handle */}
483
+ {sidebarOpen && (
484
+ <ResizeHandle
485
+ side="right"
486
+ ariaLabel="Resize sidebar"
487
+ onResizeStart={sidebarResize.beginResize}
488
+ onResize={sidebarResize.resizeBy}
489
+ onResizeEnd={sidebarResize.endResize}
490
+ />
491
+ )}
492
+
493
+ {/* Center: chat */}
494
+ <div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden", minWidth: 0 }}>
495
+ {/* Top bar */}
496
+ <div
497
+ ref={topBarRef}
498
+ className="app-topbar"
499
+ style={{
500
+ display: "flex",
501
+ alignItems: "center",
502
+ gap: 8,
503
+ flexShrink: 0,
504
+ borderBottom: "1px solid var(--border)",
505
+ height: 48,
506
+ padding: "0 8px",
507
+ background: "var(--bg)",
508
+ }}
509
+ >
510
+ <button
511
+ className="topbar-sidebar-toggle"
512
+ onClick={() => setSidebarOpen((v) => !v)}
513
+ title={sidebarOpen ? "Hide sidebar" : "Show sidebar"}
514
+ style={{
515
+ display: "flex", alignItems: "center", justifyContent: "center",
516
+ width: 32, height: 32, padding: 0,
517
+ background: "var(--bg-panel)", border: "1px solid var(--border)", borderRadius: 7,
518
+ color: "var(--text-muted)", cursor: "pointer", flexShrink: 0, transition: "color 0.12s, background 0.12s",
519
+ }}
520
+ onMouseEnter={(e) => { e.currentTarget.style.color = "var(--text)"; e.currentTarget.style.background = "var(--bg-hover)"; }}
521
+ onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-muted)"; e.currentTarget.style.background = "var(--bg-panel)"; }}
522
+ >
523
+ {sidebarOpen ? (
524
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
525
+ <rect x="3" y="3" width="18" height="18" rx="2" /><line x1="9" y1="3" x2="9" y2="21" />
526
+ </svg>
527
+ ) : (
528
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
529
+ <line x1="3" y1="6" x2="21" y2="6" /><line x1="3" y1="12" x2="21" y2="12" /><line x1="3" y1="18" x2="21" y2="18" />
530
+ </svg>
531
+ )}
532
+ </button>
533
+
534
+ <div className="topbar-title-area" style={{ display: "flex", alignItems: "center", gap: 7, minWidth: 0, flex: 1 }}>
535
+ <div style={{ minWidth: 0, display: "flex", alignItems: "center", gap: 6 }}>
536
+ {editingTitle && selectedSession ? (
537
+ <input
538
+ ref={titleInputRef}
539
+ value={titleDraft}
540
+ onChange={(e) => setTitleDraft(e.target.value)}
541
+ onKeyDown={handleTitleKeyDown}
542
+ onBlur={() => void handleSaveTitle()}
543
+ style={{
544
+ width: "min(360px, 34vw)",
545
+ height: 28,
546
+ boxSizing: "border-box",
547
+ border: "1px solid var(--border)",
548
+ borderRadius: 6,
549
+ background: "var(--bg-panel)",
550
+ color: "var(--text)",
551
+ padding: "0 8px",
552
+ fontSize: 13,
553
+ fontWeight: 500,
554
+ outline: "none",
555
+ }}
556
+ />
557
+ ) : (
558
+ <>
559
+ <div
560
+ className="topbar-session-title"
561
+ title={topBarSessionTitle}
562
+ style={{
563
+ minWidth: 0,
564
+ maxWidth: "min(420px, 36vw)",
565
+ overflow: "hidden",
566
+ textOverflow: "ellipsis",
567
+ whiteSpace: "nowrap",
568
+ color: showChat ? "var(--text)" : "var(--text-muted)",
569
+ fontSize: 14,
570
+ fontWeight: 600,
571
+ lineHeight: 1.2,
572
+ }}
573
+ >
574
+ {topBarSessionTitle}
575
+ </div>
576
+ {selectedSession && (
577
+ <button
578
+ type="button"
579
+ className="topbar-rename-button"
580
+ onClick={handleStartTitleEdit}
581
+ title="Rename session"
582
+ style={{
583
+ width: 22,
584
+ height: 22,
585
+ display: "flex",
586
+ alignItems: "center",
587
+ justifyContent: "center",
588
+ padding: 0,
589
+ border: "none",
590
+ borderRadius: 5,
591
+ background: "transparent",
592
+ color: "var(--text-dim)",
593
+ cursor: "pointer",
594
+ flexShrink: 0,
595
+ }}
596
+ onMouseEnter={(e) => { e.currentTarget.style.color = "var(--text)"; e.currentTarget.style.background = "var(--bg-hover)"; }}
597
+ onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-dim)"; e.currentTarget.style.background = "transparent"; }}
598
+ >
599
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
600
+ <path d="M12 20h9" />
601
+ <path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" />
602
+ </svg>
603
+ </button>
604
+ )}
605
+ </>
606
+ )}
607
+ </div>
608
+ {topBarProjectName && (
609
+ <>
610
+ <span className="topbar-project-crumb" style={{ color: "var(--text-dim)", fontSize: 12, flexShrink: 0 }}>/</span>
611
+ <span
612
+ className="topbar-project-crumb"
613
+ title={topBarCwd || topBarProjectName}
614
+ style={{
615
+ minWidth: 0,
616
+ maxWidth: "min(280px, 24vw)",
617
+ overflow: "hidden",
618
+ textOverflow: "ellipsis",
619
+ whiteSpace: "nowrap",
620
+ color: "var(--text-muted)",
621
+ fontSize: 12,
622
+ lineHeight: 1.2,
623
+ }}
624
+ >
625
+ {topBarProjectName}
626
+ </span>
627
+ </>
628
+ )}
629
+ </div>
630
+
631
+ {showChat && (
632
+ <div className="topbar-chat-tools" style={{ display: "flex", alignItems: "center", gap: 6, height: 28, flexShrink: 0 }}>
633
+ <button
634
+ ref={systemBtnRef}
635
+ className="topbar-system-button"
636
+ onClick={() => toggleTopPanel("system")}
637
+ title="System prompt"
638
+ aria-label="System prompt"
639
+ style={{
640
+ display: "flex", alignItems: "center", gap: 6,
641
+ height: 28, padding: "0 9px",
642
+ background: activeTopPanel === "system" ? "var(--bg-selected)" : "var(--bg-panel)",
643
+ border: `1px solid ${activeTopPanel === "system" ? "rgba(37,99,235,0.45)" : "var(--border)"}`,
644
+ borderRadius: 7,
645
+ cursor: "pointer",
646
+ color: activeTopPanel === "system" ? "var(--text)" : "var(--text-muted)",
647
+ fontSize: 11, whiteSpace: "nowrap", transition: "color 0.1s, background 0.1s",
648
+ }}
649
+ onMouseEnter={(e) => { e.currentTarget.style.color = "var(--text)"; e.currentTarget.style.background = "var(--bg-hover)"; }}
650
+ onMouseLeave={(e) => {
651
+ e.currentTarget.style.color = activeTopPanel === "system" ? "var(--text)" : "var(--text-muted)";
652
+ e.currentTarget.style.background = activeTopPanel === "system" ? "var(--bg-selected)" : "var(--bg-panel)";
653
+ }}
654
+ >
655
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ color: systemPrompt ? "var(--accent)" : "var(--text-dim)", flexShrink: 0 }}>
656
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
657
+ <polyline points="14 2 14 8 20 8" />
658
+ <line x1="8" y1="13" x2="16" y2="13" />
659
+ <line x1="8" y1="17" x2="13" y2="17" />
660
+ </svg>
661
+ <span className="topbar-system-label">System</span>
662
+ </button>
663
+ </div>
664
+ )}
665
+ {/* Session stats — right-aligned in top bar */}
666
+ {showChat && (sessionStats || contextUsage) && (() => {
667
+ const t = sessionStats?.tokens;
668
+ const c = sessionStats?.cost ?? 0;
669
+ const fmt = (n: number) => n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` : n >= 1000 ? `${(n / 1000).toFixed(0)}k` : String(n);
670
+ const costStr = c > 0 ? (c >= 0.01 ? `$${c.toFixed(2)}` : `<$0.01`) : null;
671
+
672
+ let ctxColor = "var(--text-muted)";
673
+ let ctxStr: string | null = null;
674
+ if (contextUsage?.contextWindow) {
675
+ const pct = contextUsage.percent;
676
+ const used = contextUsage.tokens;
677
+ if (pct !== null && pct > 90) ctxColor = "#ef4444";
678
+ else if (pct !== null && pct > 70) ctxColor = "rgba(234,179,8,0.95)";
679
+ ctxStr = used !== null ? `${fmt(used)} / ${fmt(contextUsage.contextWindow)}` : `? / ${fmt(contextUsage.contextWindow)}`;
680
+ }
681
+
682
+ const tooltipParts: string[] = [];
683
+ if (t) {
684
+ tooltipParts.push(`in: ${t.input.toLocaleString()}`);
685
+ tooltipParts.push(`out: ${t.output.toLocaleString()}`);
686
+ tooltipParts.push(`cache read: ${t.cacheRead.toLocaleString()}`);
687
+ tooltipParts.push(`cache write: ${t.cacheWrite.toLocaleString()}`);
688
+ if (c > 0) tooltipParts.push(`cost: $${c.toFixed(4)}`);
689
+ }
690
+ if (contextUsage?.contextWindow) {
691
+ const pct = contextUsage.percent;
692
+ tooltipParts.push(`context: ${contextUsage.tokens !== null ? contextUsage.tokens.toLocaleString() : "unknown"} of ${contextUsage.contextWindow.toLocaleString()} tokens${pct !== null ? ` (${pct.toFixed(1)}%)` : ""}`);
693
+ }
694
+ const tooltip = tooltipParts.join(" | ");
695
+
696
+ return (
697
+ <div
698
+ className="topbar-session-stats"
699
+ title={tooltip}
700
+ style={{
701
+ display: "flex", alignItems: "center", gap: 10,
702
+ padding: "0 8px",
703
+ height: 28,
704
+ border: "1px solid var(--border)",
705
+ borderRadius: 7,
706
+ background: "var(--bg-panel)",
707
+ fontSize: 11, color: "var(--text-muted)",
708
+ whiteSpace: "nowrap", cursor: "default",
709
+ fontVariantNumeric: "tabular-nums",
710
+ }}
711
+ >
712
+ {t && t.input > 0 && (
713
+ <span style={{ display: "flex", alignItems: "center", gap: 4 }}>
714
+ <svg width="12" height="12" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round">
715
+ <line x1="5" y1="8.5" x2="5" y2="1.5" /><polyline points="2 4 5 1.5 8 4" />
716
+ </svg>
717
+ {fmt(t.input)}
718
+ </span>
719
+ )}
720
+ {t && t.output > 0 && (
721
+ <span style={{ display: "flex", alignItems: "center", gap: 4 }}>
722
+ <svg width="12" height="12" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round">
723
+ <line x1="5" y1="1.5" x2="5" y2="8.5" /><polyline points="2 6 5 8.5 8 6" />
724
+ </svg>
725
+ {fmt(t.output)}
726
+ </span>
727
+ )}
728
+ {t && t.cacheRead > 0 && (
729
+ <span style={{ display: "flex", alignItems: "center", gap: 4 }}>
730
+ <svg width="12" height="12" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round">
731
+ <path d="M8.5 5a3.5 3.5 0 1 1-1-2.45" /><polyline points="6.5 1.5 8.5 2.5 7.5 4.5" />
732
+ </svg>
733
+ {fmt(t.cacheRead)}
734
+ </span>
735
+ )}
736
+ {costStr && (
737
+ <span style={{ display: "flex", alignItems: "center", color: "var(--text)", fontWeight: 500 }}>
738
+ {costStr}
739
+ </span>
740
+ )}
741
+ {ctxStr && (
742
+ <span style={{ display: "flex", alignItems: "center", gap: 4, color: ctxColor }}>
743
+ <svg width="12" height="12" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round">
744
+ <path d="M1 9 L1 5 Q1 1 5 1 Q9 1 9 5 L9 9" /><line x1="1" y1="9" x2="9" y2="9" />
745
+ </svg>
746
+ {ctxStr}
747
+ </span>
748
+ )}
749
+ </div>
750
+ );
751
+ })()}
752
+ {updateStatus && (
753
+ <div
754
+ className="topbar-update-status"
755
+ title={updateStatus.title}
756
+ style={{
757
+ display: "flex",
758
+ alignItems: "center",
759
+ gap: 6,
760
+ height: 28,
761
+ padding: "0 8px",
762
+ border: `1px solid ${updateStatus.active ? "rgba(37,99,235,0.45)" : "var(--border)"}`,
763
+ borderRadius: 7,
764
+ background: updateStatus.active ? "var(--bg-selected)" : "var(--bg-panel)",
765
+ color: updateStatus.active ? "var(--accent)" : "var(--text-muted)",
766
+ fontSize: 11,
767
+ whiteSpace: "nowrap",
768
+ flexShrink: 0,
769
+ fontVariantNumeric: "tabular-nums",
770
+ }}
771
+ >
772
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
773
+ <path d="M21 12a9 9 0 0 1-15.5 6.2" />
774
+ <path d="M3 12A9 9 0 0 1 18.5 5.8" />
775
+ <path d="M7 18H5.5v1.5" />
776
+ <path d="M17 6h1.5V4.5" />
777
+ </svg>
778
+ <span>{updateStatus.label}</span>
779
+ </div>
780
+ )}
781
+ <button
782
+ type="button"
783
+ className="topbar-icon-button topbar-generate-ui-toggle"
784
+ onClick={handleGenerativeUIToggle}
785
+ title={`生成式 UI: ${settings.generativeUI ? "on" : "off"}`}
786
+ aria-label={`生成式 UI: ${settings.generativeUI ? "on" : "off"}`}
787
+ style={{
788
+ display: "flex", alignItems: "center", justifyContent: "center",
789
+ width: 32, height: 28, padding: 0,
790
+ background: settings.generativeUI ? "var(--bg-selected)" : "var(--bg-panel)",
791
+ border: `1px solid ${settings.generativeUI ? "rgba(37,99,235,0.45)" : "var(--border)"}`,
792
+ borderRadius: 7,
793
+ color: settings.generativeUI ? "var(--accent)" : "var(--text-muted)",
794
+ cursor: "pointer",
795
+ flexShrink: 0,
796
+ transition: "color 0.12s, background 0.12s",
797
+ }}
798
+ onMouseEnter={(e) => { e.currentTarget.style.color = settings.generativeUI ? "var(--accent)" : "var(--text)"; e.currentTarget.style.background = "var(--bg-hover)"; }}
799
+ onMouseLeave={(e) => {
800
+ e.currentTarget.style.color = settings.generativeUI ? "var(--accent)" : "var(--text-muted)";
801
+ e.currentTarget.style.background = settings.generativeUI ? "var(--bg-selected)" : "var(--bg-panel)";
802
+ }}
803
+ >
804
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
805
+ <rect x="3" y="3" width="7" height="7" rx="1.5" />
806
+ <rect x="14" y="3" width="7" height="7" rx="1.5" />
807
+ <rect x="3" y="14" width="7" height="7" rx="1.5" />
808
+ <path d="M16 17h5" />
809
+ <path d="M18.5 14.5v5" />
810
+ </svg>
811
+ </button>
812
+ <button
813
+ className="topbar-icon-button topbar-theme-toggle"
814
+ onClick={(e) => {
815
+ const rect = e.currentTarget.getBoundingClientRect();
816
+ toggleTheme({ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 });
817
+ }}
818
+ title={isDark ? "Switch to light mode" : "Switch to dark mode"}
819
+ aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
820
+ aria-pressed={isDark}
821
+ style={{
822
+ display: "flex", alignItems: "center", justifyContent: "center",
823
+ width: 32, height: 28, padding: 0,
824
+ background: "var(--bg-panel)", border: "1px solid var(--border)", borderRadius: 7,
825
+ color: "var(--text-muted)", cursor: "pointer", flexShrink: 0, transition: "color 0.12s, background 0.12s",
826
+ }}
827
+ onMouseEnter={(e) => { e.currentTarget.style.color = "var(--text)"; e.currentTarget.style.background = "var(--bg-hover)"; }}
828
+ onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-muted)"; e.currentTarget.style.background = "var(--bg-panel)"; }}
829
+ >
830
+ {isDark ? (
831
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
832
+ <circle cx="12" cy="12" r="5" />
833
+ <line x1="12" y1="1" x2="12" y2="3" /><line x1="12" y1="21" x2="12" y2="23" />
834
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" /><line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
835
+ <line x1="1" y1="12" x2="3" y2="12" /><line x1="21" y1="12" x2="23" y2="12" />
836
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" /><line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
837
+ </svg>
838
+ ) : (
839
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
840
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
841
+ </svg>
842
+ )}
843
+ </button>
844
+ <button
845
+ className="topbar-icon-button topbar-right-panel-toggle"
846
+ onClick={() => setRightPanelOpen((v) => !v)}
847
+ title={rightPanelOpen ? "Hide report panel" : "Show report panel"}
848
+ aria-label={rightPanelOpen ? "Hide report panel" : "Show report panel"}
849
+ style={{
850
+ display: "flex", alignItems: "center", justifyContent: "center",
851
+ width: 32, height: 28, padding: 0,
852
+ background: rightPanelOpen ? "var(--bg-selected)" : "var(--bg-panel)",
853
+ border: `1px solid ${rightPanelOpen ? "rgba(37,99,235,0.45)" : "var(--border)"}`,
854
+ borderRadius: 7,
855
+ color: rightPanelOpen ? "var(--accent)" : "var(--text-muted)",
856
+ cursor: "pointer",
857
+ flexShrink: 0,
858
+ transition: "color 0.12s, background 0.12s",
859
+ }}
860
+ onMouseEnter={(e) => { e.currentTarget.style.color = rightPanelOpen ? "var(--accent)" : "var(--text)"; e.currentTarget.style.background = "var(--bg-hover)"; }}
861
+ onMouseLeave={(e) => {
862
+ e.currentTarget.style.color = rightPanelOpen ? "var(--accent)" : "var(--text-muted)";
863
+ e.currentTarget.style.background = rightPanelOpen ? "var(--bg-selected)" : "var(--bg-panel)";
864
+ }}
865
+ >
866
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
867
+ <rect x="3" y="3" width="18" height="18" rx="2" /><line x1="15" y1="3" x2="15" y2="21" />
868
+ </svg>
869
+ </button>
870
+ {/* Top panel dropdown — shared, only one active at a time */}
871
+ {activeTopPanel && topPanelPos && (
872
+ <div style={{
873
+ position: "fixed",
874
+ top: topPanelPos.top,
875
+ left: topPanelPos.left,
876
+ width: topPanelPos.width,
877
+ zIndex: 500,
878
+ }}>
879
+ {activeTopPanel === "system" && (
880
+ <div style={{
881
+ background: "var(--bg-panel)",
882
+ borderBottom: "1px solid var(--border)",
883
+ }}>
884
+ {systemPrompt ? (
885
+ <div style={{
886
+ maxHeight: "min(600px, 75vh)",
887
+ overflowY: "auto",
888
+ padding: "12px 16px",
889
+ color: "var(--text-muted)",
890
+ fontSize: 12,
891
+ lineHeight: 1.6,
892
+ whiteSpace: "pre-wrap",
893
+ fontFamily: "var(--font-mono)",
894
+ }}>
895
+ {systemPrompt}
896
+ </div>
897
+ ) : systemPrompt === "" ? (
898
+ <div style={{ padding: "10px 16px", fontSize: 12, color: "var(--text-muted)", fontStyle: "italic" }}>
899
+ System prompt is empty (tools are disabled)
900
+ </div>
901
+ ) : (
902
+ <div style={{ padding: "10px 16px", fontSize: 12, color: "var(--text-muted)", fontStyle: "italic" }}>
903
+ Send a message to load the system prompt
904
+ </div>
905
+ )}
906
+ </div>
907
+ )}
908
+ </div>
909
+ )}
910
+
911
+ </div>
912
+
913
+ {/* Chat content */}
914
+ <div style={{ flex: 1, overflow: "hidden", position: "relative" }}>
915
+ {showChat ? (
916
+ <ChatWindow
917
+ key={sessionKey}
918
+ session={selectedSession}
919
+ newSessionCwd={effectiveNewSessionCwd}
920
+ onAgentEnd={handleAgentEnd}
921
+ onSessionCreated={handleSessionCreated}
922
+ onSessionForked={handleSessionForked}
923
+ modelsRefreshKey={modelsRefreshKey}
924
+ chatInputRef={chatInputRef}
925
+ onSystemPromptChange={handleSystemPromptChange}
926
+ onSessionStatsChange={handleSessionStatsChange}
927
+ onContextUsageChange={handleContextUsageChange}
928
+ onReportUpdated={() => setReportRefreshKey((k) => k + 1)}
929
+ generativeUI={settings.generativeUI}
930
+ />
931
+ ) : showPlaceholder ? (
932
+ activeCwd ? (
933
+ <div style={{ height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--text-muted)", fontSize: 15 }}>
934
+ Select a session from the sidebar
935
+ </div>
936
+ ) : (
937
+ <div style={{ position: "absolute", top: 12, left: 12, display: "flex", alignItems: "flex-start", gap: 8, userSelect: "none", pointerEvents: "none" }}>
938
+ <svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" style={{ opacity: 0.7, flexShrink: 0 }}>
939
+ <line x1="20" y1="12" x2="4" y2="12" /><polyline points="10 6 4 12 10 18" />
940
+ </svg>
941
+ <div>
942
+ <div style={{ fontSize: 18, fontWeight: 600, color: "var(--text)", marginBottom: 8 }}>Get Started</div>
943
+ <div style={{ fontSize: 12, color: "var(--text-muted)", lineHeight: 1.8 }}>
944
+ <span style={{ color: "var(--text-dim)", marginRight: 6 }}>1.</span>Select a project directory from the sidebar<br />
945
+ <span style={{ color: "var(--text-dim)", marginRight: 6 }}>2.</span>Add models via the <strong style={{ color: "var(--text)" }}>Models</strong> button at the bottom
946
+ </div>
947
+ </div>
948
+ </div>
949
+ )
950
+ ) : null}
951
+ </div>
952
+ </div>
953
+
954
+ {/* Right panel drag handle */}
955
+ {rightPanelOpen && (
956
+ <ResizeHandle
957
+ side="left"
958
+ ariaLabel="Resize right panel"
959
+ onResizeStart={rightPanelResize.beginResize}
960
+ onResize={rightPanelResize.resizeBy}
961
+ onResizeEnd={rightPanelResize.endResize}
962
+ />
963
+ )}
964
+
965
+ {/* Right panel: report + file viewer */}
966
+ <div
967
+ className={`right-panel-container${rightPanelOpen ? " right-panel-open" : " right-panel-closed"}`}
968
+ style={{
969
+ display: rightPanelOpen ? "flex" : "none",
970
+ flexDirection: "column",
971
+ borderLeft: "1px solid var(--border)",
972
+ background: "var(--bg)",
973
+ flexShrink: 0,
974
+ width: rightPanelResize.width,
975
+ minWidth: rightPanelResize.width,
976
+ transition: rightPanelResize.isResizing ? "none" : undefined,
977
+ }}
978
+ >
979
+ {/* Right panel tab bar */}
980
+ <div style={{ display: "flex", alignItems: "center", flexShrink: 0, background: "var(--bg-panel)", borderBottom: "1px solid var(--border)", height: 36 }}>
981
+ <button
982
+ type="button"
983
+ className="right-panel-back-button"
984
+ onClick={() => setRightPanelOpen(false)}
985
+ title="Back to chat"
986
+ aria-label="Back to chat"
987
+ style={{
988
+ height: 36,
989
+ padding: "0 10px",
990
+ border: "none",
991
+ borderRight: "1px solid var(--border)",
992
+ background: "var(--bg-panel)",
993
+ color: "var(--text-muted)",
994
+ cursor: "pointer",
995
+ fontSize: 12,
996
+ alignItems: "center",
997
+ gap: 6,
998
+ flexShrink: 0,
999
+ }}
1000
+ >
1001
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
1002
+ <line x1="19" y1="12" x2="5" y2="12" />
1003
+ <polyline points="12 19 5 12 12 5" />
1004
+ </svg>
1005
+ <span>Chat</span>
1006
+ </button>
1007
+ <div style={{ flex: 1, overflow: "hidden" }}>
1008
+ <TabBar
1009
+ tabs={rightTabs}
1010
+ activeTabId={activeRightTabId}
1011
+ onSelectTab={setActiveRightTabId}
1012
+ onCloseTab={handleCloseFileTab}
1013
+ />
1014
+ </div>
1015
+
1016
+ </div>
1017
+
1018
+ {/* File content */}
1019
+ <div style={{ flex: 1, overflow: "hidden" }}>
1020
+ {activeRightTabId === "report" ? (
1021
+ <ReportPanel
1022
+ sessionId={selectedSession?.id ?? null}
1023
+ cwd={selectedSession?.cwd ?? activeCwd ?? undefined}
1024
+ refreshKey={reportRefreshKey}
1025
+ onOpenFile={handleOpenFile}
1026
+ />
1027
+ ) : activeFileTab?.filePath ? (
1028
+ activeFileTab.filePath.startsWith("__") ? (
1029
+ <GlobalFileEditor filePath={activeFileTab.filePath} />
1030
+ ) : (
1031
+ <FileViewer filePath={activeFileTab.filePath} cwd={activeCwd ?? undefined} onOpenFile={handleOpenFile} />
1032
+ )
1033
+ ) : (
1034
+ <div style={{ height: "100%", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--text-dim)", fontSize: 12 }}>
1035
+ No file open
1036
+ </div>
1037
+ )}
1038
+ </div>
1039
+ </div>
1040
+ </div>
1041
+ {settingsOpen && (
1042
+ <SettingsDialog
1043
+ cwd={settingsCwd}
1044
+ generativeUI={settings.generativeUI}
1045
+ initialTab={settingsInitialTab}
1046
+ onGenerativeUIToggle={handleGenerativeUIToggle}
1047
+ onModelsChanged={() => setModelsRefreshKey((k) => k + 1)}
1048
+ onProjectSelect={handleProjectSelect}
1049
+ onProjectsChanged={() => setRefreshKey((k) => k + 1)}
1050
+ onClose={() => {
1051
+ setSettingsOpen(false);
1052
+ setModelsRefreshKey((k) => k + 1);
1053
+ }}
1054
+ />
1055
+ )}
1056
+ </>
1057
+ );
1058
+ }