@nextclaw/ui 0.12.24 → 0.12.25
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 +68 -29
- package/dist/assets/api-DGD9_Bg4.js +15 -0
- package/dist/assets/app-manager-provider-oYdeYPSv.js +1 -0
- package/dist/assets/{book-open-DDlN5MvX.js → book-open-BcnAiKde.js} +1 -1
- package/dist/assets/channels-list-page-FJDuPwU6.js +8 -0
- package/dist/assets/chat-page-D1fMNBrT.js +1 -0
- package/dist/assets/config-split-page-CcrEUtwu.js +1 -0
- package/dist/assets/cpu-DPPwMzoC.js +3 -0
- package/dist/assets/{createLucideIcon-BLMK3QUd.js → createLucideIcon-DzY6wN61.js} +1 -1
- package/dist/assets/desktop-kk7qvZ-v.js +3 -0
- package/dist/assets/desktop-update-config-CP8dFYXK.js +1 -0
- package/dist/assets/{dialog-C3D7Be0p.js → dialog-BKo0RItd.js} +1 -1
- package/dist/assets/{dist-CPlbUgwU.js → dist-CFiwgaLs.js} +1 -1
- package/dist/assets/doc-browser-CAhfnm0D.js +1 -0
- package/dist/assets/{doc-browser-context-BJuMaI3o.js → doc-browser-context-FukQHvyo.js} +1 -1
- package/dist/assets/doc-browser-p9DDNPWB.js +1 -0
- package/dist/assets/doc-browser-rZIQIjuw.js +1 -0
- package/dist/assets/download-CMM8po31.js +1 -0
- package/dist/assets/{es2015-xqN1slyW.js → es2015-BhznEEyJ.js} +1 -1
- package/dist/assets/{external-link-DwfSfTLB.js → external-link-CpEvG65F.js} +1 -1
- package/dist/assets/i18n-D1144VAA.js +1 -0
- package/dist/assets/index-D-AAMKCt.js +103 -0
- package/dist/assets/index-DnBeV2Xm.css +1 -0
- package/dist/assets/{key-round-CJ5gDAAG.js → key-round-DUq47t0P.js} +1 -1
- package/dist/assets/marketplace-page-BrCLRIc4.js +105 -0
- package/dist/assets/marketplace-page-odDpPYEs.js +1 -0
- package/dist/assets/mcp-marketplace-page-CfbOBgKK.js +1 -0
- package/dist/assets/mcp-marketplace-page-DIq_SpMe.js +40 -0
- package/dist/assets/model-config-Bc6VVnxy.js +1 -0
- package/dist/assets/{notice-card-BFDbKQDA.js → notice-card-Dr6xCwva.js} +1 -1
- package/dist/assets/play-AqrNslHI.js +1 -0
- package/dist/assets/plus-B-YHtTNC.js +1 -0
- package/dist/assets/{popover-B86Dbfhf.js → popover-BDFNiLlg.js} +1 -1
- package/dist/assets/provider-scoped-model-input-BMTp4BEH.js +1 -0
- package/dist/assets/providers-list-DN0tvISH.js +1 -0
- package/dist/assets/refresh-cw-CrbD8EkT.js +1 -0
- package/dist/assets/remote-Dr3jcfWP.js +1 -0
- package/dist/assets/{rotate-cw-BZ2JObNs.js → rotate-cw-BN9yjccP.js} +1 -1
- package/dist/assets/runtime-config-page-CRWOwBbl.js +1 -0
- package/dist/assets/{save-euRxl8pI.js → save-CO_4qf6b.js} +1 -1
- package/dist/assets/{search-CLd7m0M7.js → search-CRtQwr-h.js} +1 -1
- package/dist/assets/search-config-C4c1yZSP.js +1 -0
- package/dist/assets/secrets-config-zAF30YfO.js +3 -0
- package/dist/assets/{select-CJ0wbo3D.js → select-BUTwE_lC.js} +1 -1
- package/dist/assets/{setting-row-D1Yygqp7.js → setting-row-BavcnXw1.js} +1 -1
- package/dist/assets/settings-MWL2SMyk.js +1 -0
- package/dist/assets/{sparkles-DVfeSVJQ.js → sparkles-BmgOD4nY.js} +1 -1
- package/dist/assets/{status-dot-ChvPCib9.js → status-dot-l3kPFdq_.js} +1 -1
- package/dist/assets/{tabs-custom-Hia_ong0.js → tabs-custom-D48zdZoc.js} +1 -1
- package/dist/assets/{tag-chip-FrkmkT8r.js → tag-chip-Dm2Lqnpu.js} +1 -1
- package/dist/assets/use-config-Cyv5IuSt.js +1 -0
- package/dist/assets/use-infinite-scroll-loader-Cvz8ZteY.js +1 -0
- package/dist/assets/x-BeyYA_h6.js +1 -0
- package/dist/index.html +29 -40
- package/package.json +9 -9
- package/src/app/components/layout/sidebar.layout.test.tsx +2 -4
- package/src/app/components/theme-provider.tsx +1 -0
- package/src/app/configs/app-navigation.config.ts +0 -6
- package/src/app/index.tsx +4 -7
- package/src/features/agents/components/agents-page.test.tsx +25 -15
- package/src/features/agents/components/agents-page.tsx +133 -172
- package/src/features/channels/components/config/channel-form.test.tsx +1 -0
- package/src/features/channels/components/config/channel-form.tsx +4 -3
- package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +38 -1
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +137 -40
- package/src/features/channels/index.ts +1 -1
- package/src/features/channels/utils/channel-form-fields.utils.test.ts +26 -0
- package/src/features/channels/utils/channel-form-fields.utils.ts +32 -18
- package/src/features/chat/components/chat-session-workspace-panel-nav.tsx +23 -4
- package/src/features/chat/components/chat-session-workspace-panel.tsx +34 -2
- package/src/features/chat/components/chat-sidebar-session-item.tsx +9 -3
- package/src/features/chat/components/conversation/chat-conversation-header.test.tsx +71 -0
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +6 -0
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +181 -61
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +54 -23
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.test.tsx +24 -0
- package/src/features/chat/components/conversation/session-header/chat-session-header-actions.tsx +26 -5
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +174 -0
- package/src/features/chat/components/layout/chat-sidebar.test.tsx +45 -8
- package/src/features/chat/components/layout/chat-sidebar.tsx +29 -46
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
- package/src/features/chat/components/workspace/session-cron-job-content.tsx +103 -0
- package/src/features/chat/hooks/use-ncp-agent-runtime.test.tsx +14 -0
- package/src/features/chat/hooks/use-ncp-chat-page-data.test.tsx +70 -0
- package/src/features/chat/hooks/use-ncp-chat-page-data.ts +1 -1
- package/src/features/chat/hooks/use-ncp-child-session-tabs-view.ts +2 -8
- package/src/features/chat/hooks/use-ncp-session-list-view.ts +1 -2
- package/src/features/chat/managers/chat-session-list.manager.test.ts +7 -9
- package/src/features/chat/managers/chat-session-list.manager.ts +5 -10
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +0 -2
- package/src/features/chat/managers/ncp-chat-presenter.manager.ts +6 -0
- package/src/features/chat/managers/ncp-chat-thread.manager.test.ts +52 -1
- package/src/features/chat/managers/ncp-chat-thread.manager.ts +21 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +5 -4
- package/src/features/chat/stores/chat-session-list.store.ts +0 -2
- package/src/features/chat/stores/chat-thread.store.ts +4 -0
- package/src/features/chat/utils/chat-session-display.utils.test.ts +83 -1
- package/src/features/chat/utils/chat-session-display.utils.ts +73 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.test.ts +22 -0
- package/src/features/chat/utils/ncp-session-adapter.utils.ts +32 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-scene-route.test.tsx +235 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.config.ts +162 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +355 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +118 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc-renderer.ts +201 -0
- package/src/features/marketplace/components/detail-doc/marketplace-detail-doc.test.ts +40 -0
- package/src/features/marketplace/components/marketplace-catalog-grid.tsx +114 -0
- package/src/features/marketplace/components/marketplace-detail-doc.ts +73 -24
- package/src/features/marketplace/components/marketplace-item-icon.tsx +45 -0
- package/src/features/marketplace/components/marketplace-list-card.tsx +177 -93
- package/src/features/marketplace/components/marketplace-page-detail.test.tsx +9 -2
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/marketplace/components/marketplace-page.test.tsx +25 -6
- package/src/features/marketplace/components/marketplace-page.tsx +154 -132
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +97 -0
- package/src/features/marketplace/hooks/use-marketplace.ts +59 -3
- package/src/features/system-status/components/config/runtime-agent-list-card.tsx +4 -8
- package/src/features/system-status/components/config/runtime-binding-list-card.tsx +5 -7
- package/src/features/system-status/components/config/runtime-config-editor.tsx +1 -19
- package/src/features/system-status/components/config/runtime-entry-list-card.tsx +10 -11
- package/src/features/system-status/components/config/runtime-settings-card.tsx +15 -23
- package/src/features/system-status/components/runtime-control-card.test.tsx +8 -6
- package/src/features/system-status/components/runtime-control-card.tsx +7 -6
- package/src/features/system-status/pages/runtime-config-page.test.tsx +19 -9
- package/src/features/system-status/pages/runtime-config-page.tsx +2 -3
- package/src/features/system-status/utils/runtime-config-agent.utils.ts +4 -4
- package/src/features/system-status/utils/system-status.utils.ts +31 -6
- package/src/index.css +8 -0
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +67 -0
- package/src/platforms/desktop/components/desktop-app-shell.tsx +46 -18
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +30 -0
- package/src/platforms/desktop/index.ts +6 -0
- package/src/platforms/desktop/types/desktop-update.types.ts +3 -0
- package/src/platforms/desktop/utils/desktop-host.utils.ts +56 -0
- package/src/shared/components/common/brand-header.tsx +36 -16
- package/src/shared/components/config/provider-form-support.ts +2 -22
- package/src/shared/components/cron-config.tsx +12 -58
- package/src/shared/components/doc-browser/doc-browser.tsx +4 -4
- package/src/shared/components/ui/select.tsx +19 -7
- package/src/shared/lib/api/channel-auth.types.ts +1 -0
- package/src/shared/lib/api/ncp-session.types.ts +9 -0
- package/src/shared/lib/api/types.ts +12 -1
- package/src/shared/lib/api/utils/marketplace.utils.ts +7 -1
- package/src/shared/lib/cron/cron-job-view.utils.ts +59 -0
- package/src/shared/lib/cron/index.ts +1 -0
- package/src/shared/lib/i18n/{channel-auth.ts → channel-auth.constants.ts} +31 -0
- package/src/shared/lib/i18n/chat-labels.utils.ts +3 -2
- package/src/shared/lib/i18n/index.ts +20 -59
- package/src/shared/lib/i18n/{runtime-control.ts → runtime-control-labels.utils.ts} +30 -1
- package/src/shared/lib/provider-models/index.test.ts +39 -0
- package/src/shared/lib/provider-models/index.ts +1 -3
- package/src/shared/lib/ui-document-title/index.ts +0 -1
- package/tsconfig.json +1 -0
- package/vite.config.ts +1 -1
- package/vitest.config.ts +1 -1
- package/dist/assets/api-D2xRKmZd.js +0 -15
- package/dist/assets/app-manager-provider-CNaZboG4.js +0 -1
- package/dist/assets/app-navigation.config-Ihhrrt--.js +0 -1
- package/dist/assets/channels-list-page-p26lgxLk.js +0 -8
- package/dist/assets/chat-Dkh2qtuz.js +0 -61
- package/dist/assets/chat-page-DoTmE2wx.js +0 -1
- package/dist/assets/chunk-JZWAC4HX-Kydj4yEz.js +0 -3
- package/dist/assets/config-split-page-DIOCjj2Q.js +0 -1
- package/dist/assets/desktop-update-config-DlpzDfKM.js +0 -1
- package/dist/assets/doc-browser-C8FM5fC0.js +0 -1
- package/dist/assets/doc-browser-RJUOL_GO.js +0 -1
- package/dist/assets/doc-browser-p82AdNO-.js +0 -1
- package/dist/assets/folder-CeJKPx5P.js +0 -1
- package/dist/assets/hash-BqxRTZW5.js +0 -1
- package/dist/assets/i18n-DnTGDIRw.js +0 -1
- package/dist/assets/index-D8MKmXtO.css +0 -1
- package/dist/assets/index-pBvbJ5Mt.js +0 -2
- package/dist/assets/loader-circle-fd-vQKtW.js +0 -1
- package/dist/assets/logo-badge-KAe-7d8c.js +0 -1
- package/dist/assets/logos-C4sYP1Vl.js +0 -1
- package/dist/assets/marketplace-page-Cql0kDi-.js +0 -1
- package/dist/assets/marketplace-page-m4P5g_Ht.js +0 -49
- package/dist/assets/mcp-marketplace-page-9WVKl1m1.js +0 -1
- package/dist/assets/mcp-marketplace-page-ByzBQZcx.js +0 -40
- package/dist/assets/message-square-z_osm9c0.js +0 -1
- package/dist/assets/model-config-Dbr_0APb.js +0 -1
- package/dist/assets/play-Dv6Nr1Ew.js +0 -1
- package/dist/assets/plus-D8eKFY7h.js +0 -1
- package/dist/assets/provider-scoped-model-input-DFm6N2f7.js +0 -1
- package/dist/assets/providers-list-BJcLOjun.js +0 -1
- package/dist/assets/refresh-ccw-ByVwmnN_.js +0 -1
- package/dist/assets/refresh-cw-PcqoYB3K.js +0 -1
- package/dist/assets/remote-BOxo9iwd.js +0 -1
- package/dist/assets/runtime-config-page-CjLhnbSl.js +0 -1
- package/dist/assets/search-config-J4Htco-P.js +0 -1
- package/dist/assets/secrets-config-CUdERjco.js +0 -3
- package/dist/assets/sessions-config-page-DpK991fs.js +0 -2
- package/dist/assets/settings-drbWqzA4.js +0 -1
- package/dist/assets/skeleton-BK1SOSRA.js +0 -1
- package/dist/assets/theme-provider-0hxjiPc_.js +0 -2
- package/dist/assets/tooltip-Cj4yA0gH.js +0 -1
- package/dist/assets/trash-2-CBsHCfqq.js +0 -1
- package/dist/assets/use-config-38Ur-89i.js +0 -1
- package/dist/assets/use-confirm-dialog-DPQThaeU.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-5Gf1xQi7.js +0 -1
- package/dist/assets/use-viewport-layout-D1XzKeip.js +0 -1
- package/dist/assets/x-CM-XDMpk.js +0 -1
- package/src/features/chat/components/config/sessions-config-detail-pane.tsx +0 -244
- package/src/features/chat/pages/sessions-config-page.test.tsx +0 -152
- package/src/features/chat/pages/sessions-config-page.tsx +0 -192
- /package/dist/assets/{config-hints-MogHYQ8G.js → config-hints-BNfpOL4J.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
1
|
+
import { useInfiniteQuery, useMutation, useQueries, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
2
2
|
import { toast } from 'sonner';
|
|
3
3
|
import { t } from '@/shared/lib/i18n';
|
|
4
4
|
import {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
fetchMarketplaceInstalled,
|
|
11
11
|
fetchMarketplaceItems,
|
|
12
12
|
fetchMarketplaceRecommendations,
|
|
13
|
+
fetchMarketplaceSkillScenes,
|
|
13
14
|
installMarketplaceItem,
|
|
14
15
|
manageMarketplaceItem,
|
|
15
16
|
type MarketplaceListParams
|
|
@@ -18,7 +19,8 @@ import type {
|
|
|
18
19
|
MarketplaceInstallRequest,
|
|
19
20
|
MarketplaceInstalledView,
|
|
20
21
|
MarketplaceItemType,
|
|
21
|
-
MarketplaceManageRequest
|
|
22
|
+
MarketplaceManageRequest,
|
|
23
|
+
MarketplaceSceneView
|
|
22
24
|
} from '@/shared/lib/api';
|
|
23
25
|
import { collapseMarketplaceListPages } from '@/features/marketplace/utils/marketplace-list-pages.utils';
|
|
24
26
|
import { useMemo } from 'react';
|
|
@@ -36,7 +38,12 @@ export function useMarketplaceItems(params: MarketplaceListParams) {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
const previousType = 'type' in previousParams ? previousParams.type : undefined;
|
|
39
|
-
return previousType === params.type
|
|
41
|
+
return previousType === params.type && hasSameMarketplaceListIdentity(
|
|
42
|
+
previousParams as MarketplaceListParams,
|
|
43
|
+
params
|
|
44
|
+
)
|
|
45
|
+
? previousData
|
|
46
|
+
: undefined;
|
|
40
47
|
},
|
|
41
48
|
staleTime: 15_000
|
|
42
49
|
});
|
|
@@ -49,6 +56,17 @@ export function useMarketplaceItems(params: MarketplaceListParams) {
|
|
|
49
56
|
};
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
function hasSameMarketplaceListIdentity(
|
|
60
|
+
previousParams: MarketplaceListParams,
|
|
61
|
+
nextParams: MarketplaceListParams
|
|
62
|
+
): boolean {
|
|
63
|
+
return previousParams.q === nextParams.q
|
|
64
|
+
&& previousParams.tag === nextParams.tag
|
|
65
|
+
&& previousParams.scene === nextParams.scene
|
|
66
|
+
&& previousParams.sort === nextParams.sort
|
|
67
|
+
&& previousParams.pageSize === nextParams.pageSize;
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
export function useMarketplaceRecommendations(type: MarketplaceItemType, params: { scene?: string; limit?: number }) {
|
|
53
71
|
return useQuery({
|
|
54
72
|
queryKey: ['marketplace-recommendations', type, params],
|
|
@@ -57,6 +75,44 @@ export function useMarketplaceRecommendations(type: MarketplaceItemType, params:
|
|
|
57
75
|
});
|
|
58
76
|
}
|
|
59
77
|
|
|
78
|
+
export function useMarketplaceSkillScenes(enabled = true) {
|
|
79
|
+
return useQuery({
|
|
80
|
+
queryKey: ['marketplace-skill-scenes'],
|
|
81
|
+
queryFn: fetchMarketplaceSkillScenes,
|
|
82
|
+
enabled,
|
|
83
|
+
staleTime: 60_000
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function useMarketplaceSkillSceneCounts(
|
|
88
|
+
scenes: MarketplaceSceneView[],
|
|
89
|
+
enabled = true
|
|
90
|
+
) {
|
|
91
|
+
const queries = useQueries({
|
|
92
|
+
queries: scenes.map((scene) => ({
|
|
93
|
+
queryKey: ['marketplace-skill-scene-count', scene.scene],
|
|
94
|
+
queryFn: () => fetchMarketplaceItems({
|
|
95
|
+
type: 'skill',
|
|
96
|
+
scene: scene.scene,
|
|
97
|
+
pageSize: 1
|
|
98
|
+
}),
|
|
99
|
+
enabled: enabled && typeof scene.count !== 'number',
|
|
100
|
+
staleTime: 60_000
|
|
101
|
+
}))
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return useMemo(() => {
|
|
105
|
+
const counts = new Map<string, number>();
|
|
106
|
+
scenes.forEach((scene, index) => {
|
|
107
|
+
const knownCount = typeof scene.count === 'number' ? scene.count : queries[index]?.data?.total;
|
|
108
|
+
if (typeof knownCount === 'number') {
|
|
109
|
+
counts.set(scene.scene, knownCount);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return counts;
|
|
113
|
+
}, [queries, scenes]);
|
|
114
|
+
}
|
|
115
|
+
|
|
60
116
|
export function useMarketplaceItem(slug: string | null, type?: MarketplaceItemType) {
|
|
61
117
|
return useQuery({
|
|
62
118
|
queryKey: ['marketplace-item', slug, type],
|
|
@@ -13,16 +13,12 @@ export function RuntimeAgentListCard(props: {
|
|
|
13
13
|
onRemoveAgent: (index: number) => void;
|
|
14
14
|
onAddAgent: () => void;
|
|
15
15
|
onSetDefaultAgent: (index: number, checked: boolean) => void;
|
|
16
|
-
label?: string;
|
|
17
|
-
help?: string;
|
|
18
|
-
contextTokensLabel?: string;
|
|
19
|
-
engineLabel?: string;
|
|
20
16
|
}) {
|
|
21
17
|
return (
|
|
22
18
|
<Card>
|
|
23
19
|
<CardHeader>
|
|
24
|
-
<CardTitle>{
|
|
25
|
-
<CardDescription>{
|
|
20
|
+
<CardTitle>{t('agentList')}</CardTitle>
|
|
21
|
+
<CardDescription>{t('agentListHelp')}</CardDescription>
|
|
26
22
|
</CardHeader>
|
|
27
23
|
<CardContent className="space-y-3">
|
|
28
24
|
{props.agents.map((agent, index) => (
|
|
@@ -31,7 +27,7 @@ export function RuntimeAgentListCard(props: {
|
|
|
31
27
|
<Input value={agent.id} onChange={(event) => props.onUpdateAgent(index, { id: event.target.value })} placeholder={t('agentIdPlaceholder')} />
|
|
32
28
|
<Input value={agent.workspace ?? ''} onChange={(event) => props.onUpdateAgent(index, { workspace: event.target.value })} placeholder={t('workspaceOverridePlaceholder')} />
|
|
33
29
|
<Input value={agent.model ?? ''} onChange={(event) => props.onUpdateAgent(index, { model: event.target.value })} placeholder={t('modelOverridePlaceholder')} />
|
|
34
|
-
<Input value={agent.runtime ?? agent.engine ?? ''} onChange={(event) => props.onUpdateAgent(index, { runtime: event.target.value })} placeholder={
|
|
30
|
+
<Input value={agent.runtime ?? agent.engine ?? ''} onChange={(event) => props.onUpdateAgent(index, { runtime: event.target.value })} placeholder={t('engineOverridePlaceholder')} />
|
|
35
31
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
36
32
|
<Input
|
|
37
33
|
type="number"
|
|
@@ -39,7 +35,7 @@ export function RuntimeAgentListCard(props: {
|
|
|
39
35
|
step={1000}
|
|
40
36
|
value={agent.contextTokens ?? ''}
|
|
41
37
|
onChange={(event) => props.onUpdateAgent(index, { contextTokens: parseOptionalInt(event.target.value) })}
|
|
42
|
-
placeholder={
|
|
38
|
+
placeholder={t('contextTokensPlaceholder')}
|
|
43
39
|
/>
|
|
44
40
|
<Input
|
|
45
41
|
type="number"
|
|
@@ -12,14 +12,12 @@ export function RuntimeBindingListCard(props: {
|
|
|
12
12
|
onUpdateBinding: (index: number, next: AgentBindingView) => void;
|
|
13
13
|
onRemoveBinding: (index: number) => void;
|
|
14
14
|
onAddBinding: () => void;
|
|
15
|
-
label?: string;
|
|
16
|
-
help?: string;
|
|
17
15
|
}) {
|
|
18
16
|
return (
|
|
19
17
|
<Card>
|
|
20
18
|
<CardHeader>
|
|
21
|
-
<CardTitle>{
|
|
22
|
-
<CardDescription>{
|
|
19
|
+
<CardTitle>{t('bindings')}</CardTitle>
|
|
20
|
+
<CardDescription>{t('bindingsHelp')}</CardDescription>
|
|
23
21
|
</CardHeader>
|
|
24
22
|
<CardContent className="space-y-3">
|
|
25
23
|
{props.bindings.map((binding, index) => {
|
|
@@ -47,9 +45,9 @@ export function RuntimeBindingListCard(props: {
|
|
|
47
45
|
</SelectTrigger>
|
|
48
46
|
<SelectContent>
|
|
49
47
|
<SelectItem value="__none__">{t('peerKindOptional')}</SelectItem>
|
|
50
|
-
<SelectItem value="direct">
|
|
51
|
-
<SelectItem value="group">
|
|
52
|
-
<SelectItem value="channel">
|
|
48
|
+
<SelectItem value="direct">{t('peerKindDirect')}</SelectItem>
|
|
49
|
+
<SelectItem value="group">{t('peerKindGroup')}</SelectItem>
|
|
50
|
+
<SelectItem value="channel">{t('peerKindChannel')}</SelectItem>
|
|
53
51
|
</SelectContent>
|
|
54
52
|
</Select>
|
|
55
53
|
<Input
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { useMemo, useState } from 'react';
|
|
2
2
|
import { Save } from 'lucide-react';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ConfigView, RuntimeConfigUpdate } from '@/shared/lib/api';
|
|
4
4
|
import { Button } from '@/shared/components/ui/button';
|
|
5
5
|
import { PageLayout } from '@/app/components/layout/page-layout';
|
|
6
|
-
import { hintForPath } from '@/shared/lib/config-hints';
|
|
7
6
|
import { t } from '@/shared/lib/i18n';
|
|
8
7
|
import { RuntimeConfigOverview } from '@/features/system-status/components/config/runtime-config-overview';
|
|
9
8
|
import { RuntimeAgentListCard } from '@/features/system-status/components/config/runtime-agent-list-card';
|
|
@@ -27,7 +26,6 @@ type UpdateRuntimeMutation = {
|
|
|
27
26
|
|
|
28
27
|
export function RuntimeConfigEditor(props: {
|
|
29
28
|
config: ConfigView;
|
|
30
|
-
uiHints?: ConfigUiHints;
|
|
31
29
|
updateRuntime: UpdateRuntimeMutation;
|
|
32
30
|
}) {
|
|
33
31
|
const initialState = useMemo(() => createRuntimeConfigEditorState(props.config), [props.config]);
|
|
@@ -88,22 +86,12 @@ export function RuntimeConfigEditor(props: {
|
|
|
88
86
|
onDmScopeChange={setDmScope}
|
|
89
87
|
onDefaultContextTokensChange={setDefaultContextTokens}
|
|
90
88
|
onDefaultEngineChange={setDefaultEngine}
|
|
91
|
-
companionEnabledLabel={hintForPath('companion.enabled', props.uiHints)?.label}
|
|
92
|
-
companionEnabledHelp={hintForPath('companion.enabled', props.uiHints)?.help}
|
|
93
|
-
dmScopeLabel={hintForPath('session.dmScope', props.uiHints)?.label}
|
|
94
|
-
dmScopeHelp={hintForPath('session.dmScope', props.uiHints)?.help}
|
|
95
|
-
defaultContextTokensLabel={hintForPath('agents.defaults.contextTokens', props.uiHints)?.label}
|
|
96
|
-
defaultContextTokensHelp={hintForPath('agents.defaults.contextTokens', props.uiHints)?.help}
|
|
97
|
-
defaultEngineLabel={hintForPath('agents.defaults.engine', props.uiHints)?.label}
|
|
98
|
-
defaultEngineHelp={hintForPath('agents.defaults.engine', props.uiHints)?.help}
|
|
99
89
|
/>
|
|
100
90
|
<RuntimeEntryListCard
|
|
101
91
|
entries={runtimeEntries}
|
|
102
92
|
onUpdateEntry={updateRuntimeEntry}
|
|
103
93
|
onRemoveEntry={(index) => setRuntimeEntries((previous) => previous.filter((_, cursor) => cursor !== index))}
|
|
104
94
|
onAddEntry={() => setRuntimeEntries((previous) => [...previous, createEmptyRuntimeEntry()])}
|
|
105
|
-
label={hintForPath('agents.runtimes.entries', props.uiHints)?.label}
|
|
106
|
-
help={hintForPath('agents.runtimes.entries', props.uiHints)?.help}
|
|
107
95
|
/>
|
|
108
96
|
<RuntimeAgentListCard
|
|
109
97
|
agents={agents}
|
|
@@ -113,18 +101,12 @@ export function RuntimeConfigEditor(props: {
|
|
|
113
101
|
onSetDefaultAgent={(index, checked) =>
|
|
114
102
|
setAgents((previous) => checked ? previous.map((entry, cursor) => ({ ...entry, default: cursor === index })) : previous.map((entry, cursor) => (cursor === index ? { ...entry, default: false } : entry)))
|
|
115
103
|
}
|
|
116
|
-
label={hintForPath('agents.list', props.uiHints)?.label}
|
|
117
|
-
help={hintForPath('agents.list', props.uiHints)?.help}
|
|
118
|
-
contextTokensLabel={hintForPath('agents.list.*.contextTokens', props.uiHints)?.label}
|
|
119
|
-
engineLabel={hintForPath('agents.list.*.engine', props.uiHints)?.label}
|
|
120
104
|
/>
|
|
121
105
|
<RuntimeBindingListCard
|
|
122
106
|
bindings={bindings}
|
|
123
107
|
onUpdateBinding={updateBinding}
|
|
124
108
|
onRemoveBinding={(index) => setBindings((previous) => previous.filter((_, cursor) => cursor !== index))}
|
|
125
109
|
onAddBinding={() => setBindings((previous) => [...previous, createEmptyRuntimeBinding()])}
|
|
126
|
-
label={hintForPath('bindings', props.uiHints)?.label}
|
|
127
|
-
help={hintForPath('bindings', props.uiHints)?.help}
|
|
128
110
|
/>
|
|
129
111
|
<div className="flex justify-end">
|
|
130
112
|
<Button type="button" onClick={handleSave} disabled={props.updateRuntime.isPending}>
|
|
@@ -3,6 +3,7 @@ import { Button } from '@/shared/components/ui/button';
|
|
|
3
3
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/shared/components/ui/card';
|
|
4
4
|
import { Input } from '@/shared/components/ui/input';
|
|
5
5
|
import { Switch } from '@/shared/components/ui/switch';
|
|
6
|
+
import { t } from '@/shared/lib/i18n';
|
|
6
7
|
import type { RuntimeEntryDraft } from '@/features/system-status/utils/runtime-config-agent.utils';
|
|
7
8
|
|
|
8
9
|
export function RuntimeEntryListCard(props: {
|
|
@@ -10,29 +11,27 @@ export function RuntimeEntryListCard(props: {
|
|
|
10
11
|
onUpdateEntry: (index: number, patch: Partial<RuntimeEntryDraft>) => void;
|
|
11
12
|
onRemoveEntry: (index: number) => void;
|
|
12
13
|
onAddEntry: () => void;
|
|
13
|
-
label?: string;
|
|
14
|
-
help?: string;
|
|
15
14
|
}) {
|
|
16
15
|
return (
|
|
17
16
|
<Card>
|
|
18
17
|
<CardHeader>
|
|
19
|
-
<CardTitle>{
|
|
20
|
-
<CardDescription>{
|
|
18
|
+
<CardTitle>{t('runtimeEntries')}</CardTitle>
|
|
19
|
+
<CardDescription>{t('runtimeEntriesHelp')}</CardDescription>
|
|
21
20
|
</CardHeader>
|
|
22
21
|
<CardContent className="space-y-3">
|
|
23
22
|
{props.entries.map((entry, index) => (
|
|
24
23
|
<div key={`${index}-${entry.id || 'runtime-entry'}`} className="rounded-xl border border-gray-200 p-3 space-y-3">
|
|
25
24
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
26
|
-
<Input value={entry.id} onChange={(event) => props.onUpdateEntry(index, { id: event.target.value })} placeholder=
|
|
27
|
-
<Input value={entry.label ?? ''} onChange={(event) => props.onUpdateEntry(index, { label: event.target.value })} placeholder=
|
|
28
|
-
<Input value={entry.type} onChange={(event) => props.onUpdateEntry(index, { type: event.target.value })} placeholder=
|
|
25
|
+
<Input value={entry.id} onChange={(event) => props.onUpdateEntry(index, { id: event.target.value })} placeholder={t('runtimeEntryIdPlaceholder')} />
|
|
26
|
+
<Input value={entry.label ?? ''} onChange={(event) => props.onUpdateEntry(index, { label: event.target.value })} placeholder={t('runtimeEntryLabelPlaceholder')} />
|
|
27
|
+
<Input value={entry.type} onChange={(event) => props.onUpdateEntry(index, { type: event.target.value })} placeholder={t('runtimeEntryTypePlaceholder')} />
|
|
29
28
|
<div className="flex items-center justify-between rounded-lg border border-gray-200 px-3 py-2">
|
|
30
|
-
<span className="text-sm text-gray-700">
|
|
29
|
+
<span className="text-sm text-gray-700">{t('enabled')}</span>
|
|
31
30
|
<Switch checked={entry.enabled !== false} onCheckedChange={(checked) => props.onUpdateEntry(index, { enabled: checked })} />
|
|
32
31
|
</div>
|
|
33
32
|
</div>
|
|
34
33
|
<div className="space-y-2">
|
|
35
|
-
<label className="text-sm font-medium text-gray-800">
|
|
34
|
+
<label className="text-sm font-medium text-gray-800">{t('runtimeEntryConfigJson')}</label>
|
|
36
35
|
<textarea
|
|
37
36
|
className="min-h-32 w-full rounded-md border border-gray-200 px-3 py-2 text-sm font-mono"
|
|
38
37
|
value={entry.configText}
|
|
@@ -43,14 +42,14 @@ export function RuntimeEntryListCard(props: {
|
|
|
43
42
|
<div className="flex justify-end">
|
|
44
43
|
<Button type="button" variant="outline" onClick={() => props.onRemoveEntry(index)}>
|
|
45
44
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
46
|
-
|
|
45
|
+
{t('delete')}
|
|
47
46
|
</Button>
|
|
48
47
|
</div>
|
|
49
48
|
</div>
|
|
50
49
|
))}
|
|
51
50
|
<Button type="button" variant="outline" onClick={props.onAddEntry}>
|
|
52
51
|
<Plus className="mr-2 h-4 w-4" />
|
|
53
|
-
|
|
52
|
+
{t('addRuntimeEntry')}
|
|
54
53
|
</Button>
|
|
55
54
|
</CardContent>
|
|
56
55
|
</Card>
|
|
@@ -5,11 +5,11 @@ import { Switch } from '@/shared/components/ui/switch';
|
|
|
5
5
|
import { t } from '@/shared/lib/i18n';
|
|
6
6
|
import type { DmScope } from '@/features/system-status/utils/runtime-config-agent.utils';
|
|
7
7
|
|
|
8
|
-
const DM_SCOPE_OPTIONS: Array<{ value: DmScope;
|
|
9
|
-
{ value: 'main',
|
|
10
|
-
{ value: 'per-peer',
|
|
11
|
-
{ value: 'per-channel-peer',
|
|
12
|
-
{ value: 'per-account-channel-peer',
|
|
8
|
+
const DM_SCOPE_OPTIONS: Array<{ value: DmScope; labelKey: string }> = [
|
|
9
|
+
{ value: 'main', labelKey: 'dmScopeMain' },
|
|
10
|
+
{ value: 'per-peer', labelKey: 'dmScopePerPeer' },
|
|
11
|
+
{ value: 'per-channel-peer', labelKey: 'dmScopePerChannelPeer' },
|
|
12
|
+
{ value: 'per-account-channel-peer', labelKey: 'dmScopePerAccountChannelPeer' }
|
|
13
13
|
];
|
|
14
14
|
|
|
15
15
|
export function RuntimeSettingsCard(props: {
|
|
@@ -21,31 +21,23 @@ export function RuntimeSettingsCard(props: {
|
|
|
21
21
|
onDmScopeChange: (value: DmScope) => void;
|
|
22
22
|
onDefaultContextTokensChange: (value: number) => void;
|
|
23
23
|
onDefaultEngineChange: (value: string) => void;
|
|
24
|
-
companionEnabledLabel?: string;
|
|
25
|
-
companionEnabledHelp?: string;
|
|
26
|
-
dmScopeLabel?: string;
|
|
27
|
-
dmScopeHelp?: string;
|
|
28
|
-
defaultContextTokensLabel?: string;
|
|
29
|
-
defaultContextTokensHelp?: string;
|
|
30
|
-
defaultEngineLabel?: string;
|
|
31
|
-
defaultEngineHelp?: string;
|
|
32
24
|
}) {
|
|
33
25
|
return (
|
|
34
26
|
<Card>
|
|
35
27
|
<CardHeader>
|
|
36
|
-
<CardTitle>{
|
|
37
|
-
<CardDescription>{
|
|
28
|
+
<CardTitle>{t('dmScope')}</CardTitle>
|
|
29
|
+
<CardDescription>{t('dmScopeHelp')}</CardDescription>
|
|
38
30
|
</CardHeader>
|
|
39
31
|
<CardContent className="space-y-4">
|
|
40
32
|
<div className="flex items-start justify-between gap-4 rounded-md border border-gray-200 px-4 py-3">
|
|
41
33
|
<div className="space-y-1">
|
|
42
|
-
<div className="text-sm font-medium text-gray-800">{
|
|
43
|
-
<p className="text-xs text-gray-500">{
|
|
34
|
+
<div className="text-sm font-medium text-gray-800">{t('runtimeCompanionEnabled')}</div>
|
|
35
|
+
<p className="text-xs text-gray-500">{t('runtimeCompanionEnabledHelp')}</p>
|
|
44
36
|
</div>
|
|
45
37
|
<Switch checked={props.companionEnabled} onCheckedChange={props.onCompanionEnabledChange} />
|
|
46
38
|
</div>
|
|
47
39
|
<div className="space-y-2">
|
|
48
|
-
<label className="text-sm font-medium text-gray-800">{
|
|
40
|
+
<label className="text-sm font-medium text-gray-800">{t('defaultContextTokens')}</label>
|
|
49
41
|
<Input
|
|
50
42
|
type="number"
|
|
51
43
|
min={1000}
|
|
@@ -53,19 +45,19 @@ export function RuntimeSettingsCard(props: {
|
|
|
53
45
|
value={props.defaultContextTokens}
|
|
54
46
|
onChange={(event) => props.onDefaultContextTokensChange(Math.max(1000, Number.parseInt(event.target.value, 10) || 1000))}
|
|
55
47
|
/>
|
|
56
|
-
<p className="text-xs text-gray-500">{
|
|
48
|
+
<p className="text-xs text-gray-500">{t('defaultContextTokensHelp')}</p>
|
|
57
49
|
</div>
|
|
58
50
|
<div className="space-y-2">
|
|
59
|
-
<label className="text-sm font-medium text-gray-800">{
|
|
51
|
+
<label className="text-sm font-medium text-gray-800">{t('defaultEngine')}</label>
|
|
60
52
|
<Input
|
|
61
53
|
value={props.defaultEngine}
|
|
62
54
|
onChange={(event) => props.onDefaultEngineChange(event.target.value)}
|
|
63
55
|
placeholder={t('defaultEnginePlaceholder')}
|
|
64
56
|
/>
|
|
65
|
-
<p className="text-xs text-gray-500">{
|
|
57
|
+
<p className="text-xs text-gray-500">{t('defaultEngineHelp')}</p>
|
|
66
58
|
</div>
|
|
67
59
|
<div className="space-y-2">
|
|
68
|
-
<label className="text-sm font-medium text-gray-800">{
|
|
60
|
+
<label className="text-sm font-medium text-gray-800">{t('dmScope')}</label>
|
|
69
61
|
<Select value={props.dmScope} onValueChange={(value) => props.onDmScopeChange(value as DmScope)}>
|
|
70
62
|
<SelectTrigger>
|
|
71
63
|
<SelectValue />
|
|
@@ -73,7 +65,7 @@ export function RuntimeSettingsCard(props: {
|
|
|
73
65
|
<SelectContent>
|
|
74
66
|
{DM_SCOPE_OPTIONS.map((option) => (
|
|
75
67
|
<SelectItem key={option.value} value={option.value}>
|
|
76
|
-
{option.
|
|
68
|
+
{t(option.labelKey)}
|
|
77
69
|
</SelectItem>
|
|
78
70
|
))}
|
|
79
71
|
</SelectContent>
|
|
@@ -15,7 +15,7 @@ const baseControlView = {
|
|
|
15
15
|
available: false,
|
|
16
16
|
requiresConfirmation: false,
|
|
17
17
|
impact: 'brief-ui-disconnect' as const,
|
|
18
|
-
reasonIfUnavailable: '
|
|
18
|
+
reasonIfUnavailable: 'This page is already hosted by the running local service.'
|
|
19
19
|
},
|
|
20
20
|
canRestartService: {
|
|
21
21
|
available: true,
|
|
@@ -31,9 +31,9 @@ const baseControlView = {
|
|
|
31
31
|
available: false,
|
|
32
32
|
requiresConfirmation: true,
|
|
33
33
|
impact: 'full-app-relaunch' as const,
|
|
34
|
-
reasonIfUnavailable: 'desktop
|
|
34
|
+
reasonIfUnavailable: 'App restart is only available in the desktop shell.'
|
|
35
35
|
},
|
|
36
|
-
managementHint: 'This page is served by the running local service.'
|
|
36
|
+
managementHint: 'This page is served by the running local service. Closing the browser does not stop it.'
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
const mocks = vi.hoisted(() => ({
|
|
@@ -87,7 +87,9 @@ describe('RuntimeControlCard', () => {
|
|
|
87
87
|
expect(screen.getByRole('button', { name: '停止服务' })).toBeTruthy();
|
|
88
88
|
expect(startButton.disabled).toBe(true);
|
|
89
89
|
expect(restartAppButton.disabled).toBe(true);
|
|
90
|
-
expect(screen.getByText('
|
|
90
|
+
expect(screen.getByText('运行时正常')).toBeTruthy();
|
|
91
|
+
expect(screen.getByText('此页面由正在运行的本地服务提供。关闭浏览器不会停止它。')).toBeTruthy();
|
|
92
|
+
expect(screen.getByText('只有桌面壳环境支持重启整个应用。')).toBeTruthy();
|
|
91
93
|
});
|
|
92
94
|
|
|
93
95
|
it('runs the restart service flow through the system status manager', async () => {
|
|
@@ -106,7 +108,7 @@ describe('RuntimeControlCard', () => {
|
|
|
106
108
|
await waitFor(() => {
|
|
107
109
|
expect(mocks.runRuntimeControlAction).toHaveBeenCalledWith('restart-service');
|
|
108
110
|
});
|
|
109
|
-
expect(toast.success).toHaveBeenCalledWith('
|
|
111
|
+
expect(toast.success).toHaveBeenCalledWith('重启已安排。此页面可能会短暂断开。');
|
|
110
112
|
});
|
|
111
113
|
|
|
112
114
|
it('runs the stop service flow after confirmation', async () => {
|
|
@@ -127,7 +129,7 @@ describe('RuntimeControlCard', () => {
|
|
|
127
129
|
expect(confirmSpy).toHaveBeenCalledTimes(1);
|
|
128
130
|
expect(mocks.runRuntimeControlAction).toHaveBeenCalledWith('stop-service');
|
|
129
131
|
});
|
|
130
|
-
expect(toast.success).toHaveBeenCalledWith('
|
|
132
|
+
expect(toast.success).toHaveBeenCalledWith('停止已安排。此页面即将断开。');
|
|
131
133
|
});
|
|
132
134
|
|
|
133
135
|
it('runs the desktop restart app flow after confirmation', async () => {
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import { Button } from '@/shared/components/ui/button';
|
|
9
9
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/shared/components/ui/card';
|
|
10
10
|
import { useRuntimeControlPanelView, systemStatusManager } from '@/features/system-status';
|
|
11
|
+
import { localizeRuntimeControlMessage } from '@/features/system-status/utils/system-status.utils';
|
|
11
12
|
import { t } from '@/shared/lib/i18n';
|
|
12
13
|
import { Loader2, Play, RotateCw, Square } from 'lucide-react';
|
|
13
14
|
import { toast } from 'sonner';
|
|
@@ -140,7 +141,7 @@ export function RuntimeControlCard() {
|
|
|
140
141
|
const capability = resolveActionCapability(controlView, action);
|
|
141
142
|
|
|
142
143
|
if (!capability?.available) {
|
|
143
|
-
toast.error(capability?.reasonIfUnavailable ?? t('runtimeControlLoadFailed'));
|
|
144
|
+
toast.error(localizeRuntimeControlMessage(capability?.reasonIfUnavailable) ?? t('runtimeControlLoadFailed'));
|
|
144
145
|
return;
|
|
145
146
|
}
|
|
146
147
|
if (
|
|
@@ -153,7 +154,7 @@ export function RuntimeControlCard() {
|
|
|
153
154
|
|
|
154
155
|
try {
|
|
155
156
|
const result = await systemStatusManager.runRuntimeControlAction(action);
|
|
156
|
-
toast.success(result.message);
|
|
157
|
+
toast.success(localizeRuntimeControlMessage(result.message) ?? result.message);
|
|
157
158
|
} catch (error) {
|
|
158
159
|
const message = error instanceof Error ? error.message : t('runtimeControlActionFailed');
|
|
159
160
|
toast.error(`${t('runtimeControlActionFailed')}: ${message}`);
|
|
@@ -162,7 +163,7 @@ export function RuntimeControlCard() {
|
|
|
162
163
|
|
|
163
164
|
const handleRestartApp = async () => {
|
|
164
165
|
if (!controlView?.canRestartApp.available) {
|
|
165
|
-
toast.error(controlView?.canRestartApp.reasonIfUnavailable ?? t('runtimeRestartAppUnavailable'));
|
|
166
|
+
toast.error(localizeRuntimeControlMessage(controlView?.canRestartApp.reasonIfUnavailable) ?? t('runtimeRestartAppUnavailable'));
|
|
166
167
|
return;
|
|
167
168
|
}
|
|
168
169
|
if (!window.confirm(t('runtimeControlRestartAppConfirm'))) {
|
|
@@ -171,7 +172,7 @@ export function RuntimeControlCard() {
|
|
|
171
172
|
|
|
172
173
|
try {
|
|
173
174
|
const result = await systemStatusManager.runRuntimeControlAction('restart-app');
|
|
174
|
-
toast.success(result.message);
|
|
175
|
+
toast.success(localizeRuntimeControlMessage(result.message) ?? result.message);
|
|
175
176
|
} catch (error) {
|
|
176
177
|
const message = error instanceof Error ? error.message : t('runtimeControlActionFailed');
|
|
177
178
|
toast.error(`${t('runtimeControlActionFailed')}: ${message}`);
|
|
@@ -194,7 +195,7 @@ export function RuntimeControlCard() {
|
|
|
194
195
|
</div>
|
|
195
196
|
<p className="text-sm text-gray-600">{displayedMessage}</p>
|
|
196
197
|
<div className="text-xs text-gray-500">{resolveLifecycleLabel(displayedLifecycle)}</div>
|
|
197
|
-
{controlView?.managementHint ? <p className="text-xs text-gray-500">{controlView.managementHint}</p> : null}
|
|
198
|
+
{controlView?.managementHint ? <p className="text-xs text-gray-500">{localizeRuntimeControlMessage(controlView.managementHint)}</p> : null}
|
|
198
199
|
{errorMessage && !busy ? <p className="text-sm text-amber-700">{errorMessage}</p> : null}
|
|
199
200
|
</div>
|
|
200
201
|
|
|
@@ -251,7 +252,7 @@ export function RuntimeControlCard() {
|
|
|
251
252
|
.filter((item) => !item.capability.available && item.capability.reasonIfUnavailable)
|
|
252
253
|
.map((item) => (
|
|
253
254
|
<p key={`${item.action}-reason`} className="text-xs text-gray-500">
|
|
254
|
-
{item.capability.reasonIfUnavailable}
|
|
255
|
+
{localizeRuntimeControlMessage(item.capability.reasonIfUnavailable)}
|
|
255
256
|
</p>
|
|
256
257
|
))}
|
|
257
258
|
</CardContent>
|
|
@@ -9,15 +9,11 @@ const mocks = vi.hoisted(() => ({
|
|
|
9
9
|
useConfigResult: null as unknown as {
|
|
10
10
|
data: Record<string, unknown> | null;
|
|
11
11
|
isLoading: boolean;
|
|
12
|
-
},
|
|
13
|
-
useConfigSchemaResult: null as unknown as {
|
|
14
|
-
data: Record<string, unknown> | null;
|
|
15
12
|
}
|
|
16
13
|
}));
|
|
17
14
|
|
|
18
15
|
vi.mock('@/shared/hooks/use-config', () => ({
|
|
19
16
|
useConfig: () => mocks.useConfigResult,
|
|
20
|
-
useConfigSchema: () => mocks.useConfigSchemaResult,
|
|
21
17
|
useUpdateRuntime: () => ({
|
|
22
18
|
mutate: mocks.mutate,
|
|
23
19
|
isPending: false
|
|
@@ -68,16 +64,14 @@ describe('RuntimeConfig', () => {
|
|
|
68
64
|
}
|
|
69
65
|
}
|
|
70
66
|
],
|
|
67
|
+
companion: {
|
|
68
|
+
enabled: false
|
|
69
|
+
},
|
|
71
70
|
session: {
|
|
72
71
|
dmScope: 'per-peer'
|
|
73
72
|
}
|
|
74
73
|
}
|
|
75
74
|
};
|
|
76
|
-
mocks.useConfigSchemaResult = {
|
|
77
|
-
data: {
|
|
78
|
-
uiHints: {}
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
75
|
});
|
|
82
76
|
|
|
83
77
|
it('saves the migrated runtime page through the system-status feature root', async () => {
|
|
@@ -131,10 +125,26 @@ describe('RuntimeConfig', () => {
|
|
|
131
125
|
}
|
|
132
126
|
}
|
|
133
127
|
],
|
|
128
|
+
companion: {
|
|
129
|
+
enabled: false
|
|
130
|
+
},
|
|
134
131
|
session: {
|
|
135
132
|
dmScope: 'per-peer'
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
135
|
});
|
|
139
136
|
});
|
|
137
|
+
|
|
138
|
+
it('renders runtime settings labels in Chinese when the UI language is Chinese', () => {
|
|
139
|
+
setLanguage('zh');
|
|
140
|
+
|
|
141
|
+
render(<RuntimeConfig />);
|
|
142
|
+
|
|
143
|
+
expect(screen.getByText('保存运行时设置')).toBeTruthy();
|
|
144
|
+
expect(screen.getByText('运行时入口')).toBeTruthy();
|
|
145
|
+
expect(screen.getByText('启用')).toBeTruthy();
|
|
146
|
+
expect(screen.getByText('配置 JSON')).toBeTruthy();
|
|
147
|
+
expect(screen.getByPlaceholderText('默认运行时(如 native 或 codex)')).toBeTruthy();
|
|
148
|
+
expect(screen.getByPlaceholderText('入口 ID,例如 hermes')).toBeTruthy();
|
|
149
|
+
});
|
|
140
150
|
});
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { useConfig,
|
|
1
|
+
import { useConfig, useUpdateRuntime } from '@/shared/hooks/use-config';
|
|
2
2
|
import { RuntimeConfigEditor } from '@/features/system-status/components/config/runtime-config-editor';
|
|
3
3
|
import { t } from '@/shared/lib/i18n';
|
|
4
4
|
|
|
5
5
|
export function RuntimeConfig() {
|
|
6
6
|
const { data: config, isLoading } = useConfig();
|
|
7
|
-
const { data: schema } = useConfigSchema();
|
|
8
7
|
const updateRuntime = useUpdateRuntime();
|
|
9
8
|
|
|
10
9
|
if (isLoading || !config) {
|
|
11
10
|
return <div className="p-8 text-gray-400">{t('runtimeLoading')}</div>;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
return <RuntimeConfigEditor config={config}
|
|
13
|
+
return <RuntimeConfigEditor config={config} updateRuntime={updateRuntime} />;
|
|
15
14
|
}
|
|
@@ -122,11 +122,11 @@ export function createRuntimeConfigUpdatePayload(input: {
|
|
|
122
122
|
const normalizedRuntimeEntries = input.runtimeEntries.reduce<Record<string, RuntimeEntryView>>((entries, entry, index) => {
|
|
123
123
|
const id = entry.id.trim();
|
|
124
124
|
const type = entry.type.trim();
|
|
125
|
-
if (!id) throw new Error(
|
|
126
|
-
if (!type) throw new Error(
|
|
127
|
-
if (entries[id]) throw new Error(
|
|
125
|
+
if (!id) throw new Error(t('runtimeEntryIdRequired').replace('{index}', String(index)));
|
|
126
|
+
if (!type) throw new Error(t('runtimeEntryTypeRequired').replace('{id}', id));
|
|
127
|
+
if (entries[id]) throw new Error(`${t('runtimeEntryDuplicate')}: ${id}`);
|
|
128
128
|
const configValue = entry.configText.trim() ? JSON.parse(entry.configText) : {};
|
|
129
|
-
if (configValue && (typeof configValue !== 'object' || Array.isArray(configValue))) throw new Error(
|
|
129
|
+
if (configValue && (typeof configValue !== 'object' || Array.isArray(configValue))) throw new Error(t('runtimeEntryConfigObjectRequired').replace('{id}', id));
|
|
130
130
|
entries[id] = {
|
|
131
131
|
enabled: entry.enabled !== false,
|
|
132
132
|
...(entry.label?.trim() ? { label: entry.label.trim() } : {}),
|