@undefineds.co/xpod 0.4.11 → 0.4.12

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 (372) hide show
  1. package/config/components-ignore.json +5 -1
  2. package/config/notifications.json +68 -0
  3. package/config/xpod.base.json +9 -11
  4. package/dist/ai/service/AiCredentialSecret.d.ts +30 -0
  5. package/dist/ai/service/AiCredentialSecret.js +120 -0
  6. package/dist/ai/service/CredentialReaderImpl.d.ts +18 -0
  7. package/dist/ai/service/CredentialReaderImpl.js +81 -5
  8. package/dist/ai/service/CredentialReaderImpl.jsonld +42 -2
  9. package/dist/ai/service/EmbeddingModelPolicy.d.ts +79 -0
  10. package/dist/ai/service/EmbeddingModelPolicy.js +134 -0
  11. package/dist/ai/service/EmbeddingProviderError.js +6 -0
  12. package/dist/ai/service/EmbeddingServiceImpl.d.ts +13 -1
  13. package/dist/ai/service/EmbeddingServiceImpl.js +18 -5
  14. package/dist/ai/service/EmbeddingServiceImpl.jsonld +35 -0
  15. package/dist/ai/service/index.d.ts +2 -0
  16. package/dist/ai/service/index.js +2 -0
  17. package/dist/api/ai-gateway/connect/index.d.ts +20 -2
  18. package/dist/api/ai-gateway/connect/index.js +162 -16
  19. package/dist/api/ai-gateway/credentials/AiCredentialSecretDecoder.d.ts +12 -0
  20. package/dist/api/ai-gateway/credentials/AiCredentialSecretDecoder.js +30 -0
  21. package/dist/api/ai-gateway/errors.d.ts +1 -1
  22. package/dist/api/ai-gateway/models/GatewayEmbeddingModelCatalog.d.ts +11 -0
  23. package/dist/api/ai-gateway/models/GatewayEmbeddingModelCatalog.js +31 -0
  24. package/dist/api/ai-gateway/models/PodModelSelectionRepository.js +7 -3
  25. package/dist/api/ai-gateway/models/ProviderCustomModelsService.d.ts +18 -0
  26. package/dist/api/ai-gateway/models/ProviderCustomModelsService.js +23 -0
  27. package/dist/api/ai-gateway/models/ProviderModelDiscoveryAdapters.d.ts +3 -1
  28. package/dist/api/ai-gateway/models/ProviderModelDiscoveryAdapters.js +2 -45
  29. package/dist/api/ai-gateway/models/ProviderModelSelectionService.d.ts +10 -0
  30. package/dist/api/ai-gateway/models/ProviderModelSelectionService.js +57 -20
  31. package/dist/api/ai-gateway/models/ProviderModelType.d.ts +28 -0
  32. package/dist/api/ai-gateway/models/ProviderModelType.js +34 -0
  33. package/dist/api/ai-gateway/models/ProviderModelsAdapter.d.ts +10 -0
  34. package/dist/api/ai-gateway/models/ProviderModelsAdapter.js +3 -4
  35. package/dist/api/ai-gateway/models/index.d.ts +1 -0
  36. package/dist/api/ai-gateway/models/index.js +3 -1
  37. package/dist/api/ai-gateway/providers/CustomRuntimeAdapter.d.ts +7 -0
  38. package/dist/api/ai-gateway/providers/CustomRuntimeAdapter.js +9 -2
  39. package/dist/api/ai-gateway/providers/ModelsDevCatalog.d.ts +7 -1
  40. package/dist/api/ai-gateway/providers/ModelsDevCatalog.js +4 -10
  41. package/dist/api/ai-gateway/providers/ProviderRegistry.d.ts +107 -0
  42. package/dist/api/ai-gateway/providers/ProviderRegistry.js +244 -21
  43. package/dist/api/ai-gateway/providers/ProviderRuntimeRegistry.d.ts +5 -0
  44. package/dist/api/ai-gateway/providers/ProviderRuntimeRegistry.js +5 -1
  45. package/dist/api/ai-gateway/quota/SubscriptionQuotaAdapters.d.ts +12 -4
  46. package/dist/api/ai-gateway/quota/SubscriptionQuotaAdapters.js +11 -11
  47. package/dist/api/ai-gateway/routing/ModelRouter.d.ts +14 -0
  48. package/dist/api/ai-gateway/routing/ModelRouter.js +43 -4
  49. package/dist/api/ai-gateway/service-access/AiConnectionsServiceAccess.d.ts +9 -1
  50. package/dist/api/ai-gateway/service-access/AiConnectionsServiceAccess.js +3 -27
  51. package/dist/api/chatkit/pod-store.d.ts +31 -0
  52. package/dist/api/chatkit/pod-store.js +132 -8
  53. package/dist/api/container/common.js +24 -4
  54. package/dist/api/container/routes.js +17 -2
  55. package/dist/api/container/types.d.ts +3 -1
  56. package/dist/api/handlers/AdminHandler.d.ts +13 -0
  57. package/dist/api/handlers/AdminHandler.js +47 -18
  58. package/dist/api/handlers/AiConfigHandler.d.ts +6 -0
  59. package/dist/api/handlers/AiConfigHandler.js +28 -0
  60. package/dist/api/handlers/AiGatewayManagementHandler.js +36 -0
  61. package/dist/api/runs/RdfRunContextRetriever.d.ts +14 -0
  62. package/dist/api/runs/RdfRunContextRetriever.js +41 -5
  63. package/dist/api/runtime.js +2 -1
  64. package/dist/cli/commands/secret.d.ts +1 -0
  65. package/dist/cli/commands/secret.js +25 -7
  66. package/dist/components/components.jsonld +3 -1
  67. package/dist/components/context.jsonld +53 -1
  68. package/dist/http/SubgraphSparqlHttpHandler.d.ts +59 -2
  69. package/dist/http/SubgraphSparqlHttpHandler.js +145 -13
  70. package/dist/http/SubgraphSparqlHttpHandler.jsonld +37 -0
  71. package/dist/identity/drizzle/EdgeNodeRepository.d.ts +0 -12
  72. package/dist/identity/drizzle/EdgeNodeRepository.js +5 -13
  73. package/dist/index.d.ts +2 -1
  74. package/dist/index.js +7 -3
  75. package/dist/logging/log-file.d.ts +17 -0
  76. package/dist/logging/log-file.js +69 -0
  77. package/dist/main.js +2 -1
  78. package/dist/notifications/NotificationChannelSweeper.d.ts +50 -0
  79. package/dist/notifications/NotificationChannelSweeper.js +131 -0
  80. package/dist/notifications/NotificationChannelSweeper.jsonld +136 -0
  81. package/dist/notifications/ReclaimingWebSocket2023Storer.d.ts +25 -0
  82. package/dist/notifications/ReclaimingWebSocket2023Storer.js +56 -0
  83. package/dist/notifications/ReclaimingWebSocket2023Storer.jsonld +77 -0
  84. package/dist/notifications/index.d.ts +2 -0
  85. package/dist/notifications/index.js +2 -0
  86. package/dist/runtime/Proxy.d.ts +37 -0
  87. package/dist/runtime/Proxy.js +88 -16
  88. package/dist/runtime/bootstrap.js +2 -1
  89. package/dist/runtime/upgrade/BunNativeUpgradeRelay.d.ts +116 -0
  90. package/dist/runtime/upgrade/BunNativeUpgradeRelay.js +423 -0
  91. package/dist/runtime/upgrade/UpgradeHandshake.d.ts +63 -0
  92. package/dist/runtime/upgrade/UpgradeHandshake.js +199 -0
  93. package/dist/runtime/upgrade/WebSocketFrames.d.ts +67 -0
  94. package/dist/runtime/upgrade/WebSocketFrames.js +224 -0
  95. package/node_modules/@undefineds.co/ai-connections/dist/AiApiKeyPool.js +8 -1
  96. package/node_modules/@undefineds.co/ai-connections/dist/AiAuthorizationActions.d.ts +11 -2
  97. package/node_modules/@undefineds.co/ai-connections/dist/AiAuthorizationActions.js +27 -10
  98. package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.d.ts +7 -0
  99. package/node_modules/@undefineds.co/ai-connections/dist/AiClientConfigurationSection.js +13 -11
  100. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectDialog.js +1 -1
  101. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsList.js +10 -6
  102. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.d.ts +15 -0
  103. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMain.js +29 -3
  104. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsMainHeader.js +18 -3
  105. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.d.ts +16 -1
  106. package/node_modules/@undefineds.co/ai-connections/dist/AiConnectionsPanel.js +92 -20
  107. package/node_modules/@undefineds.co/ai-connections/dist/AiCopyButton.d.ts +35 -0
  108. package/node_modules/@undefineds.co/ai-connections/dist/AiCopyButton.js +45 -0
  109. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialPoolSection.js +71 -11
  110. package/node_modules/@undefineds.co/ai-connections/dist/AiCredentialRow.js +9 -7
  111. package/node_modules/@undefineds.co/ai-connections/dist/AiEndpointList.d.ts +23 -0
  112. package/node_modules/@undefineds.co/ai-connections/dist/AiEndpointList.js +24 -0
  113. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.d.ts +13 -4
  114. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayKeysSection.js +35 -38
  115. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayModelsSection.d.ts +21 -0
  116. package/node_modules/@undefineds.co/ai-connections/dist/AiGatewayModelsSection.js +51 -0
  117. package/node_modules/@undefineds.co/ai-connections/dist/AiInfoTooltip.d.ts +15 -0
  118. package/node_modules/@undefineds.co/ai-connections/dist/AiInfoTooltip.js +16 -0
  119. package/node_modules/@undefineds.co/ai-connections/dist/AiModelCatalog.d.ts +175 -0
  120. package/node_modules/@undefineds.co/ai-connections/dist/AiModelCatalog.js +133 -0
  121. package/node_modules/@undefineds.co/ai-connections/dist/AiOfferingDetails.js +2 -2
  122. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderCard.js +13 -64
  123. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderHeader.d.ts +21 -0
  124. package/node_modules/@undefineds.co/ai-connections/dist/AiProviderHeader.js +12 -0
  125. package/node_modules/@undefineds.co/ai-connections/dist/AiRowAction.d.ts +15 -0
  126. package/node_modules/@undefineds.co/ai-connections/dist/AiRowAction.js +12 -0
  127. package/node_modules/@undefineds.co/ai-connections/dist/AiSortableCredentialList.js +2 -1
  128. package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.d.ts +7 -0
  129. package/node_modules/@undefineds.co/ai-connections/dist/ai-connections-client.js +6 -0
  130. package/node_modules/@undefineds.co/ai-connections/dist/authorization-methods.d.ts +36 -5
  131. package/node_modules/@undefineds.co/ai-connections/dist/authorization-methods.js +52 -9
  132. package/node_modules/@undefineds.co/ai-connections/dist/client/normalize.js +30 -3
  133. package/node_modules/@undefineds.co/ai-connections/dist/client/types.d.ts +21 -1
  134. package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.d.ts +1 -3
  135. package/node_modules/@undefineds.co/ai-connections/dist/client-config/base-adapter.js +3 -7
  136. package/node_modules/@undefineds.co/ai-connections/dist/client-config/index.cjs +9 -7
  137. package/node_modules/@undefineds.co/ai-connections/dist/collection-runtime.d.ts +46 -0
  138. package/node_modules/@undefineds.co/ai-connections/dist/collection-runtime.js +231 -0
  139. package/node_modules/@undefineds.co/ai-connections/dist/collections.d.ts +119 -0
  140. package/node_modules/@undefineds.co/ai-connections/dist/collections.js +173 -0
  141. package/node_modules/@undefineds.co/ai-connections/dist/controller.d.ts +71 -5
  142. package/node_modules/@undefineds.co/ai-connections/dist/controller.js +285 -16
  143. package/node_modules/@undefineds.co/ai-connections/dist/credential-labels.d.ts +5 -0
  144. package/node_modules/@undefineds.co/ai-connections/dist/credential-labels.js +8 -3
  145. package/node_modules/@undefineds.co/ai-connections/dist/credential-storage.d.ts +59 -0
  146. package/node_modules/@undefineds.co/ai-connections/dist/credential-storage.js +131 -0
  147. package/node_modules/@undefineds.co/ai-connections/dist/endpoint-urls.d.ts +15 -0
  148. package/node_modules/@undefineds.co/ai-connections/dist/endpoint-urls.js +20 -0
  149. package/node_modules/@undefineds.co/ai-connections/dist/gateway-endpoints.d.ts +14 -0
  150. package/node_modules/@undefineds.co/ai-connections/dist/gateway-endpoints.js +20 -0
  151. package/node_modules/@undefineds.co/ai-connections/dist/index.d.ts +8 -0
  152. package/node_modules/@undefineds.co/ai-connections/dist/index.js +8 -0
  153. package/node_modules/@undefineds.co/ai-connections/dist/manifest.js +1 -1
  154. package/node_modules/@undefineds.co/ai-connections/dist/offering-endpoints.d.ts +14 -0
  155. package/node_modules/@undefineds.co/ai-connections/dist/offering-endpoints.js +17 -0
  156. package/node_modules/@undefineds.co/ai-connections/dist/provider-catalog.cjs +68 -1
  157. package/node_modules/@undefineds.co/ai-connections/dist/provider-catalog.d.ts +75 -1
  158. package/node_modules/@undefineds.co/ai-connections/dist/provider-catalog.js +119 -0
  159. package/node_modules/@undefineds.co/ai-connections/dist/provider-visuals.d.ts +5 -0
  160. package/node_modules/@undefineds.co/ai-connections/dist/provider-visuals.js +5 -0
  161. package/node_modules/@undefineds.co/ai-connections/dist/service-access.js +2 -27
  162. package/node_modules/@undefineds.co/ai-connections/package.json +1 -0
  163. package/node_modules/@undefineds.co/extension-sdk/dist/manifest.d.ts +1 -1
  164. package/node_modules/@undefineds.co/extension-sdk/dist/manifest.js +2 -0
  165. package/node_modules/@undefineds.co/extension-sdk/dist/web.d.ts +150 -0
  166. package/node_modules/@undefineds.co/extension-sdk/package.json +2 -1
  167. package/node_modules/@undefineds.co/models/README.md +1 -1
  168. package/node_modules/@undefineds.co/models/dist/ai-config/index.d.ts +3 -3
  169. package/node_modules/@undefineds.co/models/dist/ai-gateway.schema.d.ts +5 -0
  170. package/node_modules/@undefineds.co/models/dist/ai-gateway.schema.js +9 -0
  171. package/node_modules/@undefineds.co/models/dist/ai-runtime.repository.d.ts +1 -1
  172. package/node_modules/@undefineds.co/models/dist/approval.repository.d.ts +2 -2
  173. package/node_modules/@undefineds.co/models/dist/audit.presentation.d.ts +2 -2
  174. package/node_modules/@undefineds.co/models/dist/capture.repository.d.ts +1 -1
  175. package/node_modules/@undefineds.co/models/dist/chat-project.repository.d.ts +1 -1
  176. package/node_modules/@undefineds.co/models/dist/chat.repository.d.ts +2 -2
  177. package/node_modules/@undefineds.co/models/dist/control-request.repository.d.ts +2 -2
  178. package/node_modules/@undefineds.co/models/dist/conversation-share.repository.d.ts +1 -1
  179. package/node_modules/@undefineds.co/models/dist/credential.schema.d.ts +12 -0
  180. package/node_modules/@undefineds.co/models/dist/credential.schema.js +15 -1
  181. package/node_modules/@undefineds.co/models/dist/discovery/index.d.ts +2 -2
  182. package/node_modules/@undefineds.co/models/dist/discovery/providers.json +248 -7
  183. package/node_modules/@undefineds.co/models/dist/discovery/types.d.ts +31 -0
  184. package/node_modules/@undefineds.co/models/dist/extension/index.d.ts +1 -1
  185. package/node_modules/@undefineds.co/models/dist/favorite/index.d.ts +2 -2
  186. package/node_modules/@undefineds.co/models/dist/fixtures/contracts-chat-contact.d.ts +5 -5
  187. package/node_modules/@undefineds.co/models/dist/import/index.d.ts +1 -1
  188. package/node_modules/@undefineds.co/models/dist/index.d.ts +73 -73
  189. package/node_modules/@undefineds.co/models/dist/index.js +1 -1
  190. package/node_modules/@undefineds.co/models/dist/input-request.repository.d.ts +2 -2
  191. package/node_modules/@undefineds.co/models/dist/knowledge/index.d.ts +1 -1
  192. package/node_modules/@undefineds.co/models/dist/message.repository.d.ts +1 -1
  193. package/node_modules/@undefineds.co/models/dist/namespaces.js +5 -0
  194. package/node_modules/@undefineds.co/models/dist/pod-storage-descriptor.d.ts +4815 -26
  195. package/node_modules/@undefineds.co/models/dist/pod-storage-descriptor.js +482 -132
  196. package/node_modules/@undefineds.co/models/dist/schema.d.ts +4 -0
  197. package/node_modules/@undefineds.co/models/dist/session/index.d.ts +1 -1
  198. package/node_modules/@undefineds.co/models/dist/settings/index.d.ts +1 -1
  199. package/node_modules/@undefineds.co/models/dist/sidecar/persistence-mapping.d.ts +5 -5
  200. package/node_modules/@undefineds.co/models/dist/thread.repository.d.ts +2 -2
  201. package/node_modules/@undefineds.co/models/dist/types/message-block.d.ts +1 -1
  202. package/node_modules/@undefineds.co/models/dist/vocab/_namespaces.d.ts +1 -1
  203. package/node_modules/@undefineds.co/models/dist/vocab/index.d.ts +10 -10
  204. package/node_modules/@undefineds.co/models/dist/vocab/sidecar.vocab.d.ts +1 -0
  205. package/node_modules/@undefineds.co/models/dist/vocab/sidecar.vocab.js +1 -0
  206. package/node_modules/@undefineds.co/models/package.json +4 -2
  207. package/node_modules/@undefineds.co/pod-collections/README.md +71 -0
  208. package/node_modules/@undefineds.co/pod-collections/dist/diff.d.ts +85 -0
  209. package/node_modules/@undefineds.co/pod-collections/dist/diff.js +166 -0
  210. package/node_modules/@undefineds.co/pod-collections/dist/feed.d.ts +17 -0
  211. package/node_modules/@undefineds.co/pod-collections/dist/feed.js +27 -0
  212. package/node_modules/@undefineds.co/pod-collections/dist/index.cjs +1042 -0
  213. package/node_modules/@undefineds.co/pod-collections/dist/index.d.ts +37 -0
  214. package/node_modules/@undefineds.co/pod-collections/dist/index.js +89 -0
  215. package/node_modules/@undefineds.co/pod-collections/dist/layout.d.ts +75 -0
  216. package/node_modules/@undefineds.co/pod-collections/dist/layout.js +173 -0
  217. package/node_modules/@undefineds.co/pod-collections/dist/mapping.d.ts +78 -0
  218. package/node_modules/@undefineds.co/pod-collections/dist/mapping.js +287 -0
  219. package/node_modules/@undefineds.co/pod-collections/dist/mutations.d.ts +83 -0
  220. package/node_modules/@undefineds.co/pod-collections/dist/mutations.js +273 -0
  221. package/node_modules/@undefineds.co/pod-collections/dist/react.d.ts +21 -0
  222. package/node_modules/@undefineds.co/pod-collections/dist/react.js +22 -0
  223. package/node_modules/@undefineds.co/pod-collections/dist/read.d.ts +65 -0
  224. package/node_modules/@undefineds.co/pod-collections/dist/read.js +31 -0
  225. package/node_modules/@undefineds.co/pod-collections/dist/sync.d.ts +66 -0
  226. package/node_modules/@undefineds.co/pod-collections/dist/sync.js +260 -0
  227. package/node_modules/@undefineds.co/pod-collections/dist/types.d.ts +155 -0
  228. package/node_modules/@undefineds.co/pod-collections/dist/types.js +9 -0
  229. package/node_modules/@undefineds.co/pod-collections/package.json +42 -0
  230. package/package.json +15 -7
  231. package/static/app/assets/main.css +1 -1
  232. package/static/auth-callback/assets/AiConfigContext-DnQs1ZpW.js +1 -0
  233. package/static/auth-callback/assets/AiConfigPage-DOtvlxH0.js +1 -0
  234. package/static/auth-callback/assets/DocumentProcessingPanel-BS2voQgL.js +1 -0
  235. package/static/auth-callback/assets/IndexLifecyclePanel-Cvt0cJtN.js +1 -0
  236. package/static/auth-callback/assets/{IndexSubjectPanel-BbUqop4b.js → IndexSubjectPanel-Dhf01GqF.js} +1 -1
  237. package/static/auth-callback/assets/ModelAssignmentsPanel-BJutWaAF.js +1 -0
  238. package/static/auth-callback/assets/ModelsPage-BRS0sje3.js +11 -0
  239. package/static/auth-callback/assets/{NetworkPage-Vp2PrMxs.js → NetworkPage-B3qBfANA.js} +2 -2
  240. package/static/auth-callback/assets/{PaneListHeader-DY7baSFl.js → PaneListHeader-QQeJGTp4.js} +1 -1
  241. package/static/auth-callback/assets/SearchIndexingPanel-MyNUSEwT.js +1 -0
  242. package/static/auth-callback/assets/{StatusSubjectPanel-ArIS-TxT.js → StatusSubjectPanel-DQkEVsFs.js} +1 -1
  243. package/static/{settings/assets/StatusWorkspace-CoMFRXoV.js → auth-callback/assets/StatusWorkspace-Dkv63Uwk.js} +1 -1
  244. package/static/auth-callback/assets/{SystemSettingsPage-BDvT1NUz.js → SystemSettingsPage-TIsUfX5d.js} +1 -1
  245. package/static/{dashboard/assets/SystemSettingsSubjectPanel-WaE8mTQi.js → auth-callback/assets/SystemSettingsSubjectPanel-BJWEt2-7.js} +5 -5
  246. package/static/auth-callback/assets/XpodAiConnectionsPodStore-BJiXm-PF.js +3 -0
  247. package/static/auth-callback/assets/{activity-D1Dsfa_0.js → activity-5hNeNKnL.js} +1 -1
  248. package/static/auth-callback/assets/ai-connections-CryuHGKW.js +1 -0
  249. package/static/auth-callback/assets/{auth-callback-Cmf4hXVZ.js → auth-callback-CAFZ3R0B.js} +6 -6
  250. package/static/auth-callback/assets/auth-callback-rk080mvG.css +1 -0
  251. package/static/auth-callback/assets/{badge-CZsHq3Ih.js → badge-CAIPI0CB.js} +1 -1
  252. package/static/auth-callback/assets/collection-runtime-D9s7h9YA.js +1 -0
  253. package/static/auth-callback/assets/dialog-BjA109aX.js +1 -0
  254. package/static/auth-callback/assets/{download-DU9ntlHw.js → download-B3Up5W2G.js} +1 -1
  255. package/static/auth-callback/assets/{external-link-BfzaaZDx.js → external-link-BTXgSPzB.js} +1 -1
  256. package/static/auth-callback/assets/index-BppXlxcp.js +41 -0
  257. package/static/auth-callback/assets/index-CirW4CJf.js +1 -0
  258. package/static/auth-callback/assets/index-DlLYWOhw.js +4 -0
  259. package/static/auth-callback/assets/{index-browser-BNFqqC74.js → index-browser-DtU38ni3.js} +1 -1
  260. package/static/auth-callback/assets/{key-round-BwP3DBWo.js → key-round-CQCOrZ7x.js} +1 -1
  261. package/static/auth-callback/assets/layout-DB3pMyTl.js +1 -0
  262. package/static/auth-callback/assets/pod-collections-host-KnnGFZHL.js +8 -0
  263. package/static/auth-callback/assets/{rotate-ccw-VMwnmyab.js → rotate-ccw-C1zM2KtS.js} +1 -1
  264. package/static/auth-callback/assets/{skeleton-CoD55rBT.js → skeleton-Cqp7_M9-.js} +1 -1
  265. package/static/auth-callback/assets/{waypoints-DRo_1Ca7.js → waypoints-Bj6gAUSQ.js} +1 -1
  266. package/static/auth-callback/assets/{workspace-layout--mcXl3My.js → workspace-layout-DK8mDzrY.js} +1 -1
  267. package/static/auth-callback/auth-callback.html +2 -2
  268. package/static/dashboard/assets/AiConfigContext-BsE7MqX8.js +1 -0
  269. package/static/dashboard/assets/AiConfigPage-DvM-lRXa.js +1 -0
  270. package/static/dashboard/assets/DocumentProcessingPanel-BPfdlfuf.js +1 -0
  271. package/static/dashboard/assets/IndexLifecyclePanel--m0AGxvh.js +1 -0
  272. package/static/dashboard/assets/{IndexSubjectPanel-B2cXwl8X.js → IndexSubjectPanel-Bb7m1k3d.js} +1 -1
  273. package/static/dashboard/assets/ModelAssignmentsPanel-BLi_zbMa.js +1 -0
  274. package/static/dashboard/assets/ModelsPage-CxdpgnfR.js +11 -0
  275. package/static/dashboard/assets/{NetworkPage-DV2V0qWO.js → NetworkPage-BAIPw-D_.js} +2 -2
  276. package/static/dashboard/assets/{PaneListHeader-D0c1yCFH.js → PaneListHeader-BBCMei0r.js} +1 -1
  277. package/static/dashboard/assets/SearchIndexingPanel-Ck4Y-xjR.js +1 -0
  278. package/static/dashboard/assets/{StatusSubjectPanel-oPSECj7i.js → StatusSubjectPanel-D1KWfAXE.js} +1 -1
  279. package/static/dashboard/assets/{StatusWorkspace-PdovQPKa.js → StatusWorkspace-JbA5gzXx.js} +1 -1
  280. package/static/dashboard/assets/{SystemSettingsPage-CtNDklkx.js → SystemSettingsPage-BbwQWsSR.js} +1 -1
  281. package/static/{settings/assets/SystemSettingsSubjectPanel-DExjDo_v.js → dashboard/assets/SystemSettingsSubjectPanel-N117nMt5.js} +5 -5
  282. package/static/dashboard/assets/XpodAiConnectionsPodStore-CdDOb4SV.js +18 -0
  283. package/static/dashboard/assets/{activity-B9ylSoyr.js → activity-DX1I96o_.js} +1 -1
  284. package/static/dashboard/assets/ai-connections-DwCRqOQa.js +1 -0
  285. package/static/dashboard/assets/{badge-BNbFbpIb.js → badge-k2vhtDqH.js} +1 -1
  286. package/static/dashboard/assets/collection-runtime-BFQ8teQJ.js +1 -0
  287. package/static/dashboard/assets/{dashboard-CD-FFVRD.js → dashboard-DWZ-fmCT.js} +4 -4
  288. package/static/dashboard/assets/dashboard-rk080mvG.css +1 -0
  289. package/static/dashboard/assets/dialog-1CCw-iHT.js +1 -0
  290. package/static/dashboard/assets/{download-nOP38LtW.js → download-C6-SNY1q.js} +1 -1
  291. package/static/dashboard/assets/{external-link-Bh2ewN9E.js → external-link-BgPcZ_j2.js} +1 -1
  292. package/static/dashboard/assets/index-6LcIUIYO.js +1 -0
  293. package/static/dashboard/assets/index-D7A8U6Pt.js +41 -0
  294. package/static/dashboard/assets/index-UD6QOE7H.js +4 -0
  295. package/static/dashboard/assets/{key-round-bGmguBLv.js → key-round-BOd7Jo0Q.js} +1 -1
  296. package/static/dashboard/assets/layout-DB3pMyTl.js +1 -0
  297. package/static/dashboard/assets/pod-collections-host-KnnGFZHL.js +8 -0
  298. package/static/dashboard/assets/{rotate-ccw-D2x__ODW.js → rotate-ccw-CCgJqsvx.js} +1 -1
  299. package/static/dashboard/assets/{skeleton-VOqft8WS.js → skeleton-Bc7abAn5.js} +1 -1
  300. package/static/dashboard/assets/{waypoints-fgRqm4oH.js → waypoints-s-opmJxx.js} +1 -1
  301. package/static/dashboard/assets/{workspace-layout-DV4dgeHX.js → workspace-layout-DqIQeTBV.js} +1 -1
  302. package/static/dashboard/dashboard.html +2 -2
  303. package/static/settings/assets/AiConfigContext-BB9KZySa.js +1 -0
  304. package/static/settings/assets/AiConfigPage-DGSXUkLs.js +1 -0
  305. package/static/settings/assets/DocumentProcessingPanel-Da2Re6Rg.js +1 -0
  306. package/static/settings/assets/IndexLifecyclePanel-BSZuIzgr.js +1 -0
  307. package/static/settings/assets/{IndexSubjectPanel-BgbI9c-a.js → IndexSubjectPanel-CFd0ay-r.js} +1 -1
  308. package/static/settings/assets/ModelAssignmentsPanel-CSAryvWJ.js +1 -0
  309. package/static/settings/assets/ModelsPage-BQwCwLHU.js +11 -0
  310. package/static/settings/assets/{NetworkPage-C8OA9Mtp.js → NetworkPage-BKymvsQc.js} +2 -2
  311. package/static/settings/assets/{PaneListHeader-COvlPJWj.js → PaneListHeader-Bjh3hRdQ.js} +1 -1
  312. package/static/settings/assets/SearchIndexingPanel-MjKL5QLU.js +1 -0
  313. package/static/settings/assets/{StatusSubjectPanel-BuLml7qn.js → StatusSubjectPanel-Bvl6K4mx.js} +1 -1
  314. package/static/settings/assets/StatusWorkspace-B0NzrP--.js +1 -0
  315. package/static/settings/assets/{SystemSettingsPage-DlzmaWV-.js → SystemSettingsPage-D2z6Z4FE.js} +1 -1
  316. package/static/{auth-callback/assets/SystemSettingsSubjectPanel-CDqRuC4t.js → settings/assets/SystemSettingsSubjectPanel-4sxma4X4.js} +5 -5
  317. package/static/settings/assets/XpodAiConnectionsPodStore-BkNQ8fZx.js +3 -0
  318. package/static/settings/assets/{activity-TLerXgMs.js → activity-CgJJGpjx.js} +1 -1
  319. package/static/settings/assets/ai-connections-CryuHGKW.js +1 -0
  320. package/static/settings/assets/{badge-Bcs8iWrH.js → badge-C4NypVyT.js} +1 -1
  321. package/static/settings/assets/collection-runtime-CTLCDxAi.js +1 -0
  322. package/static/settings/assets/dialog-Csq0vWUi.js +1 -0
  323. package/static/settings/assets/{download-Ct3QmHPt.js → download-DaLJJ-Wp.js} +1 -1
  324. package/static/settings/assets/{external-link-C8KiHbss.js → external-link--w_iW5Au.js} +1 -1
  325. package/static/settings/assets/index-B0mQa59b.js +4 -0
  326. package/static/settings/assets/index-BukHiu4y.js +1 -0
  327. package/static/settings/assets/index-COmVnZi2.js +41 -0
  328. package/static/settings/assets/{index-browser-CazX1pc2.js → index-browser-BGpvHwD0.js} +1 -1
  329. package/static/settings/assets/{key-round-B19YD1GA.js → key-round-Bk4YRjBu.js} +1 -1
  330. package/static/settings/assets/layout-DB3pMyTl.js +1 -0
  331. package/static/settings/assets/pod-collections-host-KnnGFZHL.js +8 -0
  332. package/static/settings/assets/{rotate-ccw-AQm2kMgU.js → rotate-ccw-TCabbqsr.js} +1 -1
  333. package/static/settings/assets/{settings-C-VBtt9r.js → settings-CJWpdbNC.js} +5 -5
  334. package/static/settings/assets/settings-rk080mvG.css +1 -0
  335. package/static/settings/assets/{skeleton-DyZ1OBYf.js → skeleton-JDHYVe7t.js} +1 -1
  336. package/static/settings/assets/{waypoints-CC3wq-SU.js → waypoints-DDvEl-MU.js} +1 -1
  337. package/static/settings/assets/{workspace-layout-BxXNKgf9.js → workspace-layout-DV5QU30U.js} +1 -1
  338. package/static/settings/settings.html +2 -2
  339. package/static/auth-callback/assets/AiConfigContext-DqWRSm7n.js +0 -1
  340. package/static/auth-callback/assets/AiConfigPage-C5YYyvzl.js +0 -1
  341. package/static/auth-callback/assets/DocumentProcessingPanel-DZCiacBz.js +0 -1
  342. package/static/auth-callback/assets/IndexLifecyclePanel-CCLeOljB.js +0 -1
  343. package/static/auth-callback/assets/ModelAssignmentsPanel-jUUTDKuh.js +0 -1
  344. package/static/auth-callback/assets/ModelsPage-B08zdtff.js +0 -10
  345. package/static/auth-callback/assets/SearchIndexingPanel-D4YRg1-8.js +0 -1
  346. package/static/auth-callback/assets/StatusWorkspace-Cp5LQ9Yx.js +0 -1
  347. package/static/auth-callback/assets/ai-connections-CHNTKemd.js +0 -1
  348. package/static/auth-callback/assets/auth-callback-DWMMiFjh.css +0 -1
  349. package/static/auth-callback/assets/index-DSNnLUbU.js +0 -4
  350. package/static/auth-callback/assets/index-FHhHoKgp.js +0 -41
  351. package/static/dashboard/assets/AiConfigContext-BpPDKSzI.js +0 -1
  352. package/static/dashboard/assets/AiConfigPage-wsqEZDIX.js +0 -1
  353. package/static/dashboard/assets/DocumentProcessingPanel-CDSy3juc.js +0 -1
  354. package/static/dashboard/assets/IndexLifecyclePanel-CVd6vQCX.js +0 -1
  355. package/static/dashboard/assets/ModelAssignmentsPanel-BeBZhnQd.js +0 -1
  356. package/static/dashboard/assets/ModelsPage-B6McSCJw.js +0 -25
  357. package/static/dashboard/assets/SearchIndexingPanel-CGs0OZ12.js +0 -1
  358. package/static/dashboard/assets/ai-connections-CHNTKemd.js +0 -1
  359. package/static/dashboard/assets/dashboard-DWMMiFjh.css +0 -1
  360. package/static/dashboard/assets/index-PgS-Bt7X.js +0 -41
  361. package/static/dashboard/assets/index-QN2QgEwr.js +0 -4
  362. package/static/settings/assets/AiConfigContext-zKQYdfai.js +0 -1
  363. package/static/settings/assets/AiConfigPage-Bs1EE3Ei.js +0 -1
  364. package/static/settings/assets/DocumentProcessingPanel-CSFfdOXS.js +0 -1
  365. package/static/settings/assets/IndexLifecyclePanel-BR9Su49B.js +0 -1
  366. package/static/settings/assets/ModelAssignmentsPanel-C0BE7nbk.js +0 -1
  367. package/static/settings/assets/ModelsPage-CU-YQ8_7.js +0 -10
  368. package/static/settings/assets/SearchIndexingPanel-Bdd46mIV.js +0 -1
  369. package/static/settings/assets/ai-connections-CHNTKemd.js +0 -1
  370. package/static/settings/assets/index-1Q2cRjZd.js +0 -41
  371. package/static/settings/assets/index-DBO_Vq0P.js +0 -4
  372. package/static/settings/assets/settings-DWMMiFjh.css +0 -1
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Button, TooltipProvider } from '@undefineds.co/shared-ui';
3
3
  import { ExternalLink, Plus, Settings2 } from 'lucide-react';
