@undefineds.co/xpod 0.4.3 → 0.4.5
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/config/cloud.json +1 -1
- package/config/main.json +36 -2
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +3 -0
- package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +1 -1
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +2 -1
- package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +101 -32
- package/dist/api/ai-gateway/connect/DeviceCodeProtocol.d.ts +80 -0
- package/dist/api/ai-gateway/connect/DeviceCodeProtocol.js +3 -0
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.d.ts +38 -0
- package/dist/api/ai-gateway/connect/FileSessionImportAdapter.js +63 -0
- package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.d.ts +24 -0
- package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.js +136 -0
- package/dist/api/ai-gateway/connect/OAuthConnectAdapter.d.ts +2 -0
- package/dist/api/ai-gateway/connect/OAuthConnectAdapter.js +2 -1
- package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.d.ts +17 -13
- package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.js +52 -19
- package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.d.ts +4 -28
- package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.js +7 -58
- package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.d.ts +4 -0
- package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.js +116 -0
- package/dist/api/ai-gateway/connect/SessionImportProfiles.d.ts +10 -0
- package/dist/api/ai-gateway/connect/SessionImportProfiles.js +112 -0
- package/dist/api/ai-gateway/connect/index.d.ts +125 -23
- package/dist/api/ai-gateway/connect/index.js +1277 -260
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.d.ts +4 -1
- package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +154 -19
- package/dist/api/ai-gateway/providers/DeepSeekRuntimeAdapter.js +10 -6
- package/dist/api/ai-gateway/providers/ModelsDevCatalog.d.ts +7 -0
- package/dist/api/ai-gateway/providers/ModelsDevCatalog.js +25 -1
- package/dist/api/ai-gateway/providers/OfferingAuthorization.d.ts +24 -0
- package/dist/api/ai-gateway/providers/OfferingAuthorization.js +37 -0
- package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.d.ts +1 -1
- package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.js +5 -3
- package/dist/api/ai-gateway/providers/ProviderRegistry.d.ts +6 -3
- package/dist/api/ai-gateway/providers/ProviderRegistry.js +55 -16
- package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.d.ts +2 -0
- package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.js +37 -4
- package/dist/api/ai-gateway/providers/ProviderRuntimeRegistry.js +7 -0
- package/dist/api/ai-gateway/quota/SubscriptionQuotaAdapters.js +2 -1
- package/dist/api/ai-gateway/routing/ModelRouter.js +3 -1
- package/dist/api/container/common.js +30 -40
- package/dist/api/container/index.js +6 -2
- package/dist/api/container/routes.js +5 -22
- package/dist/api/handlers/AiClientConfigurationHandler.js +39 -1
- package/dist/api/handlers/AiGatewayManagementHandler.d.ts +3 -0
- package/dist/api/handlers/AiGatewayManagementHandler.js +173 -58
- package/dist/api/service/AiClientConfigurationService.d.ts +4 -15
- package/dist/api/service/AiClientConfigurationService.js +17 -64
- package/dist/cli/commands/start.js +2 -0
- package/dist/components/components.jsonld +1 -0
- package/dist/components/context.jsonld +80 -0
- package/dist/http/InternalPodDataHttpHandler.d.ts +3 -0
- package/dist/http/InternalPodDataHttpHandler.js +13 -2
- package/dist/http/InternalPodDataHttpHandler.jsonld +12 -0
- package/dist/identity/oidc/RememberedClientPromptFactory.d.ts +1 -1
- package/dist/identity/oidc/RememberedClientPromptFactory.js +5 -2
- package/dist/identity/oidc/RememberedConsentHandler.js +1 -1
- package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +1 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.js +26 -0
- package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +4 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.d.ts +10 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.js +21 -0
- package/dist/identity/oidc/SessionBoundIdentityProviderFactory.jsonld +172 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -2
- package/dist/runtime/component-parameter-keys.js +18 -2
- package/dist/storage/accessors/MixDataAccessor.js +6 -2
- package/dist/storage/rdf/PostgresRdfEngine.js +10 -2
- package/dist/storage/rdf/RdfContentTypes.js +4 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.d.ts +3 -0
- package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.js +13 -16
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsList.js +1 -3
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.d.ts +2 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.js +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +4 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +127 -57
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +6 -4
- package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +436 -83
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.d.ts +3 -2
- package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +212 -76
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +6 -4
- package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.js +58 -41
- package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.d.ts +4 -1
- package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.js +63 -4
- package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.d.ts +46 -6
- package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.js +143 -14
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.d.ts +19 -3
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.js +45 -7
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.d.ts +7 -1
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.js +305 -40
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/index.cjs +383 -50
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/json-env-adapter.js +3 -2
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/pi.js +12 -3
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.d.ts +2 -0
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.js +39 -4
- package/node_modules/@undefineds.co/ai-connections/dist/client-config/types.d.ts +8 -1
- package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.d.ts +2 -0
- package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.js +14 -0
- package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +2 -2
- package/node_modules/@undefineds.co/ai-connections/dist/controller.js +216 -57
- package/node_modules/@undefineds.co/ai-connections/dist/index.d.ts +3 -1
- package/node_modules/@undefineds.co/ai-connections/dist/index.js +16 -2
- package/node_modules/@undefineds.co/ai-connections/dist/provider-visuals.js +6 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/execution/ldp-executor.js +27 -6
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js +2 -2
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/sparql/builder/update-builder.js +12 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/builder.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/array.js +8 -0
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/default.js +9 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/inline.js +3 -3
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/execution/ldp-executor.js +27 -6
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js +2 -2
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/sparql/builder/update-builder.js +12 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/builder.js +1 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/array.js +8 -0
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/default.js +9 -1
- package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/inline.js +3 -3
- package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +31 -0
- package/package.json +2 -2
- package/static/app/assets/{index-BikOyGPu.js → index-BCZVzMpj.js} +44 -44
- package/static/app/assets/inrupt-smoke.js +2 -2
- package/static/app/assets/main.css +1 -1
- package/static/app/assets/main.js +10 -10
- package/static/auth-callback/assets/{AiConfigContext-DrCH6ckh.js → AiConfigContext-DVkh8Kg2.js} +1 -1
- package/static/auth-callback/assets/{AiConfigPage-DVrlSSdA.js → AiConfigPage-HWVWpe-P.js} +1 -1
- package/static/auth-callback/assets/{DocumentProcessingPanel-_Md-v2R2.js → DocumentProcessingPanel-DDq-mH17.js} +1 -1
- package/static/auth-callback/assets/{IndexLifecyclePanel-BbWwe23H.js → IndexLifecyclePanel-Cx4cjrdP.js} +1 -1
- package/static/auth-callback/assets/{IndexSubjectPanel-upH9MjU-.js → IndexSubjectPanel-DzSw8M9j.js} +1 -1
- package/static/auth-callback/assets/{ModelAssignmentsPanel-CVGH4t4g.js → ModelAssignmentsPanel-BsRxGc2p.js} +1 -1
- package/static/auth-callback/assets/ModelsPage-DEe1lo6Q.js +10 -0
- package/static/auth-callback/assets/{NetworkPage-Dcbrb1fd.js → NetworkPage-DBiEh1Oz.js} +1 -1
- package/static/auth-callback/assets/{PaneListHeader-DrY-1wBq.js → PaneListHeader-lEEgjyN9.js} +1 -1
- package/static/auth-callback/assets/{SearchIndexingPanel-Dw25y53v.js → SearchIndexingPanel-CZCUUGt6.js} +1 -1
- package/static/auth-callback/assets/{StatusSubjectPanel-BHlz9rNF.js → StatusSubjectPanel-21qdueMr.js} +1 -1
- package/static/auth-callback/assets/{StatusWorkspace-BH7exQP5.js → StatusWorkspace-R8RKsqVz.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsPage-BYLKnM9f.js → SystemSettingsPage-BQ4e-4Wy.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsSubjectPanel-DT34LKku.js → SystemSettingsSubjectPanel-BopXD7SE.js} +1 -1
- package/static/auth-callback/assets/{activity-DdULRcSI.js → activity-DSfmTGfY.js} +1 -1
- package/static/auth-callback/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/auth-callback/assets/{auth-callback-D7l2P25I.js → auth-callback-DleaitAS.js} +91 -91
- package/static/auth-callback/assets/auth-callback-vPSC2d2F.css +1 -0
- package/static/auth-callback/assets/{badge-j4zjFnmg.js → badge-B0aLu1sl.js} +1 -1
- package/static/auth-callback/assets/{download-DGXoks01.js → download-D_bFwdYJ.js} +1 -1
- package/static/auth-callback/assets/{external-link-CvbgXXaI.js → external-link-DRfjTaQu.js} +1 -1
- package/static/auth-callback/assets/{index-LRWvKJ2i.js → index-CG23cLYQ.js} +1 -1
- package/static/auth-callback/assets/{index-BOK3j1dz.js → index-CyV9ejwx.js} +1 -1
- package/static/auth-callback/assets/{index-browser-C1Qpe0g4.js → index-browser-D70paxIg.js} +1 -1
- package/static/auth-callback/assets/{key-round-CmgS5UdQ.js → key-round-BjV_mVYH.js} +1 -1
- package/static/auth-callback/assets/{rotate-ccw-c4CSG6Rq.js → rotate-ccw-CKoOAfse.js} +1 -1
- package/static/auth-callback/assets/{skeleton-ZPMTiRSM.js → skeleton-BZNjZaHx.js} +1 -1
- package/static/auth-callback/assets/{waypoints-xjyBflU5.js → waypoints-DoaJVzZv.js} +1 -1
- package/static/auth-callback/assets/{workspace-layout-B9nj6F5J.js → workspace-layout-CaP46_ZR.js} +1 -1
- package/static/auth-callback/auth-callback.html +2 -2
- package/static/dashboard/assets/{AiConfigContext-BWOOSYNr.js → AiConfigContext-BSrR27yD.js} +1 -1
- package/static/dashboard/assets/{AiConfigPage-CG2kQ9Wy.js → AiConfigPage-D65Z2LET.js} +1 -1
- package/static/dashboard/assets/{DocumentProcessingPanel-B4VXajfj.js → DocumentProcessingPanel-DyTe7Nei.js} +1 -1
- package/static/dashboard/assets/{IndexLifecyclePanel-CWCy0CC8.js → IndexLifecyclePanel-fK8ZGlrL.js} +1 -1
- package/static/dashboard/assets/{IndexSubjectPanel-CSgyQnG_.js → IndexSubjectPanel-CZPGor7v.js} +1 -1
- package/static/dashboard/assets/{ModelAssignmentsPanel-CdGIGidT.js → ModelAssignmentsPanel-CfYn5LMQ.js} +1 -1
- package/static/dashboard/assets/ModelsPage-djlJsghe.js +25 -0
- package/static/dashboard/assets/{NetworkPage-COia9NCH.js → NetworkPage-Coj6GnPl.js} +1 -1
- package/static/dashboard/assets/{PaneListHeader-SZWyr602.js → PaneListHeader-CUuFNSsZ.js} +1 -1
- package/static/dashboard/assets/{SearchIndexingPanel-D6PZtG6k.js → SearchIndexingPanel-CyJLioXS.js} +1 -1
- package/static/dashboard/assets/{StatusSubjectPanel-CxRD4kv0.js → StatusSubjectPanel-Cv26GSVC.js} +1 -1
- package/static/dashboard/assets/{StatusWorkspace-D3PfmY9-.js → StatusWorkspace-CDjFrsmB.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsPage-COg1MmMj.js → SystemSettingsPage-BuNOyNoa.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsSubjectPanel-BSVVB9Ol.js → SystemSettingsSubjectPanel-B2dasdez.js} +1 -1
- package/static/dashboard/assets/{activity-BJJx5cN-.js → activity-D1d2j_4Z.js} +1 -1
- package/static/dashboard/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/dashboard/assets/{badge-elmrFSCj.js → badge-BeAAv6sp.js} +1 -1
- package/static/dashboard/assets/{dashboard-IvQrNmru.js → dashboard-BnGrUGv8.js} +68 -68
- package/static/dashboard/assets/dashboard-vPSC2d2F.css +1 -0
- package/static/dashboard/assets/{download-C3FH2KzG.js → download-DfFGiZRn.js} +1 -1
- package/static/dashboard/assets/{external-link-Bx7F0uKb.js → external-link-IsjU_LC9.js} +1 -1
- package/static/dashboard/assets/{index-liRoRIP1.js → index-BWQTucPe.js} +1 -1
- package/static/dashboard/assets/{index-M4-kWiGz.js → index-Dj75OjnW.js} +1 -1
- package/static/dashboard/assets/{key-round-CXg0bKAj.js → key-round-BJ_9wmQ_.js} +1 -1
- package/static/dashboard/assets/{rotate-ccw-yxJKOBrX.js → rotate-ccw-D3JGiFbR.js} +1 -1
- package/static/dashboard/assets/{skeleton-x9jNGrQT.js → skeleton-CC4nc-z-.js} +1 -1
- package/static/dashboard/assets/{waypoints-CTKeRsGp.js → waypoints-h7sqFqiG.js} +1 -1
- package/static/dashboard/assets/{workspace-layout--YkgkS38.js → workspace-layout-ZAPTOHhs.js} +1 -1
- package/static/dashboard/dashboard.html +2 -2
- package/static/settings/assets/{AiConfigContext-CelxXFcQ.js → AiConfigContext-lGkWpvKP.js} +1 -1
- package/static/settings/assets/{AiConfigPage-BaP-EovM.js → AiConfigPage-BbZWIBZq.js} +1 -1
- package/static/settings/assets/{DocumentProcessingPanel-DzLZluFv.js → DocumentProcessingPanel-DvYdjT-V.js} +1 -1
- package/static/settings/assets/{IndexLifecyclePanel-Bzj1-_oI.js → IndexLifecyclePanel-W5OGQZj7.js} +1 -1
- package/static/settings/assets/{IndexSubjectPanel-B263QLUh.js → IndexSubjectPanel-g_Aa5soq.js} +1 -1
- package/static/settings/assets/{ModelAssignmentsPanel-BAdT4UR0.js → ModelAssignmentsPanel-COlMBKXj.js} +1 -1
- package/static/settings/assets/ModelsPage-mApKi-aw.js +10 -0
- package/static/settings/assets/{NetworkPage-BK6a7l4g.js → NetworkPage-BDT05JUq.js} +1 -1
- package/static/settings/assets/{PaneListHeader-BAS2ICPq.js → PaneListHeader-CByhNpMG.js} +1 -1
- package/static/settings/assets/{SearchIndexingPanel-Cb_p-NgW.js → SearchIndexingPanel-BFAyzXls.js} +1 -1
- package/static/settings/assets/{StatusSubjectPanel-CcjqUua6.js → StatusSubjectPanel-Bz_XZ3VU.js} +1 -1
- package/static/settings/assets/{StatusWorkspace-7Ibty3_k.js → StatusWorkspace-CeVypHcW.js} +1 -1
- package/static/settings/assets/{SystemSettingsPage-DPSz4dg4.js → SystemSettingsPage-Dh7cQIfx.js} +1 -1
- package/static/settings/assets/{SystemSettingsSubjectPanel-Cb4HkKT5.js → SystemSettingsSubjectPanel-CSnpciO0.js} +1 -1
- package/static/settings/assets/{activity-Qhsr5Pxu.js → activity-DXz3EfFN.js} +1 -1
- package/static/settings/assets/ai-connections-C03UWyPh.js +1 -0
- package/static/settings/assets/{badge-B1vf2qCl.js → badge-1ZA144i4.js} +1 -1
- package/static/settings/assets/{download-CYNt0aZ-.js → download-CUHimlYa.js} +1 -1
- package/static/settings/assets/{external-link-ClKb39NT.js → external-link-BYHSVj2Z.js} +1 -1
- package/static/settings/assets/{index-y8ew3MLM.js → index-DKPDMdW5.js} +1 -1
- package/static/settings/assets/{index-browser-DJhvgpNI.js → index-browser-BaEFz0e0.js} +1 -1
- package/static/settings/assets/{index-BgiIDSch.js → index-viSgSvbO.js} +1 -1
- package/static/settings/assets/{key-round-5d-DrJz-.js → key-round-COByPOg8.js} +1 -1
- package/static/settings/assets/{rotate-ccw-CHZY7kS5.js → rotate-ccw-BC2EN6cE.js} +1 -1
- package/static/settings/assets/{settings-D5VRWyx6.js → settings-DLH9XUoT.js} +86 -86
- package/static/settings/assets/settings-vPSC2d2F.css +1 -0
- package/static/settings/assets/{skeleton-FNabNxNE.js → skeleton-B0-FOhKL.js} +1 -1
- package/static/settings/assets/{waypoints-BnL_8ljo.js → waypoints-DGMsvqWo.js} +1 -1
- package/static/settings/assets/{workspace-layout-D5q3F59S.js → workspace-layout-B0SVrJIu.js} +1 -1
- package/static/settings/settings.html +2 -2
- package/static/auth-callback/assets/ModelsPage-BpkVFKbf.js +0 -10
- package/static/auth-callback/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/auth-callback/assets/auth-callback-01yz-UDU.css +0 -1
- package/static/dashboard/assets/ModelsPage-Chd5UK0D.js +0 -25
- package/static/dashboard/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/dashboard/assets/dashboard-01yz-UDU.css +0 -1
- package/static/settings/assets/ModelsPage-C0NFa1zq.js +0 -10
- package/static/settings/assets/ai-connections-Bm52UO7Y.js +0 -1
- package/static/settings/assets/settings-01yz-UDU.css +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { Button, Input, LoginConnectingView, LoginFailureView, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, cn, } from '@undefineds.co/shared-ui';
|
|
4
|
-
import {
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, Input, LoginConnectingView, LoginFailureView, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, cn, } from '@undefineds.co/shared-ui';
|
|
4
|
+
import { GripVertical, Eye, EyeOff, ExternalLink, KeyRound, Loader2, LogOut, Pause, Pencil, Play, PlugZap, Plus, RotateCw, Settings2, Trash2, } from 'lucide-react';
|
|
5
5
|
import { normalizeProxyUrl } from './ai-connections-client.js';
|
|
6
6
|
import { offeringTitle } from './offering-label.js';
|
|
7
7
|
import { AiQuotaCard } from './AiQuotaCard.js';
|
|
8
|
-
export function AiCredentialPoolSection({ definition, product, status, accountLabel, attempt, attemptOfferingId, apiKey, baseUrl = '', busy, disabled = false, error, suppressError = false, quotas = {}, onApiKeyChange, onBaseUrlChange, onBeginApiKey, onBeginOffering, onBeginBrowser, onSaveApiKey, onDisconnect, onCreateApiKeyCredential, onCreateLocalCredential, onUpdateCredential, onDeleteCredential, onTestCredential, onReorderCredentials, onRefreshQuota, onDismissError, }) {
|
|
8
|
+
export function AiCredentialPoolSection({ definition, product, status, accountLabel, attempt, attemptOfferingId, apiKey, baseUrl = '', busy, disabled = false, error, suppressError = false, quotas = {}, onApiKeyChange, onBaseUrlChange, onBeginApiKey, onBeginOffering, onCancelConnect, onBeginBrowser, onSaveApiKey, onDisconnect, onCreateApiKeyCredential, onCreateLocalCredential, onUpdateCredential, onDeleteCredential, onTestCredential, onReorderCredentials, onRefreshQuota, onDismissError, }) {
|
|
9
9
|
const fallbackOfferings = [{
|
|
10
10
|
id: 'api-platform',
|
|
11
11
|
label: 'API Key',
|
|
@@ -14,52 +14,336 @@ export function AiCredentialPoolSection({ definition, product, status, accountLa
|
|
|
14
14
|
}];
|
|
15
15
|
const credentials = product?.credentials ?? [];
|
|
16
16
|
const offerings = product?.offerings.length ? product.offerings : fallbackOfferings;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
17
|
+
const [editing, setEditing] = useState();
|
|
18
|
+
const [showCreate, setShowCreate] = useState(false);
|
|
19
|
+
const [authorizationOfferingId, setAuthorizationOfferingId] = useState();
|
|
20
|
+
const [dialogError, setDialogError] = useState();
|
|
21
|
+
const [saving, setSaving] = useState(false);
|
|
22
|
+
const completedAttemptRef = useRef(attempt);
|
|
23
|
+
const apiOfferings = offerings.filter((offering) => authorizationMethodsForOffering(offering, definition).some((method) => method.lifecycle === 'active' && isApiKeyMethod(method)));
|
|
24
|
+
const authorizationPending = isPendingAttempt(attempt) && isOAuthMode(attempt?.mode);
|
|
25
|
+
const dialogOpen = showCreate || Boolean(editing) || Boolean(authorizationOfferingId);
|
|
26
|
+
const closeDialog = () => {
|
|
27
|
+
setShowCreate(false);
|
|
28
|
+
setAuthorizationOfferingId(undefined);
|
|
29
|
+
setEditing(undefined);
|
|
30
|
+
setDialogError(undefined);
|
|
31
|
+
};
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const previous = completedAttemptRef.current;
|
|
34
|
+
completedAttemptRef.current = attempt;
|
|
35
|
+
if (dialogOpen && attempt !== previous && attempt?.status === 'completed')
|
|
36
|
+
closeDialog();
|
|
37
|
+
}, [attempt, dialogOpen]);
|
|
38
|
+
const orderedCredentials = [...credentials].sort((a, b) => a.priority - b.priority);
|
|
39
|
+
const quotaBusy = credentials.some((credential) => quotas[credential.id]?.busy);
|
|
40
|
+
return (_jsx(TooltipProvider, { children: _jsxs("section", { className: "space-y-3", "aria-label": "\u5F53\u524D\u8FDE\u63A5", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("h3", { className: "flex items-center gap-2 text-sm font-medium text-foreground/90", children: [_jsx(Settings2, { className: "h-4 w-4 text-primary" }), "\u5F53\u524D\u8FDE\u63A5"] }), _jsxs("div", { 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, offerings.map((offering) => {
|
|
41
|
+
const methods = authorizationMethodsForOffering(offering, definition).filter((method) => method.lifecycle === 'active' && (isOAuthMethod(method) || isLocalMethod(method)));
|
|
42
|
+
if (!methods.length)
|
|
43
|
+
return null;
|
|
44
|
+
return _jsx("div", { role: "group", "aria-label": `${offeringTitle(offering)}快捷接入`, children: _jsx(AuthorizationMethodActions, { methods: methods, offering: offering, hasCredentials: credentials.some((credential) => credential.offeringId === offering.id), busy: busy || saving, disabled: disabled || authorizationPending, onBeginOffering: onBeginOffering ? (target, mode, method) => {
|
|
45
|
+
setDialogError(undefined);
|
|
46
|
+
onDismissError?.();
|
|
47
|
+
setAuthorizationOfferingId(target.id);
|
|
48
|
+
onBeginOffering(target, mode, method);
|
|
49
|
+
} : undefined, onCreateLocalCredential: onCreateLocalCredential ? async (target, method) => {
|
|
50
|
+
setSaving(true);
|
|
51
|
+
setDialogError(undefined);
|
|
52
|
+
onDismissError?.();
|
|
53
|
+
try {
|
|
54
|
+
await onCreateLocalCredential(target, method);
|
|
55
|
+
}
|
|
56
|
+
catch (cause) {
|
|
57
|
+
setDialogError(cause instanceof Error ? cause.message : '连接失败,请重试');
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
setSaving(false);
|
|
61
|
+
}
|
|
62
|
+
} : undefined }) }, offering.id);
|
|
63
|
+
}), apiOfferings.length > 0 && !offerings.some((offering) => authorizationMethodsForOffering(offering, definition).some((method) => method.lifecycle === 'active' && (isOAuthMethod(method) || isLocalMethod(method))))
|
|
64
|
+
&& definition.browserMode === 'browserAssistedApiKey' ? (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", "aria-label": `${definition.name} 登录`, disabled: busy || saving || disabled || authorizationPending, onClick: () => {
|
|
65
|
+
setDialogError(undefined);
|
|
66
|
+
onDismissError?.();
|
|
67
|
+
onBeginBrowser();
|
|
68
|
+
}, children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5" }), definition.browserLabel] })) : null, apiOfferings.length > 0 ? (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", "aria-label": "\u65B0\u5EFA API Key \u8FDE\u63A5", disabled: busy || saving || disabled || authorizationPending || (!product && status === 'pending' && !attempt), onClick: () => {
|
|
69
|
+
setDialogError(undefined);
|
|
70
|
+
onDismissError?.();
|
|
71
|
+
setShowCreate(true);
|
|
72
|
+
}, children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), "API Key"] })) : null] })] }), _jsxs("div", { "aria-label": "\u51ED\u636E\u5217\u8868", className: "space-y-2", children: [_jsx(SortableCredentialList, { credentials: orderedCredentials, disabled: busy || disabled, onMove: onReorderCredentials ? (from, to) => {
|
|
73
|
+
const offering = offerings.find((item) => item.id === orderedCredentials[from]?.offeringId);
|
|
74
|
+
if (offering)
|
|
75
|
+
onReorderCredentials(offering, orderedCredentials, from, to);
|
|
76
|
+
} : undefined, children: (credential, handle) => {
|
|
77
|
+
const offering = offerings.find((item) => item.id === credential.offeringId);
|
|
78
|
+
if (!offering)
|
|
79
|
+
return null;
|
|
80
|
+
const label = credential.authMode === 'apiKey' ? credentialDisplayLabel(credential)
|
|
81
|
+
: credential.label ? maskAccountLabel(credential.label) : credentialDisplayLabel(credential);
|
|
82
|
+
const state = quotas[credential.id];
|
|
83
|
+
const quotaState = state?.credentialId === credential.id ? state : undefined;
|
|
84
|
+
return _jsx(CredentialRow, { credential: credential, label: label, dragHandle: handle, kindLabel: credential.authMode === 'apiKey' ? 'API Key' : offeringTitle(offering), busy: busy, disabled: disabled, onToggle: onUpdateCredential, onTest: onTestCredential, onEdit: credential.authMode === 'apiKey' ? () => setEditing(credential) : undefined, onDelete: () => credential.authMode === 'apiKey' ? onDeleteCredential?.(credential) : onDisconnect(credential), deleteAriaLabel: credential.authMode === 'apiKey' ? `删除 ${label}` : `${label} 移除`, quota: _jsx(AiQuotaCard, { compact: true, providerName: definition.name, offeringName: offeringTitle(offering), credentialLabel: credentialDisplayLabel(credential), multiple: credentials.length > 1, quota: quotaState?.quota, busy: quotaState?.busy ?? false, error: quotaState?.error, paused: !credential.enabled, disabled: disabled || !onRefreshQuota, onRefresh: () => onRefreshQuota?.(offering, credential) }) });
|
|
85
|
+
} }), !credentials.length ? _jsx("p", { className: "py-2 text-xs text-muted-foreground", children: accountLabel ? maskAccountLabel(accountLabel) : '尚未添加连接' }) : null, credentials.length === 0 && (status === 'configured' || status === 'connected') ? (_jsx(Button, { variant: "ghost", size: "sm", disabled: busy || disabled, onClick: () => onDisconnect(), children: status === 'configured' ? '移除配置' : '断开连接' })) : null] }), _jsx(Dialog, { open: dialogOpen, onOpenChange: (open) => {
|
|
86
|
+
if (open || saving)
|
|
87
|
+
return;
|
|
88
|
+
if (attempt && isPendingAttempt(attempt) && isOAuthMode(attempt.mode)) {
|
|
89
|
+
onCancelConnect?.({ mode: attempt.mode, attemptId: attempt.attemptId, state: attempt.state,
|
|
90
|
+
signature: attempt.signature, ...(attempt.offeringId ? { offeringId: attempt.offeringId } : {}) });
|
|
91
|
+
}
|
|
92
|
+
closeDialog();
|
|
93
|
+
}, children: _jsxs(DialogContent, { className: "max-h-[85vh] overflow-y-auto sm:max-w-lg", "aria-describedby": undefined, children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: editing ? '编辑连接' : authorizationOfferingId ? '连接账号' : '新建连接' }) }), _jsx("div", { className: "space-y-5", "aria-label": "\u6DFB\u52A0\u8FDE\u63A5", children: (editing || authorizationOfferingId ? offerings.filter((offering) => offering.id === (editing?.offeringId ?? authorizationOfferingId)) : apiOfferings).map((offering) => {
|
|
94
|
+
const otherAuthorizationPending = isPendingAttempt(attempt) && isOAuthMode(attempt?.mode)
|
|
95
|
+
&& (attemptOfferingId ?? attempt?.offeringId) !== offering.id;
|
|
96
|
+
const actionDisabled = disabled || saving || otherAuthorizationPending;
|
|
97
|
+
const methods = authorizationMethodsForOffering(offering, definition);
|
|
98
|
+
const activeMethods = methods.filter((method) => method.lifecycle === 'active');
|
|
99
|
+
const supportsApiKey = activeMethods.some(isApiKeyMethod);
|
|
100
|
+
const offeringAttempt = attemptOfferingId === offering.id ? attempt : undefined;
|
|
101
|
+
const authorizationError = error?.offeringId === offering.id ? error.authorization : undefined;
|
|
102
|
+
const attemptedMethod = methods.find((method) => method.id === (authorizationError?.authorizationMethodId ?? offeringAttempt?.authorizationMethodId))
|
|
103
|
+
?? activeMethods.find(isOAuthMethod);
|
|
104
|
+
const attemptedMode = authorizationError?.mode ?? offeringAttempt?.mode ?? (attemptedMethod ? connectModeForMethod(attemptedMethod) : modeForOffering(offering, definition));
|
|
105
|
+
const offeringError = error?.offeringId === offering.id ? error.message : undefined;
|
|
106
|
+
if (offering.lifecycle === 'unavailable' && activeMethods.length === 0) {
|
|
107
|
+
return _jsxs("fieldset", { "data-create-offering": offering.id, className: "space-y-2 border-t border-border/50 pt-3 first:border-t-0 first:pt-0", children: [_jsx("legend", { className: "px-1 text-sm font-medium", children: offeringTitle(offering) }), _jsx("p", { className: "text-xs text-muted-foreground", children: offering.kind === 'oauth-subscription'
|
|
108
|
+
? '暂不可用:账号订阅需在 Xpod 桌面版中导入本机客户端(如 Codex CLI)的登录态,浏览器中无法完成。'
|
|
109
|
+
: '暂不可用:该接入方式尚未提供可用的连接流程。' })] }, offering.id);
|
|
110
|
+
}
|
|
111
|
+
const failedAuthorizationMode = authorizationError?.mode;
|
|
112
|
+
if (offeringError && isOAuthMode(failedAuthorizationMode)) {
|
|
113
|
+
return _jsxs("fieldset", { "data-create-offering": offering.id, disabled: actionDisabled, className: "space-y-3 border-t border-border/50 pt-3 first:border-t-0 first:pt-0", "aria-label": `${offeringTitle(offering)}接入操作`, children: [_jsx("legend", { className: "px-1 text-sm font-medium", children: offeringTitle(offering) }), offeringAttempt?.status === 'unsupported'
|
|
114
|
+
? _jsx("p", { className: "text-sm", children: "\u5F53\u524D\u90E8\u7F72\u672A\u542F\u7528\u8D26\u53F7\u6388\u6743" })
|
|
115
|
+
: _jsx(LoginFailureView, { title: "\u767B\u5F55\u672A\u5B8C\u6210", description: offeringError, primaryLabel: "\u91CD\u8BD5\u767B\u5F55", onPrimary: () => onBeginOffering?.(offering, failedAuthorizationMode, attemptedMethod), secondaryLabel: "\u5173\u95ED", onSecondary: () => { onDismissError?.(); closeDialog(); } })] }, offering.id);
|
|
116
|
+
}
|
|
117
|
+
if (isPendingAttempt(offeringAttempt) && isOAuthMode(attemptedMode)) {
|
|
118
|
+
return _jsxs("fieldset", { "data-create-offering": offering.id, disabled: actionDisabled, className: "space-y-3 border-t border-border/50 pt-3 first:border-t-0 first:pt-0", "aria-label": `${offeringTitle(offering)}接入操作`, children: [_jsx("legend", { className: "px-1 text-sm font-medium", children: offeringTitle(offering) }), _jsx(LoginConnectingView, { title: "\u6B63\u5728\u8FDE\u63A5", detail: attemptedMode === 'authorizationCodeOAuth' ? '等待网页授权,请在打开的页面完成登录。' : offeringAttempt?.userCode ? `验证码:${offeringAttempt.userCode}` : '请在打开的页面完成授权。', providerLabel: attemptedMethod?.label ?? offeringTitle(offering), providerHost: definition.name }), _jsx(Button, { variant: "ghost", size: "sm", disabled: disabled, onClick: () => {
|
|
119
|
+
if (offeringAttempt)
|
|
120
|
+
onCancelConnect?.({
|
|
121
|
+
mode: offeringAttempt.mode,
|
|
122
|
+
attemptId: offeringAttempt.attemptId,
|
|
123
|
+
state: offeringAttempt.state,
|
|
124
|
+
signature: offeringAttempt.signature,
|
|
125
|
+
...(offeringAttempt.offeringId ? { offeringId: offeringAttempt.offeringId } : {}),
|
|
126
|
+
});
|
|
127
|
+
closeDialog();
|
|
128
|
+
}, children: "\u53D6\u6D88\u8FDE\u63A5" })] }, offering.id);
|
|
129
|
+
}
|
|
130
|
+
return _jsxs("fieldset", { "data-create-offering": offering.id, disabled: actionDisabled, className: "space-y-3 border-t border-border/50 pt-3 first:border-t-0 first:pt-0", "aria-label": `${offeringTitle(offering)}接入操作`, children: [_jsx("legend", { className: "px-1 text-sm font-medium", children: offeringTitle(offering) }), supportsApiKey ? _jsx(ApiKeyPool, { definition: definition, offering: offering, status: status, createOfferings: [offering], initialCreate: !editing, credentials: credentials, initialEditing: editing?.offeringId === offering.id ? editing : undefined, onCloseEdit: closeDialog, onSavingChange: setSaving, attempt: offeringAttempt ?? (attempt?.mode === 'browserAssistedApiKey' ? attempt : undefined), apiKey: apiKey, baseUrl: baseUrl, busy: busy, disabled: actionDisabled, onApiKeyChange: onApiKeyChange, onBaseUrlChange: onBaseUrlChange, onBeginApiKey: onBeginApiKey, onBeginBrowser: onBeginBrowser, onSaveApiKey: onSaveApiKey, onDisconnect: onDisconnect, onUpdateCredential: onUpdateCredential, onCreateApiKeyCredential: onCreateApiKeyCredential }, `${offering.id}:${editing?.id ?? ''}`) : null, authorizationOfferingId && !offeringError ? _jsx(LoginConnectingView, { title: "\u6B63\u5728\u8FDE\u63A5", detail: "\u6B63\u5728\u542F\u52A8\u6388\u6743\uFF0C\u8BF7\u7A0D\u5019\u3002", providerLabel: offeringTitle(offering), providerHost: definition.name }) : null, offeringError ? _jsx("p", { className: "w-full text-sm text-destructive", children: offeringError }) : null] }, offering.id);
|
|
131
|
+
}) }), dialogError ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: dialogError }) : null] }) }), _jsxs("details", { className: "text-xs text-muted-foreground", children: [_jsx("summary", { className: "w-fit cursor-pointer", children: "\u63A5\u5165\u4FE1\u606F" }), _jsx("div", { className: "mt-2 space-y-3", children: offerings.map((offering) => _jsx(OfferingItem, { offering: offering }, offering.id)) })] }), dialogError && !dialogOpen && dialogError !== error?.message ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: dialogError }) : null, error && (error.offeringId ? !dialogOpen : !suppressError)
|
|
132
|
+
? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error.message }) : null] }) }));
|
|
133
|
+
}
|
|
134
|
+
function SortableCredentialList({ credentials, disabled, onMove, children }) {
|
|
135
|
+
const listRef = useRef(null);
|
|
136
|
+
const dragRef = useRef(null);
|
|
137
|
+
const frameRef = useRef(undefined);
|
|
138
|
+
const timerRef = useRef(undefined);
|
|
139
|
+
const dropRef = useRef(null);
|
|
140
|
+
const dropOrderRef = useRef(undefined);
|
|
141
|
+
const busyRef = useRef(disabled);
|
|
142
|
+
const [announcement, setAnnouncement] = useState('');
|
|
143
|
+
const unavailable = disabled || !onMove || credentials.length < 2;
|
|
144
|
+
const order = credentials.map((credential) => credential.id).join('\0');
|
|
145
|
+
const rows = () => Array.from(listRef.current?.querySelectorAll('[data-sortable-credential]') ?? []);
|
|
146
|
+
const clearMotion = () => {
|
|
147
|
+
if (frameRef.current !== undefined)
|
|
148
|
+
cancelAnimationFrame(frameRef.current);
|
|
149
|
+
if (timerRef.current !== undefined)
|
|
150
|
+
clearTimeout(timerRef.current);
|
|
151
|
+
frameRef.current = undefined;
|
|
152
|
+
timerRef.current = undefined;
|
|
153
|
+
for (const row of rows()) {
|
|
154
|
+
for (const key of ['transform', 'transition', 'zIndex', 'position', 'boxShadow', 'willChange', 'backgroundColor'])
|
|
155
|
+
row.style[key] = '';
|
|
156
|
+
delete row.dataset.dragging;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const settle = () => {
|
|
160
|
+
const positions = dropRef.current;
|
|
161
|
+
if (!positions)
|
|
162
|
+
return;
|
|
163
|
+
dropRef.current = null;
|
|
164
|
+
clearMotion();
|
|
165
|
+
const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
166
|
+
const elements = rows();
|
|
167
|
+
for (const row of elements) {
|
|
168
|
+
const previous = positions.get(row.dataset.sortableCredential);
|
|
169
|
+
if (!previous || reduced)
|
|
170
|
+
continue;
|
|
171
|
+
const rect = row.getBoundingClientRect();
|
|
172
|
+
row.style.transition = 'none';
|
|
173
|
+
row.style.transform = `translateY(${previous.top - rect.top}px)`;
|
|
174
|
+
}
|
|
175
|
+
// Commit the inverse positions before animating to the new document order.
|
|
176
|
+
listRef.current?.getBoundingClientRect();
|
|
177
|
+
frameRef.current = requestAnimationFrame(() => {
|
|
178
|
+
frameRef.current = undefined;
|
|
179
|
+
for (const row of elements) {
|
|
180
|
+
row.style.transition = reduced ? 'none' : 'transform 160ms ease-out, box-shadow 160ms ease-out';
|
|
181
|
+
row.style.transform = 'translateY(0px)';
|
|
182
|
+
}
|
|
183
|
+
timerRef.current = setTimeout(clearMotion, reduced ? 0 : 180);
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
const finish = (commit) => {
|
|
187
|
+
const drag = dragRef.current;
|
|
188
|
+
if (!drag)
|
|
189
|
+
return;
|
|
190
|
+
if (frameRef.current !== undefined)
|
|
191
|
+
cancelAnimationFrame(frameRef.current);
|
|
192
|
+
dropRef.current = new Map(drag.rows.map((row) => [row.dataset.sortableCredential, row.getBoundingClientRect()]));
|
|
193
|
+
dropOrderRef.current = order;
|
|
194
|
+
if (commit && drag.from !== drag.to && !unavailable) {
|
|
195
|
+
const source = drag.rects[drag.from];
|
|
196
|
+
const destination = drag.rects[drag.to];
|
|
197
|
+
const gap = Math.max(0, drag.rects[1].top - drag.rects[0].bottom);
|
|
198
|
+
const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
199
|
+
drag.rows.forEach((row, index) => {
|
|
200
|
+
const shift = index === drag.from
|
|
201
|
+
? drag.from < drag.to ? destination.bottom - source.bottom : destination.top - source.top
|
|
202
|
+
: drag.from < index && index <= drag.to ? -(source.height + gap)
|
|
203
|
+
: drag.to <= index && index < drag.from ? source.height + gap : 0;
|
|
204
|
+
row.style.transition = reduced ? 'none' : 'transform 160ms ease-out';
|
|
205
|
+
row.style.transform = `translateY(${shift}px)`;
|
|
206
|
+
const rect = drag.rects[index];
|
|
207
|
+
dropRef.current.set(row.dataset.sortableCredential, { ...rect, top: rect.top + shift, bottom: rect.bottom + shift });
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
dragRef.current = null;
|
|
211
|
+
if (drag.handle.hasPointerCapture?.(drag.pointerId))
|
|
212
|
+
drag.handle.releasePointerCapture?.(drag.pointerId);
|
|
213
|
+
if (commit && !unavailable && credentials[drag.from]?.id === drag.id)
|
|
214
|
+
move(drag.from, drag.to);
|
|
215
|
+
// React's layout effect handles synchronous reorder; this handles no-op/async saves.
|
|
216
|
+
frameRef.current = requestAnimationFrame(() => {
|
|
217
|
+
frameRef.current = undefined;
|
|
218
|
+
if (!commit || !busyRef.current)
|
|
219
|
+
settle();
|
|
220
|
+
});
|
|
221
|
+
// A stalled save must not leave transformed rows indefinitely.
|
|
222
|
+
timerRef.current = setTimeout(settle, 10000);
|
|
223
|
+
};
|
|
224
|
+
const move = (from, to) => {
|
|
225
|
+
if (unavailable || from === to || to < 0 || to >= credentials.length)
|
|
226
|
+
return;
|
|
227
|
+
onMove?.(from, to);
|
|
228
|
+
setAnnouncement(`已请求将${credentialDisplayLabel(credentials[from])}移至第 ${to + 1} 位`);
|
|
229
|
+
};
|
|
230
|
+
const lifecycleRef = useRef({ finish, settle, clearMotion });
|
|
231
|
+
useLayoutEffect(() => {
|
|
232
|
+
lifecycleRef.current = { finish, settle, clearMotion };
|
|
233
|
+
busyRef.current = disabled;
|
|
234
|
+
});
|
|
235
|
+
useLayoutEffect(() => {
|
|
236
|
+
if (dragRef.current)
|
|
237
|
+
lifecycleRef.current.finish(false);
|
|
238
|
+
if (dropRef.current && (dropOrderRef.current !== order || !disabled))
|
|
239
|
+
lifecycleRef.current.settle();
|
|
240
|
+
}, [order, unavailable, disabled]);
|
|
241
|
+
useEffect(() => () => {
|
|
242
|
+
const drag = dragRef.current;
|
|
243
|
+
dragRef.current = null;
|
|
244
|
+
dropRef.current = null;
|
|
245
|
+
if (drag?.handle.hasPointerCapture?.(drag.pointerId))
|
|
246
|
+
drag.handle.releasePointerCapture?.(drag.pointerId);
|
|
247
|
+
lifecycleRef.current.clearMotion();
|
|
248
|
+
}, []);
|
|
249
|
+
const paintDrag = () => {
|
|
250
|
+
frameRef.current = undefined;
|
|
251
|
+
const drag = dragRef.current;
|
|
252
|
+
if (!drag)
|
|
253
|
+
return;
|
|
254
|
+
const source = drag.rects[drag.from];
|
|
255
|
+
const gap = drag.rects.length > 1
|
|
256
|
+
? Math.max(0, drag.rects[1].top - drag.rects[0].bottom) : 0;
|
|
257
|
+
drag.rows.forEach((row, index) => {
|
|
258
|
+
const shift = index === drag.from ? drag.y - drag.startY
|
|
259
|
+
: drag.from < index && index <= drag.to ? -(source.height + gap)
|
|
260
|
+
: drag.to <= index && index < drag.from ? source.height + gap : 0;
|
|
261
|
+
row.style.transform = `translateY(${shift}px)`;
|
|
262
|
+
});
|
|
263
|
+
};
|
|
264
|
+
return (_jsxs("div", { ref: listRef, className: "space-y-2", children: [_jsx("span", { className: "sr-only", role: "status", children: announcement }), credentials.map((credential, index) => (_jsx("div", { "data-sortable-credential": credential.id, className: "rounded-lg", children: children(credential, onMove ? (_jsx("button", { type: "button", disabled: unavailable, "aria-label": `拖动排序 ${credentialDisplayLabel(credential)}`, title: credentials.length < 2 ? '至少添加两条连接后可拖动排序' : '拖动调整优先级,或使用方向键、Home / End 排序', className: "flex h-8 w-6 shrink-0 touch-none select-none items-center justify-center rounded text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:opacity-40 enabled:cursor-grab active:cursor-grabbing", onKeyDown: (event) => {
|
|
265
|
+
if (event.key === 'Escape') {
|
|
266
|
+
finish(false);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const next = event.key === 'ArrowUp' ? index - 1 : event.key === 'ArrowDown' ? index + 1
|
|
270
|
+
: event.key === 'Home' ? 0 : event.key === 'End' ? credentials.length - 1 : undefined;
|
|
271
|
+
if (next === undefined)
|
|
272
|
+
return;
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
move(index, next);
|
|
275
|
+
}, onPointerDown: (event) => {
|
|
276
|
+
if (unavailable || event.button !== 0 || dragRef.current)
|
|
277
|
+
return;
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
dropRef.current = null;
|
|
280
|
+
clearMotion();
|
|
281
|
+
event.currentTarget.focus();
|
|
282
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
283
|
+
const elements = rows();
|
|
284
|
+
const reduced = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
285
|
+
dragRef.current = { id: credential.id, from: index, to: index, pointerId: event.pointerId,
|
|
286
|
+
startY: event.clientY, y: event.clientY, handle: event.currentTarget, rows: elements,
|
|
287
|
+
rects: elements.map((row) => row.getBoundingClientRect()) };
|
|
288
|
+
for (const [rowIndex, row] of elements.entries()) {
|
|
289
|
+
row.style.willChange = 'transform';
|
|
290
|
+
row.style.transition = reduced || rowIndex === index ? 'none' : 'transform 160ms ease-out';
|
|
291
|
+
if (rowIndex === index) {
|
|
292
|
+
row.dataset.dragging = 'true';
|
|
293
|
+
row.style.position = 'relative';
|
|
294
|
+
row.style.zIndex = '10';
|
|
295
|
+
row.style.boxShadow = '0 8px 24px rgb(0 0 0 / 0.18)';
|
|
296
|
+
row.style.backgroundColor = 'hsl(var(--card))';
|
|
47
297
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
298
|
+
}
|
|
299
|
+
}, onPointerMove: (event) => {
|
|
300
|
+
const drag = dragRef.current;
|
|
301
|
+
if (!drag || drag.pointerId !== event.pointerId || unavailable)
|
|
302
|
+
return;
|
|
303
|
+
drag.y = event.clientY;
|
|
304
|
+
let distance = Infinity;
|
|
305
|
+
drag.rects.forEach((rect, rowIndex) => {
|
|
306
|
+
const delta = Math.abs(event.clientY - (rect.top + rect.bottom) / 2);
|
|
307
|
+
if (delta < distance) {
|
|
308
|
+
distance = delta;
|
|
309
|
+
drag.to = rowIndex;
|
|
51
310
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
311
|
+
});
|
|
312
|
+
if (frameRef.current === undefined)
|
|
313
|
+
frameRef.current = requestAnimationFrame(paintDrag);
|
|
314
|
+
}, onPointerUp: (event) => {
|
|
315
|
+
if (dragRef.current?.pointerId === event.pointerId)
|
|
316
|
+
finish(true);
|
|
317
|
+
}, onPointerCancel: (event) => {
|
|
318
|
+
if (dragRef.current?.pointerId === event.pointerId)
|
|
319
|
+
finish(false);
|
|
320
|
+
}, onLostPointerCapture: (event) => {
|
|
321
|
+
if (dragRef.current?.pointerId === event.pointerId)
|
|
322
|
+
finish(false);
|
|
323
|
+
}, children: _jsx(GripVertical, { "aria-hidden": "true", className: "h-4 w-4" }) })) : null) }, credential.id)))] }));
|
|
324
|
+
}
|
|
325
|
+
function AuthorizationMethodActions({ methods, offering, hasCredentials, busy, disabled, onBeginOffering, onCreateLocalCredential, }) {
|
|
326
|
+
if (methods.length === 0)
|
|
327
|
+
return null;
|
|
328
|
+
return (_jsx("div", { className: "space-y-2", children: _jsx("div", { className: "flex flex-wrap gap-2", children: methods.map((method) => {
|
|
329
|
+
const connectMode = connectModeForMethod(method);
|
|
330
|
+
if (isOAuthMode(connectMode)) {
|
|
331
|
+
return (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", disabled: busy || disabled || !onBeginOffering, onClick: () => onBeginOffering?.(offering, connectMode, method), children: [busy ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : _jsx(ExternalLink, { className: "h-3.5 w-3.5" }), method.label || (hasCredentials ? '添加账号' : '登录')] }, method.id));
|
|
332
|
+
}
|
|
333
|
+
if (isLocalMethod(method)) {
|
|
334
|
+
return (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", title: "\u5BFC\u5165\u5F53\u524D\u8BBE\u5907\u5DF2\u6709\u767B\u5F55\u6001\uFF0C\u4E0D\u4F1A\u53D1\u8D77\u65B0\u7684\u6D4F\u89C8\u5668\u6388\u6743\u3002", disabled: busy || disabled || !onCreateLocalCredential, onClick: () => void onCreateLocalCredential?.(offering, method), children: [busy ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : _jsx(Plus, { className: "h-3.5 w-3.5" }), method.label || '已有登录态'] }, method.id));
|
|
335
|
+
}
|
|
336
|
+
return null;
|
|
337
|
+
}) }) }));
|
|
56
338
|
}
|
|
57
|
-
function OfferingItem({ offering,
|
|
339
|
+
function OfferingItem({ offering, methods }) {
|
|
58
340
|
const endpoints = offering.endpoints ?? [];
|
|
59
341
|
const title = offeringTitle(offering);
|
|
60
342
|
const kindLabel = offering.kind ? offeringKindLabel(offering.kind) : undefined;
|
|
61
343
|
const subtitle = kindLabel && kindLabel !== title ? [kindLabel] : [];
|
|
62
|
-
return (_jsxs("section", { className: "space-y-
|
|
344
|
+
return (_jsxs("section", { className: "space-y-2", "aria-labelledby": `offering-${offering.id}`, children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h4", { id: `offering-${offering.id}`, className: "text-sm font-medium text-foreground", children: title }), subtitle.length ? (_jsx("div", { className: "mt-1 flex items-center gap-2 text-xs text-muted-foreground", children: subtitle.map((part) => _jsx("span", { children: part }, part)) })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center justify-end gap-x-3 gap-y-1 text-xs", children: [_jsx("span", { className: "text-muted-foreground", children: authMethodLabel(offering, methods) }), offering.consoleUrl ? _jsx(OfferingLink, { href: offering.consoleUrl, label: "\u63A7\u5236\u53F0" }) : null, offering.subscriptionUrl ? _jsx(OfferingLink, { href: offering.subscriptionUrl, label: "\u8BA2\u9605\u4E0E\u8D26\u5355" }) : null, offering.quota?.url ? _jsx(OfferingLink, { href: offering.quota.url, label: "\u989D\u5EA6\u4E0E\u7528\u91CF" }) : null, offering.usagePolicyUrl ? _jsx(OfferingLink, { href: offering.usagePolicyUrl, label: "\u4F7F\u7528\u653F\u7B56" }) : null] })] }), offering.lifecycle === 'unavailable' && !authorizationMethodsForOffering(offering).some((method) => method.lifecycle === 'active') ? (_jsx("p", { className: "text-xs text-muted-foreground", children: offering.kind === 'oauth-subscription'
|
|
345
|
+
? '暂不可用:账号订阅需在 Xpod 桌面版中导入本机客户端(如 Codex CLI)的登录态,浏览器中无法完成。'
|
|
346
|
+
: '暂不可用:该接入方式尚未提供可用的连接流程。' })) : null, endpoints.length ? (_jsx("dl", { className: "space-y-1 text-[11px] text-muted-foreground", children: endpoints.map((endpoint) => (_jsxs("div", { className: "flex min-w-0 items-baseline gap-2", children: [_jsx("dt", { className: "shrink-0 text-foreground/70", children: endpointProtocolLabel(endpoint.protocol) }), _jsx("dd", { className: "min-w-0 truncate font-mono", title: endpoint.baseUrl, children: endpointDisplayValue(endpoint.baseUrl) })] }, `${endpoint.protocol}:${endpoint.baseUrl}`))) })) : null] }));
|
|
63
347
|
}
|
|
64
348
|
function OfferingLink({ href, label }) {
|
|
65
349
|
return _jsx("a", { href: href, target: "_blank", rel: "noreferrer", className: "text-primary hover:underline", children: label });
|
|
@@ -96,37 +380,95 @@ function offeringKindLabel(kind) {
|
|
|
96
380
|
return 'Token 套餐';
|
|
97
381
|
return kind;
|
|
98
382
|
}
|
|
99
|
-
function authMethodLabel(offering) {
|
|
100
|
-
const labels = (
|
|
101
|
-
|
|
102
|
-
|
|
383
|
+
function authMethodLabel(offering, methods) {
|
|
384
|
+
const labels = (methods?.length ? methods : authorizationMethodsForOffering(offering))
|
|
385
|
+
.map((method) => method.authMode === 'apiKey'
|
|
386
|
+
? 'API Key'
|
|
387
|
+
: method.authMode === 'local'
|
|
388
|
+
? method.label
|
|
389
|
+
: '账号授权');
|
|
103
390
|
return [...new Set(labels)].join(' / ');
|
|
104
391
|
}
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
392
|
+
function authorizationMethodsForOffering(offering, definition) {
|
|
393
|
+
if (offering.authorizationMethods?.length)
|
|
394
|
+
return offering.authorizationMethods;
|
|
395
|
+
const lifecycle = offering.lifecycle === 'unavailable' ? 'unavailable' : 'active';
|
|
396
|
+
return [...new Set(offering.authModes ?? [])].map((mode) => {
|
|
397
|
+
if (mode === 'apiKey') {
|
|
398
|
+
return {
|
|
399
|
+
id: 'api-key',
|
|
400
|
+
authMode: 'apiKey',
|
|
401
|
+
connectMode: 'browserAssistedApiKey',
|
|
402
|
+
label: 'API Key',
|
|
403
|
+
lifecycle,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (mode === 'local') {
|
|
407
|
+
const localService = offering.kind === 'local';
|
|
408
|
+
return {
|
|
409
|
+
id: localService ? 'local-service' : 'local-session-import',
|
|
410
|
+
authMode: 'local',
|
|
411
|
+
label: localService ? '本地服务' : '已有登录态',
|
|
412
|
+
lifecycle,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
id: 'device-code',
|
|
417
|
+
authMode: mode,
|
|
418
|
+
connectMode: 'deviceCodeOAuth',
|
|
419
|
+
label: '',
|
|
420
|
+
lifecycle,
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
function connectModeForMethod(method) {
|
|
425
|
+
if (method.connectMode)
|
|
426
|
+
return method.connectMode;
|
|
427
|
+
if (method.authMode === 'oauth' || method.authMode === 'deviceCode')
|
|
428
|
+
return 'deviceCodeOAuth';
|
|
429
|
+
if (method.authMode === 'apiKey')
|
|
430
|
+
return 'browserAssistedApiKey';
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
function isOAuthMode(mode) {
|
|
434
|
+
return mode === 'deviceCodeOAuth' || mode === 'authorizationCodeOAuth';
|
|
435
|
+
}
|
|
436
|
+
function isOAuthMethod(method) {
|
|
437
|
+
return isOAuthMode(connectModeForMethod(method));
|
|
109
438
|
}
|
|
110
|
-
function
|
|
111
|
-
return
|
|
112
|
-
? '导入本机 OpenAI 登录'
|
|
113
|
-
: '连接本地 Ollama';
|
|
439
|
+
function isApiKeyMethod(method) {
|
|
440
|
+
return method.authMode === 'apiKey' || connectModeForMethod(method) === 'browserAssistedApiKey';
|
|
114
441
|
}
|
|
115
|
-
function
|
|
442
|
+
function isLocalMethod(method) {
|
|
443
|
+
return method.authMode === 'local';
|
|
444
|
+
}
|
|
445
|
+
function ApiKeyPool({ definition, offering, createOfferings, status, credentials, attempt, apiKey, baseUrl, busy, disabled, error, onApiKeyChange, onBaseUrlChange, onBeginApiKey, onBeginBrowser, onSaveApiKey, onDisconnect, onCreateApiKeyCredential, onUpdateCredential, initialEditing, initialCreate = false, onSavingChange, onCloseEdit, }) {
|
|
116
446
|
const apiKeyAttempt = attempt?.mode === 'browserAssistedApiKey' && attempt.status === 'pending';
|
|
117
447
|
const isConfigured = status === 'configured';
|
|
118
448
|
const isConnected = status === 'connected';
|
|
119
449
|
const hasPoolActions = Boolean(onCreateApiKeyCredential);
|
|
450
|
+
const [createOfferingId, setCreateOfferingId] = useState(offering.id);
|
|
451
|
+
const formOffering = createOfferings.find((item) => item.id === createOfferingId) ?? offering;
|
|
120
452
|
const [showKey, setShowKey] = useState(false);
|
|
121
|
-
const [formMode, setFormMode] = useState();
|
|
122
|
-
const [editingCredential, setEditingCredential] = useState();
|
|
123
|
-
const [poolLabel, setPoolLabel] = useState('');
|
|
453
|
+
const [formMode, setFormMode] = useState(initialEditing ? 'edit' : initialCreate && onCreateApiKeyCredential ? 'create' : undefined);
|
|
454
|
+
const [editingCredential, setEditingCredential] = useState(initialEditing);
|
|
455
|
+
const [poolLabel, setPoolLabel] = useState(initialEditing?.label ?? '');
|
|
124
456
|
const [poolApiKey, setPoolApiKey] = useState('');
|
|
125
|
-
const [poolBaseUrl, setPoolBaseUrl] = useState('');
|
|
126
|
-
const [poolProxyUrl, setPoolProxyUrl] = useState('');
|
|
457
|
+
const [poolBaseUrl, setPoolBaseUrl] = useState(initialEditing?.baseUrl ?? '');
|
|
458
|
+
const [poolProxyUrl, setPoolProxyUrl] = useState(initialEditing?.proxyUrl ?? '');
|
|
127
459
|
const [poolFormError, setPoolFormError] = useState();
|
|
128
460
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
461
|
+
const [saving, setSaving] = useState(false);
|
|
462
|
+
const saveInFlight = useRef(false);
|
|
463
|
+
const legacyBeginRef = useRef(false);
|
|
464
|
+
useEffect(() => {
|
|
465
|
+
if (initialCreate && !onCreateApiKeyCredential && !legacyBeginRef.current) {
|
|
466
|
+
legacyBeginRef.current = true;
|
|
467
|
+
onBeginApiKey();
|
|
468
|
+
}
|
|
469
|
+
}, [initialCreate, onCreateApiKeyCredential, onBeginApiKey]);
|
|
129
470
|
const openCreateForm = () => {
|
|
471
|
+
setCreateOfferingId(offering.id);
|
|
130
472
|
setFormMode('create');
|
|
131
473
|
setEditingCredential(undefined);
|
|
132
474
|
setPoolLabel('');
|
|
@@ -136,17 +478,8 @@ function ApiKeyPool({ definition, offering, status, accountLabel, credentials, a
|
|
|
136
478
|
setPoolFormError(undefined);
|
|
137
479
|
setShowAdvanced(false);
|
|
138
480
|
};
|
|
139
|
-
const openEditForm = (credential) => {
|
|
140
|
-
setFormMode('edit');
|
|
141
|
-
setEditingCredential(credential);
|
|
142
|
-
setPoolLabel(credential.label ?? '');
|
|
143
|
-
setPoolApiKey('');
|
|
144
|
-
setPoolBaseUrl(credential.baseUrl ?? '');
|
|
145
|
-
setPoolProxyUrl(credential.proxyUrl ?? '');
|
|
146
|
-
setPoolFormError(undefined);
|
|
147
|
-
setShowAdvanced(false);
|
|
148
|
-
};
|
|
149
481
|
const closePoolForm = () => {
|
|
482
|
+
onCloseEdit();
|
|
150
483
|
setFormMode(undefined);
|
|
151
484
|
setEditingCredential(undefined);
|
|
152
485
|
setPoolLabel('');
|
|
@@ -157,6 +490,8 @@ function ApiKeyPool({ definition, offering, status, accountLabel, credentials, a
|
|
|
157
490
|
setShowAdvanced(false);
|
|
158
491
|
};
|
|
159
492
|
const savePoolForm = async () => {
|
|
493
|
+
if (busy || disabled || saveInFlight.current)
|
|
494
|
+
return;
|
|
160
495
|
let normalizedProxyUrl;
|
|
161
496
|
try {
|
|
162
497
|
normalizedProxyUrl = normalizeProxyUrl(poolProxyUrl);
|
|
@@ -170,23 +505,34 @@ function ApiKeyPool({ definition, offering, status, accountLabel, credentials, a
|
|
|
170
505
|
const trimmedKey = poolApiKey.trim();
|
|
171
506
|
if (!trimmedKey || !onCreateApiKeyCredential)
|
|
172
507
|
return;
|
|
508
|
+
saveInFlight.current = true;
|
|
509
|
+
setSaving(true);
|
|
510
|
+
onSavingChange?.(true);
|
|
173
511
|
try {
|
|
174
|
-
await onCreateApiKeyCredential({
|
|
512
|
+
await onCreateApiKeyCredential(formOffering, {
|
|
175
513
|
apiKey: trimmedKey,
|
|
176
514
|
label: poolLabel.trim() || undefined,
|
|
177
515
|
baseUrl: poolBaseUrl.trim() || undefined,
|
|
178
516
|
proxyUrl: normalizedProxyUrl,
|
|
179
|
-
priority: nextCredentialPriority(credentials),
|
|
517
|
+
priority: nextCredentialPriority(credentials.filter((credential) => credential.offeringId === formOffering.id)),
|
|
180
518
|
});
|
|
181
519
|
closePoolForm();
|
|
182
520
|
}
|
|
183
521
|
catch (error) {
|
|
184
522
|
setPoolFormError(error instanceof Error ? error.message : '保存失败,请重试');
|
|
185
523
|
}
|
|
524
|
+
finally {
|
|
525
|
+
saveInFlight.current = false;
|
|
526
|
+
setSaving(false);
|
|
527
|
+
onSavingChange?.(false);
|
|
528
|
+
}
|
|
186
529
|
return;
|
|
187
530
|
}
|
|
188
531
|
if (formMode === 'edit' && editingCredential && onUpdateCredential) {
|
|
189
532
|
const proxyChanged = poolProxyUrl.trim() !== (editingCredential.proxyUrl ?? '').trim();
|
|
533
|
+
saveInFlight.current = true;
|
|
534
|
+
setSaving(true);
|
|
535
|
+
onSavingChange?.(true);
|
|
190
536
|
try {
|
|
191
537
|
await onUpdateCredential(editingCredential, {
|
|
192
538
|
label: poolLabel.trim() || undefined,
|
|
@@ -198,17 +544,24 @@ function ApiKeyPool({ definition, offering, status, accountLabel, credentials, a
|
|
|
198
544
|
catch (error) {
|
|
199
545
|
setPoolFormError(error instanceof Error ? error.message : '保存失败,请重试');
|
|
200
546
|
}
|
|
547
|
+
finally {
|
|
548
|
+
saveInFlight.current = false;
|
|
549
|
+
setSaving(false);
|
|
550
|
+
onSavingChange?.(false);
|
|
551
|
+
}
|
|
201
552
|
}
|
|
202
553
|
};
|
|
203
|
-
return (_jsxs("div", { className:
|
|
554
|
+
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: [_jsxs(Button, { variant: "outline", size: "sm", disabled: busy || disabled || saving || definition.browserMode === 'connectUnsupported', onClick: onBeginBrowser, children: [busy ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), definition.browserLabel] }), _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("select", { "aria-label": `${definition.name} 套餐 / 区域`, value: createOfferingId, disabled: busy || disabled || saving, onChange: (event) => { setCreateOfferingId(event.target.value); setPoolFormError(undefined); }, className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm text-foreground", 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
|
|
204
555
|
? _jsx(EyeOff, { className: "h-4 w-4 text-muted-foreground" })
|
|
205
|
-
: _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, { 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] }));
|
|
556
|
+
: _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] }));
|
|
206
557
|
}
|
|
207
|
-
function CredentialRow({ credential, label, busy, disabled,
|
|
558
|
+
function CredentialRow({ credential, label, busy, disabled, dragHandle, kindLabel, quota, onToggle, onTest, onEdit, onDelete, deleteAriaLabel, }) {
|
|
208
559
|
const actionDisabled = disabled || busy;
|
|
209
560
|
const tone = healthTone(credential.health);
|
|
210
561
|
const stateLabel = `${credential.enabled ? '已启用' : '已停用'} · ${healthLabel(credential.health)}`;
|
|
211
|
-
return (_jsxs("div", { "data-credential-state": credential.enabled ? 'enabled' : 'disabled', className: cn('flex items-center gap-3 rounded-lg border
|
|
562
|
+
return (_jsxs("div", { "data-credential-state": credential.enabled ? 'enabled' : 'disabled', className: cn('flex flex-wrap items-center gap-x-3 gap-y-2 rounded-lg border px-3 py-2.5 transition-colors', credential.enabled ? tone.row : 'border-border/50 bg-muted/40 opacity-70'), children: [dragHandle, _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { "aria-label": stateLabel, className: cn('h-2 w-2 shrink-0 rounded-full', credential.enabled ? tone.dot : 'bg-muted-foreground/40') }) }), _jsx(TooltipContent, { className: "text-xs", children: stateLabel })] }), _jsxs("div", { className: "min-w-0 flex-1 basis-24", children: [_jsx("p", { className: "truncate text-sm font-medium text-foreground", children: label }), kindLabel ? _jsx("p", { className: "text-xs text-muted-foreground", children: kindLabel }) : null, credential.maskedHint && !label.includes(credential.maskedHint)
|
|
563
|
+
? _jsx("p", { className: "mt-1 truncate font-mono text-xs text-muted-foreground", children: credential.maskedHint })
|
|
564
|
+
: null] }), _jsx("div", { className: "order-last w-full min-w-0 sm:order-none sm:w-auto sm:max-w-[45%]", role: "group", "aria-label": `${credentialDisplayLabel(credential)}额度`, children: quota }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [onTest ? (_jsx(RowAction, { label: `测试连接 ${label}`, disabled: actionDisabled, onClick: () => onTest(credential), children: _jsx(PlugZap, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })) : null, onEdit ? (_jsx(RowAction, { label: `编辑 ${label}`, disabled: actionDisabled, onClick: onEdit, children: _jsx(Pencil, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })) : null, onToggle ? (_jsx(RowAction, { label: `${credential.enabled ? '停用' : '启用'} ${label}`, disabled: actionDisabled, onClick: () => onToggle(credential, { enabled: !credential.enabled }), children: credential.enabled
|
|
212
565
|
? _jsx(Pause, { "aria-hidden": "true", className: "h-3.5 w-3.5" })
|
|
213
566
|
: _jsx(Play, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })) : null, onDelete ? (_jsx(RowAction, { label: deleteAriaLabel ?? `删除 ${label}`, disabled: actionDisabled, onClick: onDelete, children: _jsx(Trash2, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })) : null] })] }));
|
|
214
567
|
}
|
|
@@ -262,12 +615,12 @@ function maskAccountLabel(value) {
|
|
|
262
615
|
const at = value.indexOf('@');
|
|
263
616
|
if (at > 0) {
|
|
264
617
|
const accountName = value.slice(0, at);
|
|
265
|
-
const visible = accountName.length >
|
|
266
|
-
? `${accountName
|
|
267
|
-
:
|
|
618
|
+
const visible = accountName.length > 6
|
|
619
|
+
? `${accountName.slice(0, 3)}***${accountName.slice(-2)}`
|
|
620
|
+
: accountName.length > 1
|
|
621
|
+
? `${accountName[0]}***${accountName[accountName.length - 1]}`
|
|
622
|
+
: `${accountName[0]}***`;
|
|
268
623
|
return `${visible}${value.slice(at)}`;
|
|
269
624
|
}
|
|
270
|
-
|
|
271
|
-
return `${value[0] ?? ''}***`;
|
|
272
|
-
return `${value[0]}***${value[value.length - 1]}`;
|
|
625
|
+
return value;
|
|
273
626
|
}
|