@nextclaw/ui 0.15.9 → 0.15.11

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 (360) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/assets/{abnfDiagram-VRR7QNED-BYpK0F5F.js → abnfDiagram-VRR7QNED-M4g_AyR8.js} +1 -1
  3. package/dist/assets/{aiden0z-pptx-renderer.es-D87ywFdC.js → aiden0z-pptx-renderer.es-Bd99vut2.js} +1 -1
  4. package/dist/assets/api-pbx14YAL.js +13 -0
  5. package/dist/assets/app-presenter-provider-Q6u2xBAn.js +106 -0
  6. package/dist/assets/appearance-settings-page-D9wJdf-0.js +1 -0
  7. package/dist/assets/{arc-DFELHWM2.js → arc-IO7WNWOB.js} +1 -1
  8. package/dist/assets/architecture-TIHT7OUA-Cii4JyA2.js +1 -0
  9. package/dist/assets/{architectureDiagram-ZJ3FMSHR-CvRhdovL.js → architectureDiagram-ZJ3FMSHR-Dc-lvdUh.js} +1 -1
  10. package/dist/assets/{blockDiagram-677ZJIJ3-DVYNkbR_.js → blockDiagram-677ZJIJ3-CcsoWpIY.js} +1 -1
  11. package/dist/assets/{book-open-w3160PZf.js → book-open-DZyssdCc.js} +1 -1
  12. package/dist/assets/{c4Diagram-LMCZKHZV-B1w-iwRe.js → c4Diagram-LMCZKHZV-Bsrtg6b2.js} +1 -1
  13. package/dist/assets/card-D7l-gWkY.js +1 -0
  14. package/dist/assets/channel-BSKkTPe8.js +1 -0
  15. package/dist/assets/channels-list-page-B3qW3T-B.js +8 -0
  16. package/dist/assets/chat-page-DUI_Jm6r.js +1 -0
  17. package/dist/assets/chunk-2Q5K7J3B-CDMOR1xZ.js +1 -0
  18. package/dist/assets/{chunk-32BRIVSS-kG3lg0bC.js → chunk-32BRIVSS-Bt08t8oG.js} +1 -1
  19. package/dist/assets/{chunk-52WLFC77-BLeB7iGk.js → chunk-52WLFC77-Ddx68Xzx.js} +1 -1
  20. package/dist/assets/{chunk-5FCAYU7R-BQga1Rr7.js → chunk-5FCAYU7R-B7p9_q55.js} +1 -1
  21. package/dist/assets/{chunk-5HE753X5-DU81hkcF.js → chunk-5HE753X5-D52KvdTm.js} +1 -1
  22. package/dist/assets/{chunk-5JV3BV7I-C2bAx0m7.js → chunk-5JV3BV7I-C329h8nI.js} +1 -1
  23. package/dist/assets/{chunk-5TONJI2A-BExIIgWY.js → chunk-5TONJI2A-BZyUBxWt.js} +1 -1
  24. package/dist/assets/{chunk-5VM5RSS4-C3XB05yh.js → chunk-5VM5RSS4-BUiYXZmu.js} +1 -1
  25. package/dist/assets/{chunk-7BUUIJ7U-JiyRtrGu.js → chunk-7BUUIJ7U-B7o-mn4r.js} +1 -1
  26. package/dist/assets/{chunk-BIQX33UG-BZRV20pI.js → chunk-BIQX33UG-Bey9GoRP.js} +1 -1
  27. package/dist/assets/{chunk-C7G6YPKG-AsyDSQ9j.js → chunk-C7G6YPKG-C6tosszM.js} +1 -1
  28. package/dist/assets/{chunk-CQNSW5MT-iN-fiXVA.js → chunk-CQNSW5MT-BNrdkgGQ.js} +1 -1
  29. package/dist/assets/{chunk-CYSBUYHQ-DhC_TRzI.js → chunk-CYSBUYHQ-DHODFeyB.js} +1 -1
  30. package/dist/assets/{chunk-EMLP6XTP-QFLK8R6d.js → chunk-EMLP6XTP-BcmLjrcq.js} +1 -1
  31. package/dist/assets/{chunk-EX3LRPZG-DzUwhkCz.js → chunk-EX3LRPZG-r-_eEtJI.js} +1 -1
  32. package/dist/assets/{chunk-FWX5IMBZ-DFelpZBy.js → chunk-FWX5IMBZ-DOObDWbf.js} +2 -2
  33. package/dist/assets/{chunk-HOUHSVGY-BMPwFEwN.js → chunk-HOUHSVGY-Bd0FnbWZ.js} +1 -1
  34. package/dist/assets/{chunk-ICXQ74PX-DH8NJMok.js → chunk-ICXQ74PX-ntNwAm4_.js} +1 -1
  35. package/dist/assets/{chunk-JG7HCLWE-D-95AsO4.js → chunk-JG7HCLWE-YtRpblZN.js} +1 -1
  36. package/dist/assets/{chunk-JWPE2WC7-DIaTv8kx.js → chunk-JWPE2WC7-De5-D9ta.js} +1 -1
  37. package/dist/assets/{chunk-MOJQB5TN-BPFLzWgj.js → chunk-MOJQB5TN-CdvKhyp2.js} +1 -1
  38. package/dist/assets/{chunk-MOZMSUNE-DR_fzxG0.js → chunk-MOZMSUNE-DJMMN_lu.js} +1 -1
  39. package/dist/assets/{chunk-OGEWGWER-TIcEoPPH.js → chunk-OGEWGWER-DOFWVw3O.js} +1 -1
  40. package/dist/assets/{chunk-OSBZ3O6U-BALNvSfG.js → chunk-OSBZ3O6U-DZkrIN9r.js} +1 -1
  41. package/dist/assets/{chunk-PUDLZKDR-I-EXhf_k.js → chunk-PUDLZKDR-D1wma2Yf.js} +1 -1
  42. package/dist/assets/{chunk-Q4XR5HBZ-uvIGstBS.js → chunk-Q4XR5HBZ-CjPy94Lz.js} +1 -1
  43. package/dist/assets/{chunk-QBLGF6JB-r-TjLdxa.js → chunk-QBLGF6JB-Hy1bEgS-.js} +1 -1
  44. package/dist/assets/{chunk-R7FJI6CG-N-1LDgvy.js → chunk-R7FJI6CG-CUPIHyh-.js} +1 -1
  45. package/dist/assets/{chunk-RYQCIY6F-jxNASx65.js → chunk-RYQCIY6F-D5Ua4pcv.js} +1 -1
  46. package/dist/assets/{chunk-U6XO7XAA-Cphbuc7q.js → chunk-U6XO7XAA-DaUQcqb7.js} +1 -1
  47. package/dist/assets/{chunk-V7JOEXUC-BLEuBf1s.js → chunk-V7JOEXUC-DX2zLfQe.js} +1 -1
  48. package/dist/assets/chunk-VAUOI2AC-BRvqNW76.js +1 -0
  49. package/dist/assets/{chunk-VR4S4FIN-BAUbR0ke.js → chunk-VR4S4FIN-CFA-WQik.js} +1 -1
  50. package/dist/assets/{chunk-WYO6CB5R-Do6SJrnY.js → chunk-WYO6CB5R-Bk2HeDvJ.js} +2 -2
  51. package/dist/assets/chunk-XXDRQBXY-CwX_mqJ3.js +1 -0
  52. package/dist/assets/{chunk-YOTPTUD7-CQqvMTCr.js → chunk-YOTPTUD7-DUG4FpBN.js} +1 -1
  53. package/dist/assets/{chunk-ZGVPDNZ5-B7kWWaXv.js → chunk-ZGVPDNZ5-70JdIGo0.js} +1 -1
  54. package/dist/assets/{chunk-ZIRB5QZD-DTU0cDNh.js → chunk-ZIRB5QZD-XZdgBt5L.js} +1 -1
  55. package/dist/assets/classDiagram-OUVF2IWQ-RzbGxG77.js +1 -0
  56. package/dist/assets/classDiagram-v2-EOCWNBFH-C_gWB7WT.js +1 -0
  57. package/dist/assets/config-split-page-DFEKStD7.js +1 -0
  58. package/dist/assets/confirm-dialog-7wpxfMCp.js +5 -0
  59. package/dist/assets/{cose-bilkent-JH36ORCC-B9LrtV-q.js → cose-bilkent-JH36ORCC-B-PQoGX-.js} +1 -1
  60. package/dist/assets/cynefin-VYW2F7L2-fSLIS0X3.js +1 -0
  61. package/dist/assets/{cynefinDiagram-TSTJHNR4-BfjvW4su.js → cynefinDiagram-TSTJHNR4-BoLCPEM0.js} +1 -1
  62. package/dist/assets/{dagre-DkiXZKkL.js → dagre-D7Ie4eXr.js} +1 -1
  63. package/dist/assets/{dagre-VKFMJZFB-B1pQKQSY.js → dagre-VKFMJZFB-CKRrwZA-.js} +1 -1
  64. package/dist/assets/desktop-update-config-DP6IbxBO.js +1 -0
  65. package/dist/assets/{diagram-FQU43EPY-cB4EZdaP.js → diagram-FQU43EPY-CASL1J3_.js} +1 -1
  66. package/dist/assets/{diagram-G47NLZAW-CtW8im1B.js → diagram-G47NLZAW-CvLPBIzf.js} +1 -1
  67. package/dist/assets/{diagram-NH7WQ7WH-BDCtrCCm.js → diagram-NH7WQ7WH-Ba6fGdYP.js} +1 -1
  68. package/dist/assets/{diagram-OA4YK3LP-DEMJ42Gt.js → diagram-OA4YK3LP-DkpfgEjM.js} +1 -1
  69. package/dist/assets/{diagram-WEI45ONY-CdCcxSYU.js → diagram-WEI45ONY-BP5UMVsN.js} +1 -1
  70. package/dist/assets/{dist-DEJlYi0E.js → dist-B9UN0HrS.js} +1 -1
  71. package/dist/assets/doc-browser-D7klPMHs.js +1 -0
  72. package/dist/assets/{doc-browser-context-CpvHiuQG.js → doc-browser-context-Bw9m83Jp.js} +1 -1
  73. package/dist/assets/doc-browser-wgEeLkn1.js +1 -0
  74. package/dist/assets/{docx-preview-zis3U0Fa.js → docx-preview-D0tp3a9T.js} +1 -1
  75. package/dist/assets/{ebnfDiagram-CCIWWBDH-DBZETBwE.js → ebnfDiagram-CCIWWBDH-TJKbUcmc.js} +1 -1
  76. package/dist/assets/{erDiagram-Q63AITRT-y2zDXprP.js → erDiagram-Q63AITRT-DzGPKkKH.js} +1 -1
  77. package/dist/assets/eventmodeling-45OFAUF4-XVcQGgha.js +1 -0
  78. package/dist/assets/eye-IVMB0Rrz.js +1 -0
  79. package/dist/assets/flowDiagram-23GEKE2U-BiFSctqz.js +1 -0
  80. package/dist/assets/{form-actions-5OsDRt0g.js → form-actions-DrFAgO58.js} +1 -1
  81. package/dist/assets/{ganttDiagram-NO4QXBWP-D2DP344e.js → ganttDiagram-NO4QXBWP-JhJzsGp2.js} +1 -1
  82. package/dist/assets/gitGraph-TEB2WS4Q-C014uXqS.js +1 -0
  83. package/dist/assets/{gitGraphDiagram-IHSO6WYX-D37uiwA2.js → gitGraphDiagram-IHSO6WYX-DZ1CKMj8.js} +1 -1
  84. package/dist/assets/index-D297NGOb.js +2 -0
  85. package/dist/assets/index-DqZoodZL.css +1 -0
  86. package/dist/assets/info-DKCQHKI2-CQ2qjGhU.js +1 -0
  87. package/dist/assets/{infoDiagram-FWYZ7A6U-CG-WAIRi.js → infoDiagram-FWYZ7A6U-BNXNKfRQ.js} +1 -1
  88. package/dist/assets/{ishikawaDiagram-FXEZZL3T-WeCTnW0f.js → ishikawaDiagram-FXEZZL3T-DoiEfjNw.js} +1 -1
  89. package/dist/assets/{journeyDiagram-5HDEW3XC-DUFBW9Td.js → journeyDiagram-5HDEW3XC-D6oK2HSy.js} +1 -1
  90. package/dist/assets/{kanban-definition-HUTT4EX6-B0lxgMpg.js → kanban-definition-HUTT4EX6-B2UnLOH-.js} +1 -1
  91. package/dist/assets/{line-B78frP_c.js → line-BM7PoxY5.js} +1 -1
  92. package/dist/assets/{linear-C6lVEcb2.js → linear-B3DiqMjk.js} +1 -1
  93. package/dist/assets/loader-circle-DDgFo4Kv.js +1 -0
  94. package/dist/assets/{logo-badge-TdPa95AC.js → logo-badge-DoZOVuPr.js} +1 -1
  95. package/dist/assets/{map-Bdri5UL5.js → map-BAAdHTqR.js} +1 -1
  96. package/dist/assets/mcp-marketplace-page-BtNExfMn.js +1 -0
  97. package/dist/assets/{mcp-marketplace-page-cm3ifaKu.js → mcp-marketplace-page-C151EGP-.js} +2 -2
  98. package/dist/assets/{mermaid-parser.core-Cue18vLo.js → mermaid-parser.core-BeMviefY.js} +2 -2
  99. package/dist/assets/{mermaid.core-CXwWRh5i.js → mermaid.core-D0Q77r9I.js} +3 -3
  100. package/dist/assets/{mindmap-definition-LN4V7U3C-CFxz0P0-.js → mindmap-definition-LN4V7U3C-BQ7r7bR8.js} +1 -1
  101. package/dist/assets/model-config-page-BmfQzc52.js +1 -0
  102. package/dist/assets/navigation-link-CJw6FLOj.js +1 -0
  103. package/dist/assets/{ordinal-Cbd8t4LB.js → ordinal-Bt2N4IkA.js} +1 -1
  104. package/dist/assets/packet-7NZHBO7P-6IFyydij.js +1 -0
  105. package/dist/assets/{pegDiagram-2B236MQR-B_Qt9zyT.js → pegDiagram-2B236MQR-B-C9-LGz.js} +1 -1
  106. package/dist/assets/pie-RZYD4A2V-spXQC_wr.js +1 -0
  107. package/dist/assets/{pieDiagram-ENE6RG2P-CRQdSuwS.js → pieDiagram-ENE6RG2P-CtirZLtn.js} +1 -1
  108. package/dist/assets/plus-CYxLSfLh.js +1 -0
  109. package/dist/assets/{popover-EulxhA0q.js → popover-DitLJE6k.js} +1 -1
  110. package/dist/assets/provider-scoped-model-input-CuAtduIc.js +1 -0
  111. package/dist/assets/providers-config-page-CrmuY4DC.js +1 -0
  112. package/dist/assets/{quadrantDiagram-ABIIQ3AL-D5kPpttq.js → quadrantDiagram-ABIIQ3AL-DiBWAzDU.js} +1 -1
  113. package/dist/assets/radar-I7S5WNFK-DWVA8lvY.js +1 -0
  114. package/dist/assets/railroad-3IZDKUUU-Ce51u8y1.js +1 -0
  115. package/dist/assets/railroad-abnf-AHOZXSZD-D7i7cqI_.js +1 -0
  116. package/dist/assets/railroad-ebnf-EBAXGLYW-BEg2hcPs.js +1 -0
  117. package/dist/assets/railroad-peg-LSFZ7HO6-CxROSzbi.js +1 -0
  118. package/dist/assets/{railroadDiagram-RFXS5EU6-DGh_TGlQ.js → railroadDiagram-RFXS5EU6-CP6b3PBI.js} +1 -1
  119. package/dist/assets/{react-Bb0EYsLS.js → react-CTDaaQ9V.js} +1 -1
  120. package/dist/assets/{refresh-cw-CexL11m1.js → refresh-cw-E5qTlQY6.js} +1 -1
  121. package/dist/assets/remote-YzPpOPmK.js +1 -0
  122. package/dist/assets/{requirementDiagram-TGXJPOKE-EmFph0H_.js → requirementDiagram-TGXJPOKE-D8YV0-Jz.js} +1 -1
  123. package/dist/assets/rotate-cw-BXeiDT6C.js +1 -0
  124. package/dist/assets/runtime-config-page-1fPJJvo5.js +1 -0
  125. package/dist/assets/{sankeyDiagram-HTMAVEWB-BXWB5Ugi.js → sankeyDiagram-HTMAVEWB-C2vA4ojE.js} +1 -1
  126. package/dist/assets/{save-DtccrZld.js → save-CTiZvAd2.js} +1 -1
  127. package/dist/assets/{search-BArc_WOX.js → search-CztGmpnR.js} +1 -1
  128. package/dist/assets/search-config-page-D3jVALZU.js +1 -0
  129. package/dist/assets/secrets-config-page-CxWLQCjZ.js +3 -0
  130. package/dist/assets/security-config-DEyj4WBh.js +1 -0
  131. package/dist/assets/{select-C9FB0jlt.js → select-BPVI2dQ_.js} +1 -1
  132. package/dist/assets/{sequenceDiagram-DBY2YBRQ-ClKnO7v0.js → sequenceDiagram-DBY2YBRQ-B9LBmpUo.js} +1 -1
  133. package/dist/assets/settings-2-Cd7QaBsq.js +1 -0
  134. package/dist/assets/{sizeCapture-X5ZJPWSS-DSo-t5ou.js → sizeCapture-X5ZJPWSS-9UIfya9-.js} +1 -1
  135. package/dist/assets/skeleton-CUpAXGoe.js +1 -0
  136. package/dist/assets/{src-Dad2ZbCt.js → src-DKf77Yzo.js} +1 -1
  137. package/dist/assets/{stateDiagram-2N3HPSRC-BvAalBfz.js → stateDiagram-2N3HPSRC-oTW44vds.js} +1 -1
  138. package/dist/assets/stateDiagram-v2-6OUMAXLB-Bui4RXQK.js +1 -0
  139. package/dist/assets/{swimlanes-5IMT3BWC-DdVqVa1a.js → swimlanes-5IMT3BWC-Cf7lcsay.js} +2 -2
  140. package/dist/assets/swimlanesDiagram-G3AALYLV-DTwUsCBz.js +8 -0
  141. package/dist/assets/{tabs-D_mjyOOf.js → tabs-DrUmYwEQ.js} +1 -1
  142. package/dist/assets/{tag-chip-C011rd9q.js → tag-chip-CmViAZKK.js} +1 -1
  143. package/dist/assets/{timeline-definition-FHXFAJF6-D4ar8IQY.js → timeline-definition-FHXFAJF6-DoCuWKDJ.js} +1 -1
  144. package/dist/assets/{trash-2-bDhfsO1O.js → trash-2-DMRJ63AB.js} +1 -1
  145. package/dist/assets/treeView-QDETBFTQ-D5zsTiuL.js +1 -0
  146. package/dist/assets/treemap-6X3UGDF4-CR3dxqNs.js +1 -0
  147. package/dist/assets/{use-config-BNrlfEck.js → use-config-gpXy2Iyc.js} +1 -1
  148. package/dist/assets/use-confirm-dialog-Du33bHPz.js +1 -0
  149. package/dist/assets/{use-viewport-layout-P0q6Nt_N.js → use-viewport-layout-D80O1d90.js} +1 -1
  150. package/dist/assets/{vennDiagram-L72KCM5P-CDXJ_i3p.js → vennDiagram-L72KCM5P-CpU0pzGe.js} +1 -1
  151. package/dist/assets/wardley-OPB4EBWU-LatRT_Lx.js +1 -0
  152. package/dist/assets/{wardleyDiagram-EHGQE667-D-dSuT_7.js → wardleyDiagram-EHGQE667-CkN6nn6-.js} +1 -1
  153. package/dist/assets/x-CsLMuNFe.js +1 -0
  154. package/dist/assets/{xychartDiagram-FW5EYKEG-C9JCzsZF.js → xychartDiagram-FW5EYKEG-DATpapm2.js} +1 -1
  155. package/dist/index.html +31 -32
  156. package/dist/runtime-icons/opencode.svg +16 -0
  157. package/package.json +10 -9
  158. package/public/runtime-icons/opencode.svg +16 -0
  159. package/src/app/components/layout/__tests__/settings-entry-page.test.tsx +6 -4
  160. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +24 -12
  161. package/src/app/components/layout/sidebar-items.tsx +11 -2
  162. package/src/app/components/layout/sidebar.tsx +33 -234
  163. package/src/app/configs/app-navigation.config.ts +10 -24
  164. package/src/app/index.tsx +0 -5
  165. package/src/features/channels/components/config/channel-form.tsx +26 -26
  166. package/src/features/channels/components/config/weixin-channel-auth-section.tsx +6 -7
  167. package/src/features/channels/pages/__tests__/channels-list-page.test.tsx +2 -2
  168. package/src/features/channels/pages/channels-list-page.tsx +33 -32
  169. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +68 -7
  170. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +27 -0
  171. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +0 -8
  172. package/src/features/chat/components/conversation/chat-conversation-content.tsx +65 -21
  173. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +36 -12
  174. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +0 -7
  175. package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +0 -1
  176. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +17 -8
  177. package/src/features/chat/components/layout/chat-page-shell.tsx +2 -2
  178. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +2 -23
  179. package/src/features/chat/components/layout/chat-sidebar.tsx +3 -12
  180. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +155 -93
  181. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +126 -93
  182. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +8 -0
  183. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +27 -13
  184. package/src/features/chat/features/message/components/__tests__/chat-inline-file-preview.test.tsx +15 -9
  185. package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +127 -49
  186. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +187 -20
  187. package/src/features/chat/features/message/components/chat-inline-content-surface.tsx +55 -0
  188. package/src/features/chat/features/message/components/chat-inline-file-preview.tsx +49 -71
  189. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +65 -55
  190. package/src/features/chat/features/message/components/chat-message-list.container.tsx +275 -267
  191. package/src/features/chat/features/message/hooks/__tests__/use-chat-message-virtualizer.test.tsx +163 -0
  192. package/src/features/chat/features/message/hooks/use-chat-message-virtualizer.ts +68 -0
  193. package/src/features/chat/features/message/utils/__tests__/chat-message-execution-summary.utils.test.ts +188 -0
  194. package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +14 -1
  195. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +33 -0
  196. package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +24 -9
  197. package/src/features/chat/features/message/utils/chat-message-execution-summary.utils.ts +178 -0
  198. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +44 -0
  199. package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +194 -0
  200. package/src/features/chat/features/message/utils/chat-message.utils.ts +5 -0
  201. package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-session-conversation.test.tsx +151 -13
  202. package/src/features/chat/features/ncp/hooks/use-ncp-session-conversation.ts +44 -70
  203. package/src/features/chat/features/ncp/hooks/use-ncp-session-message-history.ts +197 -0
  204. package/src/features/chat/features/session/components/__tests__/chat-sidebar-session-item.test.tsx +21 -3
  205. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +7 -7
  206. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +2 -2
  207. package/src/features/chat/features/session/components/session-context-icon.tsx +1 -1
  208. package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +5 -1
  209. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +1 -1
  210. package/src/features/chat/features/workspace/components/__tests__/workspace-file-content-preview.test.tsx +32 -10
  211. package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +5 -15
  212. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +62 -9
  213. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +6 -1
  214. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +1 -25
  215. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +190 -109
  216. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +9 -10
  217. package/src/features/chat/managers/chat-session-list.manager.ts +68 -27
  218. package/src/features/cron/components/__tests__/cron-config.test.tsx +260 -0
  219. package/src/features/cron/components/cron-config.tsx +392 -388
  220. package/src/features/cron/components/cron-job-detail-dialog.tsx +210 -0
  221. package/src/features/cron/components/cron-job-row.tsx +271 -0
  222. package/src/features/cron/components/cron-task-discovery.tsx +157 -0
  223. package/src/features/cron/hooks/use-cron-jobs.ts +3 -1
  224. package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +3 -2
  225. package/src/features/marketplace/components/marketplace-external-skill-source-action.tsx +8 -6
  226. package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +26 -40
  227. package/src/features/pwa/components/pwa-install-entry.tsx +45 -58
  228. package/src/features/remote/components/remote-access-page.tsx +128 -208
  229. package/src/features/settings/components/config/provider-auth-section.tsx +32 -17
  230. package/src/features/settings/components/config/provider-form.tsx +6 -6
  231. package/src/features/settings/components/config/provider-models-section.tsx +52 -46
  232. package/src/features/settings/components/config/secrets-config-form.tsx +78 -84
  233. package/src/features/settings/index.ts +0 -1
  234. package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +35 -1
  235. package/src/features/settings/pages/__tests__/model-config-page.test.tsx +18 -1
  236. package/src/features/settings/pages/__tests__/search-config-page.test.tsx +8 -0
  237. package/src/features/settings/pages/__tests__/secrets-config-page.test.tsx +18 -17
  238. package/src/features/settings/pages/appearance-settings-page.tsx +87 -64
  239. package/src/features/settings/pages/model-config-page.tsx +75 -70
  240. package/src/features/settings/pages/providers-config-page.tsx +107 -73
  241. package/src/features/settings/pages/search-config-page.tsx +159 -152
  242. package/src/features/settings/pages/secrets-config-page.tsx +6 -1
  243. package/src/features/system-status/components/config/runtime-agent-list-card.tsx +23 -22
  244. package/src/features/system-status/components/config/runtime-binding-list-card.tsx +23 -22
  245. package/src/features/system-status/components/config/runtime-config-editor.tsx +17 -7
  246. package/src/features/system-status/components/config/runtime-config-overview.tsx +0 -3
  247. package/src/features/system-status/components/config/runtime-entry-list-card.tsx +24 -23
  248. package/src/features/system-status/components/config/runtime-settings-card.tsx +59 -52
  249. package/src/features/system-status/components/desktop-update-config.tsx +117 -112
  250. package/src/features/system-status/components/runtime-control-card.tsx +62 -59
  251. package/src/features/system-status/components/runtime-presence-card.tsx +79 -98
  252. package/src/features/system-status/components/runtime-security-card.tsx +74 -62
  253. package/src/features/system-status/components/security-config.tsx +3 -4
  254. package/src/features/system-status/pages/runtime-config-page.tsx +6 -1
  255. package/src/platforms/desktop/components/desktop-app-shell.tsx +1 -1
  256. package/src/platforms/mobile/components/mobile-settings-shell.tsx +2 -2
  257. package/src/shared/components/__tests__/config-split-page.test.tsx +15 -3
  258. package/src/shared/components/__tests__/settings-page.test.tsx +31 -0
  259. package/src/shared/components/actions/__tests__/navigation-link.test.tsx +47 -0
  260. package/src/shared/components/actions/navigation-link.tsx +116 -0
  261. package/src/shared/components/common/__tests__/brand-header.test.tsx +5 -5
  262. package/src/shared/components/common/__tests__/searchable-model-input.test.tsx +24 -0
  263. package/src/shared/components/common/brand-header.tsx +12 -10
  264. package/src/shared/components/common/searchable-model-input.tsx +9 -10
  265. package/src/shared/components/config-split-page.tsx +70 -65
  266. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +6 -7
  267. package/src/shared/components/settings/setting-row.tsx +61 -30
  268. package/src/shared/components/settings/settings-page.tsx +34 -0
  269. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +23 -2
  270. package/src/shared/components/ui/actions/icon-action-button.tsx +9 -2
  271. package/src/shared/components/ui/overlays/sheet.tsx +142 -0
  272. package/src/shared/lib/api/ncp-session.types.ts +8 -2
  273. package/src/shared/lib/api/types.ts +22 -1
  274. package/src/shared/lib/api/utils/config.utils.ts +2 -1
  275. package/src/shared/lib/api/utils/ncp-session.utils.ts +25 -11
  276. package/src/shared/lib/i18n/locales/en-US/chat.json +32 -1
  277. package/src/shared/lib/i18n/locales/en-US/core.json +3 -3
  278. package/src/shared/lib/i18n/locales/en-US/cron.json +87 -17
  279. package/src/shared/lib/i18n/locales/zh-CN/chat.json +34 -3
  280. package/src/shared/lib/i18n/locales/zh-CN/core.json +6 -6
  281. package/src/shared/lib/i18n/locales/zh-CN/cron.json +80 -10
  282. package/src/shared/lib/transport/__tests__/remote.transport.test.ts +16 -4
  283. package/dist/assets/api-D0U10BcF.js +0 -13
  284. package/dist/assets/app-presenter-provider-Dffdm_Ek.js +0 -106
  285. package/dist/assets/appearance-settings-page-sfusZc2d.js +0 -1
  286. package/dist/assets/architecture-TIHT7OUA-ClwVFq-K.js +0 -1
  287. package/dist/assets/channel-BEHOSFc5.js +0 -1
  288. package/dist/assets/channels-list-page-Dkfe87Zq.js +0 -8
  289. package/dist/assets/chat-page-CeapbQnG.js +0 -1
  290. package/dist/assets/chunk-2Q5K7J3B-DrIhesSX.js +0 -1
  291. package/dist/assets/chunk-VAUOI2AC-Dpcz6BhT.js +0 -1
  292. package/dist/assets/chunk-XXDRQBXY-C2QjjwYX.js +0 -1
  293. package/dist/assets/classDiagram-OUVF2IWQ-DESMaY3_.js +0 -1
  294. package/dist/assets/classDiagram-v2-EOCWNBFH-DwOlByYH.js +0 -1
  295. package/dist/assets/config-split-page-CnQI8cKT.js +0 -1
  296. package/dist/assets/confirm-dialog-RZUWA_j9.js +0 -5
  297. package/dist/assets/cynefin-VYW2F7L2-CzjeeOq9.js +0 -1
  298. package/dist/assets/desktop-update-config-CbSYQMYz.js +0 -1
  299. package/dist/assets/doc-browser-DwXHr-mm.js +0 -1
  300. package/dist/assets/doc-browser-jVTq8iY_2.js +0 -1
  301. package/dist/assets/download-BP7ZxrwI.js +0 -1
  302. package/dist/assets/eventmodeling-45OFAUF4-C38BqG8w.js +0 -1
  303. package/dist/assets/external-link-DvHCEe3h.js +0 -1
  304. package/dist/assets/eye-C--6dxv_.js +0 -1
  305. package/dist/assets/flowDiagram-23GEKE2U-BoKk8RRq.js +0 -1
  306. package/dist/assets/folder-BfeO1zCY.js +0 -1
  307. package/dist/assets/gitGraph-TEB2WS4Q-IsQHHJuS.js +0 -1
  308. package/dist/assets/index-BlKbckX4.css +0 -1
  309. package/dist/assets/index-DoZR11Nh.js +0 -2
  310. package/dist/assets/info-DKCQHKI2-ofy2qlHI.js +0 -1
  311. package/dist/assets/loader-circle-BWdgwVnn.js +0 -1
  312. package/dist/assets/mcp-marketplace-page-XnRWiC8c.js +0 -1
  313. package/dist/assets/model-config-page-CXnqSbSI.js +0 -1
  314. package/dist/assets/packet-7NZHBO7P-Rfopfa1o.js +0 -1
  315. package/dist/assets/pie-RZYD4A2V-CJR1xKEv.js +0 -1
  316. package/dist/assets/plus-BeApS21t.js +0 -1
  317. package/dist/assets/provider-scoped-model-input-ll_Btc9h.js +0 -1
  318. package/dist/assets/providers-config-page-Bcuhqkdl.js +0 -1
  319. package/dist/assets/radar-I7S5WNFK-CDg8Z-1r.js +0 -1
  320. package/dist/assets/railroad-3IZDKUUU-BHnNmjxO.js +0 -1
  321. package/dist/assets/railroad-abnf-AHOZXSZD-DGtNcusW.js +0 -1
  322. package/dist/assets/railroad-ebnf-EBAXGLYW-BRFtsQmm.js +0 -1
  323. package/dist/assets/railroad-peg-LSFZ7HO6-Bj5IypCA.js +0 -1
  324. package/dist/assets/remote-CqKpuOP9.js +0 -1
  325. package/dist/assets/rotate-cw-DHXoiqFB.js +0 -1
  326. package/dist/assets/runtime-config-page-D-Aqxizs.js +0 -1
  327. package/dist/assets/search-config-page-XTKf5rc0.js +0 -1
  328. package/dist/assets/secrets-config-page-Cx-qptcj.js +0 -3
  329. package/dist/assets/security-config-DWGFM2u9.js +0 -1
  330. package/dist/assets/setting-row-DFvioO2a.js +0 -1
  331. package/dist/assets/settings-2-RMWqf-ey.js +0 -1
  332. package/dist/assets/skeleton-DQ5WhuDG.js +0 -1
  333. package/dist/assets/stateDiagram-v2-6OUMAXLB-CfMU38yc.js +0 -1
  334. package/dist/assets/swimlanesDiagram-G3AALYLV-D9Zb9X57.js +0 -8
  335. package/dist/assets/treeView-QDETBFTQ-Uy7M0WvH.js +0 -1
  336. package/dist/assets/treemap-6X3UGDF4-BJfGV7KX.js +0 -1
  337. package/dist/assets/use-confirm-dialog-CTNA1PC0.js +0 -1
  338. package/dist/assets/wardley-OPB4EBWU-q263iF3K.js +0 -1
  339. package/dist/assets/x-qvh05iyu.js +0 -1
  340. package/src/features/chat/components/conversation/chat-conversation-skeleton.tsx +0 -86
  341. package/src/features/settings/pages/__tests__/language-settings-page.test.tsx +0 -36
  342. package/src/features/settings/pages/language-settings-page.tsx +0 -56
  343. package/src/shared/components/actions/action-link.tsx +0 -49
  344. /package/dist/assets/{array-BqYdCIE6.js → array-CAkJT3no.js} +0 -0
  345. /package/dist/assets/{chunk-KEIR6QF5-A3jEqwzn.js → chunk-KEIR6QF5-BgdDoWEH.js} +0 -0
  346. /package/dist/assets/{chunk-Y2CYZVJY-BGrd8tLy.js → chunk-Y2CYZVJY-pTllD0P3.js} +0 -0
  347. /package/dist/assets/{config-hints-LxG80Uj5.js → config-hints-S3g-UVZb.js} +0 -0
  348. /package/dist/assets/{cytoscape.esm-CRW8q92n.js → cytoscape.esm-DmURt-5G.js} +0 -0
  349. /package/dist/assets/{defaultLocale-Cdv9x6q7.js → defaultLocale-BKy_KP3O.js} +0 -0
  350. /package/dist/assets/{dist-dw-gAfM_.js → dist-IgNNB7K9.js} +0 -0
  351. /package/dist/assets/{graphlib-BcNFQr5y.js → graphlib-xw_TBHyH.js} +0 -0
  352. /package/dist/assets/{host-capabilities-CFbAfrrA.js → host-capabilities-B-TtABsP.js} +0 -0
  353. /package/dist/assets/{init-D8TjIqfm.js → init-C9xlLIAu.js} +0 -0
  354. /package/dist/assets/{jszip.min-CP5uPNQa.js → jszip.min-Das2IAnb.js} +0 -0
  355. /package/dist/assets/{katex-wEmrEFTg.js → katex-DSy1eD2d.js} +0 -0
  356. /package/dist/assets/{middleware-CAXxl-af.js → middleware-Df-a4x-q.js} +0 -0
  357. /package/dist/assets/{path-DzBnMI1r.js → path-DB0USYHi.js} +0 -0
  358. /package/dist/assets/{provider-models-BoUhFo9R.js → provider-models-CXUgDxGR.js} +0 -0
  359. /package/dist/assets/{rough.esm-wJ1d3nQl.js → rough.esm-B1B52ZQS.js} +0 -0
  360. /package/dist/assets/{xlsx-DUqJhg5u.js → xlsx-DfjC_iml.js} +0 -0