4
4
  import { offeringTitle } from './offering-label.js';
5
- import { authorizationMethodsForOffering, isApiKeyMethod, isLocalMethod, isOAuthMethod, isOAuthMode, isPendingAttempt, } from './authorization-methods.js';
5
+ import { authorizationMethodsForOffering, connectEntryRank, isApiKeyMethod, isBrowserConnectMethod, isLocalMethod, isOAuthMethod, isOAuthMode, isPendingAttempt, } from './authorization-methods.js';
6
6
  import { credentialDisplayLabel, maskAccountLabel } from './credential-labels.js';
7
7
  import { AiQuotaCard } from './AiQuotaCard.js';
8
8
  import { AiAuthorizationActions } from './AiAuthorizationActions.js';
@@ -25,7 +25,56 @@ export function AiCredentialPoolSection({ definition, product, status, accountLa
25
25
  }];
26
26
  const credentials = product?.credentials ?? [];
27
27
  const offerings = product?.offerings.length ? product.offerings : fallbackOfferings;
28
- const apiOfferings = offerings.filter((offering) => authorizationMethodsForOffering(offering).some((method) => method.lifecycle === 'active' && isApiKeyMethod(method)));
28
+ /**
29
+ * The connect entries ARE the offerings' authorization methods, rendered as
30
+ * ONE ordered list.
31
+ *
32
+ * Two of them are actions of the page rather than of a single offering: the
33
+ * key entry (the dialog already lists every offering that accepts a key) and
34
+ * the console entry (every offering of a provider opens the same console, so
35
+ * four offerings declaring it stay one button). Everything else keeps its
36
+ * offering, because the click has to name which authorization it starts.
37
+ *
38
+ * The order comes from the method kind alone (`connectEntryRank`), so the
39
+ * toolbar reads the same on every provider - browser sign-in, device code,
40
+ * local login state, key - and no provider can reorder it. The console entry
41
+ * also yields to an entry that already carries its label: an offering's own
42
+ * browser login names that action, and two buttons with one name would be two
43
+ * claims about the same click.
44
+ */
45
+ const offeringMethods = offerings.map((offering) => ({
46
+ offering,
47
+ methods: authorizationMethodsForOffering(offering),
48
+ }));
49
+ const apiOfferings = offeringMethods
50
+ .filter((entry) => entry.methods.some((method) => method.lifecycle === 'active' && isApiKeyMethod(method)))
51
+ .map((entry) => entry.offering);
52
+ const apiKeyMethod = offeringMethods
53
+ .flatMap((entry) => entry.methods)
54
+ .find((method) => isApiKeyMethod(method) && !isBrowserConnectMethod(method) && method.lifecycle === 'active' && method.label)
55
+ ?? offeringMethods.flatMap((entry) => entry.methods)
56
+ .find((method) => isApiKeyMethod(method) && !isBrowserConnectMethod(method) && method.label);
57
+ const authorizationEntries = offeringMethods.flatMap((entry) => entry.methods
58
+ .filter((method) => method.label && (isOAuthMethod(method) || isLocalMethod(method)))
59
+ .map((method) => ({ offering: entry.offering, method })));
60
+ const consoleMethod = offeringMethods
61
+ .flatMap((entry) => entry.methods)
62
+ .find((method) => isBrowserConnectMethod(method) && method.lifecycle === 'active' && method.label)
63
+ ?? offeringMethods.flatMap((entry) => entry.methods)
64
+ .find((method) => isBrowserConnectMethod(method) && method.label);
65
+ const consoleEntry = consoleMethod?.label
66
+ && !authorizationEntries.some((entry) => entry.method.label === consoleMethod.label)
67
+ ? consoleMethod
68
+ : undefined;
69
+ const connectEntries = [
70
+ ...authorizationEntries.map((entry) => ({
71
+ key: `${entry.offering.id}:${entry.method.id}`,
72
+ rank: connectEntryRank(entry.method),
73
+ authorization: entry,
74
+ })),
75
+ ...(consoleEntry ? [{ key: 'console-login', rank: connectEntryRank(consoleEntry), consoleMethod: consoleEntry }] : []),
76
+ ...(apiKeyMethod ? [{ key: 'api-key', rank: connectEntryRank(apiKeyMethod), apiKeyMethod }] : []),
77
+ ].sort((left, right) => left.rank - right.rank);
29
78
  const authorizationPending = isPendingAttempt(attempt) && isOAuthMode(attempt?.mode);
