@nextclaw/ui 0.6.8 → 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.
- package/CHANGELOG.md +18 -0
- package/dist/assets/{ChannelsList-DH5fzlPu.js → ChannelsList-TyMb5Mgz.js} +1 -1
- package/dist/assets/{ChatPage-BrLCnJSb.js → ChatPage-CQerYqvy.js} +12 -12
- package/dist/assets/{DocBrowser-DPQHJVsZ.js → DocBrowser-CNtrA0ps.js} +1 -1
- package/dist/assets/{LogoBadge-FEb4_vSq.js → LogoBadge-BLqiOM5D.js} +1 -1
- package/dist/assets/{MarketplacePage-BAVXYeZA.js → MarketplacePage-CotZxxNe.js} +3 -3
- package/dist/assets/{ModelConfig-BqPXe7nw.js → ModelConfig-CCsQ8KFq.js} +1 -1
- package/dist/assets/ProvidersList-BYYX5K_g.js +1 -0
- package/dist/assets/{RuntimeConfig-DTYSU4_d.js → RuntimeConfig-BO6s-ls-.js} +1 -1
- package/dist/assets/{SecretsConfig-nNzs3YDm.js → SecretsConfig-mayFdxpM.js} +1 -1
- package/dist/assets/{SessionsConfig-CHjeyqEQ.js → SessionsConfig-DAIczdBj.js} +1 -1
- package/dist/assets/{card-73MmEZi7.js → card-BP5YnL-G.js} +1 -1
- package/dist/assets/{index-DI6BuShn.css → index-BUiahmWm.css} +1 -1
- package/dist/assets/index-D6_5HaDl.js +7 -0
- package/dist/assets/{input-1MCMs6Yf.js → input-B1D2QX0O.js} +1 -1
- package/dist/assets/{label-C4Q8RlBJ.js → label-DW0j-fXA.js} +1 -1
- package/dist/assets/{page-layout-CK0vcVmV.js → page-layout-Ch-H9gD-.js} +1 -1
- package/dist/assets/{session-run-status-BaNlKvi6.js → session-run-status-BUYsQeWs.js} +1 -1
- package/dist/assets/{switch-Bf8w_cF1.js → switch-_cZHlGKB.js} +1 -1
- package/dist/assets/{tabs-custom-B6Gw8gax.js → tabs-custom-ARxqYYjG.js} +1 -1
- package/dist/assets/{useConfirmDialog-B5CZ4EDN.js → useConfirmDialog-BaU7nIat.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/api/config.ts +6 -2
- package/src/api/types.ts +40 -4
- package/src/components/chat/useChatStreamController.ts +18 -3
- package/src/components/config/ProviderForm.tsx +221 -14
- package/src/components/marketplace/MarketplacePage.tsx +48 -44
- package/src/hooks/useConfig.ts +2 -1
- package/src/hooks/useWebSocket.ts +23 -1
- package/src/lib/i18n.ts +2 -0
- package/dist/assets/ProvidersList-vpKPuIxV.js +0 -1
- package/dist/assets/index-CTLvVlk8.js +0 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{r as i,aj as b,aU as I,ad as ie,j as s,af as j,ai as le,ak as h,al as D,am as ce,an as de,ao as ue,aq as fe,ar as ge,as as me,ap as pe,aV as ve,ax as xe}from"./vendor-C--HHaLf.js";import{c as N,t as x}from"./index-
|
|
1
|
+
import{r as i,aj as b,aU as I,ad as ie,j as s,af as j,ai as le,ak as h,al as D,am as ce,an as de,ao as ue,aq as fe,ar as ge,as as me,ap as pe,aV as ve,ax as xe}from"./vendor-C--HHaLf.js";import{c as N,t as x}from"./index-D6_5HaDl.js";import{B as w}from"./page-layout-Ch-H9gD-.js";function Ne(e,t){return i.useReducer((o,a)=>t[o][a]??o,e)}var E=e=>{const{present:t,children:o}=e,a=De(t),r=typeof o=="function"?o({present:a.isPresent}):i.Children.only(o),n=b(a.ref,he(r));return typeof o=="function"||a.isPresent?i.cloneElement(r,{ref:n}):null};E.displayName="Presence";function De(e){const[t,o]=i.useState(),a=i.useRef(null),r=i.useRef(e),n=i.useRef("none"),l=e?"mounted":"unmounted",[c,u]=Ne(l,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return i.useEffect(()=>{const d=y(a.current);n.current=c==="mounted"?d:"none"},[c]),I(()=>{const d=a.current,f=r.current;if(f!==e){const C=n.current,p=y(d);e?u("MOUNT"):p==="none"||(d==null?void 0:d.display)==="none"?u("UNMOUNT"):u(f&&C!==p?"ANIMATION_OUT":"UNMOUNT"),r.current=e}},[e,u]),I(()=>{if(t){let d;const f=t.ownerDocument.defaultView??window,m=p=>{const re=y(a.current).includes(CSS.escape(p.animationName));if(p.target===t&&re&&(u("ANIMATION_END"),!r.current)){const se=t.style.animationFillMode;t.style.animationFillMode="forwards",d=f.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=se)})}},C=p=>{p.target===t&&(n.current=y(a.current))};return t.addEventListener("animationstart",C),t.addEventListener("animationcancel",m),t.addEventListener("animationend",m),()=>{f.clearTimeout(d),t.removeEventListener("animationstart",C),t.removeEventListener("animationcancel",m),t.removeEventListener("animationend",m)}}else u("ANIMATION_END")},[t,u]),{isPresent:["mounted","unmountSuspended"].includes(c),ref:i.useCallback(d=>{a.current=d?getComputedStyle(d):null,o(d)},[])}}function y(e){return(e==null?void 0:e.animationName)||"none"}function he(e){var a,r;let t=(a=Object.getOwnPropertyDescriptor(e.props,"ref"))==null?void 0:a.get,o=t&&"isReactWarning"in t&&t.isReactWarning;return o?e.ref:(t=(r=Object.getOwnPropertyDescriptor(e,"ref"))==null?void 0:r.get,o=t&&"isReactWarning"in t&&t.isReactWarning,o?e.props.ref:e.props.ref||e.ref)}var O="Dialog",[M]=ce(O),[Ce,g]=M(O),T=e=>{const{__scopeDialog:t,children:o,open:a,defaultOpen:r,onOpenChange:n,modal:l=!0}=e,c=i.useRef(null),u=i.useRef(null),[d,f]=ie({prop:a,defaultProp:r??!1,onChange:n,caller:O});return s.jsx(Ce,{scope:t,triggerRef:c,contentRef:u,contentId:j(),titleId:j(),descriptionId:j(),open:d,onOpenChange:f,onOpenToggle:i.useCallback(()=>f(m=>!m),[f]),modal:l,children:o})};T.displayName=O;var S="DialogTrigger",ye=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(S,o),n=b(t,r.triggerRef);return s.jsx(h.button,{type:"button","aria-haspopup":"dialog","aria-expanded":r.open,"aria-controls":r.contentId,"data-state":_(r.open),...a,ref:n,onClick:D(e.onClick,r.onOpenToggle)})});ye.displayName=S;var A="DialogPortal",[Re,F]=M(A,{forceMount:void 0}),L=e=>{const{__scopeDialog:t,forceMount:o,children:a,container:r}=e,n=g(A,t);return s.jsx(Re,{scope:t,forceMount:o,children:i.Children.map(a,l=>s.jsx(E,{present:o||n.open,children:s.jsx(le,{asChild:!0,container:r,children:l})}))})};L.displayName=A;var R="DialogOverlay",k=i.forwardRef((e,t)=>{const o=F(R,e.__scopeDialog),{forceMount:a=o.forceMount,...r}=e,n=g(R,e.__scopeDialog);return n.modal?s.jsx(E,{present:a||n.open,children:s.jsx(Ee,{...r,ref:t})}):null});k.displayName=R;var be=pe("DialogOverlay.RemoveScroll"),Ee=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(R,o);return s.jsx(ue,{as:be,allowPinchZoom:!0,shards:[r.contentRef],children:s.jsx(h.div,{"data-state":_(r.open),...a,ref:t,style:{pointerEvents:"auto",...a.style}})})}),v="DialogContent",W=i.forwardRef((e,t)=>{const o=F(v,e.__scopeDialog),{forceMount:a=o.forceMount,...r}=e,n=g(v,e.__scopeDialog);return s.jsx(E,{present:a||n.open,children:n.modal?s.jsx(Oe,{...r,ref:t}):s.jsx(je,{...r,ref:t})})});W.displayName=v;var Oe=i.forwardRef((e,t)=>{const o=g(v,e.__scopeDialog),a=i.useRef(null),r=b(t,o.contentRef,a);return i.useEffect(()=>{const n=a.current;if(n)return de(n)},[]),s.jsx(U,{...e,ref:r,trapFocus:o.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:D(e.onCloseAutoFocus,n=>{var l;n.preventDefault(),(l=o.triggerRef.current)==null||l.focus()}),onPointerDownOutside:D(e.onPointerDownOutside,n=>{const l=n.detail.originalEvent,c=l.button===0&&l.ctrlKey===!0;(l.button===2||c)&&n.preventDefault()}),onFocusOutside:D(e.onFocusOutside,n=>n.preventDefault())})}),je=i.forwardRef((e,t)=>{const o=g(v,e.__scopeDialog),a=i.useRef(!1),r=i.useRef(!1);return s.jsx(U,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:n=>{var l,c;(l=e.onCloseAutoFocus)==null||l.call(e,n),n.defaultPrevented||(a.current||(c=o.triggerRef.current)==null||c.focus(),n.preventDefault()),a.current=!1,r.current=!1},onInteractOutside:n=>{var u,d;(u=e.onInteractOutside)==null||u.call(e,n),n.defaultPrevented||(a.current=!0,n.detail.originalEvent.type==="pointerdown"&&(r.current=!0));const l=n.target;((d=o.triggerRef.current)==null?void 0:d.contains(l))&&n.preventDefault(),n.detail.originalEvent.type==="focusin"&&r.current&&n.preventDefault()}})}),U=i.forwardRef((e,t)=>{const{__scopeDialog:o,trapFocus:a,onOpenAutoFocus:r,onCloseAutoFocus:n,...l}=e,c=g(v,o),u=i.useRef(null),d=b(t,u);return fe(),s.jsxs(s.Fragment,{children:[s.jsx(ge,{asChild:!0,loop:!0,trapped:a,onMountAutoFocus:r,onUnmountAutoFocus:n,children:s.jsx(me,{role:"dialog",id:c.contentId,"aria-describedby":c.descriptionId,"aria-labelledby":c.titleId,"data-state":_(c.open),...l,ref:d,onDismiss:()=>c.onOpenChange(!1)})}),s.jsxs(s.Fragment,{children:[s.jsx(Ae,{titleId:c.titleId}),s.jsx(_e,{contentRef:u,descriptionId:c.descriptionId})]})]})}),P="DialogTitle",$=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(P,o);return s.jsx(h.h2,{id:r.titleId,...a,ref:t})});$.displayName=P;var G="DialogDescription",B=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(G,o);return s.jsx(h.p,{id:r.descriptionId,...a,ref:t})});B.displayName=G;var z="DialogClose",H=i.forwardRef((e,t)=>{const{__scopeDialog:o,...a}=e,r=g(z,o);return s.jsx(h.button,{type:"button",...a,ref:t,onClick:D(e.onClick,()=>r.onOpenChange(!1))})});H.displayName=z;function _(e){return e?"open":"closed"}var V="DialogTitleWarning",[$e,q]=ve(V,{contentName:v,titleName:P,docsSlug:"dialog"}),Ae=({titleId:e})=>{const t=q(V),o=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users.
|
|
2
2
|
|
|
3
3
|
If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component.
|
|
4
4
|
|
package/dist/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
8
|
<title>NextClaw - 系统配置</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-D6_5HaDl.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C--HHaLf.js">
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BUiahmWm.css">
|
|
12
12
|
</head>
|
|
13
13
|
|
|
14
14
|
<body>
|
package/package.json
CHANGED
package/src/api/config.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ProviderConfigUpdate,
|
|
10
10
|
ProviderConnectionTestRequest,
|
|
11
11
|
ProviderConnectionTestResult,
|
|
12
|
+
ProviderAuthStartRequest,
|
|
12
13
|
ProviderAuthStartResult,
|
|
13
14
|
ProviderAuthPollRequest,
|
|
14
15
|
ProviderAuthPollResult,
|
|
@@ -144,10 +145,13 @@ export async function testProviderConnection(
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
// POST /api/config/providers/:provider/auth/start
|
|
147
|
-
export async function startProviderAuth(
|
|
148
|
+
export async function startProviderAuth(
|
|
149
|
+
provider: string,
|
|
150
|
+
data: ProviderAuthStartRequest = {}
|
|
151
|
+
): Promise<ProviderAuthStartResult> {
|
|
148
152
|
const response = await api.post<ProviderAuthStartResult>(
|
|
149
153
|
`/api/config/providers/${provider}/auth/start`,
|
|
150
|
-
|
|
154
|
+
data
|
|
151
155
|
);
|
|
152
156
|
if (!response.ok) {
|
|
153
157
|
throw new Error(response.error.message);
|
package/src/api/types.ts
CHANGED
|
@@ -77,6 +77,7 @@ export type ProviderConnectionTestResult = {
|
|
|
77
77
|
export type ProviderAuthStartResult = {
|
|
78
78
|
provider: string;
|
|
79
79
|
kind: "device_code";
|
|
80
|
+
methodId?: string;
|
|
80
81
|
sessionId: string;
|
|
81
82
|
verificationUri: string;
|
|
82
83
|
userCode: string;
|
|
@@ -85,6 +86,10 @@ export type ProviderAuthStartResult = {
|
|
|
85
86
|
note?: string;
|
|
86
87
|
};
|
|
87
88
|
|
|
89
|
+
export type ProviderAuthStartRequest = {
|
|
90
|
+
methodId?: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
88
93
|
export type ProviderAuthPollRequest = {
|
|
89
94
|
sessionId: string;
|
|
90
95
|
};
|
|
@@ -208,6 +213,24 @@ export type ChatCapabilitiesView = {
|
|
|
208
213
|
stopReason?: string;
|
|
209
214
|
};
|
|
210
215
|
|
|
216
|
+
export type ChatCommandOptionView = {
|
|
217
|
+
name: string;
|
|
218
|
+
description: string;
|
|
219
|
+
type: 'string' | 'boolean' | 'number';
|
|
220
|
+
required?: boolean;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
export type ChatCommandView = {
|
|
224
|
+
name: string;
|
|
225
|
+
description: string;
|
|
226
|
+
options?: ChatCommandOptionView[];
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export type ChatCommandsView = {
|
|
230
|
+
commands: ChatCommandView[];
|
|
231
|
+
total: number;
|
|
232
|
+
};
|
|
233
|
+
|
|
211
234
|
export type ChatTurnStopRequest = {
|
|
212
235
|
runId: string;
|
|
213
236
|
sessionKey?: string;
|
|
@@ -440,6 +463,18 @@ export type ProviderSpecView = {
|
|
|
440
463
|
en?: string;
|
|
441
464
|
zh?: string;
|
|
442
465
|
};
|
|
466
|
+
methods?: Array<{
|
|
467
|
+
id: string;
|
|
468
|
+
label?: {
|
|
469
|
+
en?: string;
|
|
470
|
+
zh?: string;
|
|
471
|
+
};
|
|
472
|
+
hint?: {
|
|
473
|
+
en?: string;
|
|
474
|
+
zh?: string;
|
|
475
|
+
};
|
|
476
|
+
}>;
|
|
477
|
+
defaultMethodId?: string;
|
|
443
478
|
supportsCliImport?: boolean;
|
|
444
479
|
};
|
|
445
480
|
defaultModels?: string[];
|
|
@@ -541,6 +576,7 @@ export type ConfigActionExecuteResult = {
|
|
|
541
576
|
export type WsEvent =
|
|
542
577
|
| { type: 'config.updated'; payload: { path: string } }
|
|
543
578
|
| { type: 'run.updated'; payload: { run: ChatRunView } }
|
|
579
|
+
| { type: 'session.updated'; payload: { sessionKey: string } }
|
|
544
580
|
| { type: 'config.reload.started'; payload?: Record<string, unknown> }
|
|
545
581
|
| { type: 'config.reload.finished'; payload?: Record<string, unknown> }
|
|
546
582
|
| { type: 'error'; payload: { message: string; code?: string } }
|
|
@@ -550,7 +586,9 @@ export type MarketplaceItemType = 'plugin' | 'skill';
|
|
|
550
586
|
|
|
551
587
|
export type MarketplaceSort = 'relevance' | 'updated';
|
|
552
588
|
|
|
553
|
-
export type
|
|
589
|
+
export type MarketplacePluginInstallKind = 'npm';
|
|
590
|
+
export type MarketplaceSkillInstallKind = 'builtin' | 'marketplace';
|
|
591
|
+
export type MarketplaceInstallKind = MarketplacePluginInstallKind | MarketplaceSkillInstallKind;
|
|
554
592
|
|
|
555
593
|
export type MarketplaceInstallSpec = {
|
|
556
594
|
kind: MarketplaceInstallKind;
|
|
@@ -586,7 +624,7 @@ export type MarketplaceSkillContentView = {
|
|
|
586
624
|
slug: string;
|
|
587
625
|
name: string;
|
|
588
626
|
install: MarketplaceInstallSpec;
|
|
589
|
-
source: '
|
|
627
|
+
source: 'builtin' | 'marketplace' | 'remote';
|
|
590
628
|
raw: string;
|
|
591
629
|
metadataRaw?: string;
|
|
592
630
|
bodyRaw: string;
|
|
@@ -652,8 +690,6 @@ export type MarketplaceInstallRequest = {
|
|
|
652
690
|
kind?: MarketplaceInstallKind;
|
|
653
691
|
skill?: string;
|
|
654
692
|
installPath?: string;
|
|
655
|
-
version?: string;
|
|
656
|
-
registry?: string;
|
|
657
693
|
force?: boolean;
|
|
658
694
|
};
|
|
659
695
|
|
|
@@ -178,7 +178,7 @@ type ExecuteStreamRunParams = {
|
|
|
178
178
|
onReady: (event: { runId?: string; stopSupported?: boolean; stopReason?: string; sessionKey: string }) => void;
|
|
179
179
|
onDelta: (event: { delta: string }) => void;
|
|
180
180
|
onSessionEvent: (event: { data: SessionEventView }) => void;
|
|
181
|
-
}) => Promise<{ sessionKey: string }>;
|
|
181
|
+
}) => Promise<{ sessionKey: string; reply: string }>;
|
|
182
182
|
setters: StreamSetters;
|
|
183
183
|
};
|
|
184
184
|
|
|
@@ -226,6 +226,7 @@ async function executeStreamRun(params: ExecuteStreamRunParams): Promise<void> {
|
|
|
226
226
|
let streamText = '';
|
|
227
227
|
try {
|
|
228
228
|
let hasAssistantSessionEvent = false;
|
|
229
|
+
let hasUserSessionEvent = false;
|
|
229
230
|
const streamTimestamp = new Date().toISOString();
|
|
230
231
|
setters.setStreamingAssistantTimestamp(streamTimestamp);
|
|
231
232
|
|
|
@@ -269,6 +270,7 @@ async function executeStreamRun(params: ExecuteStreamRunParams): Promise<void> {
|
|
|
269
270
|
return;
|
|
270
271
|
}
|
|
271
272
|
if (event.data.message?.role === 'user') {
|
|
273
|
+
hasUserSessionEvent = true;
|
|
272
274
|
setters.setOptimisticUserEvent(null);
|
|
273
275
|
}
|
|
274
276
|
upsertStreamingEvent(setters.setStreamingSessionEvents, event.data);
|
|
@@ -288,7 +290,13 @@ async function executeStreamRun(params: ExecuteStreamRunParams): Promise<void> {
|
|
|
288
290
|
setSelectedSessionKey(result.sessionKey);
|
|
289
291
|
}
|
|
290
292
|
|
|
291
|
-
const
|
|
293
|
+
const finalReply = typeof result.reply === 'string' ? result.reply.trim() : '';
|
|
294
|
+
const localAssistantText = !hasAssistantSessionEvent ? (streamText.trim() || finalReply) : '';
|
|
295
|
+
const isSlashCommandMessage = typeof sourceMessage === 'string' && sourceMessage.trim().startsWith('/');
|
|
296
|
+
const shouldKeepLocalUserCommand =
|
|
297
|
+
!hasUserSessionEvent &&
|
|
298
|
+
optimisticUserEvent?.message?.role === 'user' &&
|
|
299
|
+
isSlashCommandMessage;
|
|
292
300
|
await refetchIfSessionVisible({
|
|
293
301
|
selectedSessionKeyRef,
|
|
294
302
|
currentSessionKey: sourceSessionKey,
|
|
@@ -297,7 +305,14 @@ async function executeStreamRun(params: ExecuteStreamRunParams): Promise<void> {
|
|
|
297
305
|
refetchHistory
|
|
298
306
|
});
|
|
299
307
|
|
|
300
|
-
|
|
308
|
+
const localEvents: SessionEventView[] = [];
|
|
309
|
+
if (shouldKeepLocalUserCommand && optimisticUserEvent) {
|
|
310
|
+
localEvents.push(optimisticUserEvent);
|
|
311
|
+
}
|
|
312
|
+
if (localAssistantText) {
|
|
313
|
+
localEvents.push(buildLocalAssistantEvent(localAssistantText));
|
|
314
|
+
}
|
|
315
|
+
setters.setStreamingSessionEvents(localEvents);
|
|
301
316
|
|
|
302
317
|
setters.setStreamingAssistantText('');
|
|
303
318
|
setters.setStreamingAssistantTimestamp(null);
|
|
@@ -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
|
-
}, [
|
|
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({
|
|
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
|
-
|
|
722
|
-
<
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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
|
|
|
@@ -824,7 +824,7 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
824
824
|
};
|
|
825
825
|
|
|
826
826
|
return (
|
|
827
|
-
<PageLayout>
|
|
827
|
+
<PageLayout className="flex h-full min-h-0 flex-col pb-0">
|
|
828
828
|
<PageHeader title={t(copyKeys.pageTitle)} description={t(copyKeys.pageDescription)} />
|
|
829
829
|
|
|
830
830
|
<Tabs
|
|
@@ -849,7 +849,7 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
849
849
|
}}
|
|
850
850
|
/>
|
|
851
851
|
|
|
852
|
-
<section>
|
|
852
|
+
<section className="flex min-h-0 flex-1 flex-col">
|
|
853
853
|
<div className="flex items-center justify-between mb-3">
|
|
854
854
|
<h3 className="text-[14px] font-semibold text-gray-900">
|
|
855
855
|
{scope === 'installed' ? t(copyKeys.sectionInstalled) : t(copyKeys.sectionCatalog)}
|
|
@@ -868,52 +868,56 @@ export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
|
868
868
|
</div>
|
|
869
869
|
)}
|
|
870
870
|
|
|
871
|
-
<div className="
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
871
|
+
<div className="min-h-0 flex-1 overflow-y-auto custom-scrollbar pr-1">
|
|
872
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-3">
|
|
873
|
+
{scope === 'all' && allItems.map((item) => (
|
|
874
|
+
<MarketplaceListCard
|
|
875
|
+
key={item.id}
|
|
876
|
+
item={item}
|
|
877
|
+
record={findInstalledRecordForItem(item, installedRecordLookup)}
|
|
878
|
+
localeFallbacks={localeFallbacks}
|
|
879
|
+
installState={installState}
|
|
880
|
+
manageState={manageState}
|
|
881
|
+
onOpen={() => void openItemDetail(item, findInstalledRecordForItem(item, installedRecordLookup))}
|
|
882
|
+
onInstall={handleInstall}
|
|
883
|
+
onManage={handleManage}
|
|
884
|
+
/>
|
|
885
|
+
))}
|
|
886
|
+
|
|
887
|
+
{scope === 'installed' && installedEntries.map((entry) => (
|
|
888
|
+
<MarketplaceListCard
|
|
889
|
+
key={entry.key}
|
|
890
|
+
item={entry.item}
|
|
891
|
+
record={entry.record}
|
|
892
|
+
localeFallbacks={localeFallbacks}
|
|
893
|
+
installState={installState}
|
|
894
|
+
manageState={manageState}
|
|
895
|
+
onOpen={() => void openItemDetail(entry.item, entry.record)}
|
|
896
|
+
onInstall={handleInstall}
|
|
897
|
+
onManage={handleManage}
|
|
898
|
+
/>
|
|
899
|
+
))}
|
|
900
|
+
</div>
|
|
900
901
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
902
|
+
{scope === 'all' && !itemsQuery.isLoading && !itemsQuery.isError && allItems.length === 0 && (
|
|
903
|
+
<div className="text-[13px] text-gray-500 py-8 text-center">{t(copyKeys.emptyData)}</div>
|
|
904
|
+
)}
|
|
905
|
+
{scope === 'installed' && !installedQuery.isLoading && !installedQuery.isError && installedEntries.length === 0 && (
|
|
906
|
+
<div className="text-[13px] text-gray-500 py-8 text-center">{t(copyKeys.emptyInstalled)}</div>
|
|
907
|
+
)}
|
|
908
|
+
</div>
|
|
907
909
|
</section>
|
|
908
910
|
|
|
909
911
|
{scope === 'all' && (
|
|
910
|
-
<
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
912
|
+
<div className="shrink-0">
|
|
913
|
+
<PaginationBar
|
|
914
|
+
page={page}
|
|
915
|
+
totalPages={totalPages}
|
|
916
|
+
busy={itemsQuery.isFetching}
|
|
917
|
+
onPrev={() => setPage((current) => Math.max(1, current - 1))}
|
|
918
|
+
onNext={() => setPage((current) => (totalPages > 0 ? Math.min(totalPages, current + 1) : current + 1))}
|
|
919
|
+
/>
|
|
920
|
+
</div>
|
|
917
921
|
)}
|
|
918
922
|
<ConfirmDialog />
|
|
919
923
|
</PageLayout>
|
package/src/hooks/useConfig.ts
CHANGED
|
@@ -139,7 +139,8 @@ export function useTestProviderConnection() {
|
|
|
139
139
|
|
|
140
140
|
export function useStartProviderAuth() {
|
|
141
141
|
return useMutation({
|
|
142
|
-
mutationFn: ({ provider }: { provider: string }) =>
|
|
142
|
+
mutationFn: ({ provider, data }: { provider: string; data?: unknown }) =>
|
|
143
|
+
startProviderAuth(provider, data as Parameters<typeof startProviderAuth>[1])
|
|
143
144
|
});
|
|
144
145
|
}
|
|
145
146
|
|