@nextclaw/ui 0.12.8 → 0.12.10

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 (227) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/assets/ChannelsList-M9FTK1Ak.js +8 -0
  3. package/dist/assets/DocBrowser-CH7-GxlL.js +1 -0
  4. package/dist/assets/{DocBrowser-BMxf9CIK.js → DocBrowser-DMfr0Oow.js} +1 -1
  5. package/dist/assets/{DocBrowserContext-Ce28gRXt.js → DocBrowserContext-BXydqby-.js} +1 -1
  6. package/dist/assets/{LogoBadge-o92MOA2L.js → LogoBadge-hO7tY7hE.js} +1 -1
  7. package/dist/assets/ModelConfig-CNIgLf0e.js +1 -0
  8. package/dist/assets/{ProviderScopedModelInput-CmTIzgI7.js → ProviderScopedModelInput-B3HWP4oz.js} +1 -1
  9. package/dist/assets/ProvidersList-CHjMnRhX.js +1 -0
  10. package/dist/assets/RuntimeConfig-psp8nMSG.js +1 -0
  11. package/dist/assets/SearchConfig-CSoKip1f.js +1 -0
  12. package/dist/assets/{SecretsConfig-Ba1RPJaG.js → SecretsConfig-MEt6MjuD.js} +2 -2
  13. package/dist/assets/SessionsConfig-DifCiXwR.js +2 -0
  14. package/dist/assets/{app-query-client-DniXoIN5.js → app-query-client-9jNewezV.js} +1 -1
  15. package/dist/assets/{book-open-DocgeQtR.js → book-open-DzdUViDm.js} +1 -1
  16. package/dist/assets/chat-page-CLp0UV0Y.js +58 -0
  17. package/dist/assets/chat-session-display-DsYHx0RZ.js +1 -0
  18. package/dist/assets/{chunk-JZWAC4HX-BvKvh1R8.js → chunk-JZWAC4HX-C5dEc8hV.js} +1 -1
  19. package/dist/assets/{client-CVqPF5ie.js → client-C-8fH7-c.js} +1 -1
  20. package/dist/assets/{config-Bop2oB18.js → config-CBScxsdV.js} +1 -1
  21. package/dist/assets/config-split-page-BUout_Ak.js +1 -0
  22. package/dist/assets/{createLucideIcon-DVv8taGY.js → createLucideIcon-dy5ie7Ox.js} +1 -1
  23. package/dist/assets/desktop-update-config-2BS6BMkW.js +1 -0
  24. package/dist/assets/{dist-DmAlInRu.js → dist-BruyLa92.js} +1 -1
  25. package/dist/assets/{dist-Da5Gm_pO.js → dist-Cy7_j6hA.js} +1 -1
  26. package/dist/assets/download-BD0ETkB-.js +1 -0
  27. package/dist/assets/{external-link-DFjw3x1B.js → external-link-kZSAO8nT.js} +1 -1
  28. package/dist/assets/{hash-DJtaCejM.js → hash-BHJC2Ovu.js} +1 -1
  29. package/dist/assets/i18n-CpTZLchQ.js +1 -0
  30. package/dist/assets/index-mW8W2FUu.css +1 -0
  31. package/dist/assets/index-zDZfXoI4.js +6 -0
  32. package/dist/assets/{infiniteQueryBehavior-DHSEQ3OH.js → infiniteQueryBehavior-CyER9hv0.js} +1 -1
  33. package/dist/assets/loader-circle-Bc2gCU33.js +1 -0
  34. package/dist/assets/{logos-DEFUIR12.js → logos-B7gRObP8.js} +1 -1
  35. package/dist/assets/marketplace-page-3qVMnF3d.js +1 -0
  36. package/dist/assets/marketplace-page-BhFIeQzI.js +49 -0
  37. package/dist/assets/mcp-marketplace-page-DYfteJ1D.js +40 -0
  38. package/dist/assets/{page-layout-Da3i3r6G.js → page-layout-0UcO9H9Z.js} +1 -1
  39. package/dist/assets/play-CKDjSQFL.js +1 -0
  40. package/dist/assets/plus-CG0QrVY_.js +1 -0
  41. package/dist/assets/{refresh-ccw-D6HkNtfz.js → refresh-ccw-COVhNHtN.js} +1 -1
  42. package/dist/assets/{refresh-cw-DRcvRrnc.js → refresh-cw-Bcv40SXy.js} +1 -1
  43. package/dist/assets/remote-access-page-CWHG-sug.js +1 -0
  44. package/dist/assets/{rotate-cw-BmDKfXtH.js → rotate-cw-oHMKJMC8.js} +1 -1
  45. package/dist/assets/{save-DHGmi2e9.js → save-EqJPOF0G.js} +1 -1
  46. package/dist/assets/search-BCAlB8nz.js +1 -0
  47. package/dist/assets/security-config-Slh0Mayz.js +1 -0
  48. package/dist/assets/select-CVz0t7MF.js +41 -0
  49. package/dist/assets/setting-row-CbVHAuQt.js +1 -0
  50. package/dist/assets/skeleton-D5rdKvzy.js +1 -0
  51. package/dist/assets/{status-dot-DurKKSwA.js → status-dot-DpPtVzQT.js} +1 -1
  52. package/dist/assets/{switch-0rmPBRKI.js → switch-CM29eCAR.js} +1 -1
  53. package/dist/assets/{tabs-custom-5JLVL6v8.js → tabs-custom-YcZUWn3o.js} +1 -1
  54. package/dist/assets/tag-chip-DMXdnLcj.js +1 -0
  55. package/dist/assets/{trash-2-C6caKPoz.js → trash-2-mJT6oWa2.js} +1 -1
  56. package/dist/assets/{use-infinite-scroll-loader-dwnaa_qi.js → use-infinite-scroll-loader-DJ1L81Dz.js} +1 -1
  57. package/dist/assets/{useConfirmDialog-mMeWD_yo.js → useConfirmDialog-BsVuqu1x.js} +1 -1
  58. package/dist/assets/{useMutation-BmxxvCNf.js → useMutation-CNcz2fgt.js} +1 -1
  59. package/dist/assets/x-Czwxm82I.js +1 -0
  60. package/dist/index.html +95 -21
  61. package/dist/manifest.webmanifest +30 -0
  62. package/dist/offline.html +102 -0
  63. package/dist/pwa-192.png +0 -0
  64. package/dist/pwa-512.png +0 -0
  65. package/dist/runtime-icons/claude.ico +0 -0
  66. package/dist/runtime-icons/codex-openai.svg +6 -0
  67. package/dist/runtime-icons/hermes-agent.png +0 -0
  68. package/dist/sw.js +80 -0
  69. package/index.html +73 -1
  70. package/package.json +5 -5
  71. package/public/manifest.webmanifest +30 -0
  72. package/public/offline.html +102 -0
  73. package/public/pwa-192.png +0 -0
  74. package/public/pwa-512.png +0 -0
  75. package/public/runtime-icons/claude.ico +0 -0
  76. package/public/runtime-icons/codex-openai.svg +6 -0
  77. package/public/runtime-icons/hermes-agent.png +0 -0
  78. package/public/sw.js +80 -0
  79. package/src/account/components/account-panel.tsx +217 -97
  80. package/src/account/managers/account.manager.ts +3 -2
  81. package/src/api/chat-session-type.types.ts +7 -0
  82. package/src/api/runtime-control.types.ts +8 -0
  83. package/src/api/server-path.ts +27 -4
  84. package/src/api/types.ts +25 -10
  85. package/src/app.tsx +227 -54
  86. package/src/components/agents/agent-dialogs.tsx +499 -0
  87. package/src/components/agents/agents-page.test.tsx +238 -0
  88. package/src/components/agents/agents-page.tsx +435 -0
  89. package/src/components/chat/ChatSidebar.test.tsx +43 -1
  90. package/src/components/chat/ChatSidebar.tsx +35 -35
  91. package/src/components/chat/adapters/chat-message.summary-truncation.test.ts +66 -0
  92. package/src/components/chat/adapters/file-operation/card.ts +9 -0
  93. package/src/components/chat/adapters/file-operation/diff.ts +14 -0
  94. package/src/components/chat/{ChatConversationPanel.test.tsx → chat-conversation-panel.test.tsx} +127 -206
  95. package/src/components/chat/chat-conversation-panel.tsx +482 -0
  96. package/src/components/chat/chat-page-shell.tsx +19 -13
  97. package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
  98. package/src/components/chat/chat-session-type-option-item.tsx +68 -0
  99. package/src/components/chat/chat-session-workspace-file-preview.test.tsx +178 -0
  100. package/src/components/chat/chat-session-workspace-file-preview.tsx +278 -0
  101. package/src/components/chat/chat-session-workspace-panel-nav.tsx +203 -0
  102. package/src/components/chat/chat-session-workspace-panel.tsx +318 -0
  103. package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
  104. package/src/components/chat/chat-sidebar-session-item.tsx +32 -2
  105. package/src/components/chat/containers/chat-message-list.container.test.tsx +49 -0
  106. package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
  107. package/src/components/chat/managers/chat-session-list.manager.test.ts +12 -0
  108. package/src/components/chat/managers/chat-session-list.manager.ts +7 -0
  109. package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
  110. package/src/components/chat/ncp/ncp-chat-page.tsx +9 -7
  111. package/src/components/chat/ncp/ncp-chat-thread.manager.ts +179 -41
  112. package/src/components/chat/ncp/ncp-session-adapter.test.ts +36 -1
  113. package/src/components/chat/ncp/ncp-session-adapter.ts +20 -0
  114. package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +62 -13
  115. package/src/components/chat/ncp/tests/ncp-chat-thread.manager.test.ts +189 -0
  116. package/src/components/chat/presenter/chat-presenter-context.tsx +13 -2
  117. package/src/components/chat/session-header/chat-session-header-actions.test.tsx +26 -0
  118. package/src/components/chat/session-header/chat-session-header-actions.tsx +19 -1
  119. package/src/components/chat/stores/chat-input.store.ts +2 -1
  120. package/src/components/chat/stores/chat-thread.store.ts +27 -1
  121. package/src/components/chat/useChatSessionTypeState.ts +10 -1
  122. package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
  123. package/src/components/common/BrandHeader.tsx +3 -1
  124. package/src/components/common/session-context-icon.tsx +15 -2
  125. package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
  126. package/src/components/config/ChannelForm.test.tsx +89 -3
  127. package/src/components/config/ChannelForm.tsx +157 -188
  128. package/src/components/config/ChannelsList.test.tsx +163 -119
  129. package/src/components/config/ChannelsList.tsx +90 -101
  130. package/src/components/config/ProviderForm.tsx +108 -146
  131. package/src/components/config/ProvidersList.tsx +100 -123
  132. package/src/components/config/RuntimeConfig.tsx +141 -2
  133. package/src/components/config/SearchConfig.tsx +423 -393
  134. package/src/components/config/channel-form-fields-section.tsx +70 -37
  135. package/src/components/config/config-split-page.tsx +109 -0
  136. package/src/components/config/provider-enabled-field.tsx +17 -10
  137. package/src/components/config/runtime-control-card.test.tsx +56 -0
  138. package/src/components/config/runtime-control-card.tsx +25 -0
  139. package/src/components/config/runtime-presence-card.tsx +93 -79
  140. package/src/components/layout/AppLayout.tsx +25 -37
  141. package/src/components/layout/app-layout.test.tsx +46 -14
  142. package/src/components/layout/runtime-status-entry.test.tsx +157 -0
  143. package/src/components/layout/runtime-status-entry.tsx +143 -0
  144. package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
  145. package/src/components/marketplace/marketplace-list-card.tsx +288 -0
  146. package/src/components/marketplace/marketplace-page-data.ts +129 -0
  147. package/src/components/marketplace/marketplace-page.test.tsx +339 -0
  148. package/src/components/marketplace/marketplace-page.tsx +596 -0
  149. package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
  150. package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
  151. package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
  152. package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
  153. package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
  154. package/src/components/providers/ThemeProvider.tsx +5 -0
  155. package/src/components/remote/remote-access-page.test.tsx +105 -0
  156. package/src/components/remote/remote-access-page.tsx +248 -0
  157. package/src/components/ui/notice-card.tsx +129 -0
  158. package/src/components/ui/setting-row.tsx +51 -0
  159. package/src/components/ui/tag-chip.tsx +39 -0
  160. package/src/components/ui/textarea.tsx +19 -0
  161. package/src/hooks/server-path/use-server-path-read.ts +20 -0
  162. package/src/hooks/useConfig.ts +2 -1
  163. package/src/index.css +24 -0
  164. package/src/lib/app-resource-uri.test.ts +20 -0
  165. package/src/lib/app-resource-uri.ts +29 -0
  166. package/src/lib/chat-message.ts +14 -3
  167. package/src/lib/i18n.chat.ts +12 -1
  168. package/src/lib/i18n.pwa.ts +62 -0
  169. package/src/lib/i18n.remote.ts +1 -1
  170. package/src/lib/i18n.runtime-control.ts +31 -0
  171. package/src/lib/i18n.ts +7 -10
  172. package/src/lib/session-context.utils.test.ts +71 -0
  173. package/src/lib/session-context.utils.ts +28 -3
  174. package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
  175. package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
  176. package/src/pwa/components/pwa-install-entry.test.tsx +110 -0
  177. package/src/pwa/components/pwa-install-entry.tsx +205 -0
  178. package/src/pwa/managers/pwa-install.manager.test.ts +160 -0
  179. package/src/pwa/managers/pwa-install.manager.ts +232 -0
  180. package/src/pwa/managers/pwa-runtime.manager.ts +196 -0
  181. package/src/pwa/managers/pwa-shell-theme.manager.test.ts +30 -0
  182. package/src/pwa/managers/pwa-shell-theme.manager.ts +46 -0
  183. package/src/pwa/pwa-install-banner.storage.ts +55 -0
  184. package/src/pwa/pwa.types.ts +22 -0
  185. package/src/pwa/register-pwa.ts +14 -0
  186. package/src/pwa/stores/pwa.store.ts +17 -0
  187. package/src/vite-env.d.ts +9 -0
  188. package/dist/assets/ChannelsList-KIQIxluX.js +0 -8
  189. package/dist/assets/DocBrowser-CyDgAtO9.js +0 -1
  190. package/dist/assets/MarketplacePage-BySqkYDh.js +0 -49
  191. package/dist/assets/MarketplacePage-C0olZaek.js +0 -1
  192. package/dist/assets/McpMarketplacePage-DqKaiXO9.js +0 -40
  193. package/dist/assets/ModelConfig-IrmzoslW.js +0 -1
  194. package/dist/assets/ProvidersList-8_Kalfwl.js +0 -1
  195. package/dist/assets/RemoteAccessPage-CyQlSjPf.js +0 -1
  196. package/dist/assets/RuntimeConfig-Bk0uYBhf.js +0 -1
  197. package/dist/assets/SearchConfig-DNBR-UbE.js +0 -1
  198. package/dist/assets/SessionsConfig-Doqp5ghH.js +0 -2
  199. package/dist/assets/chat-page-Bph8M5zo.js +0 -58
  200. package/dist/assets/chat-session-display-CoN3Wmn-.js +0 -1
  201. package/dist/assets/config-layout-DmlGaay2.js +0 -1
  202. package/dist/assets/desktop-update-config-1KBrqLBC.js +0 -1
  203. package/dist/assets/i18n-CwHZ-9vt.js +0 -1
  204. package/dist/assets/index-DafCdM4F.css +0 -1
  205. package/dist/assets/index-DdksE6U3.js +0 -6
  206. package/dist/assets/loader-circle-PsSP0H9n.js +0 -1
  207. package/dist/assets/play-DBQbBxTA.js +0 -1
  208. package/dist/assets/plus-DUOVbsyQ.js +0 -1
  209. package/dist/assets/popover-C_mWOFzI.js +0 -1
  210. package/dist/assets/search-MChQRYR1.js +0 -1
  211. package/dist/assets/security-config-CbXfPZzr.js +0 -1
  212. package/dist/assets/select-Caud8QvU.js +0 -41
  213. package/dist/assets/skeleton-B-4vRq_Z.js +0 -1
  214. package/dist/assets/x-DuMhMATD.js +0 -1
  215. package/src/components/agents/AgentDialogs.tsx +0 -400
  216. package/src/components/agents/AgentsPage.test.tsx +0 -217
  217. package/src/components/agents/AgentsPage.tsx +0 -352
  218. package/src/components/chat/ChatConversationPanel.tsx +0 -256
  219. package/src/components/chat/chat-child-session-panel.tsx +0 -270
  220. package/src/components/config/config-layout.ts +0 -10
  221. package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
  222. package/src/components/marketplace/MarketplacePage.tsx +0 -827
  223. package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
  224. package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
  225. package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
  226. package/src/components/remote/RemoteAccessPage.tsx +0 -144
  227. /package/dist/assets/{config-hints-BZoDjXye.js → config-hints-BhTmc9P1.js} +0 -0
