@seqyuan/annodex 0.1.12 → 0.1.14

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 (478) hide show
  1. package/.next/BUILD_ID +1 -0
  2. package/.next/app-path-routes-manifest.json +39 -0
  3. package/.next/build-manifest.json +20 -0
  4. package/.next/diagnostics/build-diagnostics.json +6 -0
  5. package/.next/diagnostics/framework.json +1 -0
  6. package/.next/export-marker.json +6 -0
  7. package/.next/images-manifest.json +68 -0
  8. package/.next/next-minimal-server.js.nft.json +1 -0
  9. package/.next/next-server.js.nft.json +1 -0
  10. package/.next/package.json +1 -0
  11. package/.next/prerender-manifest.json +109 -0
  12. package/.next/react-loadable-manifest.json +2320 -0
  13. package/.next/required-server-files.js +343 -0
  14. package/.next/required-server-files.json +343 -0
  15. package/.next/routes-manifest.json +286 -0
  16. package/.next/server/app/_global-error/page.js +32 -0
  17. package/.next/server/app/_global-error/page.js.nft.json +1 -0
  18. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -0
  19. package/.next/server/app/_global-error.html +1 -0
  20. package/.next/server/app/_global-error.meta +16 -0
  21. package/.next/server/app/_global-error.rsc +14 -0
  22. package/.next/server/app/_global-error.segments/_full.segment.rsc +14 -0
  23. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +5 -0
  24. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +5 -0
  25. package/.next/server/app/_global-error.segments/_head.segment.rsc +5 -0
  26. package/.next/server/app/_global-error.segments/_index.segment.rsc +5 -0
  27. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -0
  28. package/.next/server/app/_not-found/page.js +2 -0
  29. package/.next/server/app/_not-found/page.js.nft.json +1 -0
  30. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
  31. package/.next/server/app/_not-found.html +1 -0
  32. package/.next/server/app/_not-found.meta +16 -0
  33. package/.next/server/app/_not-found.rsc +18 -0
  34. package/.next/server/app/_not-found.segments/_full.segment.rsc +18 -0
  35. package/.next/server/app/_not-found.segments/_head.segment.rsc +6 -0
  36. package/.next/server/app/_not-found.segments/_index.segment.rsc +5 -0
  37. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -0
  38. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +5 -0
  39. package/.next/server/app/_not-found.segments/_tree.segment.rsc +4 -0
  40. package/.next/server/app/api/agent/[id]/events/route.js +3 -0
  41. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -0
  42. package/.next/server/app/api/agent/[id]/events/route_client-reference-manifest.js +1 -0
  43. package/.next/server/app/api/agent/[id]/route.js +1 -0
  44. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -0
  45. package/.next/server/app/api/agent/[id]/route_client-reference-manifest.js +1 -0
  46. package/.next/server/app/api/agent/new/route.js +1 -0
  47. package/.next/server/app/api/agent/new/route.js.nft.json +1 -0
  48. package/.next/server/app/api/agent/new/route_client-reference-manifest.js +1 -0
  49. package/.next/server/app/api/auth/all-providers/route.js +1 -0
  50. package/.next/server/app/api/auth/all-providers/route.js.nft.json +1 -0
  51. package/.next/server/app/api/auth/all-providers/route_client-reference-manifest.js +1 -0
  52. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -0
  53. package/.next/server/app/api/auth/api-key/[provider]/route.js.nft.json +1 -0
  54. package/.next/server/app/api/auth/api-key/[provider]/route_client-reference-manifest.js +1 -0
  55. package/.next/server/app/api/auth/login/[provider]/route.js +1 -0
  56. package/.next/server/app/api/auth/login/[provider]/route.js.nft.json +1 -0
  57. package/.next/server/app/api/auth/login/[provider]/route_client-reference-manifest.js +1 -0
  58. package/.next/server/app/api/auth/login/route.js +1 -0
  59. package/.next/server/app/api/auth/login/route.js.nft.json +1 -0
  60. package/.next/server/app/api/auth/login/route_client-reference-manifest.js +1 -0
  61. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -0
  62. package/.next/server/app/api/auth/logout/[provider]/route.js.nft.json +1 -0
  63. package/.next/server/app/api/auth/logout/[provider]/route_client-reference-manifest.js +1 -0
  64. package/.next/server/app/api/auth/providers/route.js +1 -0
  65. package/.next/server/app/api/auth/providers/route.js.nft.json +1 -0
  66. package/.next/server/app/api/auth/providers/route_client-reference-manifest.js +1 -0
  67. package/.next/server/app/api/auth/status/route.js +1 -0
  68. package/.next/server/app/api/auth/status/route.js.nft.json +1 -0
  69. package/.next/server/app/api/auth/status/route_client-reference-manifest.js +1 -0
  70. package/.next/server/app/api/default-cwd/route.js +1 -0
  71. package/.next/server/app/api/default-cwd/route.js.nft.json +1 -0
  72. package/.next/server/app/api/default-cwd/route_client-reference-manifest.js +1 -0
  73. package/.next/server/app/api/files/[...path]/route.js +4 -0
  74. package/.next/server/app/api/files/[...path]/route.js.nft.json +1 -0
  75. package/.next/server/app/api/files/[...path]/route_client-reference-manifest.js +1 -0
  76. package/.next/server/app/api/harness/route.js +1 -0
  77. package/.next/server/app/api/harness/route.js.nft.json +1 -0
  78. package/.next/server/app/api/harness/route_client-reference-manifest.js +1 -0
  79. package/.next/server/app/api/home/route.js +1 -0
  80. package/.next/server/app/api/home/route.js.nft.json +1 -0
  81. package/.next/server/app/api/home/route_client-reference-manifest.js +1 -0
  82. package/.next/server/app/api/internal/runtime/route.js +1 -0
  83. package/.next/server/app/api/internal/runtime/route.js.nft.json +1 -0
  84. package/.next/server/app/api/internal/runtime/route_client-reference-manifest.js +1 -0
  85. package/.next/server/app/api/models/route.js +1 -0
  86. package/.next/server/app/api/models/route.js.nft.json +1 -0
  87. package/.next/server/app/api/models/route_client-reference-manifest.js +1 -0
  88. package/.next/server/app/api/models-config/discover/route.js +1 -0
  89. package/.next/server/app/api/models-config/discover/route.js.nft.json +1 -0
  90. package/.next/server/app/api/models-config/discover/route_client-reference-manifest.js +1 -0
  91. package/.next/server/app/api/models-config/route.js +1 -0
  92. package/.next/server/app/api/models-config/route.js.nft.json +1 -0
  93. package/.next/server/app/api/models-config/route_client-reference-manifest.js +1 -0
  94. package/.next/server/app/api/models-config/test/route.js +1 -0
  95. package/.next/server/app/api/models-config/test/route.js.nft.json +1 -0
  96. package/.next/server/app/api/models-config/test/route_client-reference-manifest.js +1 -0
  97. package/.next/server/app/api/projects/browse/route.js +1 -0
  98. package/.next/server/app/api/projects/browse/route.js.nft.json +1 -0
  99. package/.next/server/app/api/projects/browse/route_client-reference-manifest.js +1 -0
  100. package/.next/server/app/api/projects/route.js +1 -0
  101. package/.next/server/app/api/projects/route.js.nft.json +1 -0
  102. package/.next/server/app/api/projects/route_client-reference-manifest.js +1 -0
  103. package/.next/server/app/api/reports/[id]/route.js +10 -0
  104. package/.next/server/app/api/reports/[id]/route.js.nft.json +1 -0
  105. package/.next/server/app/api/reports/[id]/route_client-reference-manifest.js +1 -0
  106. package/.next/server/app/api/search/route.js +1 -0
  107. package/.next/server/app/api/search/route.js.nft.json +1 -0
  108. package/.next/server/app/api/search/route_client-reference-manifest.js +1 -0
  109. package/.next/server/app/api/sessions/[id]/context/route.js +1 -0
  110. package/.next/server/app/api/sessions/[id]/context/route.js.nft.json +1 -0
  111. package/.next/server/app/api/sessions/[id]/context/route_client-reference-manifest.js +1 -0
  112. package/.next/server/app/api/sessions/[id]/route.js +1 -0
  113. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -0
  114. package/.next/server/app/api/sessions/[id]/route_client-reference-manifest.js +1 -0
  115. package/.next/server/app/api/sessions/new/route.js +1 -0
  116. package/.next/server/app/api/sessions/new/route.js.nft.json +1 -0
  117. package/.next/server/app/api/sessions/new/route_client-reference-manifest.js +1 -0
  118. package/.next/server/app/api/sessions/route.js +1 -0
  119. package/.next/server/app/api/sessions/route.js.nft.json +1 -0
  120. package/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -0
  121. package/.next/server/app/api/settings/route.js +1 -0
  122. package/.next/server/app/api/settings/route.js.nft.json +1 -0
  123. package/.next/server/app/api/settings/route_client-reference-manifest.js +1 -0
  124. package/.next/server/app/api/skills/install/route.js +5 -0
  125. package/.next/server/app/api/skills/install/route.js.nft.json +1 -0
  126. package/.next/server/app/api/skills/install/route_client-reference-manifest.js +1 -0
  127. package/.next/server/app/api/skills/route.js +6 -0
  128. package/.next/server/app/api/skills/route.js.nft.json +1 -0
  129. package/.next/server/app/api/skills/route_client-reference-manifest.js +1 -0
  130. package/.next/server/app/api/skills/search/route.js +1 -0
  131. package/.next/server/app/api/skills/search/route.js.nft.json +1 -0
  132. package/.next/server/app/api/skills/search/route_client-reference-manifest.js +1 -0
  133. package/.next/server/app/api/soul/route.js +1 -0
  134. package/.next/server/app/api/soul/route.js.nft.json +1 -0
  135. package/.next/server/app/api/soul/route_client-reference-manifest.js +1 -0
  136. package/.next/server/app/api/version/route.js +1 -0
  137. package/.next/server/app/api/version/route.js.nft.json +1 -0
  138. package/.next/server/app/api/version/route_client-reference-manifest.js +1 -0
  139. package/.next/server/app/index.html +1 -0
  140. package/.next/server/app/index.meta +14 -0
  141. package/.next/server/app/index.rsc +17 -0
  142. package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -0
  143. package/.next/server/app/index.segments/_full.segment.rsc +17 -0
  144. package/.next/server/app/index.segments/_head.segment.rsc +6 -0
  145. package/.next/server/app/index.segments/_index.segment.rsc +5 -0
  146. package/.next/server/app/index.segments/_tree.segment.rsc +4 -0
  147. package/.next/server/app/login/page.js +2 -0
  148. package/.next/server/app/login/page.js.nft.json +1 -0
  149. package/.next/server/app/login/page_client-reference-manifest.js +1 -0
  150. package/.next/server/app/login.html +1 -0
  151. package/.next/server/app/login.meta +15 -0
  152. package/.next/server/app/login.rsc +22 -0
  153. package/.next/server/app/login.segments/_full.segment.rsc +22 -0
  154. package/.next/server/app/login.segments/_head.segment.rsc +6 -0
  155. package/.next/server/app/login.segments/_index.segment.rsc +5 -0
  156. package/.next/server/app/login.segments/_tree.segment.rsc +4 -0
  157. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +9 -0
  158. package/.next/server/app/login.segments/login.segment.rsc +5 -0
  159. package/.next/server/app/page.js +261 -0
  160. package/.next/server/app/page.js.nft.json +1 -0
  161. package/.next/server/app/page_client-reference-manifest.js +1 -0
  162. package/.next/server/app-paths-manifest.json +39 -0
  163. package/.next/server/chunks/1048.js +1 -0
  164. package/.next/server/chunks/1367.js +77 -0
  165. package/.next/server/chunks/1381.js +1 -0
  166. package/.next/server/chunks/165.js +1 -0
  167. package/.next/server/chunks/1681.js +215 -0
  168. package/.next/server/chunks/1688.js +45 -0
  169. package/.next/server/chunks/1703.js +79 -0
  170. package/.next/server/chunks/1712.js +43 -0
  171. package/.next/server/chunks/1813.js +1 -0
  172. package/.next/server/chunks/2325.js +80 -0
  173. package/.next/server/chunks/258.js +1 -0
  174. package/.next/server/chunks/2671.js +287 -0
  175. package/.next/server/chunks/2778.js +1 -0
  176. package/.next/server/chunks/2943.js +1 -0
  177. package/.next/server/chunks/3031.js +226 -0
  178. package/.next/server/chunks/3181.js +1 -0
  179. package/.next/server/chunks/3493.js +1 -0
  180. package/.next/server/chunks/3672.js +1 -0
  181. package/.next/server/chunks/3701.js +104 -0
  182. package/.next/server/chunks/4013.js +1 -0
  183. package/.next/server/chunks/402.js +2 -0
  184. package/.next/server/chunks/4035.js +80 -0
  185. package/.next/server/chunks/4248.js +153 -0
  186. package/.next/server/chunks/4367.js +1 -0
  187. package/.next/server/chunks/4406.js +141 -0
  188. package/.next/server/chunks/4741.js +18 -0
  189. package/.next/server/chunks/4768.js +1 -0
  190. package/.next/server/chunks/4858.js +148 -0
  191. package/.next/server/chunks/4980.js +1 -0
  192. package/.next/server/chunks/5155.js +5 -0
  193. package/.next/server/chunks/5293.js +166 -0
  194. package/.next/server/chunks/5399.js +8 -0
  195. package/.next/server/chunks/5409.js +1 -0
  196. package/.next/server/chunks/5797.js +93 -0
  197. package/.next/server/chunks/5851.js +36 -0
  198. package/.next/server/chunks/6206.js +1 -0
  199. package/.next/server/chunks/6296.js +1 -0
  200. package/.next/server/chunks/63.js +45 -0
  201. package/.next/server/chunks/6346.js +1 -0
  202. package/.next/server/chunks/6406.js +23 -0
  203. package/.next/server/chunks/642.js +1 -0
  204. package/.next/server/chunks/6429.js +50 -0
  205. package/.next/server/chunks/6729.js +64 -0
  206. package/.next/server/chunks/6907.js +115 -0
  207. package/.next/server/chunks/6980.js +1 -0
  208. package/.next/server/chunks/7073.js +24 -0
  209. package/.next/server/chunks/7233.js +24 -0
  210. package/.next/server/chunks/7307.js +1 -0
  211. package/.next/server/chunks/7362.js +9 -0
  212. package/.next/server/chunks/7567.js +29 -0
  213. package/.next/server/chunks/7765.js +1 -0
  214. package/.next/server/chunks/7890.js +1 -0
  215. package/.next/server/chunks/8065.js +1 -0
  216. package/.next/server/chunks/8238.js +34 -0
  217. package/.next/server/chunks/8276.js +1 -0
  218. package/.next/server/chunks/8336.js +1 -0
  219. package/.next/server/chunks/8477.js +3 -0
  220. package/.next/server/chunks/8490.js +1 -0
  221. package/.next/server/chunks/8916.js +1 -0
  222. package/.next/server/chunks/9280.js +252 -0
  223. package/.next/server/chunks/9315.js +1 -0
  224. package/.next/server/chunks/9537.js +90 -0
  225. package/.next/server/chunks/966.js +1 -0
  226. package/.next/server/chunks/9818.js +21 -0
  227. package/.next/server/chunks/static/media/pdf.worker.min.c476e1a0.mjs +6 -0
  228. package/.next/server/functions-config-manifest.json +16 -0
  229. package/.next/server/interception-route-rewrite-manifest.js +1 -0
  230. package/.next/server/middleware-build-manifest.js +1 -0
  231. package/.next/server/middleware-manifest.json +6 -0
  232. package/.next/server/middleware-react-loadable-manifest.js +1 -0
  233. package/.next/server/middleware.js +18 -0
  234. package/.next/server/middleware.js.nft.json +1 -0
  235. package/.next/server/next-font-manifest.js +1 -0
  236. package/.next/server/next-font-manifest.json +1 -0
  237. package/.next/server/pages/404.html +1 -0
  238. package/.next/server/pages/500.html +1 -0
  239. package/.next/server/pages-manifest.json +4 -0
  240. package/.next/server/prefetch-hints.json +1 -0
  241. package/.next/server/server-reference-manifest.js +1 -0
  242. package/.next/server/server-reference-manifest.json +1 -0
  243. package/.next/server/webpack-runtime.js +1 -0
  244. package/.next/static/9tDQsVwah2dRfj3LNFKBR/_buildManifest.js +1 -0
  245. package/.next/static/9tDQsVwah2dRfj3LNFKBR/_ssgManifest.js +1 -0
  246. package/.next/static/chunks/0b9a0da7.9075af772487e743.js +62 -0
  247. package/.next/static/chunks/1413.922d232de90c0c41.js +115 -0
  248. package/.next/static/chunks/1643.467a526a1f24f54d.js +24 -0
  249. package/.next/static/chunks/1852.5543122f11aa7fed.js +1 -0
  250. package/.next/static/chunks/1960.b1e26436d7a5f586.js +1 -0
  251. package/.next/static/chunks/2170a4aa.4213bb2183c9cdf9.js +1 -0
  252. package/.next/static/chunks/2274.6cd173f80a1405a2.js +21 -0
  253. package/.next/static/chunks/2419.347fdfe3c170854d.js +166 -0
  254. package/.next/static/chunks/2619.9aac8983f30c7c8a.js +1 -0
  255. package/.next/static/chunks/2623.d20fabd8e18197c6.js +287 -0
  256. package/.next/static/chunks/2729.f5365061a849d659.js +34 -0
  257. package/.next/static/chunks/2821.934bcf60fbdc28c6.js +1 -0
  258. package/.next/static/chunks/2918becc.abff2ece1de37bc1.js +153 -0
  259. package/.next/static/chunks/2947.114e51cb06d1c01a.js +23 -0
  260. package/.next/static/chunks/3079.4c511fa1144e3adf.js +79 -0
  261. package/.next/static/chunks/3274.208ca44844cd7d95.js +148 -0
  262. package/.next/static/chunks/3308.465a94263d04bfea.js +73 -0
  263. package/.next/static/chunks/3325.e4bfe1ca657f3b5b.js +80 -0
  264. package/.next/static/chunks/3506.2a7eaa08b9f55337.js +90 -0
  265. package/.next/static/chunks/363642f4-043c1475ab9af70e.js +1 -0
  266. package/.next/static/chunks/3794-123fdf632563f469.js +32 -0
  267. package/.next/static/chunks/3837.a755ccfe6f9c1c1c.js +5 -0
  268. package/.next/static/chunks/394.91597771688df6d0.js +1 -0
  269. package/.next/static/chunks/3997.1009c06025691712.js +1 -0
  270. package/.next/static/chunks/4453.91a357dc43c21745.js +1 -0
  271. package/.next/static/chunks/4491.44fdf20580ac72bd.js +24 -0
  272. package/.next/static/chunks/4829.cf1d50e43e6d9db5.js +1 -0
  273. package/.next/static/chunks/498.fe1d9da9ecad6c36.js +1 -0
  274. package/.next/static/chunks/4bd1b696-e356ca5ba0218e27.js +1 -0
  275. package/.next/static/chunks/5019.b5a1a2b8daf17525.js +1 -0
  276. package/.next/static/chunks/5034.8f16c3fa3ce75411.js +1 -0
  277. package/.next/static/chunks/5074.d16651da01ec4e02.js +1 -0
  278. package/.next/static/chunks/51fb665c.0950e1b79671348d.js +45 -0
  279. package/.next/static/chunks/532.5956ed631aff722b.js +9 -0
  280. package/.next/static/chunks/5326.69460442bdcd6cd3.js +1 -0
  281. package/.next/static/chunks/5403.ff110bf5bf600758.js +64 -0
  282. package/.next/static/chunks/547.902a733488cfe3f7.js +77 -0
  283. package/.next/static/chunks/5567.540d7fc108ad6ee5.js +215 -0
  284. package/.next/static/chunks/5590.ef62922166d308b4.js +1 -0
  285. package/.next/static/chunks/5690.9d6eb1edb1399995.js +1 -0
  286. package/.next/static/chunks/5749.25faee4a1e55b854.js +226 -0
  287. package/.next/static/chunks/58bb9007.1ccb6bba34b4c635.js +80 -0
  288. package/.next/static/chunks/6121.f3f43f1896ea0cd9.js +1 -0
  289. package/.next/static/chunks/6600.583c88eef37aa524.js +1 -0
  290. package/.next/static/chunks/6696.a41aec266e657d54.js +141 -0
  291. package/.next/static/chunks/6922.42148793782d2fe7.js +1 -0
  292. package/.next/static/chunks/7006.e191611ffc2b9528.js +43 -0
  293. package/.next/static/chunks/7343.9fbb58204d8ac681.js +1 -0
  294. package/.next/static/chunks/73972abe.25a4cffa03b2bcef.js +119 -0
  295. package/.next/static/chunks/7547.58bda8a2aabba0d4.js +93 -0
  296. package/.next/static/chunks/7648.4ae2f183b4db0353.js +1 -0
  297. package/.next/static/chunks/7874.8db6929b94cdf697.js +1 -0
  298. package/.next/static/chunks/7959.1f20a35df316216a.js +104 -0
  299. package/.next/static/chunks/83.85d62d7fc9850b75.js +29 -0
  300. package/.next/static/chunks/8436.cab94b59cca0a8ff.js +1 -0
  301. package/.next/static/chunks/8451.ff6ff72b57dc52e1.js +1 -0
  302. package/.next/static/chunks/8489.45f22859734f514f.js +36 -0
  303. package/.next/static/chunks/8568.f85d8b36fc9a9037.js +1 -0
  304. package/.next/static/chunks/8771-3e14b6810486df1f.js +1 -0
  305. package/.next/static/chunks/8863.be51033a67436277.js +1 -0
  306. package/.next/static/chunks/90542734.dc1a2723e4f6affb.js +1 -0
  307. package/.next/static/chunks/9500.1488aec06ee78127.js +1 -0
  308. package/.next/static/chunks/9633.155548b5fca6e580.js +1 -0
  309. package/.next/static/chunks/9779.673004a62d70e36a.js +1 -0
  310. package/.next/static/chunks/app/_global-error/page-cc518af6b1ffb191.js +1 -0
  311. package/.next/static/chunks/app/_not-found/page-c72daab99269beff.js +1 -0
  312. package/.next/static/chunks/app/api/agent/[id]/events/route-cc518af6b1ffb191.js +1 -0
  313. package/.next/static/chunks/app/api/agent/[id]/route-cc518af6b1ffb191.js +1 -0
  314. package/.next/static/chunks/app/api/agent/new/route-cc518af6b1ffb191.js +1 -0
  315. package/.next/static/chunks/app/api/auth/all-providers/route-cc518af6b1ffb191.js +1 -0
  316. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-cc518af6b1ffb191.js +1 -0
  317. package/.next/static/chunks/app/api/auth/login/[provider]/route-cc518af6b1ffb191.js +1 -0
  318. package/.next/static/chunks/app/api/auth/login/route-cc518af6b1ffb191.js +1 -0
  319. package/.next/static/chunks/app/api/auth/logout/[provider]/route-cc518af6b1ffb191.js +1 -0
  320. package/.next/static/chunks/app/api/auth/providers/route-cc518af6b1ffb191.js +1 -0
  321. package/.next/static/chunks/app/api/auth/status/route-cc518af6b1ffb191.js +1 -0
  322. package/.next/static/chunks/app/api/default-cwd/route-cc518af6b1ffb191.js +1 -0
  323. package/.next/static/chunks/app/api/files/[...path]/route-cc518af6b1ffb191.js +1 -0
  324. package/.next/static/chunks/app/api/harness/route-cc518af6b1ffb191.js +1 -0
  325. package/.next/static/chunks/app/api/home/route-cc518af6b1ffb191.js +1 -0
  326. package/.next/static/chunks/app/api/internal/runtime/route-cc518af6b1ffb191.js +1 -0
  327. package/.next/static/chunks/app/api/models/route-cc518af6b1ffb191.js +1 -0
  328. package/.next/static/chunks/app/api/models-config/discover/route-cc518af6b1ffb191.js +1 -0
  329. package/.next/static/chunks/app/api/models-config/route-cc518af6b1ffb191.js +1 -0
  330. package/.next/static/chunks/app/api/models-config/test/route-cc518af6b1ffb191.js +1 -0
  331. package/.next/static/chunks/app/api/projects/browse/route-cc518af6b1ffb191.js +1 -0
  332. package/.next/static/chunks/app/api/projects/route-cc518af6b1ffb191.js +1 -0
  333. package/.next/static/chunks/app/api/reports/[id]/route-cc518af6b1ffb191.js +1 -0
  334. package/.next/static/chunks/app/api/search/route-cc518af6b1ffb191.js +1 -0
  335. package/.next/static/chunks/app/api/sessions/[id]/context/route-cc518af6b1ffb191.js +1 -0
  336. package/.next/static/chunks/app/api/sessions/[id]/route-cc518af6b1ffb191.js +1 -0
  337. package/.next/static/chunks/app/api/sessions/new/route-cc518af6b1ffb191.js +1 -0
  338. package/.next/static/chunks/app/api/sessions/route-cc518af6b1ffb191.js +1 -0
  339. package/.next/static/chunks/app/api/settings/route-cc518af6b1ffb191.js +1 -0
  340. package/.next/static/chunks/app/api/skills/install/route-cc518af6b1ffb191.js +1 -0
  341. package/.next/static/chunks/app/api/skills/route-cc518af6b1ffb191.js +1 -0
  342. package/.next/static/chunks/app/api/skills/search/route-cc518af6b1ffb191.js +1 -0
  343. package/.next/static/chunks/app/api/soul/route-cc518af6b1ffb191.js +1 -0
  344. package/.next/static/chunks/app/api/version/route-cc518af6b1ffb191.js +1 -0
  345. package/.next/static/chunks/app/layout-be148b7ae915b22a.js +1 -0
  346. package/.next/static/chunks/app/login/page-ebf0e6de99062783.js +1 -0
  347. package/.next/static/chunks/app/page-a26fde41f0bbe84c.js +260 -0
  348. package/.next/static/chunks/d3ac728e.7964f816a1ca64e5.js +1 -0
  349. package/.next/static/chunks/framework-711ef29bc66f648c.js +1 -0
  350. package/.next/static/chunks/main-app-45a0f19af99d61b6.js +1 -0
  351. package/.next/static/chunks/main-f74964b7ae52493e.js +5 -0
  352. package/.next/static/chunks/next/dist/client/components/builtin/app-error-cc518af6b1ffb191.js +1 -0
  353. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cc518af6b1ffb191.js +1 -0
  354. package/.next/static/chunks/next/dist/client/components/builtin/global-error-9bfa08b9491621f2.js +1 -0
  355. package/.next/static/chunks/next/dist/client/components/builtin/not-found-cc518af6b1ffb191.js +1 -0
  356. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cc518af6b1ffb191.js +1 -0
  357. package/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  358. package/.next/static/chunks/webpack-fcf4a889ecbd753c.js +1 -0
  359. package/.next/static/css/45029451a1d7255d.css +3 -0
  360. package/.next/static/media/15605e25b523335c-s.woff2 +0 -0
  361. package/.next/static/media/1a3dce5cfb5f7760-s.woff2 +0 -0
  362. package/.next/static/media/1cdd02902f937a18-s.woff2 +0 -0
  363. package/.next/static/media/4c4b3b30b6bcb2be-s.woff2 +0 -0
  364. package/.next/static/media/641a7b8a5800ee0e-s.woff2 +0 -0
  365. package/.next/static/media/7deddc85b7ffd1dc-s.p.woff2 +0 -0
  366. package/.next/static/media/ec14413c594b3356-s.p.woff2 +0 -0
  367. package/.next/static/media/pdf.worker.min.29aaf158.mjs +6 -0
  368. package/package.json +21 -21
  369. package/app/api/agent/[id]/events/route.ts +0 -94
  370. package/app/api/agent/[id]/route.ts +0 -83
  371. package/app/api/agent/new/route.ts +0 -53
  372. package/app/api/auth/all-providers/route.ts +0 -21
  373. package/app/api/auth/api-key/[provider]/route.ts +0 -7
  374. package/app/api/auth/login/[provider]/route.ts +0 -7
  375. package/app/api/auth/login/route.ts +0 -22
  376. package/app/api/auth/logout/[provider]/route.ts +0 -7
  377. package/app/api/auth/providers/route.ts +0 -15
  378. package/app/api/auth/status/route.ts +0 -6
  379. package/app/api/default-cwd/route.ts +0 -22
  380. package/app/api/files/[...path]/route.ts +0 -621
  381. package/app/api/harness/route.ts +0 -47
  382. package/app/api/home/route.ts +0 -6
  383. package/app/api/internal/runtime/route.ts +0 -26
  384. package/app/api/models/route.ts +0 -67
  385. package/app/api/models-config/discover/route.ts +0 -42
  386. package/app/api/models-config/route.ts +0 -152
  387. package/app/api/models-config/test/route.ts +0 -154
  388. package/app/api/projects/browse/route.ts +0 -51
  389. package/app/api/projects/route.ts +0 -83
  390. package/app/api/reports/[id]/route.ts +0 -108
  391. package/app/api/search/route.ts +0 -122
  392. package/app/api/sessions/[id]/context/route.ts +0 -23
  393. package/app/api/sessions/[id]/route.ts +0 -124
  394. package/app/api/sessions/new/route.ts +0 -5
  395. package/app/api/sessions/route.ts +0 -16
  396. package/app/api/settings/route.ts +0 -51
  397. package/app/api/skills/install/route.ts +0 -249
  398. package/app/api/skills/route.ts +0 -161
  399. package/app/api/skills/search/route.ts +0 -121
  400. package/app/api/soul/route.ts +0 -47
  401. package/app/api/version/route.ts +0 -55
  402. package/app/globals.css +0 -736
  403. package/app/layout.tsx +0 -40
  404. package/app/login/page.tsx +0 -133
  405. package/app/page.tsx +0 -10
  406. package/components/AppShell.tsx +0 -1058
  407. package/components/ChatInput.tsx +0 -1103
  408. package/components/ChatMinimap.tsx +0 -381
  409. package/components/ChatWindow.tsx +0 -576
  410. package/components/CodeMirrorEditor.tsx +0 -137
  411. package/components/ConversationSearch.tsx +0 -369
  412. package/components/DataTableViewer.tsx +0 -248
  413. package/components/FileExplorer.tsx +0 -758
  414. package/components/FileIcons.tsx +0 -241
  415. package/components/FileViewer.tsx +0 -1273
  416. package/components/GlobalFileEditor.tsx +0 -98
  417. package/components/MarkdownRenderer.tsx +0 -331
  418. package/components/MermaidDiagram.tsx +0 -80
  419. package/components/MessageView.tsx +0 -1141
  420. package/components/ModelsConfig.tsx +0 -1991
  421. package/components/ProjectContext.tsx +0 -252
  422. package/components/ProjectFolderPicker.tsx +0 -202
  423. package/components/ProjectsConfig.tsx +0 -288
  424. package/components/ProviderIcons.tsx +0 -91
  425. package/components/ReportPanel.tsx +0 -237
  426. package/components/ResizeHandle.tsx +0 -105
  427. package/components/SessionSidebar.tsx +0 -1464
  428. package/components/SettingsDialog.tsx +0 -287
  429. package/components/SkillsConfig.tsx +0 -1093
  430. package/components/SubagentPanel.tsx +0 -191
  431. package/components/TabBar.tsx +0 -115
  432. package/components/ToolPanel.tsx +0 -131
  433. package/components/WidgetRenderer.tsx +0 -505
  434. package/components/viewers/DocumentToolbar.tsx +0 -78
  435. package/components/viewers/DocxViewer.tsx +0 -97
  436. package/components/viewers/PdfViewer.tsx +0 -206
  437. package/components/viewers/PptxViewer.tsx +0 -240
  438. package/components/viewers/XlsxViewer.tsx +0 -143
  439. package/hooks/useAgentSession.ts +0 -710
  440. package/hooks/useAudio.ts +0 -50
  441. package/hooks/useDragDrop.ts +0 -52
  442. package/hooks/useResizable.ts +0 -60
  443. package/hooks/useTheme.ts +0 -85
  444. package/lib/agent-client.ts +0 -39
  445. package/lib/annodex-config.ts +0 -556
  446. package/lib/auth-token.ts +0 -74
  447. package/lib/auth.ts +0 -90
  448. package/lib/brand.ts +0 -5
  449. package/lib/code-theme.ts +0 -32
  450. package/lib/codex-compat-proxy.ts +0 -1603
  451. package/lib/codex-home.ts +0 -6
  452. package/lib/codex-server.ts +0 -796
  453. package/lib/codex-session.ts +0 -590
  454. package/lib/codex-usage.ts +0 -213
  455. package/lib/file-paths.ts +0 -34
  456. package/lib/model-discovery.ts +0 -379
  457. package/lib/normalize.ts +0 -30
  458. package/lib/npx.ts +0 -87
  459. package/lib/pi-types.ts +0 -49
  460. package/lib/projects.ts +0 -269
  461. package/lib/provider-api.ts +0 -88
  462. package/lib/report-prompt.ts +0 -61
  463. package/lib/report-store.ts +0 -597
  464. package/lib/report-update-parser.ts +0 -66
  465. package/lib/rpc-manager.ts +0 -668
  466. package/lib/runtime-state.ts +0 -117
  467. package/lib/session-reader.ts +0 -903
  468. package/lib/session-runtime.ts +0 -105
  469. package/lib/subagent-progress.ts +0 -279
  470. package/lib/types.ts +0 -241
  471. package/lib/widget-export.ts +0 -318
  472. package/lib/widget-guidelines.ts +0 -288
  473. package/lib/widget-prompt.ts +0 -76
  474. package/lib/widget-utils.ts +0 -523
  475. package/postcss.config.mjs +0 -8
  476. package/proxy.ts +0 -64
  477. package/scripts/postinstall.cjs +0 -25
  478. package/tsconfig.json +0 -41
