@tonyclaw/agent-inspector 2.1.15 → 2.1.16
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/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BcEd6V-V.js → CompareDrawer-Ccrrcx1j.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DLqClc9A.js +106 -0
- package/.output/public/assets/{ReplayDialog-BNpC0548.js → ReplayDialog-C7axhr-l.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-Ds1uRLVB.js → RequestAnatomy-WOptg8j_.js} +1 -1
- package/.output/public/assets/{ResponseView-7KPVqKl5.js → ResponseView-CSAcxh_M.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-BHQT261s.js → StreamingChunkSequence-DJS5KhPx.js} +1 -1
- package/.output/public/assets/_sessionId-DUSalzKH.js +1 -0
- package/.output/public/assets/index-BPpA21dY.css +1 -0
- package/.output/public/assets/index-Bt0Az2I2.js +1 -0
- package/.output/public/assets/{index-DjKt8XKe.js → index-DMPNh46t.js} +1 -1
- package/.output/public/assets/{json-viewer-CkCu-rka.js → json-viewer-inX5QSa3.js} +1 -1
- package/.output/public/assets/{main-DpD1N0S8.js → main-aCAKYGSD.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +207 -171
- package/.output/server/{_sessionId-DF9Sy8cP.mjs → _sessionId-C0fhvQnn.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BoxztaO7.mjs → CompareDrawer-CO-Ti5sg.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-CRBkqFlJ.mjs → ProxyViewerContainer-N4J7uBCX.mjs} +807 -386
- package/.output/server/_ssr/{ReplayDialog-Cc1dyDuK.mjs → ReplayDialog-J1VxC0In.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-CMGSsz5Z.mjs → RequestAnatomy-DBH-F3rq.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-Cp10DM1D.mjs → ResponseView-DLFwV7Hd.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-B1VGxy3A.mjs → StreamingChunkSequence-BywmuND0.mjs} +3 -3
- package/.output/server/_ssr/{index-47XVPghS.mjs → index-CStymIc0.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-zDE2rrmJ.mjs → json-viewer-DlQu0N1x.mjs} +3 -3
- package/.output/server/_ssr/{router-DVeuZFqI.mjs → router-CQ4Zxhmr.mjs} +112 -46
- package/.output/server/{_tanstack-start-manifest_v-Bp8JxtPW.mjs → _tanstack-start-manifest_v-BHeJBGfo.mjs} +1 -1
- package/.output/server/index.mjs +72 -72
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +146 -105
- package/src/components/ProxyViewerContainer.tsx +77 -67
- package/src/components/alerts/AlertsDialog.tsx +2 -1
- package/src/components/clients/ClientLogo.tsx +1 -1
- package/src/components/groups/GroupsDialog.tsx +19 -1
- package/src/components/providers/ProviderCard.tsx +1 -1
- package/src/components/providers/ProviderForm.tsx +18 -18
- package/src/components/providers/ProviderLogo.tsx +6 -1
- package/src/components/providers/ProvidersPanel.tsx +6 -6
- package/src/components/providers/SettingsDialog.tsx +13 -6
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +6 -6
- package/src/components/proxy-viewer/ConversationGroup.tsx +43 -32
- package/src/components/proxy-viewer/ConversationGroupList.tsx +452 -89
- package/src/components/proxy-viewer/ConversationHeader.tsx +44 -32
- package/src/components/proxy-viewer/LogEntry.tsx +21 -17
- package/src/components/proxy-viewer/LogEntryHeader.tsx +38 -45
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +2 -2
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
- package/src/components/proxy-viewer/ThreadConnector.tsx +4 -4
- package/src/components/proxy-viewer/TurnGroup.tsx +16 -11
- package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
- package/src/components/proxy-viewer/viewerState.ts +0 -4
- package/src/components/ui/icon-trigger.ts +2 -0
- package/src/components/ui/transient-toast.tsx +104 -0
- package/src/lib/sessionInfoContract.ts +4 -0
- package/src/mcp/server.ts +71 -27
- package/src/mcp/toolHandlers.ts +23 -4
- package/src/proxy/formats/openai/alibabaProvider.ts +0 -4
- package/src/proxy/sessionInfo.ts +31 -2
- package/src/proxy/sessionSupervisor.ts +20 -1
- package/src/proxy/store.ts +1 -1
- package/src/routes/api/providers.$providerId.test.log.ts +8 -4
- package/styles/globals.css +180 -180
- package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +0 -106
- package/.output/public/assets/_sessionId-DWePGjnS.js +0 -1
- package/.output/public/assets/index-CI1-G8ua.js +0 -1
- package/.output/public/assets/index-DdhFqPsI.css +0 -1
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type SessionMembershipEvidence,
|
|
13
13
|
} from "./ProxyViewer";
|
|
14
14
|
import { dispatchLogFocusRequest } from "./proxy-viewer/logFocus";
|
|
15
|
+
import { TransientToast, useTransientToast } from "./ui/transient-toast";
|
|
15
16
|
|
|
16
17
|
type SSEUpdate =
|
|
17
18
|
| {
|
|
@@ -185,7 +186,7 @@ export function ProxyViewerContainer({
|
|
|
185
186
|
const [allLogs, setAllLogs] = useState<CapturedLog[]>([]);
|
|
186
187
|
const [selectedSession, setSelectedSession] = useState(initialSessionId ?? "__all__");
|
|
187
188
|
const [selectedModel, setSelectedModel] = useState("__all__");
|
|
188
|
-
const
|
|
189
|
+
const { notice, showToast, dismissToast } = useTransientToast();
|
|
189
190
|
const [streamInitialized, setStreamInitialized] = useState(initialSessionId === undefined);
|
|
190
191
|
const [logPage, setLogPage] = useState<LogCursorPage | null>(null);
|
|
191
192
|
const [sessionPageLoading, setSessionPageLoading] = useState(initialSessionId !== undefined);
|
|
@@ -202,6 +203,12 @@ export function ProxyViewerContainer({
|
|
|
202
203
|
// Debounce buffer for SSE updates
|
|
203
204
|
const pendingUpdatesRef = useRef<CapturedLog[]>([]);
|
|
204
205
|
const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
206
|
+
const showErrorToast = useCallback(
|
|
207
|
+
(message: string) => {
|
|
208
|
+
showToast({ message, tone: "error" });
|
|
209
|
+
},
|
|
210
|
+
[showToast],
|
|
211
|
+
);
|
|
205
212
|
|
|
206
213
|
// Derived view: the logs the user actually sees. Recomputed only when the
|
|
207
214
|
// underlying set or filter changes — never on every SSE message.
|
|
@@ -269,7 +276,7 @@ export function ProxyViewerContainer({
|
|
|
269
276
|
const parsed: unknown = JSON.parse(rawData);
|
|
270
277
|
const updateResult = SSEUpdateSchema.safeParse(parsed);
|
|
271
278
|
if (!updateResult.success) {
|
|
272
|
-
|
|
279
|
+
showErrorToast("Failed to parse SSE data");
|
|
273
280
|
return;
|
|
274
281
|
}
|
|
275
282
|
const update = updateResult.data;
|
|
@@ -285,17 +292,17 @@ export function ProxyViewerContainer({
|
|
|
285
292
|
logIndexRef.current = buildLogIndex(nextLogs);
|
|
286
293
|
setAllLogs(nextLogs);
|
|
287
294
|
setStreamInitialized(true);
|
|
288
|
-
|
|
295
|
+
dismissToast();
|
|
289
296
|
} else if (update.type === "update") {
|
|
290
297
|
scheduleUpdate(update.log);
|
|
291
298
|
}
|
|
292
299
|
} catch {
|
|
293
|
-
|
|
300
|
+
showErrorToast("Failed to parse SSE data");
|
|
294
301
|
}
|
|
295
302
|
};
|
|
296
303
|
|
|
297
304
|
es.onerror = () => {
|
|
298
|
-
|
|
305
|
+
showErrorToast("SSE connection lost, reconnecting...");
|
|
299
306
|
setStreamInitialized(true);
|
|
300
307
|
es.close();
|
|
301
308
|
if (reconnectTimeoutRef.current !== null) {
|
|
@@ -303,7 +310,7 @@ export function ProxyViewerContainer({
|
|
|
303
310
|
}
|
|
304
311
|
reconnectTimeoutRef.current = setTimeout(connectSSE, 3000);
|
|
305
312
|
};
|
|
306
|
-
}, [initialSessionId, scheduleUpdate]);
|
|
313
|
+
}, [dismissToast, initialSessionId, scheduleUpdate, showErrorToast]);
|
|
307
314
|
|
|
308
315
|
const loadSessionPage = useCallback(
|
|
309
316
|
(request: SessionPageRequest) => {
|
|
@@ -312,7 +319,7 @@ export function ProxyViewerContainer({
|
|
|
312
319
|
const requestId = sessionPageRequestIdRef.current + 1;
|
|
313
320
|
sessionPageRequestIdRef.current = requestId;
|
|
314
321
|
setSessionPageLoading(true);
|
|
315
|
-
|
|
322
|
+
dismissToast();
|
|
316
323
|
|
|
317
324
|
void fetchJson(buildSessionLogsPageUrl(initialSessionId, request), LogCursorPageSchema)
|
|
318
325
|
.then((page) => {
|
|
@@ -323,17 +330,17 @@ export function ProxyViewerContainer({
|
|
|
323
330
|
setLogPage(page);
|
|
324
331
|
setSelectedSession(initialSessionId);
|
|
325
332
|
setSessionPageLoading(false);
|
|
326
|
-
|
|
333
|
+
dismissToast();
|
|
327
334
|
})
|
|
328
335
|
.catch((err: unknown) => {
|
|
329
336
|
if (sessionPageRequestIdRef.current !== requestId) return;
|
|
330
337
|
setAllLogs([]);
|
|
331
338
|
setLogPage(null);
|
|
332
339
|
setSessionPageLoading(false);
|
|
333
|
-
|
|
340
|
+
showErrorToast(err instanceof Error ? err.message : "Failed to load session logs");
|
|
334
341
|
});
|
|
335
342
|
},
|
|
336
|
-
[initialSessionId],
|
|
343
|
+
[dismissToast, initialSessionId, showErrorToast],
|
|
337
344
|
);
|
|
338
345
|
|
|
339
346
|
useEffect(() => {
|
|
@@ -458,67 +465,73 @@ export function ProxyViewerContainer({
|
|
|
458
465
|
}),
|
|
459
466
|
});
|
|
460
467
|
if (!res.ok) {
|
|
461
|
-
|
|
468
|
+
showErrorToast("Failed to clear logs");
|
|
462
469
|
return;
|
|
463
470
|
}
|
|
464
471
|
logIndexRef.current.clear();
|
|
465
472
|
setAllLogs([]);
|
|
466
|
-
|
|
473
|
+
dismissToast();
|
|
467
474
|
} catch (err) {
|
|
468
|
-
|
|
475
|
+
showErrorToast(err instanceof Error ? err.message : "Unknown error clearing logs");
|
|
469
476
|
}
|
|
470
477
|
})();
|
|
471
|
-
}, [allLogs, initialSessionId]);
|
|
472
|
-
|
|
473
|
-
const handleClearGroup = useCallback(
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
478
|
+
}, [allLogs, dismissToast, initialSessionId, showErrorToast]);
|
|
479
|
+
|
|
480
|
+
const handleClearGroup = useCallback(
|
|
481
|
+
(ids: number[]) => {
|
|
482
|
+
if (ids.length === 0) return;
|
|
483
|
+
void (async () => {
|
|
484
|
+
try {
|
|
485
|
+
const res = await fetch("/api/logs", {
|
|
486
|
+
method: "DELETE",
|
|
487
|
+
headers: { "Content-Type": "application/json" },
|
|
488
|
+
body: JSON.stringify({ ids }),
|
|
489
|
+
});
|
|
490
|
+
if (!res.ok) {
|
|
491
|
+
showErrorToast("Failed to clear group");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const idSet = new Set(ids);
|
|
495
|
+
setAllLogs((prev) => {
|
|
496
|
+
const remaining = prev.filter((l) => !idSet.has(l.id));
|
|
497
|
+
logIndexRef.current = buildLogIndex(remaining);
|
|
498
|
+
return remaining;
|
|
499
|
+
});
|
|
500
|
+
dismissToast();
|
|
501
|
+
} catch (err) {
|
|
502
|
+
showErrorToast(err instanceof Error ? err.message : "Unknown error clearing group");
|
|
485
503
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return remaining;
|
|
491
|
-
});
|
|
492
|
-
setError(null);
|
|
493
|
-
} catch (err) {
|
|
494
|
-
setError(err instanceof Error ? err.message : "Unknown error clearing group");
|
|
495
|
-
}
|
|
496
|
-
})();
|
|
497
|
-
}, []);
|
|
498
|
-
|
|
499
|
-
const handleImportLogs = useCallback(async (file: File): Promise<ImportLogsResponse> => {
|
|
500
|
-
const formData = new FormData();
|
|
501
|
-
formData.set("file", file);
|
|
502
|
-
const result = await fetchJson("/api/logs/import", ImportLogsResponseSchema, {
|
|
503
|
-
method: "POST",
|
|
504
|
-
body: formData,
|
|
505
|
-
});
|
|
504
|
+
})();
|
|
505
|
+
},
|
|
506
|
+
[dismissToast, showErrorToast],
|
|
507
|
+
);
|
|
506
508
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
509
|
+
const handleImportLogs = useCallback(
|
|
510
|
+
async (file: File): Promise<ImportLogsResponse> => {
|
|
511
|
+
const formData = new FormData();
|
|
512
|
+
formData.set("file", file);
|
|
513
|
+
const result = await fetchJson("/api/logs/import", ImportLogsResponseSchema, {
|
|
514
|
+
method: "POST",
|
|
515
|
+
body: formData,
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
setAllLogs((prev) => {
|
|
519
|
+
const importedIds = new Set(result.logs.map((log) => log.id));
|
|
520
|
+
const next = [...prev.filter((log) => !importedIds.has(log.id)), ...result.logs].sort(
|
|
521
|
+
(left, right) => left.id - right.id,
|
|
522
|
+
);
|
|
523
|
+
const trimmed = trimClientLogs(next);
|
|
524
|
+
logIndexRef.current = buildLogIndex(trimmed);
|
|
525
|
+
return trimmed;
|
|
526
|
+
});
|
|
527
|
+
setSelectedSession(result.sessionId);
|
|
528
|
+
setSelectedModel("__all__");
|
|
529
|
+
setLogPage(null);
|
|
530
|
+
dismissToast();
|
|
531
|
+
return result;
|
|
532
|
+
},
|
|
533
|
+
[dismissToast],
|
|
534
|
+
);
|
|
522
535
|
|
|
523
536
|
// Read the strip config once at the container so the virtualized list does
|
|
524
537
|
// not need N independent SWR subscriptions per row.
|
|
@@ -569,11 +582,7 @@ export function ProxyViewerContainer({
|
|
|
569
582
|
|
|
570
583
|
return (
|
|
571
584
|
<>
|
|
572
|
-
{
|
|
573
|
-
<div className="fixed top-4 right-4 bg-destructive text-destructive-foreground px-4 py-2 rounded-md text-sm z-50">
|
|
574
|
-
{error}
|
|
575
|
-
</div>
|
|
576
|
-
)}
|
|
585
|
+
<TransientToast notice={notice} onDismiss={dismissToast} />
|
|
577
586
|
<OnboardingBanner />
|
|
578
587
|
<ProxyViewer
|
|
579
588
|
logs={logs}
|
|
@@ -585,6 +594,7 @@ export function ProxyViewerContainer({
|
|
|
585
594
|
onModelChange={setSelectedModel}
|
|
586
595
|
onClearAll={handleClearAll}
|
|
587
596
|
onClearGroup={handleClearGroup}
|
|
597
|
+
onNotify={showToast}
|
|
588
598
|
onImportLogs={initialSessionId === undefined ? handleImportLogs : undefined}
|
|
589
599
|
isLoading={initialSessionId === undefined ? !streamInitialized : sessionPageLoading}
|
|
590
600
|
pagination={pagination}
|
|
@@ -5,6 +5,7 @@ import { type AlertListFilters, useAlerts, useAlertSummary } from "../../lib/use
|
|
|
5
5
|
import { cn } from "../../lib/utils";
|
|
6
6
|
import { Badge } from "../ui/badge";
|
|
7
7
|
import { Button } from "../ui/button";
|
|
8
|
+
import { INSPECTOR_ICON_TRIGGER_CLASS } from "../ui/icon-trigger";
|
|
8
9
|
import {
|
|
9
10
|
Dialog,
|
|
10
11
|
DialogContent,
|
|
@@ -477,7 +478,7 @@ export function AlertsDialog(): JSX.Element {
|
|
|
477
478
|
type="button"
|
|
478
479
|
variant="ghost"
|
|
479
480
|
size="icon"
|
|
480
|
-
className=
|
|
481
|
+
className={INSPECTOR_ICON_TRIGGER_CLASS}
|
|
481
482
|
aria-label="Alerts"
|
|
482
483
|
title="Alerts"
|
|
483
484
|
>
|
|
@@ -117,7 +117,7 @@ export function ClientLogo({
|
|
|
117
117
|
return (
|
|
118
118
|
<span
|
|
119
119
|
className={cn(
|
|
120
|
-
"inline-flex size-6 shrink-0 items-center justify-center rounded-md
|
|
120
|
+
"border border-border bg-muted/40 inline-flex size-6 shrink-0 items-center justify-center rounded-md p-1 text-cyan-100",
|
|
121
121
|
className,
|
|
122
122
|
)}
|
|
123
123
|
aria-label={label}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { Button } from "../ui/button";
|
|
15
15
|
import { Badge } from "../ui/badge";
|
|
16
16
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
|
|
17
|
+
import { INSPECTOR_ICON_TRIGGER_CLASS } from "../ui/icon-trigger";
|
|
17
18
|
import { cn } from "../../lib/utils";
|
|
18
19
|
import { parseJsonResponse, readApiError } from "../../lib/apiClient";
|
|
19
20
|
import {
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
import type { EvidenceReport } from "../../lib/runContract";
|
|
30
31
|
import type { SessionLogSummary } from "../../lib/sessionInfoContract";
|
|
31
32
|
import { getSessionPath } from "../../lib/sessionUrl";
|
|
33
|
+
import { ClientLogo, clientAppLabel, detectClientApp } from "../clients/ClientLogo";
|
|
32
34
|
import { useGroupEvidence } from "../../lib/useGroupEvidence";
|
|
33
35
|
import { useGroups } from "../../lib/useGroups";
|
|
34
36
|
import { useCopyFeedback } from "../proxy-viewer/useCopyFeedback";
|
|
@@ -195,6 +197,13 @@ function firstNonEmpty(...values: Array<string | null>): string {
|
|
|
195
197
|
return "-";
|
|
196
198
|
}
|
|
197
199
|
|
|
200
|
+
function firstOptionalText(...values: Array<string | null | undefined>): string | null {
|
|
201
|
+
for (const value of values) {
|
|
202
|
+
if (value !== null && value !== undefined && value.trim().length > 0) return value;
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
198
207
|
function providerModel(member: InspectorGroupMember): string {
|
|
199
208
|
const provider = member.provider;
|
|
200
209
|
const model = member.model;
|
|
@@ -400,7 +409,7 @@ export function GroupsDialog(): JSX.Element {
|
|
|
400
409
|
type="button"
|
|
401
410
|
variant="ghost"
|
|
402
411
|
size="icon"
|
|
403
|
-
className=
|
|
412
|
+
className={INSPECTOR_ICON_TRIGGER_CLASS}
|
|
404
413
|
aria-label="Evaluation groups"
|
|
405
414
|
title="Evaluation groups"
|
|
406
415
|
>
|
|
@@ -981,6 +990,14 @@ function MemberRow({
|
|
|
981
990
|
const tokens = session?.tokenUsage.total ?? null;
|
|
982
991
|
const firstChunkMs = log?.firstChunkMs ?? null;
|
|
983
992
|
const sessionLabel = firstNonEmpty(member.label, member.sessionId);
|
|
993
|
+
const clientUserAgent = firstOptionalText(session?.clientUserAgent, log?.userAgent, member.agent);
|
|
994
|
+
const clientProjectFolder = session?.clientProjectFolder ?? null;
|
|
995
|
+
const clientApp = detectClientApp({
|
|
996
|
+
userAgent: clientUserAgent,
|
|
997
|
+
conversationId: member.sessionId,
|
|
998
|
+
clientProjectFolder,
|
|
999
|
+
});
|
|
1000
|
+
const clientLabel = clientAppLabel(clientApp);
|
|
984
1001
|
|
|
985
1002
|
return (
|
|
986
1003
|
<tr className="border-b border-border last:border-0 hover:bg-muted/20">
|
|
@@ -1003,6 +1020,7 @@ function MemberRow({
|
|
|
1003
1020
|
<ChevronRight className="size-3.5" />
|
|
1004
1021
|
)}
|
|
1005
1022
|
</Button>
|
|
1023
|
+
{clientLabel !== null && <ClientLogo client={clientApp} className="size-5" />}
|
|
1006
1024
|
{canOpenLiveSession ? (
|
|
1007
1025
|
<a
|
|
1008
1026
|
href={sessionPath}
|
|
@@ -443,7 +443,7 @@ export function ProviderCard({
|
|
|
443
443
|
|
|
444
444
|
return (
|
|
445
445
|
<div
|
|
446
|
-
className={`border
|
|
446
|
+
className={`border border-border bg-card shadow-sm flex flex-col gap-3 rounded-[8px] p-4 transition-all duration-500 ${isHighlighted === true ? "ring-2 ring-primary shadow-md" : ""}`}
|
|
447
447
|
>
|
|
448
448
|
<div className="flex items-start justify-between gap-2">
|
|
449
449
|
<div className="flex items-center gap-2 min-w-0">
|
|
@@ -107,7 +107,7 @@ const MINIMAX_MODELS = [
|
|
|
107
107
|
];
|
|
108
108
|
|
|
109
109
|
// Alibaba model options
|
|
110
|
-
const ALIBABA_MODELS = ["
|
|
110
|
+
const ALIBABA_MODELS = ["qwen3.6-plus", "qwen3.7-max"];
|
|
111
111
|
|
|
112
112
|
// ZhipuAI / Z.AI model options
|
|
113
113
|
const ZHIPU_MODELS = [
|
|
@@ -291,7 +291,7 @@ function EndpointUrlPreview({
|
|
|
291
291
|
if (finalUrl === null) return null;
|
|
292
292
|
|
|
293
293
|
return (
|
|
294
|
-
<div className="
|
|
294
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2 text-xs">
|
|
295
295
|
<div className="text-muted-foreground">Final URL</div>
|
|
296
296
|
<div className="break-all font-mono text-foreground">{finalUrl}</div>
|
|
297
297
|
</div>
|
|
@@ -656,7 +656,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
656
656
|
}
|
|
657
657
|
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:border-ring focus-visible:outline-ring focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50"
|
|
658
658
|
>
|
|
659
|
-
<option value=""
|
|
659
|
+
<option value="">Unspecified</option>
|
|
660
660
|
<option value="personal">个人 (Personal)</option>
|
|
661
661
|
<option value="company">公司 (Company)</option>
|
|
662
662
|
</select>
|
|
@@ -681,7 +681,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
681
681
|
<p className="text-xs text-destructive">{errors.modelMetadataUrl}</p>
|
|
682
682
|
)}
|
|
683
683
|
{modelMetadataUrl.trim() === "" && (
|
|
684
|
-
<div className="
|
|
684
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2 text-xs text-muted-foreground">
|
|
685
685
|
Built-in limits cover DeepSeek v4 pro/flash, MiniMax M3/M2.7/M2.5/M2.1, and GLM coding
|
|
686
686
|
models. Use a registry URL for private or custom model limits.
|
|
687
687
|
</div>
|
|
@@ -751,7 +751,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
751
751
|
ref={(el) => {
|
|
752
752
|
modelRowRefs.current[i] = el;
|
|
753
753
|
}}
|
|
754
|
-
className="space-y-2 rounded-
|
|
754
|
+
className="border border-border bg-card shadow-sm space-y-2 rounded-[8px] p-2"
|
|
755
755
|
>
|
|
756
756
|
<div className="flex items-center gap-2">
|
|
757
757
|
<div className="relative flex-1">
|
|
@@ -775,7 +775,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
775
775
|
<ChevronDown className="size-4" />
|
|
776
776
|
</button>
|
|
777
777
|
{openModelDropdown === i && (
|
|
778
|
-
<div className="absolute left-0 right-0 top-full
|
|
778
|
+
<div className="bg-popover text-popover-foreground absolute left-0 right-0 top-full z-50 mt-1 max-h-48 overflow-y-auto rounded-md border shadow-md">
|
|
779
779
|
{suggestedModels.map((opt) => (
|
|
780
780
|
<button
|
|
781
781
|
key={opt}
|
|
@@ -784,7 +784,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
784
784
|
updateModel(i, opt);
|
|
785
785
|
setOpenModelDropdown(null);
|
|
786
786
|
}}
|
|
787
|
-
className="w-full
|
|
787
|
+
className="w-full px-3 py-2 text-left text-sm transition-colors hover:bg-white/[0.06]"
|
|
788
788
|
>
|
|
789
789
|
{opt}
|
|
790
790
|
</button>
|
|
@@ -899,7 +899,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
899
899
|
</div>
|
|
900
900
|
|
|
901
901
|
<div className="space-y-2">
|
|
902
|
-
<div className="flex gap-1
|
|
902
|
+
<div className="border border-border bg-muted/40 flex gap-1 rounded-[8px] p-1">
|
|
903
903
|
<TooltipProvider>
|
|
904
904
|
<Tooltip>
|
|
905
905
|
<TooltipTrigger asChild>
|
|
@@ -945,7 +945,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
945
945
|
<label htmlFor="provider-anthropic-base-url" className="text-sm font-medium">
|
|
946
946
|
Anthropic Base URL
|
|
947
947
|
</label>
|
|
948
|
-
<div className="flex min-w-0 overflow-hidden rounded-md
|
|
948
|
+
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
949
949
|
<input
|
|
950
950
|
id="provider-anthropic-base-url"
|
|
951
951
|
type="text"
|
|
@@ -955,9 +955,9 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
955
955
|
setAnthropicBaseUrl(e.target.value);
|
|
956
956
|
}}
|
|
957
957
|
placeholder="https://api.anthropic.com"
|
|
958
|
-
className="min-w-0 flex-1 bg-
|
|
958
|
+
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
959
959
|
/>
|
|
960
|
-
<span className="shrink-0 select-none border-l bg-
|
|
960
|
+
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
961
961
|
{ANTHROPIC_MESSAGES_ENDPOINT}
|
|
962
962
|
</span>
|
|
963
963
|
</div>
|
|
@@ -978,7 +978,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
978
978
|
<label htmlFor="provider-openai-base-url" className="text-sm font-medium">
|
|
979
979
|
OpenAI Chat Base URL
|
|
980
980
|
</label>
|
|
981
|
-
<div className="flex min-w-0 overflow-hidden rounded-md
|
|
981
|
+
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
982
982
|
<input
|
|
983
983
|
id="provider-openai-base-url"
|
|
984
984
|
type="text"
|
|
@@ -988,9 +988,9 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
988
988
|
setOpenaiBaseUrl(e.target.value);
|
|
989
989
|
}}
|
|
990
990
|
placeholder="https://api.openai.com"
|
|
991
|
-
className="min-w-0 flex-1 bg-
|
|
991
|
+
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
992
992
|
/>
|
|
993
|
-
<span className="shrink-0 select-none border-l bg-
|
|
993
|
+
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
994
994
|
{OPENAI_CHAT_COMPLETIONS_ENDPOINT}
|
|
995
995
|
</span>
|
|
996
996
|
</div>
|
|
@@ -1010,7 +1010,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
1010
1010
|
<label htmlFor="provider-openai-responses-base-url" className="text-sm font-medium">
|
|
1011
1011
|
OpenAI Responses Base URL
|
|
1012
1012
|
</label>
|
|
1013
|
-
<div className="flex min-w-0 overflow-hidden rounded-md
|
|
1013
|
+
<div className="border border-input bg-background hover:bg-accent hover:text-accent-foreground flex min-w-0 overflow-hidden rounded-md focus-within:border-ring focus-within:outline-ring focus-within:ring-[3px]">
|
|
1014
1014
|
<input
|
|
1015
1015
|
id="provider-openai-responses-base-url"
|
|
1016
1016
|
type="text"
|
|
@@ -1020,9 +1020,9 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
1020
1020
|
setOpenaiResponsesBaseUrl(e.target.value);
|
|
1021
1021
|
}}
|
|
1022
1022
|
placeholder="https://api.openai.com"
|
|
1023
|
-
className="min-w-0 flex-1 bg-
|
|
1023
|
+
className="min-w-0 flex-1 bg-transparent px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
1024
1024
|
/>
|
|
1025
|
-
<span className="shrink-0 select-none border-l bg-
|
|
1025
|
+
<span className="shrink-0 select-none border-l border-white/10 bg-white/[0.04] px-3 py-2 font-mono text-xs text-muted-foreground">
|
|
1026
1026
|
{OPENAI_RESPONSES_ENDPOINT}
|
|
1027
1027
|
</span>
|
|
1028
1028
|
</div>
|
|
@@ -1058,7 +1058,7 @@ export function ProviderForm({ provider, onSubmit, onCancel }: ProviderFormProps
|
|
|
1058
1058
|
</p>
|
|
1059
1059
|
</div>
|
|
1060
1060
|
|
|
1061
|
-
<div className="
|
|
1061
|
+
<div className="border border-border bg-background/95 shadow-sm sticky bottom-0 flex justify-end gap-2 rounded-[8px] px-3 py-2">
|
|
1062
1062
|
<Button type="button" variant="outline" onClick={onCancel} disabled={isSubmitting}>
|
|
1063
1063
|
Cancel
|
|
1064
1064
|
</Button>
|
|
@@ -26,12 +26,17 @@ const PROVIDER_MAP: Record<string, Provider> = {
|
|
|
26
26
|
"deepseek-": "deepseek",
|
|
27
27
|
MiniMax: "minimax",
|
|
28
28
|
qwen: "qwen",
|
|
29
|
-
"glm-": "zhipuai",
|
|
30
29
|
};
|
|
31
30
|
|
|
31
|
+
function isGlmModel(modelLower: string): boolean {
|
|
32
|
+
const normalized = modelLower.replace(/[_\s]+/g, "-");
|
|
33
|
+
return normalized.split(/[/:@]/).some((segment) => segment.startsWith("glm-"));
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
export function detectProvider(model: string | null): Provider {
|
|
33
37
|
if (model === null) return "unknown";
|
|
34
38
|
const modelLower = model.toLowerCase();
|
|
39
|
+
if (isGlmModel(modelLower)) return "zhipuai";
|
|
35
40
|
for (const [prefix, provider] of Object.entries(PROVIDER_MAP)) {
|
|
36
41
|
if (modelLower.startsWith(prefix.toLowerCase())) {
|
|
37
42
|
return provider;
|
|
@@ -521,7 +521,7 @@ export function ProvidersPanel({
|
|
|
521
521
|
if (showForm || editingProvider) {
|
|
522
522
|
return (
|
|
523
523
|
<div className="space-y-4">
|
|
524
|
-
<div className="
|
|
524
|
+
<div className="border border-border bg-background/95 shadow-sm sticky top-0 z-10 flex items-center justify-between rounded-[8px] px-3 py-2">
|
|
525
525
|
<h3 className="text-lg font-medium">
|
|
526
526
|
{editingProvider ? "Edit Provider" : "Add New Provider"}
|
|
527
527
|
</h3>
|
|
@@ -540,7 +540,7 @@ export function ProvidersPanel({
|
|
|
540
540
|
|
|
541
541
|
return (
|
|
542
542
|
<div className="space-y-4">
|
|
543
|
-
<div className="
|
|
543
|
+
<div className="border border-border bg-background/95 shadow-sm sticky top-0 z-10 flex items-center justify-between rounded-[8px] px-3 py-2">
|
|
544
544
|
<h3 className="text-lg font-medium">Providers</h3>
|
|
545
545
|
<div className="flex items-center gap-2">
|
|
546
546
|
<TooltipProvider>
|
|
@@ -608,7 +608,7 @@ export function ProvidersPanel({
|
|
|
608
608
|
</div>
|
|
609
609
|
|
|
610
610
|
{configPath !== null && (
|
|
611
|
-
<div className="
|
|
611
|
+
<div className="border border-border bg-card shadow-sm flex items-center gap-2 rounded-[8px] px-3 py-2 text-xs text-muted-foreground">
|
|
612
612
|
<span className="shrink-0">Config:</span>
|
|
613
613
|
<span className="font-mono truncate" title={configPath}>
|
|
614
614
|
{configPath}
|
|
@@ -644,14 +644,14 @@ export function ProvidersPanel({
|
|
|
644
644
|
)}
|
|
645
645
|
|
|
646
646
|
{error !== null && (
|
|
647
|
-
<div className="
|
|
647
|
+
<div className="border border-border bg-card shadow-sm flex items-center gap-2 rounded-[8px] px-3 py-2 text-sm text-destructive">
|
|
648
648
|
<AlertCircle className="size-4 shrink-0" />
|
|
649
649
|
{error}
|
|
650
650
|
</div>
|
|
651
651
|
)}
|
|
652
652
|
|
|
653
653
|
{providers.length === 0 ? (
|
|
654
|
-
<div className="
|
|
654
|
+
<div className="border border-border bg-card shadow-sm space-y-3 rounded-[8px] py-12 text-center">
|
|
655
655
|
<p className="text-sm text-muted-foreground">No providers configured yet.</p>
|
|
656
656
|
<Button onClick={() => setShowForm(true)} variant="outline" size="sm">
|
|
657
657
|
<Plus className="size-4" />
|
|
@@ -660,7 +660,7 @@ export function ProvidersPanel({
|
|
|
660
660
|
</div>
|
|
661
661
|
) : (
|
|
662
662
|
<div className="space-y-3">
|
|
663
|
-
<div className="flex gap-1
|
|
663
|
+
<div className="border border-border bg-muted/40 flex gap-1 rounded-[8px] p-1">
|
|
664
664
|
{(["all", "personal", "company"] as const).map((tab) => (
|
|
665
665
|
<TooltipProvider key={tab}>
|
|
666
666
|
<Tooltip>
|
|
@@ -4,6 +4,7 @@ import { z } from "zod";
|
|
|
4
4
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
|
|
5
5
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "../ui/tabs";
|
|
6
6
|
import { Button } from "../ui/button";
|
|
7
|
+
import { INSPECTOR_ICON_TRIGGER_CLASS } from "../ui/icon-trigger";
|
|
7
8
|
import { ProvidersPanel } from "./ProvidersPanel";
|
|
8
9
|
import { useProviders } from "../../lib/useProviders";
|
|
9
10
|
import { useStripConfig } from "../../lib/useStripConfig";
|
|
@@ -61,7 +62,13 @@ export function SettingsDialog(): JSX.Element {
|
|
|
61
62
|
return (
|
|
62
63
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
63
64
|
<DialogTrigger asChild>
|
|
64
|
-
<Button
|
|
65
|
+
<Button
|
|
66
|
+
variant="ghost"
|
|
67
|
+
size="icon"
|
|
68
|
+
className={INSPECTOR_ICON_TRIGGER_CLASS}
|
|
69
|
+
aria-label="Settings"
|
|
70
|
+
title="Settings"
|
|
71
|
+
>
|
|
65
72
|
<Settings className="size-4" />
|
|
66
73
|
<span className="sr-only">Settings</span>
|
|
67
74
|
</Button>
|
|
@@ -226,25 +233,25 @@ function StorageSettingsTab(): JSX.Element {
|
|
|
226
233
|
copiedId={copiedId}
|
|
227
234
|
onCopy={handleCopy}
|
|
228
235
|
/>
|
|
229
|
-
<div className="
|
|
236
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2">
|
|
230
237
|
<div className="font-medium">In-memory logs</div>
|
|
231
238
|
<div className="mt-1 font-mono text-muted-foreground">
|
|
232
239
|
{stats.memoryCount.toLocaleString()} loaded
|
|
233
240
|
</div>
|
|
234
241
|
</div>
|
|
235
|
-
<div className="
|
|
242
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2">
|
|
236
243
|
<div className="font-medium">Log files</div>
|
|
237
244
|
<div className="mt-1 font-mono text-muted-foreground">
|
|
238
245
|
{stats.logFileCount.toLocaleString()} files / {formatBytes(stats.logBytes)}
|
|
239
246
|
</div>
|
|
240
247
|
</div>
|
|
241
|
-
<div className="
|
|
248
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2">
|
|
242
249
|
<div className="font-medium">Streaming chunks</div>
|
|
243
250
|
<div className="mt-1 font-mono text-muted-foreground">
|
|
244
251
|
{stats.chunkFileCount.toLocaleString()} files / {formatBytes(stats.chunkBytes)}
|
|
245
252
|
</div>
|
|
246
253
|
</div>
|
|
247
|
-
<div className="
|
|
254
|
+
<div className="border border-border bg-muted/40 rounded-md px-3 py-2">
|
|
248
255
|
<div className="font-medium">Session archives</div>
|
|
249
256
|
<div className="mt-1 font-mono text-muted-foreground">
|
|
250
257
|
{stats.sessionArchiveFileCount.toLocaleString()} files /{" "}
|
|
@@ -274,7 +281,7 @@ function CopyableSetupValue({
|
|
|
274
281
|
}): JSX.Element {
|
|
275
282
|
const copied = copiedId === id;
|
|
276
283
|
return (
|
|
277
|
-
<div className="
|
|
284
|
+
<div className="border border-border bg-muted/40 min-w-0 rounded-md px-3 py-2">
|
|
278
285
|
<div className="mb-1 text-xs font-medium text-muted-foreground">{label}</div>
|
|
279
286
|
<div className="flex min-w-0 items-center gap-2">
|
|
280
287
|
<code className="min-w-0 flex-1 truncate font-mono text-xs text-foreground">{value}</code>
|