@@ -1,29 +1,46 @@
1
- import type { Dispatch, SetStateAction } from 'react';
2
- import { Input } from '@/components/ui/input';
3
- import { Label } from '@/components/ui/label';
4
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
5
- import { Switch } from '@/components/ui/switch';
6
- import { TagInput } from '@/components/common/TagInput';
7
- import { hintForPath } from '@/lib/config-hints';
8
- import { t } from '@/lib/i18n';
9
- import { Globe, Hash, KeyRound, Mail, Settings, ToggleLeft } from 'lucide-react';
10
- import type { ConfigUiHints } from '@/api/types';
11
- import type { ChannelField } from './channel-form-fields';
1
+ import type { Dispatch, SetStateAction } from "react";
2
+ import { Input } from "@/components/ui/input";
3
+ import { Label } from "@/components/ui/label";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger,
9
+ SelectValue,
10
+ } from "@/components/ui/select";
11
+ import { Switch } from "@/components/ui/switch";
12
+ import { TagInput } from "@/components/common/tag-input";
13
+ import { hintForPath } from "@/lib/config-hints";
14
+ import { t } from "@/lib/i18n";
15
+ import {
16
+ Globe,
17
+ Hash,
18
+ KeyRound,
19
+ Mail,
20
+ Settings,
21
+ ToggleLeft,
22
+ } from "lucide-react";
23
+ import type { ConfigUiHints } from "@/api/types";
24
+ import type { ChannelField } from "./channel-form-fields";
12
25
 