@@ -8,10 +8,13 @@ import { t } from '@/shared/lib/i18n';
8
8
  import type { ThinkingLevel } from '@/shared/lib/api';
9
9
  import { Plus, Settings2, X } from 'lucide-react';
10
10
 
11
- type ModelConfig = Record<string, {
12
- thinking?: { supported: ThinkingLevel[]; default?: ThinkingLevel | null };
13
- vision?: boolean;
14
- }>;
11
+ type ModelConfig = Record<
12
+ string,
13
+ {
14
+ thinking?: { supported: ThinkingLevel[]; default?: ThinkingLevel | null };
15
+ vision?: boolean;
16
+ }
17
+ >;
15
18
 
16
19
  type ProviderModelsSectionProps = {
17
20
  models: string[];
@@ -47,23 +50,23 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
47
50
  } = props;
48
51
 
49
52
  return (
50
- <div className="space-y-2">
51
- <div className="flex items-center justify-between">
52
- <Label className="text-sm font-medium text-foreground">{t('providerModelsTitle')}</Label>
53
+ <div className='space-y-2'>
54
+ <div className='flex items-center justify-between'>
55
+ <Label className='text-sm font-medium text-foreground'>{t('providerModelsTitle')}</Label>
53
56
  {!showModelInput ? (
54
57
  <button
55
- type="button"
58
+ type='button'
56
59
  onClick={() => onShowModelInputChange(true)}
57
- className="flex items-center gap-1 text-xs font-medium text-primary hover:text-primary/80"
60
+ className='flex items-center gap-1 text-xs font-medium text-primary hover:text-primary/80'
58
61
  >
59
- <Plus className="h-3 w-3" />
62
+ <Plus className='h-3 w-3' />
60
63
  {t('providerAddModel')}
61
64
  </button>
62
65
  ) : null}
63
66
  </div>
64
67
 
65
68
  {showModelInput ? (
66
- <div className="flex items-center gap-2">
69
+ <div className='flex items-center gap-2'>
67
70
  <Input
68
71
  value={modelDraft}
69
72
  onChange={(event) => onModelDraftChange(event.target.value)}
@@ -78,41 +81,41 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
78
81
  }
79
82
  }}