30
79
  const orderedCredentials = [...credentials].sort((a, b) => a.priority - b.priority);
31
80
  const quotaBusy = credentials.some((credential) => quotas[credential.id]?.busy);
@@ -36,13 +85,18 @@ export function AiCredentialPoolSection({ definition, product, status, accountLa
36
85
  onBeginBrowser,
37
86
  onCreateLocalCredential,
38
87
  });
39
- 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) => {
40
- const methods = authorizationMethodsForOffering(offering).filter((method) => method.lifecycle === 'active' && (isOAuthMethod(method) || isLocalMethod(method)));
41
- if (!methods.length)
42
- return null;
43
- return _jsx("div", { role: "group", "aria-label": `${offeringTitle(offering)}快捷接入`, children: _jsx(AiAuthorizationActions, { methods: methods, offering: offering, hasCredentials: credentials.some((credential) => credential.offeringId === offering.id), busy: busy || dialog.saving, disabled: disabled || authorizationPending, onBeginOffering: onBeginOffering ? dialog.beginAuthorization : undefined, onCreateLocalCredential: onCreateLocalCredential ? dialog.beginLocal : undefined }) }, offering.id);
44
- }), apiOfferings.length > 0 && !offerings.some((offering) => authorizationMethodsForOffering(offering).some((method) => method.lifecycle === 'active' && (isOAuthMethod(method) || isLocalMethod(method))))
45
- && definition.browserMode === 'browserAssistedApiKey' ? (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", "aria-label": `${definition.name} 登录`, disabled: busy || dialog.saving || disabled || authorizationPending, onClick: dialog.beginBrowser, 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 || dialog.saving || disabled || authorizationPending || (!product && status === 'pending' && !attempt), onClick: dialog.beginApiKey, 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(AiSortableCredentialList, { credentials: orderedCredentials, disabled: busy || disabled, onMove: onReorderCredentials ? (from, to) => {
88
+ 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, { "aria-hidden": "true", className: "h-4 w-4 text-primary" }), "\u5F53\u524D\u8FDE\u63A5"] }), _jsxs("div", { "data-testid": "provider-connect-actions", className: "flex flex-wrap items-center justify-end gap-2", children: [credentials.length > 1 ? (_jsx(Button, { variant: "ghost", size: "sm", "aria-label": `刷新全部 ${definition.name}额度`, disabled: disabled || quotaBusy || !credentials.some((c) => c.enabled) || !onRefreshQuota, onClick: () => offerings.forEach((offering) => onRefreshQuota?.(offering)), children: quotaBusy ? '查询中' : '刷新全部额度' })) : null, connectEntries.map((item) => {
89
+ if ('authorization' in item) {
90
+ return (_jsx("fieldset", { className: "m-0 min-w-0 border-0 p-0", "aria-label": `${offeringTitle(item.authorization.offering)}快捷接入`, children: _jsx(AiAuthorizationActions, { methods: [item.authorization.method], offering: item.authorization.offering, busy: busy || dialog.saving, disabled: disabled || authorizationPending, onBeginOffering: onBeginOffering ? dialog.beginAuthorization : undefined, onCreateLocalCredential: onCreateLocalCredential ? dialog.beginLocal : undefined }) }, item.key));
91
+ }
92
+ if ('consoleMethod' in item) {
93
+ return (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", title: item.consoleMethod.lifecycle === 'unavailable' ? item.consoleMethod.reason : undefined, disabled: busy || dialog.saving || disabled || authorizationPending
94
+ || item.consoleMethod.lifecycle === 'unavailable', onClick: dialog.beginBrowser, children: [_jsx(ExternalLink, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), item.consoleMethod.label] }, item.key));
95
+ }
96
+ return (_jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", "aria-label": "\u65B0\u5EFA API Key \u8FDE\u63A5", title: item.apiKeyMethod.lifecycle === 'unavailable' ? item.apiKeyMethod.reason : undefined, disabled: busy || dialog.saving || disabled || authorizationPending
97
+ || item.apiKeyMethod.lifecycle === 'unavailable'
98
+ || (!product && status === 'pending' && !attempt), onClick: dialog.beginApiKey, children: [_jsx(Plus, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), item.apiKeyMethod.label] }, item.key));
99
+ })] })] }), _jsxs("div", { "aria-label": "\u51ED\u636E\u5217\u8868", className: "space-y-2", children: [_jsx(AiSortableCredentialList, { credentials: orderedCredentials, disabled: busy || disabled, onMove: onReorderCredentials ? (from, to) => {
46
100
  const offering = offerings.find((item) => item.id === orderedCredentials[from]?.offeringId);
47
101
  if (offering)
48
102
  onReorderCredentials(offering, orderedCredentials, from, to);
@@ -54,7 +108,13 @@ export function AiCredentialPoolSection({ definition, product, status, accountLa
54
108
  : credential.label ? maskAccountLabel(credential.label) : credentialDisplayLabel(credential);
55
109
  const state = quotas[credential.id];
56
110
  const quotaState = state?.credentialId === credential.id ? state : undefined;
57
- return _jsx(AiCredentialRow, { 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' ? () => dialog.beginEdit(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) }) });
58
- } }), !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(AiConnectDialog, { controller: dialog, definition: definition, offerings: offerings, apiOfferings: apiOfferings, credentials: credentials, status: status, attempt: attempt, attemptOfferingId: attemptOfferingId, apiKey: apiKey, baseUrl: baseUrl, busy: busy, disabled: disabled, error: error, onApiKeyChange: onApiKeyChange, onBaseUrlChange: onBaseUrlChange, onBeginApiKey: onBeginApiKey, onBeginBrowser: onBeginBrowser, onSaveApiKey: onSaveApiKey, onDisconnect: onDisconnect, onUpdateCredential: onUpdateCredential, onCreateApiKeyCredential: onCreateApiKeyCredential, onBeginOffering: onBeginOffering, onCancelConnect: onCancelConnect, onDismissError: onDismissError }), _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(AiOfferingDetails, { offering: offering }, offering.id)) })] }), dialog.error && !dialog.open && dialog.error !== error?.message ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: dialog.error }) : null, error && (error.offeringId ? !dialog.open : !suppressError)
111
+ return _jsx(AiCredentialRow, { credential: credential, label: label, dragHandle: handle, kindLabel: credential.authMode === 'apiKey' ? 'API Key' : offeringTitle(offering), busy: busy, disabled: disabled,
112
+ // A row action is a click, not a call site: the failure is already
113
+ // reported through `setProviderError`, so it must not also surface
114
+ // as an unhandled rejection from the button.
115
+ onToggle: onUpdateCredential
116
+ ? (current, patch) => { void onUpdateCredential(current, patch).catch(() => undefined); }
117
+ : undefined, onTest: onTestCredential, onEdit: credential.authMode === 'apiKey' ? () => dialog.beginEdit(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) }) });
118
+ } }), !credentials.length ? (_jsx("p", { className: "flex items-center gap-2 py-6 text-sm 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(AiConnectDialog, { controller: dialog, definition: definition, offerings: offerings, apiOfferings: apiOfferings, credentials: credentials, status: status, attempt: attempt, attemptOfferingId: attemptOfferingId, apiKey: apiKey, baseUrl: baseUrl, busy: busy, disabled: disabled, error: error, onApiKeyChange: onApiKeyChange, onBaseUrlChange: onBaseUrlChange, onBeginApiKey: onBeginApiKey, onBeginBrowser: onBeginBrowser, onSaveApiKey: onSaveApiKey, onDisconnect: onDisconnect, onUpdateCredential: onUpdateCredential, onCreateApiKeyCredential: onCreateApiKeyCredential, onBeginOffering: onBeginOffering, onCancelConnect: onCancelConnect, onDismissError: onDismissError }), _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(AiOfferingDetails, { offering: offering }, offering.id)) })] }), dialog.error && !dialog.open && dialog.error !== error?.message ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: dialog.error }) : null, error && (error.offeringId ? !dialog.open : !suppressError)
59
119
  ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error.message }) : null] }) }));
