@nextclaw/ui 0.15.4 → 0.15.6

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 (300) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/assets/abnfDiagram-VRR7QNED-BYpK0F5F.js +1 -0
  3. package/dist/assets/{aiden0z-pptx-renderer.es-Bd99vut2.js → aiden0z-pptx-renderer.es-D87ywFdC.js} +1 -1
  4. package/dist/assets/api-Dyb5rgh0.js +13 -0
  5. package/dist/assets/app-presenter-provider-7jV0nPiL.js +106 -0
  6. package/dist/assets/{appearance-settings-page-fLIJbx1C.js → appearance-settings-page-DFm3BbEb.js} +1 -1
  7. package/dist/assets/arc-DFELHWM2.js +1 -0
  8. package/dist/assets/architecture-TIHT7OUA-ClwVFq-K.js +1 -0
  9. package/dist/assets/architectureDiagram-ZJ3FMSHR-CvRhdovL.js +36 -0
  10. package/dist/assets/array-BqYdCIE6.js +1 -0
  11. package/dist/assets/blockDiagram-677ZJIJ3-DVYNkbR_.js +132 -0
  12. package/dist/assets/{book-open-DWUjS6aW.js → book-open-L6LCuqOr.js} +1 -1
  13. package/dist/assets/c4Diagram-LMCZKHZV-B1w-iwRe.js +10 -0
  14. package/dist/assets/channel-BEHOSFc5.js +1 -0
  15. package/dist/assets/{channels-list-page-CvCTJraa.js → channels-list-page-DOaPKNJA.js} +2 -2
  16. package/dist/assets/{chat-page-B42UUIFR.js → chat-page-Cw9uDhD_.js} +1 -1
  17. package/dist/assets/chunk-2Q5K7J3B-DrIhesSX.js +1 -0
  18. package/dist/assets/chunk-32BRIVSS-kG3lg0bC.js +1 -0
  19. package/dist/assets/chunk-52WLFC77-BLeB7iGk.js +10 -0
  20. package/dist/assets/chunk-5FCAYU7R-BQga1Rr7.js +1 -0
  21. package/dist/assets/chunk-5HE753X5-DU81hkcF.js +1 -0
  22. package/dist/assets/chunk-5JV3BV7I-C2bAx0m7.js +1 -0
  23. package/dist/assets/chunk-5TONJI2A-BExIIgWY.js +2 -0
  24. package/dist/assets/chunk-5VM5RSS4-C3XB05yh.js +15 -0
  25. package/dist/assets/chunk-7BUUIJ7U-JiyRtrGu.js +1 -0
  26. package/dist/assets/chunk-BIQX33UG-BZRV20pI.js +1 -0
  27. package/dist/assets/chunk-C7G6YPKG-AsyDSQ9j.js +1 -0
  28. package/dist/assets/chunk-CQNSW5MT-iN-fiXVA.js +1 -0
  29. package/dist/assets/chunk-CYSBUYHQ-DhC_TRzI.js +1 -0
  30. package/dist/assets/chunk-EMLP6XTP-QFLK8R6d.js +1 -0
  31. package/dist/assets/chunk-EX3LRPZG-DzUwhkCz.js +231 -0
  32. package/dist/assets/chunk-FWX5IMBZ-DFelpZBy.js +2 -0
  33. package/dist/assets/chunk-HOUHSVGY-BMPwFEwN.js +1 -0
  34. package/dist/assets/chunk-ICXQ74PX-DH8NJMok.js +2 -0
  35. package/dist/assets/chunk-JG7HCLWE-D-95AsO4.js +2 -0
  36. package/dist/assets/chunk-JWPE2WC7-DIaTv8kx.js +1 -0
  37. package/dist/assets/chunk-KEIR6QF5-A3jEqwzn.js +161 -0
  38. package/dist/assets/chunk-MOJQB5TN-BPFLzWgj.js +88 -0
  39. package/dist/assets/chunk-MOZMSUNE-DR_fzxG0.js +1 -0
  40. package/dist/assets/chunk-OGEWGWER-TIcEoPPH.js +1 -0
  41. package/dist/assets/chunk-OSBZ3O6U-BALNvSfG.js +1 -0
  42. package/dist/assets/chunk-PUDLZKDR-I-EXhf_k.js +156 -0
  43. package/dist/assets/chunk-Q4XR5HBZ-uvIGstBS.js +70 -0
  44. package/dist/assets/chunk-QBLGF6JB-r-TjLdxa.js +1 -0
  45. package/dist/assets/chunk-R7FJI6CG-N-1LDgvy.js +1 -0
  46. package/dist/assets/chunk-RYQCIY6F-jxNASx65.js +1 -0
  47. package/dist/assets/chunk-U6XO7XAA-Cphbuc7q.js +2 -0
  48. package/dist/assets/chunk-V7JOEXUC-BLEuBf1s.js +206 -0
  49. package/dist/assets/chunk-VAUOI2AC-Dpcz6BhT.js +1 -0
  50. package/dist/assets/chunk-VR4S4FIN-BAUbR0ke.js +1 -0
  51. package/dist/assets/chunk-WYO6CB5R-Do6SJrnY.js +127 -0
  52. package/dist/assets/chunk-XXDRQBXY-C2QjjwYX.js +1 -0
  53. package/dist/assets/chunk-Y2CYZVJY-BGrd8tLy.js +1 -0
  54. package/dist/assets/chunk-YOTPTUD7-CQqvMTCr.js +1 -0
  55. package/dist/assets/chunk-ZGVPDNZ5-B7kWWaXv.js +62 -0
  56. package/dist/assets/chunk-ZIRB5QZD-DTU0cDNh.js +32 -0
  57. package/dist/assets/classDiagram-OUVF2IWQ-DESMaY3_.js +1 -0
  58. package/dist/assets/classDiagram-v2-EOCWNBFH-DwOlByYH.js +1 -0
  59. package/dist/assets/{config-split-page-DBgt9S8b.js → config-split-page-CqlqjnCK.js} +1 -1
  60. package/dist/assets/{confirm-dialog-GSXIvoRU.js → confirm-dialog-bAp8NU93.js} +1 -1
  61. package/dist/assets/cose-bilkent-JH36ORCC-B9LrtV-q.js +1 -0
  62. package/dist/assets/cynefin-VYW2F7L2-CzjeeOq9.js +1 -0
  63. package/dist/assets/cynefinDiagram-TSTJHNR4-BfjvW4su.js +62 -0
  64. package/dist/assets/cytoscape.esm-CRW8q92n.js +321 -0
  65. package/dist/assets/dagre-DkiXZKkL.js +1 -0
  66. package/dist/assets/dagre-VKFMJZFB-B1pQKQSY.js +4 -0
  67. package/dist/assets/defaultLocale-Cdv9x6q7.js +1 -0
  68. package/dist/assets/desktop-update-config-2SjMfCMl.js +1 -0
  69. package/dist/assets/diagram-FQU43EPY-cB4EZdaP.js +3 -0
  70. package/dist/assets/diagram-G47NLZAW-CtW8im1B.js +24 -0
  71. package/dist/assets/diagram-NH7WQ7WH-BDCtrCCm.js +24 -0
  72. package/dist/assets/diagram-OA4YK3LP-DEMJ42Gt.js +30 -0
  73. package/dist/assets/diagram-WEI45ONY-CdCcxSYU.js +41 -0
  74. package/dist/assets/{dist-DC7f4TJH.js → dist-CrtbGLlh.js} +1 -1
  75. package/dist/assets/dist-dw-gAfM_.js +1 -0
  76. package/dist/assets/doc-browser-A5IRyDXQ2.js +1 -0
  77. package/dist/assets/{doc-browser-D0monHjG.js → doc-browser-BmnQa30-.js} +1 -1
  78. package/dist/assets/{doc-browser-context-CGwEOhBy.js → doc-browser-context-BlpUUFrR.js} +1 -1
  79. package/dist/assets/{docx-preview-eFH9DRsS.js → docx-preview-zis3U0Fa.js} +1 -1
  80. package/dist/assets/{download-DiP9zx-J.js → download-BhZZNXD8.js} +1 -1
  81. package/dist/assets/ebnfDiagram-CCIWWBDH-DBZETBwE.js +1 -0
  82. package/dist/assets/erDiagram-Q63AITRT-y2zDXprP.js +85 -0
  83. package/dist/assets/eventmodeling-45OFAUF4-C38BqG8w.js +1 -0
  84. package/dist/assets/{external-link-C6ZeSPef.js → external-link-B9Uoq5J7.js} +1 -1
  85. package/dist/assets/{eye-suvKmtwn.js → eye-K5knauEM.js} +1 -1
  86. package/dist/assets/flowDiagram-23GEKE2U-BoKk8RRq.js +1 -0
  87. package/dist/assets/{folder-CMELbQGG.js → folder-BIYZZWJv.js} +1 -1
  88. package/dist/assets/{form-actions-CUAzpNe_.js → form-actions-CKprWFeM.js} +1 -1
  89. package/dist/assets/ganttDiagram-NO4QXBWP-D2DP344e.js +292 -0
  90. package/dist/assets/gitGraph-TEB2WS4Q-IsQHHJuS.js +1 -0
  91. package/dist/assets/gitGraphDiagram-IHSO6WYX-D37uiwA2.js +106 -0
  92. package/dist/assets/graphlib-BcNFQr5y.js +1 -0
  93. package/dist/assets/index-BVn702m4.css +1 -0
  94. package/dist/assets/index-CFxJ1Fkg.js +2 -0
  95. package/dist/assets/info-DKCQHKI2-ofy2qlHI.js +1 -0
  96. package/dist/assets/infoDiagram-FWYZ7A6U-CG-WAIRi.js +2 -0
  97. package/dist/assets/init-D8TjIqfm.js +1 -0
  98. package/dist/assets/ishikawaDiagram-FXEZZL3T-WeCTnW0f.js +70 -0
  99. package/dist/assets/journeyDiagram-5HDEW3XC-DUFBW9Td.js +139 -0
  100. package/dist/assets/kanban-definition-HUTT4EX6-B0lxgMpg.js +89 -0
  101. package/dist/assets/katex-wEmrEFTg.js +257 -0
  102. package/dist/assets/line-B78frP_c.js +1 -0
  103. package/dist/assets/linear-C6lVEcb2.js +1 -0
  104. package/dist/assets/loader-circle-Bj2RMrCR.js +1 -0
  105. package/dist/assets/{logo-badge-Cu5UGH_4.js → logo-badge-BQ-XFZod.js} +1 -1
  106. package/dist/assets/map-Bdri5UL5.js +1 -0
  107. package/dist/assets/mcp-marketplace-page-BfA7A3n4.js +9 -0
  108. package/dist/assets/mcp-marketplace-page-CXHYO95j.js +1 -0
  109. package/dist/assets/mermaid-parser.core-Cue18vLo.js +4 -0
  110. package/dist/assets/mermaid.core-CXwWRh5i.js +11 -0
  111. package/dist/assets/mindmap-definition-LN4V7U3C-CFxz0P0-.js +96 -0
  112. package/dist/assets/{model-config-page-CWP6pyBm.js → model-config-page-BbWbW_qM.js} +1 -1
  113. package/dist/assets/ordinal-Cbd8t4LB.js +1 -0
  114. package/dist/assets/packet-7NZHBO7P-Rfopfa1o.js +1 -0
  115. package/dist/assets/path-DzBnMI1r.js +1 -0
  116. package/dist/assets/pegDiagram-2B236MQR-B_Qt9zyT.js +1 -0
  117. package/dist/assets/pie-RZYD4A2V-CJR1xKEv.js +1 -0
  118. package/dist/assets/pieDiagram-ENE6RG2P-CRQdSuwS.js +39 -0
  119. package/dist/assets/plus-TmFdwO3P.js +1 -0
  120. package/dist/assets/{popover-DImguRok.js → popover-CX9zFsuc.js} +1 -1
  121. package/dist/assets/preload-helper-D4M6sveU.js +1 -0
  122. package/dist/assets/{provider-scoped-model-input-CE7p4SbJ.js → provider-scoped-model-input-B-9_DtIP.js} +1 -1
  123. package/dist/assets/providers-config-page-xwIjGur2.js +1 -0
  124. package/dist/assets/quadrantDiagram-ABIIQ3AL-D5kPpttq.js +7 -0
  125. package/dist/assets/radar-I7S5WNFK-CDg8Z-1r.js +1 -0
  126. package/dist/assets/railroad-3IZDKUUU-BHnNmjxO.js +1 -0
  127. package/dist/assets/railroad-abnf-AHOZXSZD-DGtNcusW.js +1 -0
  128. package/dist/assets/railroad-ebnf-EBAXGLYW-BRFtsQmm.js +1 -0
  129. package/dist/assets/railroad-peg-LSFZ7HO6-Bj5IypCA.js +1 -0
  130. package/dist/assets/railroadDiagram-RFXS5EU6-DGh_TGlQ.js +1 -0
  131. package/dist/assets/{react-5sjAT_6d.js → react-DsgudwpT.js} +1 -1
  132. package/dist/assets/{refresh-cw-CHLBzWWj.js → refresh-cw-kXdvbhFf.js} +1 -1
  133. package/dist/assets/remote-B9F2uCk9.js +1 -0
  134. package/dist/assets/requirementDiagram-TGXJPOKE-EmFph0H_.js +84 -0
  135. package/dist/assets/rotate-cw-DEc6aihr.js +1 -0
  136. package/dist/assets/rough.esm-wJ1d3nQl.js +1 -0
  137. package/dist/assets/runtime-config-page-DTsi7czJ.js +1 -0
  138. package/dist/assets/sankeyDiagram-HTMAVEWB-BXWB5Ugi.js +40 -0
  139. package/dist/assets/{save-BABzyrjB.js → save-C9qpz2_2.js} +1 -1
  140. package/dist/assets/{search-BTqxG3ZG.js → search-_kEi29qo.js} +1 -1
  141. package/dist/assets/{search-config-page-BkKKmxmS.js → search-config-page-H7ug8f4D.js} +1 -1
  142. package/dist/assets/{secrets-config-page-CWJMyu-k.js → secrets-config-page-C1_6rINu.js} +2 -2
  143. package/dist/assets/security-config-Cr2ERKLu.js +1 -0
  144. package/dist/assets/{select-TYZDBNmq.js → select-MJKhKAWR.js} +1 -1
  145. package/dist/assets/sequenceDiagram-DBY2YBRQ-ClKnO7v0.js +162 -0
  146. package/dist/assets/{setting-row-DR23Z_5I.js → setting-row-DA3tJuew.js} +1 -1
  147. package/dist/assets/{settings-2-D8WWcBnw.js → settings-2-D_y7zXJz.js} +1 -1
  148. package/dist/assets/sizeCapture-X5ZJPWSS-DSo-t5ou.js +1 -0
  149. package/dist/assets/{skeleton-CloHeg0S.js → skeleton-0uTLUhm9.js} +1 -1
  150. package/dist/assets/src-Dad2ZbCt.js +1 -0
  151. package/dist/assets/stateDiagram-2N3HPSRC-BvAalBfz.js +1 -0
  152. package/dist/assets/stateDiagram-v2-6OUMAXLB-CfMU38yc.js +1 -0
  153. package/dist/assets/swimlanes-5IMT3BWC-DdVqVa1a.js +2 -0
  154. package/dist/assets/swimlanesDiagram-G3AALYLV-D9Zb9X57.js +8 -0
  155. package/dist/assets/{tabs-CX-ckWoo.js → tabs-h44XPocd.js} +1 -1
  156. package/dist/assets/{tag-chip-mCc23Rhb.js → tag-chip-bRZ04q3C.js} +1 -1
  157. package/dist/assets/timeline-definition-FHXFAJF6-D4ar8IQY.js +120 -0
  158. package/dist/assets/{trash-2-SAW1Z4Po.js → trash-2-BRpSE-Wt.js} +1 -1
  159. package/dist/assets/treeView-QDETBFTQ-Uy7M0WvH.js +1 -0
  160. package/dist/assets/treemap-6X3UGDF4-BJfGV7KX.js +1 -0
  161. package/dist/assets/use-config-CwDEAl6f.js +1 -0
  162. package/dist/assets/{use-confirm-dialog-C95F6sT1.js → use-confirm-dialog-BbnWw-8c.js} +1 -1
  163. package/dist/assets/{use-viewport-layout-C6m3Nte7.js → use-viewport-layout-CqgIMDQL.js} +1 -1
  164. package/dist/assets/vennDiagram-L72KCM5P-CDXJ_i3p.js +34 -0
  165. package/dist/assets/wardley-OPB4EBWU-q263iF3K.js +1 -0
  166. package/dist/assets/wardleyDiagram-EHGQE667-D-dSuT_7.js +78 -0
  167. package/dist/assets/x-DdEDL6ju.js +1 -0
  168. package/dist/assets/xychartDiagram-FW5EYKEG-C9JCzsZF.js +7 -0
  169. package/dist/index.html +33 -32
  170. package/package.json +8 -8
  171. package/src/features/chat/components/chat-popover-content.tsx +1 -26
  172. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +84 -1
  173. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +13 -2
  174. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +1 -0
  175. package/src/features/chat/components/layout/__tests__/chat-sidebar-test.utils.ts +22 -0
  176. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +13 -24
  177. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +16 -4
  178. package/src/features/chat/components/layout/chat-sidebar.tsx +38 -2
  179. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -0
  180. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +52 -2
  181. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +22 -0
  182. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +36 -20
  183. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +55 -0
  184. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +12 -11
  185. package/src/features/chat/features/conversation/hooks/use-session-conversation-preference-actions.ts +94 -0
  186. package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +37 -0
  187. package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +140 -63
  188. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +175 -0
  189. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +66 -0
  190. package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +8 -1
  191. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +274 -0
  192. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +18 -11
  193. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +26 -0
  194. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +50 -4
  195. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +51 -0
  196. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +4 -10
  197. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +29 -0
  198. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +66 -8
  199. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +62 -21
  200. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +42 -10
  201. package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +80 -0
  202. package/src/features/chat/features/message/components/chat-message-list.container.tsx +55 -60
  203. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +2 -0
  204. package/src/features/chat/features/ncp/hooks/__tests__/use-hydrated-ncp-agent.test.tsx +41 -3
  205. package/src/features/chat/features/project/components/__tests__/chat-project-create-dialog.test.tsx +67 -0
  206. package/src/features/chat/features/project/components/chat-project-create-dialog.tsx +203 -0
  207. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +7 -7
  208. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-dialog.test.tsx +47 -0
  209. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +6 -3
  210. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +3 -0
  211. package/src/features/chat/features/session/components/session-header/chat-session-project-dialog.tsx +3 -0
  212. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +245 -0
  213. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +81 -127
  214. package/src/features/chat/features/session/utils/__tests__/chat-sidebar-session-groups.utils.test.ts +22 -0
  215. package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +6 -6
  216. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +12 -1
  217. package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +3 -0
  218. package/src/features/chat/features/welcome/components/chat-welcome.tsx +1 -0
  219. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +20 -0
  220. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +15 -2
  221. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +22 -24
  222. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +2 -1
  223. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +6 -17
  224. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +41 -19
  225. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
  226. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +5 -1
  227. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +104 -3
  228. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +83 -0
  229. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +4 -1
  230. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +96 -25
  231. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +25 -0
  232. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +5 -1
  233. package/src/features/chat/managers/chat-session-list.manager.ts +1 -0
  234. package/src/features/chat/managers/chat-thread.manager.ts +15 -37
  235. package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +23 -0
  236. package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +9 -0
  237. package/src/features/chat/stores/chat-thread.store.ts +16 -36
  238. package/src/features/chat/types/chat-input-bar.types.ts +2 -0
  239. package/src/features/settings/components/config/provider-form.tsx +0 -4
  240. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +7 -9
  241. package/src/features/settings/utils/provider-form-model.utils.ts +0 -4
  242. package/src/features/system-status/hooks/use-current-version-release-notes-link.ts +33 -0
  243. package/src/features/system-status/index.ts +9 -0
  244. package/src/features/system-status/utils/update-release-notes.utils.ts +44 -3
  245. package/src/index.css +10 -16
  246. package/src/shared/components/common/__tests__/brand-header.test.tsx +127 -10
  247. package/src/shared/components/common/brand-header.tsx +142 -56
  248. package/src/shared/components/path-picker/__tests__/server-path-picker-dialog.test.tsx +288 -38
  249. package/src/shared/components/path-picker/server-path-picker-dialog.tsx +290 -199
  250. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +103 -0
  251. package/src/shared/components/path-picker/server-path-picker-footer.tsx +58 -0
  252. package/src/shared/components/path-picker/server-path-picker-locations.tsx +148 -0
  253. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +198 -0
  254. package/src/shared/components/path-picker/server-path-picker-new-folder.tsx +59 -0
  255. package/src/shared/components/path-picker/server-path-picker-toolbar.tsx +25 -0
  256. package/src/shared/hooks/use-projects.ts +28 -0
  257. package/src/shared/hooks/use-server-path-create-directory.ts +6 -0
  258. package/src/shared/hooks/use-server-path-read.ts +10 -5
  259. package/src/shared/hooks/use-server-path-search.ts +40 -0
  260. package/src/shared/lib/api/index.ts +1 -0
  261. package/src/shared/lib/api/projects/project.utils.ts +14 -0
  262. package/src/shared/lib/api/types.ts +30 -3
  263. package/src/shared/lib/api/utils/server-path.utils.ts +22 -1
  264. package/src/shared/lib/i18n/locales/en-US/chat.json +43 -2
  265. package/src/shared/lib/i18n/locales/en-US/core.json +2 -3
  266. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +4 -0
  267. package/src/shared/lib/i18n/locales/en-US/path-picker.json +25 -0
  268. package/src/shared/lib/i18n/locales/zh-CN/chat.json +43 -2
  269. package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -3
  270. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +4 -0
  271. package/src/shared/lib/i18n/locales/zh-CN/path-picker.json +25 -0
  272. package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +1 -4
  273. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +1 -18
  274. package/dist/assets/api-BOXOrpcJ.js +0 -13
  275. package/dist/assets/app-presenter-provider-CkHLbvZQ.js +0 -106
  276. package/dist/assets/desktop-update-config-7bmNeWIC.js +0 -1
  277. package/dist/assets/doc-browser-CgdoLUT92.js +0 -1
  278. package/dist/assets/index-BFeNDQ4R.css +0 -1
  279. package/dist/assets/index-D_6U-M94.js +0 -2
  280. package/dist/assets/loader-circle-tYHStefV.js +0 -1
  281. package/dist/assets/mcp-marketplace-page-BUzHDwW6.js +0 -9
  282. package/dist/assets/mcp-marketplace-page-ohUAdfqJ.js +0 -1
  283. package/dist/assets/plus-tywizkeY.js +0 -1
  284. package/dist/assets/providers-config-page-BKPo8SjH.js +0 -1
  285. package/dist/assets/remote-DXJryUxB.js +0 -1
  286. package/dist/assets/rotate-cw-BefO5brU.js +0 -1
  287. package/dist/assets/runtime-config-page-1Axv4QGu.js +0 -1
  288. package/dist/assets/security-config-DeQUIXfi.js +0 -1
  289. package/dist/assets/use-config-BucdaK23.js +0 -1
  290. package/dist/assets/x-DtCPHwCu.js +0 -1
  291. package/src/features/chat/components/__tests__/chat-popover-content.test.tsx +0 -41
  292. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +0 -55
  293. package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +0 -77
  294. package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +0 -144
  295. /package/dist/assets/{config-hints-fDrYfl0l.js → config-hints-LxG80Uj5.js} +0 -0
  296. /package/dist/assets/{host-capabilities-zZvY9U63.js → host-capabilities-CFbAfrrA.js} +0 -0
  297. /package/dist/assets/{jszip.min-Das2IAnb.js → jszip.min-CP5uPNQa.js} +0 -0
  298. /package/dist/assets/{middleware-BLMHv9YF.js → middleware-CAXxl-af.js} +0 -0
  299. /package/dist/assets/{provider-models-C_yOh6DE.js → provider-models-BoUhFo9R.js} +0 -0
  300. /package/dist/assets/{xlsx-B6zVt1RK.js → xlsx-DUqJhg5u.js} +0 -0