80
83
  placeholder={t('providerModelInputPlaceholder')}
81
- className="flex-1 rounded-xl"
84
+ className='flex-1 rounded-xl'
82
85
  autoFocus
83
86
  />
84
- <Button type="button" size="sm" onClick={onAddModel} disabled={modelDraft.trim().length === 0}>
87
+ <Button type='button' size='sm' onClick={onAddModel} disabled={modelDraft.trim().length === 0}>
85
88
  {t('add')}
86
89
  </Button>
87
90
  <Button
88
- type="button"
89
- size="sm"
90
- variant="ghost"
91
+ type='button'
92
+ size='sm'
93
+ variant='ghost'
91
94
  onClick={() => {
92
95
  onShowModelInputChange(false);
93
96
  onModelDraftChange('');
94
97
  }}
95
98
  >
96
- <X className="h-4 w-4" />
99
+ <X className='h-4 w-4' />
97
100
  </Button>
98
101
  </div>
99
102
  ) : null}
100
103
 
101
104
  {models.length === 0 ? (
102
- <div className="rounded-xl border border-dashed border-border/65 bg-muted/30 px-4 py-6 text-center">
103
- <p className="text-sm text-muted-foreground">{t('providerModelsEmptyShort')}</p>
105
+ <div className='rounded-xl bg-muted/35 px-4 py-6 text-center'>
106
+ <p className='text-sm text-muted-foreground'>{t('providerModelsEmptyShort')}</p>
104
107
  {!showModelInput ? (
105
108
  <button
106
- type="button"
109
+ type='button'
107
110
  onClick={() => onShowModelInputChange(true)}
108
- className="mt-2 text-sm font-medium text-primary hover:text-primary/80"
111
+ className='mt-2 text-sm font-medium text-primary hover:text-primary/80'
109
112
  >
110
113
  {t('providerAddFirstModel')}
111
114
  </button>
112
115
  ) : null}
113
116
  </div>
114
117
  ) : (
115
- <div className="flex flex-wrap gap-2">
118
+ <div className='flex flex-wrap gap-2'>
116
119
  {models.map((modelName) => {
117
120
  const modelEntry = modelConfig[modelName];
118
121
  const thinkingEntry = modelEntry?.thinking;
@@ -123,39 +126,42 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
123
126
  return (
124
127
  <div
125
128
  key={modelName}
126
- className="group inline-flex max-w-full items-center gap-1 rounded-full border border-border/55 bg-muted/45 px-3 py-1.5"
129
+ className='group inline-flex max-w-full items-center gap-1 rounded-full border border-border/55 bg-muted/45 px-3 py-1.5'
127
130
  >
128
- <span className="max-w-[140px] truncate text-sm text-foreground sm:max-w-[220px]">{modelName}</span>
131
+ <span className='max-w-[140px] truncate text-sm text-foreground sm:max-w-[220px]'>{modelName}</span>
129
132
  <Popover>
130
133
  <PopoverTrigger asChild>
131
134
  <button
132
- type="button"
133
- className="inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground/70 opacity-100 transition-opacity hover:bg-muted hover:text-foreground md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
135
+ type='button'
136
+ className='inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground/70 opacity-100 transition-opacity hover:bg-muted hover:text-foreground md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100'
134
137
  aria-label={t('providerModelThinkingTitle')}
135
138
  title={t('providerModelThinkingTitle')}
136
139
  >
137
- <Settings2 className="h-3 w-3" />
140
+ <Settings2 className='h-3 w-3' />
138
141
  </button>
139
142
  </PopoverTrigger>
140
- <PopoverContent className="w-80 space-y-3">
141
- <div className="flex items-center justify-between gap-3 rounded-lg bg-muted/50 px-3 py-2">
142
- <div className="min-w-0">
143
- <p className="text-xs font-semibold text-foreground">{t('providerModelVisionTitle')}</p>
144
- <p className="text-xs text-muted-foreground">{t('providerModelVisionHint')}</p>
143
+ <PopoverContent className='w-80 space-y-3'>
144
+ <div className='flex items-center justify-between gap-3 rounded-lg bg-muted/50 px-3 py-2'>
145
+ <div className='min-w-0'>
146
+ <p className='text-xs font-semibold text-foreground'>{t('providerModelVisionTitle')}</p>
147
+ <p className='text-xs text-muted-foreground'>{t('providerModelVisionHint')}</p>
145
148
  </div>
146
- <Switch checked={visionEnabled} onCheckedChange={(checked) => onSetModelVision(modelName, checked)} />
149
+ <Switch
150
+ checked={visionEnabled}
151
+ onCheckedChange={(checked) => onSetModelVision(modelName, checked)}
152
+ />
147
153
  </div>
148
- <div className="space-y-1">
149
- <p className="text-xs font-semibold text-foreground">{t('providerModelThinkingTitle')}</p>
150
- <p className="text-xs text-muted-foreground">{t('providerModelThinkingHint')}</p>
154
+ <div className='space-y-1'>
155
+ <p className='text-xs font-semibold text-foreground'>{t('providerModelThinkingTitle')}</p>
156
+ <p className='text-xs text-muted-foreground'>{t('providerModelThinkingHint')}</p>
151
157
  </div>
152
- <div className="flex flex-wrap gap-1.5">
158
+ <div className='flex flex-wrap gap-1.5'>
153
159
  {thinkingLevels.map((level) => {
154
160
  const selected = supportedLevels.includes(level);
155
161
  return (
156
162
  <button
157
163
  key={level}
158
- type="button"
164
+ type='button'
159
165
  onClick={() => onToggleModelThinkingLevel(modelName, level)}
160
166
  className={`rounded-full border px-2.5 py-1 text-xs font-medium transition-colors ${
161
167
  selected
@@ -168,8 +174,8 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
168
174
  );
169
175
  })}
170
176
  </div>
171
- <div className="space-y-1.5">
172
- <Label className="text-xs font-medium text-foreground">{t('providerModelThinkingDefault')}</Label>
177
+ <div className='space-y-1.5'>
178
+ <Label className='text-xs font-medium text-foreground'>{t('providerModelThinkingDefault')}</Label>
173
179
  <Select
174
180
  value={defaultThinkingLevel ?? '__none__'}
175
181
  onValueChange={(value) =>
@@ -177,11 +183,11 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
177
183
  }
178
184
  disabled={supportedLevels.length === 0}
179
185
  >
180
- <SelectTrigger className="h-8 rounded-lg text-xs">
186
+ <SelectTrigger className='h-8 rounded-lg text-xs'>
181
187
  <SelectValue />
182
188
  </SelectTrigger>
183
189
  <SelectContent>
184
- <SelectItem value="__none__">{t('providerModelThinkingDefaultNone')}</SelectItem>
190
+ <SelectItem value='__none__'>{t('providerModelThinkingDefaultNone')}</SelectItem>
185
191
  {supportedLevels.map((level) => (
186
192
  <SelectItem key={level} value={level}>
187
193
  {formatThinkingLevelLabel(level)}
@@ -190,18 +196,18 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
190
196
  </SelectContent>
191
197
  </Select>
192
198
  {supportedLevels.length === 0 ? (
193
- <p className="text-xs text-muted-foreground">{t('providerModelThinkingNoSupported')}</p>
199
+ <p className='text-xs text-muted-foreground'>{t('providerModelThinkingNoSupported')}</p>
194
200
  ) : null}
195
201
  </div>
196
202
  </PopoverContent>
197
203
  </Popover>
198
204
  <button
199
- type="button"
205
+ type='button'
200
206
  onClick={() => onRemoveModel(modelName)}
201
- className="inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground/70 opacity-100 transition-opacity hover:bg-muted hover:text-foreground md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
207
+ className='inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground/70 opacity-100 transition-opacity hover:bg-muted hover:text-foreground md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100'
202
208
  aria-label={t('remove')}
203
209
  >
204
- <X className="h-3 w-3" />
210
+ <X className='h-3 w-3' />
205
211
  </button>
206
212
  </div>
207
213
  );
@@ -2,12 +2,11 @@ import { useMemo, useState } from 'react';
2
2
  import { Plus, Save, Trash2 } from 'lucide-react';
3
3
  import { Button } from '@/shared/components/ui/button';
4
4
  import { FormActions } from '@/shared/components/ui/actions/form-actions';
5
- import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/shared/components/ui/card';
5
+ import { SettingRow, SettingsGroup, SettingsSection } from '@/shared/components/settings/setting-row';
6
6
  import { Input } from '@/shared/components/ui/input';
7
- import { Label } from '@/shared/components/ui/label';
8
7
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/components/ui/select';
9
8
  import { Switch } from '@/shared/components/ui/switch';
10
- import { PageHeader, PageLayout } from '@/app/components/layout/page-layout';
9
+ import { SettingsPage } from '@/shared/components/settings/settings-page';
11
10
  import type { SecretSourceView } from '@/shared/lib/api';
12
11
  import { t } from '@/shared/lib/i18n';
13
12
  import { toast } from 'sonner';
@@ -21,7 +20,7 @@ import {
21
20
  type SecretsFormState,
22
21
  } from '@/features/settings/utils/secrets-config-form.utils';
23
22
 
24
- function SecretsDefaultsCard(props: {
23
+ function SecretsDefaultRows(props: {
25
24
  defaultEnv: string;
26
25
  defaultFile: string;
27
26
  defaultExec: string;
@@ -30,25 +29,22 @@ function SecretsDefaultsCard(props: {
30
29
  }) {
31
30
  const { defaultEnv, defaultFile, defaultExec, providerAliases, onChange } = props;
32
31
  return (
33
- <Card>
34
- <CardHeader>
35
- <CardTitle>{t('secrets')}</CardTitle>
36
- <CardDescription>{t('secretsEnabledHelp')}</CardDescription>
37
- </CardHeader>
38
- <CardContent className="space-y-4">
39
- {[
40
- { label: t('defaultEnvProvider'), value: defaultEnv, key: 'defaultEnv' as const },
41
- { label: t('defaultFileProvider'), value: defaultFile, key: 'defaultFile' as const },
42
- { label: t('defaultExecProvider'), value: defaultExec, key: 'defaultExec' as const },
43
- ].map((item) => (
44
- <div key={item.key} className="space-y-2">
45
- <Label>{item.label}</Label>
32
+ <>
33
+ {[
34
+ { label: t('defaultEnvProvider'), value: defaultEnv, key: 'defaultEnv' as const },
35
+ { label: t('defaultFileProvider'), value: defaultFile, key: 'defaultFile' as const },
36
+ { label: t('defaultExecProvider'), value: defaultExec, key: 'defaultExec' as const },
37
+ ].map((item) => (
38
+ <SettingRow
39
+ key={item.key}
40
+ title={item.label}
41
+ control={
46
42
  <Select value={item.value || '__none__'} onValueChange={(value) => onChange(item.key, value === '__none__' ? '' : value)}>
47
- <SelectTrigger>
43
+ <SelectTrigger className='w-56'>
48
44
  <SelectValue placeholder={t('noneOption')} />
49
45
  </SelectTrigger>
50
46
  <SelectContent>
51
- <SelectItem value="__none__">{t('noneOption')}</SelectItem>
47
+ <SelectItem value='__none__'>{t('noneOption')}</SelectItem>
52
48
  {providerAliases.map((alias) => (
53
49
  <SelectItem key={alias} value={alias}>
54
50
  {alias}
@@ -56,10 +52,10 @@ function SecretsDefaultsCard(props: {
56
52
  ))}
57
53
  </SelectContent>
58
54
  </Select>
59
- </div>
60
- ))}
61
- </CardContent>
62
- </Card>
55
+ }
56
+ />
57
+ ))}
58
+ </>
63
59
  );
64
60
  }
65
61
 
@@ -71,15 +67,20 @@ function SecretsProvidersCard(props: {
71
67
  }) {
72
68
  const { providers, onUpdate, onRemove, onAdd } = props;
73
69
  return (
74
- <Card>
75
- <CardHeader>
76
- <CardTitle>{t('secretProvidersTitle')}</CardTitle>
77
- <CardDescription>{t('secretProvidersDescription')}</CardDescription>
78
- </CardHeader>
79
- <CardContent className="space-y-3">
70
+ <SettingsSection
71
+ title={t('secretProvidersTitle')}
72
+ description={t('secretProvidersDescription')}
73
+ actions={
74
+ <Button type='button' variant='ghost' size='sm' onClick={onAdd}>
75
+ <Plus className='mr-2 h-4 w-4' />
76
+ {t('addSecretProvider')}
77
+ </Button>
78
+ }
79
+ >
80
+ <SettingsGroup>
80
81
  {providers.map((provider, index) => (
81
- <div key={`provider-${index}`} className="space-y-3 rounded-xl border border-gray-200 p-3">
82
- <div className="grid grid-cols-1 gap-3 md:grid-cols-3">
82
+ <div key={`provider-${index}`} className='space-y-3 p-4'>
83
+ <div className='grid grid-cols-1 gap-3 md:grid-cols-3'>
83
84
  <Input value={provider.alias} onChange={(event) => onUpdate(index, { alias: event.target.value })} placeholder={t('providerAlias')} />
84
85
  <Select value={provider.source} onValueChange={(value) => onUpdate(index, { source: value as SecretSourceView })}>
85
86
  <SelectTrigger>
@@ -93,8 +94,8 @@ function SecretsProvidersCard(props: {
93
94
  ))}
94
95
  </SelectContent>
95
96
  </Select>
96
- <Button type="button" variant="outline" onClick={() => onRemove(index)}>
97
- <Trash2 className="mr-2 h-4 w-4" />
97
+ <Button type='button' variant='outline' onClick={() => onRemove(index)}>
98
+ <Trash2 className='mr-2 h-4 w-4' />
98
99
  {t('removeProvider')}
99
100
  </Button>
100
101
  </div>
@@ -106,18 +107,18 @@ function SecretsProvidersCard(props: {
106
107
  <Input value={provider.path} onChange={(event) => onUpdate(index, { path: event.target.value })} placeholder={t('secretFilePath')} />
107
108
  ) : null}
108
109
  {provider.source === 'exec' ? (
109
- <div className="space-y-2">
110
+ <div className='space-y-2'>
110
111
  <Input value={provider.command} onChange={(event) => onUpdate(index, { command: event.target.value })} placeholder={t('secretExecCommand')} />
111
112
  <textarea
112
- className="min-h-[84px] w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"
113
+ className='min-h-[84px] w-full rounded-lg border border-input bg-background px-3 py-2 font-mono text-xs'
113
114
  value={provider.argsText}
114
115
  onChange={(event) => onUpdate(index, { argsText: event.target.value })}
115
116
  placeholder={t('secretExecArgs')}
116
117
  />
117
- <div className="grid grid-cols-1 gap-2 md:grid-cols-2">
118
+ <div className='grid grid-cols-1 gap-2 md:grid-cols-2'>
118
119
  <Input value={provider.cwd} onChange={(event) => onUpdate(index, { cwd: event.target.value })} placeholder={t('secretExecCwd')} />
119
120
  <Input
120
- type="number"
121
+ type='number'
121
122
  min={1}
122
123
  value={provider.timeoutMs}
123
124
  onChange={(event) => onUpdate(index, { timeoutMs: Number.parseInt(event.target.value, 10) || 5000 })}
@@ -128,13 +129,8 @@ function SecretsProvidersCard(props: {
128
129
  ) : null}
129
130
  </div>
130
131
  ))}
131
-
132
- <Button type="button" variant="outline" onClick={onAdd}>
133
- <Plus className="mr-2 h-4 w-4" />
134
- {t('addSecretProvider')}
135
- </Button>
136
- </CardContent>
137
- </Card>
132
+ </SettingsGroup>
133
+ </SettingsSection>
138
134
  );
139
135
  }
140
136
 
@@ -147,15 +143,20 @@ function SecretsRefsCard(props: {
147
143
  }) {
148
144
  const { refs, providerAliases, onUpdate, onRemove, onAdd } = props;
149
145
  return (
150
- <Card>
151
- <CardHeader>
152
- <CardTitle>{t('secretRefsTitle')}</CardTitle>
153
- <CardDescription>{t('secretRefsDescription')}</CardDescription>
154
- </CardHeader>
155
- <CardContent className="space-y-3">
146
+ <SettingsSection
147
+ title={t('secretRefsTitle')}
148
+ description={t('secretRefsDescription')}
149
+ actions={
150
+ <Button type='button' variant='ghost' size='sm' onClick={onAdd}>
151
+ <Plus className='mr-2 h-4 w-4' />
152
+ {t('addSecretRef')}
153
+ </Button>
154
+ }
155
+ >
156
+ <SettingsGroup>
156
157
  {refs.map((ref, index) => (
157
- <div key={`ref-${index}`} className="space-y-3 rounded-xl border border-gray-200 p-3">
158
- <div className="grid grid-cols-1 gap-3 md:grid-cols-2">
158
+ <div key={`ref-${index}`} className='space-y-3 p-4'>
159
+ <div className='grid grid-cols-1 gap-3 md:grid-cols-2'>
159
160
  <Input value={ref.path} onChange={(event) => onUpdate(index, { path: event.target.value })} placeholder={t('secretConfigPath')} />
160
161
  <Input value={ref.id} onChange={(event) => onUpdate(index, { id: event.target.value })} placeholder={t('secretId')} />
161
162
  <Select value={ref.source} onValueChange={(value) => onUpdate(index, { source: value as SecretSourceView })}>
@@ -170,13 +171,13 @@ function SecretsRefsCard(props: {
170
171
  ))}
171
172
  </SelectContent>
172
173
  </Select>
173
- <div className="grid grid-cols-[1fr_auto] gap-2">
174
+ <div className='grid grid-cols-[1fr_auto] gap-2'>
174
175
  <Select value={ref.provider || '__none__'} onValueChange={(value) => onUpdate(index, { provider: value === '__none__' ? '' : value })}>
175
176
  <SelectTrigger>
176
177
  <SelectValue placeholder={t('secretProviderAlias')} />
177
178
  </SelectTrigger>
178
179
  <SelectContent>
179
- <SelectItem value="__none__">{t('noneOption')}</SelectItem>
180
+ <SelectItem value='__none__'>{t('noneOption')}</SelectItem>
180
181
  {providerAliases.map((alias) => (
181
182
  <SelectItem key={alias} value={alias}>
182
183
  {alias}
@@ -184,20 +185,15 @@ function SecretsRefsCard(props: {
184
185
  ))}
185
186
  </SelectContent>
186
187
  </Select>
187
- <Button type="button" variant="outline" onClick={() => onRemove(index)}>
188
- <Trash2 className="h-4 w-4" />
188
+ <Button type='button' variant='outline' onClick={() => onRemove(index)}>
189
+ <Trash2 className='h-4 w-4' />
189
190
  </Button>
190
191
  </div>
191
192
  </div>
192
193
  </div>
193
194
  ))}
194
-
195
- <Button type="button" variant="outline" onClick={onAdd}>
196
- <Plus className="mr-2 h-4 w-4" />
197
- {t('addSecretRef')}
198
- </Button>
199
- </CardContent>
200
- </Card>
195
+ </SettingsGroup>
196
+ </SettingsSection>
201
197
  );
202
198
  }
203
199
 
@@ -236,30 +232,28 @@ export function SecretsConfigForm(props: {
236
232
  };
237
233
 
238
234
  return (
239
- <PageLayout className="space-y-6">
240
- <PageHeader title={t('secretsPageTitle')} description={t('secretsPageDescription')} />
241
- <Card>
242
- <CardHeader>
243
- <CardTitle>{t('secrets')}</CardTitle>
244
- <CardDescription>{t('secretsEnabledHelp')}</CardDescription>
245
- </CardHeader>
246
- <CardContent className="space-y-4">
247
- <div className="flex items-center justify-between rounded-xl border border-gray-200 p-3">
248
- <div>
249
- <p className="text-sm font-medium text-gray-800">{t('enabled')}</p>
250
- <p className="text-xs text-gray-500">{t('secretsEnabledHelp')}</p>
251
- </div>
252
- <Switch checked={state.enabled} onCheckedChange={(enabled) => setState((prev) => ({ ...prev, enabled }))} />
253
- </div>
254
- <SecretsDefaultsCard
235
+ <SettingsPage title={t('secretsPageTitle')} description={t('secretsPageDescription')}>
236
+ <SettingsSection title={t('secrets')} description={t('secretsEnabledHelp')}>
237
+ <SettingsGroup>
238
+ <SettingRow
239
+ title={t('enabled')}
240
+ description={t('secretsEnabledHelp')}
241
+ control={
242
+ <Switch
243
+ checked={state.enabled}
244
+ onCheckedChange={(enabled) => setState((prev) => ({ ...prev, enabled }))}
245
+ />
246
+ }
247
+ />
248
+ <SecretsDefaultRows
255
249
  defaultEnv={state.defaultEnv}
256
250
  defaultFile={state.defaultFile}
257
251
  defaultExec={state.defaultExec}
258
252
  providerAliases={providerAliases}
259
253
  onChange={(key, value) => setState((prev) => ({ ...prev, [key]: value }))}
260
254
  />
261
- </CardContent>
262
- </Card>
255
+ </SettingsGroup>
256
+ </SettingsSection>
263
257
  <SecretsProvidersCard
264
258
  providers={state.providers}
265
259
  onUpdate={updateProvider}
@@ -274,11 +268,11 @@ export function SecretsConfigForm(props: {
274
268
  onAdd={() => setState((prev) => ({ ...prev, refs: [...prev.refs, createRefRow()] }))}
275
269
  />
276
270
  <FormActions>
277
- <Button type="button" size="sm" onClick={handleSave} disabled={isPending}>
278
- <Save className="mr-1.5 h-3.5 w-3.5" />
271
+ <Button type='button' size='sm' onClick={handleSave} disabled={isPending}>
272
+ <Save className='mr-1.5 h-3.5 w-3.5' />
279
273
  {isPending ? t('saving') : t('save')}
280
274
  </Button>
281
275
  </FormActions>
282
- </PageLayout>
276
+ </SettingsPage>
283
277
  );
284
278
  }
@@ -1,2 +1 @@
1
1
  export { useLanguagePreference } from "@/features/settings/hooks/use-language-preference";
2
- export { LanguageSettingsPage } from "@/features/settings/pages/language-settings-page";
@@ -1,15 +1,37 @@
1
1
  import { fireEvent, render, screen } from "@testing-library/react";
2
- import { beforeEach, describe, expect, it } from "vitest";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import { AppearanceSettingsPage } from "@/features/settings/pages/appearance-settings-page";
4
4
  import { useChatMessageLayoutStore } from "@/features/chat";
5
5
  import { useSideDockStore } from "@/features/side-dock";
6
6
 
7
+ const mocks = vi.hoisted(() => ({
8
+ selectLanguage: vi.fn(),
9
+ setTheme: vi.fn(),
10
+ }));
11
+
12
+ vi.mock("@/app/components/theme-provider", () => ({
13
+ useTheme: () => ({ theme: "work", setTheme: mocks.setTheme }),
14
+ }));
15
+
16
+ vi.mock("@/features/settings/hooks/use-language-preference", () => ({
17
+ useLanguagePreference: () => ({
18
+ currentLanguage: "en",
19
+ languageOptions: [
20
+ { value: "en", label: "English" },
21
+ { value: "zh", label: "中文" },
22
+ ],
23
+ selectLanguage: mocks.selectLanguage,
24
+ }),
25
+ }));
26
+
7
27
  describe("AppearanceSettingsPage", () => {
8
28
  beforeEach(() => {
9
29
  window.localStorage.clear();
10
30
  useChatMessageLayoutStore.getState().setLayout("card");
11
31
  useSideDockStore.getState().setVisible(true);
12
32
  useSideDockStore.getState().setPinnedItems([]);
33
+ mocks.selectLanguage.mockClear();
34
+ mocks.setTheme.mockClear();
13
35
  });
14
36
 
15
37
  it("reopens the SideDock from the appearance settings switch", () => {
@@ -39,4 +61,16 @@ describe("AppearanceSettingsPage", () => {
39
61
  expect(flatOption.getAttribute("aria-checked")).toBe("true");
40
62
  expect(useChatMessageLayoutStore.getState().layout).toBe("flat");
41
63
  });
64
+
65
+ it("owns the persistent theme and interface language controls", () => {
66
+ render(<AppearanceSettingsPage />);
67
+
68
+ fireEvent.click(screen.getByRole("combobox", { name: "Theme" }));
69
+ fireEvent.click(screen.getByRole("option", { name: "Night" }));
70
+ expect(mocks.setTheme).toHaveBeenCalledWith("night");
71
+
72
+ fireEvent.click(screen.getByRole("combobox", { name: "Language" }));
73
+ fireEvent.click(screen.getByRole("option", { name: "中文" }));
74
+ expect(mocks.selectLanguage).toHaveBeenCalledWith("zh");
75
+ });
42
76
  });
@@ -96,6 +96,7 @@ describe('ModelConfigPage', () => {
96
96
  beforeEach(() => {
97
97
  mocks.mutate.mockReset();
98
98
  setLanguage('en');
99
+ mocks.schemaQuery.data.uiHints = {};
99
100
  HTMLElement.prototype.hasPointerCapture = vi.fn(() => false);
100
101
  HTMLElement.prototype.setPointerCapture = vi.fn();
101
102
  HTMLElement.prototype.releasePointerCapture = vi.fn();
@@ -166,7 +167,7 @@ describe('ModelConfigPage', () => {
166
167
 
167
168
  render(<ModelConfigPage />);
168
169
 
169
- const workspaceInput = await screen.findByLabelText('Default Path');
170
+ const workspaceInput = await screen.findByLabelText('Workspace');
170
171
  await user.clear(workspaceInput);
171
172
  await user.type(workspaceInput, '~/new-workspace');
172
173
  await user.click(screen.getByRole('button', { name: /save/i }));
@@ -179,6 +180,22 @@ describe('ModelConfigPage', () => {
179
180
  });
180
181
  });
181
182
 
183
+ it('keeps page labels localized when schema hints contain English labels', () => {
184
+ setLanguage('zh');
185
+ mocks.schemaQuery.data.uiHints = {
186
+ 'agents.defaults.model': { label: 'Default Model' },
187
+ 'agents.defaults.workspace': { label: 'Workspace' }
188
+ };
189
+
190
+ render(<ModelConfigPage />);
191
+
192
+ expect(screen.getByRole('heading', { name: '模型配置' })).toBeTruthy();
193
+ expect(screen.getByLabelText('默认模型')).toBeTruthy();
194
+ expect(screen.getByLabelText('工作空间')).toBeTruthy();
195
+ expect(screen.queryByText('Default Model')).toBeNull();
196
+ expect(screen.queryByText('Workspace')).toBeNull();
197
+ });
198
+
182
199
  it('shows a clear empty state and still allows manual model input when no providers are configured', async () => {
183
200
  const user = userEvent.setup();
184
201
  mocks.configQuery.data = {
@@ -99,11 +99,19 @@ describe('SearchConfigPage', () => {
99
99
  render(<SearchConfigPage />);
100
100
 
101
101
  expect(screen.getByRole('heading', { name: 'Tavily Search' })).toBeTruthy();
102
+ expect(screen.getAllByText('更适合调研和网页信息整合的搜索 provider。').length).toBeGreaterThan(0);
103
+ expect(screen.queryByText('Research-focused web search with optional synthesized answers.')).toBeNull();
102
104
  expect(screen.getByText('搜索深度')).toBeTruthy();
103
105
  expect(screen.getByText('包含回答')).toBeTruthy();
104
106
  expect(screen.getByDisplayValue('https://api.tavily.com/search')).toBeTruthy();
105
107
  expect(screen.queryByText('结果摘要')).toBeNull();
106
108
 
109
+ const docsLink = screen.getByRole('link', { name: '查看文档' });
110
+ expect(docsLink.getAttribute('href')).toBe('https://docs.tavily.com/documentation/api-reference/endpoint/search');
111
+ expect(docsLink.getAttribute('target')).toBe('_blank');
112
+ expect(docsLink.querySelector('button')).toBeNull();
113
+ expect(screen.queryByRole('button', { name: '查看文档' })).toBeNull();
114
+
107
115
  const searchDepthSection = screen.getByText('搜索深度').parentElement;
108
116
  const includeAnswerSection = screen.getByText('包含回答').parentElement;
109
117
  const searchDepthTrigger = searchDepthSection?.querySelector('[role="combobox"]');