@nextclaw/ui 0.6.9 → 0.6.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/assets/{ChannelsList-DACqpUYZ.js → ChannelsList-TyMb5Mgz.js} +1 -1
  3. package/dist/assets/{ChatPage-iji0RkTR.js → ChatPage-CQerYqvy.js} +1 -1
  4. package/dist/assets/{DocBrowser-D7mjKkGe.js → DocBrowser-CNtrA0ps.js} +1 -1
  5. package/dist/assets/{LogoBadge-BlDT-g9R.js → LogoBadge-BLqiOM5D.js} +1 -1
  6. package/dist/assets/{MarketplacePage-CZq3jVgg.js → MarketplacePage-CotZxxNe.js} +1 -1
  7. package/dist/assets/{ModelConfig-DwRU5qrw.js → ModelConfig-CCsQ8KFq.js} +1 -1
  8. package/dist/assets/ProvidersList-BYYX5K_g.js +1 -0
  9. package/dist/assets/{RuntimeConfig-C7BRLGSC.js → RuntimeConfig-BO6s-ls-.js} +1 -1
  10. package/dist/assets/{SecretsConfig-D5xZh7VF.js → SecretsConfig-mayFdxpM.js} +1 -1
  11. package/dist/assets/{SessionsConfig-ovpj_otA.js → SessionsConfig-DAIczdBj.js} +1 -1
  12. package/dist/assets/{card-Bf4CtrW8.js → card-BP5YnL-G.js} +1 -1
  13. package/dist/assets/{index-C_DhisNo.css → index-BUiahmWm.css} +1 -1
  14. package/dist/assets/index-D6_5HaDl.js +7 -0
  15. package/dist/assets/{input-CaKJyoWZ.js → input-B1D2QX0O.js} +1 -1
  16. package/dist/assets/{label-BaXSWTKI.js → label-DW0j-fXA.js} +1 -1
  17. package/dist/assets/{page-layout-DA6PFRtQ.js → page-layout-Ch-H9gD-.js} +1 -1
  18. package/dist/assets/{session-run-status-CllIZxNf.js → session-run-status-BUYsQeWs.js} +1 -1
  19. package/dist/assets/{switch-Cvd5wZs-.js → switch-_cZHlGKB.js} +1 -1
  20. package/dist/assets/{tabs-custom-0PybLkXs.js → tabs-custom-ARxqYYjG.js} +1 -1
  21. package/dist/assets/{useConfirmDialog-DdtpSju1.js → useConfirmDialog-BaU7nIat.js} +1 -1
  22. package/dist/index.html +2 -2
  23. package/package.json +1 -1
  24. package/src/api/config.ts +6 -2
  25. package/src/api/types.ts +18 -0
  26. package/src/components/config/ProviderForm.tsx +221 -14
  27. package/src/hooks/useConfig.ts +2 -1
  28. package/src/hooks/useWebSocket.ts +23 -1
  29. package/src/lib/i18n.ts +2 -0
  30. package/dist/assets/ProvidersList-DFxN3pjx.js +0 -1
  31. package/dist/assets/index-dKTqKCJo.js +0 -7
@@ -32,6 +32,15 @@ type ProviderFormProps = {
32
32
  onProviderDeleted?: (providerName: string) => void;
33
33
  };
34
34
 
35
+ type ProviderAuthMethodOption = {
36
+ id: string;
37
+ };
38
+
39
+ type PillSelectOption = {
40
+ value: string;
41
+ label: string;
42
+ };
43
+
35
44
  const EMPTY_PROVIDER_CONFIG: ProviderConfigView = {
36
45
  displayName: '',
37
46
  apiKeySet: false,
@@ -151,6 +160,101 @@ function serializeModelsForSave(models: string[], defaultModels: string[]): stri
151
160
  return models;
152
161
  }
153
162
 