60
120
  }
@@ -1,17 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button, Tooltip, TooltipContent, TooltipTrigger, cn } from '@undefineds.co/shared-ui';
2
+ import { Tooltip, TooltipContent, TooltipTrigger, cn } from '@undefineds.co/shared-ui';
3
+ import { AiRowAction } from './AiRowAction.js';
3
4
  import { Pause, Pencil, Play, PlugZap, Trash2 } from 'lucide-react';
4
5
  import { credentialDisplayLabel, healthLabel, healthTone } from './credential-labels.js';
5
6
  export function AiCredentialRow({ credential, label, busy, disabled, dragHandle, kindLabel, quota, onToggle, onTest, onEdit, onDelete, deleteAriaLabel, }) {
6
7
  const actionDisabled = disabled || busy;
7
8
  const tone = healthTone(credential.health);
8
9
  const stateLabel = `${credential.enabled ? '已启用' : '已停用'} · ${healthLabel(credential.health)}`;
9
- 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)
10
+ return (_jsxs("div", { "data-credential-state": credential.enabled ? 'enabled' : 'disabled', className: cn(
11
+ // The list owns the box and the dividers, so a row carries only its own
12
+ // tint: `rounded-[inherit]` keeps the first and last tint inside the
13
+ // container's corners.
14
+ 'flex flex-wrap items-center gap-x-3 gap-y-2 rounded-[inherit] px-4 py-3 transition-colors', credential.enabled ? tone.row : '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)
10
15
  ? _jsx("p", { className: "mt-1 truncate font-mono text-xs text-muted-foreground", children: credential.maskedHint })
11
- : 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
16
+ : 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(AiRowAction, { label: `测试连接 ${label}`, disabled: actionDisabled, onClick: () => onTest(credential), children: _jsx(PlugZap, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })) : null, onEdit ? (_jsx(AiRowAction, { label: `编辑 ${label}`, disabled: actionDisabled, onClick: onEdit, children: _jsx(Pencil, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })) : null, onToggle ? (_jsx(AiRowAction, { label: `${credential.enabled ? '停用' : '启用'} ${label}`, disabled: actionDisabled, onClick: () => onToggle(credential, { enabled: !credential.enabled }), children: credential.enabled
12
17
  ? _jsx(Pause, { "aria-hidden": "true", className: "h-3.5 w-3.5" })
13
- : _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] })] }));
14
- }
15
- function RowAction({ label, disabled, onClick, children, }) {
16
- return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", "aria-label": label, disabled: disabled, onClick: onClick, children: children }) }), _jsx(TooltipContent, { className: "text-xs", children: label })] }));
18
+ : _jsx(Play, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })) : null, onDelete ? (_jsx(AiRowAction, { label: deleteAriaLabel ?? `删除 ${label}`, disabled: actionDisabled, onClick: onDelete, children: _jsx(Trash2, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) })) : null] })] }));
17
19
  }
