@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
@@ -1,23 +1,27 @@
1
- import { useEffect, useMemo, useState } from 'react';
2
- import { ChevronRight, Folder, FolderUp, Home, RefreshCcw, Search } from 'lucide-react';
1
+ import { useMemo, useState } from 'react';
2
+ import { Search } from 'lucide-react';
3
3
  import { useServerPathBrowse } from '@/shared/hooks/use-server-path-browse';
4
- import { Button } from '@/shared/components/ui/button';
4
+ import { useServerPathCreateDirectory } from '@/shared/hooks/use-server-path-create-directory';
5
+ import { ServerPathPickerDirectoryList } from '@/shared/components/path-picker/server-path-picker-directory-list';
6
+ import { ServerPathPickerFooter } from '@/shared/components/path-picker/server-path-picker-footer';
7
+ import { ServerPathPickerLocations } from '@/shared/components/path-picker/server-path-picker-locations';
8
+ import { ServerPathPickerNavigationBar } from '@/shared/components/path-picker/server-path-picker-navigation-bar';
9
+ import { ServerPathPickerNewFolder } from '@/shared/components/path-picker/server-path-picker-new-folder';
10
+ import { ServerPathPickerToolbar } from '@/shared/components/path-picker/server-path-picker-toolbar';
5
11
  import {
6
12
  Dialog,
7
13
  DialogContent,
8
14
  DialogDescription,
9
- DialogFooter,
10
15
  DialogHeader,
11
16
  DialogTitle,
12
17
  } from '@/shared/components/ui/dialog';
13
18
  import { Input } from '@/shared/components/ui/input';
14
- import { Label } from '@/shared/components/ui/label';
15
- import { ScrollArea } from '@/shared/components/ui/scroll-area';
16
19
  import { t } from '@/shared/lib/i18n';
17
20
 