13
26
  function getFieldIcon(fieldName: string) {
14
- if (fieldName.includes('token') || fieldName.includes('secret') || fieldName.includes('password')) {
27
+ if (
28
+ fieldName.includes("token") ||
29
+ fieldName.includes("secret") ||
30
+ fieldName.includes("password")
31
+ ) {
15
32
  return <KeyRound className="h-3.5 w-3.5 text-gray-500" />;
16
33
  }
17
- if (fieldName.includes('url') || fieldName.includes('host')) {
34
+ if (fieldName.includes("url") || fieldName.includes("host")) {
18
35
  return <Globe className="h-3.5 w-3.5 text-gray-500" />;
19
36
  }
20
- if (fieldName.includes('email') || fieldName.includes('mail')) {
37
+ if (fieldName.includes("email") || fieldName.includes("mail")) {
21
38
  return <Mail className="h-3.5 w-3.5 text-gray-500" />;
22
39
  }
23
- if (fieldName.includes('id') || fieldName.includes('from')) {
40
+ if (fieldName.includes("id") || fieldName.includes("from")) {
24
41
  return <Hash className="h-3.5 w-3.5 text-gray-500" />;
25
42
  }
26
- if (fieldName === 'enabled' || fieldName === 'consentGranted') {
43
+ if (fieldName === "enabled" || fieldName === "consentGranted") {
27
44
  return <ToggleLeft className="h-3.5 w-3.5 text-gray-500" />;
28
45
  }
29
46
  return <Settings className="h-3.5 w-3.5 text-gray-500" />;
@@ -46,79 +63,95 @@ export function ChannelFormFieldsSection({
46
63
  jsonDrafts,
47
64
  setJsonDrafts,
48
65
  updateField,
49
- uiHints
66
+ uiHints,
50
67
  }: ChannelFormFieldsSectionProps) {
51
68
  return (
52
69
  <>
53
70
  {fields.map((field) => {
54
- const hint = hintForPath(`channels.${channelName}.${field.name}`, uiHints);
71
+ const hint = hintForPath(
72
+ `channels.${channelName}.${field.name}`,
73
+ uiHints,
74
+ );
55
75
  const label = hint?.label ?? field.label;
56
76
  const placeholder = hint?.placeholder;
57
77
 
58
78
  return (
59
79
  <div key={field.name} className="space-y-2.5">
60
- <Label htmlFor={field.name} className="flex items-center gap-2 text-sm font-medium text-gray-900">
80
+ <Label
81
+ htmlFor={field.name}
82
+ className="flex items-center gap-2 text-sm font-medium text-gray-900"
83
+ >
61
84
  {getFieldIcon(field.name)}
62
85
  {label}
63
86
  </Label>
64
87
 
65
- {field.type === 'boolean' && (
88
+ {field.type === "boolean" && (
66
89
  <div className="flex items-center justify-between rounded-xl bg-gray-50 p-3">
67
90
  <span className="text-sm text-gray-500">
68
- {(formData[field.name] as boolean) ? t('enabled') : t('disabled')}
91
+ {(formData[field.name] as boolean)
92
+ ? t("enabled")
93
+ : t("disabled")}
69
94
  </span>
70
95
  <Switch
71
96
  id={field.name}
72
97
  checked={(formData[field.name] as boolean) || false}
73
- onCheckedChange={(checked) => updateField(field.name, checked)}
98
+ onCheckedChange={(checked) =>
99
+ updateField(field.name, checked)
100
+ }
74
101
  className="data-[state=checked]:bg-emerald-500"
75
102
  />
76
103
  </div>
77
104
  )}
78
105
 
79
- {(field.type === 'text' || field.type === 'email') && (
106
+ {(field.type === "text" || field.type === "email") && (
80
107
  <Input
81
108
  id={field.name}
82
109
  type={field.type}
83
- value={(formData[field.name] as string) || ''}
84
- onChange={(event) => updateField(field.name, event.target.value)}
110
+ value={(formData[field.name] as string) || ""}
111
+ onChange={(event) =>
112
+ updateField(field.name, event.target.value)
113
+ }
85
114
  placeholder={placeholder}
86
115
  className="rounded-xl"
87
116
  />
88
117
  )}
89
118
 
90
- {field.type === 'password' && (
119
+ {field.type === "password" && (
91
120
  <Input
92
121
  id={field.name}
93
122
  type="password"
94
- value={(formData[field.name] as string) || ''}
95
- onChange={(event) => updateField(field.name, event.target.value)}
96
- placeholder={placeholder ?? t('leaveBlankToKeepUnchanged')}
123
+ value={(formData[field.name] as string) || ""}
124
+ onChange={(event) =>
125
+ updateField(field.name, event.target.value)
126
+ }
127
+ placeholder={placeholder ?? t("leaveBlankToKeepUnchanged")}
97
128
  className="rounded-xl"
98
129
  />
99
130
  )}
100
131
 
101
- {field.type === 'number' && (
132
+ {field.type === "number" && (
102
133
  <Input
103
134
  id={field.name}
104
135
  type="number"
105
136
  value={(formData[field.name] as number) || 0}
106
- onChange={(event) => updateField(field.name, parseInt(event.target.value, 10) || 0)}
137
+ onChange={(event) =>
138
+ updateField(field.name, parseInt(event.target.value, 10) || 0)
139
+ }
107
140
  placeholder={placeholder}
108
141
  className="rounded-xl"
109
142
  />
110
143
  )}
111
144
 
112
- {field.type === 'tags' && (
145
+ {field.type === "tags" && (
113
146
  <TagInput
114
147
  value={(formData[field.name] as string[]) || []}
115
148
  onChange={(tags) => updateField(field.name, tags)}
116
149
  />
117
150
  )}
118
151
 
119
- {field.type === 'select' && (
152
+ {field.type === "select" && (
120
153
  <Select
121
- value={(formData[field.name] as string) || ''}
154
+ value={(formData[field.name] as string) || ""}
122
155
  onValueChange={(value) => updateField(field.name, value)}
123
156
  >
124
157
  <SelectTrigger className="rounded-xl">
@@ -134,14 +167,14 @@ export function ChannelFormFieldsSection({
134
167
  </Select>
135
168
  )}
136
169
 
137
- {field.type === 'json' && (
170
+ {field.type === "json" && (
138
171
  <textarea
139
172
  id={field.name}
140
- value={jsonDrafts[field.name] ?? '{}'}
173
+ value={jsonDrafts[field.name] ?? "{}"}
141
174
  onChange={(event) =>
142
175
  setJsonDrafts((prev) => ({
143
176
  ...prev,
144
- [field.name]: event.target.value
177
+ [field.name]: event.target.value,
145
178
  }))
146
179
  }
147
180
  className="min-h-[120px] w-full resize-none rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"
@@ -0,0 +1,109 @@
1
+ import type { ButtonHTMLAttributes, HTMLAttributes } from "react";
2
+ import type { LucideIcon } from "lucide-react";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const CARD_CLASS =
6
+ "min-w-0 overflow-hidden rounded-2xl border border-gray-200/70 bg-white shadow-card xl:h-[calc(100vh-180px)] xl:max-h-[860px]";
7
+ type DivProps = HTMLAttributes<HTMLDivElement>;
8
+ type SectionProps = HTMLAttributes<HTMLElement>;
9
+
10
+ function ConfigSplitPane({ className, ...props }: SectionProps) {
11
+ return <section className={cn(CARD_CLASS, "flex flex-col", className)} {...props} />;
12
+ }
13
+
14
+ export function ConfigSplitPage({ className, ...props }: DivProps) {
15
+ return (
16
+ <div
17
+ className={cn(
18
+ "grid min-h-0 grid-cols-1 gap-5 xl:flex-1 xl:grid-cols-[340px_minmax(0,1fr)]",
19
+ className,
20
+ )}
21
+ {...props}
22
+ />
23
+ );
24
+ }
25
+
26
+ export { ConfigSplitPane as ConfigSplitSidebar, ConfigSplitPane as ConfigSplitDetailPane };
27
+
28
+ export function ConfigSplitEmptyPane({ className, ...props }: SectionProps) {
29
+ return (
30
+ <section
31
+ className={cn(CARD_CLASS, "flex items-center justify-center px-6 py-12 text-center", className)}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ export function ConfigSplitPaneHeader({ className, ...props }: DivProps) {
38
+ return <div className={cn("shrink-0 border-b border-gray-100", className)} {...props} />;
39
+ }
40
+
41
+ export function ConfigSplitPaneBody({
42
+ className,
43
+ scrollOnDesktop = true,
44
+ ...props
45
+ }: DivProps & { scrollOnDesktop?: boolean }) {
46
+ return (
47
+ <div
48
+ className={cn(
49
+ "min-h-0 flex-1",
50
+ scrollOnDesktop && "overflow-visible xl:overflow-y-auto xl:overscroll-contain",
51
+ className,
52
+ )}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ export function ConfigSplitPaneFooter({ className, ...props }: DivProps) {
59
+ return <div className={cn("shrink-0 border-t border-gray-100", className)} {...props} />;
60
+ }
61
+
62
+ export function ConfigSelectionCard({
63
+ active = false,
64
+ className,
65
+ type = "button",
66
+ ...props
67
+ }: ButtonHTMLAttributes<HTMLButtonElement> & { active?: boolean }) {
68
+ return (
69
+ <button
70
+ type={type}
71
+ className={cn(
72
+ "w-full rounded-xl border p-2.5 text-left transition-all",
73
+ active
74
+ ? "border-primary/30 bg-primary-50/40 shadow-sm"
75
+ : "border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70",
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ export function ConfigSplitEmptyState({
84
+ icon: Icon,
85
+ title,
86
+ description,
87
+ className,
88
+ ...props
89
+ }: HTMLAttributes<HTMLDivElement> & {
90
+ icon: LucideIcon;
91
+ title: string;
92
+ description?: string;
93
+ }) {
94
+ return (
95
+ <div
96
+ className={cn(
97
+ "flex min-h-[220px] flex-col items-center justify-center rounded-xl border border-dashed border-gray-200 bg-gray-50/70 px-4 py-10 text-center",
98
+ className,
99
+ )}
100
+ {...props}
101
+ >
102
+ <div className="mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-white">
103
+ <Icon className="h-5 w-5 text-gray-300" />
104
+ </div>
105
+ <p className="text-sm font-medium text-gray-700">{title}</p>
106
+ {description ? <p className="mt-2 text-xs text-gray-500">{description}</p> : null}
107
+ </div>
108
+ );
109
+ }
@@ -1,6 +1,6 @@
1
- import { Label } from '@/components/ui/label';
2
- import { Switch } from '@/components/ui/switch';
3
- import { t } from '@/lib/i18n';
1
+ import { SettingRow } from "@/components/ui/setting-row";
2
+ import { Switch } from "@/components/ui/switch";
3
+ import { t } from "@/lib/i18n";
4
4
 
5
5
  type ProviderEnabledFieldProps = {
6
6
  enabled: boolean;
@@ -8,13 +8,20 @@ type ProviderEnabledFieldProps = {
8
8
  };
9
9
 
10
10
  export function ProviderEnabledField(props: ProviderEnabledFieldProps) {
11
+ const { enabled, onChange } = props;
12
+
11
13
  return (
12
- <div className="flex items-center justify-between rounded-xl border border-gray-200 bg-gray-50 px-4 py-3">
13
- <Label className="text-sm font-medium text-gray-900">{t('enabled')}</Label>
14
- <div className="flex items-center gap-3">
15
- <span className="text-xs text-gray-500">{props.enabled ? t('enabled') : t('disabled')}</span>
16
- <Switch checked={props.enabled} onCheckedChange={props.onChange} />
17
- </div>
18
- </div>
14
+ <SettingRow
15
+ tone="muted"
16
+ title={t("enabled")}
17
+ control={
18
+ <div className="flex items-center gap-3">
19
+ <span className="text-xs text-gray-500">
20
+ {enabled ? t("enabled") : t("disabled")}
21
+ </span>
22
+ <Switch checked={enabled} onCheckedChange={onChange} />
23
+ </div>
24
+ }
25
+ />
19
26
  );
20
27
  }
@@ -49,6 +49,7 @@ describe('RuntimeControlCard', () => {
49
49
  lifecycle: 'healthy',
50
50
  serviceState: 'running',
51
51
  message: 'runtime healthy',
52
+ pendingRestart: null,
52
53
  canStartService: {
53
54
  available: false,
54
55
  requiresConfirmation: false,
@@ -90,6 +91,7 @@ describe('RuntimeControlCard', () => {
90
91
  lifecycle: 'healthy',
91
92
  serviceState: 'running',
92
93
  message: 'runtime healthy',
94
+ pendingRestart: null,
93
95
  canStartService: {
94
96
  available: false,
95
97
  requiresConfirmation: false,
@@ -212,6 +214,7 @@ describe('RuntimeControlCard', () => {
212
214
  lifecycle: 'healthy',
213
215
  serviceState: 'running',
214
216
  message: 'runtime healthy',
217
+ pendingRestart: null,
215
218
  canStartService: {
216
219
  available: false,
217
220
  requiresConfirmation: false,
@@ -252,4 +255,57 @@ describe('RuntimeControlCard', () => {
252
255
  });
253
256
  expect(toast.success).toHaveBeenCalledWith('NextClaw app restart scheduled.');
254
257
  });
258
+
259
+ it('shows a pending restart notice instead of auto-applying hidden restarts', () => {
260
+ const queryClient = new QueryClient();
261
+
262
+ mocks.useRuntimeControl.mockReturnValue({
263
+ data: {
264
+ environment: 'managed-local-service',
265
+ lifecycle: 'healthy',
266
+ serviceState: 'running',
267
+ message: 'Saved changes are waiting for a manual restart.',
268
+ pendingRestart: {
269
+ changedPaths: ['plugins', 'ui'],
270
+ message: 'Saved changes are waiting for a manual restart.',
271
+ reasons: ['config reload requires restart: plugins, ui'],
272
+ requestedAt: '2026-04-17T10:00:00.000Z'
273
+ },
274
+ canStartService: {
275
+ available: false,
276
+ requiresConfirmation: false,
277
+ impact: 'brief-ui-disconnect',
278
+ reasonIfUnavailable: '当前页面已经由运行中的本地服务托管。'
279
+ },
280
+ canRestartService: {
281
+ available: true,
282
+ requiresConfirmation: false,
283
+ impact: 'brief-ui-disconnect',
284
+ },
285
+ canStopService: {
286
+ available: true,
287
+ requiresConfirmation: true,
288
+ impact: 'brief-ui-disconnect',
289
+ },
290
+ canRestartApp: {
291
+ available: false,
292
+ requiresConfirmation: true,
293
+ impact: 'full-app-relaunch',
294
+ reasonIfUnavailable: 'desktop only',
295
+ },
296
+ managementHint: 'This page is served by the running local service.'
297
+ },
298
+ isError: false,
299
+ error: null,
300
+ });
301
+
302
+ render(<RuntimeControlCard />, {
303
+ wrapper: createWrapper(queryClient),
304
+ });
305
+
306
+ expect(screen.getByText('待重启')).toBeTruthy();
307
+ expect(screen.getByText('这次改动已经保存,但系统不会自动重启。请在你方便的时候手动重启,重启完成后该提示会自动清空。')).toBeTruthy();
308
+ expect(screen.getByText('plugins')).toBeTruthy();
309
+ expect(screen.getByText('ui')).toBeTruthy();
310
+ });
255
311
  });
@@ -159,6 +159,7 @@ export function RuntimeControlCard() {
159
159
  const displayedMessage = localMessage ?? controlView?.message ?? t('runtimeControlDescription');
160
160
  const busy = serviceActionMutation.isPending || busyAction !== null || displayedLifecycle === 'recovering';
161
161
  const visibleActions = resolveVisibleActions(controlView);
162
+ const pendingRestart = controlView?.pendingRestart ?? null;
162
163
 
163
164
  const resetLocalState = () => {
164
165
  setLocalLifecycle(null);
@@ -261,6 +262,30 @@ export function RuntimeControlCard() {
261
262
  ) : null}
262
263
  </div>
263
264
 
265
+ {pendingRestart ? (
266
+ <div className="rounded-xl border border-amber-200 bg-amber-50 p-4 space-y-3">
267
+ <div className="text-sm font-medium text-amber-900">{t('runtimeControlPendingRestartTitle')}</div>
268
+ <p className="text-sm text-amber-800">{t('runtimeControlPendingRestartDescription')}</p>
269
+ {pendingRestart.changedPaths.length > 0 ? (
270
+ <div className="space-y-2">
271
+ <div className="text-xs font-medium uppercase tracking-[0.08em] text-amber-700">
272
+ {t('runtimeControlPendingRestartPaths')}
273
+ </div>
274
+ <div className="flex flex-wrap gap-2">
275
+ {pendingRestart.changedPaths.map((path) => (
276
+ <span
277
+ key={path}
278
+ className="rounded-full border border-amber-200 bg-white px-2.5 py-1 text-xs text-amber-800"
279
+ >
280
+ {path}
281
+ </span>
282
+ ))}
283
+ </div>
284
+ </div>
285
+ ) : null}
286
+ </div>
287
+ ) : null}
288
+
264
289
  <div className="flex flex-col gap-3 md:flex-row md:flex-wrap">
265
290
  {visibleActions.map((item) => {
266
291
  const isBusyAction = busyAction === item.action;