@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.
- package/CHANGELOG.md +96 -0
- package/dist/assets/ChannelsList-M9FTK1Ak.js +8 -0
- package/dist/assets/DocBrowser-CH7-GxlL.js +1 -0
- package/dist/assets/{DocBrowser-BMxf9CIK.js → DocBrowser-DMfr0Oow.js} +1 -1
- package/dist/assets/{DocBrowserContext-Ce28gRXt.js → DocBrowserContext-BXydqby-.js} +1 -1
- package/dist/assets/{LogoBadge-o92MOA2L.js → LogoBadge-hO7tY7hE.js} +1 -1
- package/dist/assets/ModelConfig-CNIgLf0e.js +1 -0
- package/dist/assets/{ProviderScopedModelInput-CmTIzgI7.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-Ba1RPJaG.js → SecretsConfig-MEt6MjuD.js} +2 -2
- package/dist/assets/SessionsConfig-DifCiXwR.js +2 -0
- package/dist/assets/{app-query-client-DniXoIN5.js → app-query-client-9jNewezV.js} +1 -1
- package/dist/assets/{book-open-DocgeQtR.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-BvKvh1R8.js → chunk-JZWAC4HX-C5dEc8hV.js} +1 -1
- package/dist/assets/{client-CVqPF5ie.js → client-C-8fH7-c.js} +1 -1
- package/dist/assets/{config-Bop2oB18.js → config-CBScxsdV.js} +1 -1
- package/dist/assets/config-split-page-BUout_Ak.js +1 -0
- package/dist/assets/{createLucideIcon-DVv8taGY.js → createLucideIcon-dy5ie7Ox.js} +1 -1
- package/dist/assets/desktop-update-config-2BS6BMkW.js +1 -0
- package/dist/assets/{dist-DmAlInRu.js → dist-BruyLa92.js} +1 -1
- package/dist/assets/{dist-Da5Gm_pO.js → dist-Cy7_j6hA.js} +1 -1
- package/dist/assets/download-BD0ETkB-.js +1 -0
- package/dist/assets/{external-link-DFjw3x1B.js → external-link-kZSAO8nT.js} +1 -1
- package/dist/assets/{hash-DJtaCejM.js → hash-BHJC2Ovu.js} +1 -1
- package/dist/assets/i18n-CpTZLchQ.js +1 -0
- package/dist/assets/index-mW8W2FUu.css +1 -0
- package/dist/assets/index-zDZfXoI4.js +6 -0
- package/dist/assets/{infiniteQueryBehavior-DHSEQ3OH.js → infiniteQueryBehavior-CyER9hv0.js} +1 -1
- package/dist/assets/loader-circle-Bc2gCU33.js +1 -0
- package/dist/assets/{logos-DEFUIR12.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-Da3i3r6G.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-D6HkNtfz.js → refresh-ccw-COVhNHtN.js} +1 -1
- package/dist/assets/{refresh-cw-DRcvRrnc.js → refresh-cw-Bcv40SXy.js} +1 -1
- package/dist/assets/remote-access-page-CWHG-sug.js +1 -0
- package/dist/assets/{rotate-cw-BmDKfXtH.js → rotate-cw-oHMKJMC8.js} +1 -1
- package/dist/assets/{save-DHGmi2e9.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-DurKKSwA.js → status-dot-DpPtVzQT.js} +1 -1
- package/dist/assets/{switch-0rmPBRKI.js → switch-CM29eCAR.js} +1 -1
- package/dist/assets/{tabs-custom-5JLVL6v8.js → tabs-custom-YcZUWn3o.js} +1 -1
- package/dist/assets/tag-chip-DMXdnLcj.js +1 -0
- package/dist/assets/{trash-2-C6caKPoz.js → trash-2-mJT6oWa2.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-dwnaa_qi.js → use-infinite-scroll-loader-DJ1L81Dz.js} +1 -1
- package/dist/assets/{useConfirmDialog-mMeWD_yo.js → useConfirmDialog-BsVuqu1x.js} +1 -1
- package/dist/assets/{useMutation-BmxxvCNf.js → useMutation-CNcz2fgt.js} +1 -1
- package/dist/assets/x-Czwxm82I.js +1 -0
- package/dist/index.html +95 -21
- package/dist/manifest.webmanifest +30 -0
- package/dist/offline.html +102 -0
- package/dist/pwa-192.png +0 -0
- package/dist/pwa-512.png +0 -0
- 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/dist/sw.js +80 -0
- package/index.html +73 -1
- package/package.json +5 -5
- package/public/manifest.webmanifest +30 -0
- package/public/offline.html +102 -0
- package/public/pwa-192.png +0 -0
- package/public/pwa-512.png +0 -0
- 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/public/sw.js +80 -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/server-path.ts +27 -4
- package/src/api/types.ts +25 -10
- package/src/app.tsx +227 -54
- 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.test.tsx +43 -1
- package/src/components/chat/ChatSidebar.tsx +35 -35
- package/src/components/chat/adapters/chat-message.summary-truncation.test.ts +66 -0
- package/src/components/chat/adapters/file-operation/card.ts +9 -0
- package/src/components/chat/adapters/file-operation/diff.ts +14 -0
- package/src/components/chat/{ChatConversationPanel.test.tsx → chat-conversation-panel.test.tsx} +127 -206
- package/src/components/chat/chat-conversation-panel.tsx +482 -0
- 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 +178 -0
- package/src/components/chat/chat-session-workspace-file-preview.tsx +278 -0
- package/src/components/chat/chat-session-workspace-panel-nav.tsx +203 -0
- package/src/components/chat/chat-session-workspace-panel.tsx +318 -0
- package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
- package/src/components/chat/chat-sidebar-session-item.tsx +32 -2
- package/src/components/chat/containers/chat-message-list.container.test.tsx +49 -0
- package/src/components/chat/containers/chat-message-list.container.tsx +4 -0
- package/src/components/chat/managers/chat-session-list.manager.test.ts +12 -0
- package/src/components/chat/managers/chat-session-list.manager.ts +7 -0
- package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
- package/src/components/chat/ncp/ncp-chat-page.tsx +9 -7
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +179 -41
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +36 -1
- package/src/components/chat/ncp/ncp-session-adapter.ts +20 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +62 -13
- package/src/components/chat/ncp/tests/ncp-chat-thread.manager.test.ts +189 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +13 -2
- package/src/components/chat/session-header/chat-session-header-actions.test.tsx +26 -0
- package/src/components/chat/session-header/chat-session-header-actions.tsx +19 -1
- package/src/components/chat/stores/chat-input.store.ts +2 -1
- package/src/components/chat/stores/chat-thread.store.ts +27 -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/RuntimeConfig.tsx +141 -2
- 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/providers/ThemeProvider.tsx +5 -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/server-path/use-server-path-read.ts +20 -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/chat-message.ts +14 -3
- package/src/lib/i18n.chat.ts +12 -1
- package/src/lib/i18n.pwa.ts +62 -0
- package/src/lib/i18n.remote.ts +1 -1
- package/src/lib/i18n.runtime-control.ts +31 -0
- package/src/lib/i18n.ts +7 -10
- 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/src/pwa/components/pwa-install-entry.test.tsx +110 -0
- package/src/pwa/components/pwa-install-entry.tsx +205 -0
- package/src/pwa/managers/pwa-install.manager.test.ts +160 -0
- package/src/pwa/managers/pwa-install.manager.ts +232 -0
- package/src/pwa/managers/pwa-runtime.manager.ts +196 -0
- package/src/pwa/managers/pwa-shell-theme.manager.test.ts +30 -0
- package/src/pwa/managers/pwa-shell-theme.manager.ts +46 -0
- package/src/pwa/pwa-install-banner.storage.ts +55 -0
- package/src/pwa/pwa.types.ts +22 -0
- package/src/pwa/register-pwa.ts +14 -0
- package/src/pwa/stores/pwa.store.ts +17 -0
- package/src/vite-env.d.ts +9 -0
- package/dist/assets/ChannelsList-KIQIxluX.js +0 -8
- package/dist/assets/DocBrowser-CyDgAtO9.js +0 -1
- package/dist/assets/MarketplacePage-BySqkYDh.js +0 -49
- package/dist/assets/MarketplacePage-C0olZaek.js +0 -1
- package/dist/assets/McpMarketplacePage-DqKaiXO9.js +0 -40
- package/dist/assets/ModelConfig-IrmzoslW.js +0 -1
- package/dist/assets/ProvidersList-8_Kalfwl.js +0 -1
- package/dist/assets/RemoteAccessPage-CyQlSjPf.js +0 -1
- package/dist/assets/RuntimeConfig-Bk0uYBhf.js +0 -1
- package/dist/assets/SearchConfig-DNBR-UbE.js +0 -1
- package/dist/assets/SessionsConfig-Doqp5ghH.js +0 -2
- package/dist/assets/chat-page-Bph8M5zo.js +0 -58
- package/dist/assets/chat-session-display-CoN3Wmn-.js +0 -1
- package/dist/assets/config-layout-DmlGaay2.js +0 -1
- package/dist/assets/desktop-update-config-1KBrqLBC.js +0 -1
- package/dist/assets/i18n-CwHZ-9vt.js +0 -1
- package/dist/assets/index-DafCdM4F.css +0 -1
- package/dist/assets/index-DdksE6U3.js +0 -6
- package/dist/assets/loader-circle-PsSP0H9n.js +0 -1
- package/dist/assets/play-DBQbBxTA.js +0 -1
- package/dist/assets/plus-DUOVbsyQ.js +0 -1
- package/dist/assets/popover-C_mWOFzI.js +0 -1
- package/dist/assets/search-MChQRYR1.js +0 -1
- package/dist/assets/security-config-CbXfPZzr.js +0 -1
- package/dist/assets/select-Caud8QvU.js +0 -41
- package/dist/assets/skeleton-B-4vRq_Z.js +0 -1
- package/dist/assets/x-DuMhMATD.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/chat/ChatConversationPanel.tsx +0 -256
- package/src/components/chat/chat-child-session-panel.tsx +0 -270
- 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
- /package/dist/assets/{config-hints-BZoDjXye.js → config-hints-BhTmc9P1.js} +0 -0
|
@@ -1,41 +1,44 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { StatusDot } from
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { KeyRound, Plus, Search } from "lucide-react";
|
|
3
|
+
import { useConfig, useConfigMeta, useConfigSchema, useCreateProvider } from "@/hooks/useConfig";
|
|
4
|
+
import { LogoBadge } from "@/components/common/LogoBadge";
|
|
5
|
+
import { PageHeader, PageLayout } from "@/components/layout/page-layout";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Input } from "@/components/ui/input";
|
|
8
|
+
import { Tabs } from "@/components/ui/tabs-custom";
|
|
9
|
+
import { StatusDot } from "@/components/ui/status-dot";
|
|
10
|
+
import { hintForPath } from "@/lib/config-hints";
|
|
11
|
+
import { t } from "@/lib/i18n";
|
|
12
|
+
import { cn } from "@/lib/utils";
|
|
13
|
+
import { ProviderForm } from "./ProviderForm";
|
|
14
|
+
import {
|
|
15
|
+
ConfigSelectionCard,
|
|
16
|
+
ConfigSplitEmptyState,
|
|
17
|
+
ConfigSplitPage,
|
|
18
|
+
ConfigSplitPaneBody,
|
|
19
|
+
ConfigSplitPaneHeader,
|
|
20
|
+
ConfigSplitSidebar,
|
|
21
|
+
} from "./config-split-page";
|
|
22
|
+
|
|
23
|
+
function formatBasePreview(base?: string | null) {
|
|
17
24
|
if (!base) {
|
|
18
25
|
return null;
|
|
19
26
|
}
|
|
20
27
|
try {
|
|
21
28
|
const parsed = new URL(base);
|
|
22
|
-
|
|
23
|
-
return `${parsed.host}${path}`;
|
|
29
|
+
return `${parsed.host}${parsed.pathname && parsed.pathname !== "/" ? parsed.pathname : ""}`;
|
|
24
30
|
} catch {
|
|
25
|
-
return base.replace(/^https?:\/\//,
|
|
31
|
+
return base.replace(/^https?:\/\//, "");
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
function sortProvidersForDisplay<T extends { name: string }>(providers: T[])
|
|
35
|
+
function sortProvidersForDisplay<T extends { name: string }>(providers: T[]) {
|
|
30
36
|
return providers
|
|
31
37
|
.map((provider, index) => ({ provider, index }))
|
|
32
38
|
.sort((left, right) => {
|
|
33
|
-
const leftPriority = left.provider.name ===
|
|
34
|
-
const rightPriority = right.provider.name ===
|
|
35
|
-
|
|
36
|
-
return leftPriority - rightPriority;
|
|
37
|
-
}
|
|
38
|
-
return left.index - right.index;
|
|
39
|
+
const leftPriority = left.provider.name === "nextclaw" ? 1 : 0;
|
|
40
|
+
const rightPriority = right.provider.name === "nextclaw" ? 1 : 0;
|
|
41
|
+
return leftPriority !== rightPriority ? leftPriority - rightPriority : left.index - right.index;
|
|
39
42
|
})
|
|
40
43
|
.map(({ provider }) => provider);
|
|
41
44
|
}
|
|
@@ -45,130 +48,109 @@ export function ProvidersList() {
|
|
|
45
48
|
const { data: meta } = useConfigMeta();
|
|
46
49
|
const { data: schema } = useConfigSchema();
|
|
47
50
|
const createProvider = useCreateProvider();
|
|
48
|
-
|
|
49
|
-
const [
|
|
50
|
-
const [
|
|
51
|
-
const [query, setQuery] = useState('');
|
|
52
|
-
|
|
53
|
-
const uiHints = schema?.uiHints;
|
|
51
|
+
const [activeTab, setActiveTab] = useState("installed");
|
|
52
|
+
const [selectedProvider, setSelectedProvider] = useState<string>();
|
|
53
|
+
const [query, setQuery] = useState("");
|
|
54
54
|
const providers = useMemo(() => sortProvidersForDisplay(meta?.providers ?? []), [meta?.providers]);
|
|
55
55
|
const providersConfig = config?.providers ?? {};
|
|
56
|
-
const configuredCount = providers.filter((provider) => {
|
|
57
|
-
const current = providersConfig[provider.name];
|
|
58
|
-
return current?.enabled !== false && current?.apiKeySet;
|
|
59
|
-
}).length;
|
|
60
|
-
|
|
61
|
-
const tabs = [
|
|
62
|
-
{ id: 'installed', label: t('providersTabConfigured'), count: configuredCount },
|
|
63
|
-
{ id: 'all', label: t('providersTabAll'), count: providers.length }
|
|
64
|
-
];
|
|
65
56
|
|
|
66
57
|
const filteredProviders = useMemo(() => {
|
|
67
|
-
const baseProviders = providers;
|
|
68
|
-
const baseConfig = config?.providers ?? {};
|
|
69
58
|
const keyword = query.trim().toLowerCase();
|
|
70
|
-
return
|
|
71
|
-
.filter((provider) =>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return Boolean(current?.enabled !== false && current?.apiKeySet);
|
|
75
|
-
}
|
|
76
|
-
return true;
|
|
77
|
-
})
|
|
59
|
+
return providers
|
|
60
|
+
.filter((provider) =>
|
|
61
|
+
activeTab !== "installed" || Boolean(providersConfig[provider.name]?.enabled !== false && providersConfig[provider.name]?.apiKeySet),
|
|
62
|
+
)
|
|
78
63
|
.filter((provider) => {
|
|
79
64
|
if (!keyword) {
|
|
80
65
|
return true;
|
|
81
66
|
}
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
return display.includes(keyword) || provider.name.toLowerCase().includes(keyword);
|
|
67
|
+
const display =
|
|
68
|
+
providersConfig[provider.name]?.displayName?.trim() || provider.displayName || provider.name;
|
|
69
|
+
return display.toLowerCase().includes(keyword) || provider.name.toLowerCase().includes(keyword);
|
|
85
70
|
});
|
|
86
|
-
}, [
|
|
71
|
+
}, [activeTab, providers, providersConfig, query]);
|
|
87
72
|
|
|
88
73
|
useEffect(() => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const exists = filteredProviders.some((provider) => provider.name === selectedProvider);
|
|
95
|
-
if (!exists) {
|
|
96
|
-
setSelectedProvider(filteredProviders[0].name);
|
|
97
|
-
}
|
|
74
|
+
setSelectedProvider(
|
|
75
|
+
filteredProviders.some((provider) => provider.name === selectedProvider)
|
|
76
|
+
? selectedProvider
|
|
77
|
+
: filteredProviders[0]?.name,
|
|
78
|
+
);
|
|
98
79
|
}, [filteredProviders, selectedProvider]);
|
|
99
80
|
|
|
100
|
-
const selectedName = selectedProvider;
|
|
101
|
-
|
|
102
|
-
const handleCreateCustomProvider = async () => {
|
|
103
|
-
try {
|
|
104
|
-
const result = await createProvider.mutateAsync({ data: {} });
|
|
105
|
-
setActiveTab('all');
|
|
106
|
-
setQuery('');
|
|
107
|
-
setSelectedProvider(result.name);
|
|
108
|
-
} catch {
|
|
109
|
-
// toast handled in hook
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
81
|
if (!config || !meta) {
|
|
114
|
-
return <div className="p-8">{t(
|
|
82
|
+
return <div className="p-8">{t("providersLoading")}</div>;
|
|
115
83
|
}
|
|
116
84
|
|
|
117
85
|
return (
|
|
118
|
-
<PageLayout>
|
|
119
|
-
<PageHeader title={t(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
86
|
+
<PageLayout className="pb-0 xl:flex xl:h-full xl:min-h-0 xl:flex-col">
|
|
87
|
+
<PageHeader title={t("providersPageTitle")} description={t("providersPageDescription")} />
|
|
88
|
+
<ConfigSplitPage className="xl:min-h-0">
|
|
89
|
+
<ConfigSplitSidebar>
|
|
90
|
+
<ConfigSplitPaneHeader className="space-y-3 px-4 pb-3 pt-4">
|
|
91
|
+
<Tabs
|
|
92
|
+
tabs={[
|
|
93
|
+
{
|
|
94
|
+
id: "installed",
|
|
95
|
+
label: t("providersTabConfigured"),
|
|
96
|
+
count: providers.filter((provider) => providersConfig[provider.name]?.enabled !== false && providersConfig[provider.name]?.apiKeySet).length,
|
|
97
|
+
},
|
|
98
|
+
{ id: "all", label: t("providersTabAll"), count: providers.length },
|
|
99
|
+
]}
|
|
100
|
+
activeTab={activeTab}
|
|
101
|
+
onChange={setActiveTab}
|
|
102
|
+
className="mb-0"
|
|
103
|
+
/>
|
|
125
104
|
<Button
|
|
126
105
|
type="button"
|
|
127
106
|
variant="outline"
|
|
128
107
|
className="w-full justify-center"
|
|
129
|
-
onClick={
|
|
108
|
+
onClick={async () => {
|
|
109
|
+
try {
|
|
110
|
+
const result = await createProvider.mutateAsync({ data: {} });
|
|
111
|
+
setActiveTab("all");
|
|
112
|
+
setQuery("");
|
|
113
|
+
setSelectedProvider(result.name);
|
|
114
|
+
} catch {
|
|
115
|
+
// toast handled in hook
|
|
116
|
+
}
|
|
117
|
+
}}
|
|
130
118
|
disabled={createProvider.isPending}
|
|
131
119
|
>
|
|
132
120
|
<Plus className="mr-2 h-4 w-4" />
|
|
133
|
-
{createProvider.isPending ? t(
|
|
121
|
+
{createProvider.isPending ? t("saving") : t("providerAddCustom")}
|
|
134
122
|
</Button>
|
|
135
|
-
</
|
|
123
|
+
</ConfigSplitPaneHeader>
|
|
136
124
|
|
|
137
125
|
<div className="border-b border-gray-100 px-4 py-3">
|
|
138
126
|
<div className="relative">
|
|
139
127
|
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400" />
|
|
140
128
|
<Input
|
|
141
129
|
value={query}
|
|
142
|
-
onChange={(
|
|
143
|
-
placeholder={t(
|
|
130
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
131
|
+
placeholder={t("providersFilterPlaceholder")}
|
|
144
132
|
className="h-10 rounded-xl pl-9"
|
|
145
133
|
/>
|
|
146
134
|
</div>
|
|
147
135
|
</div>
|
|
148
136
|
|
|
149
|
-
<
|
|
137
|
+
<ConfigSplitPaneBody className="space-y-2 p-3">
|
|
150
138
|
{filteredProviders.map((provider) => {
|
|
151
|
-
const providerConfig =
|
|
139
|
+
const providerConfig = providersConfig[provider.name];
|
|
152
140
|
const isEnabled = providerConfig?.enabled !== false;
|
|
153
141
|
const isReady = Boolean(isEnabled && providerConfig?.apiKeySet);
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
142
|
+
const providerLabel =
|
|
143
|
+
providerConfig?.displayName?.trim() || provider.displayName || provider.name;
|
|
144
|
+
const description =
|
|
145
|
+
formatBasePreview(providerConfig?.apiBase || provider.defaultApiBase || "") ||
|
|
146
|
+
hintForPath(`providers.${provider.name}`, schema?.uiHints)?.help ||
|
|
147
|
+
t("providersDefaultDescription");
|
|
160
148
|
|
|
161
149
|
return (
|
|
162
|
-
<
|
|
150
|
+
<ConfigSelectionCard
|
|
163
151
|
key={provider.name}
|
|
164
|
-
type="button"
|
|
165
152
|
onClick={() => setSelectedProvider(provider.name)}
|
|
166
|
-
|
|
167
|
-
'w-full rounded-xl border p-2.5 text-left transition-all',
|
|
168
|
-
isActive
|
|
169
|
-
? 'border-primary/30 bg-primary-50/40 shadow-sm'
|
|
170
|
-
: 'border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70'
|
|
171
|
-
)}
|
|
153
|
+
active={selectedProvider === provider.name}
|
|
172
154
|
>
|
|
173
155
|
<div className="flex items-start justify-between gap-3">
|
|
174
156
|
<div className="flex min-w-0 items-center gap-3">
|
|
@@ -176,8 +158,8 @@ export function ProvidersList() {
|
|
|
176
158
|
name={provider.name}
|
|
177
159
|
src={provider.logo ? `/logos/${provider.logo}` : null}
|
|
178
160
|
className={cn(
|
|
179
|
-
|
|
180
|
-
isReady ?
|
|
161
|
+
"h-10 w-10 rounded-lg border",
|
|
162
|
+
isReady ? "border-primary/30 bg-white" : "border-gray-200/70 bg-white",
|
|
181
163
|
)}
|
|
182
164
|
imgClassName="h-5 w-5 object-contain"
|
|
183
165
|
fallback={<span className="text-sm font-semibold uppercase text-gray-500">{provider.name[0]}</span>}
|
|
@@ -188,35 +170,30 @@ export function ProvidersList() {
|
|
|
188
170
|
</div>
|
|
189
171
|
</div>
|
|
190
172
|
<StatusDot
|
|
191
|
-
status={isEnabled ? (isReady ?
|
|
192
|
-
label={isEnabled ? (isReady ? t(
|
|
173
|
+
status={isEnabled ? (isReady ? "ready" : "setup") : "inactive"}
|
|
174
|
+
label={isEnabled ? (isReady ? t("statusReady") : t("statusSetup")) : t("disabled")}
|
|
193
175
|
className="min-w-[56px] justify-center"
|
|
194
176
|
/>
|
|
195
177
|
</div>
|
|
196
|
-
</
|
|
178
|
+
</ConfigSelectionCard>
|
|
197
179
|
);
|
|
198
180
|
})}
|
|
199
181
|
|
|
200
|
-
{filteredProviders.length === 0
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
<p className="text-sm font-medium text-gray-700">{t('providersNoMatch')}</p>
|
|
206
|
-
</div>
|
|
207
|
-
)}
|
|
208
|
-
</div>
|
|
209
|
-
</section>
|
|
182
|
+
{filteredProviders.length === 0 ? (
|
|
183
|
+
<ConfigSplitEmptyState icon={KeyRound} title={t("providersNoMatch")} />
|
|
184
|
+
) : null}
|
|
185
|
+
</ConfigSplitPaneBody>
|
|
186
|
+
</ConfigSplitSidebar>
|
|
210
187
|
|
|
211
188
|
<ProviderForm
|
|
212
|
-
providerName={
|
|
189
|
+
providerName={selectedProvider}
|
|
213
190
|
onProviderDeleted={(deletedProvider) => {
|
|
214
191
|
if (deletedProvider === selectedProvider) {
|
|
215
192
|
setSelectedProvider(undefined);
|
|
216
193
|
}
|
|
217
194
|
}}
|
|
218
195
|
/>
|
|
219
|
-
</
|
|
196
|
+
</ConfigSplitPage>
|
|
220
197
|
</PageLayout>
|
|
221
198
|
);
|
|
222
199
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useConfig, useConfigSchema, useUpdateRuntime } from '@/hooks/useConfig';
|
|
3
|
-
import type { AgentBindingView, AgentProfileView } from '@/api/types';
|
|
3
|
+
import type { AgentBindingView, AgentProfileView, RuntimeEntryView } from '@/api/types';
|
|
4
4
|
import { Button } from '@/components/ui/button';
|
|
5
5
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
6
6
|
import { RuntimeControlCard } from '@/components/config/runtime-control-card';
|
|
@@ -19,11 +19,16 @@ import {
|
|
|
19
19
|
import { hintForPath } from '@/lib/config-hints';
|
|
20
20
|
import { t } from '@/lib/i18n';
|
|
21
21
|
import { PageLayout, PageHeader } from '@/components/layout/page-layout';
|
|
22
|
+
import { PwaInstallCard } from '@/pwa/components/pwa-install-entry';
|
|
22
23
|
import { Plus, Save, Trash2 } from 'lucide-react';
|
|
23
24
|
import { toast } from 'sonner';
|
|
24
25
|
|
|
25
26
|
type DmScope = 'main' | 'per-peer' | 'per-channel-peer' | 'per-account-channel-peer';
|
|
26
27
|
type PeerKind = '' | 'direct' | 'group' | 'channel';
|
|
28
|
+
type RuntimeEntryDraft = RuntimeEntryView & {
|
|
29
|
+
id: string;
|
|
30
|
+
configText: string;
|
|
31
|
+
};
|
|
27
32
|
|
|
28
33
|
const DM_SCOPE_OPTIONS: Array<{ value: DmScope; label: string }> = [
|
|
29
34
|
{ value: 'main', label: 'main' },
|
|
@@ -32,12 +37,25 @@ const DM_SCOPE_OPTIONS: Array<{ value: DmScope; label: string }> = [
|
|
|
32
37
|
{ value: 'per-account-channel-peer', label: 'per-account-channel-peer' }
|
|
33
38
|
];
|
|
34
39
|
|
|
40
|
+
const DEFAULT_NARP_STDIO_ENTRY_CONFIG = {
|
|
41
|
+
wireDialect: 'acp',
|
|
42
|
+
processScope: 'per-session',
|
|
43
|
+
command: '',
|
|
44
|
+
args: ['acp'],
|
|
45
|
+
env: {},
|
|
46
|
+
cwd: '',
|
|
47
|
+
startupTimeoutMs: 8000,
|
|
48
|
+
probeTimeoutMs: 3000,
|
|
49
|
+
requestTimeoutMs: 120000
|
|
50
|
+
};
|
|
51
|
+
|
|
35
52
|
function RuntimeConfigOverview() {
|
|
36
53
|
return (
|
|
37
54
|
<>
|
|
38
55
|
<PageHeader title={t('runtimePageTitle')} description={t('runtimePageDescription')} />
|
|
39
56
|
<RuntimeControlCard />
|
|
40
57
|
<RuntimePresenceCard />
|
|
58
|
+
<PwaInstallCard />
|
|
41
59
|
</>
|
|
42
60
|
);
|
|
43
61
|
}
|
|
@@ -49,6 +67,7 @@ export function RuntimeConfig() {
|
|
|
49
67
|
|
|
50
68
|
const [agents, setAgents] = useState<AgentProfileView[]>([]);
|
|
51
69
|
const [bindings, setBindings] = useState<AgentBindingView[]>([]);
|
|
70
|
+
const [runtimeEntries, setRuntimeEntries] = useState<RuntimeEntryDraft[]>([]);
|
|
52
71
|
const [dmScope, setDmScope] = useState<DmScope>('per-channel-peer');
|
|
53
72
|
const [defaultContextTokens, setDefaultContextTokens] = useState(200000);
|
|
54
73
|
const [defaultEngine, setDefaultEngine] = useState('native');
|
|
@@ -59,6 +78,16 @@ export function RuntimeConfig() {
|
|
|
59
78
|
}
|
|
60
79
|
setAgents((config.agents.list ?? []).map(hydrateRuntimeAgent));
|
|
61
80
|
setBindings((config.bindings ?? []).map(hydrateRuntimeBinding));
|
|
81
|
+
setRuntimeEntries(
|
|
82
|
+
Object.entries(config.agents.runtimes?.entries ?? {}).map(([id, entry]) => ({
|
|
83
|
+
id,
|
|
84
|
+
enabled: entry.enabled !== false,
|
|
85
|
+
label: entry.label ?? '',
|
|
86
|
+
type: entry.type,
|
|
87
|
+
config: entry.config ?? {},
|
|
88
|
+
configText: JSON.stringify(entry.config ?? {}, null, 2)
|
|
89
|
+
}))
|
|
90
|
+
);
|
|
62
91
|
setDmScope((config.session?.dmScope as DmScope) ?? 'per-channel-peer');
|
|
63
92
|
setDefaultContextTokens(config.agents.defaults.contextTokens ?? 200000);
|
|
64
93
|
setDefaultEngine(config.agents.defaults.engine ?? 'native');
|
|
@@ -72,6 +101,7 @@ export function RuntimeConfig() {
|
|
|
72
101
|
const agentEngineHint = hintForPath('agents.list.*.engine', uiHints);
|
|
73
102
|
const agentsHint = hintForPath('agents.list', uiHints);
|
|
74
103
|
const bindingsHint = hintForPath('bindings', uiHints);
|
|
104
|
+
const runtimeEntriesHint = hintForPath('agents.runtimes.entries', uiHints);
|
|
75
105
|
|
|
76
106
|
const knownAgentIds = useMemo(() => {
|
|
77
107
|
const ids = new Set<string>(['main']);
|
|
@@ -91,6 +121,29 @@ export function RuntimeConfig() {
|
|
|
91
121
|
const updateBinding = (index: number, next: AgentBindingView) => {
|
|
92
122
|
setBindings((prev) => prev.map((binding, cursor) => (cursor === index ? next : binding)));
|
|
93
123
|
};
|
|
124
|
+
|
|
125
|
+
const updateRuntimeEntry = (index: number, patch: Partial<RuntimeEntryDraft>) => {
|
|
126
|
+
setRuntimeEntries((prev) => prev.map((entry, cursor) => (cursor === index ? { ...entry, ...patch } : entry)));
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const removeRuntimeEntry = (index: number) => {
|
|
130
|
+
setRuntimeEntries((prev) => prev.filter((_, cursor) => cursor !== index));
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const addRuntimeEntry = () => {
|
|
134
|
+
setRuntimeEntries((prev) => [
|
|
135
|
+
...prev,
|
|
136
|
+
{
|
|
137
|
+
id: '',
|
|
138
|
+
enabled: true,
|
|
139
|
+
label: '',
|
|
140
|
+
type: 'narp-stdio',
|
|
141
|
+
config: DEFAULT_NARP_STDIO_ENTRY_CONFIG,
|
|
142
|
+
configText: JSON.stringify(DEFAULT_NARP_STDIO_ENTRY_CONFIG, null, 2)
|
|
143
|
+
}
|
|
144
|
+
]);
|
|
145
|
+
};
|
|
146
|
+
|
|
94
147
|
const handleSave = () => {
|
|
95
148
|
try {
|
|
96
149
|
const normalizedAgents = agents.map((agent, index) => {
|
|
@@ -150,6 +203,33 @@ export function RuntimeConfig() {
|
|
|
150
203
|
return normalized;
|
|
151
204
|
});
|
|
152
205
|
|
|
206
|
+
const normalizedRuntimeEntries = runtimeEntries.reduce<Record<string, RuntimeEntryView>>((entries, entry, index) => {
|
|
207
|
+
const id = entry.id.trim();
|
|
208
|
+
const type = entry.type.trim();
|
|
209
|
+
if (!id) {
|
|
210
|
+
throw new Error(`Runtime entry id is required at index ${index}.`);
|
|
211
|
+
}
|
|
212
|
+
if (!type) {
|
|
213
|
+
throw new Error(`Runtime entry type is required for "${id}".`);
|
|
214
|
+
}
|
|
215
|
+
if (entries[id]) {
|
|
216
|
+
throw new Error(`Duplicate runtime entry id: ${id}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const configValue = entry.configText.trim() ? JSON.parse(entry.configText) : {};
|
|
220
|
+
if (configValue && (typeof configValue !== 'object' || Array.isArray(configValue))) {
|
|
221
|
+
throw new Error(`Runtime entry config for "${id}" must be a JSON object.`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
entries[id] = {
|
|
225
|
+
enabled: entry.enabled !== false,
|
|
226
|
+
...(entry.label?.trim() ? { label: entry.label.trim() } : {}),
|
|
227
|
+
type,
|
|
228
|
+
config: (configValue as Record<string, unknown>) ?? {}
|
|
229
|
+
};
|
|
230
|
+
return entries;
|
|
231
|
+
}, {});
|
|
232
|
+
|
|
153
233
|
updateRuntime.mutate({
|
|
154
234
|
data: {
|
|
155
235
|
agents: {
|
|
@@ -157,7 +237,10 @@ export function RuntimeConfig() {
|
|
|
157
237
|
contextTokens: Math.max(1000, defaultContextTokens),
|
|
158
238
|
engine: defaultEngine.trim() || 'native'
|
|
159
239
|
},
|
|
160
|
-
list: normalizedAgents
|
|
240
|
+
list: normalizedAgents,
|
|
241
|
+
runtimes: {
|
|
242
|
+
entries: normalizedRuntimeEntries
|
|
243
|
+
}
|
|
161
244
|
},
|
|
162
245
|
bindings: normalizedBindings,
|
|
163
246
|
session: {
|
|
@@ -228,6 +311,62 @@ export function RuntimeConfig() {
|
|
|
228
311
|
</CardContent>
|
|
229
312
|
</Card>
|
|
230
313
|
|
|
314
|
+
<Card>
|
|
315
|
+
<CardHeader>
|
|
316
|
+
<CardTitle>{runtimeEntriesHint?.label ?? 'Runtime Entries'}</CardTitle>
|
|
317
|
+
<CardDescription>{runtimeEntriesHint?.help ?? '统一管理可见的 runtime entry 与其配置。'}</CardDescription>
|
|
318
|
+
</CardHeader>
|
|
319
|
+
<CardContent className="space-y-3">
|
|
320
|
+
{runtimeEntries.map((entry, index) => (
|
|
321
|
+
<div key={`${index}-${entry.id || 'runtime-entry'}`} className="rounded-xl border border-gray-200 p-3 space-y-3">
|
|
322
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
323
|
+
<Input
|
|
324
|
+
value={entry.id}
|
|
325
|
+
onChange={(event) => updateRuntimeEntry(index, { id: event.target.value })}
|
|
326
|
+
placeholder="entry id,例如 hermes"
|
|
327
|
+
/>
|
|
328
|
+
<Input
|
|
329
|
+
value={entry.label ?? ''}
|
|
330
|
+
onChange={(event) => updateRuntimeEntry(index, { label: event.target.value })}
|
|
331
|
+
placeholder="展示名称,例如 Hermes"
|
|
332
|
+
/>
|
|
333
|
+
<Input
|
|
334
|
+
value={entry.type}
|
|
335
|
+
onChange={(event) => updateRuntimeEntry(index, { type: event.target.value })}
|
|
336
|
+
placeholder="runtime type,例如 narp-stdio"
|
|
337
|
+
/>
|
|
338
|
+
<div className="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
|
|
339
|
+
<span className="text-sm text-gray-700">Enabled</span>
|
|
340
|
+
<Switch
|
|
341
|
+
checked={entry.enabled !== false}
|
|
342
|
+
onCheckedChange={(checked) => updateRuntimeEntry(index, { enabled: checked })}
|
|
343
|
+
/>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
<div className="space-y-2">
|
|
347
|
+
<label className="text-sm font-medium text-gray-800">Config JSON</label>
|
|
348
|
+
<textarea
|
|
349
|
+
className="min-h-32 w-full rounded-md border border-gray-200 px-3 py-2 text-sm font-mono"
|
|
350
|
+
value={entry.configText}
|
|
351
|
+
onChange={(event) => updateRuntimeEntry(index, { configText: event.target.value })}
|
|
352
|
+
spellCheck={false}
|
|
353
|
+
/>
|
|
354
|
+
</div>
|
|
355
|
+
<div className="flex justify-end">
|
|
356
|
+
<Button type="button" variant="outline" onClick={() => removeRuntimeEntry(index)}>
|
|
357
|
+
<Trash2 className="mr-2 h-4 w-4" />
|
|
358
|
+
{t('deleteButton')}
|
|
359
|
+
</Button>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
))}
|
|
363
|
+
<Button type="button" variant="outline" onClick={addRuntimeEntry}>
|
|
364
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
365
|
+
Add Runtime Entry
|
|
366
|
+
</Button>
|
|
367
|
+
</CardContent>
|
|
368
|
+
</Card>
|
|
369
|
+
|
|
231
370
|
<Card>
|
|
232
371
|
<CardHeader>
|
|
233
372
|
<CardTitle>{agentsHint?.label ?? t('agentList')}</CardTitle>
|