@nextclaw/ui 0.12.1 → 0.12.2
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 +38 -0
- package/dist/assets/ChannelsList-uKmkpD25.js +8 -0
- package/dist/assets/ChatPage-CslhBPfT.js +43 -0
- package/dist/assets/{DocBrowser-DjcghYGO.js → DocBrowser-C7-1sXqo.js} +1 -1
- package/dist/assets/DocBrowser-DQjtSsY3.js +1 -0
- package/dist/assets/{DocBrowserContext-CLlq7rZQ.js → DocBrowserContext-DN5tjUoS.js} +1 -1
- package/dist/assets/{LogoBadge-D_dOy5U3.js → LogoBadge-DDS1sU_U.js} +1 -1
- package/dist/assets/MarketplacePage-BZQW70ti.js +1 -0
- package/dist/assets/{MarketplacePage-BlIeNn3x.js → MarketplacePage-DE0QjYVv.js} +1 -1
- package/dist/assets/{McpMarketplacePage-mz2_IX1O.js → McpMarketplacePage-CeLvv1xy.js} +1 -1
- package/dist/assets/ModelConfig-D1JtGtQv.js +1 -0
- package/dist/assets/ProviderScopedModelInput-SAJH6nkC.js +1 -0
- package/dist/assets/{ProvidersList-B0RCb_Vg.js → ProvidersList-1rKi3aQT.js} +1 -1
- package/dist/assets/{RemoteAccessPage-CcfQjLtx.js → RemoteAccessPage-bIAKxDky.js} +1 -1
- package/dist/assets/RuntimeConfig-BTk9319O.js +1 -0
- package/dist/assets/SearchConfig-EjeszXbv.js +1 -0
- package/dist/assets/{SecretsConfig-DbiS3txa.js → SecretsConfig-cnAXvREZ.js} +1 -1
- package/dist/assets/{SessionsConfig-CbIOcAp8.js → SessionsConfig-BIXiDaK2.js} +1 -1
- package/dist/assets/{book-open-BLxSL7Dk.js → book-open-DvWqOode.js} +1 -1
- package/dist/assets/chat-session-display-D4bYa0b8.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-Bp0t5xoO.js → chunk-JZWAC4HX-CxfKRD7X.js} +1 -1
- package/dist/assets/{config-C96FWufn.js → config-BeGwf2Ao.js} +1 -1
- package/dist/assets/{createLucideIcon-B_U7Nq4F.js → createLucideIcon-C7MmdIX3.js} +1 -1
- package/dist/assets/{dist-D9pHzW9z.js → dist-B6VMuIQN.js} +1 -1
- package/dist/assets/{dist-BFY-GyT4.js → dist-RWNFhxvR.js} +1 -1
- package/dist/assets/{external-link-BydIQTIH.js → external-link-U86Acd1t.js} +1 -1
- package/dist/assets/{hash-Djdf0x1C.js → hash-D-OVfV3Z.js} +1 -1
- package/dist/assets/i18n-hM3v-3YG.js +1 -0
- package/dist/assets/{index-DqSv8Azv.js → index-8XNPYwJu.js} +3 -3
- package/dist/assets/index-CpxuJa9o.css +1 -0
- package/dist/assets/{label-Bvv4Mrea.js → label-CHJ1ATds.js} +1 -1
- package/dist/assets/loader-circle-C8cpaL0w.js +1 -0
- package/dist/assets/{logos-CGJJRI5_.js → logos-U1_qDA3U.js} +1 -1
- package/dist/assets/{page-layout-6Nm4Cnvr.js → page-layout-Z1klaUFW.js} +1 -1
- package/dist/assets/plus-CrkO1kob.js +1 -0
- package/dist/assets/{popover-b9rSYI6X.js → popover-xWbqMnIN.js} +1 -1
- package/dist/assets/{react-CDZz_StC.js → react-3YE87-lE.js} +1 -1
- package/dist/assets/{refresh-ccw-BvSSnnCw.js → refresh-ccw-JQh1lwq-.js} +1 -1
- package/dist/assets/{save-CAf0_-b9.js → save-4VRlzkii.js} +1 -1
- package/dist/assets/search-EX-Papzl.js +1 -0
- package/dist/assets/{security-config-DF66-l25.js → security-config-CGazBahs.js} +1 -1
- package/dist/assets/{select-CEIMqc0H.js → select-DF-AUoie.js} +1 -1
- package/dist/assets/skeleton-B0mmt1vo.js +1 -0
- package/dist/assets/{status-dot-CmQI5Qq2.js → status-dot-Bq_8Ojvv.js} +1 -1
- package/dist/assets/{switch-B7SxDXyR.js → switch-D7JF_RZ-.js} +1 -1
- package/dist/assets/{tabs-custom-Dxt6EJJW.js → tabs-custom-CLksZ2bO.js} +1 -1
- package/dist/assets/{trash-2-BnQ1PDTw.js → trash-2-VV8jvziy.js} +1 -1
- package/dist/assets/{useConfirmDialog-B-vMOmhG.js → useConfirmDialog-D6HxybcM.js} +1 -1
- package/dist/assets/{useMutation-Bi39Z9_J.js → useMutation-DBTWPbTg.js} +1 -1
- package/dist/assets/x-B4sxJkGY.js +1 -0
- package/dist/index.html +18 -18
- package/package.json +5 -5
- package/src/api/agents.ts +9 -1
- package/src/api/types.ts +25 -1
- package/src/components/agents/AgentDialogs.tsx +400 -0
- package/src/components/agents/AgentsPage.test.tsx +112 -1
- package/src/components/agents/AgentsPage.tsx +104 -112
- package/src/components/chat/ncp/NcpChatPage.tsx +1 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +6 -1
- package/src/components/chat/useChatSessionTypeState.ts +1 -1
- package/src/components/common/ProviderScopedModelInput.tsx +149 -0
- package/src/components/config/ChannelForm.test.tsx +60 -0
- package/src/components/config/ChannelForm.tsx +52 -12
- package/src/components/config/ModelConfig.test.tsx +61 -0
- package/src/components/config/ModelConfig.tsx +15 -90
- package/src/components/config/RuntimeConfig.tsx +2 -2
- package/src/components/config/SearchConfig.test.tsx +150 -0
- package/src/components/config/SearchConfig.tsx +257 -71
- package/src/components/config/runtime-config-agent.utils.ts +5 -4
- package/src/hooks/agents/useAgents.ts +18 -1
- package/src/lib/i18n.agents.ts +19 -0
- package/src/lib/i18n.search.ts +37 -0
- package/src/lib/i18n.ts +6 -26
- package/dist/assets/ChannelsList-DekMP4a3.js +0 -8
- package/dist/assets/ChatPage-Dgw4vlDt.js +0 -43
- package/dist/assets/DocBrowser-CExjX5is.js +0 -1
- package/dist/assets/MarketplacePage-DGfzg1LG.js +0 -1
- package/dist/assets/ModelConfig-C_49_a9v.js +0 -1
- package/dist/assets/RuntimeConfig-DBWzwoY-.js +0 -1
- package/dist/assets/SearchConfig-jSdwlH4b.js +0 -1
- package/dist/assets/chat-session-display-8yW6-mtm.js +0 -1
- package/dist/assets/i18n-DAekxt_G.js +0 -1
- package/dist/assets/index-CHEgQIiO.css +0 -1
- package/dist/assets/loader-circle-CGXXikVG.js +0 -1
- package/dist/assets/plus-CrW9BJDy.js +0 -1
- package/dist/assets/provider-models-IJDA940D.js +0 -1
- package/dist/assets/search-DgoXxocn.js +0 -1
- package/dist/assets/skeleton-BiPUQkOD.js +0 -1
- package/dist/assets/x-PBSiWt3l.js +0 -1
|
@@ -9,7 +9,7 @@ import { useConfig, useConfigMeta, useUpdateSearch } from '@/hooks/useConfig';
|
|
|
9
9
|
import { t } from '@/lib/i18n';
|
|
10
10
|
import { cn } from '@/lib/utils';
|
|
11
11
|
import { CONFIG_DETAIL_CARD_CLASS, CONFIG_SIDEBAR_CARD_CLASS, CONFIG_SPLIT_GRID_CLASS } from './config-layout';
|
|
12
|
-
import type { SearchConfigUpdate, SearchProviderName } from '@/api/types';
|
|
12
|
+
import type { SearchConfigUpdate, SearchProviderName, TavilySearchDepthValue } from '@/api/types';
|
|
13
13
|
|
|
14
14
|
const FRESHNESS_OPTIONS = [
|
|
15
15
|
{ value: 'noLimit', label: 'searchFreshnessNoLimit' },
|
|
@@ -19,11 +19,219 @@ const FRESHNESS_OPTIONS = [
|
|
|
19
19
|
{ value: 'oneYear', label: 'searchFreshnessOneYear' }
|
|
20
20
|
] as const;
|
|
21
21
|
|
|
22
|
+
const SEARCH_DEPTH_OPTIONS = [
|
|
23
|
+
{ value: 'basic', label: 'searchDepthBasic' },
|
|
24
|
+
{ value: 'advanced', label: 'searchDepthAdvanced' }
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
const SEARCH_PROVIDER_DESCRIPTION_KEYS: Record<SearchProviderName, string> = {
|
|
28
|
+
bocha: 'searchProviderBochaDescription',
|
|
29
|
+
tavily: 'searchProviderTavilyDescription',
|
|
30
|
+
brave: 'searchProviderBraveDescription'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type BochaProviderFieldsProps = {
|
|
34
|
+
apiKey: string;
|
|
35
|
+
apiKeyMasked?: string;
|
|
36
|
+
baseUrl: string;
|
|
37
|
+
summary: boolean;
|
|
38
|
+
freshness: string;
|
|
39
|
+
docsUrl?: string;
|
|
40
|
+
onApiKeyChange: (value: string) => void;
|
|
41
|
+
onBaseUrlChange: (value: string) => void;
|
|
42
|
+
onSummaryChange: (value: boolean) => void;
|
|
43
|
+
onFreshnessChange: (value: string) => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function BochaProviderFields({
|
|
47
|
+
apiKey,
|
|
48
|
+
apiKeyMasked,
|
|
49
|
+
baseUrl,
|
|
50
|
+
summary,
|
|
51
|
+
freshness,
|
|
52
|
+
docsUrl,
|
|
53
|
+
onApiKeyChange,
|
|
54
|
+
onBaseUrlChange,
|
|
55
|
+
onSummaryChange,
|
|
56
|
+
onFreshnessChange
|
|
57
|
+
}: BochaProviderFieldsProps) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="space-y-4">
|
|
60
|
+
<div className="space-y-2">
|
|
61
|
+
<Label>{t('apiKey')}</Label>
|
|
62
|
+
<Input
|
|
63
|
+
type="password"
|
|
64
|
+
value={apiKey}
|
|
65
|
+
onChange={(event) => onApiKeyChange(event.target.value)}
|
|
66
|
+
placeholder={apiKeyMasked || t('enterApiKey')}
|
|
67
|
+
className="rounded-xl"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
<div className="space-y-2">
|
|
71
|
+
<Label>{t('searchProviderBaseUrl')}</Label>
|
|
72
|
+
<Input value={baseUrl} onChange={(event) => onBaseUrlChange(event.target.value)} className="rounded-xl" />
|
|
73
|
+
</div>
|
|
74
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
75
|
+
<div className="space-y-2">
|
|
76
|
+
<Label>{t('searchProviderSummary')}</Label>
|
|
77
|
+
<Select value={summary ? 'true' : 'false'} onValueChange={(value) => onSummaryChange(value === 'true')}>
|
|
78
|
+
<SelectTrigger className="rounded-xl">
|
|
79
|
+
<SelectValue />
|
|
80
|
+
</SelectTrigger>
|
|
81
|
+
<SelectContent>
|
|
82
|
+
<SelectItem value="true">{t('enabled')}</SelectItem>
|
|
83
|
+
<SelectItem value="false">{t('disabled')}</SelectItem>
|
|
84
|
+
</SelectContent>
|
|
85
|
+
</Select>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<Label>{t('searchProviderFreshness')}</Label>
|
|
89
|
+
<Select value={freshness} onValueChange={onFreshnessChange}>
|
|
90
|
+
<SelectTrigger className="rounded-xl">
|
|
91
|
+
<SelectValue />
|
|
92
|
+
</SelectTrigger>
|
|
93
|
+
<SelectContent>
|
|
94
|
+
{FRESHNESS_OPTIONS.map((option) => (
|
|
95
|
+
<SelectItem key={option.value} value={option.value}>{t(option.label)}</SelectItem>
|
|
96
|
+
))}
|
|
97
|
+
</SelectContent>
|
|
98
|
+
</Select>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
<div className="space-y-2">
|
|
102
|
+
<a href={docsUrl ?? 'https://open.bocha.cn'} target="_blank" rel="noreferrer">
|
|
103
|
+
<Button type="button" variant="outline" className="rounded-xl">
|
|
104
|
+
<ExternalLink className="mr-2 h-4 w-4" />
|
|
105
|
+
{t('searchProviderOpenDocs')}
|
|
106
|
+
</Button>
|
|
107
|
+
</a>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type TavilyProviderFieldsProps = {
|
|
114
|
+
apiKey: string;
|
|
115
|
+
apiKeyMasked?: string;
|
|
116
|
+
baseUrl: string;
|
|
117
|
+
searchDepth: TavilySearchDepthValue;
|
|
118
|
+
includeAnswer: boolean;
|
|
119
|
+
docsUrl?: string;
|
|
120
|
+
onApiKeyChange: (value: string) => void;
|
|
121
|
+
onBaseUrlChange: (value: string) => void;
|
|
122
|
+
onSearchDepthChange: (value: TavilySearchDepthValue) => void;
|
|
123
|
+
onIncludeAnswerChange: (value: boolean) => void;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
function TavilyProviderFields({
|
|
127
|
+
apiKey,
|
|
128
|
+
apiKeyMasked,
|
|
129
|
+
baseUrl,
|
|
130
|
+
searchDepth,
|
|
131
|
+
includeAnswer,
|
|
132
|
+
docsUrl,
|
|
133
|
+
onApiKeyChange,
|
|
134
|
+
onBaseUrlChange,
|
|
135
|
+
onSearchDepthChange,
|
|
136
|
+
onIncludeAnswerChange
|
|
137
|
+
}: TavilyProviderFieldsProps) {
|
|
138
|
+
return (
|
|
139
|
+
<div className="space-y-4">
|
|
140
|
+
<div className="space-y-2">
|
|
141
|
+
<Label>{t('apiKey')}</Label>
|
|
142
|
+
<Input
|
|
143
|
+
type="password"
|
|
144
|
+
value={apiKey}
|
|
145
|
+
onChange={(event) => onApiKeyChange(event.target.value)}
|
|
146
|
+
placeholder={apiKeyMasked || t('enterApiKey')}
|
|
147
|
+
className="rounded-xl"
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
<div className="space-y-2">
|
|
151
|
+
<Label>{t('searchProviderBaseUrl')}</Label>
|
|
152
|
+
<Input value={baseUrl} onChange={(event) => onBaseUrlChange(event.target.value)} className="rounded-xl" />
|
|
153
|
+
</div>
|
|
154
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
155
|
+
<div className="space-y-2">
|
|
156
|
+
<Label>{t('searchProviderSearchDepth')}</Label>
|
|
157
|
+
<Select value={searchDepth} onValueChange={(value) => onSearchDepthChange(value as TavilySearchDepthValue)}>
|
|
158
|
+
<SelectTrigger className="rounded-xl">
|
|
159
|
+
<SelectValue />
|
|
160
|
+
</SelectTrigger>
|
|
161
|
+
<SelectContent>
|
|
162
|
+
{SEARCH_DEPTH_OPTIONS.map((option) => (
|
|
163
|
+
<SelectItem key={option.value} value={option.value}>{t(option.label)}</SelectItem>
|
|
164
|
+
))}
|
|
165
|
+
</SelectContent>
|
|
166
|
+
</Select>
|
|
167
|
+
</div>
|
|
168
|
+
<div className="space-y-2">
|
|
169
|
+
<Label>{t('searchProviderIncludeAnswer')}</Label>
|
|
170
|
+
<Select value={includeAnswer ? 'true' : 'false'} onValueChange={(value) => onIncludeAnswerChange(value === 'true')}>
|
|
171
|
+
<SelectTrigger className="rounded-xl">
|
|
172
|
+
<SelectValue />
|
|
173
|
+
</SelectTrigger>
|
|
174
|
+
<SelectContent>
|
|
175
|
+
<SelectItem value="true">{t('enabled')}</SelectItem>
|
|
176
|
+
<SelectItem value="false">{t('disabled')}</SelectItem>
|
|
177
|
+
</SelectContent>
|
|
178
|
+
</Select>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
{docsUrl ? (
|
|
182
|
+
<div className="space-y-2">
|
|
183
|
+
<a href={docsUrl} target="_blank" rel="noreferrer">
|
|
184
|
+
<Button type="button" variant="outline" className="rounded-xl">
|
|
185
|
+
<ExternalLink className="mr-2 h-4 w-4" />
|
|
186
|
+
{t('searchProviderOpenDocs')}
|
|
187
|
+
</Button>
|
|
188
|
+
</a>
|
|
189
|
+
</div>
|
|
190
|
+
) : null}
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
type BraveProviderFieldsProps = {
|
|
196
|
+
apiKey: string;
|
|
197
|
+
apiKeyMasked?: string;
|
|
198
|
+
baseUrl: string;
|
|
199
|
+
onApiKeyChange: (value: string) => void;
|
|
200
|
+
onBaseUrlChange: (value: string) => void;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
function BraveProviderFields({
|
|
204
|
+
apiKey,
|
|
205
|
+
apiKeyMasked,
|
|
206
|
+
baseUrl,
|
|
207
|
+
onApiKeyChange,
|
|
208
|
+
onBaseUrlChange
|
|
209
|
+
}: BraveProviderFieldsProps) {
|
|
210
|
+
return (
|
|
211
|
+
<div className="space-y-4">
|
|
212
|
+
<div className="space-y-2">
|
|
213
|
+
<Label>{t('apiKey')}</Label>
|
|
214
|
+
<Input
|
|
215
|
+
type="password"
|
|
216
|
+
value={apiKey}
|
|
217
|
+
onChange={(event) => onApiKeyChange(event.target.value)}
|
|
218
|
+
placeholder={apiKeyMasked || t('enterApiKey')}
|
|
219
|
+
className="rounded-xl"
|
|
220
|
+
/>
|
|
221
|
+
</div>
|
|
222
|
+
<div className="space-y-2">
|
|
223
|
+
<Label>{t('searchProviderBaseUrl')}</Label>
|
|
224
|
+
<Input value={baseUrl} onChange={(event) => onBaseUrlChange(event.target.value)} className="rounded-xl" />
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
22
230
|
export function SearchConfig() {
|
|
23
231
|
const { data: config } = useConfig();
|
|
24
232
|
const { data: meta } = useConfigMeta();
|
|
25
233
|
const updateSearch = useUpdateSearch();
|
|
26
|
-
const providers = meta?.search ?? [];
|
|
234
|
+
const providers = useMemo(() => meta?.search ?? [], [meta]);
|
|
27
235
|
const search = config?.search;
|
|
28
236
|
|
|
29
237
|
const [selectedProvider, setSelectedProvider] = useState<SearchProviderName>('bocha');
|
|
@@ -34,6 +242,10 @@ export function SearchConfig() {
|
|
|
34
242
|
const [bochaBaseUrl, setBochaBaseUrl] = useState('https://api.bocha.cn/v1/web-search');
|
|
35
243
|
const [bochaSummary, setBochaSummary] = useState(true);
|
|
36
244
|
const [bochaFreshness, setBochaFreshness] = useState('noLimit');
|
|
245
|
+
const [tavilyApiKey, setTavilyApiKey] = useState('');
|
|
246
|
+
const [tavilyBaseUrl, setTavilyBaseUrl] = useState('https://api.tavily.com/search');
|
|
247
|
+
const [tavilySearchDepth, setTavilySearchDepth] = useState<TavilySearchDepthValue>('basic');
|
|
248
|
+
const [tavilyIncludeAnswer, setTavilyIncludeAnswer] = useState(false);
|
|
37
249
|
const [braveApiKey, setBraveApiKey] = useState('');
|
|
38
250
|
const [braveBaseUrl, setBraveBaseUrl] = useState('https://api.search.brave.com/res/v1/web/search');
|
|
39
251
|
|
|
@@ -48,6 +260,9 @@ export function SearchConfig() {
|
|
|
48
260
|
setBochaBaseUrl(search.providers.bocha.baseUrl);
|
|
49
261
|
setBochaSummary(Boolean(search.providers.bocha.summary));
|
|
50
262
|
setBochaFreshness(search.providers.bocha.freshness ?? 'noLimit');
|
|
263
|
+
setTavilyBaseUrl(search.providers.tavily.baseUrl);
|
|
264
|
+
setTavilySearchDepth(search.providers.tavily.searchDepth ?? 'basic');
|
|
265
|
+
setTavilyIncludeAnswer(Boolean(search.providers.tavily.includeAnswer));
|
|
51
266
|
setBraveBaseUrl(search.providers.brave.baseUrl);
|
|
52
267
|
}, [search]);
|
|
53
268
|
|
|
@@ -57,7 +272,7 @@ export function SearchConfig() {
|
|
|
57
272
|
);
|
|
58
273
|
const selectedView = search?.providers[selectedProvider];
|
|
59
274
|
const selectedEnabled = enabledProviders.includes(selectedProvider);
|
|
60
|
-
const
|
|
275
|
+
const selectedDocsUrl = selectedView?.docsUrl ?? selectedMeta?.docsUrl;
|
|
61
276
|
const activationButtonLabel = selectedEnabled
|
|
62
277
|
? t('searchProviderDeactivate')
|
|
63
278
|
: t('searchProviderActivate');
|
|
@@ -78,6 +293,12 @@ export function SearchConfig() {
|
|
|
78
293
|
summary: bochaSummary,
|
|
79
294
|
freshness: bochaFreshness
|
|
80
295
|
},
|
|
296
|
+
tavily: {
|
|
297
|
+
apiKey: tavilyApiKey || undefined,
|
|
298
|
+
baseUrl: tavilyBaseUrl,
|
|
299
|
+
searchDepth: tavilySearchDepth,
|
|
300
|
+
includeAnswer: tavilyIncludeAnswer
|
|
301
|
+
},
|
|
81
302
|
brave: {
|
|
82
303
|
apiKey: braveApiKey || undefined,
|
|
83
304
|
baseUrl: braveBaseUrl
|
|
@@ -140,7 +361,7 @@ export function SearchConfig() {
|
|
|
140
361
|
<div className="min-w-0">
|
|
141
362
|
<p className="truncate text-sm font-semibold text-gray-900">{provider.displayName}</p>
|
|
142
363
|
<p className="line-clamp-2 text-[11px] text-gray-500">
|
|
143
|
-
{provider.name
|
|
364
|
+
{t(SEARCH_PROVIDER_DESCRIPTION_KEYS[provider.name])}
|
|
144
365
|
</p>
|
|
145
366
|
</div>
|
|
146
367
|
<div className="flex flex-col items-end gap-1">
|
|
@@ -212,74 +433,39 @@ export function SearchConfig() {
|
|
|
212
433
|
</div>
|
|
213
434
|
|
|
214
435
|
{selectedProvider === 'bocha' ? (
|
|
215
|
-
<
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
</SelectContent>
|
|
241
|
-
</Select>
|
|
242
|
-
</div>
|
|
243
|
-
<div className="space-y-2">
|
|
244
|
-
<Label>{t('searchProviderFreshness')}</Label>
|
|
245
|
-
<Select value={bochaFreshness} onValueChange={setBochaFreshness}>
|
|
246
|
-
<SelectTrigger className="rounded-xl">
|
|
247
|
-
<SelectValue />
|
|
248
|
-
</SelectTrigger>
|
|
249
|
-
<SelectContent>
|
|
250
|
-
{FRESHNESS_OPTIONS.map((option) => (
|
|
251
|
-
<SelectItem key={option.value} value={option.value}>{t(option.label)}</SelectItem>
|
|
252
|
-
))}
|
|
253
|
-
</SelectContent>
|
|
254
|
-
</Select>
|
|
255
|
-
</div>
|
|
256
|
-
</div>
|
|
257
|
-
<div className="space-y-2">
|
|
258
|
-
<a href={bochaDocsUrl} target="_blank" rel="noreferrer">
|
|
259
|
-
<Button type="button" variant="outline" className="rounded-xl">
|
|
260
|
-
<ExternalLink className="mr-2 h-4 w-4" />
|
|
261
|
-
{t('searchProviderOpenDocs')}
|
|
262
|
-
</Button>
|
|
263
|
-
</a>
|
|
264
|
-
</div>
|
|
265
|
-
</div>
|
|
436
|
+
<BochaProviderFields
|
|
437
|
+
apiKey={bochaApiKey}
|
|
438
|
+
apiKeyMasked={search.providers.bocha.apiKeyMasked}
|
|
439
|
+
baseUrl={bochaBaseUrl}
|
|
440
|
+
summary={bochaSummary}
|
|
441
|
+
freshness={bochaFreshness}
|
|
442
|
+
docsUrl={selectedDocsUrl}
|
|
443
|
+
onApiKeyChange={setBochaApiKey}
|
|
444
|
+
onBaseUrlChange={setBochaBaseUrl}
|
|
445
|
+
onSummaryChange={setBochaSummary}
|
|
446
|
+
onFreshnessChange={setBochaFreshness}
|
|
447
|
+
/>
|
|
448
|
+
) : selectedProvider === 'tavily' ? (
|
|
449
|
+
<TavilyProviderFields
|
|
450
|
+
apiKey={tavilyApiKey}
|
|
451
|
+
apiKeyMasked={search.providers.tavily.apiKeyMasked}
|
|
452
|
+
baseUrl={tavilyBaseUrl}
|
|
453
|
+
searchDepth={tavilySearchDepth}
|
|
454
|
+
includeAnswer={tavilyIncludeAnswer}
|
|
455
|
+
docsUrl={selectedDocsUrl}
|
|
456
|
+
onApiKeyChange={setTavilyApiKey}
|
|
457
|
+
onBaseUrlChange={setTavilyBaseUrl}
|
|
458
|
+
onSearchDepthChange={setTavilySearchDepth}
|
|
459
|
+
onIncludeAnswerChange={setTavilyIncludeAnswer}
|
|
460
|
+
/>
|
|
266
461
|
) : (
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
placeholder={search.providers.brave.apiKeyMasked || t('enterApiKey')}
|
|
275
|
-
className="rounded-xl"
|
|
276
|
-
/>
|
|
277
|
-
</div>
|
|
278
|
-
<div className="space-y-2">
|
|
279
|
-
<Label>{t('searchProviderBaseUrl')}</Label>
|
|
280
|
-
<Input value={braveBaseUrl} onChange={(event) => setBraveBaseUrl(event.target.value)} className="rounded-xl" />
|
|
281
|
-
</div>
|
|
282
|
-
</div>
|
|
462
|
+
<BraveProviderFields
|
|
463
|
+
apiKey={braveApiKey}
|
|
464
|
+
apiKeyMasked={search.providers.brave.apiKeyMasked}
|
|
465
|
+
baseUrl={braveBaseUrl}
|
|
466
|
+
onApiKeyChange={setBraveApiKey}
|
|
467
|
+
onBaseUrlChange={setBraveBaseUrl}
|
|
468
|
+
/>
|
|
283
469
|
)}
|
|
284
470
|
|
|
285
471
|
<div className="flex justify-end">
|
|
@@ -6,7 +6,7 @@ export function createEmptyRuntimeAgent(): AgentProfileView {
|
|
|
6
6
|
default: false,
|
|
7
7
|
workspace: '',
|
|
8
8
|
model: '',
|
|
9
|
-
|
|
9
|
+
runtime: '',
|
|
10
10
|
contextTokens: undefined,
|
|
11
11
|
maxToolIterations: undefined
|
|
12
12
|
};
|
|
@@ -31,7 +31,7 @@ export function hydrateRuntimeAgent(agent: AgentProfileView): AgentProfileView {
|
|
|
31
31
|
avatar: agent.avatar ?? '',
|
|
32
32
|
workspace: agent.workspace ?? '',
|
|
33
33
|
model: agent.model ?? '',
|
|
34
|
-
|
|
34
|
+
runtime: agent.runtime ?? agent.engine ?? '',
|
|
35
35
|
contextTokens: agent.contextTokens,
|
|
36
36
|
maxToolIterations: agent.maxToolIterations
|
|
37
37
|
};
|
|
@@ -82,8 +82,9 @@ export function toPersistedRuntimeAgent(agent: AgentProfileView): AgentProfileVi
|
|
|
82
82
|
if (agent.model?.trim()) {
|
|
83
83
|
normalized.model = agent.model.trim();
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const runtime = agent.runtime?.trim() ?? agent.engine?.trim();
|
|
86
|
+
if (runtime) {
|
|
87
|
+
normalized.engine = runtime;
|
|
87
88
|
}
|
|
88
89
|
if (typeof agent.contextTokens === 'number') {
|
|
89
90
|
normalized.contextTokens = Math.max(1000, agent.contextTokens);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
2
|
-
import { createAgent, deleteAgent, fetchAgents } from '@/api/agents';
|
|
2
|
+
import { createAgent, deleteAgent, fetchAgents, updateAgent } from '@/api/agents';
|
|
3
3
|
import { toast } from 'sonner';
|
|
4
4
|
import { t } from '@/lib/i18n';
|
|
5
5
|
|
|
@@ -27,6 +27,23 @@ export function useCreateAgent() {
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export function useUpdateAgent() {
|
|
31
|
+
const queryClient = useQueryClient();
|
|
32
|
+
|
|
33
|
+
return useMutation({
|
|
34
|
+
mutationFn: ({ agentId, data }: { agentId: string; data: Parameters<typeof updateAgent>[1] }) =>
|
|
35
|
+
updateAgent(agentId, data),
|
|
36
|
+
onSuccess: () => {
|
|
37
|
+
queryClient.invalidateQueries({ queryKey: ['agents'] });
|
|
38
|
+
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
39
|
+
toast.success(t('configSavedApplied'));
|
|
40
|
+
},
|
|
41
|
+
onError: (error: Error) => {
|
|
42
|
+
toast.error(t('configSaveFailed') + ': ' + error.message);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
export function useDeleteAgent() {
|
|
31
48
|
const queryClient = useQueryClient();
|
|
32
49
|
|
package/src/lib/i18n.agents.ts
CHANGED
|
@@ -33,7 +33,25 @@ export const AGENT_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
33
33
|
agentsFormDescriptionPlaceholder: { zh: '角色描述,可选', en: 'Role description, optional' },
|
|
34
34
|
agentsFormAvatarPlaceholder: { zh: '头像 URL 或本地路径,可选', en: 'Avatar URL or local path, optional' },
|
|
35
35
|
agentsFormHomePlaceholder: { zh: '主目录,可选', en: 'Home Directory, optional' },
|
|
36
|
+
agentsFormRuntimePlaceholder: { zh: 'Runtime(如 native 或 codex,可选)', en: 'Runtime (e.g. native or codex, optional)' },
|
|
37
|
+
agentsRuntimeSelectPlaceholder: { zh: '选择 Runtime', en: 'Select runtime' },
|
|
38
|
+
agentsRuntimeUnavailableHelp: {
|
|
39
|
+
zh: '当前 Runtime 已不在可用列表中;建议改选一个已安装且可用的 Runtime。',
|
|
40
|
+
en: 'This runtime is no longer available in the current list. Switch to an installed and available runtime.'
|
|
41
|
+
},
|
|
36
42
|
agentsCreateAction: { zh: '创建 Agent', en: 'Create Agent' },
|
|
43
|
+
agentsEditAction: { zh: '编辑', en: 'Edit' },
|
|
44
|
+
agentsEditDialogTitle: { zh: '编辑 Agent 身份', en: 'Edit agent identity' },
|
|
45
|
+
agentsEditDialogDescription: {
|
|
46
|
+
zh: '更新名称、角色描述与头像。Agent ID 与主目录保持稳定,避免影响既有记忆、技能与会话。',
|
|
47
|
+
en: 'Update the name, role description, and avatar. The agent ID and home directory stay stable to protect existing memory, skills, and sessions.'
|
|
48
|
+
},
|
|
49
|
+
agentsEditHomeReadonly: { zh: '主目录保持不变', en: 'Home directory stays unchanged' },
|
|
50
|
+
agentsEditHomeReadonlyHint: {
|
|
51
|
+
zh: '如需迁移主目录,请走独立配置迁移流程,避免意外丢失 Agent 上下文。',
|
|
52
|
+
en: 'Use a dedicated config migration flow to move the home directory and avoid losing agent context unexpectedly.'
|
|
53
|
+
},
|
|
54
|
+
agentsEditSaveAction: { zh: '保存编辑', en: 'Save edits' },
|
|
37
55
|
agentsRemoveAction: { zh: '移除', en: 'Remove' },
|
|
38
56
|
agentsNewChat: { zh: '新建会话', en: 'New Chat' },
|
|
39
57
|
agentsLoading: { zh: '正在加载 Agent...', en: 'Loading agents...' },
|
|
@@ -55,6 +73,7 @@ export const AGENT_LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
55
73
|
zh: '专属 Agent 身份,可沉淀自己的记忆、技能与角色风格。',
|
|
56
74
|
en: 'A dedicated identity with its own memory, skills, and role style.'
|
|
57
75
|
},
|
|
76
|
+
agentsCardRuntimeLabel: { zh: 'Runtime', en: 'Runtime' },
|
|
58
77
|
agentsCardHomeLabel: { zh: '主目录', en: 'Home Directory' },
|
|
59
78
|
agentsCardAvatarLabel: { zh: 'Avatar', en: 'Avatar' },
|
|
60
79
|
agentsCardStartChat: { zh: '开始对话', en: 'Start Chat' },
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const SEARCH_LABELS: Record<string, { zh: string; en: string }> = {
|
|
2
|
+
searchPageTitle: { zh: '搜索渠道', en: 'Search Channels' },
|
|
3
|
+
searchPageDescription: { zh: '配置网页搜索提供商', en: 'Configure web search providers.' },
|
|
4
|
+
searchActiveProvider: { zh: '当前搜索提供商', en: 'Active Search Provider' },
|
|
5
|
+
searchDefaultMaxResults: { zh: '默认返回条数', en: 'Default Result Count' },
|
|
6
|
+
searchProviderSummary: { zh: '结果摘要', en: 'Result Summary' },
|
|
7
|
+
searchProviderFreshness: { zh: '时间范围', en: 'Freshness' },
|
|
8
|
+
searchProviderSearchDepth: { zh: '搜索深度', en: 'Search Depth' },
|
|
9
|
+
searchProviderIncludeAnswer: { zh: '包含回答', en: 'Include Answer' },
|
|
10
|
+
searchProviderBaseUrl: { zh: '接口地址', en: 'API Base URL' },
|
|
11
|
+
searchProviderOpenDocs: { zh: '查看文档', en: 'Open Docs' },
|
|
12
|
+
searchProviderActivate: { zh: '激活', en: 'Activate' },
|
|
13
|
+
searchProviderActivated: { zh: '已激活', en: 'Activated' },
|
|
14
|
+
searchProviderDeactivate: { zh: '取消激活', en: 'Deactivate' },
|
|
15
|
+
searchProviderBochaDescription: {
|
|
16
|
+
zh: '更适合中国大陆用户的 AI 搜索。',
|
|
17
|
+
en: 'AI-ready search that works better for mainland China users.'
|
|
18
|
+
},
|
|
19
|
+
searchProviderTavilyDescription: {
|
|
20
|
+
zh: '更适合调研和网页信息整合的搜索 provider。',
|
|
21
|
+
en: 'Research-focused search with configurable answer synthesis.'
|
|
22
|
+
},
|
|
23
|
+
searchProviderBraveDescription: {
|
|
24
|
+
zh: '保留 Brave 作为可选 provider。',
|
|
25
|
+
en: 'Keep Brave as an optional provider.'
|
|
26
|
+
},
|
|
27
|
+
searchStatusConfigured: { zh: '已配置', en: 'Configured' },
|
|
28
|
+
searchStatusNeedsSetup: { zh: '待配置', en: 'Needs Setup' },
|
|
29
|
+
searchFreshnessNoLimit: { zh: '不限', en: 'No Limit' },
|
|
30
|
+
searchFreshnessOneDay: { zh: '一天内', en: 'One Day' },
|
|
31
|
+
searchFreshnessOneWeek: { zh: '一周内', en: 'One Week' },
|
|
32
|
+
searchFreshnessOneMonth: { zh: '一个月内', en: 'One Month' },
|
|
33
|
+
searchFreshnessOneYear: { zh: '一年内', en: 'One Year' },
|
|
34
|
+
searchDepthBasic: { zh: '基础', en: 'Basic' },
|
|
35
|
+
searchDepthAdvanced: { zh: '高级', en: 'Advanced' },
|
|
36
|
+
searchNoProviderSelected: { zh: '请选择左侧搜索 provider', en: 'Select a search provider from the left.' }
|
|
37
|
+
};
|
package/src/lib/i18n.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import { MARKETPLACE_LABELS } from './i18n.marketplace';
|
|
16
16
|
import { PATH_PICKER_LABELS } from './i18n-runtime/i18n.path-picker';
|
|
17
17
|
import { REMOTE_LABELS } from './i18n.remote';
|
|
18
|
+
import { SEARCH_LABELS } from './i18n.search';
|
|
18
19
|
export type { I18nLanguage };
|
|
19
20
|
export {
|
|
20
21
|
getLanguage,
|
|
@@ -124,28 +125,6 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
124
125
|
maxToolIterations: { zh: '最大工具迭代次数', en: 'Max Tool Iterations' },
|
|
125
126
|
saveChanges: { zh: '保存变更', en: 'Save Changes' },
|
|
126
127
|
|
|
127
|
-
// Provider
|
|
128
|
-
searchPageTitle: { zh: '搜索渠道', en: 'Search Channels' },
|
|
129
|
-
searchPageDescription: { zh: '配置网页搜索提供商', en: 'Configure web search providers.' },
|
|
130
|
-
searchActiveProvider: { zh: '当前搜索提供商', en: 'Active Search Provider' },
|
|
131
|
-
searchDefaultMaxResults: { zh: '默认返回条数', en: 'Default Result Count' },
|
|
132
|
-
searchProviderSummary: { zh: '结果摘要', en: 'Result Summary' },
|
|
133
|
-
searchProviderFreshness: { zh: '时间范围', en: 'Freshness' },
|
|
134
|
-
searchProviderBaseUrl: { zh: '接口地址', en: 'API Base URL' },
|
|
135
|
-
searchProviderOpenDocs: { zh: '获取博查 API', en: 'Get Bocha API' },
|
|
136
|
-
searchProviderActivate: { zh: '激活', en: 'Activate' },
|
|
137
|
-
searchProviderActivated: { zh: '已激活', en: 'Activated' },
|
|
138
|
-
searchProviderDeactivate: { zh: '取消激活', en: 'Deactivate' },
|
|
139
|
-
searchProviderBochaDescription: { zh: '更适合中国大陆用户的 AI 搜索。', en: 'AI-ready search that works better for mainland China users.' },
|
|
140
|
-
searchProviderBraveDescription: { zh: '保留 Brave 作为可选 provider。', en: 'Keep Brave as an optional provider.' },
|
|
141
|
-
searchStatusConfigured: { zh: '已配置', en: 'Configured' },
|
|
142
|
-
searchStatusNeedsSetup: { zh: '待配置', en: 'Needs Setup' },
|
|
143
|
-
searchFreshnessNoLimit: { zh: '不限', en: 'No Limit' },
|
|
144
|
-
searchFreshnessOneDay: { zh: '一天内', en: 'One Day' },
|
|
145
|
-
searchFreshnessOneWeek: { zh: '一周内', en: 'One Week' },
|
|
146
|
-
searchFreshnessOneMonth: { zh: '一个月内', en: 'One Month' },
|
|
147
|
-
searchFreshnessOneYear: { zh: '一年内', en: 'One Year' },
|
|
148
|
-
searchNoProviderSelected: { zh: '请选择左侧搜索 provider', en: 'Select a search provider from the left.' },
|
|
149
128
|
providersPageTitle: { zh: 'AI 提供商', en: 'AI Providers' },
|
|
150
129
|
providersPageDescription: { zh: '在一个页面内完成提供商切换、配置与保存。', en: 'Switch, configure, and save providers in one continuous workspace.' },
|
|
151
130
|
providersLoading: { zh: '加载中...', en: 'Loading...' },
|
|
@@ -245,6 +224,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
245
224
|
providerAuthSessionLabel: { zh: '会话', en: 'Session' },
|
|
246
225
|
resetToDefault: { zh: '恢复默认', en: 'Reset to Default' },
|
|
247
226
|
leaveBlankToKeepUnchanged: { zh: '留空则保持不变', en: 'Leave blank to keep unchanged' },
|
|
227
|
+
...SEARCH_LABELS,
|
|
248
228
|
|
|
249
229
|
// Channel
|
|
250
230
|
...CHANNEL_LABELS,
|
|
@@ -379,8 +359,8 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
379
359
|
dmScopeHelp: { zh: '控制私聊会话如何隔离。', en: 'Control how direct-message sessions are isolated.' },
|
|
380
360
|
defaultContextTokens: { zh: '默认上下文 Token', en: 'Default Context Tokens' },
|
|
381
361
|
defaultContextTokensHelp: { zh: '当 Agent 未设置单独值时使用该上下文预算。', en: 'Input context budget for agents when no per-agent override is set.' },
|
|
382
|
-
defaultEngine: { zh: '
|
|
383
|
-
defaultEngineHelp: { zh: '默认使用的 Agent
|
|
362
|
+
defaultEngine: { zh: '默认 Runtime', en: 'Default Runtime' },
|
|
363
|
+
defaultEngineHelp: { zh: '默认使用的 Agent Runtime,例如 native、codex 或 claude。', en: 'Default agent runtime, for example native, codex, or claude.' },
|
|
384
364
|
agentList: { zh: 'Agent 列表', en: 'Agent List' },
|
|
385
365
|
agentListHelp: { zh: '在同一个网关进程中运行多个固定角色 Agent。', en: 'Run multiple fixed-role agents in one gateway process.' },
|
|
386
366
|
bindings: { zh: '绑定规则', en: 'Bindings' },
|
|
@@ -394,8 +374,8 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
394
374
|
agentIdPlaceholder: { zh: 'Agent ID(例如 engineer)', en: 'Agent ID (e.g. engineer)' },
|
|
395
375
|
workspaceOverridePlaceholder: { zh: '工作空间覆盖(可选)', en: 'Workspace override (optional)' },
|
|
396
376
|
modelOverridePlaceholder: { zh: '模型覆盖(可选)', en: 'Model override (optional)' },
|
|
397
|
-
defaultEnginePlaceholder: { zh: '
|
|
398
|
-
engineOverridePlaceholder: { zh: '
|
|
377
|
+
defaultEnginePlaceholder: { zh: '默认 Runtime(如 native 或 codex)', en: 'Default runtime (e.g. native or codex)' },
|
|
378
|
+
engineOverridePlaceholder: { zh: 'Runtime 覆盖(可选)', en: 'Runtime override (optional)' },
|
|
399
379
|
contextTokensPlaceholder: { zh: '上下文 tokens', en: 'Context tokens' },
|
|
400
380
|
maxToolsPlaceholder: { zh: '最大工具次数', en: 'Max tools' },
|
|
401
381
|
defaultAgent: { zh: '默认 Agent', en: 'Default agent' },
|