@@ -1,1464 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useState, useCallback, useRef, useMemo } from "react";
4
- import type { ProjectInfo, SessionInfo } from "@/lib/types";
5
- import { FileExplorer } from "./FileExplorer";
6
- import { ConversationSearch } from "./ConversationSearch";
7
- import { ProjectFolderPicker } from "./ProjectFolderPicker";
8
- import { APP_NAME } from "@/lib/brand";
9
-
10
- interface Props {
11
- selectedSessionId: string | null;
12
- onSelectSession: (session: SessionInfo, isRestore?: boolean) => void;
13
- onNewSession?: (sessionId: string, cwd: string) => void;
14
- initialSessionId?: string | null;
15
- onInitialRestoreDone?: () => void;
16
- refreshKey?: number;
17
- onSessionDeleted?: (sessionId: string) => void;
18
- selectedCwd?: string | null;
19
- onCwdChange?: (cwd: string | null) => void;
20
- onOpenFile?: (filePath: string, fileName: string) => void;
21
- explorerRefreshKey?: number;
22
- onAtMention?: (relativePath: string) => void;
23
- }
24
-
25
- const SESSION_TRUNCATE_LIMIT = 10;
26
-
27
- async function getResponseError(label: string, res: Response): Promise<string> {
28
- const fallback = `${label}: HTTP ${res.status}`;
29
- try {
30
- const data = await res.json() as { error?: unknown };
31
- return typeof data.error === "string" && data.error ? `${label}: ${data.error}` : fallback;
32
- } catch {
33
- return fallback;
34
- }
35
- }
36
-
37
- function formatRelativeTime(dateStr: string): string {
38
- const date = new Date(dateStr);
39
- const now = new Date();
40
- const diff = now.getTime() - date.getTime();
41
- const mins = Math.floor(diff / 60000);
42
- const hours = Math.floor(diff / 3600000);
43
- const days = Math.floor(diff / 86400000);
44
- if (mins < 1) return "just now";
45
- if (mins < 60) return `${mins}m ago`;
46
- if (hours < 24) return `${hours}h ago`;
47
- if (days < 7) return `${days}d ago`;
48
- return date.toLocaleDateString();
49
- }
50
-
51
- function shortenCwd(cwd: string, homeDir?: string): string {
52
- const path = (homeDir && cwd.startsWith(homeDir)) ? "~" + cwd.slice(homeDir.length) : cwd;
53
- const sep = path.includes("/") ? "/" : "\\";
54
- const parts = path.split(sep).filter(Boolean);
55
- if (parts.length <= 2) return path;
56
- return "…/" + parts.slice(-2).join(sep);
57
- }
58
-
59
- function projectNameFromCwd(cwd: string, homeDir?: string): string {
60
- if (!cwd) return "No Project";
61
- const normalized = cwd.replace(/[\\/]+$/, "");
62
- const sep = normalized.includes("/") ? "/" : "\\";
63
- const name = normalized.split(sep).filter(Boolean).pop();
64
- return name || shortenCwd(cwd, homeDir);
65
- }
66
-
67
- function getProjectDisplayName(projects: ProjectInfo[], cwd: string | null, homeDir?: string): string {
68
- if (!cwd) return "Open project...";
69
- return projects.find((project) => project.cwd === cwd)?.displayName || projectNameFromCwd(cwd, homeDir);
70
- }
71
-
72
- function projectLastAccessed(project: ProjectInfo): string {
73
- return project.lastAccessed ?? project.created ?? "";
74
- }
75
-
76
- interface SessionTreeNode {
77
- session: SessionInfo;
78
- children: SessionTreeNode[];
79
- }
80
-
81
- function buildSessionTree(sessions: SessionInfo[]): SessionTreeNode[] {
82
- const byId = new Map<string, SessionTreeNode>();
83
- for (const s of sessions) {
84
- byId.set(s.id, { session: s, children: [] });
85
- }
86
-
87
- // Build a map of parentSessionId chains so we can resolve missing ancestors
88
- const parentOf = new Map<string, string>();
89
- for (const s of sessions) {
90
- if (s.parentSessionId) parentOf.set(s.id, s.parentSessionId);
91
- }
92
-
93
- // Walk up the parentSessionId chain to find the nearest ancestor that exists in byId
94
- function resolveAncestor(id: string): string | null {
95
- let cur = parentOf.get(id);
96
- const visited = new Set<string>();
97
- while (cur) {
98
- if (visited.has(cur)) return null; // cycle guard
99
- visited.add(cur);
100
- if (byId.has(cur)) return cur;
101
- cur = parentOf.get(cur);
102
- }
103
- return null;
104
- }
105
-
106
- const roots: SessionTreeNode[] = [];
107
- for (const node of byId.values()) {
108
- const ancestor = resolveAncestor(node.session.id);
109
- if (ancestor) {
110
- byId.get(ancestor)!.children.push(node);
111
- } else {
112
- roots.push(node);
113
- }
114
- }
115
-
116
- // Sort each level by modified desc
117
- const sort = (nodes: SessionTreeNode[]) => {
118
- nodes.sort((a, b) => b.session.modified.localeCompare(a.session.modified));
119
- nodes.forEach((n) => sort(n.children));
120
- };
121
- sort(roots);
122
- return roots;
123
- }
124
-
125
- function containsSession(node: SessionTreeNode, sessionId: string | null): boolean {
126
- if (!sessionId) return false;
127
- if (node.session.id === sessionId) return true;
128
- return node.children.some((child) => containsSession(child, sessionId));
129
- }
130
-
131
- const SCRAMBLE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
132
-
133
- function useScramble(target: string, running: boolean): string {
134
- const [display, setDisplay] = useState(target);
135
- const frameRef = useRef<number | null>(null);
136
- const iterRef = useRef(0);
137
-
138
- useEffect(() => {
139
- if (!running) {
140
- setDisplay(target);
141
- return;
142
- }
143
- iterRef.current = 0;
144
- const totalFrames = target.length * 4;
145
-
146
- const step = () => {
147
- iterRef.current += 1;
148
- const progress = iterRef.current / totalFrames;
149
- const resolved = Math.floor(progress * target.length);
150
-
151
- setDisplay(
152
- target
153
- .split("")
154
- .map((char, i) => {
155
- if (char === " ") return " ";
156
- if (i < resolved) return char;
157
- return SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)];
158
- })
159
- .join("")
160
- );
161
-
162
- if (iterRef.current < totalFrames) {
163
- frameRef.current = requestAnimationFrame(step);
164
- } else {
165
- setDisplay(target);
166
- }
167
- };
168
-
169
- frameRef.current = requestAnimationFrame(step);
170
- return () => { if (frameRef.current) cancelAnimationFrame(frameRef.current); };
171
- }, [target, running]);
172
-
173
- return display;
174
- }
175
-
176
- function AnnodexTitle() {
177
- const [showVersion, setShowVersion] = useState(false);
178
- const [scrambling, setScrambling] = useState(false);
179
- const revertTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
180
-
181
- const target = showVersion ? `v${process.env.NEXT_PUBLIC_APP_VERSION ?? "0.0.0"}` : APP_NAME;
182
- const display = useScramble(target, scrambling);
183
-
184
- const triggerScramble = useCallback((toVersion: boolean) => {
185
- setShowVersion(toVersion);
186
- setScrambling(true);
187
- setTimeout(() => setScrambling(false), (toVersion ? 6 : 8) * 4 * (1000 / 60) + 100);
188
- }, []);
189
-
190
- const handleClick = useCallback(() => {
191
- if (revertTimerRef.current) clearTimeout(revertTimerRef.current);
192
-
193
- const next = !showVersion;
194
- triggerScramble(next);
195
-
196
- if (next) {
197
- revertTimerRef.current = setTimeout(() => triggerScramble(false), 3000);
198
- }
199
- }, [showVersion, triggerScramble]);
200
-
201
- useEffect(() => () => { if (revertTimerRef.current) clearTimeout(revertTimerRef.current); }, []);
202
-
203
- return (
204
- <button
205
- onClick={handleClick}
206
- style={{
207
- background: "none", border: "none", padding: 0, cursor: "default",
208
- fontWeight: 700, fontSize: 15, letterSpacing: "-0.01em",
209
- color: showVersion ? "var(--accent)" : "var(--text)",
210
- fontFamily: "var(--font-mono)",
211
- minWidth: "9ch",
212
- }}
213
- >
214
- {display}
215
- </button>
216
- );
217
- }
218
-
219
- export function SessionSidebar({ selectedSessionId, onSelectSession, onNewSession, initialSessionId, onInitialRestoreDone, refreshKey, onSessionDeleted, selectedCwd: selectedCwdProp, onCwdChange, onOpenFile, explorerRefreshKey, onAtMention }: Props) {
220
- const [allSessions, setAllSessions] = useState<SessionInfo[]>([]);
221
- const [projects, setProjects] = useState<ProjectInfo[]>([]);
222
- const [loading, setLoading] = useState(true);
223
- const [isRefreshing, setIsRefreshing] = useState(false);
224
- const [error, setError] = useState<string | null>(null);
225
- const [selectedCwd, setSelectedCwd] = useState<string | null>(null);
226
- const [homeDir, setHomeDir] = useState<string>("");
227
- const [dropdownOpen, setDropdownOpen] = useState(false);
228
- const [customPathError, setCustomPathError] = useState<string | null>(null);
229
- const [folderPickerOpen, setFolderPickerOpen] = useState(false);
230
- const [conversationSearchOpen, setConversationSearchOpen] = useState(false);
231
- const [sessionsExpanded, setSessionsExpanded] = useState(false);
232
- const selectingSessionRef = useRef(false);
233
- const projectSelectSyncRef = useRef(false);
234
- const dropdownRef = useRef<HTMLDivElement>(null);
235
- const restoredRef = useRef(false);
236
- const [projectOpen, setProjectOpen] = useState(true);
237
- const [chatsOpen, setChatsOpen] = useState(true);
238
- const [explorerOpen, setExplorerOpen] = useState(true);
239
- const [explorerKey, setExplorerKey] = useState(0);
240
- const [explorerFileFilter, setExplorerFileFilter] = useState("");
241
- const loadProjects = useCallback(async (showLoading = false) => {
242
- try {
243
- if (showLoading) setLoading(true);
244
- const projectsRes = await fetch("/api/projects?sessionStats=1");
245
- if (!projectsRes.ok) throw new Error(await getResponseError("projects", projectsRes));
246
- const projectsData = await projectsRes.json() as { projects: ProjectInfo[] };
247
- setProjects(projectsData.projects);
248
- setError(null);
249
- // no-op — refresh signal handled by re-render
250
- } catch (e) {
251
- setError(String(e));
252
- } finally {
253
- if (showLoading) setLoading(false);
254
- }
255
- }, []);
256
-
257
- const loadSessionsForCwd = useCallback(async (cwd: string | null, showLoading = false) => {
258
- if (!cwd) {
259
- setAllSessions([]);
260
- return;
261
- }
262
- try {
263
- if (showLoading) setLoading(true);
264
- const sessionsRes = await fetch(`/api/sessions?cwd=${encodeURIComponent(cwd)}`);
265
- if (!sessionsRes.ok) throw new Error(await getResponseError("sessions", sessionsRes));
266
- const sessionsData = await sessionsRes.json() as { sessions: SessionInfo[] };
267
- setAllSessions(sessionsData.sessions);
268
- setError(null);
269
- } catch (e) {
270
- setError(String(e));
271
- } finally {
272
- if (showLoading) setLoading(false);
273
- }
274
- }, []);
275
-
276
- const initialLoadDone = useRef(false);
277
- useEffect(() => {
278
- const isFirst = !initialLoadDone.current;
279
- initialLoadDone.current = true;
280
- loadProjects(isFirst);
281
- }, [loadProjects, refreshKey]);
282
-
283
- useEffect(() => {
284
- if (explorerRefreshKey !== undefined) setExplorerKey((k) => k + 1);
285
- }, [explorerRefreshKey]);
286
-
287
- useEffect(() => {
288
- fetch("/api/home").then((r) => r.json()).then((d: { home?: string }) => {
289
- if (d.home) setHomeDir(d.home);
290
- }).catch(() => {});
291
- }, []);
292
-
293
- useEffect(() => {
294
- if (selectingSessionRef.current) {
295
- selectingSessionRef.current = false;
296
- return;
297
- }
298
- if (projectSelectSyncRef.current) {
299
- projectSelectSyncRef.current = false;
300
- return;
301
- }
302
- onCwdChange?.(selectedCwd);
303
- }, [selectedCwd, onCwdChange]);
304
-
305
- const registerProject = useCallback(async (cwd: string, create = false): Promise<boolean> => {
306
- try {
307
- const res = await fetch("/api/projects", {
308
- method: "POST",
309
- headers: { "Content-Type": "application/json" },
310
- body: JSON.stringify({ cwd, create }),
311
- });
312
- const data = await res.json() as { error?: string };
313
- if (!res.ok || data.error) {
314
- setCustomPathError(data.error ?? `HTTP ${res.status}`);
315
- return false;
316
- }
317
- void loadProjects(false);
318
- return true;
319
- } catch (error) {
320
- setCustomPathError(error instanceof Error ? error.message : String(error));
321
- return false;
322
- }
323
- }, [loadProjects]);
324
-
325
- const touchProject = useCallback((cwd: string) => {
326
- fetch("/api/projects", {
327
- method: "PATCH",
328
- headers: { "Content-Type": "application/json" },
329
- body: JSON.stringify({ cwd, touch: true }),
330
- }).catch(() => {});
331
- }, []);
332
-
333
- useEffect(() => {
334
- if (!initialSessionId || restoredRef.current) return;
335
- let cancelled = false;
336
- fetch("/api/sessions")
337
- .then((res) => res.ok ? res.json() : null)
338
- .then((data: { sessions?: SessionInfo[] } | null) => {
339
- if (cancelled || restoredRef.current) return;
340
- const target = data?.sessions?.find((session) => session.id === initialSessionId);
341
- if (!target) return;
342
- restoredRef.current = true;
343
- setSelectedCwd(target.cwd);
344
- touchProject(target.cwd);
345
- onSelectSession(target, true);
346
- })
347
- .finally(() => {
348
- if (!cancelled && !restoredRef.current) onInitialRestoreDone?.();
349
- });
350
- return () => { cancelled = true; };
351
- }, [initialSessionId, onInitialRestoreDone, onSelectSession, touchProject]);
352
-
353
- const chooseProject = useCallback((cwd: string) => {
354
- projectSelectSyncRef.current = true;
355
- setSelectedCwd(cwd);
356
- setSessionsExpanded(false);
357
- setExplorerOpen(true);
358
- setExplorerFileFilter("");
359
- setExplorerKey((k) => k + 1);
360
- onCwdChange?.(cwd);
361
- touchProject(cwd);
362
- }, [onCwdChange, touchProject]);
363
-
364
- // Auto-select cwd and restore session from URL on first load
365
- useEffect(() => {
366
- if (allSessions.length === 0 && projects.length === 0) return;
367
-
368
- if (selectedCwd === null) {
369
- if (initialSessionId && !restoredRef.current) return;
370
- const firstProject = projects[0]?.cwd;
371
- if (firstProject) {
372
- setSelectedCwd(firstProject);
373
- touchProject(firstProject);
374
- }
375
- }
376
- }, [allSessions, projects, selectedCwd, initialSessionId, onSelectSession, onInitialRestoreDone, touchProject]);
377
-
378
- const handleDefaultCwd = useCallback(async () => {
379
- try {
380
- const res = await fetch("/api/default-cwd", { method: "POST" });
381
- const data = await res.json() as { cwd?: string; error?: string };
382
- if (data.cwd) {
383
- await registerProject(data.cwd, false);
384
- chooseProject(data.cwd);
385
- setDropdownOpen(false);
386
- }
387
- } catch {
388
- // ignore
389
- }
390
- }, [chooseProject, registerProject]);
391
-
392
- const handleFolderPickerSelect = useCallback(async (path: string) => {
393
- const ok = await registerProject(path, false);
394
- if (!ok) return;
395
- chooseProject(path);
396
- setFolderPickerOpen(false);
397
- setCustomPathError(null);
398
- setDropdownOpen(false);
399
- }, [chooseProject, registerProject]);
400
-
401
- // Close dropdown on outside click
402
- useEffect(() => {
403
- const handler = (e: MouseEvent) => {
404
- if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
405
- setDropdownOpen(false);
406
- setCustomPathError(null);
407
- }
408
- };
409
- document.addEventListener("mousedown", handler);
410
- return () => document.removeEventListener("mousedown", handler);
411
- }, []);
412
-
413
- const effectiveCwd = selectedCwdProp ?? selectedCwd;
414
-
415
- const handleRefreshProject = useCallback(async () => {
416
- setIsRefreshing(true);
417
- try {
418
- await Promise.all([
419
- loadProjects(false),
420
- loadSessionsForCwd(effectiveCwd, false),
421
- ]);
422
- } finally {
423
- setIsRefreshing(false);
424
- }
425
- }, [effectiveCwd, loadProjects, loadSessionsForCwd]);
426
-
427
- useEffect(() => {
428
- void loadSessionsForCwd(effectiveCwd, false);
429
- }, [effectiveCwd, loadSessionsForCwd, refreshKey]);
430
-
431
- const createSessionInCwd = useCallback((cwd: string | null) => {
432
- if (!cwd) {
433
- setDropdownOpen(true);
434
- return;
435
- }
436
- // Generate a temporary UUID client-side — no backend call needed.
437
- // Pi will be spawned lazily when the user sends the first message.
438
- const tempId = typeof crypto.randomUUID === "function"
439
- ? crypto.randomUUID()
440
- : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}-${Math.random().toString(36).slice(2)}`;
441
- onNewSession?.(tempId, cwd);
442
- }, [onNewSession]);
443
-
444
- const handleNewSession = useCallback(() => {
445
- createSessionInCwd(effectiveCwd);
446
- }, [createSessionInCwd, effectiveCwd]);
447
-
448
- const sortedProjects = useMemo(() => {
449
- const withActive = effectiveCwd && !projects.some((project) => project.cwd === effectiveCwd)
450
- ? [{
451
- cwd: effectiveCwd,
452
- displayName: projectNameFromCwd(effectiveCwd, homeDir),
453
- chatCount: allSessions.filter((session) => session.cwd === effectiveCwd).length,
454
- lastAccessed: null,
455
- created: null,
456
- exists: true,
457
- source: "manual" as const,
458
- }]
459
- : [];
460
- return [...withActive, ...projects].sort((a, b) => {
461
- if (effectiveCwd && a.cwd === effectiveCwd) return -1;
462
- if (effectiveCwd && b.cwd === effectiveCwd) return 1;
463
- const aTime = projectLastAccessed(a);
464
- const bTime = projectLastAccessed(b);
465
- if (aTime !== bTime) return bTime.localeCompare(aTime);
466
- return a.displayName.localeCompare(b.displayName);
467
- });
468
- }, [allSessions, effectiveCwd, homeDir, projects]);
469
-
470
- const currentProjectSessions = useMemo(
471
- () => allSessions
472
- .filter((session) => session.cwd === effectiveCwd)
473
- .sort((a, b) => b.modified.localeCompare(a.modified)),
474
- [allSessions, effectiveCwd],
475
- );
476
-
477
- const currentProject = effectiveCwd
478
- ? sortedProjects.find((project) => project.cwd === effectiveCwd)
479
- : undefined;
480
- const currentProjectName = getProjectDisplayName(sortedProjects, effectiveCwd, homeDir);
481
- const currentChatCount = currentProject?.chatCount ?? currentProjectSessions.length;
482
- const currentSessionTree = useMemo(() => buildSessionTree(currentProjectSessions), [currentProjectSessions]);
483
- const flatRootCount = currentSessionTree.length;
484
- let visibleSessionTree = currentSessionTree;
485
- if (flatRootCount > SESSION_TRUNCATE_LIMIT && !sessionsExpanded) {
486
- visibleSessionTree = currentSessionTree.slice(0, SESSION_TRUNCATE_LIMIT);
487
- const activeRoot = currentSessionTree.find((node) => containsSession(node, selectedSessionId));
488
- if (activeRoot && !visibleSessionTree.includes(activeRoot)) visibleSessionTree = [...visibleSessionTree, activeRoot];
489
- }
490
- const hiddenSessionCount = flatRootCount - visibleSessionTree.length;
491
-
492
- const handleSelectSessionFromList = useCallback((session: SessionInfo, isRestore = false) => {
493
- if (session.cwd !== effectiveCwd) {
494
- selectingSessionRef.current = true;
495
- setSelectedCwd(session.cwd);
496
- }
497
- touchProject(session.cwd);
498
- onSelectSession(session, isRestore);
499
- }, [onSelectSession, effectiveCwd, touchProject]);
500
-
501
- return (
502
- <div style={{ display: "flex", flexDirection: "column", height: "100%", overflow: "hidden" }}>
503
- {/* Header */}
504
- <div
505
- style={{
506
- padding: "12px 10px 10px",
507
- borderBottom: "1px solid var(--border)",
508
- flexShrink: 0,
509
- }}
510
- >
511
- <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 8 }}>
512
- <AnnodexTitle />
513
- <div style={{ display: "flex", gap: 6 }}>
514
- <button
515
- onClick={handleNewSession}
516
- style={{
517
- display: "flex", alignItems: "center", justifyContent: "center", gap: 5,
518
- background: "var(--bg-hover)",
519
- border: "1px solid var(--border)",
520
- color: "var(--text-muted)",
521
- cursor: "pointer",
522
- height: 32,
523
- paddingLeft: 10,
524
- paddingRight: 12,
525
- borderRadius: 7,
526
- fontSize: 12,
527
- fontWeight: 500,
528
- letterSpacing: "-0.01em",
529
- flexShrink: 0,
530
- transition: "background 0.12s, color 0.12s, border-color 0.12s",
531
- }}
532
- title={effectiveCwd ? `New session in ${effectiveCwd}` : "Select a project first"}
533
- onMouseEnter={(e) => {
534
- e.currentTarget.style.background = "var(--bg-selected)";
535
- e.currentTarget.style.color = "var(--accent)";
536
- e.currentTarget.style.borderColor = "rgba(37,99,235,0.35)";
537
- }}
538
- onMouseLeave={(e) => {
539
- e.currentTarget.style.background = "var(--bg-hover)";
540
- e.currentTarget.style.color = "var(--text-muted)";
541
- e.currentTarget.style.borderColor = "var(--border)";
542
- }}
543
- >
544
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round">
545
- <line x1="6" y1="1" x2="6" y2="11" />
546
- <line x1="1" y1="6" x2="11" y2="6" />
547
- </svg>
548
- New
549
- </button>
550
- <button
551
- onClick={() => setConversationSearchOpen(true)}
552
- style={{
553
- display: "flex", alignItems: "center", justifyContent: "center",
554
- background: "var(--bg-hover)",
555
- border: "1px solid var(--border)",
556
- color: "var(--text-muted)",
557
- cursor: "pointer",
558
- width: 32, height: 32,
559
- borderRadius: 7,
560
- padding: 0,
561
- flexShrink: 0,
562
- transition: "background 0.12s, color 0.12s, border-color 0.12s",
563
- }}
564
- title="Search conversations"
565
- onMouseEnter={(e) => {
566
- e.currentTarget.style.background = "var(--bg-selected)";
567
- e.currentTarget.style.color = "var(--accent)";
568
- e.currentTarget.style.borderColor = "rgba(37,99,235,0.35)";
569
- }}
570
- onMouseLeave={(e) => {
571
- e.currentTarget.style.background = "var(--bg-hover)";
572
- e.currentTarget.style.color = "var(--text-muted)";
573
- e.currentTarget.style.borderColor = "var(--border)";
574
- }}
575
- >
576
- <svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
577
- <circle cx="7" cy="7" r="4.4" />
578
- <path d="M10.4 10.4 13.2 13.2" />
579
- </svg>
580
- </button>
581
- </div>
582
- </div>
583
-
584
- {/* Project picker */}
585
- <div ref={dropdownRef} style={{ position: "relative" }}>
586
- <div style={{ marginBottom: projectOpen ? 5 : 0, display: "flex", alignItems: "center", justifyContent: "space-between", color: "var(--text-dim)" }}>
587
- <button
588
- type="button"
589
- onClick={() => {
590
- setProjectOpen((value) => {
591
- const next = !value;
592
- if (!next) setDropdownOpen(false);
593
- return next;
594
- });
595
- void handleRefreshProject();
596
- }}
597
- title={projectOpen ? "Collapse project" : "Expand project"}
598
- aria-expanded={projectOpen}
599
- style={{
600
- display: "flex",
601
- alignItems: "center",
602
- gap: 6,
603
- padding: "4px 0",
604
- background: "none",
605
- border: "none",
606
- color: "var(--text-dim)",
607
- cursor: "pointer",
608
- fontSize: 10,
609
- fontWeight: 600,
610
- letterSpacing: "0.05em",
611
- textTransform: "uppercase",
612
- textAlign: "left",
613
- }}
614
- >
615
- <svg
616
- width="9"
617
- height="9"
618
- viewBox="0 0 10 10"
619
- fill="none"
620
- stroke="currentColor"
621
- strokeWidth="1.8"
622
- strokeLinecap="round"
623
- strokeLinejoin="round"
624
- style={{ transform: projectOpen ? "rotate(90deg)" : "none", transition: "transform 0.15s", flexShrink: 0 }}
625
- >
626
- <polyline points="3 2 7 5 3 8" />
627
- </svg>
628
- Project
629
- </button>
630
- <button
631
- type="button"
632
- onClick={(event) => {
633
- event.stopPropagation();
634
- void handleRefreshProject();
635
- }}
636
- disabled={isRefreshing}
637
- title="Refresh projects and chats"
638
- aria-label="Refresh projects and chats"
639
- style={{
640
- width: 22,
641
- height: 22,
642
- display: "flex",
643
- alignItems: "center",
644
- justifyContent: "center",
645
- padding: 0,
646
- border: "1px solid var(--border)",
647
- borderRadius: 5,
648
- background: "var(--bg-panel)",
649
- color: isRefreshing ? "var(--accent)" : "var(--text-dim)",
650
- cursor: isRefreshing ? "default" : "pointer",
651
- flexShrink: 0,
652
- transition: "color 0.12s, background 0.12s, border-color 0.12s",
653
- }}
654
- onMouseEnter={(event) => {
655
- if (isRefreshing) return;
656
- event.currentTarget.style.color = "var(--text)";
657
- event.currentTarget.style.background = "var(--bg-hover)";
658
- }}
659
- onMouseLeave={(event) => {
660
- if (isRefreshing) return;
661
- event.currentTarget.style.color = "var(--text-dim)";
662
- event.currentTarget.style.background = "var(--bg-panel)";
663
- }}
664
- >
665
- <svg
666
- width="12"
667
- height="12"
668
- viewBox="0 0 24 24"
669
- fill="none"
670
- stroke="currentColor"
671
- strokeWidth="2"
672
- strokeLinecap="round"
673
- strokeLinejoin="round"
674
- style={{ animation: isRefreshing ? "spin 0.9s linear infinite" : undefined }}
675
- >
676
- <path d="M21 12a9 9 0 0 1-15.5 6.2" />
677
- <path d="M3 12A9 9 0 0 1 18.5 5.8" />
678
- <path d="M7 18H5.5v1.5" />
679
- <path d="M17 6h1.5V4.5" />
680
- </svg>
681
- </button>
682
- </div>
683
-
684
- {projectOpen && (
685
- <>
686
- <button
687
- onClick={() => setDropdownOpen((v) => !v)}
688
- style={{
689
- width: "100%",
690
- display: "flex",
691
- alignItems: "center",
692
- gap: 9,
693
- padding: "8px 10px",
694
- background: effectiveCwd ? "var(--bg-hover)" : "rgba(37,99,235,0.06)",
695
- border: effectiveCwd ? "1px solid var(--border)" : "1px solid rgba(37,99,235,0.4)",
696
- borderRadius: 7,
697
- cursor: "pointer",
698
- fontSize: 12,
699
- color: "var(--text)",
700
- textAlign: "left",
701
- transition: "border-color 0.15s, background 0.15s",
702
- }}
703
- >
704
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" style={{ color: effectiveCwd ? "var(--accent)" : "var(--text-dim)", flexShrink: 0 }}>
705
- <path d="M3 6.5A2.5 2.5 0 0 1 5.5 4H10l2 2.5h6.5A2.5 2.5 0 0 1 21 9v8.5A2.5 2.5 0 0 1 18.5 20h-13A2.5 2.5 0 0 1 3 17.5v-11Z" />
706
- </svg>
707
- <span style={{ flex: 1, minWidth: 0 }}>
708
- <span style={{ display: "block", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: 12, fontWeight: 650, color: effectiveCwd ? "var(--text)" : "var(--text-dim)" }}>
709
- {effectiveCwd ? currentProjectName : (initialSessionId && !restoredRef.current ? "" : "Open project...")}
710
- </span>
711
- {effectiveCwd && (
712
- <span style={{ display: "block", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--text-dim)" }} title={effectiveCwd}>
713
- {shortenCwd(effectiveCwd, homeDir)}
714
- </span>
715
- )}
716
- </span>
717
- {effectiveCwd && (
718
- <span style={{ flexShrink: 0, color: "var(--text-dim)", fontSize: 10, whiteSpace: "nowrap" }}>
719
- {currentChatCount} chats
720
- </span>
721
- )}
722
- <svg width="11" height="11" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" style={{ color: "var(--text-dim)", flexShrink: 0, transform: dropdownOpen ? "rotate(180deg)" : "none", transition: "transform 0.15s" }}>
723
- <polyline points="2 3.5 5 6.5 8 3.5" />
724
- </svg>
725
- </button>
726
-
727
- {dropdownOpen && (
728
- <div
729
- style={{
730
- position: "absolute",
731
- top: "calc(100% + 4px)",
732
- left: 0,
733
- right: 0,
734
- zIndex: 100,
735
- background: "var(--bg)",
736
- border: "1px solid var(--border)",
737
- borderRadius: 8,
738
- boxShadow: "0 6px 20px rgba(0,0,0,0.10)",
739
- overflow: "hidden",
740
- }}
741
- >
742
- <div style={{ maxHeight: 260, overflowY: "auto" }}>
743
- {sortedProjects.length === 0 && (
744
- <div style={{ padding: "10px 12px", color: "var(--text-muted)", fontSize: 12 }}>
745
- No projects yet
746
- </div>
747
- )}
748
- {sortedProjects.map((project) => {
749
- const active = project.cwd === effectiveCwd;
750
- const last = projectLastAccessed(project);
751
- return (
752
- <button
753
- key={project.cwd}
754
- onClick={() => {
755
- chooseProject(project.cwd);
756
- setCustomPathError(null);
757
- setDropdownOpen(false);
758
- }}
759
- style={{
760
- display: "flex",
761
- alignItems: "center",
762
- gap: 8,
763
- width: "100%",
764
- padding: "8px 10px",
765
- background: active ? "var(--bg-selected)" : "none",
766
- border: "none",
767
- borderBottom: "1px solid var(--border)",
768
- color: active ? "var(--text)" : "var(--text-muted)",
769
- cursor: "pointer",
770
- textAlign: "left",
771
- fontSize: 11,
772
- }}
773
- title={project.cwd}
774
- >
775
- {active ? (
776
- <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="var(--accent)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
777
- <polyline points="1.5 5 4 7.5 8.5 2.5" />
778
- </svg>
779
- ) : (
780
- <span style={{ width: 10, flexShrink: 0 }} />
781
- )}
782
- <span style={{ flex: 1, minWidth: 0 }}>
783
- <span style={{ display: "block", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: active ? "var(--text)" : "var(--text-muted)", fontWeight: active ? 650 : 500 }}>
784
- {project.displayName}
785
- </span>
786
- <span style={{ display: "block", marginTop: 2, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--text-dim)", fontSize: 10 }}>
787
- {project.chatCount} chats{last ? ` / ${formatRelativeTime(last)}` : ""}{!project.exists ? " / missing" : ""}
788
- </span>
789
- </span>
790
- </button>
791
- );
792
- })}
793
- </div>
794
-
795
- <div style={{ borderTop: sortedProjects.length > 0 ? "1px solid var(--border)" : "none" }}>
796
- <button
797
- onClick={(e) => { e.stopPropagation(); handleDefaultCwd(); }}
798
- style={{
799
- display: "flex",
800
- alignItems: "center",
801
- gap: 7,
802
- width: "100%",
803
- padding: "8px 10px",
804
- background: "none",
805
- border: "none",
806
- color: "var(--text-muted)",
807
- cursor: "pointer",
808
- textAlign: "left",
809
- fontSize: 11,
810
- }}
811
- >
812
- <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
813
- <path d="M1 3A1 1 0 0 1 2 2H4L5 3.5H8.5a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5h-7A.5.5 0 0 1 1 8V3Z" />
814
- </svg>
815
- <span>Use default directory</span>
816
- </button>
817
- <button
818
- onClick={(e) => {
819
- e.stopPropagation();
820
- setCustomPathError(null);
821
- setFolderPickerOpen(true);
822
- }}
823
- style={{
824
- display: "flex",
825
- alignItems: "center",
826
- gap: 7,
827
- width: "100%",
828
- padding: "8px 10px",
829
- background: "none",
830
- border: "none",
831
- color: "var(--text-muted)",
832
- cursor: "pointer",
833
- textAlign: "left",
834
- fontSize: 11,
835
- }}
836
- >
837
- <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
838
- <path d="M1 3A1 1 0 0 1 2 2h1.5L4.5 3H8a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V3Z" />
839
- <path d="M5 4.5v3M3.5 6h3" />
840
- </svg>
841
- <span>Select project path...</span>
842
- </button>
843
- {customPathError && (
844
- <div style={{ padding: "0 10px 8px 27px", color: "#f87171", fontSize: 10, lineHeight: 1.4 }}>
845
- {customPathError}
846
- </div>
847
- )}
848
- </div>
849
- </div>
850
- )}
851
- </>
852
- )}
853
- </div>
854
-
855
- </div>
856
-
857
- <ConversationSearch
858
- open={conversationSearchOpen}
859
- cwd={effectiveCwd}
860
- homeDir={homeDir}
861
- onClose={() => setConversationSearchOpen(false)}
862
- onSelectSession={(session) => {
863
- handleSelectSessionFromList(session);
864
- }}
865
- />
866
-
867
- {/* Chats section */}
868
- {effectiveCwd && (
869
- <div style={{ padding: "0 10px 4px", display: "flex", alignItems: "center", justifyContent: "space-between", color: "var(--text-dim)", flexShrink: 0 }}>
870
- <button
871
- type="button"
872
- onClick={() => setChatsOpen((v) => !v)}
873
- title={chatsOpen ? "Collapse chats" : "Expand chats"}
874
- aria-expanded={chatsOpen}
875
- style={{
876
- display: "flex", alignItems: "center", gap: 6,
877
- padding: "4px 0", background: "none", border: "none",
878
- color: "var(--text-dim)", cursor: "pointer",
879
- fontSize: 10, fontWeight: 600, letterSpacing: "0.05em",
880
- textTransform: "uppercase", textAlign: "left",
881
- }}
882
- >
883
- <svg
884
- width="9" height="9" viewBox="0 0 10 10" fill="none"
885
- stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"
886
- style={{ transform: chatsOpen ? "rotate(90deg)" : "none", transition: "transform 0.15s", flexShrink: 0 }}
887
- >
888
- <polyline points="3 2 7 5 3 8" />
889
- </svg>
890
- Chats
891
- </button>
892
- <span style={{ fontSize: 10, fontWeight: 500 }}>{currentChatCount} sessions</span>
893
- </div>
894
- )}
895
-
896
- {/* Current project sessions */}
897
- {chatsOpen && (
898
- <div style={{ flex: explorerOpen && effectiveCwd ? "1 1 0" : "1 1 auto", overflowY: "auto", padding: "6px 8px 8px", minHeight: 120 }}>
899
- {loading && (
900
- <div style={{ padding: "16px 14px", color: "var(--text-muted)", fontSize: 12 }}>
901
- Loading...
902
- </div>
903
- )}
904
- {error && (
905
- <div style={{ padding: "12px 14px", color: "#f87171", fontSize: 12 }}>
906
- {error}
907
- </div>
908
- )}
909
- {!loading && !error && !effectiveCwd && (
910
- <div style={{ padding: "16px 14px", color: "var(--text-muted)", fontSize: 12 }}>
911
- Open or create a project to start chatting.
912
- </div>
913
- )}
914
- {!loading && !error && effectiveCwd && (
915
- <>
916
- {currentProjectSessions.length === 0 ? (
917
- <button
918
- type="button"
919
- onClick={() => createSessionInCwd(effectiveCwd)}
920
- style={{
921
- width: "100%",
922
- padding: "12px",
923
- background: "transparent",
924
- border: "none",
925
- color: "var(--text-muted)",
926
- cursor: "pointer",
927
- textAlign: "left",
928
- fontSize: 12,
929
- }}
930
- onMouseEnter={(e) => { e.currentTarget.style.background = "var(--bg-hover)"; e.currentTarget.style.color = "var(--text)"; }}
931
- onMouseLeave={(e) => { e.currentTarget.style.background = "transparent"; e.currentTarget.style.color = "var(--text-muted)"; }}
932
- >
933
- Start a new session in this project
934
- </button>
935
- ) : (
936
- visibleSessionTree.map((node) => (
937
- <SessionTreeItem
938
- key={node.session.id}
939
- node={node}
940
- selectedSessionId={selectedSessionId}
941
- onSelectSession={handleSelectSessionFromList}
942
- onRenamed={() => loadSessionsForCwd(effectiveCwd, false)}
943
- onSessionDeleted={(id) => {
944
- onSessionDeleted?.(id);
945
- void loadSessionsForCwd(effectiveCwd, false);
946
- }}
947
- depth={0}
948
- />
949
- ))
950
- )}
951
- {(sessionsExpanded || hiddenSessionCount > 0) && flatRootCount > SESSION_TRUNCATE_LIMIT && (
952
- <button
953
- type="button"
954
- onClick={() => setSessionsExpanded((value) => !value)}
955
- style={{
956
- width: "100%",
957
- padding: "6px 10px",
958
- background: "transparent",
959
- border: "none",
960
- color: "var(--text-dim)",
961
- cursor: "pointer",
962
- fontSize: 11,
963
- textAlign: "center",
964
- }}
965
- >
966
- {sessionsExpanded ? "Show less" : `Show ${Math.max(0, hiddenSessionCount)} more`}
967
- </button>
968
- )}
969
- </>
970
- )}
971
- </div>
972
- )}
973
-
974
- {/* File Explorer section */}
975
- {effectiveCwd && (
976
- <div
977
- style={{
978
- borderTop: "1px solid var(--border)",
979
- display: "flex",
980
- flexDirection: "column",
981
- flex: explorerOpen ? "1 1 0" : "0 0 auto",
982
- minHeight: 0,
983
- overflow: "hidden",
984
- }}
985
- >
986
- <div style={{ display: "flex", alignItems: "center", flexShrink: 0, gap: 0, padding: "2px 4px" }}>
987
- <button
988
- onClick={() => {
989
- setExplorerOpen((v) => !v);
990
- setExplorerKey((k) => k + 1);
991
- }}
992
- style={{
993
- display: "flex",
994
- alignItems: "center",
995
- gap: 6,
996
- padding: "6px 6px 6px 6px",
997
- background: "none",
998
- border: "none",
999
- color: "var(--text-muted)",
1000
- cursor: "pointer",
1001
- fontSize: 11,
1002
- fontWeight: 600,
1003
- letterSpacing: "0.05em",
1004
- textTransform: "uppercase",
1005
- textAlign: "left",
1006
- flexShrink: 0,
1007
- }}
1008
- >
1009
- <svg
1010
- width="9" height="9" viewBox="0 0 10 10" fill="none"
1011
- stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"
1012
- style={{ transform: explorerOpen ? "rotate(90deg)" : "none", transition: "transform 0.15s", flexShrink: 0 }}
1013
- >
1014
- <polyline points="3 2 7 5 3 8" />
1015
- </svg>
1016
- Explorer
1017
- </button>
1018
- <div style={{ position: "relative", flex: 1, minWidth: 0 }}>
1019
- <svg
1020
- width="12"
1021
- height="12"
1022
- viewBox="0 0 16 16"
1023
- fill="none"
1024
- stroke="currentColor"
1025
- strokeWidth="1.7"
1026
- strokeLinecap="round"
1027
- strokeLinejoin="round"
1028
- style={{
1029
- position: "absolute",
1030
- left: 8,
1031
- top: "50%",
1032
- transform: "translateY(-50%)",
1033
- color: "var(--text-dim)",
1034
- pointerEvents: "none",
1035
- }}
1036
- >
1037
- <circle cx="7" cy="7" r="4.4" />
1038
- <path d="M10.4 10.4 13.2 13.2" />
1039
- </svg>
1040
- <input
1041
- value={explorerFileFilter}
1042
- onChange={(e) => setExplorerFileFilter(e.target.value)}
1043
- placeholder="Filter files..."
1044
- style={{
1045
- width: "100%",
1046
- height: 28,
1047
- boxSizing: "border-box",
1048
- padding: "0 26px 0 26px",
1049
- borderRadius: 6,
1050
- border: "1px solid var(--border)",
1051
- background: "var(--bg)",
1052
- color: "var(--text)",
1053
- fontSize: 12,
1054
- outline: "none",
1055
- }}
1056
- />
1057
- {explorerFileFilter && (
1058
- <button
1059
- type="button"
1060
- onClick={() => setExplorerFileFilter("")}
1061
- title="Clear filter"
1062
- style={{
1063
- position: "absolute",
1064
- right: 4,
1065
- top: "50%",
1066
- transform: "translateY(-50%)",
1067
- width: 20,
1068
- height: 20,
1069
- display: "flex",
1070
- alignItems: "center",
1071
- justifyContent: "center",
1072
- border: "none",
1073
- borderRadius: 4,
1074
- background: "transparent",
1075
- color: "var(--text-dim)",
1076
- cursor: "pointer",
1077
- padding: 0,
1078
- }}
1079
- >
1080
- <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round">
1081
- <line x1="2" y1="2" x2="8" y2="8" />
1082
- <line x1="8" y1="2" x2="2" y2="8" />
1083
- </svg>
1084
- </button>
1085
- )}
1086
- </div>
1087
- </div>
1088
- {explorerOpen && (
1089
- <div style={{ flex: 1, overflowY: "auto", overflowX: "hidden" }}>
1090
- <FileExplorer
1091
- cwd={effectiveCwd}
1092
- onOpenFile={onOpenFile ?? (() => {})}
1093
- refreshKey={explorerKey}
1094
- onAtMention={onAtMention}
1095
- searchQuery={explorerFileFilter}
1096
- onSearchChange={setExplorerFileFilter}
1097
- />
1098
- </div>
1099
- )}
1100
- </div>
1101
- )}
1102
- <ProjectFolderPicker
1103
- open={folderPickerOpen}
1104
- initialPath={effectiveCwd || homeDir || null}
1105
- onClose={() => setFolderPickerOpen(false)}
1106
- onSelect={(path) => void handleFolderPickerSelect(path)}
1107
- />
1108
- </div>
1109
- );
1110
- }
1111
-
1112
- function SessionTreeItem({
1113
- node,
1114
- selectedSessionId,
1115
- onSelectSession,
1116
- onRenamed,
1117
- onSessionDeleted,
1118
- depth,
1119
- }: {
1120
- node: SessionTreeNode;
1121
- selectedSessionId: string | null;
1122
- onSelectSession: (s: SessionInfo) => void;
1123
- onRenamed?: () => void;
1124
- onSessionDeleted?: (id: string) => void;
1125
- depth: number;
1126
- }) {
1127
- const [collapsed, setCollapsed] = useState(false);
1128
- const hasChildren = node.children.length > 0;
1129
-
1130
- return (
1131
- <div>
1132
- <div style={{ position: "relative" }}>
1133
- {/* Indent line for child sessions */}
1134
- {depth > 0 && (
1135
- <div style={{
1136
- position: "absolute",
1137
- left: depth * 12 + 6,
1138
- top: 0, bottom: 0,
1139
- width: 1,
1140
- background: "var(--border)",
1141
- pointerEvents: "none",
1142
- }} />
1143
- )}
1144
- <SessionItem
1145
- session={node.session}
1146
- isSelected={node.session.id === selectedSessionId}
1147
- onClick={() => onSelectSession(node.session)}
1148
- onRenamed={onRenamed}
1149
- onDeleted={(id) => onSessionDeleted?.(id)}
1150
- depth={depth}
1151
- hasChildren={hasChildren}
1152
- collapsed={collapsed}
1153
- onToggleCollapse={() => setCollapsed((v) => !v)}
1154
- />
1155
- </div>
1156
- {hasChildren && !collapsed && (
1157
- <div>
1158
- {node.children.map((child) => (
1159
- <SessionTreeItem
1160
- key={child.session.id}
1161
- node={child}
1162
- selectedSessionId={selectedSessionId}
1163
- onSelectSession={onSelectSession}
1164
- onRenamed={onRenamed}
1165
- onSessionDeleted={onSessionDeleted}
1166
- depth={depth + 1}
1167
- />
1168
- ))}
1169
- </div>
1170
- )}
1171
- </div>
1172
- );
1173
- }
1174
-
1175
- function SessionItem({
1176
- session,
1177
- isSelected,
1178
- onClick,
1179
- onRenamed,
1180
- onDeleted,
1181
- depth = 0,
1182
- hasChildren = false,
1183
- collapsed = false,
1184
- onToggleCollapse,
1185
- }: {
1186
- session: SessionInfo;
1187
- isSelected: boolean;
1188
- onClick: () => void;
1189
- onRenamed?: () => void;
1190
- onDeleted?: (id: string) => void;
1191
- depth?: number;
1192
- hasChildren?: boolean;
1193
- collapsed?: boolean;
1194
- onToggleCollapse?: () => void;
1195
- }) {
1196
- const [hovered, setHovered] = useState(false);
1197
- const [renaming, setRenaming] = useState(false);
1198
- const [renameValue, setRenameValue] = useState("");
1199
- const [confirmDelete, setConfirmDelete] = useState(false);
1200
- const [deleting, setDeleting] = useState(false);
1201
- const inputRef = useRef<HTMLInputElement>(null);
1202
-
1203
- const title = session.name || session.firstMessage.slice(0, 50) || session.id.slice(0, 12);
1204
-
1205
- const startRename = useCallback((e: React.MouseEvent) => {
1206
- e.stopPropagation();
1207
- setRenameValue(session.name ?? "");
1208
- setRenaming(true);
1209
- setTimeout(() => inputRef.current?.select(), 0);
1210
- }, [session.name]);
1211
-
1212
- const commitRename = useCallback(async () => {
1213
- const name = renameValue.trim();
1214
- setRenaming(false);
1215
- if (name === (session.name ?? "")) return;
1216
- try {
1217
- await fetch(`/api/sessions/${encodeURIComponent(session.id)}`, {
1218
- method: "PATCH",
1219
- headers: { "Content-Type": "application/json" },
1220
- body: JSON.stringify({ name }),
1221
- });
1222
- onRenamed?.();
1223
- } catch {
1224
- // ignore
1225
- }
1226
- }, [renameValue, session.id, session.name, onRenamed]);
1227
-
1228
- const handleDeleteClick = useCallback((e: React.MouseEvent) => {
1229
- e.stopPropagation();
1230
- setConfirmDelete(true);
1231
- }, []);
1232
-
1233
- const handleDeleteConfirm = useCallback(async (e: React.MouseEvent) => {
1234
- e.stopPropagation();
1235
- setConfirmDelete(false);
1236
- setDeleting(true);
1237
- try {
1238
- await fetch(`/api/sessions/${encodeURIComponent(session.id)}`, { method: "DELETE" });
1239
- onDeleted?.(session.id);
1240
- } catch {
1241
- setDeleting(false);
1242
- }
1243
- }, [session.id, onDeleted]);
1244
-
1245
- const handleDeleteCancel = useCallback((e: React.MouseEvent) => {
1246
- e.stopPropagation();
1247
- setConfirmDelete(false);
1248
- }, []);
1249
-
1250
- // Fixed-height outer wrapper — content swaps in place so the list never reflows
1251
- const ITEM_HEIGHT = 54;
1252
-
1253
- return (
1254
- <div
1255
- onClick={confirmDelete || renaming ? undefined : onClick}
1256
- onMouseEnter={() => setHovered(true)}
1257
- onMouseLeave={() => { setHovered(false); }}
1258
- style={{
1259
- height: ITEM_HEIGHT,
1260
- display: "flex",
1261
- alignItems: "center",
1262
- paddingLeft: depth > 0 ? depth * 12 + 14 : 14,
1263
- paddingRight: 8,
1264
- cursor: confirmDelete || renaming ? "default" : "pointer",
1265
- background: confirmDelete
1266
- ? "rgba(239,68,68,0.06)"
1267
- : isSelected ? "var(--bg-selected)" : hovered ? "var(--bg-hover)" : "transparent",
1268
- borderLeft: confirmDelete
1269
- ? "2px solid #ef4444"
1270
- : isSelected ? "2px solid var(--accent)" : "2px solid transparent",
1271
- transition: "background 0.1s",
1272
- opacity: deleting ? 0.5 : 1,
1273
- gap: 6,
1274
- overflow: "hidden",
1275
- }}
1276
- >
1277
- {confirmDelete ? (
1278
- /* ── Delete confirmation: same height, two flat buttons ── */
1279
- <>
1280
- <div style={{ flex: 1, minWidth: 0, fontSize: 12, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
1281
- Delete <span style={{ fontWeight: 600 }}>&ldquo;{title.slice(0, 22)}{title.length > 22 ? "…" : ""}&rdquo;</span>?
1282
- </div>
1283
- <div style={{ display: "flex", gap: 5, flexShrink: 0 }}>
1284
- <button
1285
- onClick={handleDeleteConfirm}
1286
- style={{
1287
- display: "flex", alignItems: "center", justifyContent: "center", gap: 4,
1288
- height: 30, padding: "0 11px",
1289
- background: "#ef4444", border: "none",
1290
- borderRadius: 6, color: "#fff",
1291
- cursor: "pointer", fontSize: 12, fontWeight: 600,
1292
- whiteSpace: "nowrap",
1293
- }}
1294
- >
1295
- <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1296
- <polyline points="3 6 5 6 21 6" />
1297
- <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
1298
- <path d="M10 11v6M14 11v6" />
1299
- <path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
1300
- </svg>
1301
- Delete
1302
- </button>
1303
- <button
1304
- onClick={handleDeleteCancel}
1305
- style={{
1306
- display: "flex", alignItems: "center", justifyContent: "center",
1307
- height: 30, padding: "0 11px",
1308
- background: "var(--bg)", border: "1px solid var(--border)",
1309
- borderRadius: 6, color: "var(--text-muted)",
1310
- cursor: "pointer", fontSize: 12, fontWeight: 500,
1311
- whiteSpace: "nowrap",
1312
- }}
1313
- >
1314
- Cancel
1315
- </button>
1316
- </div>
1317
- </>
1318
- ) : renaming ? (
1319
- /* ── Rename: input fills the same row ── */
1320
- <input
1321
- ref={inputRef}
1322
- value={renameValue}
1323
- onChange={(e) => setRenameValue(e.target.value)}
1324
- onBlur={commitRename}
1325
- onKeyDown={(e) => {
1326
- if (e.key === "Enter") commitRename();
1327
- if (e.key === "Escape") setRenaming(false);
1328
- }}
1329
- autoFocus
1330
- style={{
1331
- flex: 1,
1332
- fontSize: 12,
1333
- padding: "5px 8px",
1334
- border: "1px solid var(--accent)",
1335
- borderRadius: 5,
1336
- outline: "none",
1337
- background: "var(--bg)",
1338
- color: "var(--text)",
1339
- height: 30,
1340
- }}
1341
- />
1342
- ) : (
1343
- /* ── Normal view ── */
1344
- <>
1345
- {/* Fork indicator for child sessions */}
1346
- {depth > 0 && (
1347
- <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0 }}>
1348
- <line x1="6" y1="3" x2="6" y2="15" />
1349
- <circle cx="18" cy="6" r="3" />
1350
- <circle cx="6" cy="18" r="3" />
1351
- <path d="M18 9a9 9 0 0 1-9 9" />
1352
- </svg>
1353
- )}
1354
- <div style={{ flex: 1, minWidth: 0 }}>
1355
- <div
1356
- style={{
1357
- fontSize: 12,
1358
- fontWeight: isSelected ? 500 : 400,
1359
- lineHeight: 1.4,
1360
- overflow: "hidden",
1361
- textOverflow: "ellipsis",
1362
- whiteSpace: "nowrap",
1363
- color: "var(--text)",
1364
- }}
1365
- title={title}
1366
- >
1367
- {title}
1368
- </div>
1369
- <div style={{ marginTop: 2, display: "flex", gap: 8, color: "var(--text-dim)", fontSize: 11 }}>
1370
- <span title={session.modified}>{formatRelativeTime(session.modified)}</span>
1371
- <span>{session.messageCount} msgs</span>
1372
- {session.cwdExists === false && (
1373
- <span title={`Project path missing: ${session.cwd}`} style={{ color: "#f87171" }}>
1374
- missing path
1375
- </span>
1376
- )}
1377
- </div>
1378
- </div>
1379
-
1380
- {/* Collapse toggle — always visible when has children */}
1381
- {hasChildren && (
1382
- <button
1383
- onClick={(e) => { e.stopPropagation(); onToggleCollapse?.(); }}
1384
- title={collapsed ? "Expand forks" : "Collapse forks"}
1385
- style={{
1386
- display: "flex", alignItems: "center", justifyContent: "center",
1387
- width: 20, height: 20, padding: 0, flexShrink: 0,
1388
- background: "none", border: "none",
1389
- color: "var(--text-dim)", cursor: "pointer",
1390
- transform: collapsed ? "rotate(-90deg)" : "none",
1391
- transition: "transform 0.15s",
1392
- }}
1393
- >
1394
- <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
1395
- <polyline points="2 3.5 5 6.5 8 3.5" />
1396
- </svg>
1397
- </button>
1398
- )}
1399
-
1400
- {/* Action buttons — shown on hover */}
1401
- {hovered && (
1402
- <div style={{ display: "flex", gap: 4, flexShrink: 0 }}>
1403
- <button
1404
- onClick={startRename}
1405
- title="Rename"
1406
- style={{
1407
- display: "flex", alignItems: "center", justifyContent: "center",
1408
- width: 32, height: 32, padding: 0,
1409
- background: "var(--bg-hover)", border: "1px solid var(--border)",
1410
- borderRadius: 7, color: "var(--text-muted)",
1411
- cursor: "pointer", flexShrink: 0,
1412
- transition: "background 0.12s, color 0.12s, border-color 0.12s",
1413
- }}
1414
- onMouseEnter={(e) => {
1415
- e.currentTarget.style.background = "var(--bg-selected)";
1416
- e.currentTarget.style.color = "var(--accent)";
1417
- e.currentTarget.style.borderColor = "rgba(37,99,235,0.35)";
1418
- }}
1419
- onMouseLeave={(e) => {
1420
- e.currentTarget.style.background = "var(--bg-hover)";
1421
- e.currentTarget.style.color = "var(--text-muted)";
1422
- e.currentTarget.style.borderColor = "var(--border)";
1423
- }}
1424
- >
1425
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1426
- <path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z" />
1427
- </svg>
1428
- </button>
1429
- <button
1430
- onClick={handleDeleteClick}
1431
- title="Delete"
1432
- style={{
1433
- display: "flex", alignItems: "center", justifyContent: "center",
1434
- width: 32, height: 32, padding: 0,
1435
- background: "var(--bg-hover)", border: "1px solid var(--border)",
1436
- borderRadius: 7, color: "var(--text-muted)",
1437
- cursor: "pointer", flexShrink: 0,
1438
- transition: "background 0.12s, color 0.12s, border-color 0.12s",
1439
- }}
1440
- onMouseEnter={(e) => {
1441
- e.currentTarget.style.background = "rgba(239,68,68,0.08)";
1442
- e.currentTarget.style.color = "#ef4444";
1443
- e.currentTarget.style.borderColor = "rgba(239,68,68,0.35)";
1444
- }}
1445
- onMouseLeave={(e) => {
1446
- e.currentTarget.style.background = "var(--bg-hover)";
1447
- e.currentTarget.style.color = "var(--text-muted)";
1448
- e.currentTarget.style.borderColor = "var(--border)";
1449
- }}
1450
- >
1451
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
1452
- <polyline points="3 6 5 6 21 6" />
1453
- <path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
1454
- <path d="M10 11v6M14 11v6" />
1455
- <path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
1456
- </svg>
1457
- </button>
1458
- </div>
1459
- )}
1460
- </>
1461
- )}
1462
- </div>
1463
- );
1464
- }