@@ -0,0 +1,23 @@
1
+ import { type AiEndpointEntry } from './offering-endpoints';
2
+ /**
3
+ * Protocol/endpoint list shared by a provider offering's details and Xpod's own
4
+ * 接入信息: in both places a list of `protocol → base URL` pairs is what the user
5
+ * reads, so the rows (and their copy affordance) are one implementation.
6
+ *
7
+ * `copy` is opt-in because the provider pages read their upstream endpoints
8
+ * without offering them for pasting, while Xpod's own endpoints exist to be
9
+ * copied into a client.
10
+ */
11
+ export declare function AiEndpointList({ endpoints, copy, display }: {
12
+ endpoints: readonly AiEndpointEntry[];
13
+ copy?: boolean;
14
+ /**
15
+ * `compact` shortens the address, `full` shows it in full.
16
+ *
17
+ * `protocol` drops the address and lists the accept-compatible protocols as
18
+ * chips. Xpod publishes three protocols from one gateway, so two of them even
19
+ * share an address: repeating it tells the reader less than naming the
20
+ * protocol they should pick, and the address stays one click away.
21
+ */
22
+ display?: 'compact' | 'full' | 'protocol';
23
+ }): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { compatibleProtocolLabel, endpointDisplayValue, endpointProtocolLabel } from './offering-endpoints.js';
3
+ import { AiCopyButton } from './AiCopyButton.js';
4
+ /**
5
+ * Protocol/endpoint list shared by a provider offering's details and Xpod's own
6
+ * 接入信息: in both places a list of `protocol → base URL` pairs is what the user
7
+ * reads, so the rows (and their copy affordance) are one implementation.
8
+ *
9
+ * `copy` is opt-in because the provider pages read their upstream endpoints
10
+ * without offering them for pasting, while Xpod's own endpoints exist to be
11
+ * copied into a client.
12
+ */
13
+ export function AiEndpointList({ endpoints, copy = false, display = 'compact' }) {
14
+ if (display === 'protocol') {
15
+ return (_jsx("ul", { className: "flex flex-wrap gap-2", children: endpoints.map((endpoint) => {
16
+ const label = compatibleProtocolLabel(endpoint.protocol);
17
+ return (_jsx("li", { children: copy ? (_jsx(AiCopyButton, { value: endpoint.baseUrl, label: `${label} 地址`, text: label, variant: "outline", className: "h-7 gap-1.5 px-2 text-[11px] font-normal", iconClassName: "h-3 w-3" })) : (_jsx("span", { className: "inline-flex h-7 items-center rounded-md border border-border px-2 text-[11px] text-muted-foreground", children: label })) }, endpoint.protocol));
18
+ }) }));
19
+ }
20
+ return (_jsx("dl", { className: "space-y-1 text-[11px] text-muted-foreground", children: endpoints.map((endpoint) => {
21
+ const label = endpointProtocolLabel(endpoint.protocol);
22
+ return (_jsxs("div", { className: "flex min-w-0 items-baseline gap-2", children: [_jsx("dt", { className: "shrink-0 text-foreground/70", children: label }), _jsx("dd", { className: "min-w-0 truncate font-mono", title: endpoint.baseUrl, children: display === 'full' ? endpoint.baseUrl : endpointDisplayValue(endpoint.baseUrl) }), copy ? (_jsx("dd", { className: "shrink-0", children: _jsx(AiCopyButton, { value: endpoint.baseUrl, label: `${label} 地址`, variant: "ghost", className: "h-6 gap-1 px-1.5 text-[11px]", iconClassName: "h-3 w-3" }) })) : null] }, `${endpoint.protocol}:${endpoint.baseUrl}`));
23
+ }) }));
24
+ }
@@ -1,13 +1,22 @@
1
1
  import type { AiConnectionsClient, AiGatewayModel } from './ai-connections-client';
2
2
  import { type AiClientConfigurationBridge } from './AiClientConfigurationSection';