18
21
  type ServerPathPickerDialogProps = {
19
22
  open: boolean;
20
23
  currentPath?: string | null;
24
+ defaultWorkspacePath?: string | null;
21
25
  isSaving: boolean;
22
26
  onOpenChange: (open: boolean) => void;
23
27
  onConfirm: (path: string) => Promise<void> | void;
@@ -29,9 +33,26 @@ type ServerPathPickerDialogProps = {
29
33
  hint?: string;
30
34
  };
31
35
 
36
+ function resolveHomeRelativePath(path: string, homePath?: string | null): string {
37
+ if (!homePath) {
38
+ return path;
39
+ }
40
+ if (path === '~') {
41
+ return homePath;
42
+ }
43
+ const windowsHome = homePath.includes('\\');
44
+ if (path.startsWith('~/') || (windowsHome && path.startsWith('~\\'))) {
45
+ const separator = windowsHome ? '\\' : '/';
46
+ const relativePath = path.slice(2).replaceAll(separator === '\\' ? '/' : '\\', separator);
47
+ return `${homePath.replace(/[\\/]+$/, '')}${separator}${relativePath}`;
48
+ }
49
+ return path;
50
+ }
51
+
32
52
  export function ServerPathPickerDialog({
33
53
  open,
34
54
  currentPath,
55
+ defaultWorkspacePath,
35
56
  isSaving,
36
57
  onOpenChange,
37
58
  onConfirm,
@@ -42,36 +63,101 @@ export function ServerPathPickerDialog({
42
63
  confirmLabel,
43
64
  hint,
44
65
  }: ServerPathPickerDialogProps) {
45
- const [draftPath, setDraftPath] = useState('');
46
- const [browsePath, setBrowsePath] = useState<string | null>(null);
47
- const [searchText, setSearchText] = useState('');
66
+ const normalizedCurrentPath = currentPath?.trim() ?? '';
67
+ const normalizedDefaultWorkspacePath = defaultWorkspacePath?.trim() ?? '';
68
+ const sessionKey = `${normalizedCurrentPath}\u0000${normalizedDefaultWorkspacePath}`;
48
69
 
49
- useEffect(() => {
50
- if (!open) {
51
- return;
52
- }
53
- const nextPath = currentPath?.trim() || null;
54
- setDraftPath(nextPath ?? '');
55
- setBrowsePath(nextPath);
56
- setSearchText('');
57
- }, [currentPath, open]);
70
+ return (
71
+ <Dialog
72
+ open={open}
73
+ onOpenChange={(nextOpen) => {
74
+ if (isSaving) {
75
+ return;
76
+ }
77
+ onOpenChange(nextOpen);
78
+ }}
79
+ >
80
+ <DialogContent className="max-h-[92vh] overflow-hidden p-0 sm:h-[44rem] sm:max-w-5xl sm:grid-rows-[auto_minmax(0,1fr)]">
81
+ <DialogHeader className="border-b border-border px-5 py-4 pr-12">
82
+ <DialogTitle>{title}</DialogTitle>
83
+ <DialogDescription className={description ? undefined : 'sr-only'}>
84
+ {description ?? title}
85
+ </DialogDescription>
86
+ </DialogHeader>
87
+
88
+ {open ? (
89
+ <ServerPathPickerSession
90
+ key={sessionKey}
91
+ currentPath={normalizedCurrentPath}
92
+ defaultWorkspacePath={normalizedDefaultWorkspacePath}
93
+ isSaving={isSaving}
94
+ onCancel={() => onOpenChange(false)}
95
+ onConfirm={onConfirm}
96
+ pathLabel={pathLabel}
97
+ pathPlaceholder={pathPlaceholder}
98
+ confirmLabel={confirmLabel}
99
+ hint={hint}
100
+ />
101
+ ) : null}
102
+ </DialogContent>
103
+ </Dialog>
104
+ );
105
+ }
106
+
107
+ type ServerPathPickerSessionProps = {
108
+ currentPath: string;
109
+ defaultWorkspacePath: string;
110
+ isSaving: boolean;
111
+ onCancel: () => void;
112
+ onConfirm: (path: string) => Promise<void> | void;
113
+ pathLabel: string;
114
+ pathPlaceholder?: string;
115
+ confirmLabel: string;
116
+ hint?: string;
117
+ };
118
+
119
+ function ServerPathPickerSession({
120
+ currentPath,
121
+ defaultWorkspacePath,
122
+ isSaving,
123
+ onCancel,
124
+ onConfirm,
125
+ pathLabel,
126
+ pathPlaceholder,
127
+ confirmLabel,
128
+ hint,
129
+ }: ServerPathPickerSessionProps) {
130
+ const initialPath = currentPath || defaultWorkspacePath;
131
+ const [selectedPath, setSelectedPath] = useState<string | null>(initialPath || null);
132
+ const [browsePath, setBrowsePath] = useState<string | null>(initialPath || null);
133
+ const [backPaths, setBackPaths] = useState<string[]>([]);
134
+ const [forwardPaths, setForwardPaths] = useState<string[]>([]);
135
+ const [newFolderName, setNewFolderName] = useState('');
136
+ const [newFolderVisible, setNewFolderVisible] = useState(false);
137
+ const [searchText, setSearchText] = useState('');
58
138
 
59
139
  const browseQuery = useServerPathBrowse({
60
140
  path: browsePath,
61
- enabled: open,
141
+ enabled: true,
62
142
  });
63
-
64
- useEffect(() => {
65
- if (!open || !browseQuery.data) {
66
- return;
67
- }
68
- if (draftPath.trim().length === 0) {
69
- setDraftPath(browseQuery.data.currentPath);
70
- }
71
- }, [browseQuery.data, draftPath, open]);
72
-
73
- const normalizedDraftPath = draftPath.trim();
74
- const submitDisabled = normalizedDraftPath.length === 0 || isSaving;
143
+ const createDirectoryMutation = useServerPathCreateDirectory();
144
+ const resolvedBrowsePath = resolveHomeRelativePath(
145
+ browsePath?.trim() ?? '',
146
+ browseQuery.data?.homePath,
147
+ );
148
+ const canonicalSelectedPath =
149
+ selectedPath === null
150
+ ? (browseQuery.data?.currentPath ?? '')
151
+ : selectedPath === browsePath && browseQuery.data?.currentPath === resolvedBrowsePath
152
+ ? browseQuery.data.currentPath
153
+ : selectedPath;
154
+ const normalizedSelectedPath = canonicalSelectedPath.trim();
155
+ const submitDisabled = normalizedSelectedPath.length === 0 || isSaving;
156
+ const addressPath = resolvedBrowsePath || browseQuery.data?.currentPath || '';
157
+ const visibleBreadcrumbs =
158
+ !browsePath || browseQuery.data?.currentPath === addressPath
159
+ ? (browseQuery.data?.breadcrumbs ?? [])
160
+ : [];
75
161
  const errorMessage = useMemo(() => {
76
162
  if (!browseQuery.error) {
77
163
  return null;
@@ -80,14 +166,25 @@ export function ServerPathPickerDialog({
80
166
  ? browseQuery.error.message
81
167
  : String(browseQuery.error);
82
168
  }, [browseQuery.error]);
169
+ const createDirectoryErrorMessage = useMemo(() => {
170
+ if (!createDirectoryMutation.error) {
171
+ return null;
172
+ }
173
+ return createDirectoryMutation.error instanceof Error
174
+ ? createDirectoryMutation.error.message
175
+ : String(createDirectoryMutation.error);
176
+ }, [createDirectoryMutation.error]);
83
177
 
84
178
  const normalizedSearchText = searchText.trim().toLowerCase();
179
+ const directoryEntries = useMemo(
180
+ () => (browseQuery.data?.entries ?? []).filter((entry) => entry.kind === 'directory'),
181
+ [browseQuery.data?.entries],
182
+ );
85
183
  const filteredEntries = useMemo(() => {
86
- const entries = browseQuery.data?.entries ?? [];
87
184
  if (normalizedSearchText.length === 0) {
88
- return entries;
185
+ return directoryEntries;
89
186
  }
90
- return entries.filter((entry) => {
187
+ return directoryEntries.filter((entry) => {
91
188
  const normalizedName = entry.name.toLowerCase();
92
189
  const normalizedPath = entry.path.toLowerCase();
93
190
  return (
@@ -95,188 +192,182 @@ export function ServerPathPickerDialog({
95
192
  normalizedPath.includes(normalizedSearchText)
96
193
  );
97
194
  });
98
- }, [browseQuery.data?.entries, normalizedSearchText]);
195
+ }, [directoryEntries, normalizedSearchText]);
99
196
 
100
- const navigateTo = (path: string | null) => {
197
+ const showPath = (path: string) => {
101
198
  setBrowsePath(path);
199
+ setSelectedPath(path);
102
200
  setSearchText('');
103
- if (path) {
104
- setDraftPath(path);
201
+ setNewFolderVisible(false);
202
+ setNewFolderName('');
203
+ createDirectoryMutation.reset();
204
+ };
205
+
206
+ const navigateTo = (path: string) => {
207
+ const nextPath = path.trim();
208
+ if (!nextPath) {
209
+ return;
210
+ }
211
+ const visiblePath = addressPath;
212
+ if (visiblePath && visiblePath !== nextPath) {
213
+ setBackPaths((paths) => [...paths, visiblePath].slice(-50));
214
+ }
215
+ setForwardPaths([]);
216
+ showPath(nextPath);
217
+ };
218
+
219
+ const navigateBack = () => {
220
+ const targetPath = backPaths.at(-1);
221
+ if (!targetPath) {
222
+ return;
223
+ }
224
+ const visiblePath = addressPath;
225
+ setBackPaths((paths) => paths.slice(0, -1));
226
+ if (visiblePath) {
227
+ setForwardPaths((paths) => [visiblePath, ...paths].slice(0, 50));
228
+ }
229
+ showPath(targetPath);
230
+ };
231
+
232
+ const navigateForward = () => {
233
+ const targetPath = forwardPaths[0];
234
+ if (!targetPath) {
235
+ return;
236
+ }
237
+ const visiblePath = addressPath;
238
+ setForwardPaths((paths) => paths.slice(1));
239
+ if (visiblePath) {
240
+ setBackPaths((paths) => [...paths, visiblePath].slice(-50));
105
241
  }
242
+ showPath(targetPath);
106
243
  };
107
244
 
245
+ const createDirectory = async () => {
246
+ const parentPath = browseQuery.data?.currentPath;
247
+ const name = newFolderName.trim();
248
+ if (!parentPath || !name || createDirectoryMutation.isPending) {
249
+ return;
250
+ }
251
+ try {
252
+ const created = await createDirectoryMutation.mutateAsync({ parentPath, name });
253
+ await browseQuery.refetch();
254
+ setSelectedPath(created.path);
255
+ setNewFolderName('');
256
+ setNewFolderVisible(false);
257
+ } catch {
258
+ // The mutation owns the user-visible error state rendered below the input.
259
+ }
260
+ };
261
+
262
+ const emptyMessage = browseQuery.isLoading
263
+ ? t('loading')
264
+ : errorMessage
265
+ ? `${t('pathBrowseFailed')}: ${errorMessage}`
266
+ : directoryEntries.length > 0
267
+ ? t('pathPickerSearchEmpty')
268
+ : t('emptyDirectory');
269
+ const navigationDisabled = isSaving || browseQuery.isLoading;
270
+
108
271
  return (
109
- <Dialog
110
- open={open}
111
- onOpenChange={(nextOpen) => {
112
- if (isSaving) {
272
+ <form
273
+ className="flex min-h-0 min-w-0 flex-col overflow-hidden"
274
+ onSubmit={(event) => {
275
+ event.preventDefault();
276
+ if (submitDisabled) {
113
277
  return;
114
278
  }
115
- onOpenChange(nextOpen);
279
+ void onConfirm(normalizedSelectedPath);
116
280
  }}
117
281
  >
118
- <DialogContent className="max-h-[85vh] overflow-hidden sm:h-[42rem] sm:max-w-2xl sm:grid-rows-[auto_minmax(0,1fr)]">
119
- <DialogHeader>
120
- <DialogTitle>{title}</DialogTitle>
121
- {description ? (
122
- <DialogDescription>{description}</DialogDescription>
123
- ) : null}
124
- </DialogHeader>
125
- <form
126
- className="flex min-h-0 flex-col gap-4"
127
- onSubmit={(event) => {
128
- event.preventDefault();
129
- if (submitDisabled) {
130
- return;
131
- }
132
- void onConfirm(normalizedDraftPath);
133
- }}
134
- >
135
- <div className="space-y-2">
136
- <Label htmlFor="server-path-picker-input">{pathLabel}</Label>
137
- <div className="flex gap-2">
138
- <Input
139
- id="server-path-picker-input"
140
- value={draftPath}
141
- onChange={(event) => setDraftPath(event.target.value)}
142
- placeholder={pathPlaceholder}
143
- autoFocus
144
- disabled={isSaving}
145
- />
146
- <Button
147
- type="button"
148
- variant="outline"
149
- onClick={() => navigateTo(normalizedDraftPath || null)}
150
- disabled={isSaving}
151
- >
152
- {t('openPath')}
153
- </Button>
154
- </div>
155
- </div>
156
-
157
- <div className="flex min-h-0 flex-1 flex-col overflow-hidden rounded-2xl border border-gray-200 bg-gray-50/70">
158
- <div className="flex flex-wrap items-center gap-2 border-b border-gray-200 px-3 py-2">
159
- <Button
160
- type="button"
161
- variant="ghost"
162
- size="sm"
163
- onClick={() => navigateTo(browseQuery.data?.homePath ?? null)}
164
- disabled={isSaving || browseQuery.isLoading}
165
- >
166
- <Home className="mr-1 h-4 w-4" />
167
- {t('homeDirectory')}
168
- </Button>
169
- <Button
170
- type="button"
171
- variant="ghost"
172
- size="sm"
173
- onClick={() => navigateTo(browseQuery.data?.parentPath ?? null)}
174
- disabled={!browseQuery.data?.parentPath || isSaving || browseQuery.isLoading}
175
- >
176
- <FolderUp className="mr-1 h-4 w-4" />
177
- {t('parentDirectory')}
178
- </Button>
179
- <Button
180
- type="button"
181
- variant="ghost"
182
- size="sm"
183
- onClick={() => {
184
- void browseQuery.refetch();
185
- }}
186
- disabled={isSaving || browseQuery.isLoading}
187
- >
188
- <RefreshCcw className="mr-1 h-4 w-4" />
189
- {t('chatRefresh')}
190
- </Button>
191
- </div>
282
+ <ServerPathPickerNavigationBar
283
+ addressPath={addressPath}
284
+ breadcrumbs={visibleBreadcrumbs}
285
+ canGoBack={backPaths.length > 0}
286
+ canGoForward={forwardPaths.length > 0}
287
+ canGoUp={Boolean(browseQuery.data?.parentPath)}
288
+ disabled={navigationDisabled}
289
+ onBack={navigateBack}
290
+ onForward={navigateForward}
291
+ onNavigate={navigateTo}
292
+ onRefresh={() => void browseQuery.refetch()}
293
+ onSearchChange={setSearchText}
294
+ onUp={() => {
295
+ const parentPath = browseQuery.data?.parentPath;
296
+ if (parentPath) {
297
+ navigateTo(parentPath);
298
+ }
299
+ }}
300
+ searchText={searchText}
301
+ />
192
302
 
193
- <div className="border-b border-gray-200 px-3 py-2">
194
- <div className="mb-2 text-xs font-medium text-gray-500">
195
- {t('currentDirectory')}
196
- </div>
197
- <div className="flex flex-wrap items-center gap-1 text-xs text-gray-600">
198
- {browseQuery.data?.breadcrumbs.map((breadcrumb, index) => (
199
- <div key={breadcrumb.path} className="flex items-center gap-1">
200
- <button
201
- type="button"
202
- className="rounded px-1.5 py-0.5 hover:bg-gray-200"
203
- onClick={() => navigateTo(breadcrumb.path)}
204
- disabled={isSaving}
205
- >
206
- {breadcrumb.label}
207
- </button>
208
- {index < browseQuery.data.breadcrumbs.length - 1 ? (
209
- <ChevronRight className="h-3 w-3 text-gray-400" />
210
- ) : null}
211
- </div>
212
- ))}
213
- </div>
214
- </div>
303
+ <ServerPathPickerToolbar
304
+ disabled={navigationDisabled || !browseQuery.data?.currentPath}
305
+ onNewFolder={() => {
306
+ setNewFolderVisible(true);
307
+ setNewFolderName('');
308
+ createDirectoryMutation.reset();
309
+ }}
310
+ />
215
311
 
216
- <div className="border-b border-gray-200 px-3 py-2">
217
- <div className="relative">
218
- <Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
219
- <Input
220
- value={searchText}
221
- onChange={(event) => setSearchText(event.target.value)}
222
- placeholder={t('pathPickerSearchPlaceholder')}
223
- disabled={isSaving || browseQuery.isLoading}
224
- className="pl-9"
225
- />
226
- </div>
227
- </div>
228
-
229
- <ScrollArea className="min-h-0 flex-1 px-2 py-2">
230
- {browseQuery.isLoading ? (
231
- <div className="px-2 py-6 text-sm text-gray-500">{t('loading')}</div>
232
- ) : errorMessage ? (
233
- <div className="px-2 py-4 text-sm text-destructive">
234
- {t('pathBrowseFailed')}: {errorMessage}
235
- </div>
236
- ) : browseQuery.data && filteredEntries.length > 0 ? (
237
- <div className="space-y-1">
238
- {filteredEntries.map((entry) => (
239
- <button
240
- key={entry.path}
241
- type="button"
242
- className="flex w-full items-center gap-2 rounded-xl px-3 py-2 text-left text-sm text-gray-700 transition-colors hover:bg-white"
243
- onClick={() => navigateTo(entry.path)}
244
- disabled={isSaving}
245
- >
246
- <Folder className="h-4 w-4 shrink-0 text-emerald-600" />
247
- <span className="truncate">{entry.name}</span>
248
- </button>
249
- ))}
250
- </div>
251
- ) : browseQuery.data && browseQuery.data.entries.length > 0 ? (
252
- <div className="px-2 py-6 text-sm text-gray-500">
253
- {t('pathPickerSearchEmpty')}
254
- </div>
255
- ) : (
256
- <div className="px-2 py-6 text-sm text-gray-500">{t('emptyDirectory')}</div>
257
- )}
258
- </ScrollArea>
312
+ <div className="relative border-b border-border p-2 sm:hidden">
313
+ <Search className="pointer-events-none absolute left-5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
314
+ <Input
315
+ value={searchText}
316
+ onChange={(event) => setSearchText(event.target.value)}
317
+ placeholder={t('pathPickerSearchPlaceholder')}
318
+ disabled={navigationDisabled}
319
+ className="h-9 pl-9"
320
+ />
259
321
  </div>
260
322
 
261
- {hint ? (
262
- <p className="text-xs leading-relaxed text-gray-500">{hint}</p>
323
+ {newFolderVisible ? (
324
+ <ServerPathPickerNewFolder
325
+ errorMessage={createDirectoryErrorMessage}
326
+ isCreating={createDirectoryMutation.isPending}
327
+ name={newFolderName}
328
+ onCancel={() => setNewFolderVisible(false)}
329
+ onCreate={() => void createDirectory()}
330
+ onNameChange={(name) => {
331
+ setNewFolderName(name);
332
+ createDirectoryMutation.reset();
333
+ }}
334
+ />
263
335
  ) : null}
264
336
 
265
- <DialogFooter>
266
- <Button
267
- type="button"
268
- variant="outline"
269
- onClick={() => onOpenChange(false)}
337
+ <div className="flex min-h-0 min-w-0 flex-1 overflow-hidden">
338
+ <ServerPathPickerLocations
339
+ currentPath={addressPath}
340
+ defaultWorkspacePath={
341
+ resolveHomeRelativePath(defaultWorkspacePath, browseQuery.data?.homePath) ||
342
+ null
343
+ }
344
+ disabled={navigationDisabled}
345
+ homePath={browseQuery.data?.homePath}
346
+ locations={browseQuery.data?.locations ?? []}
347
+ rootPath={browseQuery.data?.breadcrumbs[0]?.path}
348
+ onNavigate={navigateTo}
349
+ />
350
+ <ServerPathPickerDirectoryList
351
+ entries={filteredEntries}
352
+ emptyMessage={emptyMessage}
270
353
  disabled={isSaving}
271
- >
272
- {t('cancel')}
273
- </Button>
274
- <Button type="submit" disabled={submitDisabled}>
275
- {isSaving ? t('saving') : confirmLabel}
276
- </Button>
277
- </DialogFooter>
278
- </form>
279
- </DialogContent>
280
- </Dialog>
354
+ onOpen={navigateTo}
355
+ onSelect={setSelectedPath}
356
+ selectedPath={normalizedSelectedPath}
357
+ />
358
+ </div>
359
+
360
+ <ServerPathPickerFooter
361
+ confirmLabel={confirmLabel}
362
+ disabled={submitDisabled}
363
+ hint={hint}
364
+ isSaving={isSaving}
365
+ onCancel={onCancel}
366
+ onSelectedPathChange={setSelectedPath}
367
+ pathLabel={pathLabel}
368
+ pathPlaceholder={pathPlaceholder}
369
+ selectedPath={canonicalSelectedPath}
370
+ />
371
+ </form>
281
372
  );
282
373
  }
@@ -0,0 +1,103 @@
1
+ import type { KeyboardEvent } from 'react';
2
+ import { Folder } from 'lucide-react';
3
+ import type { ServerPathEntryView } from '@/shared/lib/api';
4
+ import { ScrollArea } from '@/shared/components/ui/scroll-area';
5
+ import { cn } from '@/shared/lib/utils';
6
+ import { t } from '@/shared/lib/i18n';
7
+
8
+ type ServerPathPickerDirectoryListProps = {
9
+ entries: readonly ServerPathEntryView[];
10
+ emptyMessage: string;
11
+ disabled: boolean;
12
+ onOpen: (path: string) => void;
13
+ onSelect: (path: string) => void;
14
+ selectedPath: string;
15
+ };
16
+
17
+ export function ServerPathPickerDirectoryList({
18
+ entries,
19
+ emptyMessage,
20
+ disabled,
21
+ onOpen,
22
+ onSelect,
23
+ selectedPath,
24
+ }: ServerPathPickerDirectoryListProps) {
25
+ const moveSelection = (
26
+ event: KeyboardEvent<HTMLButtonElement>,
27
+ direction: -1 | 1,
28
+ ) => {
29
+ event.preventDefault();
30
+ const rows = Array.from(
31
+ event.currentTarget.parentElement?.querySelectorAll<HTMLButtonElement>(
32
+ '[data-server-path-row]',
33
+ ) ?? [],
34
+ );
35
+ const currentIndex = rows.indexOf(event.currentTarget);
36
+ const nextRow = rows[currentIndex + direction];
37
+ if (!nextRow) {
38
+ return;
39
+ }
40
+ nextRow.focus();
41
+ nextRow.click();
42
+ };
43
+
44
+ return (
45
+ <div className="flex min-h-0 flex-1 flex-col bg-background">
46
+ <div className="grid grid-cols-[minmax(0,1fr)_8rem] border-b border-border bg-muted/20 px-3 py-1.5 text-xs text-muted-foreground">
47
+ <span>{t('pathPickerNameColumn')}</span>
48
+ <span>{t('pathPickerTypeColumn')}</span>
49
+ </div>
50
+ <ScrollArea className="min-h-0 flex-1">
51
+ {entries.length > 0 ? (
52
+ <div role="listbox" aria-label={t('pathPickerDirectoryList')} className="p-1.5">
53
+ {entries.map((entry) => {
54
+ const selected = selectedPath === entry.path;
55
+ return (
56
+ <button
57
+ key={entry.path}
58
+ type="button"
59
+ role="option"
60
+ aria-selected={selected}
61
+ data-server-path-row
62
+ className={cn(
63
+ 'grid w-full grid-cols-[minmax(0,1fr)_8rem] items-center rounded-sm border border-transparent px-2 py-1.5 text-left text-sm text-foreground outline-none transition-colors',
64
+ selected
65
+ ? 'border-primary/25 bg-primary/15'
66
+ : 'hover:bg-accent/70 focus-visible:border-primary/35 focus-visible:bg-accent/70',
67
+ )}
68
+ onClick={() => onSelect(entry.path)}
69
+ onDoubleClick={() => onOpen(entry.path)}
70
+ onKeyDown={(event) => {
71
+ if (event.key === 'Enter') {
72
+ event.preventDefault();
73
+ onOpen(entry.path);
74
+ }
75
+ if (event.key === 'ArrowDown') {
76
+ moveSelection(event, 1);
77
+ }
78
+ if (event.key === 'ArrowUp') {
79
+ moveSelection(event, -1);
80
+ }
81
+ }}
82
+ disabled={disabled}
83
+ >
84
+ <span className="flex min-w-0 items-center gap-2">
85
+ <Folder className="h-4 w-4 shrink-0 fill-amber-300 text-amber-500" />
86
+ <span className="truncate">{entry.name}</span>
87
+ </span>
88
+ <span className="truncate text-xs text-muted-foreground">
89
+ {t('pathPickerDirectoryType')}
90
+ </span>
91
+ </button>
92
+ );
93
+ })}
94
+ </div>
95
+ ) : (
96
+ <div className="grid h-full min-h-48 place-items-center px-6 text-center text-sm text-muted-foreground">
97
+ {emptyMessage}
98
+ </div>
99
+ )}
100
+ </ScrollArea>
101
+ </div>
102
+ );
103
+ }