@nextclaw/ui 0.6.11 → 0.6.13
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 +13 -0
- package/dist/assets/ChannelsList-CXLzowHj.js +1 -0
- package/dist/assets/ChatPage-CvtonrzM.js +36 -0
- package/dist/assets/DocBrowser-4NK6-Q_u.js +1 -0
- package/dist/assets/LogoBadge-NI7KQCLa.js +1 -0
- package/dist/assets/{MarketplacePage-BOzko5s9.js → MarketplacePage-n7y-pif2.js} +2 -2
- package/dist/assets/ModelConfig-DztCs0mA.js +1 -0
- package/dist/assets/ProvidersList-hSzfE0pG.js +1 -0
- package/dist/assets/{RuntimeConfig-Dt9pLB9P.js → RuntimeConfig-CKFGVus7.js} +1 -1
- package/dist/assets/SearchConfig-Cxs1744q.js +1 -0
- package/dist/assets/{SecretsConfig-C1PU0Yy8.js → SecretsConfig-C90UckNB.js} +2 -2
- package/dist/assets/SessionsConfig-CRor418P.js +2 -0
- package/dist/assets/{card-C7Gtw2Vs.js → card-BQiPUGaa.js} +1 -1
- package/dist/assets/config-layout-BHnOoweL.js +1 -0
- package/dist/assets/index-BCfS4UY1.css +1 -0
- package/dist/assets/index-CB5eJOGS.js +8 -0
- package/dist/assets/index-CkqvHQAt.js +1 -0
- package/dist/assets/{input-oBvxsnV9.js → input-DmFFMdAk.js} +1 -1
- package/dist/assets/{label-C7F8lMpQ.js → label-BHvlZoIz.js} +1 -1
- package/dist/assets/{page-layout-DO8BlScF.js → page-layout-COPE9JyG.js} +1 -1
- package/dist/assets/popover-gcypYeec.js +1 -0
- package/dist/assets/provider-models-D3B_xWXx.js +1 -0
- package/dist/assets/{session-run-status-Kg0FwAPn.js → session-run-status-BgNvd_-a.js} +1 -1
- package/dist/assets/{switch-C6a5GyZB.js → switch-BampMwqT.js} +1 -1
- package/dist/assets/{tabs-custom-BatFap5k.js → tabs-custom-DSQeYaKd.js} +1 -1
- package/dist/assets/useConfirmDialog-DZUn23Li.js +5 -0
- package/dist/assets/{vendor-TlME1INH.js → vendor-BKtTvQYU.js} +69 -64
- package/dist/index.html +3 -3
- package/package.json +1 -1
- package/src/App.tsx +2 -0
- package/src/api/config.ts +13 -0
- package/src/api/types.ts +61 -0
- package/src/components/chat/ChatPage.tsx +16 -0
- package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +12 -0
- package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +3 -3
- package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputThinkingSelector.tsx +74 -0
- package/src/components/chat/chat-input/useChatInputBarController.ts +11 -2
- package/src/components/chat/chat-input.types.ts +8 -0
- package/src/components/chat/chat-page-data.ts +40 -3
- package/src/components/chat/chat-stream/transport.ts +3 -0
- package/src/components/chat/chat-stream/types.ts +3 -1
- package/src/components/chat/managers/chat-input.manager.ts +51 -0
- package/src/components/chat/stores/chat-input.store.ts +5 -1
- package/src/components/common/SearchableModelInput.tsx +22 -5
- package/src/components/config/ModelConfig.tsx +13 -12
- package/src/components/config/ProviderForm.tsx +292 -19
- package/src/components/config/SearchConfig.tsx +297 -0
- package/src/components/layout/Sidebar.tsx +6 -1
- package/src/hooks/useConfig.ts +17 -0
- package/src/lib/i18n.ts +37 -0
- package/src/lib/provider-models.ts +91 -3
- package/dist/assets/ChannelsList-C49JQ-Zt.js +0 -1
- package/dist/assets/ChatPage-DIx05c6s.js +0 -36
- package/dist/assets/DocBrowser-CpOosDEI.js +0 -1
- package/dist/assets/LogoBadge-CL_8ZPXU.js +0 -1
- package/dist/assets/ModelConfig-BZ4ZfaQB.js +0 -1
- package/dist/assets/ProvidersList-fPpJ5gl6.js +0 -1
- package/dist/assets/SessionsConfig-EskBOofQ.js +0 -2
- package/dist/assets/index-Cn6_2To7.js +0 -8
- package/dist/assets/index-nEYGCJTC.css +0 -1
- package/dist/assets/provider-models-y4mUDcGF.js +0 -1
- package/dist/assets/useConfirmDialog-zJzVKMdu.js +0 -5
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { ExternalLink, KeyRound, Search as SearchIcon } from 'lucide-react';
|
|
3
|
+
import { PageHeader, PageLayout } from '@/components/layout/page-layout';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { Input } from '@/components/ui/input';
|
|
6
|
+
import { Label } from '@/components/ui/label';
|
|
7
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
8
|
+
import { useConfig, useConfigMeta, useUpdateSearch } from '@/hooks/useConfig';
|
|
9
|
+
import { t } from '@/lib/i18n';
|
|
10
|
+
import { cn } from '@/lib/utils';
|
|
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';
|
|
13
|
+
|
|
14
|
+
const FRESHNESS_OPTIONS = [
|
|
15
|
+
{ value: 'noLimit', label: 'searchFreshnessNoLimit' },
|
|
16
|
+
{ value: 'oneDay', label: 'searchFreshnessOneDay' },
|
|
17
|
+
{ value: 'oneWeek', label: 'searchFreshnessOneWeek' },
|
|
18
|
+
{ value: 'oneMonth', label: 'searchFreshnessOneMonth' },
|
|
19
|
+
{ value: 'oneYear', label: 'searchFreshnessOneYear' }
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export function SearchConfig() {
|
|
23
|
+
const { data: config } = useConfig();
|
|
24
|
+
const { data: meta } = useConfigMeta();
|
|
25
|
+
const updateSearch = useUpdateSearch();
|
|
26
|
+
const providers = meta?.search ?? [];
|
|
27
|
+
const search = config?.search;
|
|
28
|
+
|
|
29
|
+
const [selectedProvider, setSelectedProvider] = useState<SearchProviderName>('bocha');
|
|
30
|
+
const [activeProvider, setActiveProvider] = useState<SearchProviderName>('bocha');
|
|
31
|
+
const [enabledProviders, setEnabledProviders] = useState<SearchProviderName[]>(['bocha']);
|
|
32
|
+
const [maxResults, setMaxResults] = useState('10');
|
|
33
|
+
const [bochaApiKey, setBochaApiKey] = useState('');
|
|
34
|
+
const [bochaBaseUrl, setBochaBaseUrl] = useState('https://api.bocha.cn/v1/web-search');
|
|
35
|
+
const [bochaSummary, setBochaSummary] = useState(true);
|
|
36
|
+
const [bochaFreshness, setBochaFreshness] = useState('noLimit');
|
|
37
|
+
const [braveApiKey, setBraveApiKey] = useState('');
|
|
38
|
+
const [braveBaseUrl, setBraveBaseUrl] = useState('https://api.search.brave.com/res/v1/web/search');
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!search) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
setSelectedProvider(search.provider);
|
|
45
|
+
setActiveProvider(search.provider);
|
|
46
|
+
setEnabledProviders(search.enabledProviders);
|
|
47
|
+
setMaxResults(String(search.defaults.maxResults));
|
|
48
|
+
setBochaBaseUrl(search.providers.bocha.baseUrl);
|
|
49
|
+
setBochaSummary(Boolean(search.providers.bocha.summary));
|
|
50
|
+
setBochaFreshness(search.providers.bocha.freshness ?? 'noLimit');
|
|
51
|
+
setBraveBaseUrl(search.providers.brave.baseUrl);
|
|
52
|
+
}, [search]);
|
|
53
|
+
|
|
54
|
+
const selectedMeta = useMemo(
|
|
55
|
+
() => providers.find((provider) => provider.name === selectedProvider),
|
|
56
|
+
[providers, selectedProvider]
|
|
57
|
+
);
|
|
58
|
+
const selectedView = search?.providers[selectedProvider];
|
|
59
|
+
const selectedEnabled = enabledProviders.includes(selectedProvider);
|
|
60
|
+
const bochaDocsUrl = search?.providers.bocha.docsUrl ?? meta?.search.find((provider) => provider.name === 'bocha')?.docsUrl ?? 'https://open.bocha.cn';
|
|
61
|
+
const activationButtonLabel = selectedEnabled
|
|
62
|
+
? t('searchProviderDeactivate')
|
|
63
|
+
: t('searchProviderActivate');
|
|
64
|
+
|
|
65
|
+
const buildSearchPayload = (
|
|
66
|
+
nextEnabledProviders: SearchProviderName[] = enabledProviders,
|
|
67
|
+
nextActiveProvider: SearchProviderName = activeProvider
|
|
68
|
+
): SearchConfigUpdate => ({
|
|
69
|
+
provider: nextActiveProvider,
|
|
70
|
+
enabledProviders: nextEnabledProviders,
|
|
71
|
+
defaults: {
|
|
72
|
+
maxResults: Number(maxResults) || 10
|
|
73
|
+
},
|
|
74
|
+
providers: {
|
|
75
|
+
bocha: {
|
|
76
|
+
apiKey: bochaApiKey || undefined,
|
|
77
|
+
baseUrl: bochaBaseUrl,
|
|
78
|
+
summary: bochaSummary,
|
|
79
|
+
freshness: bochaFreshness
|
|
80
|
+
},
|
|
81
|
+
brave: {
|
|
82
|
+
apiKey: braveApiKey || undefined,
|
|
83
|
+
baseUrl: braveBaseUrl
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const handleToggleEnabled = () => {
|
|
89
|
+
const nextEnabledProviders = selectedEnabled
|
|
90
|
+
? enabledProviders.filter((provider) => provider !== selectedProvider)
|
|
91
|
+
: [...enabledProviders, selectedProvider];
|
|
92
|
+
setEnabledProviders(nextEnabledProviders);
|
|
93
|
+
updateSearch.mutate({
|
|
94
|
+
data: buildSearchPayload(nextEnabledProviders)
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const handleActiveProviderChange = (value: string) => {
|
|
99
|
+
setActiveProvider(value as SearchProviderName);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleSubmit = (event: React.FormEvent) => {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
updateSearch.mutate({
|
|
105
|
+
data: buildSearchPayload()
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (!search || providers.length === 0) {
|
|
110
|
+
return <div className="p-8">{t('loading')}</div>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<PageLayout>
|
|
115
|
+
<PageHeader title={t('searchPageTitle')} description={t('searchPageDescription')} />
|
|
116
|
+
|
|
117
|
+
<div className={CONFIG_SPLIT_GRID_CLASS}>
|
|
118
|
+
<section className={CONFIG_SIDEBAR_CARD_CLASS}>
|
|
119
|
+
<div className="border-b border-gray-100 px-4 py-4">
|
|
120
|
+
<p className="text-xs font-semibold uppercase tracking-[0.16em] text-gray-500">{t('searchChannels')}</p>
|
|
121
|
+
</div>
|
|
122
|
+
<div className="min-h-0 flex-1 space-y-2 overflow-y-auto p-3">
|
|
123
|
+
{providers.map((provider) => {
|
|
124
|
+
const providerView = search.providers[provider.name];
|
|
125
|
+
const isEnabled = enabledProviders.includes(provider.name);
|
|
126
|
+
const isSelected = selectedProvider === provider.name;
|
|
127
|
+
return (
|
|
128
|
+
<button
|
|
129
|
+
key={provider.name}
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={() => setSelectedProvider(provider.name)}
|
|
132
|
+
className={cn(
|
|
133
|
+
'w-full rounded-xl border p-3 text-left transition-all',
|
|
134
|
+
isSelected
|
|
135
|
+
? 'border-primary/30 bg-primary-50/40 shadow-sm'
|
|
136
|
+
: 'border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70'
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
<div className="flex items-start justify-between gap-3">
|
|
140
|
+
<div className="min-w-0">
|
|
141
|
+
<p className="truncate text-sm font-semibold text-gray-900">{provider.displayName}</p>
|
|
142
|
+
<p className="line-clamp-2 text-[11px] text-gray-500">
|
|
143
|
+
{provider.name === 'bocha' ? t('searchProviderBochaDescription') : t('searchProviderBraveDescription')}
|
|
144
|
+
</p>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="flex flex-col items-end gap-1">
|
|
147
|
+
<span className="rounded-full bg-gray-100 px-2 py-0.5 text-[11px] font-medium text-gray-600">
|
|
148
|
+
{providerView.apiKeySet ? t('searchStatusConfigured') : t('searchStatusNeedsSetup')}
|
|
149
|
+
</span>
|
|
150
|
+
{isEnabled ? (
|
|
151
|
+
<span className="rounded-full bg-emerald-50 px-2 py-0.5 text-[11px] font-medium text-emerald-700">
|
|
152
|
+
{t('searchProviderActivated')}
|
|
153
|
+
</span>
|
|
154
|
+
) : null}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</button>
|
|
158
|
+
);
|
|
159
|
+
})}
|
|
160
|
+
</div>
|
|
161
|
+
</section>
|
|
162
|
+
|
|
163
|
+
<form onSubmit={handleSubmit} className={cn(CONFIG_DETAIL_CARD_CLASS, 'p-6')}>
|
|
164
|
+
{!selectedMeta || !selectedView ? (
|
|
165
|
+
<div className="flex h-full items-center justify-center text-sm text-gray-500">{t('searchNoProviderSelected')}</div>
|
|
166
|
+
) : (
|
|
167
|
+
<div className="space-y-6 overflow-y-auto">
|
|
168
|
+
<div className="flex items-start justify-between gap-4">
|
|
169
|
+
<div className="flex items-center gap-3">
|
|
170
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary text-white">
|
|
171
|
+
<SearchIcon className="h-5 w-5" />
|
|
172
|
+
</div>
|
|
173
|
+
<div>
|
|
174
|
+
<h3 className="text-lg font-semibold text-gray-900">{selectedMeta.displayName}</h3>
|
|
175
|
+
<p className="text-sm text-gray-500">{selectedMeta.description}</p>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<Button
|
|
179
|
+
type="button"
|
|
180
|
+
variant={selectedEnabled ? 'secondary' : 'outline'}
|
|
181
|
+
className="rounded-xl"
|
|
182
|
+
onClick={handleToggleEnabled}
|
|
183
|
+
>
|
|
184
|
+
{activationButtonLabel}
|
|
185
|
+
</Button>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
189
|
+
<div className="space-y-2">
|
|
190
|
+
<Label>{t('searchActiveProvider')}</Label>
|
|
191
|
+
<Select value={activeProvider} onValueChange={handleActiveProviderChange}>
|
|
192
|
+
<SelectTrigger className="rounded-xl">
|
|
193
|
+
<SelectValue />
|
|
194
|
+
</SelectTrigger>
|
|
195
|
+
<SelectContent>
|
|
196
|
+
{providers.map((provider) => (
|
|
197
|
+
<SelectItem key={provider.name} value={provider.name}>{provider.displayName}</SelectItem>
|
|
198
|
+
))}
|
|
199
|
+
</SelectContent>
|
|
200
|
+
</Select>
|
|
201
|
+
</div>
|
|
202
|
+
|
|
203
|
+
<div className="space-y-2">
|
|
204
|
+
<Label>{t('searchDefaultMaxResults')}</Label>
|
|
205
|
+
<Input
|
|
206
|
+
value={maxResults}
|
|
207
|
+
onChange={(event) => setMaxResults(event.target.value)}
|
|
208
|
+
inputMode="numeric"
|
|
209
|
+
className="rounded-xl"
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{selectedProvider === 'bocha' ? (
|
|
215
|
+
<div className="space-y-4">
|
|
216
|
+
<div className="space-y-2">
|
|
217
|
+
<Label>{t('apiKey')}</Label>
|
|
218
|
+
<Input
|
|
219
|
+
type="password"
|
|
220
|
+
value={bochaApiKey}
|
|
221
|
+
onChange={(event) => setBochaApiKey(event.target.value)}
|
|
222
|
+
placeholder={search.providers.bocha.apiKeyMasked || t('enterApiKey')}
|
|
223
|
+
className="rounded-xl"
|
|
224
|
+
/>
|
|
225
|
+
</div>
|
|
226
|
+
<div className="space-y-2">
|
|
227
|
+
<Label>{t('searchProviderBaseUrl')}</Label>
|
|
228
|
+
<Input value={bochaBaseUrl} onChange={(event) => setBochaBaseUrl(event.target.value)} className="rounded-xl" />
|
|
229
|
+
</div>
|
|
230
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
231
|
+
<div className="space-y-2">
|
|
232
|
+
<Label>{t('searchProviderSummary')}</Label>
|
|
233
|
+
<Select value={bochaSummary ? 'true' : 'false'} onValueChange={(value) => setBochaSummary(value === 'true')}>
|
|
234
|
+
<SelectTrigger className="rounded-xl">
|
|
235
|
+
<SelectValue />
|
|
236
|
+
</SelectTrigger>
|
|
237
|
+
<SelectContent>
|
|
238
|
+
<SelectItem value="true">{t('enabled')}</SelectItem>
|
|
239
|
+
<SelectItem value="false">{t('disabled')}</SelectItem>
|
|
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>
|
|
266
|
+
) : (
|
|
267
|
+
<div className="space-y-4">
|
|
268
|
+
<div className="space-y-2">
|
|
269
|
+
<Label>{t('apiKey')}</Label>
|
|
270
|
+
<Input
|
|
271
|
+
type="password"
|
|
272
|
+
value={braveApiKey}
|
|
273
|
+
onChange={(event) => setBraveApiKey(event.target.value)}
|
|
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>
|
|
283
|
+
)}
|
|
284
|
+
|
|
285
|
+
<div className="flex justify-end">
|
|
286
|
+
<Button type="submit" disabled={updateSearch.isPending}>
|
|
287
|
+
<KeyRound className="mr-2 h-4 w-4" />
|
|
288
|
+
{updateSearch.isPending ? t('saving') : t('saveChanges')}
|
|
289
|
+
</Button>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
</form>
|
|
294
|
+
</div>
|
|
295
|
+
</PageLayout>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cn } from '@/lib/utils';
|
|
2
2
|
import { LANGUAGE_OPTIONS, t, type I18nLanguage } from '@/lib/i18n';
|
|
3
3
|
import { THEME_OPTIONS, type UiTheme } from '@/lib/theme';
|
|
4
|
-
import { Cpu, GitBranch, History, MessageCircle, MessageSquare, Sparkles, BookOpen, Plug, BrainCircuit, AlarmClock, Languages, Palette, KeyRound, Settings, ArrowLeft } from 'lucide-react';
|
|
4
|
+
import { Cpu, GitBranch, History, MessageCircle, MessageSquare, Sparkles, BookOpen, Plug, BrainCircuit, AlarmClock, Languages, Palette, KeyRound, Settings, ArrowLeft, Search } from 'lucide-react';
|
|
5
5
|
import { NavLink } from 'react-router-dom';
|
|
6
6
|
import { useDocBrowser } from '@/components/doc-browser';
|
|
7
7
|
import { BrandHeader } from '@/components/common/BrandHeader';
|
|
@@ -67,6 +67,11 @@ export function Sidebar({ mode }: SidebarProps) {
|
|
|
67
67
|
label: t('providers'),
|
|
68
68
|
icon: Sparkles,
|
|
69
69
|
},
|
|
70
|
+
{
|
|
71
|
+
target: '/search',
|
|
72
|
+
label: t('searchChannels'),
|
|
73
|
+
icon: Search,
|
|
74
|
+
},
|
|
70
75
|
{
|
|
71
76
|
target: '/channels',
|
|
72
77
|
label: t('channels'),
|
package/src/hooks/useConfig.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
fetchConfigMeta,
|
|
6
6
|
fetchConfigSchema,
|
|
7
7
|
updateModel,
|
|
8
|
+
updateSearch,
|
|
8
9
|
createProvider,
|
|
9
10
|
deleteProvider,
|
|
10
11
|
updateProvider,
|
|
@@ -81,6 +82,22 @@ export function useUpdateModel() {
|
|
|
81
82
|
});
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
export function useUpdateSearch() {
|
|
86
|
+
const queryClient = useQueryClient();
|
|
87
|
+
|
|
88
|
+
return useMutation({
|
|
89
|
+
mutationFn: ({ data }: { data: Parameters<typeof updateSearch>[0] }) => updateSearch(data),
|
|
90
|
+
onSuccess: () => {
|
|
91
|
+
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
92
|
+
queryClient.invalidateQueries({ queryKey: ['config-meta'] });
|
|
93
|
+
toast.success(t('configSavedApplied'));
|
|
94
|
+
},
|
|
95
|
+
onError: (error: Error) => {
|
|
96
|
+
toast.error(t('configSaveFailed') + ': ' + error.message);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
84
101
|
export function useUpdateProvider() {
|
|
85
102
|
const queryClient = useQueryClient();
|
|
86
103
|
|
package/src/lib/i18n.ts
CHANGED
|
@@ -125,6 +125,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
125
125
|
// Navigation
|
|
126
126
|
chat: { zh: '对话', en: 'Chat' },
|
|
127
127
|
model: { zh: '模型', en: 'Model' },
|
|
128
|
+
searchChannels: { zh: '搜索渠道', en: 'Search Channels' },
|
|
128
129
|
providers: { zh: '提供商', en: 'Providers' },
|
|
129
130
|
channels: { zh: '渠道', en: 'Channels' },
|
|
130
131
|
cron: { zh: '定时任务', en: 'Cron Jobs' },
|
|
@@ -182,6 +183,27 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
182
183
|
saveChanges: { zh: '保存变更', en: 'Save Changes' },
|
|
183
184
|
|
|
184
185
|
// Provider
|
|
186
|
+
searchPageTitle: { zh: '搜索渠道', en: 'Search Channels' },
|
|
187
|
+
searchPageDescription: { zh: '配置网页搜索提供商', en: 'Configure web search providers.' },
|
|
188
|
+
searchActiveProvider: { zh: '当前搜索提供商', en: 'Active Search Provider' },
|
|
189
|
+
searchDefaultMaxResults: { zh: '默认返回条数', en: 'Default Result Count' },
|
|
190
|
+
searchProviderSummary: { zh: '结果摘要', en: 'Result Summary' },
|
|
191
|
+
searchProviderFreshness: { zh: '时间范围', en: 'Freshness' },
|
|
192
|
+
searchProviderBaseUrl: { zh: '接口地址', en: 'API Base URL' },
|
|
193
|
+
searchProviderOpenDocs: { zh: '获取博查 API', en: 'Get Bocha API' },
|
|
194
|
+
searchProviderActivate: { zh: '激活', en: 'Activate' },
|
|
195
|
+
searchProviderActivated: { zh: '已激活', en: 'Activated' },
|
|
196
|
+
searchProviderDeactivate: { zh: '取消激活', en: 'Deactivate' },
|
|
197
|
+
searchProviderBochaDescription: { zh: '更适合中国大陆用户的 AI 搜索。', en: 'AI-ready search that works better for mainland China users.' },
|
|
198
|
+
searchProviderBraveDescription: { zh: '保留 Brave 作为可选 provider。', en: 'Keep Brave as an optional provider.' },
|
|
199
|
+
searchStatusConfigured: { zh: '已配置', en: 'Configured' },
|
|
200
|
+
searchStatusNeedsSetup: { zh: '待配置', en: 'Needs Setup' },
|
|
201
|
+
searchFreshnessNoLimit: { zh: '不限', en: 'No Limit' },
|
|
202
|
+
searchFreshnessOneDay: { zh: '一天内', en: 'One Day' },
|
|
203
|
+
searchFreshnessOneWeek: { zh: '一周内', en: 'One Week' },
|
|
204
|
+
searchFreshnessOneMonth: { zh: '一个月内', en: 'One Month' },
|
|
205
|
+
searchFreshnessOneYear: { zh: '一年内', en: 'One Year' },
|
|
206
|
+
searchNoProviderSelected: { zh: '请选择左侧搜索 provider', en: 'Select a search provider from the left.' },
|
|
185
207
|
providersPageTitle: { zh: 'AI 提供商', en: 'AI Providers' },
|
|
186
208
|
providersPageDescription: { zh: '在一个页面内完成提供商切换、配置与保存。', en: 'Switch, configure, and save providers in one continuous workspace.' },
|
|
187
209
|
providersLoading: { zh: '加载中...', en: 'Loading...' },
|
|
@@ -250,6 +272,14 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
250
272
|
},
|
|
251
273
|
providerModelsEmptyShort: { zh: '暂无可用模型', en: 'No models available' },
|
|
252
274
|
providerAddFirstModel: { zh: '添加第一个模型', en: 'Add first model' },
|
|
275
|
+
providerModelThinkingTitle: { zh: '思考档位能力', en: 'Thinking Capability' },
|
|
276
|
+
providerModelThinkingHint: {
|
|
277
|
+
zh: '为该模型声明可切换的思考档位,聊天会话将按这里的能力展示下拉。',
|
|
278
|
+
en: 'Declare supported thinking levels for this model. Chat sessions will show the selector accordingly.'
|
|
279
|
+
},
|
|
280
|
+
providerModelThinkingDefault: { zh: '默认思考档位', en: 'Default Thinking Level' },
|
|
281
|
+
providerModelThinkingDefaultNone: { zh: '无默认(回落 off)', en: 'No default (fallback off)' },
|
|
282
|
+
providerModelThinkingNoSupported: { zh: '请先至少选择一个支持档位。', en: 'Select at least one supported level first.' },
|
|
253
283
|
providerDisplayNameHelpShort: { zh: '便于区分多个自定义提供商', en: 'Helps distinguish multiple custom providers' },
|
|
254
284
|
providerApiBaseHelpShort: { zh: '一般只需填写域名,系统自动补全路径', en: 'Usually just the domain; path auto-appended' },
|
|
255
285
|
providerExtraHeadersHelpShort: { zh: '可选,用于自定义鉴权等场景', en: 'Optional, for custom auth etc.' },
|
|
@@ -506,6 +536,13 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
506
536
|
chatSelectAgent: { zh: '选择 Agent', en: 'Select Agent' },
|
|
507
537
|
chatModelLabel: { zh: '对话模型', en: 'Chat Model' },
|
|
508
538
|
chatSelectModel: { zh: '选择模型', en: 'Select model' },
|
|
539
|
+
chatThinkingLevelOff: { zh: '思考关闭', en: 'Thinking Off' },
|
|
540
|
+
chatThinkingLevelMinimal: { zh: '思考 Minimal', en: 'Thinking Minimal' },
|
|
541
|
+
chatThinkingLevelLow: { zh: '思考 Low', en: 'Thinking Low' },
|
|
542
|
+
chatThinkingLevelMedium: { zh: '思考 Medium', en: 'Thinking Medium' },
|
|
543
|
+
chatThinkingLevelHigh: { zh: '思考 High', en: 'Thinking High' },
|
|
544
|
+
chatThinkingLevelAdaptive: { zh: '思考 Adaptive', en: 'Thinking Adaptive' },
|
|
545
|
+
chatThinkingLevelXhigh: { zh: '思考 XHigh', en: 'Thinking XHigh' },
|
|
509
546
|
chatSessionTypeLabel: { zh: '会话类型', en: 'Session Type' },
|
|
510
547
|
chatSessionTypeNative: { zh: '原生', en: 'Native' },
|
|
511
548
|
chatSessionTypeCodex: { zh: 'Codex', en: 'Codex' },
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import type { ConfigMetaView, ConfigView, ProviderConfigView } from '@/api/types';
|
|
1
|
+
import type { ConfigMetaView, ConfigView, ProviderConfigView, ThinkingLevel } from '@/api/types';
|
|
2
|
+
|
|
3
|
+
const THINKING_LEVELS: ThinkingLevel[] = ['off', 'minimal', 'low', 'medium', 'high', 'adaptive', 'xhigh'];
|
|
4
|
+
const THINKING_LEVEL_SET = new Set<string>(THINKING_LEVELS);
|
|
5
|
+
|
|
6
|
+
export type ModelThinkingCapability = {
|
|
7
|
+
supported: ThinkingLevel[];
|
|
8
|
+
default?: ThinkingLevel | null;
|
|
9
|
+
};
|
|
2
10
|
|
|
3
11
|
export type ProviderModelCatalogItem = {
|
|
4
12
|
name: string;
|
|
@@ -6,6 +14,7 @@ export type ProviderModelCatalogItem = {
|
|
|
6
14
|
prefix: string;
|
|
7
15
|
aliases: string[];
|
|
8
16
|
models: string[];
|
|
17
|
+
modelThinking: Record<string, ModelThinkingCapability>;
|
|
9
18
|
configured: boolean;
|
|
10
19
|
};
|
|
11
20
|
|
|
@@ -59,9 +68,73 @@ export function composeProviderModel(prefix: string, localModel: string): string
|
|
|
59
68
|
return `${normalizedPrefix}/${normalizedModel}`;
|
|
60
69
|
}
|
|
61
70
|
|
|
71
|
+
function parseThinkingLevel(value: unknown): ThinkingLevel | null {
|
|
72
|
+
if (typeof value !== 'string') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const normalized = value.trim().toLowerCase();
|
|
76
|
+
if (!normalized) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return THINKING_LEVEL_SET.has(normalized) ? (normalized as ThinkingLevel) : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizeThinkingLevels(values: unknown): ThinkingLevel[] {
|
|
83
|
+
if (!Array.isArray(values)) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const deduped: ThinkingLevel[] = [];
|
|
87
|
+
for (const value of values) {
|
|
88
|
+
const level = parseThinkingLevel(value);
|
|
89
|
+
if (!level || deduped.includes(level)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
deduped.push(level);
|
|
93
|
+
}
|
|
94
|
+
return deduped;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function normalizeModelThinkingMap(
|
|
98
|
+
input: ProviderConfigView['modelThinking'],
|
|
99
|
+
aliases: string[]
|
|
100
|
+
): Record<string, ModelThinkingCapability> {
|
|
101
|
+
if (!input) {
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
const normalized: Record<string, ModelThinkingCapability> = {};
|
|
105
|
+
for (const [rawModel, rawValue] of Object.entries(input)) {
|
|
106
|
+
const localModel = toProviderLocalModel(rawModel, aliases);
|
|
107
|
+
if (!localModel) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const supported = normalizeThinkingLevels(rawValue?.supported);
|
|
111
|
+
if (supported.length === 0) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const defaultLevel = parseThinkingLevel(rawValue?.default);
|
|
115
|
+
normalized[localModel] =
|
|
116
|
+
defaultLevel && supported.includes(defaultLevel)
|
|
117
|
+
? { supported, default: defaultLevel }
|
|
118
|
+
: { supported };
|
|
119
|
+
}
|
|
120
|
+
return normalized;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function resolveModelThinkingCapability(
|
|
124
|
+
map: Record<string, ModelThinkingCapability>,
|
|
125
|
+
model: string,
|
|
126
|
+
aliases: string[]
|
|
127
|
+
): ModelThinkingCapability | null {
|
|
128
|
+
const localModel = toProviderLocalModel(model, aliases);
|
|
129
|
+
if (!localModel) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return map[localModel] ?? null;
|
|
133
|
+
}
|
|
134
|
+
|
|
62
135
|
export function findProviderByModel(
|
|
63
136
|
model: string,
|
|
64
|
-
providerCatalog: Array<{ name: string; aliases: string[] }>
|
|
137
|
+
providerCatalog: Array<{ name: string; aliases: string[]; models?: string[] }>
|
|
65
138
|
): string | null {
|
|
66
139
|
const trimmed = model.trim();
|
|
67
140
|
if (!trimmed) {
|
|
@@ -81,7 +154,20 @@ export function findProviderByModel(
|
|
|
81
154
|
}
|
|
82
155
|
}
|
|
83
156
|
}
|
|
84
|
-
|
|
157
|
+
if (bestMatch) {
|
|
158
|
+
return bestMatch.name;
|
|
159
|
+
}
|
|
160
|
+
for (const provider of providerCatalog) {
|
|
161
|
+
const normalizedModel = toProviderLocalModel(trimmed, provider.aliases);
|
|
162
|
+
if (!normalizedModel) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const models = normalizeStringList(provider.models ?? []);
|
|
166
|
+
if (models.some((modelId) => modelId === normalizedModel)) {
|
|
167
|
+
return provider.name;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
85
171
|
}
|
|
86
172
|
|
|
87
173
|
function isProviderConfigured(provider: ProviderConfigView | undefined): boolean {
|
|
@@ -108,6 +194,7 @@ export function buildProviderModelCatalog(params: {
|
|
|
108
194
|
(providerConfig?.models ?? []).map((model) => toProviderLocalModel(model, aliases))
|
|
109
195
|
);
|
|
110
196
|
const models = normalizeStringList([...defaultModels, ...customModels]);
|
|
197
|
+
const modelThinking = normalizeModelThinkingMap(providerConfig?.modelThinking, aliases);
|
|
111
198
|
const configDisplayName = providerConfig?.displayName?.trim();
|
|
112
199
|
const configured = isProviderConfigured(providerConfig);
|
|
113
200
|
|
|
@@ -117,6 +204,7 @@ export function buildProviderModelCatalog(params: {
|
|
|
117
204
|
prefix,
|
|
118
205
|
aliases,
|
|
119
206
|
models,
|
|
207
|
+
modelThinking,
|
|
120
208
|
configured
|
|
121
209
|
} satisfies ProviderModelCatalogItem;
|
|
122
210
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as v,j as a,aE as Z,z as ee,d as T,K as ae,ad as te,aP as se,aQ as ne,aR as le,w as re,a1 as oe,aA as ce,q as ie}from"./vendor-TlME1INH.js";import{t as e,c as I,K as me,u as q,a as $,b as K,N as pe,O as de,S as be,e as ue,f as xe,g as ye,h as ge}from"./index-Cn6_2To7.js";import{B as U,P as he,a as fe}from"./page-layout-DO8BlScF.js";import{I as A}from"./input-oBvxsnV9.js";import{L as we}from"./label-C7F8lMpQ.js";import{S as ve}from"./switch-C6a5GyZB.js";import{C as je,a as ke,L as H,S as J,c as Se,b as Ce}from"./LogoBadge-CL_8ZPXU.js";import{h as O}from"./config-hints-CApS3K_7.js";import{T as Ne}from"./tabs-custom-BatFap5k.js";function Pe({value:t,onChange:m,className:i,placeholder:r=""}){const[o,u]=v.useState(""),d=x=>{x.key==="Enter"&&o.trim()?(x.preventDefault(),m([...t,o.trim()]),u("")):x.key==="Backspace"&&!o&&t.length>0&&m(t.slice(0,-1))},g=x=>{m(t.filter((j,h)=>h!==x))};return a.jsxs("div",{className:I("flex flex-wrap gap-2 p-2 border rounded-md min-h-[42px]",i),children:[t.map((x,j)=>a.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-primary text-primary-foreground rounded text-sm",children:[x,a.jsx("button",{type:"button",onClick:()=>g(j),className:"hover:text-red-300 transition-colors",children:a.jsx(Z,{className:"h-3 w-3"})})]},j)),a.jsx("input",{type:"text",value:o,onChange:x=>u(x.target.value),onKeyDown:d,className:"flex-1 outline-none min-w-[100px] bg-transparent text-sm",placeholder:r||e("enterTag")})]})}function z(t){var r,o;const m=me();return((r=t.tutorialUrls)==null?void 0:r[m])||((o=t.tutorialUrls)==null?void 0:o.default)||t.tutorialUrl}const Ie={telegram:"telegram.svg",slack:"slack.svg",discord:"discord.svg",whatsapp:"whatsapp.svg",qq:"qq.svg",feishu:"feishu.svg",dingtalk:"dingtalk.svg",wecom:"wecom.svg",mochat:"mochat.svg",email:"email.svg"};function Fe(t,m){const i=m.toLowerCase(),r=t[i];return r?`/logos/${r}`:null}function Y(t){return Fe(Ie,t)}const R=[{value:"pairing",label:"pairing"},{value:"allowlist",label:"allowlist"},{value:"open",label:"open"},{value:"disabled",label:"disabled"}],B=[{value:"open",label:"open"},{value:"allowlist",label:"allowlist"},{value:"disabled",label:"disabled"}],Te=[{value:"off",label:"off"},{value:"partial",label:"partial"},{value:"block",label:"block"},{value:"progress",label:"progress"}],Ae=t=>t.includes("token")||t.includes("secret")||t.includes("password")?a.jsx(ae,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("url")||t.includes("host")?a.jsx(te,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("email")||t.includes("mail")?a.jsx(se,{className:"h-3.5 w-3.5 text-gray-500"}):t.includes("id")||t.includes("from")?a.jsx(ne,{className:"h-3.5 w-3.5 text-gray-500"}):t==="enabled"||t==="consentGranted"?a.jsx(le,{className:"h-3.5 w-3.5 text-gray-500"}):a.jsx(re,{className:"h-3.5 w-3.5 text-gray-500"});function G(){return{telegram:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"proxy",type:"text",label:e("proxy")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:R},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:B},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],discord:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"token",type:"password",label:e("botToken")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"allowFrom",type:"tags",label:e("allowFrom")},{name:"gatewayUrl",type:"text",label:e("gatewayUrl")},{name:"intents",type:"number",label:e("intents")},{name:"proxy",type:"text",label:e("proxy")},{name:"mediaMaxMb",type:"number",label:e("attachmentMaxSizeMb")},{name:"streaming",type:"select",label:e("streamingMode"),options:Te},{name:"draftChunk",type:"json",label:e("draftChunkingJson")},{name:"textChunkLimit",type:"number",label:e("textChunkLimit")},{name:"accountId",type:"text",label:e("accountId")},{name:"dmPolicy",type:"select",label:e("dmPolicy"),options:R},{name:"groupPolicy",type:"select",label:e("groupPolicy"),options:B},{name:"groupAllowFrom",type:"tags",label:e("groupAllowFrom")},{name:"requireMention",type:"boolean",label:e("requireMention")},{name:"mentionPatterns",type:"tags",label:e("mentionPatterns")},{name:"groups",type:"json",label:e("groupRulesJson")}],whatsapp:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"bridgeUrl",type:"text",label:e("bridgeUrl")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],feishu:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"appSecret",type:"password",label:e("appSecret")},{name:"encryptKey",type:"password",label:e("encryptKey")},{name:"verificationToken",type:"password",label:e("verificationToken")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],dingtalk:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"clientId",type:"text",label:e("clientId")},{name:"clientSecret",type:"password",label:e("clientSecret")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],wecom:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"corpId",type:"text",label:e("corpId")},{name:"agentId",type:"text",label:e("agentId")},{name:"secret",type:"password",label:e("secret")},{name:"token",type:"password",label:e("token")},{name:"callbackPort",type:"number",label:e("callbackPort")},{name:"callbackPath",type:"text",label:e("callbackPath")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],slack:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"mode",type:"text",label:e("mode")},{name:"webhookPath",type:"text",label:e("webhookPath")},{name:"allowBots",type:"boolean",label:e("allowBotMessages")},{name:"botToken",type:"password",label:e("botToken")},{name:"appToken",type:"password",label:e("appToken")}],email:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"consentGranted",type:"boolean",label:e("consentGranted")},{name:"imapHost",type:"text",label:e("imapHost")},{name:"imapPort",type:"number",label:e("imapPort")},{name:"imapUsername",type:"text",label:e("imapUsername")},{name:"imapPassword",type:"password",label:e("imapPassword")},{name:"fromAddress",type:"email",label:e("fromAddress")}],mochat:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"baseUrl",type:"text",label:e("baseUrl")},{name:"clawToken",type:"password",label:e("clawToken")},{name:"agentUserId",type:"text",label:e("agentUserId")},{name:"allowFrom",type:"tags",label:e("allowFrom")}],qq:[{name:"enabled",type:"boolean",label:e("enabled")},{name:"appId",type:"text",label:e("appId")},{name:"secret",type:"password",label:e("appSecret")},{name:"markdownSupport",type:"boolean",label:e("markdownSupport")},{name:"allowFrom",type:"tags",label:e("allowFrom")}]}}function D(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function Q(t,m){const i={...t};for(const[r,o]of Object.entries(m)){const u=i[r];if(D(u)&&D(o)){i[r]=Q(u,o);continue}i[r]=o}return i}function De(t,m){const i=t.split("."),r={};let o=r;for(let u=0;u<i.length-1;u+=1){const d=i[u];o[d]={},o=o[d]}return o[i[i.length-1]]=m,r}function Le({channelName:t}){var _,E;const{data:m}=q(),{data:i}=$(),{data:r}=K(),o=pe(),u=de(),[d,g]=v.useState({}),[x,j]=v.useState({}),[h,f]=v.useState(null),k=t?m==null?void 0:m.channels[t]:null,w=t?G()[t]??[]:[],c=r==null?void 0:r.uiHints,p=t?`channels.${t}`:null,S=((_=r==null?void 0:r.actions)==null?void 0:_.filter(s=>s.scope===p))??[],C=t&&(((E=O(`channels.${t}`,c))==null?void 0:E.label)??t),P=i==null?void 0:i.channels.find(s=>s.name===t),F=P?z(P):void 0;v.useEffect(()=>{if(k){g({...k});const s={};(t?G()[t]??[]:[]).filter(l=>l.type==="json").forEach(l=>{const y=k[l.name];s[l.name]=JSON.stringify(y??{},null,2)}),j(s)}else g({}),j({})},[k,t]);const N=(s,n)=>{g(l=>({...l,[s]:n}))},L=s=>{if(s.preventDefault(),!t)return;const n={...d};for(const l of w){if(l.type!=="password")continue;const y=n[l.name];(typeof y!="string"||y.length===0)&&delete n[l.name]}for(const l of w){if(l.type!=="json")continue;const y=x[l.name]??"";try{n[l.name]=y.trim()?JSON.parse(y):{}}catch{T.error(`${e("invalidJson")}: ${l.name}`);return}}o.mutate({channel:t,data:n})},V=s=>{if(!s||!t)return;const n=s.channels;if(!D(n))return;const l=n[t];D(l)&&g(y=>Q(y,l))},W=async s=>{if(!(!t||!p)){f(s.id);try{let n={...d};s.saveBeforeRun&&(n={...n,...s.savePatch??{}},g(n),await o.mutateAsync({channel:t,data:n}));const l=await u.mutateAsync({actionId:s.id,data:{scope:p,draftConfig:De(p,n)}});V(l.patch),l.ok?T.success(l.message||e("success")):T.error(l.message||e("error"))}catch(n){const l=n instanceof Error?n.message:String(n);T.error(`${e("error")}: ${l}`)}finally{f(null)}}};if(!t||!P||!k)return a.jsx("div",{className:je,children:a.jsxs("div",{children:[a.jsx("h3",{className:"text-base font-semibold text-gray-900",children:e("channelsSelectTitle")}),a.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsSelectDescription")})]})});const M=!!k.enabled;return a.jsxs("div",{className:ke,children:[a.jsx("div",{className:"border-b border-gray-100 px-6 py-5",children:a.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3",children:[a.jsxs("div",{className:"min-w-0",children:[a.jsxs("div",{className:"flex items-center gap-3",children:[a.jsx(H,{name:t,src:Y(t),className:I("h-9 w-9 rounded-lg border",M?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:a.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:t[0]})}),a.jsx("h3",{className:"truncate text-lg font-semibold text-gray-900 capitalize",children:C})]}),a.jsx("p",{className:"mt-2 text-sm text-gray-500",children:e("channelsFormDescription")}),F&&a.jsxs("a",{href:F,className:"mt-2 inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover",children:[a.jsx(ee,{className:"h-3.5 w-3.5"}),e("channelsGuideTitle")]})]}),a.jsx(J,{status:M?"active":"inactive",label:M?e("statusActive"):e("statusInactive")})]})}),a.jsxs("form",{onSubmit:L,className:"flex min-h-0 flex-1 flex-col",children:[a.jsx("div",{className:"min-h-0 flex-1 space-y-6 overflow-y-auto overscroll-contain px-6 py-5",children:w.map(s=>{const n=t?O(`channels.${t}.${s.name}`,c):void 0,l=(n==null?void 0:n.label)??s.label,y=n==null?void 0:n.placeholder;return a.jsxs("div",{className:"space-y-2.5",children:[a.jsxs(we,{htmlFor:s.name,className:"flex items-center gap-2 text-sm font-medium text-gray-900",children:[Ae(s.name),l]}),s.type==="boolean"&&a.jsxs("div",{className:"flex items-center justify-between rounded-xl bg-gray-50 p-3",children:[a.jsx("span",{className:"text-sm text-gray-500",children:d[s.name]?e("enabled"):e("disabled")}),a.jsx(ve,{id:s.name,checked:d[s.name]||!1,onCheckedChange:b=>N(s.name,b),className:"data-[state=checked]:bg-emerald-500"})]}),(s.type==="text"||s.type==="email")&&a.jsx(A,{id:s.name,type:s.type,value:d[s.name]||"",onChange:b=>N(s.name,b.target.value),placeholder:y,className:"rounded-xl"}),s.type==="password"&&a.jsx(A,{id:s.name,type:"password",value:d[s.name]||"",onChange:b=>N(s.name,b.target.value),placeholder:y??e("leaveBlankToKeepUnchanged"),className:"rounded-xl"}),s.type==="number"&&a.jsx(A,{id:s.name,type:"number",value:d[s.name]||0,onChange:b=>N(s.name,parseInt(b.target.value,10)||0),placeholder:y,className:"rounded-xl"}),s.type==="tags"&&a.jsx(Pe,{value:d[s.name]||[],onChange:b=>N(s.name,b)}),s.type==="select"&&a.jsxs(be,{value:d[s.name]||"",onValueChange:b=>N(s.name,b),children:[a.jsx(ue,{className:"rounded-xl",children:a.jsx(xe,{})}),a.jsx(ye,{children:(s.options??[]).map(b=>a.jsx(ge,{value:b.value,children:b.label},b.value))})]}),s.type==="json"&&a.jsx("textarea",{id:s.name,value:x[s.name]??"{}",onChange:b=>j(X=>({...X,[s.name]:b.target.value})),className:"min-h-[120px] w-full resize-none rounded-lg border border-gray-200 bg-white px-3 py-2 text-xs font-mono"})]},s.name)})}),a.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-3 border-t border-gray-100 px-6 py-4",children:[a.jsx("div",{className:"flex flex-wrap items-center gap-2",children:S.filter(s=>s.trigger==="manual").map(s=>a.jsx(U,{type:"button",onClick:()=>W(s),disabled:o.isPending||!!h,variant:"secondary",children:h===s.id?e("connecting"):s.title},s.id))}),a.jsx(U,{type:"submit",disabled:o.isPending||!!h,children:o.isPending?e("saving"):e("save")})]})]})]})}const Me={telegram:"channelDescTelegram",slack:"channelDescSlack",email:"channelDescEmail",webhook:"channelDescWebhook",discord:"channelDescDiscord",feishu:"channelDescFeishu"};function Ke(){const{data:t}=q(),{data:m}=$(),{data:i}=K(),[r,o]=v.useState("enabled"),[u,d]=v.useState(),[g,x]=v.useState(""),j=i==null?void 0:i.uiHints,h=m==null?void 0:m.channels,f=t==null?void 0:t.channels,k=[{id:"enabled",label:e("channelsTabEnabled"),count:(h??[]).filter(c=>{var p;return(p=f==null?void 0:f[c.name])==null?void 0:p.enabled}).length},{id:"all",label:e("channelsTabAll"),count:(h??[]).length}],w=v.useMemo(()=>{const c=g.trim().toLowerCase();return(h??[]).filter(p=>{var C;const S=((C=f==null?void 0:f[p.name])==null?void 0:C.enabled)||!1;return r==="enabled"?S:!0}).filter(p=>c?(p.displayName||p.name).toLowerCase().includes(c)||p.name.toLowerCase().includes(c):!0)},[r,f,h,g]);return v.useEffect(()=>{if(w.length===0){d(void 0);return}w.some(p=>p.name===u)||d(w[0].name)},[w,u]),!t||!m?a.jsx("div",{className:"p-8 text-gray-400",children:e("channelsLoading")}):a.jsxs(he,{className:"xl:flex xl:h-full xl:min-h-0 xl:flex-col xl:pb-0",children:[a.jsx(fe,{title:e("channelsPageTitle"),description:e("channelsPageDescription")}),a.jsxs("div",{className:I(Ce,"xl:min-h-0 xl:flex-1"),children:[a.jsxs("section",{className:Se,children:[a.jsx("div",{className:"border-b border-gray-100 px-4 pt-4",children:a.jsx(Ne,{tabs:k,activeTab:r,onChange:o,className:"mb-0"})}),a.jsx("div",{className:"border-b border-gray-100 px-4 py-3",children:a.jsxs("div",{className:"relative",children:[a.jsx(oe,{className:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),a.jsx(A,{value:g,onChange:c=>x(c.target.value),placeholder:e("channelsFilterPlaceholder"),className:"h-10 rounded-xl pl-9"})]})}),a.jsxs("div",{className:"min-h-0 flex-1 space-y-2 overflow-y-auto overscroll-contain p-3",children:[w.map(c=>{const p=t.channels[c.name],S=(p==null?void 0:p.enabled)||!1,C=O(`channels.${c.name}`,j),P=z(c),F=(C==null?void 0:C.help)||e(Me[c.name]||"channelDescriptionDefault"),N=u===c.name;return a.jsx("button",{type:"button",onClick:()=>d(c.name),className:I("w-full rounded-xl border p-2.5 text-left transition-all",N?"border-primary/30 bg-primary-50/40 shadow-sm":"border-gray-200/70 bg-white hover:border-gray-300 hover:bg-gray-50/70"),children:a.jsxs("div",{className:"flex items-start justify-between gap-3",children:[a.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[a.jsx(H,{name:c.name,src:Y(c.name),className:I("h-10 w-10 rounded-lg border",S?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:a.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:c.name[0]})}),a.jsxs("div",{className:"min-w-0",children:[a.jsx("p",{className:"truncate text-sm font-semibold text-gray-900",children:c.displayName||c.name}),a.jsx("p",{className:"line-clamp-1 text-[11px] text-gray-500",children:F})]})]}),a.jsxs("div",{className:"flex items-center gap-2",children:[P&&a.jsx("a",{href:P,onClick:L=>L.stopPropagation(),className:"inline-flex h-7 w-7 items-center justify-center rounded-md text-gray-300 transition-colors hover:bg-gray-100/70 hover:text-gray-500",title:e("channelsGuideTitle"),children:a.jsx(ce,{className:"h-3.5 w-3.5"})}),a.jsx(J,{status:S?"active":"inactive",label:S?e("statusActive"):e("statusInactive"),className:"min-w-[56px] justify-center"})]})]})},c.name)}),w.length===0&&a.jsxs("div",{className:"flex h-full min-h-[220px] flex-col items-center justify-center rounded-xl border border-dashed border-gray-200 bg-gray-50/70 py-10 text-center",children:[a.jsx("div",{className:"mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-white",children:a.jsx(ie,{className:"h-5 w-5 text-gray-300"})}),a.jsx("p",{className:"text-sm font-medium text-gray-700",children:e("channelsNoMatch")})]})]})]}),a.jsx(Le,{channelName:u})]})]})}export{Ke as ChannelsList};
|