3
+ import { type GatewayModelSelection } from './AiGatewayModelsSection';
3
4
  /**
4
5
  * The Xpod provider page. From a coding client's point of view Xpod is just
5
- * another provider, so this surface shares the provider pages' chrome: a
6
- * provider-style header, a credential-pool-style section, and rows that only
7
- * record what the issued credential means.
6
+ * another provider, so this surface shares the provider pages' components: the
7
+ * same header (`AiProviderHeader`), the same model list (`AiGatewayModelsSection`
8
+ * over `AiModelCatalog`), and the same endpoint list (`AiEndpointList`) that the
9
+ * provider pages use for their offerings' protocols.
8
10
  */
9
- export declare function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewayModels, }: {
11
+ export declare function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewayModels, modelSelection, liveRevision, }: {
10
12
  client: AiConnectionsClient;
11
13
  clientConfigurationBridge?: AiClientConfigurationBridge;
12
14
  gatewayModels?: AiGatewayModel[];
15
+ /** Publishes or withdraws models on the models list endpoint, from this page. */
16
+ modelSelection?: GatewayModelSelection;
17
+ /**
18
+ * Advances when the credentials table changed outside this page (another
19
+ * client, a script); the Key list lives in that table and re-reads with it.
20
+ */
21
+ liveRevision?: number;
13
22
  }): import("react").JSX.Element;
@@ -1,13 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef, useState } from 'react';
3
- import { Avatar, AvatarFallback, Badge, Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, controlFocusClass, dismissToast, toast, } from '@undefineds.co/shared-ui';
4
- import { Check, Copy, KeyRound, Loader2, Plus } from 'lucide-react';
3
+ import { Badge, Button, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, TooltipProvider, controlFocusClass, dismissToast, toast, } from '@undefineds.co/shared-ui';
4
+ import { Check, Loader2, Plus, Settings2 } from 'lucide-react';
5
5
  import { AI_CONNECTIONS_CLIENTS, AI_CLIENT_LABELS, manualConfigurationText, } from './AiClientConfigurationSection.js';
6
+ import { AiCopyButton } from './AiCopyButton.js';
7
+ import { AiEndpointList } from './AiEndpointList.js';
8
+ import { AiProviderHeader } from './AiProviderHeader.js';
9
+ import { xpodProtocolEndpoints } from './gateway-endpoints.js';
10
+ import { XPOD_AVATAR } from './provider-visuals.js';
6
11
  import { AiGatewayKeyRow } from './AiGatewayKeyRow.js';
12
+ import { AiGatewayModelsSection } from './AiGatewayModelsSection.js';
7
13
  const DEFAULT_KEY_NAME = '我的 API Key';
8
14
  const CREATED_NOTIFICATION = 'API Key 已创建,请复制或应用到客户端。';
9
15
  const UNPERSISTED_COPY_MESSAGE = '这个 API Key 只在创建时可见:请销毁它,然后重新创建并立即复制或应用。';
10
16
  const UNPERSISTED_APPLY_MESSAGE = '这个 API Key 只在创建时可见:请销毁它,然后重新创建并直接应用到客户端。';
17
+ /** Header tooltip: what Xpod is, then how the Key itself is protected. */
18
+ const XPOD_DESCRIPTION = '把已接入的 Provider 模型统一发布给编码客户端,兼容 OpenAI 与 Anthropic 协议。';
19
+ const XPOD_CREDENTIAL_NOTE = 'API Key 保存在当前 Pod,由 Pod 权限保护;Xpod 不保存明文。';
11
20
  const SELECT_CLASS = [
12
21
  'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm transition-[border-color]',
13
22
  'disabled:cursor-not-allowed disabled:opacity-50',
@@ -15,11 +24,12 @@ const SELECT_CLASS = [
15
24
  ].join(' ');
16
25
  /**
17
26
  * The Xpod provider page. From a coding client's point of view Xpod is just
18
- * another provider, so this surface shares the provider pages' chrome: a
19
- * provider-style header, a credential-pool-style section, and rows that only
20
- * record what the issued credential means.
27
+ * another provider, so this surface shares the provider pages' components: the
28
+ * same header (`AiProviderHeader`), the same model list (`AiGatewayModelsSection`
29
+ * over `AiModelCatalog`), and the same endpoint list (`AiEndpointList`) that the
30
+ * provider pages use for their offerings' protocols.
21
31
  */
22
- export function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewayModels, }) {
32
+ export function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewayModels, modelSelection, liveRevision = 0, }) {
23
33
  const [keys, setKeys] = useState([]);
24
34
  const [loading, setLoading] = useState(true);
25
35
  const [showCreate, setShowCreate] = useState(false);
@@ -32,13 +42,11 @@ export function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewa
32
42
  const [issuedClient, setIssuedClient] = useState();
33
43
  const [appliedClient, setAppliedClient] = useState();
34
44
  const [applying, setApplying] = useState(false);
35
- const [copied, setCopied] = useState();
36
45
  const [busyKeyId, setBusyKeyId] = useState();
37
46
  const [error, setError] = useState();
38
47
  const plaintexts = useRef(new Map());
39
48
  const operation = useRef(false);
40
49
  const notification = useRef(undefined);
41
- const copyTimer = useRef(undefined);
42
50
  const notify = useCallback((options) => {
43
51
  if (notification.current)
44
52
  dismissToast(notification.current);
@@ -69,16 +77,30 @@ export function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewa
69
77
  });
70
78
  return () => {
71
79
  active = false;
72
- clearTimeout(copyTimer.current);
73
80
  if (notification.current)
74
81
  dismissToast(notification.current);
75
82
  };
76
83
  }, [client, notifyError]);
84
+ // A change made elsewhere only needs new rows: it must not drop the wrapper
85
+ // copies this session is still showing, nor flash the loading state.
86
+ useEffect(() => {
87
+ if (liveRevision === 0)
88
+ return;
89
+ let active = true;
90
+ void client.listGatewayKeys()
91
+ .then((records) => {
92
+ if (active)
93
+ setKeys(records.filter((record) => !record.revokedAt));
94
+ })
95
+ .catch(() => undefined);
96
+ return () => {
97
+ active = false;
98
+ };
99
+ }, [client, liveRevision]);
77
100
  const openCreate = () => {
78
101
  setError(undefined);
79
102
  setName(DEFAULT_KEY_NAME);
80
103
  setPurpose('');
81
- setCopied(undefined);
82
104
  // A key issued earlier in this session keeps its wrapper in the cache, so
83
105
  // the flow resumes there instead of pretending the key was never created.
84
106
  setShowCreate(true);
@@ -87,7 +109,6 @@ export function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewa
87
109
  setIssued(undefined);
88
110
  setIssuedClient(undefined);
89
111
  setAppliedClient(undefined);
90
- setCopied(undefined);
91
112
  setError(undefined);
92
113
  setName(DEFAULT_KEY_NAME);
93
114
  setPurpose('');
@@ -116,7 +137,6 @@ export function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewa
116
137
  setIssued(created.record);
117
138
  setIssuedClient(purpose);
118
139
  setAppliedClient(undefined);
119
- setCopied(undefined);
120
140
  notify({ variant: 'success', description: CREATED_NOTIFICATION });
121
141
  }
122
142
  catch (cause) {
@@ -132,24 +152,6 @@ export function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewa
132
152
  throw new Error(message);
133
153
  return plaintext;
134
154
  };
135
- const copy = async (record, target) => {
136
- setError(undefined);
137
- try {
138
- const plaintext = sessionPlaintext(record, UNPERSISTED_COPY_MESSAGE);
139
- if (!navigator.clipboard?.writeText) {
140
- throw new Error('当前浏览器无法访问剪贴板,请允许剪贴板访问后重试。');
141
- }
142
- await navigator.clipboard.writeText(target === 'key'
143
- ? plaintext
144
- : manualConfigurationText(target, client.apiBase, plaintext));
145
- setCopied(target);
146
- clearTimeout(copyTimer.current);
147
- copyTimer.current = setTimeout(() => setCopied(undefined), 2000);
148
- }
149
- catch (cause) {
150
- setError(errorMessage(cause));
151
- }
152
- };
153
155
  const apply = async () => {
154
156
  if (!issued || !issuedClient || !clientConfigurationBridge)
155
157
  return;
@@ -212,7 +214,6 @@ export function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewa
212
214
  setIssued(undefined);
213
215
  setIssuedClient(undefined);
214
216
  setAppliedClient(undefined);
215
- setCopied(undefined);
216
217
  }
217
218
  }