@@ -4,13 +4,11 @@ import {
4
4
  type ChatInlineDisplayViewModel,
5
5
  type ChatInlineTokenViewModel,
6
6
  type ChatMessageViewModel,
7
+ type ChatPanelAppCardViewModel,
7
8
  ChatMessageList,
8
9
  } from "@nextclaw/agent-chat-ui";
9
10
  import { usePresenter } from "@/features/chat/components/providers/chat-presenter.provider";
10
- import {
11
- adaptChatMessage,
12
- type ChatMessageSource,
13
- } from "@/features/chat/features/message/utils/chat-message.utils";
11
+ import { adaptChatMessage, type ChatMessageSource } from "@/features/chat/features/message/utils/chat-message.utils";
14
12
  import { buildChatMessageProcessSummary } from "@/features/chat/features/message/utils/chat-message-process-summary.utils";
15
13
  import {
16
14
  buildChatMessageAdapterTexts,
@@ -23,6 +21,7 @@ import {
23
21
  type ContextCompactionTimelineView,
24
22
  } from "@/features/chat/features/session/utils/ncp-session-context-metadata.utils";
25
23
  import { AgentIdentityAvatar } from "@/shared/components/common/agent-identity";
24
+ import { ChatInlineFilePreview } from "@/features/chat/features/message/components/chat-inline-file-preview";
26
25
  import { ChatInlinePanelAppCard } from "@/features/chat/features/message/components/chat-inline-panel-app-card";
27
26
  import { useChatQueryStore } from "@/features/chat/stores/ncp-chat-query.store";
28
27
  import { useChatSessionListStore } from "@/features/chat/stores/chat-session-list.store";
@@ -62,6 +61,10 @@ function renderChatInlineDisplay(display: ChatInlineDisplayViewModel) {
62
61
  />
63
62
  );
64
63
  }
