@undefineds.co/xpod 0.4.25 → 0.4.28
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/dist/api/ApiServer.d.ts +1 -0
- package/dist/api/ApiServer.js +38 -0
- package/dist/api/ai-config/AiConfigStore.js +18 -6
- package/dist/api/ai-gateway/connect/index.d.ts +1 -0
- package/dist/api/ai-gateway/connect/index.js +39 -18
- package/dist/api/ai-gateway/models/ProviderModelsAdapter.js +5 -2
- package/dist/api/container/common.js +8 -2
- package/dist/api/container/index.js +2 -2
- package/dist/api/container/local.js +6 -3
- package/dist/api/handlers/AiGatewayHandler.d.ts +1 -0
- package/dist/api/handlers/AiGatewayHandler.js +53 -3
- package/dist/api/handlers/AiGatewayManagementHandler.js +9 -0
- package/dist/api/handlers/TaskHandler.js +15 -10
- package/dist/api/network/NetworkEnvironmentConfigurationStore.js +1 -1
- package/dist/api/runs/ManagedRunWorker.d.ts +0 -1
- package/dist/api/runs/ManagedRunWorker.js +2 -14
- package/dist/api/runs/PiAgentRuntimeDriver.d.ts +0 -8
- package/dist/api/runs/PiAgentRuntimeDriver.js +65 -21
- package/dist/api/runs/RunStateCenter.js +1 -0
- package/dist/api/runs/store.d.ts +1 -0
- package/dist/api/runs/store.js +13 -0
- package/dist/api/tasks/TaskMaterializer.js +13 -6
- package/dist/api/tasks/TaskResumeDiagnostics.d.ts +28 -0
- package/dist/api/tasks/TaskResumeDiagnostics.js +148 -0
- package/dist/api/tasks/TaskService.js +40 -23
- package/dist/storage/conditions/StorageETagHandler.d.ts +1 -1
- package/dist/storage/conditions/StorageETagHandler.js +13 -4
- package/dist/util/task-model-diagnostics.d.ts +67 -0
- package/dist/util/task-model-diagnostics.js +118 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiApiKeyPool.d.ts +1 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiApiKeyPool.js +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectDialog.d.ts +1 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +1 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +10 -10
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.d.ts +3 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeyRow.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +77 -23
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +1 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiSortableCredentialList.js +59 -59
- package/node_modules/@undefineds.co/ai-connections/dist/collection-runtime.js +2 -0
- package/node_modules/@undefineds.co/ai-connections/dist/collections.js +14 -6
- package/node_modules/@undefineds.co/ai-connections/dist/contract/ai-connections-client.d.ts +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/contract/ai-connections-client.js +1 -0
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/gateway-model-roles.d.ts +11 -0
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/gateway-model-roles.js +11 -0
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/normalize.d.ts +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/normalize.js +12 -5
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/request.js +10 -2
- package/node_modules/@undefineds.co/ai-connections/dist/contract/client/types.d.ts +10 -0
- package/node_modules/@undefineds.co/ai-connections/package.json +1 -1
- package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +1 -0
- package/node_modules/@undefineds.co/extension-sdk/package.json +1 -1
- package/node_modules/@undefineds.co/pod-collections/dist/index.cjs +60 -13
- package/node_modules/@undefineds.co/pod-collections/dist/mapping.d.ts +16 -1
- package/node_modules/@undefineds.co/pod-collections/dist/mapping.js +51 -0
- package/node_modules/@undefineds.co/pod-collections/dist/mutations.js +14 -18
- package/node_modules/@undefineds.co/pod-collections/package.json +1 -1
- package/node_modules/@undefineds.co/shared-ui/dist/list.d.ts +8 -1
- package/node_modules/@undefineds.co/shared-ui/dist/list.js +4 -1
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.d.ts +1 -1
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.js +2 -2
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpViews.js +2 -2
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.d.ts +3 -3
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.js +5 -5
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.d.ts +5 -5
- package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.js +5 -5
- package/node_modules/@undefineds.co/shared-ui/dist/section-header.js +1 -1
- package/node_modules/@undefineds.co/shared-ui/dist/theme.css +5 -32
- package/node_modules/@undefineds.co/shared-ui/package.json +1 -1
- package/node_modules/@undefineds.co/solid-sdk/package.json +1 -1
- package/package.json +2 -2
- package/static/app/assets/main.css +1 -1
- package/static/app/assets/main.js +1 -1
- package/static/dashboard/assets/AccountAuthBoundary-Dv52SZc6.js +1 -0
- package/static/dashboard/assets/DesktopOidcCallback-DaT_Rumf.js +1 -0
- package/static/dashboard/assets/DevicePages-HjclpIVm.js +2 -0
- package/static/dashboard/assets/ModelsPage-Be2TPd-x.js +11 -0
- package/static/dashboard/assets/{PaneListHeader-Dn5O0mwy.js → PaneListHeader-DtvnRmFq.js} +1 -1
- package/static/dashboard/assets/{PodDeletionAuthorizationPanel-D3NOG6ZM.js → PodDeletionAuthorizationPanel-BJk_8rJq.js} +3 -3
- package/static/dashboard/assets/PodPage-C4gzEspV.js +1 -0
- package/static/dashboard/assets/{SystemSettingsPage-BNQP_Yuc.js → SystemSettingsPage-HaNrP1H1.js} +1 -1
- package/static/dashboard/assets/TasksPage-BdO5K2L7.js +1 -0
- package/static/dashboard/assets/{WebDesktopEntry-vnb4CZWp.js → WebDesktopEntry-Cj9_ZfJU.js} +1 -1
- package/static/dashboard/assets/XpodAiConnectionsPodStore-B7gmXBmC.js +3 -0
- package/static/dashboard/assets/XpodShellApp-D_KY4nCW.js +4 -0
- package/static/dashboard/assets/{account-session-DCUmCop1.js → account-session-DlcKNG7k.js} +1 -1
- package/static/dashboard/assets/{button-By5Bgvmb.js → button-wslF55uZ.js} +1 -1
- package/static/dashboard/assets/collection-runtime-uxxgEu13.js +1 -0
- package/static/dashboard/assets/{createLucideIcon-bunMzPms.js → createLucideIcon-BJQl82Qk.js} +1 -1
- package/static/dashboard/assets/dashboard-CbJ-JkrI.css +1 -0
- package/static/dashboard/assets/{dashboard-mlsg3yaw.js → dashboard-Or0gnkou.js} +2 -2
- package/static/dashboard/assets/index-BqFeOML9.js +1 -0
- package/static/dashboard/assets/{index-DvMG5pHk.js → index-DgKn1twH.js} +2 -2
- package/static/dashboard/assets/{label-Bj03L9xs.js → label-CS6Sx6Zb.js} +1 -1
- package/static/dashboard/assets/list-UyhlCFKk.js +1 -0
- package/static/dashboard/assets/{nav-item-style-BNqWQY6p.js → nav-item-style-CW1Nnorq.js} +1 -1
- package/static/dashboard/assets/{pod-collections-host-KnnGFZHL.js → pod-collections-host-DjK2xf-b.js} +6 -6
- package/static/dashboard/assets/{safe-continuation-D9AhcWiB.js → safe-continuation-ClWAhcma.js} +1 -1
- package/static/dashboard/assets/{section-header-CLCzbOp_.js → section-header-CEIEcco-.js} +1 -1
- package/static/dashboard/assets/{setting-row-DTCec8A2.js → setting-row-CaYv3dfF.js} +1 -1
- package/static/dashboard/assets/xpod-remembered-login-BUF9fin1.js +310 -0
- package/static/dashboard/dashboard.html +2 -2
- package/static/settings/assets/{AccountAuthBoundary-C0og_gIA.js → AccountAuthBoundary-Chqd38oU.js} +1 -1
- package/static/settings/assets/{DesktopOidcCallback-C5q4oFZ0.js → DesktopOidcCallback-VUrOSM_q.js} +1 -1
- package/static/settings/assets/DevicePages-C6qwvWwu.js +2 -0
- package/static/settings/assets/ModelsPage-Ry_dObRQ.js +11 -0
- package/static/settings/assets/{PaneListHeader-DqVlMe2D.js → PaneListHeader-B5RkRqr4.js} +1 -1
- package/static/settings/assets/{PodDeletionAuthorizationPanel-DgVvarVY.js → PodDeletionAuthorizationPanel-DPM-5Izt.js} +3 -3
- package/static/settings/assets/PodPage-CxND6wwc.js +1 -0
- package/static/settings/assets/{SystemSettingsPage-Crzm_DrI.js → SystemSettingsPage-C8EdMkW7.js} +1 -1
- package/static/settings/assets/TasksPage-DuqptEm8.js +1 -0
- package/static/settings/assets/{WebDesktopEntry-BGC5eRF2.js → WebDesktopEntry-BRcyktkv.js} +1 -1
- package/static/settings/assets/XpodAiConnectionsPodStore-CyA_BJVC.js +3 -0
- package/static/settings/assets/XpodShellApp-CsivHJl0.js +4 -0
- package/static/settings/assets/XpodThemeProvider-CbJ-JkrI.css +1 -0
- package/static/settings/assets/{XpodThemeProvider-CB6LepTA.js → XpodThemeProvider-DK3pIdRG.js} +2 -2
- package/static/settings/assets/{account-session-BcLjCj0x.js → account-session-DJOZ03xr.js} +1 -1
- package/static/settings/assets/auth-callback-GIBYEz_7.js +1 -0
- package/static/settings/assets/{button-BObT8pTE.js → button-D3yW-2o1.js} +1 -1
- package/static/settings/assets/collection-runtime-DfGo25mh.js +1 -0
- package/static/settings/assets/{createLucideIcon-BVsUxXpZ.js → createLucideIcon-CacwLsmB.js} +1 -1
- package/static/settings/assets/{index-ktjMkj7-.js → index-r5KRYsKC.js} +1 -1
- package/static/settings/assets/{label-BrqO8_m_.js → label-MJjUmvzG.js} +1 -1
- package/static/settings/assets/list-Ce-5k1MX.js +1 -0
- package/static/settings/assets/{nav-item-style-Di2CxkDU.js → nav-item-style-ZUbL8KOZ.js} +1 -1
- package/static/settings/assets/{pod-collections-host-KnnGFZHL.js → pod-collections-host-DjK2xf-b.js} +6 -6
- package/static/settings/assets/{safe-continuation-Dx6ceJpY.js → safe-continuation-vlBZFSlU.js} +1 -1
- package/static/settings/assets/{section-header-Dtl7yjVr.js → section-header-DGJdEvQw.js} +1 -1
- package/static/settings/assets/{setting-row-BL6Hhe8N.js → setting-row-C7tuFiho.js} +1 -1
- package/static/settings/assets/{settings-DaRBxoZk.js → settings-CyQPih1O.js} +1 -1
- package/static/settings/assets/xpod-remembered-login-D-TzzT5K.js +567 -0
- package/static/settings/auth-callback.html +3 -3
- package/static/settings/settings.html +3 -3
- package/static/dashboard/assets/AccountAuthBoundary-BvoDydRz.js +0 -1
- package/static/dashboard/assets/DesktopOidcCallback-BwIj874x.js +0 -1
- package/static/dashboard/assets/DevicePages-jOjMcvsr.js +0 -2
- package/static/dashboard/assets/ModelsPage-CMTdfqBR.js +0 -11
- package/static/dashboard/assets/PodPage-BueBdJ9_.js +0 -1
- package/static/dashboard/assets/TasksPage-CxtLJiSF.js +0 -1
- package/static/dashboard/assets/XpodAiConnectionsPodStore-wx3Ttupm.js +0 -18
- package/static/dashboard/assets/XpodShellApp-5FWY8-d5.js +0 -4
- package/static/dashboard/assets/collection-runtime-BnomuB0E.js +0 -1
- package/static/dashboard/assets/dashboard-C64R553B.css +0 -1
- package/static/dashboard/assets/xpod-remembered-login-Dr69A0Rv.js +0 -295
- package/static/settings/assets/DevicePages-fyHjjucT.js +0 -2
- package/static/settings/assets/ModelsPage-EyOByLyE.js +0 -11
- package/static/settings/assets/PodPage-DY4Oi9wg.js +0 -1
- package/static/settings/assets/TasksPage-CfXJbnlt.js +0 -1
- package/static/settings/assets/XpodAiConnectionsPodStore-CXcmgKmQ.js +0 -3
- package/static/settings/assets/XpodShellApp-D1PS3Iut.js +0 -4
- package/static/settings/assets/XpodThemeProvider-C64R553B.css +0 -1
- package/static/settings/assets/auth-callback-DSPQ28ar.js +0 -1
- package/static/settings/assets/collection-runtime-DzNbdZGe.js +0 -1
- package/static/settings/assets/index-browser-3WfrNbcG.js +0 -273
- package/static/settings/assets/xpod-remembered-login-Q66DH12t.js +0 -295
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TASK_MODEL_SDK_ERROR_KINDS = exports.TASK_GATEWAY_DIAGNOSTIC_CODES = exports.TASK_GATEWAY_DIAGNOSTIC_ROUTES = exports.TASK_MODEL_DIAGNOSTIC_STOP_REASONS = exports.TASK_MODEL_DIAGNOSTIC_APIS = exports.TASK_MODEL_DIAGNOSTIC_STAGES = void 0;
|
|
4
|
+
exports.hashTaskModelDiagnosticSession = hashTaskModelDiagnosticSession;
|
|
5
|
+
exports.classifyTaskModelSdkErrorHint = classifyTaskModelSdkErrorHint;
|
|
6
|
+
exports.selectTaskModelDiagnosticReceipt = selectTaskModelDiagnosticReceipt;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
exports.TASK_MODEL_DIAGNOSTIC_STAGES = [
|
|
9
|
+
'not_invoked', 'model_invoked', 'payload_prepared', 'stream_open',
|
|
10
|
+
];
|
|
11
|
+
exports.TASK_MODEL_DIAGNOSTIC_APIS = ['openai-completions', 'openai-responses', 'other'];
|
|
12
|
+
exports.TASK_MODEL_DIAGNOSTIC_STOP_REASONS = ['error', 'aborted', 'unknown'];
|
|
13
|
+
exports.TASK_GATEWAY_DIAGNOSTIC_ROUTES = ['chat_completions', 'responses', 'anthropic_messages'];
|
|
14
|
+
exports.TASK_GATEWAY_DIAGNOSTIC_CODES = [
|
|
15
|
+
'invalid_request', 'invalid_tool_arguments', 'unsupported_protocol_event', 'service_access_missing',
|
|
16
|
+
'credential_unavailable', 'model_not_available', 'no_model_available', 'embedding_model_not_allowed',
|
|
17
|
+
'provider_error', 'internal_error',
|
|
18
|
+
];
|
|
19
|
+
exports.TASK_MODEL_SDK_ERROR_KINDS = [
|
|
20
|
+
'http_status', 'connection', 'timeout', 'aborted', 'length_limit', 'content_filter', 'unknown',
|
|
21
|
+
];
|
|
22
|
+
/** Hash the exact header bytes; malformed headers never enter a diagnostic receipt. */
|
|
23
|
+
function hashTaskModelDiagnosticSession(sessionHeader) {
|
|
24
|
+
if (typeof sessionHeader !== 'string'
|
|
25
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9_.:/-]{0,255}$/u.test(sessionHeader))
|
|
26
|
+
return undefined;
|
|
27
|
+
return (0, node_crypto_1.createHash)('sha256').update(sessionHeader).digest('hex');
|
|
28
|
+
}
|
|
29
|
+
function member(value, allowed) {
|
|
30
|
+
return typeof value === 'string' && allowed.some(entry => entry === value);
|
|
31
|
+
}
|
|
32
|
+
function boundedInteger(value, min, max) {
|
|
33
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= min && value <= max;
|
|
34
|
+
}
|
|
35
|
+
const SDK_ERROR_MESSAGE_LIMIT = 8192;
|
|
36
|
+
// openai/core/error.mjs APIError.makeMessage prefixes "<status> " to the body message.
|
|
37
|
+
const SDK_HTTP_STATUS_PREFIX = /^([45]\d{2}) \S/u;
|
|
38
|
+
const SDK_EXACT_ERROR_KINDS = new Map([
|
|
39
|
+
['Connection error.', 'connection'],
|
|
40
|
+
['Request timed out.', 'timeout'],
|
|
41
|
+
['Request was aborted.', 'aborted'],
|
|
42
|
+
['Could not parse response content as the length limit was reached', 'length_limit'],
|
|
43
|
+
['Could not parse response content as the request was rejected by the content filter', 'content_filter'],
|
|
44
|
+
]);
|
|
45
|
+
/**
|
|
46
|
+
* Classify the installed SDK's provider error message into a fixed enum.
|
|
47
|
+
* Only the SDK's own leading status prefix and exact error constants are
|
|
48
|
+
* recognized; embedded response text is never inspected or retained.
|
|
49
|
+
*/
|
|
50
|
+
function classifyTaskModelSdkErrorHint(message) {
|
|
51
|
+
if (typeof message !== 'string' || message.length === 0)
|
|
52
|
+
return undefined;
|
|
53
|
+
const head = message.slice(0, SDK_ERROR_MESSAGE_LIMIT);
|
|
54
|
+
const status = SDK_HTTP_STATUS_PREFIX.exec(head);
|
|
55
|
+
if (status) {
|
|
56
|
+
const code = Number(status[1]);
|
|
57
|
+
if (code >= 400 && code <= 599)
|
|
58
|
+
return { kind: 'http_status', status: code };
|
|
59
|
+
}
|
|
60
|
+
return { kind: SDK_EXACT_ERROR_KINDS.get(head) ?? 'unknown' };
|
|
61
|
+
}
|
|
62
|
+
function selectTaskModelSdkErrorHint(value) {
|
|
63
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
64
|
+
return undefined;
|
|
65
|
+
const row = value;
|
|
66
|
+
if (!member(row.kind, exports.TASK_MODEL_SDK_ERROR_KINDS))
|
|
67
|
+
return undefined;
|
|
68
|
+
if (row.kind === 'http_status') {
|
|
69
|
+
return boundedInteger(row.status, 400, 599) ? { kind: 'http_status', status: row.status } : undefined;
|
|
70
|
+
}
|
|
71
|
+
return row.status === undefined ? { kind: row.kind } : undefined;
|
|
72
|
+
}
|
|
73
|
+
/** The sole field allowlist used by producers and the candidate artifact projector. */
|
|
74
|
+
function selectTaskModelDiagnosticReceipt(value) {
|
|
75
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
76
|
+
return undefined;
|
|
77
|
+
const row = value;
|
|
78
|
+
if (row.schemaVersion !== 1 || row.scope !== 'session'
|
|
79
|
+
|| typeof row.correlationHash !== 'string' || !/^[a-f0-9]{64}$/u.test(row.correlationHash))
|
|
80
|
+
return undefined;
|
|
81
|
+
const common = { schemaVersion: 1, scope: 'session', correlationHash: row.correlationHash };
|
|
82
|
+
if (row.event === 'xpod.task-gateway-http-diagnostic') {
|
|
83
|
+
if (!member(row.route, exports.TASK_GATEWAY_DIAGNOSTIC_ROUTES) || !boundedInteger(row.callerHTTPstatus, 400, 599)
|
|
84
|
+
|| (row.statusSource !== 'response_finished' && row.statusSource !== 'response_closed')
|
|
85
|
+
|| !boundedInteger(row.durationMs, 0, 2147483647))
|
|
86
|
+
return undefined;
|
|
87
|
+
return { event: row.event, ...common, route: row.route, callerHTTPstatus: row.callerHTTPstatus,
|
|
88
|
+
statusSource: row.statusSource, durationMs: row.durationMs };
|
|
89
|
+
}
|
|
90
|
+
if (row.event === 'xpod.task-model-diagnostic') {
|
|
91
|
+
if (!member(row.stage, exports.TASK_MODEL_DIAGNOSTIC_STAGES) || !member(row.api, exports.TASK_MODEL_DIAGNOSTIC_APIS)
|
|
92
|
+
|| !member(row.stopReason, exports.TASK_MODEL_DIAGNOSTIC_STOP_REASONS)
|
|
93
|
+
|| !boundedInteger(row.retryCount, 0, Number.MAX_SAFE_INTEGER)
|
|
94
|
+
|| typeof row.credentialPresent !== 'boolean' || row.httpStatus !== null)
|
|
95
|
+
return undefined;
|
|
96
|
+
const receipt = { event: row.event, ...common, stage: row.stage, api: row.api,
|
|
97
|
+
stopReason: row.stopReason, retryCount: row.retryCount, credentialPresent: row.credentialPresent, httpStatus: null };
|
|
98
|
+
if (row.sdkErrorHint !== undefined) {
|
|
99
|
+
const sdkErrorHint = selectTaskModelSdkErrorHint(row.sdkErrorHint);
|
|
100
|
+
if (!sdkErrorHint)
|
|
101
|
+
return undefined;
|
|
102
|
+
receipt.sdkErrorHint = sdkErrorHint;
|
|
103
|
+
}
|
|
104
|
+
return receipt;
|
|
105
|
+
}
|
|
106
|
+
if (row.event === 'xpod.task-gateway-diagnostic') {
|
|
107
|
+
if (!member(row.route, exports.TASK_GATEWAY_DIAGNOSTIC_ROUTES) || !member(row.code, exports.TASK_GATEWAY_DIAGNOSTIC_CODES)
|
|
108
|
+
|| !boundedInteger(row.callerHTTPstatus, 100, 599) || typeof row.streamOpen !== 'boolean'
|
|
109
|
+
|| !boundedInteger(row.durationMs, 0, 2147483647)
|
|
110
|
+
|| (row.underlyingErrorStatus !== undefined && !boundedInteger(row.underlyingErrorStatus, 100, 599)))
|
|
111
|
+
return undefined;
|
|
112
|
+
return { event: row.event, ...common, route: row.route, callerHTTPstatus: row.callerHTTPstatus,
|
|
113
|
+
code: row.code, ...(row.underlyingErrorStatus === undefined ? {} : { underlyingErrorStatus: row.underlyingErrorStatus }),
|
|
114
|
+
streamOpen: row.streamOpen, durationMs: row.durationMs };
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=task-model-diagnostics.js.map
|
|
@@ -27,6 +27,7 @@ export declare function AiApiKeyPool({ definition, offering, createOfferings, st
|
|
|
27
27
|
priority: number;
|
|
28
28
|
}) => Promise<void>;
|
|
29
29
|
onUpdateCredential?: (credential: AiProviderCredentialSummary, patch: {
|
|
30
|
+
apiKey?: string;
|
|
30
31
|
label?: string;
|
|
31
32
|
enabled?: boolean;
|
|
32
33
|
priority?: number;
|
|
@@ -106,6 +106,7 @@ export function AiApiKeyPool({ definition, offering, createOfferings, status, cr
|
|
|
106
106
|
onSavingChange?.(true);
|
|
107
107
|
try {
|
|
108
108
|
await onUpdateCredential(editingCredential, {
|
|
109
|
+
...(poolApiKey.trim() ? { apiKey: poolApiKey.trim() } : {}),
|
|
109
110
|
label: poolLabel.trim() || undefined,
|
|
110
111
|
baseUrl: poolBaseUrl.trim() || undefined,
|
|
111
112
|
...(proxyChanged ? { proxyUrl: normalizedProxyUrl } : {}),
|
|
@@ -122,7 +123,7 @@ export function AiApiKeyPool({ definition, offering, createOfferings, status, cr
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
};
|
|
125
|
-
return (_jsxs("div", { className: cn('space-y-3', formMode && 'w-full'), children: [attempt?.userCode ? (_jsxs("div", { className: "border-l-2 border-primary bg-muted/30 px-3 py-2 text-sm", children: ["\u9A8C\u8BC1\u7801\uFF1A", _jsx("strong", { className: "font-mono", children: attempt.userCode })] })) : null, !initialCreate && !initialEditing ? _jsxs("div", { className: "flex flex-wrap gap-2", children: [onCreateApiKeyCredential && createOfferings.length > 0 ? (_jsxs(Button, { variant: "outline", size: "sm", disabled: busy || disabled || saving, onClick: openCreateForm, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "\u6DFB\u52A0 API Key"] })) : null, hasPoolActions ? null : isConnected ? (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", disabled: busy || disabled || saving, onClick: onBeginBrowser, children: [busy ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(RotateCw, { className: "mr-2 h-4 w-4" }), "\u91CD\u65B0\u8FDE\u63A5"] }), _jsxs(Button, { variant: "ghost", size: "sm", disabled: busy || disabled || saving, onClick: onDisconnect, children: [_jsx(LogOut, { className: "mr-2 h-4 w-4" }), "\u65AD\u5F00\u8FDE\u63A5"] })] })) : isConfigured ? (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", "aria-label": "\u66F4\u65B0 API Key", disabled: busy || disabled || saving, onClick: onBeginApiKey, children: [_jsx(KeyRound, { className: "mr-2 h-4 w-4" }), "\u66F4\u65B0 API Key"] }), _jsx(Button, { variant: "ghost", size: "sm", disabled: busy || disabled || saving, onClick: onDisconnect, children: "\u79FB\u9664\u914D\u7F6E" })] })) : (_jsxs(_Fragment, { children: [browserMethod ? (_jsxs(Button, { variant: "outline", size: "sm", title: browserMethod.lifecycle === 'unavailable' ? browserMethod.reason : undefined, disabled: busy || disabled || saving || browserMethod.lifecycle === 'unavailable', onClick: onBeginBrowser, children: [busy ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), browserMethod.label] })) : null, _jsxs(Button, { variant: "outline", size: "sm", "aria-label": `${definition.name} API Key`, disabled: busy || disabled || saving, onClick: onBeginApiKey, children: [_jsx(KeyRound, { className: "mr-2 h-4 w-4" }), "\u914D\u7F6E API Key"] })] }))] }) : null, formMode ? (_jsxs("div", { className: "space-y-2 rounded-lg border border-border/50 bg-muted/10 p-3", children: [formMode === 'create' && createOfferings.length > 1 ? (_jsxs("label", { className: "block space-y-1 text-xs text-muted-foreground", children: [_jsx("span", { children: "\u5957\u9910 / \u533A\u57DF" }), _jsx(NativeSelect, { "aria-label": `${definition.name} 套餐 / 区域`, value: createOfferingId, disabled: busy || disabled || saving, onChange: (event) => { setCreateOfferingId(event.target.value); setPoolFormError(undefined); }, className: "h-9", children: createOfferings.map((item) => _jsx("option", { value: item.id, children: offeringTitle(item) }, item.id)) })] })) : null, formMode === 'edit' ? (_jsx(Input, { disabled: busy || disabled || saving, autoComplete: "off", "aria-label": `${definition.name} API Key 标签`, placeholder: "\u540D\u79F0\uFF0C\u4F8B\u5982 Work key", value: poolLabel, onChange: (event) => setPoolLabel(event.target.value) })) : null, formMode === 'create' ? (_jsx(Input, { disabled: busy || disabled || saving, type: showKey ? 'text' : 'password', autoComplete: "new-password", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} API Key 输入`, placeholder: definition.apiKeyPlaceholder || '从官方控制台复制 API Key', value: poolApiKey, onChange: (event) => setPoolApiKey(event.target.value), className: "font-mono" })) : null, formMode === 'edit' || showAdvanced ? (_jsxs(_Fragment, { children: [_jsx(Input, { disabled: busy || disabled || saving, autoComplete: "off", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} Base URL 输入`, placeholder: offeringEndpoint(formMode === 'edit' ? offering : formOffering) || definition.defaultBaseUrl || '默认服务地址', value: poolBaseUrl, onChange: (event) => setPoolBaseUrl(event.target.value), className: "font-mono text-xs" }), _jsx(Input, { disabled: busy || disabled || saving, autoComplete: "off", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} Proxy URL 输入`, placeholder: "http://127.0.0.1:7890", value: poolProxyUrl, onChange: (event) => setPoolProxyUrl(event.target.value), className: "font-mono text-xs" })] })) : null, poolFormError ? _jsx("p", { className: "text-xs text-destructive", children: poolFormError }) : null, _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { size: "sm", "aria-label": formMode === 'create' ? `保存 ${definition.name} API Key` : '保存凭证', disabled: busy || disabled || saving || (formMode === 'create' && !poolApiKey.trim()), onClick: savePoolForm, children: formMode === 'create' ? '保存 API Key' : '保存凭证' }), _jsx(Button, { variant: "ghost", size: "sm", disabled: saving, onClick: closePoolForm, children: "\u53D6\u6D88" }), formMode === 'create' ? (_jsx(Button, { variant: "ghost", size: "sm", disabled: saving, onClick: () => setShowAdvanced((current) => !current), children: showAdvanced ? '收起高级设置' : '高级设置' })) : null] })] })) : null, apiKeyAttempt && !formMode ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-medium", children: "API Key" }), definition.apiKeyUrl ? (_jsx("a", { href: definition.apiKeyUrl, target: "_blank", rel: "noreferrer", className: "text-xs text-primary hover:underline", children: "\u83B7\u53D6 API Key" })) : null] }), _jsxs("div", { className: "group relative", children: [_jsx(Input, { disabled: busy || disabled || saving, type: showKey ? 'text' : 'password', autoComplete: "new-password", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} API Key 输入`, placeholder: definition.apiKeyPlaceholder || '从官方控制台复制 API Key', value: apiKey, onChange: (event) => onApiKeyChange(event.target.value), className: "border-border/60 bg-muted/20 pr-10 font-mono transition-colors focus:border-primary/50 focus:bg-background" }), _jsx("div", { className: "absolute bottom-1 right-1 top-1 flex items-center", children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-full w-8 rounded hover:bg-muted", onClick: () => setShowKey((current) => !current), "aria-label": showKey ? '隐藏 API Key' : '显示 API Key', children: showKey
|
|
126
|
+
return (_jsxs("div", { className: cn('space-y-3', formMode && 'w-full'), children: [attempt?.userCode ? (_jsxs("div", { className: "border-l-2 border-primary bg-muted/30 px-3 py-2 text-sm", children: ["\u9A8C\u8BC1\u7801\uFF1A", _jsx("strong", { className: "font-mono", children: attempt.userCode })] })) : null, !initialCreate && !initialEditing ? _jsxs("div", { className: "flex flex-wrap gap-2", children: [onCreateApiKeyCredential && createOfferings.length > 0 ? (_jsxs(Button, { variant: "outline", size: "sm", disabled: busy || disabled || saving, onClick: openCreateForm, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "\u6DFB\u52A0 API Key"] })) : null, hasPoolActions ? null : isConnected ? (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", disabled: busy || disabled || saving, onClick: onBeginBrowser, children: [busy ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(RotateCw, { className: "mr-2 h-4 w-4" }), "\u91CD\u65B0\u8FDE\u63A5"] }), _jsxs(Button, { variant: "ghost", size: "sm", disabled: busy || disabled || saving, onClick: onDisconnect, children: [_jsx(LogOut, { className: "mr-2 h-4 w-4" }), "\u65AD\u5F00\u8FDE\u63A5"] })] })) : isConfigured ? (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", "aria-label": "\u66F4\u65B0 API Key", disabled: busy || disabled || saving, onClick: onBeginApiKey, children: [_jsx(KeyRound, { className: "mr-2 h-4 w-4" }), "\u66F4\u65B0 API Key"] }), _jsx(Button, { variant: "ghost", size: "sm", disabled: busy || disabled || saving, onClick: onDisconnect, children: "\u79FB\u9664\u914D\u7F6E" })] })) : (_jsxs(_Fragment, { children: [browserMethod ? (_jsxs(Button, { variant: "outline", size: "sm", title: browserMethod.lifecycle === 'unavailable' ? browserMethod.reason : undefined, disabled: busy || disabled || saving || browserMethod.lifecycle === 'unavailable', onClick: onBeginBrowser, children: [busy ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), browserMethod.label] })) : null, _jsxs(Button, { variant: "outline", size: "sm", "aria-label": `${definition.name} API Key`, disabled: busy || disabled || saving, onClick: onBeginApiKey, children: [_jsx(KeyRound, { className: "mr-2 h-4 w-4" }), "\u914D\u7F6E API Key"] })] }))] }) : null, formMode ? (_jsxs("div", { className: "space-y-2 rounded-lg border border-border/50 bg-muted/10 p-3", children: [formMode === 'create' && createOfferings.length > 1 ? (_jsxs("label", { className: "block space-y-1 text-xs text-muted-foreground", children: [_jsx("span", { children: "\u5957\u9910 / \u533A\u57DF" }), _jsx(NativeSelect, { "aria-label": `${definition.name} 套餐 / 区域`, value: createOfferingId, disabled: busy || disabled || saving, onChange: (event) => { setCreateOfferingId(event.target.value); setPoolFormError(undefined); }, className: "h-9", children: createOfferings.map((item) => _jsx("option", { value: item.id, children: offeringTitle(item) }, item.id)) })] })) : null, formMode === 'edit' ? (_jsx(Input, { disabled: busy || disabled || saving, autoComplete: "off", "aria-label": `${definition.name} API Key 标签`, placeholder: "\u540D\u79F0\uFF0C\u4F8B\u5982 Work key", value: poolLabel, onChange: (event) => setPoolLabel(event.target.value) })) : null, formMode === 'create' || formMode === 'edit' ? (_jsx(Input, { disabled: busy || disabled || saving, type: showKey ? 'text' : 'password', autoComplete: "new-password", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} API Key 输入`, placeholder: formMode === 'edit' ? '留空保留现有 Key,填写以替换' : definition.apiKeyPlaceholder || '从官方控制台复制 API Key', value: poolApiKey, onChange: (event) => setPoolApiKey(event.target.value), className: "font-mono" })) : null, formMode === 'edit' || showAdvanced ? (_jsxs(_Fragment, { children: [_jsx(Input, { disabled: busy || disabled || saving, autoComplete: "off", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} Base URL 输入`, placeholder: offeringEndpoint(formMode === 'edit' ? offering : formOffering) || definition.defaultBaseUrl || '默认服务地址', value: poolBaseUrl, onChange: (event) => setPoolBaseUrl(event.target.value), className: "font-mono text-xs" }), _jsx(Input, { disabled: busy || disabled || saving, autoComplete: "off", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} Proxy URL 输入`, placeholder: "http://127.0.0.1:7890", value: poolProxyUrl, onChange: (event) => setPoolProxyUrl(event.target.value), className: "font-mono text-xs" })] })) : null, poolFormError ? _jsx("p", { className: "text-xs text-destructive", children: poolFormError }) : null, _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { size: "sm", "aria-label": formMode === 'create' ? `保存 ${definition.name} API Key` : '保存凭证', disabled: busy || disabled || saving || (formMode === 'create' && !poolApiKey.trim()), onClick: savePoolForm, children: formMode === 'create' ? '保存 API Key' : '保存凭证' }), _jsx(Button, { variant: "ghost", size: "sm", disabled: saving, onClick: closePoolForm, children: "\u53D6\u6D88" }), formMode === 'create' ? (_jsx(Button, { variant: "ghost", size: "sm", disabled: saving, onClick: () => setShowAdvanced((current) => !current), children: showAdvanced ? '收起高级设置' : '高级设置' })) : null] })] })) : null, apiKeyAttempt && !formMode ? (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-medium", children: "API Key" }), definition.apiKeyUrl ? (_jsx("a", { href: definition.apiKeyUrl, target: "_blank", rel: "noreferrer", className: "text-xs text-primary hover:underline", children: "\u83B7\u53D6 API Key" })) : null] }), _jsxs("div", { className: "group relative", children: [_jsx(Input, { disabled: busy || disabled || saving, type: showKey ? 'text' : 'password', autoComplete: "new-password", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} API Key 输入`, placeholder: definition.apiKeyPlaceholder || '从官方控制台复制 API Key', value: apiKey, onChange: (event) => onApiKeyChange(event.target.value), className: "border-border/60 bg-muted/20 pr-10 font-mono transition-colors focus:border-primary/50 focus:bg-background" }), _jsx("div", { className: "absolute bottom-1 right-1 top-1 flex items-center", children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-full w-8 rounded hover:bg-muted", onClick: () => setShowKey((current) => !current), "aria-label": showKey ? '隐藏 API Key' : '显示 API Key', children: showKey
|
|
126
127
|
? _jsx(EyeOff, { className: "h-4 w-4 text-muted-foreground" })
|
|
127
128
|
: _jsx(Eye, { className: "h-4 w-4 text-muted-foreground" }) }) })] }), onBaseUrlChange ? (_jsxs("div", { className: "space-y-1.5", children: [_jsx("span", { className: "text-xs font-medium text-muted-foreground", children: "Base URL\uFF08\u9009\u586B\uFF09" }), _jsx(Input, { disabled: busy || disabled || saving, autoComplete: "off", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} Base URL 输入`, placeholder: definition.defaultBaseUrl || '默认服务地址', value: baseUrl, onChange: (event) => onBaseUrlChange(event.target.value), className: "border-border/60 bg-muted/20 font-mono text-xs transition-colors focus:border-primary/50 focus:bg-background" }), _jsxs("p", { className: "break-all font-mono text-[11px] text-muted-foreground opacity-80", children: [_jsx("span", { className: "mr-1 select-none opacity-50", children: "\u9884\u89C8:" }), (baseUrl.trim() || definition.defaultBaseUrl || '').replace(/\/+$/, ''), "/chat/completions"] })] })) : null, _jsx(Button, { size: "sm", "aria-label": `保存 ${definition.name} API Key`, disabled: !apiKey.trim() || busy || disabled, onClick: onSaveApiKey, children: "\u4FDD\u5B58 API Key" })] })) : null, error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null] }));
|
|
128
129
|
}
|
|
@@ -30,6 +30,7 @@ export declare function AiConnectDialog({ controller, definition, offerings, api
|
|
|
30
30
|
onSaveApiKey: () => void;
|
|
31
31
|
onDisconnect: (credential?: AiProviderCredentialSummary) => void;
|
|
32
32
|
onUpdateCredential?: (credential: AiProviderCredentialSummary, patch: {
|
|
33
|
+
apiKey?: string;
|
|
33
34
|
label?: string;
|
|
34
35
|
enabled?: boolean;
|
|
35
36
|
priority?: number;
|
|
@@ -48,6 +48,7 @@ export declare function AiCredentialPoolSection({ definition, product, status, a
|
|
|
48
48
|
}) => Promise<void>;
|
|
49
49
|
onCreateLocalCredential?: (offering: AiProviderOffering, method?: AiProviderAuthorizationMethod) => Promise<void>;
|
|
50
50
|
onUpdateCredential?: (credential: AiProviderCredentialSummary, patch: {
|
|
51
|
+
apiKey?: string;
|
|
51
52
|
label?: string;
|
|
52
53
|
enabled?: boolean;
|
|
53
54
|
priority?: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Button, TooltipProvider } from '@undefineds.co/shared-ui';
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, SectionHeader, TooltipProvider } from '@undefineds.co/shared-ui';
|
|
3
3
|
import { Plus, Settings2 } from 'lucide-react';
|
|
4
4
|
import { offeringTitle } from './offering-label.js';
|
|
5
5
|
import { authorizationMethodsForOffering, connectEntryRank, isApiKeyMethod, isBrowserConnectMethod, isLocalMethod, isOAuthMethod, isOAuthMode, isPendingAttempt, } from './authorization-methods.js';
|
|
@@ -61,14 +61,14 @@ export function AiCredentialPoolSection({ definition, product, status, accountLa
|
|
|
61
61
|
onBeginBrowser,
|
|
62
62
|
onCreateLocalCredential,
|
|
63
63
|
});
|
|
64
|
-
return (_jsx(TooltipProvider, { children: _jsxs("section", { className: "space-y-3", "aria-label": "\u5F53\u524D\u8FDE\u63A5", children: [
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
return (_jsx(TooltipProvider, { children: _jsxs("section", { className: "space-y-3", "aria-label": "\u5F53\u524D\u8FDE\u63A5", children: [_jsx(SectionHeader, { level: 3, className: "items-center gap-2", titleClassName: "flex items-center gap-2 text-sm font-medium text-foreground/90", title: _jsxs(_Fragment, { children: [_jsx(Settings2, { "aria-hidden": "true", className: "h-4 w-4 text-primary" }), "\u5F53\u524D\u8FDE\u63A5"] }), actions: _jsxs("div", { "data-testid": "provider-connect-actions", className: "flex flex-wrap items-center justify-end gap-2", children: [credentials.length > 1 ? (_jsx(Button, { variant: "ghost", size: "sm", "aria-label": `刷新全部 ${definition.name}额度`, disabled: disabled || quotaBusy || !credentials.some((c) => c.enabled) || !onRefreshQuota, onClick: () => offerings.forEach((offering) => onRefreshQuota?.(offering)), children: quotaBusy ? '查询中' : '刷新全部额度' })) : null, connectEntries.map((item) => {
|
|
65
|
+
if ('authorization' in item) {
|
|
66
|
+
return (_jsx("fieldset", { className: "m-0 min-w-0 border-0 p-0", "aria-label": `${offeringTitle(item.authorization.offering)}快捷接入`, children: _jsx(AiAuthorizationActions, { methods: [item.authorization.method], offering: item.authorization.offering, busy: busy || dialog.saving, disabled: disabled || authorizationPending, onBeginOffering: onBeginOffering ? dialog.beginAuthorization : undefined, onCreateLocalCredential: onCreateLocalCredential ? dialog.beginLocal : undefined }) }, item.key));
|
|
67
|
+
}
|
|
68
|
+
return (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", "aria-label": "\u65B0\u5EFA API Key \u8FDE\u63A5", title: item.apiKeyMethod.lifecycle === 'unavailable' ? item.apiKeyMethod.reason : undefined, disabled: busy || dialog.saving || disabled || authorizationPending
|
|
69
|
+
|| item.apiKeyMethod.lifecycle === 'unavailable'
|
|
70
|
+
|| (!product && status === 'pending' && !attempt), onClick: dialog.beginApiKey, children: [_jsx(Plus, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), item.apiKeyMethod.label] }, item.key));
|
|
71
|
+
})] }) }), offeringMethods.filter(({ offering, methods }) => offering.lifecycle === 'unavailable'
|
|
72
72
|
&& !methods.some((method) => method.lifecycle === 'active')).map(({ offering, methods }) => (_jsx(AiOfferingDetails, { offering: offering, methods: methods }, offering.id))), credentials.some((credential) => credential.enabled && (credential.health === 'expired' || credential.health === 'invalid' || credential.lastFailureCode === 'quota_exhausted')) ? _jsxs("p", { role: "status", className: "text-sm text-destructive", children: [credentials.length, " \u6761\u91CC\u6709 ", credentials.filter((credential) => credential.enabled && credential.health === 'healthy' && credential.lastFailureCode !== 'quota_exhausted').length, " \u6761\u5DF2\u9A8C\u8BC1\u53EF\u7528"] }) : null, credentials.some((credential) => credential.enabled && credential.lastFailureCode === 'upstream_unavailable') ?
|
|
73
73
|
_jsx("p", { role: "status", className: "text-sm text-destructive", children: "\u8FDE\u63A5\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u7A0D\u540E\u91CD\u8BD5\u3002" }) : null, _jsxs("div", { "aria-label": "\u51ED\u636E\u5217\u8868", className: "space-y-2", children: [_jsx(AiSortableCredentialList, { credentials: orderedCredentials, disabled: busy || disabled, onMove: onReorderCredentials ? (from, to) => {
|
|
74
74
|
const offering = offerings.find((item) => item.id === orderedCredentials[from]?.offeringId);
|
|
@@ -9,7 +9,7 @@ export interface AiGatewayKeyBinding {
|
|
|
9
9
|
* carries only the purpose declared at creation, the client it was written
|
|
10
10
|
* into, when it was last used, and the one action that still applies: destroy.
|
|
11
11
|
*/
|
|
12
|
-
export declare function AiGatewayKeyRow({ record, busy, confirming, configurationStatus, onReissue, onEnable, onRequestDestroy, onCancelDestroy, onDestroy, }: {
|
|
12
|
+
export declare function AiGatewayKeyRow({ record, busy, confirming, configurationStatus, onReissue, onEnable, onTest, testing, onRequestDestroy, onCancelDestroy, onDestroy, }: {
|
|
13
13
|
record: GatewayKeyRecord;
|
|
14
14
|
busy: boolean;
|
|
15
15
|
/** §7.3:销毁前先说明影响,第二次点击才真正删除。 */
|
|
@@ -17,6 +17,8 @@ export declare function AiGatewayKeyRow({ record, busy, confirming, configuratio
|
|
|
17
17
|
configurationStatus?: AiClientConfigurationStatus;
|
|
18
18
|
onReissue?: () => void;
|
|
19
19
|
onEnable?: () => void;
|
|
20
|
+
onTest?: () => void;
|
|
21
|
+
testing?: boolean;
|
|
20
22
|
onRequestDestroy: () => void;
|
|
21
23
|
onCancelDestroy: () => void;
|
|
22
24
|
onDestroy: () => void;
|
|
@@ -7,14 +7,14 @@ import { AI_CLIENT_LABELS, AI_CONNECTIONS_CLIENTS, AiClientIcon, } from './AiCli
|
|
|
7
7
|
* carries only the purpose declared at creation, the client it was written
|
|
8
8
|
* into, when it was last used, and the one action that still applies: destroy.
|
|
9
9
|
*/
|
|
10
|
-
export function AiGatewayKeyRow({ record, busy, confirming = false, configurationStatus, onReissue, onEnable, onRequestDestroy, onCancelDestroy, onDestroy, }) {
|
|
10
|
+
export function AiGatewayKeyRow({ record, busy, confirming = false, configurationStatus, onReissue, onEnable, onTest, testing = false, onRequestDestroy, onCancelDestroy, onDestroy, }) {
|
|
11
11
|
const label = record.name || '未命名 Xpod 密钥';
|
|
12
12
|
const binding = gatewayKeyBinding(record);
|
|
13
13
|
// Credentials issued through CSS are revoked by client id; a record without
|
|
14
14
|
// one cannot be destroyed from here, so the row says so instead of offering
|
|
15
15
|
// an action that always fails.
|
|
16
16
|
const destroyable = record.kind !== 'client-credentials' || Boolean(record.clientCredentialId);
|
|
17
|
-
return (_jsxs("li", { "data-key-id": record.id, "data-key-binding": record.appliedTo ? 'bound' : 'unbound', className: "flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-border/60 px-4 py-3 first:rounded-t-xl last:rounded-b-xl last:border-b-0", children: [_jsxs("dl", { className: "flex min-w-0 flex-1 basis-64 flex-wrap items-center gap-x-6 gap-y-2", children: [_jsx(KeyField, { label: "\u540D\u79F0", className: "min-w-0 flex-1 basis-28", children: _jsx("span", { className: "block truncate text-sm font-medium text-foreground/90", title: label, children: label }) }), _jsx(KeyField, { label: "\u5E94\u7528\u5230", className: "min-w-0 flex-1 basis-28", children: _jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground", children: [binding.clientId ? _jsx(AiClientIcon, { client: binding.clientId, className: "h-3.5 w-3.5" }) : null, _jsx("span", { className: "truncate", title: binding.label, children: binding.label })] }) }), _jsx(KeyField, { label: "\u6700\u540E\u4F7F\u7528", className: "shrink-0", children: _jsx("span", { className: "block text-xs text-muted-foreground", children: record.lastUsedAt ? formatTimestamp(record.lastUsedAt) : '暂无调用记录' }) }), _jsx(KeyField, { label: "\u8303\u56F4", children: _jsx("span", { className: "text-xs", children: "\u8303\u56F4\uFF1A\u6574\u4E2A Pod" }) }), record.appliedAt ? _jsx(KeyField, { label: "\u5199\u5165\u65F6\u95F4", children: _jsx("span", { className: "text-xs", children: formatTimestamp(record.appliedAt) }) }) : null] }), configurationStatus && (configurationStatus.status === 'unavailable' || (record.fingerprint && configurationStatus.appliedKeyFingerprint === record.fingerprint)) && configurationStatus.status !== 'configured' && configurationStatus.status !== 'notConfigured' ? (_jsx("p", { role: "status", className: "order-last w-full text-sm text-muted-foreground", children: configurationStatusLabel(configurationStatus.status) })) : null, record.plaintextAvailable === false && onReissue ? _jsxs("div", { className: "order-last flex w-full flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [_jsx("span", { children: "\u5BC6\u94A5\u539F\u6587\u6CA1\u6709\u4FDD\u5B58\uFF0C\u4E0D\u80FD\u518D\u663E\u793A" }), _jsx(Button, { variant: "ghost", size: "sm", disabled: busy, onClick: onReissue, children: "\u91CD\u65B0\u7B7E\u53D1" })] }) : null, record.disabledAt ? _jsxs("div", { className: "order-last flex w-full items-center gap-2 text-sm text-muted-foreground", children: ["\u5DF2\u505C\u7528", onEnable ? _jsx(Button, { size: "sm", variant: "outline", disabled: busy, onClick: onEnable, children: "\u542F\u7528" }) : null] }) : null, destroyable ? confirming ? (_jsxs("div", { "data-testid": "gateway-key-destroy-confirm", className: "mt-2 shrink-0 rounded-lg border border-destructive/40 bg-destructive/5 p-3 text-xs", children: [_jsxs("p", { className: "text-foreground", children: ["\u5220\u9664\u540E\uFF0C\u6B63\u5728\u4F7F\u7528\u8FD9\u628A\u5BC6\u94A5\u7684\u5BA2\u6237\u7AEF\u4F1A\u7ACB\u5373\u5931\u6548\u3002\u5DF2\u8BB0\u5F55\u7684\u5E94\u7528\uFF1A", binding.label, "\u3002"] }), _jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [_jsx(Button, { variant: "secondary", size: "sm", className: "h-8 text-xs", "aria-label": `确认删除 ${label}`, disabled: busy, onClick: onDestroy, children: "\u786E\u8BA4\u5220\u9664" }), _jsx(Button, { variant: "ghost", size: "sm", className: "h-8 text-xs", "aria-label": `取消删除 ${label}`, disabled: busy, onClick: onCancelDestroy, children: "\u53D6\u6D88" })] })] })) : (_jsxs(Button, { variant: "ghost", size: "sm", className: "h-8 shrink-0 gap-1.5 text-xs text-muted-foreground hover:text-destructive", "aria-label": `销毁 ${label}`, title: `销毁 ${label}`, disabled: busy, onClick: onRequestDestroy, children: [_jsx(Trash2, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "\u9500\u6BC1"] })) : (_jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: "\u7F3A\u5C11\u5BC6\u94A5\u6807\u8BC6\uFF0C\u65E0\u6CD5\u5728\u6B64\u9500\u6BC1" }))] }));
|
|
17
|
+
return (_jsxs("li", { "data-key-id": record.id, "data-key-binding": record.appliedTo ? 'bound' : 'unbound', className: "flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-border/60 px-4 py-3 first:rounded-t-xl last:rounded-b-xl last:border-b-0", children: [_jsxs("dl", { className: "flex min-w-0 flex-1 basis-64 flex-wrap items-center gap-x-6 gap-y-2", children: [_jsx(KeyField, { label: "\u540D\u79F0", className: "min-w-0 flex-1 basis-28", children: _jsx("span", { className: "block truncate text-sm font-medium text-foreground/90", title: label, children: label }) }), _jsx(KeyField, { label: "\u5E94\u7528\u5230", className: "min-w-0 flex-1 basis-28", children: _jsxs("span", { className: "flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground", children: [binding.clientId ? _jsx(AiClientIcon, { client: binding.clientId, className: "h-3.5 w-3.5" }) : null, _jsx("span", { className: "truncate", title: binding.label, children: binding.label })] }) }), _jsx(KeyField, { label: "\u6700\u540E\u4F7F\u7528", className: "shrink-0", children: _jsx("span", { className: "block text-xs text-muted-foreground", children: record.lastUsedAt ? formatTimestamp(record.lastUsedAt) : '暂无调用记录' }) }), _jsx(KeyField, { label: "\u8303\u56F4", children: _jsx("span", { className: "text-xs", children: "\u8303\u56F4\uFF1A\u6574\u4E2A Pod" }) }), record.appliedAt ? _jsx(KeyField, { label: "\u5199\u5165\u65F6\u95F4", children: _jsx("span", { className: "text-xs", children: formatTimestamp(record.appliedAt) }) }) : null] }), configurationStatus && (configurationStatus.status === 'unavailable' || (record.fingerprint && configurationStatus.appliedKeyFingerprint === record.fingerprint)) && configurationStatus.status !== 'configured' && configurationStatus.status !== 'notConfigured' ? (_jsx("p", { role: "status", className: "order-last w-full text-sm text-muted-foreground", children: configurationStatusLabel(configurationStatus.status) })) : null, onTest ? _jsx(Button, { variant: "outline", size: "sm", disabled: busy, onClick: onTest, children: testing ? '正在测试…' : '测试一次' }) : null, record.plaintextAvailable === false && onReissue ? _jsxs("div", { className: "order-last flex w-full flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [_jsx("span", { children: "\u5BC6\u94A5\u539F\u6587\u6CA1\u6709\u4FDD\u5B58\uFF0C\u4E0D\u80FD\u518D\u663E\u793A" }), _jsx(Button, { variant: "ghost", size: "sm", disabled: busy, onClick: onReissue, children: "\u91CD\u65B0\u7B7E\u53D1" })] }) : null, record.disabledAt ? _jsxs("div", { className: "order-last flex w-full items-center gap-2 text-sm text-muted-foreground", children: ["\u5DF2\u505C\u7528", onEnable ? _jsx(Button, { size: "sm", variant: "outline", disabled: busy, onClick: onEnable, children: "\u542F\u7528" }) : null] }) : null, destroyable ? confirming ? (_jsxs("div", { "data-testid": "gateway-key-destroy-confirm", className: "mt-2 shrink-0 rounded-lg border border-destructive/40 bg-destructive/5 p-3 text-xs", children: [_jsxs("p", { className: "text-foreground", children: ["\u5220\u9664\u540E\uFF0C\u6B63\u5728\u4F7F\u7528\u8FD9\u628A\u5BC6\u94A5\u7684\u5BA2\u6237\u7AEF\u4F1A\u7ACB\u5373\u5931\u6548\u3002\u5DF2\u8BB0\u5F55\u7684\u5E94\u7528\uFF1A", binding.label, "\u3002"] }), _jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [_jsx(Button, { variant: "secondary", size: "sm", className: "h-8 text-xs", "aria-label": `确认删除 ${label}`, disabled: busy, onClick: onDestroy, children: "\u786E\u8BA4\u5220\u9664" }), _jsx(Button, { variant: "ghost", size: "sm", className: "h-8 text-xs", "aria-label": `取消删除 ${label}`, disabled: busy, onClick: onCancelDestroy, children: "\u53D6\u6D88" })] })] })) : (_jsxs(Button, { variant: "ghost", size: "sm", className: "h-8 shrink-0 gap-1.5 text-xs text-muted-foreground hover:text-destructive", "aria-label": `销毁 ${label}`, title: `销毁 ${label}`, disabled: busy, onClick: onRequestDestroy, children: [_jsx(Trash2, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "\u9500\u6BC1"] })) : (_jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: "\u7F3A\u5C11\u5BC6\u94A5\u6807\u8BC6\uFF0C\u65E0\u6CD5\u5728\u6B64\u9500\u6BC1" }))] }));
|
|
18
18
|
}
|
|
19
19
|
/** Where the credential is in effect: the client application and the device. */
|
|
20
20
|
export function gatewayKeyBinding(record) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { Badge, Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Input, NativeSelect, InlineNotice, SectionHeader, TooltipProvider, dismissToast, toast, } from '@undefineds.co/shared-ui';
|
|
3
|
+
import { Badge, Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, Input, NativeSelect, InlineNotice, ListSurface, SectionHeader, TooltipProvider, dismissToast, toast, } from '@undefineds.co/shared-ui';
|
|
4
4
|
import { Check, Loader2, Plus, Settings2 } from 'lucide-react';
|
|
5
5
|
import { AI_CONNECTIONS_CLIENTS, AI_CLIENT_LABELS, manualConfigurationText, } from './AiClientConfigurationSection.js';
|
|
6
6
|
import { aiConnectionsErrorMessage } from './error-wording.js';
|
|
@@ -27,10 +27,20 @@ const XPOD_CREDENTIAL_NOTE = '密钥允许客户端以你的 WebID 访问整个
|
|
|
27
27
|
*/
|
|
28
28
|
export function AiGatewayKeysSection({ client, onAuthorizeService, clientConfigurationBridge, gatewayModels, modelSelection, liveRevision = 0, }) {
|
|
29
29
|
const bridge = clientConfigurationBridge?.available === false ? undefined : clientConfigurationBridge;
|
|
30
|
+
const [busyKeyId, setBusyKeyId] = useState();
|
|
31
|
+
const operation = useRef(false);
|
|
32
|
+
const verificationGeneration = useRef(0);
|
|
33
|
+
const [sessionPlans, setSessionPlans] = useState({});
|
|
34
|
+
const [testingKeyId, setTestingKeyId] = useState();
|
|
30
35
|
const [clientStatuses, setClientStatuses] = useState({});
|
|
31
36
|
useEffect(() => {
|
|
32
37
|
let active = true;
|
|
38
|
+
verificationGeneration.current += 1;
|
|
39
|
+
operation.current = false;
|
|
40
|
+
setBusyKeyId(undefined);
|
|
41
|
+
setTestingKeyId(undefined);
|
|
33
42
|
setClientStatuses({});
|
|
43
|
+
setSessionPlans({});
|
|
34
44
|
if (bridge) {
|
|
35
45
|
for (const id of AI_CONNECTIONS_CLIENTS) {
|
|
36
46
|
void bridge.inspect(id).then((status) => {
|
|
@@ -39,8 +49,8 @@ export function AiGatewayKeysSection({ client, onAuthorizeService, clientConfigu
|
|
|
39
49
|
}).catch(() => undefined);
|
|
40
50
|
}
|
|
41
51
|
}
|
|
42
|
-
return () => { active = false; };
|
|
43
|
-
}, [bridge]);
|
|
52
|
+
return () => { active = false; verificationGeneration.current += 1; };
|
|
53
|
+
}, [bridge, client]);
|
|
44
54
|
const [keys, setKeys] = useState([]);
|
|
45
55
|
const [confirmingKeyId, setConfirmingKeyId] = useState(undefined);
|
|
46
56
|
const [loadError, setLoadError] = useState();
|
|
@@ -57,10 +67,8 @@ export function AiGatewayKeysSection({ client, onAuthorizeService, clientConfigu
|
|
|
57
67
|
const [issuedClient, setIssuedClient] = useState();
|
|
58
68
|
const [appliedClient, setAppliedClient] = useState();
|
|
59
69
|
const [applying, setApplying] = useState(false);
|
|
60
|
-
const [busyKeyId, setBusyKeyId] = useState();
|
|
61
70
|
const [error, setError] = useState();
|
|
62
71
|
const plaintexts = useRef(new Map());
|
|
63
|
-
const operation = useRef(false);
|
|
64
72
|
const notification = useRef(undefined);
|
|
65
73
|
const notify = useCallback((options) => {
|
|
66
74
|
if (notification.current)
|
|
@@ -218,6 +226,7 @@ export function AiGatewayKeysSection({ client, onAuthorizeService, clientConfigu
|
|
|
218
226
|
targetHash: plan.confirmation.targetHash,
|
|
219
227
|
} } : {}),
|
|
220
228
|
});
|
|
229
|
+
setSessionPlans(current => ({ ...current, [plan.client]: issued.fingerprint ? { planId: plan.planId, fingerprint: issued.fingerprint } : undefined }));
|
|
221
230
|
setAppliedClient(plan.client);
|
|
222
231
|
setClientStatuses((current) => ({ ...current, [plan.client]: { status: 'unverifiable', appliedKeyFingerprint: issued.fingerprint } }));
|
|
223
232
|
// The record now points at the client the wrapper was written into.
|
|
@@ -235,6 +244,51 @@ export function AiGatewayKeysSection({ client, onAuthorizeService, clientConfigu
|
|
|
235
244
|
setApplying(false);
|
|
236
245
|
}
|
|
237
246
|
};
|
|
247
|
+
const verificationPlan = (record) => {
|
|
248
|
+
const clientId = AI_CONNECTIONS_CLIENTS.find(id => id === record.appliedTo);
|
|
249
|
+
if (!bridge || !clientId || record.disabledAt || !record.fingerprint)
|
|
250
|
+
return;
|
|
251
|
+
const status = clientStatuses[clientId];
|
|
252
|
+
const plan = sessionPlans[clientId];
|
|
253
|
+
if (status?.status !== 'unverifiable' || status.appliedKeyFingerprint !== record.fingerprint || plan?.fingerprint !== record.fingerprint)
|
|
254
|
+
return;
|
|
255
|
+
return { clientId, ...plan };
|
|
256
|
+
};
|
|
257
|
+
const testConfiguration = async (record) => {
|
|
258
|
+
const plan = verificationPlan(record);
|
|
259
|
+
if (!bridge || !plan || operation.current)
|
|
260
|
+
return;
|
|
261
|
+
const generation = verificationGeneration.current;
|
|
262
|
+
operation.current = true;
|
|
263
|
+
setBusyKeyId(record.id);
|
|
264
|
+
setTestingKeyId(record.id);
|
|
265
|
+
try {
|
|
266
|
+
let status = await bridge.verify({ client: plan.clientId, planId: plan.planId });
|
|
267
|
+
if (generation !== verificationGeneration.current)
|
|
268
|
+
return;
|
|
269
|
+
if (status.status === 'configured' && !status.appliedKeyFingerprint) {
|
|
270
|
+
const inspected = await bridge.inspect(plan.clientId);
|
|
271
|
+
if (generation !== verificationGeneration.current)
|
|
272
|
+
return;
|
|
273
|
+
status = { ...status, ...inspected };
|
|
274
|
+
}
|
|
275
|
+
const fingerprint = status.appliedKeyFingerprint ?? (status.status !== 'configured' ? plan.fingerprint : undefined);
|
|
276
|
+
if (fingerprint !== plan.fingerprint)
|
|
277
|
+
throw new Error('Configuration key changed');
|
|
278
|
+
setClientStatuses(current => ({ ...current, [plan.clientId]: { ...status, appliedKeyFingerprint: fingerprint } }));
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
if (generation === verificationGeneration.current)
|
|
282
|
+
notify({ variant: 'destructive', description: '客户端配置测试失败,请重试。', duration: 8000 });
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
if (generation === verificationGeneration.current) {
|
|
286
|
+
operation.current = false;
|
|
287
|
+
setBusyKeyId(undefined);
|
|
288
|
+
setTestingKeyId(undefined);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
238
292
|
const destroy = async (record) => {
|
|
239
293
|
if (operation.current)
|
|
240
294
|
return;
|
|
@@ -260,24 +314,24 @@ export function AiGatewayKeysSection({ client, onAuthorizeService, clientConfigu
|
|
|
260
314
|
}
|
|
261
315
|
};
|
|
262
316
|
return (_jsx(TooltipProvider, { children: _jsx(Dialog, { open: showCreate, onOpenChange: (open) => { if (!open && (creating || applying))
|
|
263
|
-
return; setShowCreate(open); }, children: _jsxs("section", { className: "space-y-8", "aria-label": "Xpod \u5BC6\u94A5", children: [_jsx(AiProviderHeader, { name: "Xpod", mark: "XP", avatar: XPOD_AVATAR, infoLabel: "Xpod \u8BF4\u660E", infoLines: [XPOD_DESCRIPTION, XPOD_CREDENTIAL_NOTE], link: { href: client.apiBase, label: '访问 Xpod' }, badge: (_jsx(Badge, { variant: keys.length > 0 ? 'default' : 'secondary', children: keys.length > 0 ? `已签发 ${keys.length} 个` : '未配置' })) }), _jsxs("section", { className: "space-y-3", "aria-label": "\u5F53\u524D\u8FDE\u63A5", children: [_jsx(SectionHeader, { level: 3, title: _jsxs(_Fragment, { children: [_jsx(Settings2, { "aria-hidden": "true", className: "h-4 w-4 text-primary" }), "\u5F53\u524D\u8FDE\u63A5"] }), titleClassName: "flex items-center gap-2 font-medium text-foreground/90", actions: (_jsx(DialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", "aria-label": "\u65B0\u5EFA Xpod \u5BC6\u94A5", disabled: creating || loading, onClick: openCreate, children: [_jsx(Plus, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "Xpod \u5BC6\u94A5"] }) })) }), loadError ? _jsx(InlineNotice, { tone: "destructive", role: "alert", action: serviceAccessMissing && onAuthorizeService ? _jsx(Button, { size: "sm", disabled: authorizing, onClick: () => void authorize(), children: authorizing ? '正在授权…' : '允许 Xpod 访问' }) : undefined, children: loadError }) : null, loading ? (_jsxs("div", { className: "flex items-center gap-2 py-6 text-sm text-muted-foreground", children: [_jsx(Loader2, { "aria-hidden": "true", className: "h-4 w-4 animate-spin" }), "\u6B63\u5728\u8BFB\u53D6 Xpod \u5BC6\u94A5"] })) : loadError ? null : keys.length === 0 ? (_jsx("p", { className: "py-2 text-xs text-muted-foreground", children: "\u5C1A\u672A\u7B7E\u53D1 Xpod \u5BC6\u94A5" })) : (_jsx("ul", { "aria-label": "Xpod \u5BC6\u94A5 \u5217\u8868",
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
317
|
+
return; setShowCreate(open); }, children: _jsxs("section", { className: "space-y-8", "aria-label": "Xpod \u5BC6\u94A5", children: [_jsx(AiProviderHeader, { name: "Xpod", mark: "XP", avatar: XPOD_AVATAR, infoLabel: "Xpod \u8BF4\u660E", infoLines: [XPOD_DESCRIPTION, XPOD_CREDENTIAL_NOTE], link: { href: client.apiBase, label: '访问 Xpod' }, badge: (_jsx(Badge, { variant: keys.length > 0 ? 'default' : 'secondary', children: keys.length > 0 ? `已签发 ${keys.length} 个` : '未配置' })) }), _jsxs("section", { className: "space-y-3", "aria-label": "\u5F53\u524D\u8FDE\u63A5", children: [_jsx(SectionHeader, { level: 3, title: _jsxs(_Fragment, { children: [_jsx(Settings2, { "aria-hidden": "true", className: "h-4 w-4 text-primary" }), "\u5F53\u524D\u8FDE\u63A5"] }), titleClassName: "flex items-center gap-2 font-medium text-foreground/90", actions: (_jsx(DialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", "aria-label": "\u65B0\u5EFA Xpod \u5BC6\u94A5", disabled: creating || loading, onClick: openCreate, children: [_jsx(Plus, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "Xpod \u5BC6\u94A5"] }) })) }), loadError ? _jsx(InlineNotice, { tone: "destructive", role: "alert", action: serviceAccessMissing && onAuthorizeService ? _jsx(Button, { size: "sm", disabled: authorizing, onClick: () => void authorize(), children: authorizing ? '正在授权…' : '允许 Xpod 访问' }) : undefined, children: loadError }) : null, loading ? (_jsxs("div", { className: "flex items-center gap-2 py-6 text-sm text-muted-foreground", children: [_jsx(Loader2, { "aria-hidden": "true", className: "h-4 w-4 animate-spin" }), "\u6B63\u5728\u8BFB\u53D6 Xpod \u5BC6\u94A5"] })) : loadError ? null : keys.length === 0 ? (_jsx("p", { className: "py-2 text-xs text-muted-foreground", children: "\u5C1A\u672A\u7B7E\u53D1 Xpod \u5BC6\u94A5" })) : (_jsx(ListSurface, { asChild: true, className: "divide-y-0 border-border/70 bg-transparent", children: _jsx("ul", { "aria-label": "Xpod \u5BC6\u94A5 \u5217\u8868", children: keys.map((record) => (_jsx(AiGatewayKeyRow, { record: record, busy: Boolean(busyKeyId), confirming: confirmingKeyId === record.id, configurationStatus: record.appliedTo && AI_CONNECTIONS_CLIENTS.includes(record.appliedTo)
|
|
318
|
+
? clientStatuses[record.appliedTo] : undefined, onTest: verificationPlan(record) ? () => void testConfiguration(record) : undefined, testing: testingKeyId === record.id, onEnable: () => {
|
|
319
|
+
if (operation.current)
|
|
320
|
+
return;
|
|
321
|
+
operation.current = true;
|
|
322
|
+
setBusyKeyId(record.id);
|
|
323
|
+
void client.updateGatewayKey(record.id, { enabled: true }).then((updated) => {
|
|
324
|
+
setKeys((current) => current.map((item) => item.id === record.id ? updated : item));
|
|
325
|
+
}).catch(notifyError).finally(() => { operation.current = false; setBusyKeyId(undefined); });
|
|
326
|
+
}, onReissue: () => {
|
|
327
|
+
beginAnother();
|
|
328
|
+
setName(record.name ?? DEFAULT_KEY_NAME);
|
|
329
|
+
setPurpose(AI_CONNECTIONS_CLIENTS.find((id) => id === record.appliedTo) ?? '');
|
|
330
|
+
setShowCreate(true);
|
|
331
|
+
}, onRequestDestroy: () => setConfirmingKeyId(record.id), onCancelDestroy: () => setConfirmingKeyId((current) => (current === record.id ? undefined : current)), onDestroy: () => {
|
|
332
|
+
setConfirmingKeyId(undefined);
|
|
333
|
+
void destroy(record);
|
|
334
|
+
} }, record.id))) }) }))] }), _jsx(AiGatewayModelsSection, { models: gatewayModels, selection: modelSelection }), _jsxs(DialogContent, { className: "sm:max-w-md", "aria-describedby": undefined, children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: issued ? 'Xpod 密钥 已签发' : '新建 Xpod 密钥' }) }), issued ? (_jsxs("div", { className: "space-y-5", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs("p", { className: "text-sm", children: ["\u5DF2\u7B7E\u53D1\u300C", issued.name || '未命名 Xpod 密钥', "\u300D\uFF0C\u7528\u9014\uFF1A", issuedClient ? AI_CLIENT_LABELS[issuedClient] : '只复制', "\u3002"] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "\u5BC6\u94A5\u53EA\u663E\u793A\u8FD9\u4E00\u6B21\uFF0C\u8BF7\u7ACB\u5373\u590D\u5236\u3002\u8303\u56F4\uFF1A\u6574\u4E2A Pod\u3002" })] }), issued.maskedHint ? (_jsx("code", { className: "block truncate rounded-md border border-border/60 bg-muted/30 px-3 py-2 font-mono text-xs text-muted-foreground", children: issued.maskedHint })) : null, _jsx(AiEndpointList, { endpoints: xpodProtocolEndpoints(client.apiBase), copy: true, display: "protocol" }), !bridge ? (_jsx("p", { className: "text-xs text-muted-foreground", children: "\u5F53\u524D Web \u73AF\u5883\u65E0\u6CD5\u81EA\u52A8\u5199\u5165\u5BA2\u6237\u7AEF\u914D\u7F6E\uFF0C\u8BF7\u590D\u5236\u540E\u624B\u52A8\u7C98\u8D34\uFF1B\u81EA\u52A8\u5E94\u7528\u9700\u8981\u672C\u673A\u8FDE\u63A5\u3002" })) : null, error ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(AiCopyButton, { value: () => sessionPlaintext(issued, UNPERSISTED_COPY_MESSAGE), label: "Xpod \u5BC6\u94A5", text: "\u590D\u5236 Xpod \u5BC6\u94A5", disabled: applying, iconClassName: "mr-1.5 h-3.5 w-3.5", copiedIconClassName: "mr-1.5 h-3.5 w-3.5 text-emerald-600", onError: (cause) => setError(errorMessage(cause)) }), issuedClient ? _jsx(AiCopyButton, { value: () => manualConfigurationText(issuedClient, client.apiBase, sessionPlaintext(issued, UNPERSISTED_COPY_MESSAGE)), label: `${AI_CLIENT_LABELS[issuedClient]} 配置`, text: `复制 ${AI_CLIENT_LABELS[issuedClient]} 配置`, disabled: applying, iconClassName: "mr-1.5 h-3.5 w-3.5", copiedIconClassName: "mr-1.5 h-3.5 w-3.5 text-emerald-600", onError: (cause) => setError(errorMessage(cause)) }) : null, bridge && issuedClient ? (appliedClient ? (_jsxs("span", { role: "status", className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Check, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "\u5DF2\u5E94\u7528\u5230 ", AI_CLIENT_LABELS[appliedClient]] })) : (_jsxs(Button, { type: "button", size: "sm", disabled: applying || clientStatuses[issuedClient]?.status === 'unavailable', "aria-label": `写入 ${AI_CLIENT_LABELS[issuedClient]}`, onClick: () => void apply(), children: [applying ? _jsx(Loader2, { "aria-hidden": "true", className: "mr-2 h-4 w-4 animate-spin" }) : null, applying ? '正在应用…' : `写入 ${AI_CLIENT_LABELS[issuedClient]}`] }))) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", disabled: applying, onClick: beginAnother, children: "\u518D\u5EFA\u4E00\u4E2A" }), _jsx(Button, { type: "button", variant: "outline", onClick: () => setShowCreate(false), children: "\u5B8C\u6210" })] })] })) : (_jsxs("form", { className: "space-y-5", onSubmit: (event) => { event.preventDefault(); if (!creating)
|
|
281
335
|
void create(); }, children: [_jsxs("label", { className: "block space-y-2", children: [_jsx("span", { className: "text-sm font-medium", children: "\u540D\u79F0" }), _jsx(Input, { autoFocus: true, "aria-label": "Xpod \u5BC6\u94A5 \u540D\u79F0", value: name, disabled: creating, onChange: (event) => setName(event.target.value) })] }), _jsxs("label", { className: "block space-y-2", children: [_jsx("span", { className: "text-sm font-medium", children: "\u7ED9\u54EA\u4E2A\u5BA2\u6237\u7AEF\u7528" }), _jsxs(NativeSelect, { "aria-label": "Xpod \u5BC6\u94A5 \u7528\u9014", value: purpose, disabled: creating, onChange: (event) => setPurpose(event.target.value), children: [_jsx("option", { value: "", children: "\u4E0D\u5199\u5165\uFF0C\u53EA\u590D\u5236" }), AI_CONNECTIONS_CLIENTS.map((clientId) => (_jsxs("option", { value: clientId, children: [AI_CLIENT_LABELS[clientId], bridge ? (clientStatuses[clientId]?.status === 'unavailable' ? ' · 未安装' : clientStatuses[clientId] ? ' · 可配置' : ' · 检测中') : ' · 只复制配置'] }, clientId)))] })] }), _jsx("p", { className: "text-xs leading-normal text-muted-foreground", children: "\u5BA2\u6237\u7AEF\u9ED8\u8BA4\u8DDF\u968F Pod \u7684\u667A\u80FD\u6A21\u578B\u3002" }), error ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", disabled: creating, onClick: () => setShowCreate(false), children: "\u53D6\u6D88" }), _jsxs(Button, { type: "submit", "aria-label": "\u521B\u5EFA Xpod \u5BC6\u94A5", disabled: creating || !name.trim(), children: [creating ? _jsx(Loader2, { "aria-hidden": "true", className: "mr-2 h-4 w-4 animate-spin" }) : null, creating ? '正在创建…' : '创建 Xpod 密钥'] })] })] }))] })] }) }) }));
|
|
282
336
|
}
|
|
283
337
|
function errorMessage(error) {
|
|
@@ -35,6 +35,7 @@ export declare function AiProviderCard({ definition, product, status, accountLab
|
|
|
35
35
|
}) => Promise<void>;
|
|
36
36
|
onCreateLocalCredential?: (offering: AiProviderOffering, method?: AiProviderAuthorizationMethod) => Promise<void>;
|
|
37
37
|
onUpdateCredential?: (credential: AiProviderCredentialSummary, patch: {
|
|
38
|
+
apiKey?: string;
|
|
38
39
|
label?: string;
|
|
39
40
|
enabled?: boolean;
|
|
40
41
|
priority?: number;
|