218
219
  catch (cause) {
@@ -223,13 +224,9 @@ export function AiGatewayKeysSection({ client, clientConfigurationBridge, gatewa
223
224
  setBusyKeyId(undefined);
224
225
  }
225
226
  };
226
- return (_jsxs("section", { className: "space-y-8 [container-type:inline-size]", "aria-label": "API Keys", children: [_jsxs("header", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Avatar, { className: "h-9 w-9 shrink-0 rounded-lg border border-border/50 bg-muted/50 shadow-sm", children: _jsx(AvatarFallback, { className: "rounded-lg bg-transparent text-sm font-bold uppercase text-muted-foreground", children: "XP" }) }), _jsxs("div", { className: "flex flex-col justify-center gap-1", children: [_jsx("h2", { className: "text-base font-semibold leading-none tracking-tight text-foreground", children: "Xpod" }), _jsx("p", { className: "max-w-3xl text-xs leading-snug text-muted-foreground", children: "API Key \u7528\u4E8E\u8BA9\u5BA2\u6237\u7AEF\u628A Xpod \u5F53\u4F5C Provider \u63A5\u5165\uFF1BXpod \u4E0D\u4FDD\u5B58 Key \u660E\u6587\u3002" })] })] }), _jsx(Badge, { variant: keys.length > 0 ? 'default' : 'secondary', children: keys.length > 0 ? `已签发 ${keys.length} 个` : '未配置' })] }), _jsxs("section", { className: "space-y-3", "aria-label": "\u5DF2\u7B7E\u53D1 API Key", 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(KeyRound, { "aria-hidden": "true", className: "h-4 w-4 text-primary" }), "\u5DF2\u7B7E\u53D1 API Key"] }), _jsxs(Button, { size: "sm", "aria-label": "\u65B0\u5EFA API Key", disabled: creating || loading, onClick: openCreate, children: [_jsx(Plus, { "aria-hidden": "true", className: "mr-2 h-4 w-4" }), "\u65B0\u5EFA API Key"] })] }), loading ? (_jsxs("div", { className: "flex items-center gap-2 py-6 text-sm text-muted-foreground", children: [_jsx(Loader2, { "aria-hidden": "true", className: "h-4 w-4 animate-spin" }), "\u6B63\u5728\u8BFB\u53D6 API Key"] })) : keys.length === 0 ? (_jsx("p", { className: "py-8 text-center text-sm text-muted-foreground", children: "\u5C1A\u672A\u7B7E\u53D1 API Key\u3002" })) : (_jsx("ul", { "aria-label": "API Key \u5217\u8868", className: "rounded-xl border border-border/70", children: keys.map((record) => (_jsx(AiGatewayKeyRow, { record: record, busy: Boolean(busyKeyId), onDestroy: () => void destroy(record) }, record.id))) }))] }), _jsx(Dialog, { open: showCreate, onOpenChange: (open) => { if (!open && (creating || applying))
227
- return; setShowCreate(open); }, children: _jsxs(DialogContent, { className: "sm:max-w-md", "aria-describedby": undefined, children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: issued ? 'API Key 已签发' : '新建 API Key' }) }), issued && issuedClient ? (_jsxs("div", { className: "space-y-5", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs("p", { className: "text-sm", children: ["\u5DF2\u7B7E\u53D1\u300C", issued.name || '未命名 API Key', "\u300D\uFF0C\u7528\u9014\uFF1A", AI_CLIENT_LABELS[issuedClient], "\u3002"] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Xpod \u4E0D\u4FDD\u5B58 Key \u660E\u6587\uFF0C\u8BF7\u7ACB\u5373\u590D\u5236\u6216\u76F4\u63A5\u5E94\u7528\u5230\u5BA2\u6237\u7AEF\uFF1B\u79BB\u5F00\u672C\u6B21\u4F1A\u8BDD\u540E\u65E0\u6CD5\u518D\u6B21\u83B7\u53D6\u3002" })] }), issued.maskedHint ? (_jsx("code", { className: "block truncate rounded-md border border-border/60 bg-muted/30 px-3 py-2 font-mono text-xs text-muted-foreground", children: issued.maskedHint })) : null, !clientConfigurationBridge ? (_jsx("p", { className: "text-xs text-muted-foreground", children: "\u5F53\u524D Web \u73AF\u5883\u65E0\u6CD5\u81EA\u52A8\u5199\u5165\u5BA2\u6237\u7AEF\u914D\u7F6E\uFF0C\u8BF7\u590D\u5236\u540E\u624B\u52A8\u7C98\u8D34\uFF1B\u81EA\u52A8\u5E94\u7528\u9700\u8981\u672C\u673A\u8FDE\u63A5\u3002" })) : null, error ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Button, { type: "button", variant: "outline", size: "sm", "aria-label": "\u590D\u5236 API Key", disabled: applying, onClick: () => void copy(issued, 'key'), children: [copied === 'key'
228
- ? _jsx(Check, { "aria-hidden": "true", className: "mr-1.5 h-3.5 w-3.5 text-emerald-600" })
229
- : _jsx(Copy, { "aria-hidden": "true", className: "mr-1.5 h-3.5 w-3.5" }), copied === 'key' ? '已复制' : '复制 API Key'] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: applying, "aria-label": `复制 ${AI_CLIENT_LABELS[issuedClient]} 配置`, onClick: () => void copy(issued, issuedClient), children: [copied === issuedClient
230
- ? _jsx(Check, { "aria-hidden": "true", className: "mr-1.5 h-3.5 w-3.5 text-emerald-600" })
231
- : _jsx(Copy, { "aria-hidden": "true", className: "mr-1.5 h-3.5 w-3.5" }), copied === issuedClient ? '已复制' : `复制 ${AI_CLIENT_LABELS[issuedClient]} 配置`] }), clientConfigurationBridge ? (appliedClient ? (_jsxs("span", { role: "status", className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Check, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "\u5DF2\u5E94\u7528\u5230 ", AI_CLIENT_LABELS[appliedClient]] })) : (_jsxs(Button, { type: "button", size: "sm", disabled: applying, "aria-label": `应用到 ${AI_CLIENT_LABELS[issuedClient]}`, onClick: () => void apply(), children: [applying ? _jsx(Loader2, { "aria-hidden": "true", className: "mr-2 h-4 w-4 animate-spin" }) : null, applying ? '正在应用…' : `应用到 ${AI_CLIENT_LABELS[issuedClient]}`] }))) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", disabled: applying, onClick: beginAnother, children: "\u518D\u5EFA\u4E00\u4E2A" }), _jsx(Button, { type: "button", variant: "outline", onClick: () => setShowCreate(false), children: "\u5B8C\u6210" })] })] })) : (_jsxs("form", { className: "space-y-5", onSubmit: (event) => { event.preventDefault(); if (!creating)
232
- void create(); }, children: [_jsxs("label", { className: "block space-y-2", children: [_jsx("span", { className: "text-sm font-medium", children: "\u540D\u79F0" }), _jsx(Input, { autoFocus: true, "aria-label": "API Key \u540D\u79F0", value: name, disabled: creating, onChange: (event) => setName(event.target.value) })] }), _jsxs("label", { className: "block space-y-2", children: [_jsx("span", { className: "text-sm font-medium", children: "\u7528\u9014" }), _jsxs("select", { "aria-label": "API Key \u7528\u9014", className: SELECT_CLASS, value: purpose, disabled: creating, onChange: (event) => setPurpose(event.target.value), children: [_jsx("option", { value: "", disabled: true, children: "\u9009\u62E9\u5BA2\u6237\u7AEF\u5E94\u7528" }), AI_CONNECTIONS_CLIENTS.map((clientId) => (_jsx("option", { value: clientId, children: AI_CLIENT_LABELS[clientId] }, clientId)))] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "\u7528\u9014\u5728\u521B\u5EFA\u65F6\u786E\u5B9A\uFF0CKey \u4F1A\u7ED1\u5B9A\u5230\u8BE5\u5BA2\u6237\u7AEF\u5E94\u7528\u3002" }), error ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", disabled: creating, onClick: () => setShowCreate(false), children: "\u53D6\u6D88" }), _jsxs(Button, { type: "submit", "aria-label": "\u521B\u5EFA API Key", disabled: creating || !name.trim() || !purpose, children: [creating ? _jsx(Loader2, { "aria-hidden": "true", className: "mr-2 h-4 w-4 animate-spin" }) : null, creating ? '正在创建…' : '创建 API Key'] })] })] }))] }) })] }));
227
+ return (_jsx(TooltipProvider, { children: _jsxs("section", { className: "space-y-8", "aria-label": "API Keys", children: [_jsx(AiProviderHeader, { name: "Xpod", mark: "XP", avatar: XPOD_AVATAR, infoLabel: "Xpod \u8BF4\u660E", infoLines: [XPOD_DESCRIPTION, XPOD_CREDENTIAL_NOTE], link: { href: client.apiBase, label: '访问 Xpod' }, badge: (_jsx(Badge, { variant: keys.length > 0 ? 'default' : 'secondary', children: keys.length > 0 ? `已签发 ${keys.length} 个` : '未配置' })) }), _jsxs("section", { className: "space-y-3", "aria-label": "\u5F53\u524D\u8FDE\u63A5", children: [_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, { "aria-hidden": "true", className: "h-4 w-4 text-primary" }), "\u5F53\u524D\u8FDE\u63A5"] }), _jsx("div", { className: "flex flex-wrap items-center justify-end gap-2", children: _jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", "aria-label": "\u65B0\u5EFA API Key", disabled: creating || loading, onClick: openCreate, children: [_jsx(Plus, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "API Key"] }) })] }), loading ? (_jsxs("div", { className: "flex items-center gap-2 py-6 text-sm text-muted-foreground", children: [_jsx(Loader2, { "aria-hidden": "true", className: "h-4 w-4 animate-spin" }), "\u6B63\u5728\u8BFB\u53D6 API Key"] })) : keys.length === 0 ? (_jsx("p", { className: "py-2 text-xs text-muted-foreground", children: "\u5C1A\u672A\u7B7E\u53D1 API Key" })) : (_jsx("ul", { "aria-label": "API Key \u5217\u8868", className: "rounded-xl border border-border/70", children: keys.map((record) => (_jsx(AiGatewayKeyRow, { record: record, busy: Boolean(busyKeyId), onDestroy: () => void destroy(record) }, record.id))) })), _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: _jsxs("section", { className: "space-y-2", "aria-labelledby": "xpod-access-info", children: [_jsx("h4", { id: "xpod-access-info", className: "text-sm font-medium text-foreground", children: "Xpod \u63A5\u5165\u4FE1\u606F" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "API Key \u662F\u7B7E\u53D1\u7ED9\u5BA2\u6237\u7AEF\u5E94\u7528\u7684 CSS \u5BA2\u6237\u7AEF\u51ED\u636E\uFF1BPod \u53EA\u8BB0\u5F55\u5B83\u7ED1\u5B9A\u5230\u54EA\u4E2A\u5BA2\u6237\u7AEF\uFF0C\u4E0D\u4FDD\u5B58 Key \u660E\u6587\u3002" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "\u540C\u4E00\u4E2A API Key \u652F\u6301\u4EE5\u4E0B\u517C\u5BB9\u534F\u8BAE\uFF0C\u6309\u5BA2\u6237\u7AEF\u9009\u62E9\u5E76\u590D\u5236\u5BF9\u5E94\u5730\u5740\uFF1A" }), _jsx(AiEndpointList, { endpoints: xpodProtocolEndpoints(client.apiBase), copy: true, display: "protocol" })] }) })] })] }), _jsx(AiGatewayModelsSection, { models: gatewayModels, selection: modelSelection }), _jsx(Dialog, { open: showCreate, onOpenChange: (open) => { if (!open && (creating || applying))
228
+ return; setShowCreate(open); }, children: _jsxs(DialogContent, { className: "sm:max-w-md", "aria-describedby": undefined, children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: issued ? 'API Key 已签发' : '新建 API Key' }) }), issued && issuedClient ? (_jsxs("div", { className: "space-y-5", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsxs("p", { className: "text-sm", children: ["\u5DF2\u7B7E\u53D1\u300C", issued.name || '未命名 API Key', "\u300D\uFF0C\u7528\u9014\uFF1A", AI_CLIENT_LABELS[issuedClient], "\u3002"] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Xpod \u4E0D\u4FDD\u5B58 Key \u660E\u6587\uFF0C\u8BF7\u7ACB\u5373\u590D\u5236\u6216\u76F4\u63A5\u5E94\u7528\u5230\u5BA2\u6237\u7AEF\uFF1B\u79BB\u5F00\u672C\u6B21\u4F1A\u8BDD\u540E\u65E0\u6CD5\u518D\u6B21\u83B7\u53D6\u3002" })] }), issued.maskedHint ? (_jsx("code", { className: "block truncate rounded-md border border-border/60 bg-muted/30 px-3 py-2 font-mono text-xs text-muted-foreground", children: issued.maskedHint })) : null, !clientConfigurationBridge ? (_jsx("p", { className: "text-xs text-muted-foreground", children: "\u5F53\u524D Web \u73AF\u5883\u65E0\u6CD5\u81EA\u52A8\u5199\u5165\u5BA2\u6237\u7AEF\u914D\u7F6E\uFF0C\u8BF7\u590D\u5236\u540E\u624B\u52A8\u7C98\u8D34\uFF1B\u81EA\u52A8\u5E94\u7528\u9700\u8981\u672C\u673A\u8FDE\u63A5\u3002" })) : null, error ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }) : null, _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(AiCopyButton, { value: () => sessionPlaintext(issued, UNPERSISTED_COPY_MESSAGE), label: "API Key", text: "\u590D\u5236 API Key", disabled: applying, iconClassName: "mr-1.5 h-3.5 w-3.5", copiedIconClassName: "mr-1.5 h-3.5 w-3.5 text-emerald-600", onError: (cause) => setError(errorMessage(cause)) }), _jsx(AiCopyButton, { value: () => manualConfigurationText(issuedClient, client.apiBase, sessionPlaintext(issued, UNPERSISTED_COPY_MESSAGE)), label: `${AI_CLIENT_LABELS[issuedClient]} 配置`, text: `复制 ${AI_CLIENT_LABELS[issuedClient]} 配置`, disabled: applying, iconClassName: "mr-1.5 h-3.5 w-3.5", copiedIconClassName: "mr-1.5 h-3.5 w-3.5 text-emerald-600", onError: (cause) => setError(errorMessage(cause)) }), clientConfigurationBridge ? (appliedClient ? (_jsxs("span", { role: "status", className: "inline-flex items-center gap-1 text-xs text-muted-foreground", children: [_jsx(Check, { "aria-hidden": "true", className: "h-3.5 w-3.5" }), "\u5DF2\u5E94\u7528\u5230 ", AI_CLIENT_LABELS[appliedClient]] })) : (_jsxs(Button, { type: "button", size: "sm", disabled: applying, "aria-label": `应用到 ${AI_CLIENT_LABELS[issuedClient]}`, onClick: () => void apply(), children: [applying ? _jsx(Loader2, { "aria-hidden": "true", className: "mr-2 h-4 w-4 animate-spin" }) : null, applying ? '正在应用…' : `应用到 ${AI_CLIENT_LABELS[issuedClient]}`] }))) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", disabled: applying, onClick: beginAnother, children: "\u518D\u5EFA\u4E00\u4E2A" }), _jsx(Button, { type: "button", variant: "outline", onClick: () => setShowCreate(false), children: "\u5B8C\u6210" })] })] })) : (_jsxs("form", { className: "space-y-5", onSubmit: (event) => { event.preventDefault(); if (!creating)
229
+ void create(); }, children: [_jsxs("label", { className: "block space-y-2", children: [_jsx("span", { className: "text-sm font-medium", children: "\u540D\u79F0" }), _jsx(Input, { autoFocus: true, "aria-label": "API Key \u540D\u79F0", value: name, disabled: creating, onChange: (event) => setName(event.target.value) })] }), _jsxs("label", { className: "block space-y-2", children: [_jsx("span", { className: "text-sm font-medium", children: "\u7528\u9014" }), _jsxs("select", { "aria-label": "API Key \u7528\u9014", className: SELECT_CLASS, value: purpose, disabled: creating, onChange: (event) => setPurpose(event.target.value), children: [_jsx("option", { value: "", disabled: true, children: "\u9009\u62E9\u5BA2\u6237\u7AEF\u5E94\u7528" }), AI_CONNECTIONS_CLIENTS.map((clientId) => (_jsx("option", { value: clientId, children: AI_CLIENT_LABELS[clientId] }, clientId)))] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: "\u7528\u9014\u5728\u521B\u5EFA\u65F6\u786E\u5B9A\uFF0CKey \u4F1A\u7ED1\u5B9A\u5230\u8BE5\u5BA2\u6237\u7AEF\u5E94\u7528\u3002" }), error ? _jsx("p", { role: "alert", className: "text-sm text-destructive", children: error }) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "outline", disabled: creating, onClick: () => setShowCreate(false), children: "\u53D6\u6D88" }), _jsxs(Button, { type: "submit", "aria-label": "\u521B\u5EFA API Key", disabled: creating || !name.trim() || !purpose, children: [creating ? _jsx(Loader2, { "aria-hidden": "true", className: "mr-2 h-4 w-4 animate-spin" }) : null, creating ? '正在创建…' : '创建 API Key'] })] })] }))] }) })] }) }));
233
230
  }