64
+
65
+ const renderChatToolAgent = (agentId: string) => <AgentIdentityAvatar agentId={agentId} className="h-4 w-4 shrink-0" />;
66
+ const renderChatPanelAppCard = (panelApp: ChatPanelAppCardViewModel) => <ChatInlinePanelAppCard panelApp={panelApp} />;
67
+
65
68
  type ContextInheritanceTimelineView = {
66
69
  sourceSessionId: string;
67
70
  inheritedMessageCount: number;
@@ -88,11 +91,7 @@ type ChatTimelineItem =
88
91
  inheritance: ContextInheritanceTimelineView;
89
92
  };
90
93
 
91
- function ChatContextCompactionDivider({
92
- checkpoint,
93
- }: {
94
- checkpoint: ContextCompactionTimelineView;
95
- }) {
94
+ function ChatContextCompactionDivider({ checkpoint }: { checkpoint: ContextCompactionTimelineView }) {
96
95
  const title = [
97
96
  `${t("chatContextCompactionCoveredMessages")}: ${checkpoint.coveredSessionMessageCount}`,
98
97
  `${t("chatContextCompactionOriginalTokens")}: ${checkpoint.originalEstimatedTokens}`,
@@ -118,11 +117,7 @@ function ChatContextCompactionDivider({
118
117
  );
119
118
  }
120
119
 
121
- function ChatContextInheritanceDivider({
122
- inheritance,
123
- }: {
124
- inheritance: ContextInheritanceTimelineView;
125
- }) {
120
+ function ChatContextInheritanceDivider({ inheritance }: { inheritance: ContextInheritanceTimelineView }) {
126
121
  const title = [
127
122
  `${t("chatContextInheritanceSourceSession")}: ${inheritance.sourceSessionId}`,
128
123
  `${t("chatContextInheritanceMessages")}: ${inheritance.inheritedMessageCount}`,
@@ -144,20 +139,12 @@ function resolveCompactionBoundaryIndex(params: {
144
139
  visibleRawMessages: readonly NcpMessage[];
145
140
  rawMessageId: string;
146
141
  }): number {
147
- const {
148
- rawMessageId,
149
- rawMessages,
150
- visibleRawMessages,
151
- } = params;
152
- const physicalIndex = rawMessages.findIndex(
153
- (message) => message.id === rawMessageId,
154
- );
142
+ const { rawMessageId, rawMessages, visibleRawMessages } = params;
143
+ const physicalIndex = rawMessages.findIndex((message) => message.id === rawMessageId);
155
144
  if (physicalIndex < 0) {
156
145
  return visibleRawMessages.length - 1;
157
146
  }
158
- const previousVisibleCount = rawMessages
159
- .slice(0, physicalIndex)
160
- .filter(isVisibleChatMessage).length;
147
+ const previousVisibleCount = rawMessages.slice(0, physicalIndex).filter(isVisibleChatMessage).length;
161
148
  return previousVisibleCount - 1;
162
149
  }
163
150
 
@@ -170,9 +157,7 @@ function isVisibleChatMessage(message: NcpMessage): boolean {
170
157
  return !readContextCompactionTimeline(message) && !readInheritedSourceSessionId(message);
171
158
  }
172
159
 
173
- function resolveContextInheritanceBoundary(
174
- messages: readonly NcpMessage[],
175
- ): ContextInheritanceTimelineBoundary | null {
160
+ function resolveContextInheritanceBoundary(messages: readonly NcpMessage[]): ContextInheritanceTimelineBoundary | null {
176
161
  const boundaryIndex = messages.findIndex((message) => readInheritedSourceSessionId(message));
177
162
  if (boundaryIndex < 0) {
178
163
  return null;
@@ -184,7 +169,8 @@ function resolveContextInheritanceBoundary(
184
169
  return {
185
170
  boundaryIndex: messages.slice(0, boundaryIndex).filter(isVisibleChatMessage).length,
186
171
  sourceSessionId,
187
- inheritedMessageCount: messages.filter((message) => readInheritedSourceSessionId(message) === sourceSessionId).length,
172
+ inheritedMessageCount: messages.filter((message) => readInheritedSourceSessionId(message) === sourceSessionId)
173
+ .length,
188
174
  };
189
175
  }
190
176
 
@@ -199,8 +185,12 @@ function buildTimelineItems(params: {
199
185
  checkpoint: readContextCompactionTimeline(message),
200
186
  }))
201
187
  .filter(
202
- (entry): entry is { rawMessageId: string; checkpoint: ContextCompactionTimelineView } =>
203
- Boolean(entry.checkpoint),
188
+ (
189
+ entry,
190
+ ): entry is {
191
+ rawMessageId: string;
192
+ checkpoint: ContextCompactionTimelineView;
193
+ } => Boolean(entry.checkpoint),
204
194
  )
205
195
  .map((entry) => ({
206
196
  key: entry.rawMessageId,
@@ -290,19 +280,35 @@ export function ChatMessageListContainer({
290
280
  const presenter = usePresenter();
291
281
  const { language } = useI18n();
292
282
  const messageLayout = useChatMessageLayoutStore((state) => state.layout);
293
- const selectedSessionKey = useChatSessionListStore(
294
- (state) => state.snapshot.selectedSessionKey,
295
- );
283
+ const selectedSessionKey = useChatSessionListStore((state) => state.snapshot.selectedSessionKey);
296
284
  const selectedSession = useNcpChatSelectedSession(selectedSessionKey);
297
285
  const localFileBasePath = selectedSession?.workingDir ?? selectedSession?.projectRoot ?? null;
286
+ const renderInlineDisplayWithFiles = useCallback(
287
+ (display: ChatInlineDisplayViewModel) => {
288
+ const panelAppDisplay = renderChatInlineDisplay(display);
289
+ if (panelAppDisplay) {
290
+ return panelAppDisplay;
291
+ }
292
+ if (display.target.type === "file") {
293
+ return (
294
+ <ChatInlineFilePreview
295
+ display={display}
296
+ parentSessionKey={selectedSessionKey}
297
+ sessionProjectRoot={selectedSession?.projectRoot ?? null}
298
+ sessionWorkingDir={localFileBasePath}
299
+ onFileOpen={presenter.chatThreadManager.openFilePreview}
300
+ />
301
+ );
302
+ }
303
+ return undefined;
304
+ },
305
+ [localFileBasePath, presenter.chatThreadManager, selectedSession?.projectRoot, selectedSessionKey],
306
+ );
298
307
  const resolveFileContentUrl = useCallback(
299
308
  (action: { path: string }) => buildServerPathContentUrl(action.path, localFileBasePath),
300
309
  [localFileBasePath],
301
310
  );
302
- const texts = useMemo(
303
- () => buildChatMessageAdapterTexts(language),
304
- [language],
305
- );
311
+ const texts = useMemo(() => buildChatMessageAdapterTexts(language), [language]);
306
312
 
307
313
  const messages = useMemo(() => {
308
314
  const visibleRawMessages = rawMessages.filter(isVisibleChatMessage);
@@ -335,7 +341,11 @@ export function ChatMessageListContainer({
335
341
  texts,
336
342
  });
337
343
 
338
- messageViewModelCache.set(message, { language, processSummaryLabel, viewModel });
344
+ messageViewModelCache.set(message, {
345
+ language,
346
+ processSummaryLabel,
347
+ viewModel,
348
+ });
339
349
  return [viewModel];
340
350
  });
341
351
  }, [language, rawMessages, texts]);
@@ -343,20 +353,12 @@ export function ChatMessageListContainer({
343
353
  const hasAssistantDraft = useMemo(
344
354
  () =>
345
355
  messages.some(
346
- (message) =>
347
- message.role === "assistant" &&
348
- (message.status === "streaming" || message.status === "pending"),
356
+ (message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"),
349
357
  ),
350
358
  [messages],
351
359
  );
352
- const messageTexts = useMemo(
353
- () => buildChatMessageTexts(language),
354
- [language],
355
- );
356
- const timelineItems = useMemo(
357
- () => buildTimelineItems({ rawMessages, messages }),
358
- [messages, rawMessages],
359
- );
360
+ const messageTexts = useMemo(() => buildChatMessageTexts(language), [language]);
361
+ const timelineItems = useMemo(() => buildTimelineItems({ rawMessages, messages }), [messages, rawMessages]);
360
362
  const sessionSkillsQuery = useChatQueryStore((state) => state.snapshot.sessionSkillsQuery);
361
363
  const handleInlineTokenClick = useCallback(
362
364
  (token: ChatInlineTokenViewModel) => {
@@ -419,16 +421,9 @@ export function ChatMessageListContainer({
419
421
  onAttachmentOpen={handleAttachmentOpen}
420
422
  onInlineTokenClick={handleInlineTokenClick}
421
423
  resolveFileContentUrl={resolveFileContentUrl}
422
- renderInlineDisplay={renderChatInlineDisplay}
423
- renderToolAgent={(agentId) => (
424
- <AgentIdentityAvatar
425
- agentId={agentId}
426
- className="h-4 w-4 shrink-0"
427
- />
428
- )}
429
- renderPanelAppCard={(panelApp) => (
430
- <ChatInlinePanelAppCard panelApp={panelApp} />
431
- )}
424
+ renderInlineDisplay={renderInlineDisplayWithFiles}
425
+ renderToolAgent={renderChatToolAgent}
426
+ renderPanelAppCard={renderChatPanelAppCard}
432
427
  />
433
428
  ),
434
429
  )}
@@ -106,6 +106,8 @@ export function buildChatMessageTexts(
106
106
  copiedCodeLabel: t("chatCodeCopied"),
107
107
  copyMessageLabel: t("chatMessageCopy"),
108
108
  copiedMessageLabel: t("chatMessageCopied"),
109
+ mermaidDiagramLabel: t("chatMermaidDiagram"),
110
+ mermaidRenderErrorLabel: t("chatMermaidRenderError"),
109
111
  typingLabel: t("chatTyping"),
110
112
  reasoningCharacterCountTemplates: {
111
113
  inProgress: t("chatReasoningInProgressCharacterCount"),
@@ -1,19 +1,57 @@
1
- import { renderHook, waitFor } from "@testing-library/react";
2
- import type { NcpAgentClientEndpoint } from "@nextclaw/ncp";
3
- import { useHydratedNcpAgent } from "@nextclaw/ncp-react";
1
+ import { act, renderHook, waitFor } from "@testing-library/react";
2
+ import { NcpEventType, type NcpAgentClientEndpoint } from "@nextclaw/ncp";
3
+ import { useHydratedNcpAgent, useNcpAgentRuntime } from "@nextclaw/ncp-react";
4
+ import { DefaultNcpAgentConversationStateManager } from "@nextclaw/ncp-toolkit";
4
5
  import { beforeEach, describe, expect, it, vi } from "vitest";
5
6
 
6
7
  const mocks = vi.hoisted(() => ({
8
+ send: vi.fn(),
7
9
  stream: vi.fn(),
8
10
  stop: vi.fn(),
9
11
  }));
10
12
 
11
13
  describe("useHydratedNcpAgent", () => {
12
14
  beforeEach(() => {
15
+ mocks.send.mockReset();
13
16
  mocks.stream.mockReset();
14
17
  mocks.stop.mockReset();
15
18
  });
16
19
 
20
+ it("clears a draft conversation error when a valid retry starts", async () => {
21
+ const client = {
22
+ send: mocks.send.mockResolvedValue(null),
23
+ stop: mocks.stop.mockResolvedValue(undefined),
24
+ stream: mocks.stream.mockResolvedValue(undefined),
25
+ subscribe: vi.fn(() => () => {}),
26
+ } as unknown as NcpAgentClientEndpoint;
27
+ const manager = new DefaultNcpAgentConversationStateManager();
28
+ const { result } = renderHook(() =>
29
+ useNcpAgentRuntime({
30
+ client,
31
+ manager,
32
+ }),
33
+ );
34
+
35
+ await act(async () => {
36
+ await manager.dispatch({
37
+ occurredAt: new Date().toISOString(),
38
+ type: NcpEventType.EndpointError,
39
+ payload: {
40
+ code: "runtime-error",
41
+ message: "network error",
42
+ },
43
+ });
44
+ });
45
+ expect(result.current.snapshot.error?.message).toBe("network error");
46
+
47
+ await act(async () => {
48
+ await result.current.send("retry");
49
+ });
50
+
51
+ expect(result.current.snapshot.error).toBeNull();
52
+ expect(mocks.send).toHaveBeenCalledOnce();
53
+ });
54
+
17
55
  it("treats a newly selected session as hydrating immediately on rerender", async () => {
18
56
  const client = {
19
57
  stop: mocks.stop.mockResolvedValue(undefined),
@@ -0,0 +1,67 @@
1
+ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+ import { ChatProjectCreateDialog } from '@/features/chat/features/project/components/chat-project-create-dialog';
5
+ import { setLanguage } from '@/shared/lib/i18n';
6
+
7
+ const pathPickerSpy = vi.fn();
8
+
9
+ vi.mock('@/shared/components/path-picker/server-path-picker-dialog', () => ({
10
+ ServerPathPickerDialog: (props: unknown) => pathPickerSpy(props),
11
+ }));
12
+
13
+ describe('ChatProjectCreateDialog', () => {
14
+ it('selects the optional target directory with the shared path picker', async () => {
15
+ setLanguage('zh');
16
+ const user = userEvent.setup();
17
+ const onCreate = vi.fn(async () => undefined);
18
+
19
+ render(
20
+ <ChatProjectCreateDialog
21
+ open
22
+ defaultWorkspacePath="/workspace"
23
+ templates={[
24
+ { id: 'empty', name: 'Empty', description: 'Empty project' },
25
+ {
26
+ id: 'knowledge-base',
27
+ name: 'Knowledge base',
28
+ description: 'Knowledge base project',
29
+ },
30
+ ]}
31
+ isCreating={false}
32
+ onOpenChange={() => {}}
33
+ onCreate={onCreate}
34
+ />,
35
+ );
36
+
37
+ await user.type(screen.getByLabelText('项目名称'), '个人知识库');
38
+ fireEvent.change(document.querySelector('select')!, {
39
+ target: { value: 'knowledge-base' },
40
+ });
41
+ await user.click(screen.getByRole('button', { name: '浏览' }));
42
+ expect(pathPickerSpy).toHaveBeenLastCalledWith(
43
+ expect.objectContaining({
44
+ open: true,
45
+ currentPath: '',
46
+ defaultWorkspacePath: '/workspace',
47
+ title: '选择项目目标目录',
48
+ }),
49
+ );
50
+ const pickerProps = pathPickerSpy.mock.lastCall?.[0] as {
51
+ onConfirm: (path: string) => void;
52
+ };
53
+ act(() => pickerProps.onConfirm('/srv/knowledge'));
54
+ expect((screen.getByLabelText('目标目录(可选)') as HTMLInputElement).value).toBe(
55
+ '/srv/knowledge',
56
+ );
57
+ await user.click(screen.getByRole('button', { name: '创建项目' }));
58
+
59
+ await waitFor(() => {
60
+ expect(onCreate).toHaveBeenCalledWith({
61
+ name: '个人知识库',
62
+ rootPath: '/srv/knowledge',
63
+ template: 'knowledge-base',
64
+ });
65
+ });
66
+ });
67
+ });
@@ -0,0 +1,203 @@
1
+ import { useState } from 'react';
2
+ import { FolderOpen } from 'lucide-react';
3
+ import type { ProjectCreateRequest, ProjectTemplateView } from '@/shared/lib/api';
4
+ import { ServerPathPickerDialog } from '@/shared/components/path-picker/server-path-picker-dialog';
5
+ import { Button } from '@/shared/components/ui/button';
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from '@/shared/components/ui/dialog';
14
+ import { Input } from '@/shared/components/ui/input';
15
+ import { Label } from '@/shared/components/ui/label';
16
+ import {
17
+ Select,
18
+ SelectContent,
19
+ SelectItem,
20
+ SelectTrigger,
21
+ SelectValue,
22
+ } from '@/shared/components/ui/select';
23
+ import { t } from '@/shared/lib/i18n';
24
+
25
+ type ProjectTemplateId = NonNullable<ProjectCreateRequest['template']>;
26
+
27
+ type ChatProjectCreateDialogProps = {
28
+ open: boolean;
29
+ defaultWorkspacePath?: string | null;
30
+ templates: readonly ProjectTemplateView[];
31
+ isCreating: boolean;
32
+ errorMessage?: string | null;
33
+ onOpenChange: (open: boolean) => void;
34
+ onCreate: (input: ProjectCreateRequest) => Promise<void>;
35
+ };
36
+
37
+ function projectTemplateLabel(templateId: ProjectTemplateId): string {
38
+ return t(
39
+ templateId === 'knowledge-base'
40
+ ? 'chatProjectTemplateKnowledgeBase'
41
+ : 'chatProjectTemplateEmpty',
42
+ );
43
+ }
44
+
45
+ export function ChatProjectCreateDialog({
46
+ open,
47
+ defaultWorkspacePath,
48
+ templates,
49
+ isCreating,
50
+ errorMessage,
51
+ onOpenChange,
52
+ onCreate,
53
+ }: ChatProjectCreateDialogProps) {
54
+ const [name, setName] = useState('');
55
+ const [rootPath, setRootPath] = useState('');
56
+ const [pathPickerOpen, setPathPickerOpen] = useState(false);
57
+ const [template, setTemplate] = useState<ProjectTemplateId>('empty');
58
+ const availableTemplateIds = templates.length > 0
59
+ ? templates.map((entry) => entry.id)
60
+ : ['empty' as const];
61
+ const normalizedName = name.trim();
62
+
63
+ const resetForm = () => {
64
+ setName('');
65
+ setRootPath('');
66
+ setTemplate('empty');
67
+ setPathPickerOpen(false);
68
+ };
69
+
70
+ const closeDialog = () => {
71
+ if (isCreating) {
72
+ return;
73
+ }
74
+ resetForm();
75
+ onOpenChange(false);
76
+ };
77
+
78
+ return (
79
+ <>
80
+ <Dialog
81
+ open={open}
82
+ onOpenChange={(nextOpen) => {
83
+ if (nextOpen) {
84
+ onOpenChange(true);
85
+ return;
86
+ }
87
+ closeDialog();
88
+ }}
89
+ >
90
+ <DialogContent className="sm:max-w-lg">
91
+ <DialogHeader>
92
+ <DialogTitle>{t('chatProjectCreateTitle')}</DialogTitle>
93
+ <DialogDescription>{t('chatProjectCreateDescription')}</DialogDescription>
94
+ </DialogHeader>
95
+ <form
96
+ className="space-y-4"
97
+ onSubmit={(event) => {
98
+ event.preventDefault();
99
+ if (!normalizedName || isCreating) {
100
+ return;
101
+ }
102
+ const normalizedRootPath = rootPath.trim();
103
+ void onCreate({
104
+ name: normalizedName,
105
+ template,
106
+ ...(normalizedRootPath ? { rootPath: normalizedRootPath } : {}),
107
+ }).then(resetForm).catch(() => undefined);
108
+ }}
109
+ >
110
+ <div className="space-y-2">
111
+ <Label htmlFor="chat-project-name">{t('chatProjectNameLabel')}</Label>
112
+ <Input
113
+ id="chat-project-name"
114
+ value={name}
115
+ onChange={(event) => setName(event.target.value)}
116
+ placeholder={t('chatProjectNamePlaceholder')}
117
+ autoFocus
118
+ disabled={isCreating}
119
+ />
120
+ </div>
121
+
122
+ <div className="space-y-2">
123
+ <Label htmlFor="chat-project-template">{t('chatProjectTemplateLabel')}</Label>
124
+ <Select
125
+ value={template}
126
+ onValueChange={(value) => setTemplate(value as ProjectTemplateId)}
127
+ disabled={isCreating}
128
+ >
129
+ <SelectTrigger id="chat-project-template">
130
+ <SelectValue />
131
+ </SelectTrigger>
132
+ <SelectContent>
133
+ {availableTemplateIds.map((templateId) => (
134
+ <SelectItem key={templateId} value={templateId}>
135
+ {projectTemplateLabel(templateId)}
136
+ </SelectItem>
137
+ ))}
138
+ </SelectContent>
139
+ </Select>
140
+ </div>
141
+
142
+ <div className="space-y-2">
143
+ <Label htmlFor="chat-project-root-path">{t('chatProjectPathLabel')}</Label>
144
+ <div className="flex gap-2">
145
+ <Input
146
+ id="chat-project-root-path"
147
+ value={rootPath}
148
+ onChange={(event) => setRootPath(event.target.value)}
149
+ placeholder={t('chatProjectPathPlaceholder')}
150
+ disabled={isCreating}
151
+ />
152
+ <Button
153
+ type="button"
154
+ variant="outline"
155
+ onClick={() => setPathPickerOpen(true)}
156
+ disabled={isCreating}
157
+ >
158
+ <FolderOpen className="mr-2 h-4 w-4" />
159
+ {t('browse')}
160
+ </Button>
161
+ </div>
162
+ <p className="text-xs leading-relaxed text-muted-foreground">
163
+ {t('chatProjectPathHint')}
164
+ </p>
165
+ </div>
166
+
167
+ {errorMessage ? (
168
+ <p role="alert" className="text-sm text-destructive">
169
+ {errorMessage}
170
+ </p>
171
+ ) : null}
172
+
173
+ <DialogFooter>
174
+ <Button type="button" variant="outline" onClick={closeDialog} disabled={isCreating}>
175
+ {t('cancel')}
176
+ </Button>
177
+ <Button type="submit" disabled={!normalizedName || isCreating}>
178
+ {isCreating ? t('saving') : t('chatProjectCreateAction')}
179
+ </Button>
180
+ </DialogFooter>
181
+ </form>
182
+ </DialogContent>
183
+ </Dialog>
184
+ <ServerPathPickerDialog
185
+ open={pathPickerOpen}
186
+ currentPath={rootPath}
187
+ defaultWorkspacePath={defaultWorkspacePath}
188
+ isSaving={false}
189
+ onOpenChange={setPathPickerOpen}
190
+ onConfirm={(path) => {
191
+ setRootPath(path);
192
+ setPathPickerOpen(false);
193
+ }}
194
+ title={t('chatProjectPathPickerTitle')}
195
+ description={t('chatProjectPathPickerDescription')}
196
+ pathLabel={t('chatProjectPathLabel')}
197
+ pathPlaceholder={t('chatProjectPathPlaceholder')}
198
+ confirmLabel={t('selectCurrentDirectory')}
199
+ hint={t('chatProjectPathHint')}
200
+ />
201
+ </>
202
+ );
203
+ }
@@ -32,7 +32,7 @@ describe('ChatSessionHeaderActions', () => {
32
32
  sessionKey="session-1"
33
33
  canDeleteSession
34
34
  isDeletePending={false}
35
- projectRoot="/tmp/project-alpha"
35
+ currentPath="/tmp/project-alpha"
36
36
  isWorkspaceOpen={false}
37
37
  onToggleWorkspace={mocks.onToggleWorkspace}
38
38
  onDeleteSession={mocks.onDeleteSession}
@@ -55,7 +55,7 @@ describe('ChatSessionHeaderActions', () => {
55
55
  sessionKey="draft-session"
56
56
  canDeleteSession={false}
57
57
  isDeletePending={false}
58
- projectRoot={null}
58
+ currentPath={null}
59
59
  isWorkspaceOpen={false}
60
60
  onToggleWorkspace={mocks.onToggleWorkspace}
61
61
  onDeleteSession={mocks.onDeleteSession}
@@ -77,7 +77,7 @@ describe('ChatSessionHeaderActions', () => {
77
77
  sessionKey="session-codex"
78
78
  canDeleteSession
79
79
  isDeletePending={false}
80
- projectRoot={null}
80
+ currentPath={null}
81
81
  metadata={{
82
82
  codex_thread_id: 'thread-123',
83
83
  runtime: 'codex',
@@ -106,7 +106,7 @@ describe('ChatSessionHeaderActions', () => {
106
106
  sessionKey="session-children"
107
107
  canDeleteSession
108
108
  isDeletePending={false}
109
- projectRoot={null}
109
+ currentPath={null}
110
110
  isWorkspaceOpen={false}
111
111
  onToggleWorkspace={mocks.onToggleWorkspace}
112
112
  onDeleteSession={mocks.onDeleteSession}
@@ -126,7 +126,7 @@ describe('ChatSessionHeaderActions', () => {
126
126
  sessionKey="session-actions"
127
127
  canDeleteSession
128
128
  isDeletePending={false}
129
- projectRoot={null}
129
+ currentPath={null}
130
130
  isWorkspaceOpen={false}
131
131
  onToggleWorkspace={mocks.onToggleWorkspace}
132
132
  onDeleteSession={mocks.onDeleteSession}
@@ -146,7 +146,7 @@ describe('ChatSessionHeaderActions', () => {
146
146
  sessionKey="session-actions"
147
147
  canDeleteSession
148
148
  isDeletePending={false}
149
- projectRoot={null}
149
+ currentPath={null}
150
150
  isWorkspaceOpen={false}
151
151
  onToggleWorkspace={mocks.onToggleWorkspace}
152
152
  onDeleteSession={mocks.onDeleteSession}
@@ -169,7 +169,7 @@ describe('ChatSessionHeaderActions', () => {
169
169
  sessionKey="session-actions"
170
170
  canDeleteSession
171
171
  isDeletePending={false}
172
- projectRoot={null}
172
+ currentPath={null}
173
173
  isWorkspaceOpen
174
174
  onToggleWorkspace={mocks.onToggleWorkspace}
175
175
  onDeleteSession={mocks.onDeleteSession}
@@ -0,0 +1,47 @@
1
+ import { render } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { ChatSessionProjectDialog } from "@/features/chat/features/session/components/session-header/chat-session-project-dialog";
4
+
5
+ const pathPickerSpy = vi.fn((_props: unknown) => null);
6
+
7
+ vi.mock("@/shared/components/path-picker/server-path-picker-dialog", () => ({
8
+ ServerPathPickerDialog: (props: unknown) => pathPickerSpy(props),
9
+ }));
10
+
11
+ describe("ChatSessionProjectDialog", () => {
12
+ it("uses the default workspace as the browse start and quick-access location", () => {
13
+ render(
14
+ <ChatSessionProjectDialog
15
+ open
16
+ currentProjectRoot={null}
17
+ defaultWorkspacePath="/tmp/default-workspace"
18
+ isSaving={false}
19
+ onOpenChange={() => undefined}
20
+ onSave={() => undefined}
21
+ />,
22
+ );
23
+
24
+ expect(pathPickerSpy).toHaveBeenCalledWith(expect.objectContaining({
25
+ currentPath: null,
26
+ defaultWorkspacePath: "/tmp/default-workspace",
27
+ }));
28
+ });
29
+
30
+ it("prefers an explicit project over the default workdir", () => {
31
+ render(
32
+ <ChatSessionProjectDialog
33
+ open
34
+ currentProjectRoot="/tmp/project"
35
+ defaultWorkspacePath="/tmp/default-workspace"
36
+ isSaving={false}
37
+ onOpenChange={() => undefined}
38
+ onSave={() => undefined}
39
+ />,
40
+ );
41
+
42
+ expect(pathPickerSpy).toHaveBeenLastCalledWith(expect.objectContaining({
43
+ currentPath: "/tmp/project",
44
+ defaultWorkspacePath: "/tmp/default-workspace",
45
+ }));
46
+ });
47
+ });