@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.
Files changed (223) hide show
  1. package/config/cloud.json +1 -1
  2. package/config/main.json +36 -2
  3. package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.d.ts +3 -0
  4. package/dist/api/ai-gateway/auth/GatewayApiKeyAuthenticator.js +1 -1
  5. package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.d.ts +2 -1
  6. package/dist/api/ai-gateway/auth/PodGatewayAccessKeyRepository.js +101 -32
  7. package/dist/api/ai-gateway/connect/DeviceCodeProtocol.d.ts +80 -0
  8. package/dist/api/ai-gateway/connect/DeviceCodeProtocol.js +3 -0
  9. package/dist/api/ai-gateway/connect/FileSessionImportAdapter.d.ts +38 -0
  10. package/dist/api/ai-gateway/connect/FileSessionImportAdapter.js +63 -0
  11. package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.d.ts +24 -0
  12. package/dist/api/ai-gateway/connect/LoopbackAuthorizationCallbackReceiver.js +136 -0
  13. package/dist/api/ai-gateway/connect/OAuthConnectAdapter.d.ts +2 -0
  14. package/dist/api/ai-gateway/connect/OAuthConnectAdapter.js +2 -1
  15. package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.d.ts +17 -13
  16. package/dist/api/ai-gateway/connect/OAuthIntegrationRegistry.js +52 -19
  17. package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.d.ts +4 -28
  18. package/dist/api/ai-gateway/connect/OpenAiSubscriptionSessionImportAdapter.js +7 -58
  19. package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.d.ts +4 -0
  20. package/dist/api/ai-gateway/connect/ProviderAuthorizationProfiles.js +116 -0
  21. package/dist/api/ai-gateway/connect/SessionImportProfiles.d.ts +10 -0
  22. package/dist/api/ai-gateway/connect/SessionImportProfiles.js +112 -0
  23. package/dist/api/ai-gateway/connect/index.d.ts +125 -23
  24. package/dist/api/ai-gateway/connect/index.js +1277 -260
  25. package/dist/api/ai-gateway/models/PodModelSelectionRepository.d.ts +4 -1
  26. package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +154 -19
  27. package/dist/api/ai-gateway/providers/DeepSeekRuntimeAdapter.js +10 -6
  28. package/dist/api/ai-gateway/providers/ModelsDevCatalog.d.ts +7 -0
  29. package/dist/api/ai-gateway/providers/ModelsDevCatalog.js +25 -1
  30. package/dist/api/ai-gateway/providers/OfferingAuthorization.d.ts +24 -0
  31. package/dist/api/ai-gateway/providers/OfferingAuthorization.js +37 -0
  32. package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.d.ts +1 -1
  33. package/dist/api/ai-gateway/providers/OpenAiRuntimeAdapter.js +5 -3
  34. package/dist/api/ai-gateway/providers/ProviderRegistry.d.ts +6 -3
  35. package/dist/api/ai-gateway/providers/ProviderRegistry.js +55 -16
  36. package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.d.ts +2 -0
  37. package/dist/api/ai-gateway/providers/ProviderRuntimeAdapter.js +37 -4
  38. package/dist/api/ai-gateway/providers/ProviderRuntimeRegistry.js +7 -0
  39. package/dist/api/ai-gateway/quota/SubscriptionQuotaAdapters.js +2 -1
  40. package/dist/api/ai-gateway/routing/ModelRouter.js +3 -1
  41. package/dist/api/container/common.js +30 -40
  42. package/dist/api/container/index.js +6 -2
  43. package/dist/api/container/routes.js +5 -22
  44. package/dist/api/handlers/AiClientConfigurationHandler.js +39 -1
  45. package/dist/api/handlers/AiGatewayManagementHandler.d.ts +3 -0
  46. package/dist/api/handlers/AiGatewayManagementHandler.js +173 -58
  47. package/dist/api/service/AiClientConfigurationService.d.ts +4 -15
  48. package/dist/api/service/AiClientConfigurationService.js +17 -64
  49. package/dist/cli/commands/start.js +2 -0
  50. package/dist/components/components.jsonld +1 -0
  51. package/dist/components/context.jsonld +80 -0
  52. package/dist/http/InternalPodDataHttpHandler.d.ts +3 -0
  53. package/dist/http/InternalPodDataHttpHandler.js +13 -2
  54. package/dist/http/InternalPodDataHttpHandler.jsonld +12 -0
  55. package/dist/identity/oidc/RememberedClientPromptFactory.d.ts +1 -1
  56. package/dist/identity/oidc/RememberedClientPromptFactory.js +5 -2
  57. package/dist/identity/oidc/RememberedConsentHandler.js +1 -1
  58. package/dist/identity/oidc/ScopedPickWebIdHandler.d.ts +1 -0
  59. package/dist/identity/oidc/ScopedPickWebIdHandler.js +26 -0
  60. package/dist/identity/oidc/ScopedPickWebIdHandler.jsonld +4 -0
  61. package/dist/identity/oidc/SessionBoundIdentityProviderFactory.d.ts +10 -0
  62. package/dist/identity/oidc/SessionBoundIdentityProviderFactory.js +21 -0
  63. package/dist/identity/oidc/SessionBoundIdentityProviderFactory.jsonld +172 -0
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.js +4 -2
  66. package/dist/runtime/component-parameter-keys.js +18 -2
  67. package/dist/storage/accessors/MixDataAccessor.js +6 -2
  68. package/dist/storage/rdf/PostgresRdfEngine.js +10 -2
  69. package/dist/storage/rdf/RdfContentTypes.js +4 -0
  70. package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.d.ts +3 -0
  71. package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.js +13 -16
  72. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsList.js +1 -3
  73. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.d.ts +2 -1
  74. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.js +2 -2
  75. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +4 -1
  76. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +127 -57
  77. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.d.ts +6 -4
  78. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +436 -83
  79. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.d.ts +3 -2
  80. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +212 -76
  81. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.d.ts +6 -4
  82. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.js +58 -41
  83. package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.d.ts +4 -1
  84. package/node_modules/@undefineds.co/ai-connections/dist/AiQuotaCard.js +63 -4
  85. package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.d.ts +46 -6
  86. package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.js +143 -14
  87. package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.d.ts +19 -3
  88. package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.js +45 -7
  89. package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.d.ts +7 -1
  90. package/node_modules/@undefineds.co/ai-connections/dist/client-config/codex.js +305 -40
  91. package/node_modules/@undefineds.co/ai-connections/dist/client-config/index.cjs +383 -50
  92. package/node_modules/@undefineds.co/ai-connections/dist/client-config/json-env-adapter.js +3 -2
  93. package/node_modules/@undefineds.co/ai-connections/dist/client-config/pi.js +12 -3
  94. package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.d.ts +2 -0
  95. package/node_modules/@undefineds.co/ai-connections/dist/client-config/transaction.js +39 -4
  96. package/node_modules/@undefineds.co/ai-connections/dist/client-config/types.d.ts +8 -1
  97. package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.d.ts +2 -0
  98. package/node_modules/@undefineds.co/ai-connections/dist/client-visuals.js +14 -0
  99. package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +2 -2
  100. package/node_modules/@undefineds.co/ai-connections/dist/controller.js +216 -57
  101. package/node_modules/@undefineds.co/ai-connections/dist/index.d.ts +3 -1
  102. package/node_modules/@undefineds.co/ai-connections/dist/index.js +16 -2
  103. package/node_modules/@undefineds.co/ai-connections/dist/provider-visuals.js +6 -1
  104. package/node_modules/@undefineds.co/drizzle-solid/dist/core/execution/ldp-executor.js +27 -6
  105. package/node_modules/@undefineds.co/drizzle-solid/dist/core/pod-database.js +1 -1
  106. package/node_modules/@undefineds.co/drizzle-solid/dist/core/query-builders/select-query-builder.js +2 -2
  107. package/node_modules/@undefineds.co/drizzle-solid/dist/core/sparql/builder/update-builder.js +12 -1
  108. package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/builder.js +1 -1
  109. package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/array.js +8 -0
  110. package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/default.js +9 -1
  111. package/node_modules/@undefineds.co/drizzle-solid/dist/core/triple/handlers/inline.js +3 -3
  112. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/execution/ldp-executor.js +27 -6
  113. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/pod-database.js +1 -1
  114. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/query-builders/select-query-builder.js +2 -2
  115. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/sparql/builder/update-builder.js +12 -1
  116. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/builder.js +1 -1
  117. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/array.js +8 -0
  118. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/default.js +9 -1
  119. package/node_modules/@undefineds.co/drizzle-solid/dist/esm/core/triple/handlers/inline.js +3 -3
  120. package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +31 -0
  121. package/package.json +2 -2
  122. package/static/app/assets/{index-BikOyGPu.js → index-BCZVzMpj.js} +44 -44
  123. package/static/app/assets/inrupt-smoke.js +2 -2
  124. package/static/app/assets/main.css +1 -1
  125. package/static/app/assets/main.js +10 -10
  126. package/static/auth-callback/assets/{AiConfigContext-DrCH6ckh.js → AiConfigContext-DVkh8Kg2.js} +1 -1
  127. package/static/auth-callback/assets/{AiConfigPage-DVrlSSdA.js → AiConfigPage-HWVWpe-P.js} +1 -1
  128. package/static/auth-callback/assets/{DocumentProcessingPanel-_Md-v2R2.js → DocumentProcessingPanel-DDq-mH17.js} +1 -1
  129. package/static/auth-callback/assets/{IndexLifecyclePanel-BbWwe23H.js → IndexLifecyclePanel-Cx4cjrdP.js} +1 -1
  130. package/static/auth-callback/assets/{IndexSubjectPanel-upH9MjU-.js → IndexSubjectPanel-DzSw8M9j.js} +1 -1
  131. package/static/auth-callback/assets/{ModelAssignmentsPanel-CVGH4t4g.js → ModelAssignmentsPanel-BsRxGc2p.js} +1 -1
  132. package/static/auth-callback/assets/ModelsPage-DEe1lo6Q.js +10 -0
  133. package/static/auth-callback/assets/{NetworkPage-Dcbrb1fd.js → NetworkPage-DBiEh1Oz.js} +1 -1
  134. package/static/auth-callback/assets/{PaneListHeader-DrY-1wBq.js → PaneListHeader-lEEgjyN9.js} +1 -1
  135. package/static/auth-callback/assets/{SearchIndexingPanel-Dw25y53v.js → SearchIndexingPanel-CZCUUGt6.js} +1 -1
  136. package/static/auth-callback/assets/{StatusSubjectPanel-BHlz9rNF.js → StatusSubjectPanel-21qdueMr.js} +1 -1
  137. package/static/auth-callback/assets/{StatusWorkspace-BH7exQP5.js → StatusWorkspace-R8RKsqVz.js} +1 -1
  138. package/static/auth-callback/assets/{SystemSettingsPage-BYLKnM9f.js → SystemSettingsPage-BQ4e-4Wy.js} +1 -1
  139. package/static/auth-callback/assets/{SystemSettingsSubjectPanel-DT34LKku.js → SystemSettingsSubjectPanel-BopXD7SE.js} +1 -1
  140. package/static/auth-callback/assets/{activity-DdULRcSI.js → activity-DSfmTGfY.js} +1 -1
  141. package/static/auth-callback/assets/ai-connections-C03UWyPh.js +1 -0
  142. package/static/auth-callback/assets/{auth-callback-D7l2P25I.js → auth-callback-DleaitAS.js} +91 -91
  143. package/static/auth-callback/assets/auth-callback-vPSC2d2F.css +1 -0
  144. package/static/auth-callback/assets/{badge-j4zjFnmg.js → badge-B0aLu1sl.js} +1 -1
  145. package/static/auth-callback/assets/{download-DGXoks01.js → download-D_bFwdYJ.js} +1 -1
  146. package/static/auth-callback/assets/{external-link-CvbgXXaI.js → external-link-DRfjTaQu.js} +1 -1
  147. package/static/auth-callback/assets/{index-LRWvKJ2i.js → index-CG23cLYQ.js} +1 -1
  148. package/static/auth-callback/assets/{index-BOK3j1dz.js → index-CyV9ejwx.js} +1 -1
  149. package/static/auth-callback/assets/{index-browser-C1Qpe0g4.js → index-browser-D70paxIg.js} +1 -1
  150. package/static/auth-callback/assets/{key-round-CmgS5UdQ.js → key-round-BjV_mVYH.js} +1 -1
  151. package/static/auth-callback/assets/{rotate-ccw-c4CSG6Rq.js → rotate-ccw-CKoOAfse.js} +1 -1
  152. package/static/auth-callback/assets/{skeleton-ZPMTiRSM.js → skeleton-BZNjZaHx.js} +1 -1
  153. package/static/auth-callback/assets/{waypoints-xjyBflU5.js → waypoints-DoaJVzZv.js} +1 -1
  154. package/static/auth-callback/assets/{workspace-layout-B9nj6F5J.js → workspace-layout-CaP46_ZR.js} +1 -1
  155. package/static/auth-callback/auth-callback.html +2 -2
  156. package/static/dashboard/assets/{AiConfigContext-BWOOSYNr.js → AiConfigContext-BSrR27yD.js} +1 -1
  157. package/static/dashboard/assets/{AiConfigPage-CG2kQ9Wy.js → AiConfigPage-D65Z2LET.js} +1 -1
  158. package/static/dashboard/assets/{DocumentProcessingPanel-B4VXajfj.js → DocumentProcessingPanel-DyTe7Nei.js} +1 -1
  159. package/static/dashboard/assets/{IndexLifecyclePanel-CWCy0CC8.js → IndexLifecyclePanel-fK8ZGlrL.js} +1 -1
  160. package/static/dashboard/assets/{IndexSubjectPanel-CSgyQnG_.js → IndexSubjectPanel-CZPGor7v.js} +1 -1
  161. package/static/dashboard/assets/{ModelAssignmentsPanel-CdGIGidT.js → ModelAssignmentsPanel-CfYn5LMQ.js} +1 -1
  162. package/static/dashboard/assets/ModelsPage-djlJsghe.js +25 -0
  163. package/static/dashboard/assets/{NetworkPage-COia9NCH.js → NetworkPage-Coj6GnPl.js} +1 -1
  164. package/static/dashboard/assets/{PaneListHeader-SZWyr602.js → PaneListHeader-CUuFNSsZ.js} +1 -1
  165. package/static/dashboard/assets/{SearchIndexingPanel-D6PZtG6k.js → SearchIndexingPanel-CyJLioXS.js} +1 -1
  166. package/static/dashboard/assets/{StatusSubjectPanel-CxRD4kv0.js → StatusSubjectPanel-Cv26GSVC.js} +1 -1
  167. package/static/dashboard/assets/{StatusWorkspace-D3PfmY9-.js → StatusWorkspace-CDjFrsmB.js} +1 -1
  168. package/static/dashboard/assets/{SystemSettingsPage-COg1MmMj.js → SystemSettingsPage-BuNOyNoa.js} +1 -1
  169. package/static/dashboard/assets/{SystemSettingsSubjectPanel-BSVVB9Ol.js → SystemSettingsSubjectPanel-B2dasdez.js} +1 -1
  170. package/static/dashboard/assets/{activity-BJJx5cN-.js → activity-D1d2j_4Z.js} +1 -1
  171. package/static/dashboard/assets/ai-connections-C03UWyPh.js +1 -0
  172. package/static/dashboard/assets/{badge-elmrFSCj.js → badge-BeAAv6sp.js} +1 -1
  173. package/static/dashboard/assets/{dashboard-IvQrNmru.js → dashboard-BnGrUGv8.js} +68 -68
  174. package/static/dashboard/assets/dashboard-vPSC2d2F.css +1 -0
  175. package/static/dashboard/assets/{download-C3FH2KzG.js → download-DfFGiZRn.js} +1 -1
  176. package/static/dashboard/assets/{external-link-Bx7F0uKb.js → external-link-IsjU_LC9.js} +1 -1
  177. package/static/dashboard/assets/{index-liRoRIP1.js → index-BWQTucPe.js} +1 -1
  178. package/static/dashboard/assets/{index-M4-kWiGz.js → index-Dj75OjnW.js} +1 -1
  179. package/static/dashboard/assets/{key-round-CXg0bKAj.js → key-round-BJ_9wmQ_.js} +1 -1
  180. package/static/dashboard/assets/{rotate-ccw-yxJKOBrX.js → rotate-ccw-D3JGiFbR.js} +1 -1
  181. package/static/dashboard/assets/{skeleton-x9jNGrQT.js → skeleton-CC4nc-z-.js} +1 -1
  182. package/static/dashboard/assets/{waypoints-CTKeRsGp.js → waypoints-h7sqFqiG.js} +1 -1
  183. package/static/dashboard/assets/{workspace-layout--YkgkS38.js → workspace-layout-ZAPTOHhs.js} +1 -1
  184. package/static/dashboard/dashboard.html +2 -2
  185. package/static/settings/assets/{AiConfigContext-CelxXFcQ.js → AiConfigContext-lGkWpvKP.js} +1 -1
  186. package/static/settings/assets/{AiConfigPage-BaP-EovM.js → AiConfigPage-BbZWIBZq.js} +1 -1
  187. package/static/settings/assets/{DocumentProcessingPanel-DzLZluFv.js → DocumentProcessingPanel-DvYdjT-V.js} +1 -1
  188. package/static/settings/assets/{IndexLifecyclePanel-Bzj1-_oI.js → IndexLifecyclePanel-W5OGQZj7.js} +1 -1
  189. package/static/settings/assets/{IndexSubjectPanel-B263QLUh.js → IndexSubjectPanel-g_Aa5soq.js} +1 -1
  190. package/static/settings/assets/{ModelAssignmentsPanel-BAdT4UR0.js → ModelAssignmentsPanel-COlMBKXj.js} +1 -1
  191. package/static/settings/assets/ModelsPage-mApKi-aw.js +10 -0
  192. package/static/settings/assets/{NetworkPage-BK6a7l4g.js → NetworkPage-BDT05JUq.js} +1 -1
  193. package/static/settings/assets/{PaneListHeader-BAS2ICPq.js → PaneListHeader-CByhNpMG.js} +1 -1
  194. package/static/settings/assets/{SearchIndexingPanel-Cb_p-NgW.js → SearchIndexingPanel-BFAyzXls.js} +1 -1
  195. package/static/settings/assets/{StatusSubjectPanel-CcjqUua6.js → StatusSubjectPanel-Bz_XZ3VU.js} +1 -1
  196. package/static/settings/assets/{StatusWorkspace-7Ibty3_k.js → StatusWorkspace-CeVypHcW.js} +1 -1
  197. package/static/settings/assets/{SystemSettingsPage-DPSz4dg4.js → SystemSettingsPage-Dh7cQIfx.js} +1 -1
  198. package/static/settings/assets/{SystemSettingsSubjectPanel-Cb4HkKT5.js → SystemSettingsSubjectPanel-CSnpciO0.js} +1 -1
  199. package/static/settings/assets/{activity-Qhsr5Pxu.js → activity-DXz3EfFN.js} +1 -1
  200. package/static/settings/assets/ai-connections-C03UWyPh.js +1 -0
  201. package/static/settings/assets/{badge-B1vf2qCl.js → badge-1ZA144i4.js} +1 -1
  202. package/static/settings/assets/{download-CYNt0aZ-.js → download-CUHimlYa.js} +1 -1
  203. package/static/settings/assets/{external-link-ClKb39NT.js → external-link-BYHSVj2Z.js} +1 -1
  204. package/static/settings/assets/{index-y8ew3MLM.js → index-DKPDMdW5.js} +1 -1
  205. package/static/settings/assets/{index-browser-DJhvgpNI.js → index-browser-BaEFz0e0.js} +1 -1
  206. package/static/settings/assets/{index-BgiIDSch.js → index-viSgSvbO.js} +1 -1
  207. package/static/settings/assets/{key-round-5d-DrJz-.js → key-round-COByPOg8.js} +1 -1
  208. package/static/settings/assets/{rotate-ccw-CHZY7kS5.js → rotate-ccw-BC2EN6cE.js} +1 -1
  209. package/static/settings/assets/{settings-D5VRWyx6.js → settings-DLH9XUoT.js} +86 -86
  210. package/static/settings/assets/settings-vPSC2d2F.css +1 -0
  211. package/static/settings/assets/{skeleton-FNabNxNE.js → skeleton-B0-FOhKL.js} +1 -1
  212. package/static/settings/assets/{waypoints-BnL_8ljo.js → waypoints-DGMsvqWo.js} +1 -1
  213. package/static/settings/assets/{workspace-layout-D5q3F59S.js → workspace-layout-B0SVrJIu.js} +1 -1
  214. package/static/settings/settings.html +2 -2
  215. package/static/auth-callback/assets/ModelsPage-BpkVFKbf.js +0 -10
  216. package/static/auth-callback/assets/ai-connections-Bm52UO7Y.js +0 -1
  217. package/static/auth-callback/assets/auth-callback-01yz-UDU.css +0 -1
  218. package/static/dashboard/assets/ModelsPage-Chd5UK0D.js +0 -25
  219. package/static/dashboard/assets/ai-connections-Bm52UO7Y.js +0 -1
  220. package/static/dashboard/assets/dashboard-01yz-UDU.css +0 -1
  221. package/static/settings/assets/ModelsPage-C0NFa1zq.js +0 -10
  222. package/static/settings/assets/ai-connections-Bm52UO7Y.js +0 -1
  223. 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 { ArrowDown, ArrowUp, Eye, EyeOff, ExternalLink, KeyRound, Loader2, LogOut, Pause, Pencil, Play, PlugZap, Plus, RotateCw, Settings2, Trash2, } from 'lucide-react';
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
- return (_jsx(TooltipProvider, { children: _jsxs("section", { className: "space-y-4", "aria-label": "\u5F53\u524D\u8FDE\u63A5", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border/40 pb-2", children: [_jsx(Settings2, { className: "h-4 w-4 text-primary" }), _jsx("h3", { className: "text-sm font-medium text-foreground/90", children: "\u5F53\u524D\u8FDE\u63A5" })] }), _jsxs("div", { className: "space-y-3", children: [offerings.map((offering) => {
18
- const mode = modeForOffering(offering, definition);
19
- const offeringAttempt = attemptOfferingId === offering.id ? attempt : undefined;
20
- const offeringCredentials = credentials
21
- .filter((credential) => credential.offeringId === offering.id)
22
- .sort((left, right) => left.priority - right.priority);
23
- const offeringError = error?.offeringId === offering.id ? error.message : undefined;
24
- const quotaCredential = offeringCredentials.find((credential) => credential.enabled)
25
- ?? offeringCredentials[0];
26
- const quotaState = quotas[offering.id];
27
- const visibleQuotaState = quotaCredential && quotaState?.credentialId === quotaCredential.id
28
- ? quotaState
29
- : undefined;
30
- const quotaCard = (_jsx(AiQuotaCard, { providerName: definition.name, offeringName: offeringTitle(offering), quota: visibleQuotaState?.quota, busy: visibleQuotaState?.busy ?? false, disabled: disabled || (Boolean(product) && !quotaCredential), credentialLabel: quotaCredential ? credentialDisplayLabel(quotaCredential) : undefined, error: visibleQuotaState?.error, onRefresh: () => onRefreshQuota?.(offering, quotaCredential) }));
31
- if (offering.lifecycle === 'unavailable') {
32
- return (_jsx(OfferingItem, { offering: offering, children: _jsx("p", { className: "text-sm text-muted-foreground", children: offering.kind === 'oauth-subscription'
33
- ? '暂不可用:账号订阅需在 Xpod 桌面版中导入本机客户端(如 Codex CLI)的登录态,浏览器中无法完成。'
34
- : '暂不可用:该 Offering 尚未提供可用的连接流程。' }) }, offering.id));
35
- }
36
- if (offeringError && offeringAttempt?.status === 'unsupported' && mode === 'deviceCodeOAuth') {
37
- return (_jsxs(OfferingItem, { offering: offering, children: [_jsxs("div", { className: "rounded-md border border-border/60 bg-muted/30 px-3 py-3", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "\u5F53\u524D\u90E8\u7F72\u672A\u542F\u7528\u8D26\u53F7\u6388\u6743" }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: "\u4ECD\u53EF\u4F7F\u7528\u5176\u4ED6\u5DF2\u63D0\u4F9B\u7684\u63A5\u5165\u65B9\u5F0F\u3002" })] }), quotaCard] }, offering.id));
38
- }
39
- if (offeringError && mode === 'deviceCodeOAuth') {
40
- return (_jsxs(OfferingItem, { offering: offering, children: [_jsx(LoginFailureView, { title: "\u767B\u5F55\u672A\u5B8C\u6210", description: offeringError, primaryLabel: "\u91CD\u8BD5\u767B\u5F55", onPrimary: () => onBeginOffering?.(offering, mode), secondaryLabel: "\u5173\u95ED", onSecondary: onDismissError ?? (() => undefined) }), quotaCard] }, offering.id));
41
- }
42
- if (isPendingAttempt(offeringAttempt) && mode === 'deviceCodeOAuth') {
43
- return (_jsxs(OfferingItem, { offering: offering, children: [_jsx(LoginConnectingView, { title: "\u6B63\u5728\u8FDE\u63A5", detail: offeringAttempt?.userCode ? `验证码:${offeringAttempt.userCode}` : '请在打开的页面完成授权。', providerLabel: offeringTitle(offering), providerHost: definition.name }), quotaCard] }, offering.id));
44
- }
45
- if (mode === 'deviceCodeOAuth') {
46
- return (_jsxs(OfferingItem, { offering: offering, children: [_jsx("div", { className: "space-y-2", children: offeringCredentials.map((credential) => (_jsx(CredentialRow, { credential: credential, label: credential.label ? maskAccountLabel(credential.label) : credentialDisplayLabel(credential), busy: busy, disabled: disabled, onToggle: onUpdateCredential, onTest: onTestCredential, onDelete: () => onDisconnect(credential), deleteAriaLabel: `${credential.label ? maskAccountLabel(credential.label) : credentialDisplayLabel(credential)} 移除` }, credential.id))) }), _jsx("div", { className: "flex flex-wrap gap-2", children: _jsxs(Button, { size: "sm", disabled: busy || disabled, onClick: () => onBeginOffering?.(offering, mode), children: [busy ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), offeringCredentials.length ? '添加账号' : '登录'] }) }), quotaCard] }, offering.id));
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
- if (offering.authModes?.includes('local')) {
49
- const importsOpenAiSession = definition.id === 'openai' && offering.id === 'official-subscription';
50
- return (_jsx(OfferingItem, { offering: offering, children: _jsxs("div", { className: "space-y-2", children: [offeringCredentials.map((credential) => (_jsx(CredentialRow, { credential: credential, label: credential.label ?? '本地 Ollama', busy: busy, disabled: disabled, onToggle: onUpdateCredential, onTest: onTestCredential, onDelete: () => onDeleteCredential?.(credential), deleteAriaLabel: `删除 ${credential.label ?? '本地 Ollama'}` }, credential.id))), offeringCredentials.length === 0 ? (_jsxs("div", { className: "space-y-2", children: [importsOpenAiSession ? (_jsx("p", { className: "text-xs text-muted-foreground", children: "\u4ECE\u5F53\u524D\u8BBE\u5907\u5DF2\u6709\u7684 OpenAI \u767B\u5F55\u5BFC\u5165\uFF0C\u4E0D\u4F1A\u53D1\u8D77\u65B0\u7684\u6D4F\u89C8\u5668\u6388\u6743\u3002" })) : null, _jsxs(Button, { variant: "outline", size: "sm", disabled: busy || disabled || !onCreateLocalCredential, onClick: () => void onCreateLocalCredential?.(offering), children: [busy ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Plus, { className: "mr-2 h-4 w-4" }), localOfferingActionLabel(offering)] })] })) : null] }) }, offering.id));
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
- return (_jsxs(OfferingItem, { offering: offering, children: [_jsx(ApiKeyPool, { definition: definition, offering: offering, status: status, accountLabel: accountLabel, credentials: offeringCredentials, attempt: attempt, apiKey: apiKey, baseUrl: baseUrl, busy: busy, disabled: disabled, error: offeringError, onApiKeyChange: onApiKeyChange, onBaseUrlChange: onBaseUrlChange, onBeginApiKey: onBeginApiKey, onBeginBrowser: onBeginBrowser, onSaveApiKey: onSaveApiKey, onDisconnect: onDisconnect, onCreateApiKeyCredential: onCreateApiKeyCredential
53
- ? (input) => onCreateApiKeyCredential(offering, input)
54
- : undefined, onUpdateCredential: onUpdateCredential, onDeleteCredential: onDeleteCredential, onTestCredential: onTestCredential, onMoveCredential: (fromIndex, toIndex) => onReorderCredentials?.(offering, offeringCredentials, fromIndex, toIndex) }), quotaCard] }, offering.id));
55
- }), error && !error.offeringId && !suppressError ? (_jsx("p", { className: "rounded-md border border-destructive/30 px-3 py-2 text-sm text-destructive", children: error.message })) : null] })] }) }));
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, children }) {
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-3 rounded-lg border border-border/50 bg-card p-3", "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) }), 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] })] }), 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, children] }));
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 = (offering.authModes ?? []).map((mode) => mode === 'local'
101
- ? localAuthLabel(offering)
102
- : mode === 'oauth' || mode === 'deviceCode' ? '账号授权' : 'API Key');
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 localAuthLabel(offering) {
106
- return offering.id === 'official-subscription' && offering.productLabel === 'OpenAI'
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 localOfferingActionLabel(offering) {
111
- return offering.id === 'official-subscription' && offering.productLabel === 'OpenAI'
112
- ? '导入本机 OpenAI 登录'
113
- : '连接本地 Ollama';
439
+ function isApiKeyMethod(method) {
440
+ return method.authMode === 'apiKey' || connectModeForMethod(method) === 'browserAssistedApiKey';
114
441
  }
115
- function ApiKeyPool({ definition, offering, status, accountLabel, credentials, attempt, apiKey, baseUrl, busy, disabled, error, onApiKeyChange, onBaseUrlChange, onBeginApiKey, onBeginBrowser, onSaveApiKey, onDisconnect, onCreateApiKeyCredential, onUpdateCredential, onDeleteCredential, onTestCredential, onMoveCredential, }) {
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: "space-y-4", children: [_jsx("div", { children: credentials.length > 0 ? (_jsx("div", { className: "space-y-2", children: credentials.map((credential, index) => (_jsx(CredentialRow, { credential: credential, label: credentialDisplayLabel(credential), busy: busy, disabled: disabled, onToggle: onUpdateCredential, onTest: onTestCredential, onEdit: () => openEditForm(credential), onDelete: () => onDeleteCredential?.(credential), deleteAriaLabel: `删除 ${credentialDisplayLabel(credential)}`, beforeActions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", "aria-label": `上移 ${credentialDisplayLabel(credential)}`, disabled: index === 0 || disabled || busy, onClick: () => onMoveCredential?.(index, index - 1), children: _jsx(ArrowUp, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", "aria-label": `下移 ${credentialDisplayLabel(credential)}`, disabled: index === credentials.length - 1 || disabled || busy, onClick: () => onMoveCredential?.(index, index + 1), children: _jsx(ArrowDown, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })] }) }, credential.id))) })) : accountLabel ? (_jsx("p", { className: "text-xs text-muted-foreground", children: maskAccountLabel(accountLabel) })) : (_jsx("p", { className: "text-xs text-muted-foreground", children: "Provider \u51ED\u8BC1\u4FDD\u5B58\u5728\u5F53\u524D Pod\uFF0C\u7531 Pod \u6743\u9650\u4FDD\u62A4\u3002" })) }), 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, _jsxs("div", { className: "flex flex-wrap gap-2", children: [onCreateApiKeyCredential ? (_jsxs(Button, { variant: "outline", size: "sm", disabled: busy || disabled, 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, 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, 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, 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, onClick: onDisconnect, children: "\u79FB\u9664\u914D\u7F6E" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", disabled: busy || disabled || 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, onClick: onBeginApiKey, children: [_jsx(KeyRound, { className: "mr-2 h-4 w-4" }), "\u914D\u7F6E API Key"] })] }))] }), formMode ? (_jsxs("div", { className: "space-y-2 rounded-lg border border-border/50 bg-muted/10 p-3", children: [formMode === 'edit' ? (_jsx(Input, { 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, { 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, { autoComplete: "off", "data-lpignore": "true", "data-1p-ignore": true, "aria-label": `${definition.name} Base URL 输入`, placeholder: offeringEndpoint(offering) || definition.defaultBaseUrl || '默认服务地址', value: poolBaseUrl, onChange: (event) => setPoolBaseUrl(event.target.value), className: "font-mono text-xs" }), _jsx(Input, { 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 || (formMode === 'create' && !poolApiKey.trim()), onClick: savePoolForm, children: formMode === 'create' ? '保存 API Key' : '保存凭证' }), _jsx(Button, { variant: "ghost", size: "sm", onClick: closePoolForm, children: "\u53D6\u6D88" }), formMode === 'create' ? (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setShowAdvanced((current) => !current), children: showAdvanced ? '收起高级设置' : '高级设置' })) : null] })] })) : null, apiKeyAttempt ? (_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, { 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
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, beforeActions, onToggle, onTest, onEdit, onDelete, deleteAriaLabel, }) {
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 p-3 transition-colors', credential.enabled ? tone.row : 'border-border/50 bg-muted/40 opacity-70'), children: [_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", children: [_jsx("p", { className: "truncate text-sm font-medium text-foreground", children: label }), credential.maskedHint ? _jsx("p", { className: "mt-1 truncate font-mono text-xs text-muted-foreground", children: credential.maskedHint }) : null] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [beforeActions, 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
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 > 1
266
- ? `${accountName[0]}***${accountName[accountName.length - 1]}`
267
- : `${accountName[0]}***`;
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
- if (value.length <= 2)
271
- return `${value[0] ?? ''}***`;
272
- return `${value[0]}***${value[value.length - 1]}`;
625
+ return value;
273
626
  }