@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.
Files changed (62) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/assets/ChannelsList-CXLzowHj.js +1 -0
  3. package/dist/assets/ChatPage-CvtonrzM.js +36 -0
  4. package/dist/assets/DocBrowser-4NK6-Q_u.js +1 -0
  5. package/dist/assets/LogoBadge-NI7KQCLa.js +1 -0
  6. package/dist/assets/{MarketplacePage-BOzko5s9.js → MarketplacePage-n7y-pif2.js} +2 -2
  7. package/dist/assets/ModelConfig-DztCs0mA.js +1 -0
  8. package/dist/assets/ProvidersList-hSzfE0pG.js +1 -0
  9. package/dist/assets/{RuntimeConfig-Dt9pLB9P.js → RuntimeConfig-CKFGVus7.js} +1 -1
  10. package/dist/assets/SearchConfig-Cxs1744q.js +1 -0
  11. package/dist/assets/{SecretsConfig-C1PU0Yy8.js → SecretsConfig-C90UckNB.js} +2 -2
  12. package/dist/assets/SessionsConfig-CRor418P.js +2 -0
  13. package/dist/assets/{card-C7Gtw2Vs.js → card-BQiPUGaa.js} +1 -1
  14. package/dist/assets/config-layout-BHnOoweL.js +1 -0
  15. package/dist/assets/index-BCfS4UY1.css +1 -0
  16. package/dist/assets/index-CB5eJOGS.js +8 -0
  17. package/dist/assets/index-CkqvHQAt.js +1 -0
  18. package/dist/assets/{input-oBvxsnV9.js → input-DmFFMdAk.js} +1 -1
  19. package/dist/assets/{label-C7F8lMpQ.js → label-BHvlZoIz.js} +1 -1
  20. package/dist/assets/{page-layout-DO8BlScF.js → page-layout-COPE9JyG.js} +1 -1
  21. package/dist/assets/popover-gcypYeec.js +1 -0
  22. package/dist/assets/provider-models-D3B_xWXx.js +1 -0
  23. package/dist/assets/{session-run-status-Kg0FwAPn.js → session-run-status-BgNvd_-a.js} +1 -1
  24. package/dist/assets/{switch-C6a5GyZB.js → switch-BampMwqT.js} +1 -1
  25. package/dist/assets/{tabs-custom-BatFap5k.js → tabs-custom-DSQeYaKd.js} +1 -1
  26. package/dist/assets/useConfirmDialog-DZUn23Li.js +5 -0
  27. package/dist/assets/{vendor-TlME1INH.js → vendor-BKtTvQYU.js} +69 -64
  28. package/dist/index.html +3 -3
  29. package/package.json +1 -1
  30. package/src/App.tsx +2 -0
  31. package/src/api/config.ts +13 -0
  32. package/src/api/types.ts +61 -0
  33. package/src/components/chat/ChatPage.tsx +16 -0
  34. package/src/components/chat/chat-input/ChatInputBottomToolbar.tsx +12 -0
  35. package/src/components/chat/chat-input/components/ChatInputSlashPanelSection.tsx +3 -3
  36. package/src/components/chat/chat-input/components/bottom-toolbar/ChatInputThinkingSelector.tsx +74 -0
  37. package/src/components/chat/chat-input/useChatInputBarController.ts +11 -2
  38. package/src/components/chat/chat-input.types.ts +8 -0
  39. package/src/components/chat/chat-page-data.ts +40 -3
  40. package/src/components/chat/chat-stream/transport.ts +3 -0
  41. package/src/components/chat/chat-stream/types.ts +3 -1
  42. package/src/components/chat/managers/chat-input.manager.ts +51 -0
  43. package/src/components/chat/stores/chat-input.store.ts +5 -1
  44. package/src/components/common/SearchableModelInput.tsx +22 -5
  45. package/src/components/config/ModelConfig.tsx +13 -12
  46. package/src/components/config/ProviderForm.tsx +292 -19
  47. package/src/components/config/SearchConfig.tsx +297 -0
  48. package/src/components/layout/Sidebar.tsx +6 -1
  49. package/src/hooks/useConfig.ts +17 -0
  50. package/src/lib/i18n.ts +37 -0
  51. package/src/lib/provider-models.ts +91 -3
  52. package/dist/assets/ChannelsList-C49JQ-Zt.js +0 -1
  53. package/dist/assets/ChatPage-DIx05c6s.js +0 -36
  54. package/dist/assets/DocBrowser-CpOosDEI.js +0 -1
  55. package/dist/assets/LogoBadge-CL_8ZPXU.js +0 -1
  56. package/dist/assets/ModelConfig-BZ4ZfaQB.js +0 -1
  57. package/dist/assets/ProvidersList-fPpJ5gl6.js +0 -1
  58. package/dist/assets/SessionsConfig-EskBOofQ.js +0 -2
  59. package/dist/assets/index-Cn6_2To7.js +0 -8
  60. package/dist/assets/index-nEYGCJTC.css +0 -1
  61. package/dist/assets/provider-models-y4mUDcGF.js +0 -1
  62. 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'),
@@ -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
- return bestMatch?.name ?? null;
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};