@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
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { PageHeader, PageLayout } from "@/components/layout/page-layout";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
} from "@/components/ui/card";
|
|
10
|
+
import { NoticeCard } from "@/components/ui/notice-card";
|
|
11
|
+
import { StatusDot } from "@/components/ui/status-dot";
|
|
12
|
+
import { useRemoteStatus } from "@/hooks/useRemoteAccess";
|
|
13
|
+
import { formatDateTime, t } from "@/lib/i18n";
|
|
14
|
+
import { useAppPresenter } from "@/presenter/app-presenter-context";
|
|
15
|
+
import { resolveRemoteWebBase } from "@/remote/remote-access.query";
|
|
16
|
+
import { buildRemoteAccessFeedbackView } from "@/remote/remote-access-feedback.service";
|
|
17
|
+
import { useRemoteAccessStore } from "@/remote/stores/remote-access.store";
|
|
18
|
+
import { Laptop, RefreshCcw, SquareArrowOutUpRight } from "lucide-react";
|
|
19
|
+
import { useEffect, useMemo } from "react";
|
|
20
|
+
|
|
21
|
+
function KeyValueRow(props: {
|
|
22
|
+
label: string;
|
|
23
|
+
value?: string | number | null;
|
|
24
|
+
muted?: boolean;
|
|
25
|
+
}) {
|
|
26
|
+
const { label, muted, value: rawValue } = props;
|
|
27
|
+
const value =
|
|
28
|
+
rawValue === undefined || rawValue === null || rawValue === ""
|
|
29
|
+
? "-"
|
|
30
|
+
: String(rawValue);
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex items-start justify-between gap-4 py-2 text-sm">
|
|
33
|
+
<span className="text-gray-500">{label}</span>
|
|
34
|
+
<span
|
|
35
|
+
className={
|
|
36
|
+
muted ? "text-right text-gray-500" : "text-right text-gray-900"
|
|
37
|
+
}
|
|
38
|
+
>
|
|
39
|
+
{value}
|
|
40
|
+
</span>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function RemoteAccessPage() {
|
|
46
|
+
const presenter = useAppPresenter();
|
|
47
|
+
const remoteStatus = useRemoteStatus();
|
|
48
|
+
const status = remoteStatus.data;
|
|
49
|
+
const actionLabel = useRemoteAccessStore((state) => state.actionLabel);
|
|
50
|
+
const feedbackView = useMemo(
|
|
51
|
+
() => buildRemoteAccessFeedbackView(status),
|
|
52
|
+
[status],
|
|
53
|
+
);
|
|
54
|
+
const busy = Boolean(actionLabel);
|
|
55
|
+
const deviceName =
|
|
56
|
+
status?.runtime?.deviceName?.trim() ||
|
|
57
|
+
status?.settings.deviceName?.trim() ||
|
|
58
|
+
t("remoteDeviceNameAuto");
|
|
59
|
+
const canOpenDeviceList = Boolean(
|
|
60
|
+
status?.account.loggedIn && resolveRemoteWebBase(status),
|
|
61
|
+
);
|
|
62
|
+
const { hero: heroView, issueHint } = feedbackView;
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
presenter.remoteAccessManager.syncStatus(status);
|
|
66
|
+
}, [presenter, status]);
|
|
67
|
+
|
|
68
|
+
if (remoteStatus.isLoading && !status) {
|
|
69
|
+
return <div className="p-8 text-gray-400">{t("remoteLoading")}</div>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<PageLayout className="space-y-6">
|
|
74
|
+
<PageHeader
|
|
75
|
+
title={t("remotePageTitle")}
|
|
76
|
+
description={t("remotePageDescription")}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
<div className="grid gap-6 xl:grid-cols-[1.2fr_0.8fr]">
|
|
80
|
+
<Card>
|
|
81
|
+
<CardHeader className="space-y-4">
|
|
82
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
83
|
+
<CardTitle>{heroView.title}</CardTitle>
|
|
84
|
+
<StatusDot
|
|
85
|
+
status={heroView.badgeStatus}
|
|
86
|
+
label={heroView.badgeLabel}
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
<CardDescription>{heroView.description}</CardDescription>
|
|
90
|
+
</CardHeader>
|
|
91
|
+
<CardContent className="space-y-5">
|
|
92
|
+
<NoticeCard tone="neutral">
|
|
93
|
+
<KeyValueRow
|
|
94
|
+
label={t("remoteSignedInAccount")}
|
|
95
|
+
value={status?.account.email}
|
|
96
|
+
/>
|
|
97
|
+
<KeyValueRow label={t("remoteDeviceName")} value={deviceName} />
|
|
98
|
+
<KeyValueRow
|
|
99
|
+
label={t("remoteConnectionStatus")}
|
|
100
|
+
value={heroView.badgeLabel}
|
|
101
|
+
/>
|
|
102
|
+
<KeyValueRow
|
|
103
|
+
label={t("remoteLastConnectedAt")}
|
|
104
|
+
value={
|
|
105
|
+
status?.runtime?.lastConnectedAt
|
|
106
|
+
? formatDateTime(status.runtime.lastConnectedAt)
|
|
107
|
+
: "-"
|
|
108
|
+
}
|
|
109
|
+
muted
|
|
110
|
+
/>
|
|
111
|
+
</NoticeCard>
|
|
112
|
+
|
|
113
|
+
<div className="flex flex-wrap gap-3">
|
|
114
|
+
{feedbackView.primaryAction ? (
|
|
115
|
+
<Button
|
|
116
|
+
onClick={() => {
|
|
117
|
+
if (feedbackView.primaryAction?.kind === "reauthorize") {
|
|
118
|
+
void presenter.remoteAccessManager.reauthorizeRemoteAccess(
|
|
119
|
+
status,
|
|
120
|
+
);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (feedbackView.primaryAction?.kind === "repair") {
|
|
124
|
+
void presenter.remoteAccessManager.repairRemoteAccess(
|
|
125
|
+
status,
|
|
126
|
+
);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
void presenter.remoteAccessManager.enableRemoteAccess(
|
|
130
|
+
status,
|
|
131
|
+
);
|
|
132
|
+
}}
|
|
133
|
+
disabled={busy}
|
|
134
|
+
>
|
|
135
|
+
{feedbackView.primaryAction.showRefreshIcon ? (
|
|
136
|
+
<RefreshCcw className="mr-2 h-4 w-4" />
|
|
137
|
+
) : null}
|
|
138
|
+
{actionLabel || feedbackView.primaryAction.label}
|
|
139
|
+
</Button>
|
|
140
|
+
) : null}
|
|
141
|
+
|
|
142
|
+
<Button
|
|
143
|
+
variant="outline"
|
|
144
|
+
onClick={() => void presenter.accountManager.openNextClawWeb()}
|
|
145
|
+
disabled={busy || !canOpenDeviceList}
|
|
146
|
+
>
|
|
147
|
+
<SquareArrowOutUpRight className="mr-2 h-4 w-4" />
|
|
148
|
+
{t("remoteOpenDeviceList")}
|
|
149
|
+
</Button>
|
|
150
|
+
|
|
151
|
+
{status?.settings.enabled ? (
|
|
152
|
+
<Button
|
|
153
|
+
variant="outline"
|
|
154
|
+
onClick={() =>
|
|
155
|
+
void presenter.remoteAccessManager.disableRemoteAccess(
|
|
156
|
+
status,
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
disabled={busy}
|
|
160
|
+
>
|
|
161
|
+
{t("remoteDisable")}
|
|
162
|
+
</Button>
|
|
163
|
+
) : null}
|
|
164
|
+
</div>
|
|
165
|
+
|
|
166
|
+
{feedbackView.shouldShowIssueHint && issueHint ? (
|
|
167
|
+
<NoticeCard
|
|
168
|
+
tone="warning"
|
|
169
|
+
title={issueHint.title}
|
|
170
|
+
description={issueHint.body}
|
|
171
|
+
/>
|
|
172
|
+
) : null}
|
|
173
|
+
|
|
174
|
+
<p className="text-xs text-gray-500">{t("remoteOpenWebHint")}</p>
|
|
175
|
+
</CardContent>
|
|
176
|
+
</Card>
|
|
177
|
+
|
|
178
|
+
<Card>
|
|
179
|
+
<CardHeader>
|
|
180
|
+
<CardTitle className="flex items-center gap-2">
|
|
181
|
+
<Laptop className="h-4 w-4 text-primary" />
|
|
182
|
+
{t("remoteDeviceSectionTitle")}
|
|
183
|
+
</CardTitle>
|
|
184
|
+
<CardDescription>
|
|
185
|
+
{t("remoteDeviceSectionDescription")}
|
|
186
|
+
</CardDescription>
|
|
187
|
+
</CardHeader>
|
|
188
|
+
<CardContent className="space-y-5">
|
|
189
|
+
<div className="flex flex-wrap gap-2">
|
|
190
|
+
<StatusDot
|
|
191
|
+
status={status?.account.loggedIn ? "ready" : "inactive"}
|
|
192
|
+
label={
|
|
193
|
+
status?.account.loggedIn
|
|
194
|
+
? t("remoteAccountConnected")
|
|
195
|
+
: t("remoteAccountNotConnected")
|
|
196
|
+
}
|
|
197
|
+
/>
|
|
198
|
+
<StatusDot
|
|
199
|
+
status={status?.settings.enabled ? "active" : "inactive"}
|
|
200
|
+
label={
|
|
201
|
+
status?.settings.enabled
|
|
202
|
+
? t("remoteEnabled")
|
|
203
|
+
: t("remoteStateDisabled")
|
|
204
|
+
}
|
|
205
|
+
/>
|
|
206
|
+
<StatusDot
|
|
207
|
+
status={status?.service.running ? "active" : "inactive"}
|
|
208
|
+
label={
|
|
209
|
+
status?.service.running
|
|
210
|
+
? t("remoteServiceRunning")
|
|
211
|
+
: t("remoteServiceStopped")
|
|
212
|
+
}
|
|
213
|
+
/>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<NoticeCard tone="neutral">
|
|
217
|
+
<KeyValueRow label={t("remoteDeviceName")} value={deviceName} />
|
|
218
|
+
<KeyValueRow
|
|
219
|
+
label={t("remoteConnectionStatus")}
|
|
220
|
+
value={heroView.badgeLabel}
|
|
221
|
+
/>
|
|
222
|
+
<KeyValueRow
|
|
223
|
+
label={t("remoteLastConnectedAt")}
|
|
224
|
+
value={
|
|
225
|
+
status?.runtime?.lastConnectedAt
|
|
226
|
+
? formatDateTime(status.runtime.lastConnectedAt)
|
|
227
|
+
: "-"
|
|
228
|
+
}
|
|
229
|
+
muted
|
|
230
|
+
/>
|
|
231
|
+
</NoticeCard>
|
|
232
|
+
|
|
233
|
+
<NoticeCard
|
|
234
|
+
tone="neutral"
|
|
235
|
+
borderStyle="dashed"
|
|
236
|
+
description={
|
|
237
|
+
status?.account.loggedIn
|
|
238
|
+
? t("remoteOpenWebHint")
|
|
239
|
+
: t("remoteStatusNeedsSignInDescription")
|
|
240
|
+
}
|
|
241
|
+
className="text-sm"
|
|
242
|
+
/>
|
|
243
|
+
</CardContent>
|
|
244
|
+
</Card>
|
|
245
|
+
</div>
|
|
246
|
+
</PageLayout>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const noticeCardVariants = cva("rounded-2xl border px-4 py-3", {
|
|
6
|
+
variants: {
|
|
7
|
+
tone: {
|
|
8
|
+
neutral: "border-gray-200 bg-gray-50 text-gray-900",
|
|
9
|
+
success: "border-emerald-200 bg-emerald-50 text-emerald-900",
|
|
10
|
+
warning: "border-amber-200 bg-amber-50 text-amber-900",
|
|
11
|
+
danger: "border-rose-200 bg-rose-50 text-rose-700",
|
|
12
|
+
info: "border-primary/20 bg-primary/10 text-primary",
|
|
13
|
+
},
|
|
14
|
+
borderStyle: {
|
|
15
|
+
solid: "",
|
|
16
|
+
dashed: "border-dashed",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
defaultVariants: {
|
|
20
|
+
tone: "neutral",
|
|
21
|
+
borderStyle: "solid",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const titleClassMap: Record<
|
|
26
|
+
NonNullable<VariantProps<typeof noticeCardVariants>["tone"]>,
|
|
27
|
+
string
|
|
28
|
+
> = {
|
|
29
|
+
neutral: "text-gray-900",
|
|
30
|
+
success: "text-emerald-800",
|
|
31
|
+
warning: "text-amber-900",
|
|
32
|
+
danger: "text-rose-700",
|
|
33
|
+
info: "text-primary",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const descriptionClassMap: Record<
|
|
37
|
+
NonNullable<VariantProps<typeof noticeCardVariants>["tone"]>,
|
|
38
|
+
string
|
|
39
|
+
> = {
|
|
40
|
+
neutral: "text-gray-600",
|
|
41
|
+
success: "text-emerald-700",
|
|
42
|
+
warning: "text-amber-800",
|
|
43
|
+
danger: "text-rose-700",
|
|
44
|
+
info: "text-primary/90",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export interface NoticeCardProps
|
|
48
|
+
extends
|
|
49
|
+
Omit<React.HTMLAttributes<HTMLDivElement>, "title">,
|
|
50
|
+
VariantProps<typeof noticeCardVariants> {
|
|
51
|
+
title?: React.ReactNode;
|
|
52
|
+
description?: React.ReactNode;
|
|
53
|
+
icon?: React.ReactNode;
|
|
54
|
+
actions?: React.ReactNode;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const NoticeCard = React.forwardRef<HTMLDivElement, NoticeCardProps>(
|
|
58
|
+
(
|
|
59
|
+
{
|
|
60
|
+
className,
|
|
61
|
+
tone = "neutral",
|
|
62
|
+
borderStyle = "solid",
|
|
63
|
+
title,
|
|
64
|
+
description,
|
|
65
|
+
icon,
|
|
66
|
+
actions,
|
|
67
|
+
children,
|
|
68
|
+
...props
|
|
69
|
+
},
|
|
70
|
+
ref,
|
|
71
|
+
) => {
|
|
72
|
+
const resolvedTone = tone ?? "neutral";
|
|
73
|
+
const hasHeader =
|
|
74
|
+
Boolean(title) ||
|
|
75
|
+
Boolean(description) ||
|
|
76
|
+
Boolean(icon) ||
|
|
77
|
+
Boolean(actions);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div
|
|
81
|
+
ref={ref}
|
|
82
|
+
className={cn(
|
|
83
|
+
noticeCardVariants({ tone: resolvedTone, borderStyle }),
|
|
84
|
+
className,
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
>
|
|
88
|
+
{hasHeader ? (
|
|
89
|
+
<div className="flex items-start justify-between gap-3">
|
|
90
|
+
<div className="min-w-0 flex-1">
|
|
91
|
+
<div className="flex items-start gap-2">
|
|
92
|
+
{icon ? <div className="mt-0.5 shrink-0">{icon}</div> : null}
|
|
93
|
+
<div className="min-w-0 flex-1">
|
|
94
|
+
{title ? (
|
|
95
|
+
<p
|
|
96
|
+
className={cn(
|
|
97
|
+
"text-sm font-medium",
|
|
98
|
+
titleClassMap[resolvedTone],
|
|
99
|
+
)}
|
|
100
|
+
>
|
|
101
|
+
{title}
|
|
102
|
+
</p>
|
|
103
|
+
) : null}
|
|
104
|
+
{description ? (
|
|
105
|
+
<p
|
|
106
|
+
className={cn(
|
|
107
|
+
title ? "mt-1" : "",
|
|
108
|
+
"text-sm leading-6",
|
|
109
|
+
descriptionClassMap[resolvedTone],
|
|
110
|
+
)}
|
|
111
|
+
>
|
|
112
|
+
{description}
|
|
113
|
+
</p>
|
|
114
|
+
) : null}
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
{actions ? <div className="shrink-0">{actions}</div> : null}
|
|
119
|
+
</div>
|
|
120
|
+
) : null}
|
|
121
|
+
{children ? (
|
|
122
|
+
<div className={cn(hasHeader ? "mt-3" : "")}>{children}</div>
|
|
123
|
+
) : null}
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
NoticeCard.displayName = "NoticeCard";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const settingRowVariants = cva(
|
|
6
|
+
"flex items-start justify-between gap-4 rounded-xl border p-4",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
tone: {
|
|
10
|
+
default: "border-gray-200 bg-white",
|
|
11
|
+
muted: "border-gray-200 bg-gray-50",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: {
|
|
15
|
+
tone: "default",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export interface SettingRowProps
|
|
21
|
+
extends
|
|
22
|
+
Omit<React.HTMLAttributes<HTMLDivElement>, "title">,
|
|
23
|
+
VariantProps<typeof settingRowVariants> {
|
|
24
|
+
title: React.ReactNode;
|
|
25
|
+
description?: React.ReactNode;
|
|
26
|
+
control?: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const SettingRow = React.forwardRef<HTMLDivElement, SettingRowProps>(
|
|
30
|
+
(
|
|
31
|
+
{ className, tone, title, description, control, children, ...props },
|
|
32
|
+
ref,
|
|
33
|
+
) => (
|
|
34
|
+
<div
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cn(settingRowVariants({ tone }), className)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
<div className="min-w-0 flex-1 space-y-2">
|
|
40
|
+
<div className="text-sm font-medium text-gray-900">{title}</div>
|
|
41
|
+
{description ? (
|
|
42
|
+
<p className="text-sm leading-6 text-gray-500">{description}</p>
|
|
43
|
+
) : null}
|
|
44
|
+
{children}
|
|
45
|
+
</div>
|
|
46
|
+
{control ? <div className="shrink-0">{control}</div> : null}
|
|
47
|
+
</div>
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
SettingRow.displayName = "SettingRow";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
const tagChipVariants = cva(
|
|
6
|
+
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
tone: {
|
|
10
|
+
subtle: "border-gray-200 bg-gray-50 text-gray-600",
|
|
11
|
+
neutral: "border-gray-200 bg-white text-gray-600",
|
|
12
|
+
success: "border-emerald-200 bg-emerald-50 text-emerald-700",
|
|
13
|
+
warning: "border-amber-200 bg-amber-50 text-amber-800",
|
|
14
|
+
danger: "border-rose-200 bg-rose-50 text-rose-600",
|
|
15
|
+
info: "border-primary/20 bg-primary/10 text-primary",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
tone: "subtle",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export interface TagChipProps
|
|
25
|
+
extends
|
|
26
|
+
React.HTMLAttributes<HTMLSpanElement>,
|
|
27
|
+
VariantProps<typeof tagChipVariants> {}
|
|
28
|
+
|
|
29
|
+
export const TagChip = React.forwardRef<HTMLSpanElement, TagChipProps>(
|
|
30
|
+
({ className, tone, ...props }, ref) => (
|
|
31
|
+
<span
|
|
32
|
+
ref={ref}
|
|
33
|
+
className={cn(tagChipVariants({ tone }), className)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
TagChip.displayName = "TagChip";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
|
|
4
|
+
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
5
|
+
|
|
6
|
+
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
7
|
+
({ className, ...props }, ref) => (
|
|
8
|
+
<textarea
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex min-h-28 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2.5 text-sm text-gray-900 placeholder:text-gray-300 placeholder:font-normal focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/40 transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
ref={ref}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
Textarea.displayName = "Textarea";
|
package/src/hooks/useConfig.ts
CHANGED
|
@@ -185,7 +185,8 @@ export function useUpdateChannel() {
|
|
|
185
185
|
updateChannel(channel, data as Parameters<typeof updateChannel>[1]),
|
|
186
186
|
onSuccess: () => {
|
|
187
187
|
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
188
|
-
|
|
188
|
+
queryClient.invalidateQueries({ queryKey: ['config-meta'] });
|
|
189
|
+
toast.success(t('configSavedApplying'));
|
|
189
190
|
},
|
|
190
191
|
onError: (error: Error) => {
|
|
191
192
|
toast.error(t('configSaveFailed') + ': ' + error.message);
|
package/src/index.css
CHANGED
|
@@ -59,6 +59,30 @@
|
|
|
59
59
|
background: hsl(var(--gray-400));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
.workspace-horizontal-scrollbar {
|
|
63
|
+
scrollbar-width: thin;
|
|
64
|
+
scrollbar-color: hsl(var(--gray-300) / 0.38) transparent;
|
|
65
|
+
scrollbar-gutter: stable;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.workspace-horizontal-scrollbar::-webkit-scrollbar {
|
|
69
|
+
width: 3px;
|
|
70
|
+
height: 2px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.workspace-horizontal-scrollbar::-webkit-scrollbar-track {
|
|
74
|
+
background: transparent;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.workspace-horizontal-scrollbar::-webkit-scrollbar-thumb {
|
|
78
|
+
background: hsl(var(--gray-300) / 0.38);
|
|
79
|
+
border-radius: 999px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.workspace-horizontal-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
83
|
+
background: hsl(var(--gray-400) / 0.48);
|
|
84
|
+
}
|
|
85
|
+
|
|
62
86
|
/* ========================================
|
|
63
87
|
GLASSMORPHISM
|
|
64
88
|
======================================== */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { resolveAppResourceUri } from "@/lib/app-resource-uri";
|
|
3
|
+
|
|
4
|
+
describe("resolveAppResourceUri", () => {
|
|
5
|
+
it("maps app resource uris to public app paths", () => {
|
|
6
|
+
expect(resolveAppResourceUri("app://runtime-icons/codex-openai.svg")).toBe(
|
|
7
|
+
"/runtime-icons/codex-openai.svg",
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("passes through ordinary image src values for compatibility", () => {
|
|
12
|
+
expect(resolveAppResourceUri("https://example.com/icon.png")).toBe(
|
|
13
|
+
"https://example.com/icon.png",
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("rejects app resource uris that escape the app resource directory", () => {
|
|
18
|
+
expect(resolveAppResourceUri("app://../icon.png")).toBeNull();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const APP_RESOURCE_URI_PREFIX = "app://";
|
|
2
|
+
|
|
3
|
+
function normalizeAppResourcePath(value: string): string | null {
|
|
4
|
+
const normalized = value.trim().replace(/^\/+/, "");
|
|
5
|
+
if (!normalized) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const segments = normalized.split("/");
|
|
9
|
+
if (
|
|
10
|
+
segments.some((segment) => segment.trim().length === 0 || segment === "." || segment === "..")
|
|
11
|
+
) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return segments.join("/");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveAppResourceUri(uri: string): string | null {
|
|
18
|
+
const normalized = uri.trim();
|
|
19
|
+
if (!normalized) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (!normalized.startsWith(APP_RESOURCE_URI_PREFIX)) {
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
const appResourcePath = normalizeAppResourcePath(
|
|
26
|
+
normalized.slice(APP_RESOURCE_URI_PREFIX.length),
|
|
27
|
+
);
|
|
28
|
+
return appResourcePath ? `/${appResourcePath}` : null;
|
|
29
|
+
}
|
package/src/lib/i18n.remote.ts
CHANGED
|
@@ -5,7 +5,7 @@ export const REMOTE_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
5
5
|
en: 'Make this device appear in your NextClaw Platform device list and open it from the web.'
|
|
6
6
|
},
|
|
7
7
|
remoteOpenWeb: { zh: '前往 NextClaw Web', en: 'Open NextClaw Web' },
|
|
8
|
-
remoteOpenDeviceList: { zh: '
|
|
8
|
+
remoteOpenDeviceList: { zh: '前往 NextClaw Web', en: 'Open NextClaw Web' },
|
|
9
9
|
remoteOpenWebHint: {
|
|
10
10
|
zh: '开启后,这台设备会出现在 NextClaw Web 中,你可以在那里点击打开并继续使用。',
|
|
11
11
|
en: 'Once enabled, this device appears in NextClaw Web, where you can open it and keep working.'
|
|
@@ -75,6 +75,37 @@ export const RUNTIME_CONTROL_LABELS: Record<string, { zh: string; en: string }>
|
|
|
75
75
|
runtimeControlRestartService: { zh: '重启服务', en: 'Restart Service' },
|
|
76
76
|
runtimeControlStopService: { zh: '停止服务', en: 'Stop Service' },
|
|
77
77
|
runtimeControlRestartApp: { zh: '重启应用', en: 'Restart App' },
|
|
78
|
+
runtimeControlPendingRestartTitle: { zh: '待重启', en: 'Pending Restart' },
|
|
79
|
+
runtimeControlPendingRestartDescription: {
|
|
80
|
+
zh: '这次改动已经保存,但系统不会自动重启。请在你方便的时候手动重启,重启完成后该提示会自动清空。',
|
|
81
|
+
en: 'These changes are saved, but the system will not restart automatically. Restart manually when you are ready, and this notice clears after the restart finishes.'
|
|
82
|
+
},
|
|
83
|
+
runtimeControlPendingRestartPaths: { zh: '待生效项', en: 'Changes Waiting For Restart' },
|
|
84
|
+
runtimeStatusLoadingTitle: { zh: '读取状态中', en: 'Loading status' },
|
|
85
|
+
runtimeStatusLoadingDescription: {
|
|
86
|
+
zh: '正在读取当前系统状态。',
|
|
87
|
+
en: 'Loading the current system status.'
|
|
88
|
+
},
|
|
89
|
+
runtimeStatusHealthyTitle: { zh: '系统正常', en: 'System healthy' },
|
|
90
|
+
runtimeStatusHealthyDescription: {
|
|
91
|
+
zh: '当前没有需要你立即处理的系统动作。',
|
|
92
|
+
en: 'There is no system action that needs your attention right now.'
|
|
93
|
+
},
|
|
94
|
+
runtimeStatusPendingRestartTitle: { zh: '待重启', en: 'Restart required' },
|
|
95
|
+
runtimeStatusPendingRestartDescription: {
|
|
96
|
+
zh: '这些改动已经保存,但不会自动重启。你可以在这里查看原因,并在方便的时候手动重启。',
|
|
97
|
+
en: 'These changes are saved, but the system will not restart automatically. Review the reason here and restart when you are ready.'
|
|
98
|
+
},
|
|
99
|
+
runtimeStatusPendingRestartReasonItem: {
|
|
100
|
+
zh: '{path} 改动将在重启后生效。',
|
|
101
|
+
en: 'Changes in {path} will apply after restart.'
|
|
102
|
+
},
|
|
103
|
+
runtimeStatusActionHint: {
|
|
104
|
+
zh: '准备好时再执行',
|
|
105
|
+
en: 'Run when you are ready'
|
|
106
|
+
},
|
|
107
|
+
runtimeStatusRestartAction: { zh: '立即重启', en: 'Restart now' },
|
|
108
|
+
runtimeStatusRestartingAction: { zh: '重启中...', en: 'Restarting...' },
|
|
78
109
|
runtimeControlStartingServiceHelp: {
|
|
79
110
|
zh: '正在启动 NextClaw 服务,页面可能会在服务恢复后重新连接。',
|
|
80
111
|
en: 'Starting the NextClaw service. The page may reconnect after the service becomes available.'
|
package/src/lib/i18n.ts
CHANGED
|
@@ -497,22 +497,19 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
497
497
|
|
|
498
498
|
// Remote & Status
|
|
499
499
|
...REMOTE_LABELS,
|
|
500
|
-
|
|
501
|
-
// Action labels
|
|
502
500
|
actionConfigure: { zh: '配置', en: 'Configure' },
|
|
503
501
|
actionAddProvider: { zh: '添加提供商', en: 'Add Provider' },
|
|
504
502
|
actionEnable: { zh: '启用', en: 'Enable' },
|
|
505
|
-
|
|
506
|
-
// Messages
|
|
507
503
|
configSaved: { zh: '配置已保存', en: 'Configuration saved' },
|
|
504
|
+
configSavedApplying: { zh: '配置已保存,正在应用', en: 'Configuration saved, applying changes' },
|
|
508
505
|
configSavedApplied: { zh: '配置已保存并已应用', en: 'Configuration saved and applied' },
|
|
509
506
|
configSaveFailed: { zh: '保存配置失败', en: 'Failed to save configuration' },
|
|
510
507
|
configReloaded: { zh: '配置已重载', en: 'Configuration reloaded' },
|
|
511
508
|
configReloadFailed: { zh: '重载配置失败', en: 'Failed to reload configuration' },
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
},
|
|
509
|
+
channelConfigApplying: { zh: '渠道配置正在应用', en: 'Channel configuration is applying' },
|
|
510
|
+
channelConfigApplied: { zh: '渠道配置已应用', en: 'Channel configuration applied' },
|
|
511
|
+
channelConfigApplyFailed: { zh: '渠道配置应用失败', en: 'Failed to apply channel configuration' },
|
|
512
|
+
feishuVerifySuccess: { zh: '验证成功,请到飞书开放平台完成事件订阅与发布后再开始使用。', en: 'Verified. Please finish Feishu event subscription and app publishing before using.' },
|
|
516
513
|
feishuVerifyFailed: { zh: '验证失败', en: 'Verification failed' },
|
|
517
514
|
enterTag: { zh: '输入后按回车...', en: 'Type and press Enter...' },
|
|
518
515
|
headerName: { zh: 'Header 名称', en: 'Header Name' },
|