163
+ function resolvePreferredAuthMethodId(params: {
164
+ providerName?: string;
165
+ methods: ProviderAuthMethodOption[];
166
+ defaultMethodId?: string;
167
+ language: 'zh' | 'en';
168
+ }): string {
169
+ const { providerName, methods, defaultMethodId, language } = params;
170
+ if (methods.length === 0) {
171
+ return '';
172
+ }
173
+
174
+ const methodIdMap = new Map<string, string>();
175
+ for (const method of methods) {
176
+ const methodId = method.id.trim();
177
+ if (methodId) {
178
+ methodIdMap.set(methodId.toLowerCase(), methodId);
179
+ }
180
+ }
181
+
182
+ const pick = (...candidates: string[]): string | undefined => {
183
+ for (const candidate of candidates) {
184
+ const resolved = methodIdMap.get(candidate.toLowerCase());
185
+ if (resolved) {
186
+ return resolved;
187
+ }
188
+ }
189
+ return undefined;
190
+ };
191
+
192
+ const normalizedDefault = defaultMethodId?.trim();
193
+ if (providerName === 'minimax-portal') {
194
+ if (language === 'zh') {
195
+ return pick('cn', 'china-mainland') ?? pick(normalizedDefault ?? '') ?? methods[0]?.id ?? '';
196
+ }
197
+ if (language === 'en') {
198
+ return pick('global', 'intl', 'international') ?? pick(normalizedDefault ?? '') ?? methods[0]?.id ?? '';
199
+ }
200
+ }
201
+
202
+ if (normalizedDefault) {
203
+ const matchedDefault = pick(normalizedDefault);
204
+ if (matchedDefault) {
205
+ return matchedDefault;
206
+ }
207
+ }
208
+
209
+ if (language === 'zh') {
210
+ return pick('cn') ?? methods[0]?.id ?? '';
211
+ }
212
+ if (language === 'en') {
213
+ return pick('global') ?? methods[0]?.id ?? '';
214
+ }
215
+
216
+ return methods[0]?.id ?? '';
217
+ }
218
+
219
+ function shouldUsePillSelector(params: {
220
+ required: boolean;
221
+ hasDefault: boolean;
222
+ optionCount: number;
223
+ }): boolean {
224
+ return params.required && params.hasDefault && params.optionCount > 1 && params.optionCount <= 3;
225
+ }
226
+
227
+ function PillSelector(props: {
228
+ value: string;
229
+ onChange: (value: string) => void;
230
+ options: PillSelectOption[];
231
+ }) {
232
+ const { value, onChange, options } = props;
233
+
234
+ return (
235
+ <div className="flex flex-wrap gap-2">
236
+ {options.map((option) => {
237
+ const selected = option.value === value;
238
+ return (
239
+ <button
240
+ key={option.value}
241
+ type="button"
242
+ onClick={() => onChange(option.value)}
243
+ aria-pressed={selected}
244
+ className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${
245
+ selected
246
+ ? 'border-primary bg-primary text-white shadow-sm'
247
+ : 'border-gray-200 bg-white text-gray-700 hover:border-primary/40 hover:text-primary'
248
+ }`}
249
+ >
250
+ {option.label}
251
+ </button>
252
+ );
253
+ })}
254
+ </div>
255
+ );
256
+ }
257
+
154
258
  export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormProps) {
155
259
  const queryClient = useQueryClient();
156
260
  const { data: config } = useConfig();
@@ -174,6 +278,7 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
174
278
  const [showModelInput, setShowModelInput] = useState(false);
175
279
  const [authSessionId, setAuthSessionId] = useState<string | null>(null);
176
280
  const [authStatusMessage, setAuthStatusMessage] = useState('');
281
+ const [authMethodId, setAuthMethodId] = useState('');
177
282
  const authPollTimerRef = useRef<number | null>(null);
178
283
 
179
284
  const providerSpec = meta?.providers.find((p) => p.name === providerName);
@@ -225,11 +330,63 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
225
330
  apiBaseHint?.help ||
226
331
  t('providerApiBaseHelp');
227
332
  const providerAuth = providerSpec?.auth;
333
+ const providerAuthMethods = useMemo(
334
+ () => providerAuth?.methods ?? [],
335
+ [providerAuth?.methods]
336
+ );
337
+ const providerAuthMethodOptions = useMemo(
338
+ () =>
339
+ providerAuthMethods.map((method) => ({
340
+ value: method.id,
341
+ label: method.label?.[language] || method.label?.en || method.id
342
+ })),
343
+ [providerAuthMethods, language]
344
+ );
345
+ const preferredAuthMethodId = useMemo(
346
+ () => resolvePreferredAuthMethodId({
347
+ providerName,
348
+ methods: providerAuthMethods,
349
+ defaultMethodId: providerAuth?.defaultMethodId,
350
+ language
351
+ }),
352
+ [providerName, providerAuth?.defaultMethodId, providerAuthMethods, language]
353
+ );
354
+ const resolvedAuthMethodId = useMemo(() => {
355
+ if (!providerAuthMethods.length) {
356
+ return '';
357
+ }
358
+ const normalizedCurrent = authMethodId.trim();
359
+ if (normalizedCurrent && providerAuthMethods.some((method) => method.id === normalizedCurrent)) {
360
+ return normalizedCurrent;
361
+ }
362
+ return preferredAuthMethodId || providerAuthMethods[0]?.id || '';
363
+ }, [authMethodId, preferredAuthMethodId, providerAuthMethods]);
364
+ const selectedAuthMethod = useMemo(
365
+ () => providerAuthMethods.find((method) => method.id === resolvedAuthMethodId),
366
+ [providerAuthMethods, resolvedAuthMethodId]
367
+ );
368
+ const selectedAuthMethodHint =
369
+ selectedAuthMethod?.hint?.[language] || selectedAuthMethod?.hint?.en || '';
370
+ const shouldUseAuthMethodPills = shouldUsePillSelector({
371
+ required: providerAuth?.kind === 'device_code',
372
+ hasDefault: Boolean(providerAuth?.defaultMethodId?.trim()),
373
+ optionCount: providerAuthMethods.length
374
+ });
228
375
  const providerAuthNote =
229
376
  providerAuth?.note?.[language] ||
230
377
  providerAuth?.note?.en ||
231
378
  providerAuth?.displayName ||
232
379
  '';
380
+ const wireApiOptions = providerSpec?.wireApiOptions || ['auto', 'chat', 'responses'];
381
+ const wireApiSelectOptions: PillSelectOption[] = wireApiOptions.map((option) => ({
382
+ value: option,
383
+ label: option === 'chat' ? t('wireApiChat') : option === 'responses' ? t('wireApiResponses') : t('wireApiAuto')
384
+ }));
385
+ const shouldUseWireApiPills = shouldUsePillSelector({
386
+ required: Boolean(providerSpec?.supportsWireApi),
387
+ hasDefault: typeof providerSpec?.defaultWireApi === 'string' && providerSpec.defaultWireApi.length > 0,
388
+ optionCount: wireApiSelectOptions.length
389
+ });
233
390
 
234
391
  const clearAuthPollTimer = useCallback(() => {
235
392
  if (authPollTimerRef.current !== null) {
@@ -290,6 +447,7 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
290
447
  setProviderDisplayName('');
291
448
  setAuthSessionId(null);
292
449
  setAuthStatusMessage('');
450
+ setAuthMethodId('');
293
451
  clearAuthPollTimer();
294
452
  return;
295
453
  }
@@ -303,8 +461,18 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
303
461
  setProviderDisplayName(effectiveDisplayName);
304
462
  setAuthSessionId(null);
305
463
  setAuthStatusMessage('');
464
+ setAuthMethodId(preferredAuthMethodId);
306
465
  clearAuthPollTimer();
307
- }, [providerName, currentApiBase, resolvedProviderConfig.extraHeaders, currentWireApi, currentEditableModels, effectiveDisplayName, clearAuthPollTimer]);
466
+ }, [
467
+ providerName,
468
+ currentApiBase,
469
+ resolvedProviderConfig.extraHeaders,
470
+ currentWireApi,
471
+ currentEditableModels,
472
+ effectiveDisplayName,
473
+ preferredAuthMethodId,
474
+ clearAuthPollTimer
475
+ ]);
308
476
 
309
477
  useEffect(() => () => clearAuthPollTimer(), [clearAuthPollTimer]);
310
478
 
@@ -453,7 +621,10 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
453
621
 
454
622
  try {
455
623
  setAuthStatusMessage('');
456
- const result = await startProviderAuth.mutateAsync({ provider: providerName });
624
+ const result = await startProviderAuth.mutateAsync({
625
+ provider: providerName,
626
+ data: resolvedAuthMethodId ? { methodId: resolvedAuthMethodId } : {}
627
+ });
457
628
  if (!result.sessionId || !result.verificationUri) {
458
629
  throw new Error(t('providerAuthStartFailed'));
459
630
  }
@@ -567,6 +738,34 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
567
738
  {providerAuthNote ? (
568
739
  <p className="text-xs text-gray-600">{providerAuthNote}</p>
569
740
  ) : null}
741
+ {providerAuthMethods.length > 1 ? (
742
+ <div className="space-y-2">
743
+ <Label className="text-xs font-medium text-gray-700">{t('providerAuthMethodLabel')}</Label>
744
+ {shouldUseAuthMethodPills ? (
745
+ <PillSelector
746
+ value={resolvedAuthMethodId}
747
+ onChange={setAuthMethodId}
748
+ options={providerAuthMethodOptions}
749
+ />
750
+ ) : (
751
+ <Select value={resolvedAuthMethodId} onValueChange={setAuthMethodId}>
752
+ <SelectTrigger className="h-8 rounded-lg bg-white">
753
+ <SelectValue placeholder={t('providerAuthMethodPlaceholder')} />
754
+ </SelectTrigger>
755
+ <SelectContent>
756
+ {providerAuthMethodOptions.map((method) => (
757
+ <SelectItem key={method.value} value={method.value}>
758
+ {method.label}
759
+ </SelectItem>
760
+ ))}
761
+ </SelectContent>
762
+ </Select>
763
+ )}
764
+ {selectedAuthMethodHint ? (
765
+ <p className="text-xs text-gray-500">{selectedAuthMethodHint}</p>
766
+ ) : null}
767
+ </div>
768
+ ) : null}
570
769
  <div className="flex flex-wrap items-center gap-2">
571
770
  <Button
572
771
  type="button"
@@ -718,18 +917,26 @@ export function ProviderForm({ providerName, onProviderDeleted }: ProviderFormPr
718
917
  <Label htmlFor="wireApi" className="text-sm font-medium text-gray-900">
719
918
  {wireApiHint?.label ?? t('wireApi')}
720
919
  </Label>
721
- <Select value={wireApi} onValueChange={(v) => setWireApi(v as WireApiType)}>
722
- <SelectTrigger className="rounded-xl">
723
- <SelectValue />
724
- </SelectTrigger>
725
- <SelectContent>
726
- {(providerSpec.wireApiOptions || ['auto', 'chat', 'responses']).map((option) => (
727
- <SelectItem key={option} value={option}>
728
- {option === 'chat' ? t('wireApiChat') : option === 'responses' ? t('wireApiResponses') : t('wireApiAuto')}
729
- </SelectItem>
730
- ))}
731
- </SelectContent>
732
- </Select>
920
+ {shouldUseWireApiPills ? (
921
+ <PillSelector
922
+ value={wireApi}
923
+ onChange={(v) => setWireApi(v as WireApiType)}
924
+ options={wireApiSelectOptions}
925
+ />
926
+ ) : (
927
+ <Select value={wireApi} onValueChange={(v) => setWireApi(v as WireApiType)}>
928
+ <SelectTrigger className="rounded-xl">
929
+ <SelectValue />
930
+ </SelectTrigger>
931
+ <SelectContent>
932
+ {wireApiSelectOptions.map((option) => (
933
+ <SelectItem key={option.value} value={option.value}>
934
+ {option.label}
935
+ </SelectItem>
936
+ ))}
937
+ </SelectContent>
938
+ </Select>
939
+ )}
733
940
  </div>
734
941
  )}
735
942
 
@@ -139,7 +139,8 @@ export function useTestProviderConnection() {
139
139
 
140
140
  export function useStartProviderAuth() {
141
141
  return useMutation({
142
- mutationFn: ({ provider }: { provider: string }) => startProviderAuth(provider)
142
+ mutationFn: ({ provider, data }: { provider: string; data?: unknown }) =>
143
+ startProviderAuth(provider, data as Parameters<typeof startProviderAuth>[1])
143
144
  });
144
145
  }
145
146
 
@@ -38,15 +38,30 @@ export function useWebSocket(queryClient?: QueryClient) {
38
38
  })();
39
39
  const client = new ConfigWebSocket(wsUrl);
40
40
 
41
+ const invalidateSessionQueries = (sessionKey?: string) => {
42
+ if (!queryClient) {
43
+ return;
44
+ }
45
+ queryClient.invalidateQueries({ queryKey: ['sessions'] });
46
+ if (sessionKey && sessionKey.trim().length > 0) {
47
+ queryClient.invalidateQueries({ queryKey: ['session-history', sessionKey.trim()] });
48
+ return;
49
+ }
50
+ queryClient.invalidateQueries({ queryKey: ['session-history'] });
51
+ };
52
+
41
53
  client.on('connection.open', () => {
42
54
  setConnectionStatus('connected');
43
55
  });
44
56
 
45
- client.on('config.updated', () => {
57
+ client.on('config.updated', (event) => {
46
58
  // Trigger refetch of config
47
59
  if (queryClient) {
48
60
  queryClient.invalidateQueries({ queryKey: ['config'] });
49
61
  }
62
+ if (event.type === 'config.updated' && event.payload.path.startsWith('session')) {
63
+ invalidateSessionQueries();
64
+ }
50
65
  });
51
66
 
52
67
  client.on('run.updated', (event) => {
@@ -70,6 +85,13 @@ export function useWebSocket(queryClient?: QueryClient) {
70
85
  }
71
86
  });
72
87
 
88
+ client.on('session.updated', (event) => {
89
+ if (event.type !== 'session.updated') {
90
+ return;
91
+ }
92
+ invalidateSessionQueries(event.payload.sessionKey);
93
+ });
94
+
73
95
  client.on('error', (event) => {
74
96
  if (event.type === 'error') {
75
97
  console.error('WebSocket error:', event.payload.message);
package/src/lib/i18n.ts CHANGED
@@ -263,6 +263,8 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
263
263
  providerAuthOpenPrompt: { zh: '请在浏览器完成授权,验证码:', en: 'Open browser and complete authorization (code: ' },
264
264
  providerAuthOpenPromptSuffix: { zh: '', en: ')' },
265
265
  providerAuthStartFailed: { zh: '启动授权失败', en: 'Failed to start authorization' },
266
+ providerAuthMethodLabel: { zh: '授权区域', en: 'Authorization Region' },
267
+ providerAuthMethodPlaceholder: { zh: '请选择授权方式', en: 'Select authorization method' },
266
268
  providerAuthImportFromCli: { zh: '从 Qwen CLI 导入', en: 'Import From Qwen CLI' },
267
269
  providerAuthImporting: { zh: '导入中...', en: 'Importing...' },
268
270
  providerAuthImportSuccess: { zh: '已从 CLI 导入凭证。', en: 'Imported provider credentials from CLI.' },
@@ -1 +0,0 @@
1
- import{r as c,j as e,aH as Ge,aI as Qe,aB as $e,a0 as ye,a as Ue,d as B,ax as Ie,aJ as Ve,C as Ye,aK as Je,a1 as Xe,K as Ze}from"./vendor-C--HHaLf.js";import{c as te,t,u as Le,a as Te,b as Be,z as es,A as ss,C as ts,E as as,F as rs,G as ns,H as is,S as ls,e as os,f as cs,g as ds,h as us,I as ms}from"./index-dKTqKCJo.js";import{B as k,P as xs,a as hs}from"./page-layout-DA6PFRtQ.js";import{I as R}from"./input-CaKJyoWZ.js";import{L as O}from"./label-BaXSWTKI.js";import{C as ps,a as fs,S as Ke,b as ys,c as gs,L as js}from"./LogoBadge-BlDT-g9R.js";import{h as ee}from"./config-hints-CApS3K_7.js";import{T as bs}from"./tabs-custom-0PybLkXs.js";function vs({maskedValue:s,isSet:r,className:i,value:l,onChange:y,placeholder:w,...g}){const[u,f]=c.useState(!1),[j,b]=c.useState(!1),E=typeof l=="string"&&l.length>0,D=r&&!E&&!j;return e.jsxs("div",{className:"relative",children:[D?e.jsx("div",{onClick:()=>b(!0),className:te("flex h-9 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2 text-sm text-gray-500 cursor-text items-center pr-12",i),children:"••••••••••••"}):e.jsx(R,{type:u?"text":"password",className:te("pr-12",i),value:l,onChange:y,onBlur:()=>{E||b(!1)},placeholder:w,autoFocus:j,...g}),e.jsx("div",{className:"absolute right-2 top-1/2 -translate-y-1/2 flex gap-1",children:(r||E)&&e.jsx(k,{type:"button",variant:"ghost",size:"icon",className:"h-7 w-7",onClick:()=>f(!u),children:u?e.jsx(Ge,{className:"h-4 w-4"}):e.jsx(Qe,{className:"h-4 w-4"})})})]})}function ws({value:s,onChange:r,className:i}){const l=s?Object.entries(s):[],y=(u,f,j)=>{const b=[...l];b[u]=[f,j],r(Object.fromEntries(b))},w=()=>{r({...s,"":""})},g=u=>{const f=l.filter((j,b)=>b!==u);r(Object.fromEntries(f))};return e.jsxs("div",{className:te("space-y-2",i),children:[l.map(([u,f],j)=>e.jsxs("div",{className:"flex gap-2",children:[e.jsx(R,{type:"text",value:u,onChange:b=>y(j,b.target.value,f),placeholder:t("headerName"),className:"flex-1"}),e.jsx(R,{type:"text",value:f,onChange:b=>y(j,u,b.target.value),placeholder:t("headerValue"),className:"flex-1"}),e.jsx(k,{type:"button",variant:"ghost",size:"icon",onClick:()=>g(j),children:e.jsx($e,{className:"h-4 w-4 text-red-500"})})]},j)),e.jsxs(k,{type:"button",variant:"outline",size:"sm",onClick:w,children:[e.jsx(ye,{className:"h-4 w-4 mr-2"}),t("add")]})]})}const Ns={displayName:"",apiKeySet:!1,apiBase:null,extraHeaders:null,wireApi:null,models:[]};function se(s){if(!s)return null;const r=Object.entries(s).map(([i,l])=>[i.trim(),l]).filter(([i])=>i.length>0);return r.length===0?null:Object.fromEntries(r)}function ke(s,r){const i=se(s),l=se(r);if(i===null&&l===null)return!0;if(!i||!l)return!1;const y=Object.entries(i).sort(([g],[u])=>g.localeCompare(u)),w=Object.entries(l).sort(([g],[u])=>g.localeCompare(u));return y.length!==w.length?!1:y.every(([g,u],f)=>g===w[f][0]&&u===w[f][1])}function pe(s){if(!s||s.length===0)return[];const r=new Set;for(const i of s){const l=i.trim();l&&r.add(l)}return[...r]}function Cs(s,r){const i=s.trim();if(!i||!r.trim())return i;const l=`${r.trim()}/`;return i.startsWith(l)?i.slice(l.length):i}function oe(s,r){let i=s.trim();if(!i)return"";for(const l of r){const y=l.trim();y&&(i=Cs(i,y))}return i.trim()}function fe(s,r){return s.length!==r.length?!1:s.every((i,l)=>i===r[l])}function As(s,r){const i=[...s];for(const l of r)i.includes(l)||i.push(l);return i}function Ps(s,r){return r.length===0?s:r.every(l=>!s.includes(l))?As(s,r):r}function Ss(s,r){return fe(s,r)?[]:s}function Es({providerName:s,onProviderDeleted:r}){var Se,Ee,De,Me;const i=Ue(),{data:l}=Le(),{data:y}=Te(),{data:w}=Be(),g=es(),u=ss(),f=ts(),j=as(),b=rs(),E=ns(),[D,Q]=c.useState(""),[z,M]=c.useState(""),[K,U]=c.useState(null),[o,h]=c.useState("auto"),[v,N]=c.useState([]),[$,C]=c.useState(""),[I,_]=c.useState(""),[V,ae]=c.useState(!1),[ce,re]=c.useState(!1),[ne,F]=c.useState(null),[ge,P]=c.useState(""),ie=c.useRef(null),n=y==null?void 0:y.providers.find(a=>a.name===s),S=(s?l==null?void 0:l.providers[s]:null)??Ns,le=w==null?void 0:w.uiHints,H=!!(n!=null&&n.isCustom),W=s?ee(`providers.${s}.apiKey`,le):void 0,L=s?ee(`providers.${s}.apiBase`,le):void 0,de=s?ee(`providers.${s}.extraHeaders`,le):void 0,ue=s?ee(`providers.${s}.wireApi`,le):void 0,je=(n==null?void 0:n.displayName)||s||"",q=(S.displayName||"").trim()||je,Fe=I.trim()||q||s||t("providersSelectPlaceholder"),be=(n==null?void 0:n.modelPrefix)||s||"",G=c.useMemo(()=>pe([be,s||""]),[be,s]),me=(n==null?void 0:n.defaultApiBase)||"",Y=S.apiBase||me,xe=se(S.extraHeaders||null),J=S.wireApi||(n==null?void 0:n.defaultWireApi)||"auto",he=c.useMemo(()=>pe(((n==null?void 0:n.defaultModels)??[]).map(a=>oe(a,G))),[n==null?void 0:n.defaultModels,G]),ve=c.useMemo(()=>pe((S.models??[]).map(a=>oe(a,G))),[S.models,G]),X=c.useMemo(()=>Ps(he,ve),[he,ve]),we=is(),ze=((Se=n==null?void 0:n.apiBaseHelp)==null?void 0:Se[we])||((Ee=n==null?void 0:n.apiBaseHelp)==null?void 0:Ee.en)||(L==null?void 0:L.help)||t("providerApiBaseHelp"),p=n==null?void 0:n.auth,Ne=((De=p==null?void 0:p.note)==null?void 0:De[we])||((Me=p==null?void 0:p.note)==null?void 0:Me.en)||(p==null?void 0:p.displayName)||"",A=c.useCallback(()=>{ie.current!==null&&(window.clearTimeout(ie.current),ie.current=null)},[]),Ce=c.useCallback((a,d)=>{A(),ie.current=window.setTimeout(()=>{(async()=>{if(s)try{const x=await b.mutateAsync({provider:s,data:{sessionId:a}});if(x.status==="pending"){P(t("providerAuthWaitingBrowser")),Ce(a,x.nextPollMs??d);return}if(x.status==="authorized"){F(null),A(),P(t("providerAuthCompleted")),B.success(t("providerAuthCompleted")),i.invalidateQueries({queryKey:["config"]}),i.invalidateQueries({queryKey:["config-meta"]});return}F(null),A(),P(x.message||`Authorization ${x.status}.`),B.error(x.message||`Authorization ${x.status}.`)}catch(x){F(null),A();const m=x instanceof Error?x.message:String(x);P(m),B.error(`Authorization failed: ${m}`)}})()},Math.max(1e3,d))},[A,b,s,i]);c.useEffect(()=>{if(!s){Q(""),M(""),U(null),h("auto"),N([]),C(""),_(""),F(null),P(""),A();return}Q(""),M(Y),U(S.extraHeaders||null),h(J),N(X),C(""),_(q),F(null),P(""),A()},[s,Y,S.extraHeaders,J,X,q,A]),c.useEffect(()=>()=>A(),[A]);const Ae=c.useMemo(()=>{if(!s)return!1;const a=D.trim().length>0,d=z.trim()!==Y.trim(),x=!ke(K,xe),m=n!=null&&n.supportsWireApi?o!==J:!1,T=!fe(v,X),Z=H?I.trim()!==q:!1;return a||d||x||m||T||Z},[s,H,I,q,D,z,Y,K,xe,n==null?void 0:n.supportsWireApi,o,J,v,X]),Pe=()=>{const a=oe($,G);if(a){if(v.includes(a)){C("");return}N(d=>[...d,a]),C("")}},_e=a=>{if(a.preventDefault(),!s)return;const d={},x=D.trim(),m=z.trim(),T=se(K),Z=I.trim();H&&Z!==q&&(d.displayName=Z.length>0?Z:null),x.length>0&&(d.apiKey=x),m!==Y.trim()&&(d.apiBase=m.length>0&&m!==me?m:null),ke(T,xe)||(d.extraHeaders=T),n!=null&&n.supportsWireApi&&o!==J&&(d.wireApi=o),fe(v,X)||(d.models=Ss(v,he)),g.mutate({provider:s,data:d})},Oe=async()=>{if(!s)return;const a=v.find(m=>m.trim().length>0)??"",d=oe(a,G),x={apiBase:z.trim(),extraHeaders:se(K),model:d||null};D.trim().length>0&&(x.apiKey=D.trim()),n!=null&&n.supportsWireApi&&(x.wireApi=o);try{const m=await f.mutateAsync({provider:s,data:x});if(m.success){B.success(`${t("providerTestConnectionSuccess")} (${m.latencyMs}ms)`);return}const T=[`provider=${m.provider}`,`latency=${m.latencyMs}ms`];m.model&&T.push(`model=${m.model}`),B.error(`${t("providerTestConnectionFailed")}: ${m.message} | ${T.join(" | ")}`)}catch(m){const T=m instanceof Error?m.message:String(m);B.error(`${t("providerTestConnectionFailed")}: ${T}`)}},Re=async()=>{if(!(!s||!H||!window.confirm(t("providerDeleteConfirm"))))try{await u.mutateAsync({provider:s}),r==null||r(s)}catch{}},He=async()=>{if(!(!s||(p==null?void 0:p.kind)!=="device_code"))try{P("");const a=await j.mutateAsync({provider:s});if(!a.sessionId||!a.verificationUri)throw new Error(t("providerAuthStartFailed"));F(a.sessionId),P(`${t("providerAuthOpenPrompt")}${a.userCode}${t("providerAuthOpenPromptSuffix")}`),window.open(a.verificationUri,"_blank","noopener,noreferrer"),Ce(a.sessionId,a.intervalMs)}catch(a){const d=a instanceof Error?a.message:String(a);F(null),A(),P(d),B.error(`${t("providerAuthStartFailed")}: ${d}`)}},We=async()=>{if(!(!s||(p==null?void 0:p.kind)!=="device_code"))try{A(),F(null);const a=await E.mutateAsync({provider:s}),d=a.expiresAt?` (expires: ${a.expiresAt})`:"";P(`${t("providerAuthImportStatusPrefix")}${d}`),B.success(t("providerAuthImportSuccess")),i.invalidateQueries({queryKey:["config"]}),i.invalidateQueries({queryKey:["config-meta"]})}catch(a){const d=a instanceof Error?a.message:String(a);P(d),B.error(`${t("providerAuthImportFailed")}: ${d}`)}};if(!s||!n)return e.jsx("div",{className:ps,children:e.jsxs("div",{children:[e.jsx("h3",{className:"text-base font-semibold text-gray-900",children:t("providersSelectTitle")}),e.jsx("p",{className:"mt-2 text-sm text-gray-500",children:t("providersSelectDescription")})]})});const qe=S.apiKeySet?t("statusReady"):t("statusSetup");return e.jsxs("div",{className:fs,children:[e.jsx("div",{className:"border-b border-gray-100 px-6 py-4",children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("h3",{className:"truncate text-lg font-semibold text-gray-900",children:Fe}),e.jsxs("div",{className:"flex items-center gap-3",children:[H&&e.jsx("button",{type:"button",onClick:Re,disabled:u.isPending,className:"text-gray-400 hover:text-red-500 transition-colors",title:t("providerDelete"),children:e.jsx($e,{className:"h-4 w-4"})}),e.jsx(Ke,{status:S.apiKeySet?"ready":"setup",label:qe})]})]})}),e.jsxs("form",{onSubmit:_e,className:"flex min-h-0 flex-1 flex-col",children:[e.jsxs("div",{className:"min-h-0 flex-1 space-y-5 overflow-y-auto px-6 py-5",children:[H&&e.jsxs("div",{className:"space-y-2",children:[e.jsx(O,{htmlFor:"providerDisplayName",className:"text-sm font-medium text-gray-900",children:t("providerDisplayName")}),e.jsx(R,{id:"providerDisplayName",type:"text",value:I,onChange:a=>_(a.target.value),placeholder:je||t("providerDisplayNamePlaceholder"),className:"rounded-xl"}),e.jsx("p",{className:"text-xs text-gray-500",children:t("providerDisplayNameHelpShort")})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(O,{htmlFor:"apiKey",className:"text-sm font-medium text-gray-900",children:(W==null?void 0:W.label)??t("apiKey")}),e.jsx(vs,{id:"apiKey",value:D,isSet:S.apiKeySet,onChange:a=>Q(a.target.value),placeholder:(W==null?void 0:W.placeholder)??t("enterApiKey"),className:"rounded-xl"}),e.jsx("p",{className:"text-xs text-gray-500",children:t("leaveBlankToKeepUnchanged")})]}),(p==null?void 0:p.kind)==="device_code"&&e.jsxs("div",{className:"space-y-2 rounded-xl border border-primary/20 bg-primary-50/50 p-3",children:[e.jsx(O,{className:"text-sm font-medium text-gray-900",children:p.displayName||t("providerAuthSectionTitle")}),Ne?e.jsx("p",{className:"text-xs text-gray-600",children:Ne}):null,e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsx(k,{type:"button",variant:"outline",size:"sm",onClick:He,disabled:j.isPending||!!ne,children:j.isPending?t("providerAuthStarting"):ne?t("providerAuthAuthorizing"):t("providerAuthAuthorizeInBrowser")}),p.supportsCliImport?e.jsx(k,{type:"button",variant:"outline",size:"sm",onClick:We,disabled:E.isPending,children:E.isPending?t("providerAuthImporting"):t("providerAuthImportFromCli")}):null,ne?e.jsxs("span",{className:"text-xs text-gray-500",children:[t("providerAuthSessionLabel"),": ",ne.slice(0,8),"…"]}):null]}),ge?e.jsx("p",{className:"text-xs text-gray-600",children:ge}):null]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(O,{htmlFor:"apiBase",className:"text-sm font-medium text-gray-900",children:(L==null?void 0:L.label)??t("apiBase")}),e.jsx(R,{id:"apiBase",type:"text",value:z,onChange:a=>M(a.target.value),placeholder:me||(L==null?void 0:L.placeholder)||"https://api.example.com",className:"rounded-xl"}),e.jsx("p",{className:"text-xs text-gray-500",children:ze})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx(O,{className:"text-sm font-medium text-gray-900",children:t("providerModelsTitle")}),!ce&&e.jsxs("button",{type:"button",onClick:()=>re(!0),className:"text-xs text-primary hover:text-primary/80 font-medium flex items-center gap-1",children:[e.jsx(ye,{className:"h-3 w-3"}),t("providerAddModel")]})]}),ce&&e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(R,{value:$,onChange:a=>C(a.target.value),onKeyDown:a=>{a.key==="Enter"&&(a.preventDefault(),Pe()),a.key==="Escape"&&(re(!1),C(""))},placeholder:t("providerModelInputPlaceholder"),className:"flex-1 rounded-xl",autoFocus:!0}),e.jsx(k,{type:"button",size:"sm",onClick:Pe,disabled:$.trim().length===0,children:t("add")}),e.jsx(k,{type:"button",size:"sm",variant:"ghost",onClick:()=>{re(!1),C("")},children:e.jsx(Ie,{className:"h-4 w-4"})})]}),v.length===0?e.jsxs("div",{className:"rounded-xl border border-dashed border-gray-200 bg-gray-50 px-4 py-6 text-center",children:[e.jsx("p",{className:"text-sm text-gray-500",children:t("providerModelsEmptyShort")}),!ce&&e.jsx("button",{type:"button",onClick:()=>re(!0),className:"mt-2 text-sm text-primary hover:text-primary/80 font-medium",children:t("providerAddFirstModel")})]}):e.jsx("div",{className:"flex flex-wrap gap-2",children:v.map(a=>e.jsxs("div",{className:"group inline-flex max-w-full items-center gap-1 rounded-full border border-gray-200 bg-white px-3 py-1.5",children:[e.jsx("span",{className:"max-w-[180px] truncate text-sm text-gray-800 sm:max-w-[240px]",children:a}),e.jsx("button",{type:"button",onClick:()=>N(d=>d.filter(x=>x!==a)),className:"inline-flex h-5 w-5 items-center justify-center rounded-full text-gray-400 transition-opacity hover:bg-gray-100 hover:text-gray-600 opacity-100 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100","aria-label":t("remove"),children:e.jsx(Ie,{className:"h-3 w-3"})})]},a))})]}),e.jsxs("div",{className:"border-t border-gray-100 pt-4",children:[e.jsxs("button",{type:"button",onClick:()=>ae(!V),className:"flex w-full items-center justify-between text-sm text-gray-600 hover:text-gray-900 transition-colors",children:[e.jsxs("span",{className:"flex items-center gap-1.5",children:[e.jsx(Ve,{className:"h-3.5 w-3.5"}),t("providerAdvancedSettings")]}),e.jsx(Ye,{className:`h-4 w-4 transition-transform ${V?"rotate-180":""}`})]}),V&&e.jsxs("div",{className:"mt-4 space-y-5",children:[n.supportsWireApi&&e.jsxs("div",{className:"space-y-2",children:[e.jsx(O,{htmlFor:"wireApi",className:"text-sm font-medium text-gray-900",children:(ue==null?void 0:ue.label)??t("wireApi")}),e.jsxs(ls,{value:o,onValueChange:a=>h(a),children:[e.jsx(os,{className:"rounded-xl",children:e.jsx(cs,{})}),e.jsx(ds,{children:(n.wireApiOptions||["auto","chat","responses"]).map(a=>e.jsx(us,{value:a,children:a==="chat"?t("wireApiChat"):a==="responses"?t("wireApiResponses"):t("wireApiAuto")},a))})]})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(O,{className:"text-sm font-medium text-gray-900",children:(de==null?void 0:de.label)??t("extraHeaders")}),e.jsx(ws,{value:K,onChange:U}),e.jsx("p",{className:"text-xs text-gray-500",children:t("providerExtraHeadersHelpShort")})]})]})]})]}),e.jsxs("div",{className:"flex items-center justify-between border-t border-gray-100 px-6 py-4",children:[e.jsxs(k,{type:"button",variant:"outline",size:"sm",onClick:Oe,disabled:f.isPending,children:[e.jsx(Je,{className:"mr-1.5 h-4 w-4"}),f.isPending?t("providerTestingConnection"):t("providerTestConnection")]}),e.jsx(k,{type:"submit",disabled:g.isPending||!Ae,children:g.isPending?t("saving"):Ae?t("save"):t("unchanged")})]})]})]})}function Ds(s){if(!s)return null;try{const r=new URL(s),i=r.pathname&&r.pathname!=="/"?r.pathname:"";return`${r.host}${i}`}catch{return s.replace(/^https?:\/\//,"")}}function _s(){const{data:s}=Le(),{data:r}=Te(),{data:i}=Be(),l=ms(),[y,w]=c.useState("installed"),[g,u]=c.useState(),[f,j]=c.useState(""),b=i==null?void 0:i.uiHints,E=(r==null?void 0:r.providers)??[],D=(s==null?void 0:s.providers)??{},Q=E.filter(o=>{var h;return(h=D[o.name])==null?void 0:h.apiKeySet}).length,z=[{id:"installed",label:t("providersTabConfigured"),count:Q},{id:"all",label:t("providersTabAll"),count:E.length}],M=c.useMemo(()=>{const o=(r==null?void 0:r.providers)??[],h=(s==null?void 0:s.providers)??{},v=f.trim().toLowerCase();return o.filter(N=>{var $;return y==="installed"?!!(($=h[N.name])!=null&&$.apiKeySet):!0}).filter(N=>{var I,_;return v?(((_=(I=h[N.name])==null?void 0:I.displayName)==null?void 0:_.trim())||N.displayName||N.name).toLowerCase().includes(v)||N.name.toLowerCase().includes(v):!0})},[r,s,y,f]);c.useEffect(()=>{if(M.length===0){u(void 0);return}M.some(h=>h.name===g)||u(M[0].name)},[M,g]);const K=g,U=async()=>{try{const o=await l.mutateAsync({data:{}});w("all"),j(""),u(o.name)}catch{}};return!s||!r?e.jsx("div",{className:"p-8",children:t("providersLoading")}):e.jsxs(xs,{children:[e.jsx(hs,{title:t("providersPageTitle"),description:t("providersPageDescription")}),e.jsxs("div",{className:ys,children:[e.jsxs("section",{className:gs,children:[e.jsxs("div",{className:"border-b border-gray-100 px-4 pt-4 pb-3 space-y-3",children:[e.jsx(bs,{tabs:z,activeTab:y,onChange:w,className:"mb-0"}),e.jsxs(k,{type:"button",variant:"outline",className:"w-full justify-center",onClick:U,disabled:l.isPending,children:[e.jsx(ye,{className:"mr-2 h-4 w-4"}),l.isPending?t("saving"):t("providerAddCustom")]})]}),e.jsx("div",{className:"border-b border-gray-100 px-4 py-3",children:e.jsxs("div",{className:"relative",children:[e.jsx(Xe,{className:"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-400"}),e.jsx(R,{value:f,onChange:o=>j(o.target.value),placeholder:t("providersFilterPlaceholder"),className:"h-10 rounded-xl pl-9"})]})}),e.jsxs("div",{className:"min-h-0 flex-1 space-y-2 overflow-y-auto p-3",children:[M.map(o=>{var ae;const h=s.providers[o.name],v=!!(h!=null&&h.apiKeySet),N=K===o.name,$=((ae=h==null?void 0:h.displayName)==null?void 0:ae.trim())||o.displayName||o.name,C=ee(`providers.${o.name}`,b),I=(h==null?void 0:h.apiBase)||o.defaultApiBase||"",V=Ds(I)||(C==null?void 0:C.help)||t("providersDefaultDescription");return e.jsx("button",{type:"button",onClick:()=>u(o.name),className:te("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:e.jsxs("div",{className:"flex items-start justify-between gap-3",children:[e.jsxs("div",{className:"flex min-w-0 items-center gap-3",children:[e.jsx(js,{name:o.name,src:o.logo?`/logos/${o.logo}`:null,className:te("h-10 w-10 rounded-lg border",v?"border-primary/30 bg-white":"border-gray-200/70 bg-white"),imgClassName:"h-5 w-5 object-contain",fallback:e.jsx("span",{className:"text-sm font-semibold uppercase text-gray-500",children:o.name[0]})}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("p",{className:"truncate text-sm font-semibold text-gray-900",children:$}),e.jsx("p",{className:"line-clamp-1 text-[11px] text-gray-500",children:V})]})]}),e.jsx(Ke,{status:v?"ready":"setup",label:v?t("statusReady"):t("statusSetup"),className:"min-w-[56px] justify-center"})]})},o.name)}),M.length===0&&e.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:[e.jsx("div",{className:"mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-white",children:e.jsx(Ze,{className:"h-5 w-5 text-gray-300"})}),e.jsx("p",{className:"text-sm font-medium text-gray-700",children:t("providersNoMatch")})]})]})]}),e.jsx(Es,{providerName:K,onProviderDeleted:o=>{o===g&&u(void 0)}})]})]})}export{_s as ProvidersList};