234
231
  function errorMessage(error) {
235
232
  const message = error instanceof Error ? error.message.trim() : '';
@@ -0,0 +1,21 @@
1
+ import type { AiGatewayModel } from './ai-connections-client';
2
+ /**
3
+ * What the gateway list shares with the provider pages.
4
+ *
5
+ * A published model is a joined one, so switching here writes the same
6
+ * selection the provider pages write rather than a second flag to keep in sync.
7
+ */
8
+ export interface GatewayModelSelection {
9
+ isSelected(model: AiGatewayModel): boolean;
10
+ toggle(model: AiGatewayModel): void;
11
+ disabled?: boolean;
12
+ }
13
+ /**
14
+ * The catalog the Gateway publishes to clients (`/v1/models`), switched with the
15
+ * same row the provider pages use: 停用 withdraws a model from the models list
16
+ * endpoint, 启用 publishes it again.
17
+ */
18
+ export declare function AiGatewayModelsSection({ models, selection }: {
19
+ models?: AiGatewayModel[];
20
+ selection?: GatewayModelSelection;
21
+ }): import("react").JSX.Element;
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { Badge } from '@undefineds.co/shared-ui';
4
+ import { Box } from 'lucide-react';
5
+ import { AiModelEmptyPanel, AiModelSearchInput, AiModelRow, modelIconTokens, withCatalogModelId, } from './AiModelCatalog.js';
6
+ /**
7
+ * The catalog the Gateway publishes to clients (`/v1/models`), switched with the
8
+ * same row the provider pages use: 停用 withdraws a model from the models list
9
+ * endpoint, 启用 publishes it again.
10
+ */
11
+ export function AiGatewayModelsSection({ models, selection }) {
12
+ const [search, setSearch] = useState('');
13
+ const catalog = useMemo(() => aggregateGatewayModels(models ?? []), [models]);
14
+ const query = search.trim().toLocaleLowerCase();
15
+ const visibleModels = query
16
+ ? catalog.filter((model) => model.searchText.includes(query))
17
+ : catalog;
18
+ const unavailableCount = catalog.filter((model) => model.availability === 'unavailable').length;
19
+ const publishedCount = selection
20
+ ? catalog.filter((model) => selection.isSelected(model)).length
21
+ : catalog.length;
22
+ return (_jsxs("section", { className: "space-y-8", "aria-label": "\u53EF\u7528\u6A21\u578B", children: [_jsxs("div", { "data-testid": "gateway-models-header", className: "flex flex-wrap items-center justify-between gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1", children: [_jsxs("h3", { className: "flex items-center gap-2 text-sm font-medium text-foreground/90", children: [_jsx(Box, { "aria-hidden": "true", className: "h-4 w-4 text-primary" }), "\u53EF\u7528\u6A21\u578B"] }), _jsx("span", { className: "text-xs text-muted-foreground", children: selection
23
+ ? `共 ${catalog.length} · 已发布 ${publishedCount} · 已失效 ${unavailableCount}`
24
+ : `共 ${catalog.length} · 已失效 ${unavailableCount}` }), _jsx("span", { className: "text-xs text-muted-foreground", children: "Xpod \u5F53\u524D\u53D1\u5E03\u7684\u6A21\u578B" })] }), _jsx(AiModelSearchInput, { value: search, onChange: setSearch })] }), models === undefined ? (_jsx(AiModelEmptyPanel, { children: "Xpod \u6A21\u578B\u76EE\u5F55\u5C1A\u672A\u5C31\u7EEA" })) : catalog.length === 0 ? (_jsx(AiModelEmptyPanel, { children: "\u6682\u65E0\u53EF\u7528\u6A21\u578B" })) : visibleModels.length === 0 ? (_jsx(AiModelEmptyPanel, { children: "\u672A\u627E\u5230\u5339\u914D\u7684\u6A21\u578B" })) : (_jsx("div", { className: "grid gap-2", children: visibleModels.map((model) => (_jsx(AiModelRow, { label: model.displayName ?? model.id, modelId: model.id, iconTokens: model.iconTokens, enabled: selection?.isSelected(model) ?? false, toggleDisabled: selection?.disabled, onToggle: selection ? () => selection.toggle(model) : undefined, unavailable: model.availability === 'unavailable', badges: (_jsxs(_Fragment, { children: [model.custom ? _jsx(Badge, { variant: "outline", className: "shrink-0 text-[10px] font-normal", children: "\u624B\u5DE5" }) : null, model.availability === 'unavailable' ? (_jsx(Badge, { variant: "destructive", className: "shrink-0 text-[10px] font-normal", children: "\u5DF2\u5931\u6548" })) : null] })) }, model.key))) }))] }));
25
+ }
26
+ /**
27
+ * One gateway model can be published once per credential/offering, so the
28
+ * read surface folds those entries the same way the provider page does.
29
+ */
30
+ function aggregateGatewayModels(models) {
31
+ const catalog = new Map();
32
+ for (const entry of models) {
33
+ const model = withCatalogModelId(entry);
34
+ const key = `${model.provider}\0${model.id}`;
35
+ const iconTokens = modelIconTokens(model);
36
+ const searchText = [model.id, model.displayName].filter(Boolean).join('\n').toLocaleLowerCase();
37
+ const existing = catalog.get(key);
38
+ if (!existing) {
39
+ catalog.set(key, { ...model, key, iconTokens, searchText });
40
+ continue;
41
+ }
42
+ if (model.availability !== 'unavailable')
43
+ existing.availability = model.availability;
44
+ if (existing.displayName === undefined && model.displayName !== undefined) {
45
+ existing.displayName = model.displayName;
46
+ }
47
+ existing.iconTokens = [...new Set([...existing.iconTokens, ...iconTokens])];
48
+ existing.searchText += `\n${searchText}`;
49
+ }
50
+ return [...catalog.values()];
51
+ }
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * The ⓘ affordance that explains a provider-style page.
4
+ *
5
+ * It owns its `TooltipProvider` on purpose. Radix keeps one "pointer in transit"
6
+ * flag per provider, and every trigger inside that provider ignores pointer
7
+ * moves while the flag is set. Sharing a provider with the model list below the
8
+ * header meant that leaving a capability glyph silently swallowed the next hover
9
+ * of the ⓘ, so it never opened on the pages whose catalog carries capability
10
+ * metadata. An island per ⓘ keeps that state out of everyone else's way.
11
+ */
12
+ export declare function AiInfoTooltip({ label, lines }: {
13
+ label: string;
14
+ lines: ReactNode[];
15
+ }): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@undefineds.co/shared-ui';
3
+ import { Info } from 'lucide-react';
4
+ /**
5
+ * The ⓘ affordance that explains a provider-style page.
6
+ *
7
+ * It owns its `TooltipProvider` on purpose. Radix keeps one "pointer in transit"
8
+ * flag per provider, and every trigger inside that provider ignores pointer
9
+ * moves while the flag is set. Sharing a provider with the model list below the
10
+ * header meant that leaving a capability glyph silently swallowed the next hover
11
+ * of the ⓘ, so it never opened on the pages whose catalog carries capability
12
+ * metadata. An island per ⓘ keeps that state out of everyone else's way.
13
+ */
14
+ export function AiInfoTooltip({ label, lines }) {
15
+ return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": label, className: "cursor-help rounded-sm text-muted-foreground/50 focus:outline-none focus-visible:text-foreground", children: _jsx(Info, { "aria-hidden": "true", className: "h-3.5 w-3.5" }) }) }), _jsx(TooltipContent, { className: "max-w-xs space-y-2 text-xs", children: lines.map((line, index) => _jsx("p", { children: line }, index)) })] }) }));
16
+ }