@nextclaw/ui 0.12.9 → 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.
- package/CHANGELOG.md +61 -0
- package/dist/assets/ChannelsList-M9FTK1Ak.js +8 -0
- package/dist/assets/DocBrowser-CH7-GxlL.js +1 -0
- package/dist/assets/{DocBrowser-6ReNjvzF.js → DocBrowser-DMfr0Oow.js} +1 -1
- package/dist/assets/{DocBrowserContext-B6SpA7Qs.js → DocBrowserContext-BXydqby-.js} +1 -1
- package/dist/assets/{LogoBadge-ByNLYg65.js → LogoBadge-hO7tY7hE.js} +1 -1
- package/dist/assets/ModelConfig-CNIgLf0e.js +1 -0
- package/dist/assets/{ProviderScopedModelInput-Da7khnBA.js → ProviderScopedModelInput-B3HWP4oz.js} +1 -1
- package/dist/assets/ProvidersList-CHjMnRhX.js +1 -0
- package/dist/assets/RuntimeConfig-psp8nMSG.js +1 -0
- package/dist/assets/SearchConfig-CSoKip1f.js +1 -0
- package/dist/assets/{SecretsConfig-D281Rotl.js → SecretsConfig-MEt6MjuD.js} +2 -2
- package/dist/assets/SessionsConfig-DifCiXwR.js +2 -0
- package/dist/assets/{app-query-client-VnFElj4E.js → app-query-client-9jNewezV.js} +1 -1
- package/dist/assets/{book-open-BdcxxoQu.js → book-open-DzdUViDm.js} +1 -1
- package/dist/assets/chat-page-CLp0UV0Y.js +58 -0
- package/dist/assets/chat-session-display-DsYHx0RZ.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-DK5HPmIK.js → chunk-JZWAC4HX-C5dEc8hV.js} +1 -1
- package/dist/assets/{client-_i4MU2bB.js → client-C-8fH7-c.js} +1 -1
- package/dist/assets/{config-DtIQwrHF.js → config-CBScxsdV.js} +1 -1
- package/dist/assets/config-split-page-BUout_Ak.js +1 -0
- package/dist/assets/{createLucideIcon-BSeTgkZW.js → createLucideIcon-dy5ie7Ox.js} +1 -1
- package/dist/assets/desktop-update-config-2BS6BMkW.js +1 -0
- package/dist/assets/{dist-ccBFUi-o.js → dist-BruyLa92.js} +1 -1
- package/dist/assets/{dist-6TrrnPCR.js → dist-Cy7_j6hA.js} +1 -1
- package/dist/assets/{download-BhDxnyvU.js → download-BD0ETkB-.js} +1 -1
- package/dist/assets/{external-link-BgErLCNT.js → external-link-kZSAO8nT.js} +1 -1
- package/dist/assets/{hash-Bl7dr_UG.js → hash-BHJC2Ovu.js} +1 -1
- package/dist/assets/{i18n-eDHeDY0n.js → i18n-CpTZLchQ.js} +1 -1
- package/dist/assets/index-mW8W2FUu.css +1 -0
- package/dist/assets/index-zDZfXoI4.js +6 -0
- package/dist/assets/{infiniteQueryBehavior-ZDS92Qpp.js → infiniteQueryBehavior-CyER9hv0.js} +1 -1
- package/dist/assets/loader-circle-Bc2gCU33.js +1 -0
- package/dist/assets/{logos-x89HbrZ4.js → logos-B7gRObP8.js} +1 -1
- package/dist/assets/marketplace-page-3qVMnF3d.js +1 -0
- package/dist/assets/marketplace-page-BhFIeQzI.js +49 -0
- package/dist/assets/mcp-marketplace-page-DYfteJ1D.js +40 -0
- package/dist/assets/{page-layout-vZnghcFy.js → page-layout-0UcO9H9Z.js} +1 -1
- package/dist/assets/play-CKDjSQFL.js +1 -0
- package/dist/assets/plus-CG0QrVY_.js +1 -0
- package/dist/assets/{refresh-ccw-DT98i__E.js → refresh-ccw-COVhNHtN.js} +1 -1
- package/dist/assets/{refresh-cw-C47QSEwg.js → refresh-cw-Bcv40SXy.js} +1 -1
- package/dist/assets/remote-access-page-CWHG-sug.js +1 -0
- package/dist/assets/{rotate-cw-JtFzpNn6.js → rotate-cw-oHMKJMC8.js} +1 -1
- package/dist/assets/{save-3S6-H3Xw.js → save-EqJPOF0G.js} +1 -1
- package/dist/assets/search-BCAlB8nz.js +1 -0
- package/dist/assets/security-config-Slh0Mayz.js +1 -0
- package/dist/assets/select-CVz0t7MF.js +41 -0
- package/dist/assets/setting-row-CbVHAuQt.js +1 -0
- package/dist/assets/skeleton-D5rdKvzy.js +1 -0
- package/dist/assets/{status-dot-vbanNPFU.js → status-dot-DpPtVzQT.js} +1 -1
- package/dist/assets/{switch-BsLtHOH-.js → switch-CM29eCAR.js} +1 -1
- package/dist/assets/{tabs-custom-D3HYMt6k.js → tabs-custom-YcZUWn3o.js} +1 -1
- package/dist/assets/tag-chip-DMXdnLcj.js +1 -0
- package/dist/assets/{trash-2-G48scll7.js → trash-2-mJT6oWa2.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-DkNhD-42.js → use-infinite-scroll-loader-DJ1L81Dz.js} +1 -1
- package/dist/assets/{useConfirmDialog-BkvTN-vd.js → useConfirmDialog-BsVuqu1x.js} +1 -1
- package/dist/assets/{useMutation-CBWjE2uj.js → useMutation-CNcz2fgt.js} +1 -1
- package/dist/assets/x-Czwxm82I.js +1 -0
- package/dist/index.html +22 -22
- package/dist/runtime-icons/claude.ico +0 -0
- package/dist/runtime-icons/codex-openai.svg +6 -0
- package/dist/runtime-icons/hermes-agent.png +0 -0
- package/package.json +6 -6
- package/public/runtime-icons/claude.ico +0 -0
- package/public/runtime-icons/codex-openai.svg +6 -0
- package/public/runtime-icons/hermes-agent.png +0 -0
- package/src/account/components/account-panel.tsx +217 -97
- package/src/account/managers/account.manager.ts +3 -2
- package/src/api/chat-session-type.types.ts +7 -0
- package/src/api/runtime-control.types.ts +8 -0
- package/src/api/types.ts +8 -0
- package/src/app.tsx +221 -57
- package/src/components/agents/agent-dialogs.tsx +499 -0
- package/src/components/agents/agents-page.test.tsx +238 -0
- package/src/components/agents/agents-page.tsx +435 -0
- package/src/components/chat/ChatSidebar.tsx +11 -35
- package/src/components/chat/chat-conversation-panel.test.tsx +20 -0
- package/src/components/chat/chat-conversation-panel.tsx +83 -13
- package/src/components/chat/chat-page-shell.tsx +19 -13
- package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
- package/src/components/chat/chat-session-type-option-item.tsx +68 -0
- package/src/components/chat/chat-session-workspace-file-preview.test.tsx +87 -0
- package/src/components/chat/chat-session-workspace-file-preview.tsx +14 -43
- package/src/components/chat/chat-session-workspace-panel-nav.tsx +8 -2
- package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
- package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
- package/src/components/chat/ncp/ncp-chat-page.tsx +2 -0
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +1 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +3 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +10 -4
- package/src/components/chat/stores/chat-input.store.ts +2 -1
- package/src/components/chat/stores/chat-thread.store.ts +3 -1
- package/src/components/chat/useChatSessionTypeState.ts +10 -1
- package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
- package/src/components/common/BrandHeader.tsx +3 -1
- package/src/components/common/session-context-icon.tsx +15 -2
- package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
- package/src/components/config/ChannelForm.test.tsx +89 -3
- package/src/components/config/ChannelForm.tsx +157 -188
- package/src/components/config/ChannelsList.test.tsx +163 -119
- package/src/components/config/ChannelsList.tsx +90 -101
- package/src/components/config/ProviderForm.tsx +108 -146
- package/src/components/config/ProvidersList.tsx +100 -123
- package/src/components/config/SearchConfig.tsx +423 -393
- package/src/components/config/channel-form-fields-section.tsx +70 -37
- package/src/components/config/config-split-page.tsx +109 -0
- package/src/components/config/provider-enabled-field.tsx +17 -10
- package/src/components/config/runtime-control-card.test.tsx +56 -0
- package/src/components/config/runtime-control-card.tsx +25 -0
- package/src/components/config/runtime-presence-card.tsx +93 -79
- package/src/components/layout/AppLayout.tsx +25 -37
- package/src/components/layout/app-layout.test.tsx +46 -14
- package/src/components/layout/runtime-status-entry.test.tsx +157 -0
- package/src/components/layout/runtime-status-entry.tsx +143 -0
- package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
- package/src/components/marketplace/marketplace-list-card.tsx +288 -0
- package/src/components/marketplace/marketplace-page-data.ts +129 -0
- package/src/components/marketplace/marketplace-page.test.tsx +339 -0
- package/src/components/marketplace/marketplace-page.tsx +596 -0
- package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
- package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
- package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
- package/src/components/remote/remote-access-page.test.tsx +105 -0
- package/src/components/remote/remote-access-page.tsx +248 -0
- package/src/components/ui/notice-card.tsx +129 -0
- package/src/components/ui/setting-row.tsx +51 -0
- package/src/components/ui/tag-chip.tsx +39 -0
- package/src/components/ui/textarea.tsx +19 -0
- package/src/hooks/useConfig.ts +2 -1
- package/src/index.css +24 -0
- package/src/lib/app-resource-uri.test.ts +20 -0
- package/src/lib/app-resource-uri.ts +29 -0
- package/src/lib/i18n.remote.ts +1 -1
- package/src/lib/i18n.runtime-control.ts +31 -0
- package/src/lib/i18n.ts +5 -8
- package/src/lib/session-context.utils.test.ts +71 -0
- package/src/lib/session-context.utils.ts +28 -3
- package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
- package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
- package/dist/assets/ChannelsList-Ita2Zm1_.js +0 -8
- package/dist/assets/DocBrowser-BNwbPHf4.js +0 -1
- package/dist/assets/MarketplacePage-CjX2MWww.js +0 -1
- package/dist/assets/MarketplacePage-D0sDlYX4.js +0 -49
- package/dist/assets/McpMarketplacePage-BGKJm1sJ.js +0 -40
- package/dist/assets/ModelConfig-BzZenCH-.js +0 -1
- package/dist/assets/ProvidersList-BbVzRxjY.js +0 -1
- package/dist/assets/RemoteAccessPage-BaDH_X1Q.js +0 -1
- package/dist/assets/RuntimeConfig-F_XKGgLm.js +0 -1
- package/dist/assets/SearchConfig-BGkzXQP-.js +0 -1
- package/dist/assets/SessionsConfig-ChHQ7M5c.js +0 -2
- package/dist/assets/chat-page-Doe0yTtB.js +0 -58
- package/dist/assets/chat-session-display-cw78aiI_.js +0 -1
- package/dist/assets/config-layout-CHs0mAaR.js +0 -1
- package/dist/assets/desktop-update-config-Dpcf4BKG.js +0 -1
- package/dist/assets/index-CF9xve0E.js +0 -6
- package/dist/assets/index-FgA52VBt.css +0 -1
- package/dist/assets/loader-circle-ACM1s51e.js +0 -1
- package/dist/assets/play-CFUwCA2E.js +0 -1
- package/dist/assets/plus-rYsv72JG.js +0 -1
- package/dist/assets/popover-Bg1VoTZ6.js +0 -1
- package/dist/assets/search-3kFR_zh9.js +0 -1
- package/dist/assets/security-config-BWaiARNk.js +0 -1
- package/dist/assets/select-DJ2MUjBB.js +0 -41
- package/dist/assets/skeleton-ByQepn0M.js +0 -1
- package/dist/assets/x-ByDbItbq.js +0 -1
- package/src/components/agents/AgentDialogs.tsx +0 -400
- package/src/components/agents/AgentsPage.test.tsx +0 -217
- package/src/components/agents/AgentsPage.tsx +0 -352
- package/src/components/config/config-layout.ts +0 -10
- package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
- package/src/components/marketplace/MarketplacePage.tsx +0 -827
- package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
- package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
- package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
- package/src/components/remote/RemoteAccessPage.tsx +0 -144
|
@@ -1,29 +1,46 @@
|
|
|
1
|
-
import type { Dispatch, SetStateAction } from
|
|
2
|
-
import { Input } from
|
|
3
|
-
import { Label } from
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import
|
|
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 (
|
|
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(
|
|
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(
|
|
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(
|
|
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 ===
|
|
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(
|
|
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
|
|
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 ===
|
|
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)
|
|
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) =>
|
|
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 ===
|
|
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) =>
|
|
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 ===
|
|
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) =>
|
|
96
|
-
|
|
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 ===
|
|
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) =>
|
|
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 ===
|
|
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 ===
|
|
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 ===
|
|
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 {
|
|
2
|
-
import { Switch } from
|
|
3
|
-
import { t } from
|
|
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
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
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;
|