@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
@@ -1,9 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from 'react';
3
3
  import { Badge, Button, Input, } from '@undefineds.co/shared-ui';
4
- import { Copy, RotateCcw, SquareTerminal } from 'lucide-react';
4
+ import { RotateCcw, SquareTerminal } from 'lucide-react';
5
5
  import { normalizeAiConnectionsThrownError } from './ai-connections-client.js';
6
6
  import { getClientAvatar } from './client-visuals.js';
7
+ import { normalizeMessagesEndpoint, normalizeV1Endpoint } from './endpoint-urls.js';
8
+ import { AiCopyButton } from './AiCopyButton.js';
7
9
  export const AI_CONNECTIONS_CLIENTS = ['codex', 'claude-code', 'pi', 'codebuddy'];
8
10
  export const AI_CLIENT_LABELS = {
9
11
  codex: 'Codex',
@@ -23,12 +25,10 @@ export function AiClientConfigurationSection({ bridge, endpoint, client, createC
23
25
  const [dryRun, setDryRun] = useState();
24
26
  const [confirmationValue, setConfirmationValue] = useState('');
25
27
  const [busy, setBusy] = useState(false);
26
- const [copiedManualConfig, setCopiedManualConfig] = useState(false);
27
28
  const autoApplyStarted = useRef(false);
28
29
  useEffect(() => {
29
30
  setDryRun(undefined);
30
31
  setConfirmationValue('');
31
- setCopiedManualConfig(false);
32
32
  if (!bridge) {
33
33
  setStatus({ status: 'unavailable' });
34
34
  return;
@@ -149,13 +149,10 @@ export function AiClientConfigurationSection({ bridge, endpoint, client, createC
149
149
  const confirmationSatisfied = !confirmation?.required || confirmationValue === confirmation.token;
150
150
  const clientLabel = AI_CLIENT_LABELS[client];
151
151
  const manual = !bridge;
152
- const copyManualConfig = async () => {
153
- await navigator.clipboard?.writeText(manualConfigurationText(client, endpoint, manualApiKey));
154
- setCopiedManualConfig(true);
155
- };
152
+ const manualConfig = manualConfigurationText(client, endpoint, manualApiKey);
156
153
  return (_jsxs("div", { className: compact ? '' : 'overflow-hidden rounded-md border border-border/60', children: [!compact ? _jsxs("div", { className: "flex flex-wrap items-center gap-2 px-3 py-2", children: [_jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground", children: _jsx(AiClientIcon, { client: client }) }), _jsxs("div", { className: "min-w-[10rem] flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: clientLabel }), !manual ? _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground", children: status.message ?? (status.status === 'configured'
157
154
  ? `${clientLabel} 正在使用 Xpod。`
158
- : '可自动写入本机配置。') }) : null] }), !manual ? _jsx(Badge, { variant: "secondary", children: statusLabel(status.status) }) : null, manual ? (_jsxs(Button, { variant: "ghost", size: "icon", "aria-label": `复制 ${clientLabel} 配置`, title: `复制 ${clientLabel} 配置`, onClick: () => void copyManualConfig(), children: [_jsx(Copy, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: copiedManualConfig ? '已复制' : '复制配置' })] })) : (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "outline", size: "sm", "aria-label": `配置 ${clientLabel}`, disabled: busy, onClick: () => void plan(), children: "\u914D\u7F6E" }), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": `恢复 ${clientLabel} 配置`, disabled: status.status === 'notConfigured' || busy, onClick: () => void restore(), children: _jsx(RotateCcw, { className: "h-4 w-4" }) })] }))] }) : null, compact && busy ? (_jsxs("div", { className: "py-1 text-xs text-muted-foreground", children: ["\u6B63\u5728\u5E94\u7528 ", clientLabel, " \u914D\u7F6E\u2026"] })) : null, compact && !busy && status.message ? (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 py-1 text-xs text-destructive", children: [_jsx("span", { children: status.message }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => void plan(), children: "\u91CD\u8BD5\u5E94\u7528\u914D\u7F6E" })] })) : null, dryRun ? (_jsxs("div", { className: "space-y-3 border-t border-border/60 bg-muted/20 px-4 py-3", children: [_jsx("div", { className: "text-xs font-medium", children: "\u5C06\u6267\u884C\u4EE5\u4E0B\u66F4\u6539" }), dryRun.changes.map((change) => (_jsxs("div", { className: "text-xs text-muted-foreground", children: [_jsx("span", { className: "font-mono", children: change.target }), ' · ', changeActionLabel(change.action), change.backup ? ' · 创建备份' : ''] }, `${change.target}:${change.action}`))), confirmation?.required ? (_jsxs("div", { className: "space-y-2", children: [confirmation.message ? _jsx("div", { className: "text-xs text-muted-foreground", children: confirmation.message }) : null, _jsxs("div", { className: "text-xs text-muted-foreground", children: ["\u786E\u8BA4\u7801\uFF1A", _jsx("code", { className: "font-mono text-foreground", children: confirmation.token })] }), _jsx(Input, { "aria-label": `输入确认码以应用 ${clientLabel} 配置`, value: confirmationValue, onChange: (event) => setConfirmationValue(event.target.value) })] })) : null, _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", "aria-label": confirmation?.required
155
+ : '可自动写入本机配置。') }) : null] }), !manual ? _jsx(Badge, { variant: "secondary", children: statusLabel(status.status) }) : null, manual ? (_jsx(AiCopyButton, { value: manualConfig, label: `${clientLabel} 配置`, variant: "ghost", size: "icon", iconClassName: "h-4 w-4" })) : (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Button, { variant: "outline", size: "sm", "aria-label": `配置 ${clientLabel}`, disabled: busy, onClick: () => void plan(), children: "\u914D\u7F6E" }), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": `恢复 ${clientLabel} 配置`, disabled: status.status === 'notConfigured' || busy, onClick: () => void restore(), children: _jsx(RotateCcw, { className: "h-4 w-4" }) })] }))] }) : null, compact && busy ? (_jsxs("div", { className: "py-1 text-xs text-muted-foreground", children: ["\u6B63\u5728\u5E94\u7528 ", clientLabel, " \u914D\u7F6E\u2026"] })) : null, compact && !busy && status.message ? (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 py-1 text-xs text-destructive", children: [_jsx("span", { children: status.message }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => void plan(), children: "\u91CD\u8BD5\u5E94\u7528\u914D\u7F6E" })] })) : null, dryRun ? (_jsxs("div", { className: "space-y-3 border-t border-border/60 bg-muted/20 px-4 py-3", children: [_jsx("div", { className: "text-xs font-medium", children: "\u5C06\u6267\u884C\u4EE5\u4E0B\u66F4\u6539" }), dryRun.changes.map((change) => (_jsxs("div", { className: "text-xs text-muted-foreground", children: [_jsx("span", { className: "font-mono", children: change.target }), ' · ', changeActionLabel(change.action), change.backup ? ' · 创建备份' : ''] }, `${change.target}:${change.action}`))), confirmation?.required ? (_jsxs("div", { className: "space-y-2", children: [confirmation.message ? _jsx("div", { className: "text-xs text-muted-foreground", children: confirmation.message }) : null, _jsxs("div", { className: "text-xs text-muted-foreground", children: ["\u786E\u8BA4\u7801\uFF1A", _jsx("code", { className: "font-mono text-foreground", children: confirmation.token })] }), _jsx(Input, { "aria-label": `输入确认码以应用 ${clientLabel} 配置`, value: confirmationValue, onChange: (event) => setConfirmationValue(event.target.value) })] })) : null, _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", "aria-label": confirmation?.required
159
156
  ? `确认并应用 ${clientLabel} 配置`
160
157
  : `应用 ${clientLabel} 配置`, disabled: !createClientCredential || busy || !confirmationSatisfied, onClick: () => void apply(), children: confirmation?.required ? '确认并应用' : '应用配置' }), _jsx(Button, { variant: "ghost", size: "sm", disabled: busy, onClick: () => {
161
158
  setDryRun(undefined);
@@ -179,11 +176,16 @@ function statusLabel(status) {
179
176
  default: return '未配置';
180
177
  }
181
178
  }
179
+ /**
180
+ * The OpenAI-compatible base a client is pointed at, derived from the Xpod URL.
181
+ * The rule itself is shared with the client-config adapters, so what a copied
182
+ * config claims and what the adapters write cannot drift apart.
183
+ */
184
+ export const openAiCompatibleBaseUrl = normalizeV1Endpoint;
182
185
  export function manualConfigurationText(client, endpoint, apiKey) {
183
186
  const secret = apiKey ?? '<粘贴 Xpod API Key>';
184
- const baseUrl = endpoint.trim().replace(/\/+$/, '');
185
- const v1Url = baseUrl.endsWith('/v1') ? baseUrl : `${baseUrl}/v1`;
186
- const messagesUrl = baseUrl.replace(/\/v1$/, '');
187
+ const v1Url = normalizeV1Endpoint(endpoint);
188
+ const messagesUrl = normalizeMessagesEndpoint(endpoint);
187
189
  switch (client) {
188
190
  case 'codex':
189
191
  return [
@@ -33,7 +33,7 @@ export function AiConnectDialog({ controller, definition, offerings, apiOffering
33
33
  const authorizationError = error?.offeringId === offering.id ? error.authorization : undefined;
34
34
  const attemptedMethod = methods.find((method) => method.id === (authorizationError?.authorizationMethodId ?? offeringAttempt?.authorizationMethodId))
35
35
  ?? activeMethods.find(isOAuthMethod);
36
- const attemptedMode = authorizationError?.mode ?? offeringAttempt?.mode ?? (attemptedMethod ? connectModeForMethod(attemptedMethod) : modeForOffering(offering, definition));
36
+ const attemptedMode = authorizationError?.mode ?? offeringAttempt?.mode ?? (attemptedMethod ? connectModeForMethod(attemptedMethod) : modeForOffering(offering));
37
37
  const offeringError = error?.offeringId === offering.id ? error.message : undefined;
38
38
  if (offering.lifecycle === 'unavailable' && activeMethods.length === 0) {
39
39
  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'
@@ -2,9 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useContext, useRef } from 'react';
3
3
  import { Avatar, AvatarFallback, AvatarImage, cn } from '@undefineds.co/shared-ui';
4
4
  import { WorkspaceLayoutContext } from '@undefineds.co/extension-sdk/react';
5
- import { getProviderAvatar, getProviderAvatarBackground } from './provider-visuals.js';
6
- import { AI_CONNECTIONS_PINNED_SECTIONS, PROVIDERS, useProviderLoadError, useProviderProducts, useProviderSearch, useProviderStates, useSelectedCredentialId, useSelectedProvider, useSelectedSection, } from './controller.js';
5
+ import { XPOD_AVATAR, getProviderAvatar, getProviderAvatarBackground } from './provider-visuals.js';
6
+ import { AI_CONNECTIONS_PINNED_SECTIONS, PROVIDERS, useAvailableProviders, useProviderLoadError, useProviderProducts, useProviderSearch, useProviderStates, useSelectedCredentialId, useSelectedProvider, useSelectedSection, } from './controller.js';
7
7
  export function AiConnectionsList({ controller }) {
8
+ const availableProviders = useAvailableProviders(controller);
8
9
  const workspace = useContext(WorkspaceLayoutContext);
9
10
  const selectedSection = useSelectedSection(controller);
10
11
  const selectedProvider = useSelectedProvider(controller);
@@ -13,7 +14,10 @@ export function AiConnectionsList({ controller }) {
13
14
  const providerStates = useProviderStates(controller);
14
15
  const providerProducts = useProviderProducts(controller);
15
16
  const providerLoadError = useProviderLoadError(controller);
16
- const providerItems = PROVIDERS.reduce((items, provider) => {
17
+ const visibleProviders = availableProviders
18
+ ? PROVIDERS.filter((provider) => availableProviders.includes(provider.id))
19
+ : PROVIDERS;
20
+ const providerItems = visibleProviders.reduce((items, provider) => {
17
21
  if (provider.id !== 'custom')
18
22
  return [...items, { ...provider }];
19
23
  const credentials = providerProducts.custom?.credentials ?? [];
@@ -86,11 +90,11 @@ function moveSelection(items, requestedIndex, controller, optionRefs) {
86
90
  }
87
91
  /**
88
92
  * The pinned section is an issued-credential surface, i.e. another provider from
89
- * the client's point of view, so it uses the provider row's mark slot instead of
90
- * a generic key icon.
93
+ * the client's point of view, so it carries the Xpod provider mark and falls
94
+ * back to its initials only while that mark is unavailable.
91
95
  */
92
96
  function PinnedMark({ section: _section }) {
93
- return (_jsx(Avatar, { className: "h-9 w-9 shrink-0 rounded-md border border-border/20 bg-muted", children: _jsx(AvatarFallback, { className: "rounded-md bg-muted text-[10px] font-bold uppercase text-muted-foreground", children: "XP" }) }));
97
+ return (_jsxs(Avatar, { className: "h-9 w-9 shrink-0 rounded-md border border-border/20", children: [_jsx(AvatarImage, { src: XPOD_AVATAR, className: "object-cover" }), _jsx(AvatarFallback, { className: "rounded-md bg-muted text-[10px] font-bold uppercase text-muted-foreground", children: "XP" })] }));
94
98
  }
95
99
  function ProviderStateIndicator({ state, statusId, }) {
96
100
  const active = state === 'configured' || state === 'connected';
@@ -1,4 +1,19 @@
1
1
  import { type AiConnectionsController } from './controller';
2
+ /**
3
+ * The page's live credentials table.
4
+ *
5
+ * `useCredentialRows` subscribes to the controller's snapshot of the collection:
6
+ * the rows it returns already include optimistic writes, so a create/update/
7
+ * delete shows before the Pod has confirmed it, and the projection diff keeps
8
+ * unchanged rows identical. Both the table and the layer that reads it are
9
+ * loaded lazily, so this hook is a plain `useSyncExternalStore` over whatever the
10
+ * controller has - which is nothing until the collection's first read lands.
11
+ *
12
+ * Until then - while the layer is still loading, after a failed first read, or
13
+ * when the host offers no collection at all - `credentialRows` stays undefined
14
+ * and the page keeps rendering what the store reported, so a table that cannot
15
+ * load is never shown as an empty table.
16
+ */
2
17
  export declare function AiConnectionsMain({ controller, renderToaster }: {
3
18
  controller: AiConnectionsController;
4
19
  renderToaster?: boolean;
@@ -1,14 +1,35 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { AI_CONNECTIONS_PINNED_SECTIONS, PROVIDERS, useProviderLoadError, useProviderProducts, useProviderSummaries, useSelectedCredentialId, useSelectedProvider, useSelectedSection, } from './controller.js';
2
+ import { AI_CONNECTIONS_PINNED_SECTIONS, PROVIDERS, useLiveRevision, useProviderLoadError, useProviderProducts, useProviderSummaries, useSelectedCredentialId, useSelectedProvider, useSelectedSection, } from './controller.js';
3
+ import { useCredentialRows } from './collections.js';
3
4
  import { AiConnectionsPanel } from './AiConnectionsPanel.js';
4
5
  import { useEffect } from 'react';
6
+ /**
7
+ * The page's live credentials table.
8
+ *
9
+ * `useCredentialRows` subscribes to the controller's snapshot of the collection:
10
+ * the rows it returns already include optimistic writes, so a create/update/
11
+ * delete shows before the Pod has confirmed it, and the projection diff keeps
12
+ * unchanged rows identical. Both the table and the layer that reads it are
13
+ * loaded lazily, so this hook is a plain `useSyncExternalStore` over whatever the
14
+ * controller has - which is nothing until the collection's first read lands.
15
+ *
16
+ * Until then - while the layer is still loading, after a failed first read, or
17
+ * when the host offers no collection at all - `credentialRows` stays undefined
18
+ * and the page keeps rendering what the store reported, so a table that cannot
19
+ * load is never shown as an empty table.
20
+ */
5
21
  export function AiConnectionsMain({ controller, renderToaster = true }) {
22
+ const liveCredentialRows = useCredentialRows(controller);
23
+ return (_jsx(AiConnectionsMainBody, { controller: controller, renderToaster: renderToaster, liveCredentialRows: liveCredentialRows }));
24
+ }
25
+ function AiConnectionsMainBody({ controller, renderToaster, liveCredentialRows, }) {
6
26
  const selectedSection = useSelectedSection(controller);
7
27
  const selectedProvider = useSelectedProvider(controller);
8
28
  const selectedCredentialId = useSelectedCredentialId(controller);
9
29
  const providerSummaries = useProviderSummaries(controller);
10
30
  const providerProducts = useProviderProducts(controller);
11
31
  const providerLoadError = useProviderLoadError(controller);
32
+ const liveRevision = useLiveRevision(controller);
12
33
  const provider = PROVIDERS.find((item) => item.id === selectedProvider);
13
34
  const selectedCredential = selectedProvider === 'custom'
14
35
  ? providerProducts.custom?.credentials.find((credential) => credential.id === selectedCredentialId)
@@ -28,15 +49,20 @@ export function AiConnectionsMain({ controller, renderToaster = true }) {
28
49
  },
29
50
  }
30
51
  : providerProducts;
52
+ useEffect(() => {
53
+ // The open page holds the live subscriptions for the tables it renders, and
54
+ // releases them when it goes away. Re-entrant, so StrictMode is harmless.
55
+ return controller.watchPageTables();
56
+ }, [controller]);
31
57
  useEffect(() => {
32
58
  if (controller.client)
33
59
  void controller.loadProviders();
34
60
  return () => controller.cancelProviderLoads();
35
- }, [controller]);
61
+ }, [controller, liveRevision]);
36
62
  if (!controller.client) {
37
63
  return (_jsxs("section", { role: "alert", "aria-label": "AI Connections unavailable", children: [_jsx("h2", { children: "AI Connections \u5C1A\u672A\u5C31\u7EEA" }), _jsx("p", { children: "\u5BBF\u4E3B\u9700\u8981\u5148\u63D0\u4F9B\u5DF2\u767B\u5F55\u7684 WebID \u548C\u53EF\u7528\u7684 Pod\u3002" })] }));
38
64
  }
39
- return (_jsx("section", { role: "region", "aria-label": regionLabel, children: _jsx(AiConnectionsPanel, { client: controller.client, renderToaster: renderToaster, clientConfigurationBridge: controller.clientConfigurationBridge, selectedSection: selectedSection, selectedProvider: selectedProvider, selectedCredentialId: selectedCredentialId, openExternal: controller.openExternal, providerSummaries: providerSummaries, providerProducts: scopedProducts, providerLoadError: providerLoadError, providerLoading: !providerProducts[selectedProvider] && !providerLoadError, onProviderStateChange: controller.setProviderState }) }));
65
+ return (_jsx("section", { role: "region", "aria-label": regionLabel, children: _jsx(AiConnectionsPanel, { client: controller.client, renderToaster: renderToaster, clientConfigurationBridge: controller.clientConfigurationBridge, selectedSection: selectedSection, selectedProvider: selectedProvider, selectedCredentialId: selectedCredentialId, openExternal: controller.openExternal, providerSummaries: providerSummaries, providerProducts: scopedProducts, providerLoadError: providerLoadError, providerLoading: !providerProducts[selectedProvider] && !providerLoadError, onProviderStateChange: controller.setProviderState, liveRevision: liveRevision, liveCredentialRows: liveCredentialRows }) }));
40
66
  }
41
67
  function scopedCustomOfferings(product, credential) {
42
68
  if (!credential)
@@ -1,13 +1,28 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { AI_CONNECTIONS_PINNED_SECTIONS, PROVIDERS, useProviderProducts, useSelectedProvider, useSelectedSection, } from './controller.js';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AI_CONNECTIONS_PINNED_SECTIONS, PROVIDERS, useLiveUpdates, useProviderProducts, useSelectedProvider, useSelectedSection, } from './controller.js';
3
+ /** One short line per state; the title carries the explanation. */
4
+ const LIVE_UPDATE_AFFORDANCE = {
5
+ live: {
6
+ label: '实时',
7
+ title: '实时更新已开启:Pod 中的改动会立即反映到本页。',
8
+ dotClass: 'bg-emerald-500',
9
+ },
10
+ unavailable: {
11
+ label: '实时不可用',
12
+ title: '实时更新当前不可用;本页仍会正常读取与操作。',
13
+ dotClass: 'bg-muted-foreground/60',
14
+ },
15
+ };
3
16
  export function AiConnectionsMainHeader({ controller, }) {
4
17
  const selectedSection = useSelectedSection(controller);
5
18
  const selectedProvider = useSelectedProvider(controller);
6
19
  const providerProducts = useProviderProducts(controller);
20
+ const liveUpdates = useLiveUpdates(controller);
7
21
  const provider = PROVIDERS.find((candidate) => candidate.id === selectedProvider);
8
22
  const title = AI_CONNECTIONS_PINNED_SECTIONS.find((item) => item.id === selectedSection)?.title
9
23
  ?? providerProducts[selectedProvider]?.name
10
24
  ?? provider?.name
11
25
  ?? selectedProvider;
12
- return (_jsx("div", { className: "flex h-full min-w-0 items-center px-4", children: _jsx("h1", { className: "truncate text-sm font-medium text-foreground", children: title }) }));
26
+ const liveAffordance = liveUpdates === 'idle' ? undefined : LIVE_UPDATE_AFFORDANCE[liveUpdates];
27
+ return (_jsxs("div", { className: "flex h-full min-w-0 items-center gap-2 px-4", children: [_jsx("h1", { className: "truncate text-sm font-medium text-foreground", children: title }), liveAffordance && (_jsxs("span", { "data-live-updates": liveUpdates, title: liveAffordance.title, "aria-label": liveAffordance.title, className: "ml-auto inline-flex shrink-0 items-center gap-1 text-[10px] font-medium text-muted-foreground", children: [_jsx("span", { "aria-hidden": "true", className: `h-1.5 w-1.5 rounded-full ${liveAffordance.dotClass}` }), liveAffordance.label] }))] }));
13
28
  }
@@ -1,6 +1,7 @@
1
1
  import { type AiConnectionsClient, type AiConnectionsProvider, type AiProviderConnectionSummary, type AiProviderSummary } from './ai-connections-client';
2
2
  import { type AiConnectionsWorkspaceSection, type ProviderProductState } from './controller';
3
3
  import type { AiClientConfigurationBridge } from './AiClientConfigurationSection';
4
+ import { type CredentialRow } from './collections';
4
5
  export interface AiConnectionsPanelProps {
5
6
  client: AiConnectionsClient;
6
7
  /** Disable when the embedding application already renders shared-ui Toaster. */
@@ -16,5 +17,19 @@ export interface AiConnectionsPanelProps {
16
17
  providerLoading?: boolean;
17
18
  onProviderStateChange?: (provider: AiConnectionsProvider, state: ProviderProductState) => void;
18
19
  onModelSelectionChange?: (provider: AiConnectionsProvider, modelIds: string[]) => void;
20
+ /**
21
+ * Advances when a watched Pod table document changed; the page re-reads the
22
+ * tables it renders through its existing loaders.
23
+ */
24
+ liveRevision?: number;
25
+ /**
26
+ * Live rows of the credentials table, when the host exposes a collection.
27
+ *
28
+ * Present, every credential list the page renders is rebuilt from these rows
29
+ * (including optimistic writes) instead of from the summary the store read, so
30
+ * one table has one owner. The store's summaries stay the enrichment source
31
+ * for the attributes the descriptor cannot project.
32
+ */
33
+ liveCredentialRows?: readonly CredentialRow[];
19
34
  }
20
- export declare function AiConnectionsPanel({ client, renderToaster, openExternal, clientConfigurationBridge, selectedSection, selectedProvider, selectedCredentialId, providerSummaries: providerSummariesInput, providerProducts, providerLoadError, providerLoading, onProviderStateChange, onModelSelectionChange, }: AiConnectionsPanelProps): import("react").JSX.Element;
35
+ export declare function AiConnectionsPanel({ client, renderToaster, openExternal, clientConfigurationBridge, selectedSection, selectedProvider, selectedCredentialId, providerSummaries: providerSummariesInput, providerProducts, providerLoadError, providerLoading, onProviderStateChange, onModelSelectionChange, liveRevision, liveCredentialRows, }: AiConnectionsPanelProps): import("react").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Toaster, toast, } from '@undefineds.co/shared-ui';
4
4
  import { normalizeAiConnectionsThrownError, } from './ai-connections-client.js';
5
5
  import { PROVIDERS, } from './controller.js';
@@ -7,8 +7,10 @@ import { offeringTitle } from './offering-label.js';
7
7
  import { AiProviderCard, } from './AiProviderCard.js';
8
8
  import { AiModelEditorDialog, } from './AiModelEditorDialog.js';
9
9
  import { AiGatewayKeysSection } from './AiGatewayKeysSection.js';
10
+ import { credentialCarriers, withLiveCredentials } from './collections.js';
11
+ import { modelCatalogId, withCatalogModelId } from './AiModelCatalog.js';
10
12
  const EMPTY_PROVIDER_SUMMARIES = {};
11
- export function AiConnectionsPanel({ client, renderToaster = true, openExternal = openExternalUrl, clientConfigurationBridge, selectedSection = 'provider', selectedProvider, selectedCredentialId, providerSummaries: providerSummariesInput = EMPTY_PROVIDER_SUMMARIES, providerProducts = {}, providerLoadError, providerLoading = false, onProviderStateChange, onModelSelectionChange, }) {
13
+ export function AiConnectionsPanel({ client, renderToaster = true, openExternal = openExternalUrl, clientConfigurationBridge, selectedSection = 'provider', selectedProvider, selectedCredentialId, providerSummaries: providerSummariesInput = EMPTY_PROVIDER_SUMMARIES, providerProducts = {}, providerLoadError, providerLoading = false, onProviderStateChange, onModelSelectionChange, liveRevision = 0, liveCredentialRows, }) {
12
14
  const [connectionStates, setConnectionStates] = useState({});
13
15
  const [models, setModels] = useState([]);
14
16
  const [gatewayModels, setGatewayModels] = useState();
@@ -29,10 +31,17 @@ export function AiConnectionsPanel({ client, renderToaster = true, openExternal
29
31
  const [providerProductOverrides, setProviderProductOverrides] = useState({});
30
32
  const pollingGeneration = useRef(0);
31
33
  const modelSelectionGeneration = useRef({});
32
- const effectiveProviderProducts = {
34
+ /**
35
+ * Credentials come from the live rows whenever the host has a collection for
36
+ * that table; the store's own list is only the enrichment carrier, and an
37
+ * override produced by a mutation is a projection of those same rows, never an
38
+ * independent read. Everything else about a product (offerings, models,
39
+ * status) still comes from the merged products.
40
+ */
41
+ const effectiveProviderProducts = withLiveCredentials({
33
42
  ...providerProducts,
34
43
  ...providerProductOverrides,
35
- };
44
+ }, credentialCarriers(providerProducts, providerProductOverrides), liveCredentialRows);
36
45
  const updateConnectionState = useCallback((provider, state) => {
37
46
  setConnectionStates((current) => ({ ...current, [provider]: state }));
38
47
  onProviderStateChange?.(provider, productStateFromConnection(state));
@@ -80,7 +89,7 @@ export function AiConnectionsPanel({ client, renderToaster = true, openExternal
80
89
  active = false;
81
90
  pollingGeneration.current += 1;
82
91
  };
83
- }, [client]);
92
+ }, [client, liveRevision]);
84
93
  useEffect(() => {
85
94
  let active = true;
86
95
  setGatewayModels(undefined);
@@ -130,14 +139,15 @@ export function AiConnectionsPanel({ client, renderToaster = true, openExternal
130
139
  setBusy(provider, false);
131
140
  }
132
141
  };
142
+ /**
143
+ * The provider-level browser entry the legacy connect path still calls.
144
+ *
145
+ * Which ways in exist is offering data, so a definition can no longer name a
146
+ * mode; the only mode left at this level is the API-key flow, which is what
147
+ * this handler starts.
148
+ */
133
149
  const beginBrowserConnect = async (definition) => {
134
- if (definition.browserMode === 'connectUnsupported')
135
- return;
136
- if (definition.browserMode === 'browserAssistedApiKey') {
137
- await beginApiKey(definition.id);
138
- return;
139
- }
140
- await beginConnectMode(definition.id, definition.browserMode);
150
+ await beginApiKey(definition.id);
141
151
  };
142
152
  const beginOfferingConnect = async (provider, offering, mode, method) => {
143
153
  if (mode === 'browserAssistedApiKey') {
@@ -651,10 +661,39 @@ export function AiConnectionsPanel({ client, renderToaster = true, openExternal
651
661
  }
652
662
  })();
653
663
  }, [client, effectiveProviderProducts, modelSelectionGeneration, models, onModelSelectionChange, selectedCredentialId, selectedModelIds]);
664
+ /** Selection ids a provider currently has, however they were last reported. */
665
+ const selectionIdsFor = useCallback((provider) => (selectedModelIds[provider]
666
+ ?? effectiveProviderProducts[provider]?.selectedModels.map(modelSelectionId)
667
+ ?? []), [selectedModelIds, effectiveProviderProducts]);
668
+ /**
669
+ * The key a gateway row is selected under.
670
+ *
671
+ * The projection names a model by its id; the Pod stores the selection as the
672
+ * model's resource URI. Selecting by the bare id would therefore compare
673
+ * unequal against everything already stored and resolve to a resource the
674
+ * write cannot find, so the row's own id is used whenever the account has one.
675
+ */
676
+ const gatewaySelectionId = useCallback((model) => {
677
+ const stored = models.find((candidate) => candidate.provider === model.provider
678
+ && modelCatalogId(candidate) === modelCatalogId(model));
679
+ return stored ? modelSelectionId(stored) : modelSelectionId(model);
680
+ }, [models]);
681
+ /** The gateway list switches the same selection the provider pages switch. */
682
+ const gatewayModelSelection = useMemo(() => ({
683
+ isSelected: (model) => selectionIdsFor(model.provider).includes(gatewaySelectionId(model)),
684
+ toggle: (model) => {
685
+ const current = selectionIdsFor(model.provider);
686
+ const selectionId = gatewaySelectionId(model);
687
+ void handleModelSelectionChange(model.provider, current.includes(selectionId)
688
+ ? current.filter((id) => id !== selectionId)
689
+ : [...current, selectionId]);
690
+ },
691
+ disabled: providerLoading,
692
+ }), [gatewaySelectionId, handleModelSelectionChange, providerLoading, selectionIdsFor]);
654
693
  const providerContent = (_jsxs("section", { children: [providerLoadError ? (_jsxs("p", { className: "mb-4 rounded-md border border-destructive/30 px-3 py-2 text-sm text-destructive", children: ["Provider \u72B6\u6001\u8BFB\u53D6\u5931\u8D25\uFF1A", providerLoadError] })) : null, PROVIDERS.filter((definition) => definition.id === (selectedProvider ?? 'openai')).map((definition) => {
655
694
  const providerProduct = effectiveProviderProducts[definition.id];
656
695
  const providerModels = mergeProviderModelCatalog(models.filter((model) => model.provider === definition.id
657
- && (definition.id !== 'custom' || !selectedCredentialId || model.credentialId === selectedCredentialId)), providerProduct?.selectedModels ?? []);
696
+ && (definition.id !== 'custom' || !selectedCredentialId || model.credentialId === selectedCredentialId)), providerProduct?.selectedModels ?? [], gatewayModels ?? []);
658
697
  const providerSelectedModelIds = selectedModelIds[definition.id]
659
698
  ?? providerProduct?.selectedModels.map(modelSelectionId)
660
699
  ?? [];
@@ -666,7 +705,7 @@ export function AiConnectionsPanel({ client, renderToaster = true, openExternal
666
705
  [definition.id]: value,
667
706
  })), onBeginApiKey: () => void beginApiKey(definition.id), onBeginOffering: (offering, mode, method) => void beginOfferingConnect(definition.id, offering, mode, method), onCancelConnect: (attempt) => void cancelConnect(definition.id, attempt), onBeginBrowser: () => void beginBrowserConnect(definition), onSaveApiKey: () => void saveApiKey(definition), onDisconnect: (credential) => void disconnect(definition.id, credential?.id), onCreateApiKeyCredential: (offering, input) => createApiKeyCredential(definition.id, offering, input), onCreateLocalCredential: (offering, method) => createLocalCredential(definition.id, offering, method), onUpdateCredential: (credential, patch) => updateProviderCredential(definition.id, credential, patch), onDeleteCredential: (credential) => void deleteProviderCredential(definition.id, credential), onTestCredential: (credential) => void testProviderCredential(definition.id, credential), onReorderCredentials: (offering, credentials, fromIndex, toIndex) => void reorderProviderCredentials(definition.id, offering, credentials, fromIndex, toIndex), onRefreshQuota: (offering, credential) => void refreshQuota(definition.id, offering, credential), verifyPending: Boolean(verifyingProviders[definition.id]), onVerify: () => void verifyProvider(definition.id), onAddModel: () => openModelEditor(definition.id), onEditModel: (model) => openModelEditor(definition.id, model), onDeleteModel: (model) => void deleteProviderModel(definition.id, model), onModelSelectionChange: handleModelSelectionChange, onDismissError: () => setProviderError(definition.id) }, definition.id));
668
707
  })] }));
669
- const keyContent = (_jsx(AiGatewayKeysSection, { client: client, clientConfigurationBridge: clientConfigurationBridge, gatewayModels: gatewayModels }));
708
+ const keyContent = (_jsx(AiGatewayKeysSection, { client: client, clientConfigurationBridge: clientConfigurationBridge, gatewayModels: gatewayModels, modelSelection: gatewayModelSelection, liveRevision: liveRevision }));
670
709
  return (_jsxs("div", { "data-testid": "ai-connections-panel", className: "mx-auto w-full max-w-5xl space-y-10 px-4 py-6 sm:px-8 sm:py-8", children: [renderToaster ? _jsx(Toaster, {}) : null, selectedSection === 'keys' ? keyContent : null, selectedSection === 'provider' ? providerContent : null, selectedSection === 'provider' && modelEditor ? (_jsx(AiModelEditorDialog, { open: true, providerName: PROVIDERS.find((definition) => definition.id === modelEditor.provider)?.name ?? modelEditor.provider, initialValue: modelEditor.model
671
710
  ? {
672
711
  id: modelEditor.model.id,
@@ -836,6 +875,9 @@ function delay(milliseconds) {
836
875
  return new Promise((resolve) => window.setTimeout(resolve, milliseconds));
837
876
  }
838
877
  function errorMessage(error) {
878
+ if (error instanceof TypeError && /failed to fetch|fetch failed|networkerror|load failed/i.test(error.message)) {
879
+ return '暂时无法连接 Xpod,请在连接恢复后确认操作结果,再重试。';
880
+ }
839
881
  const message = normalizeAiConnectionsThrownError(error);
840
882
  return message === 'AI Connection request failed. Please try again.'
841
883
  ? '请求未完成。请确认 Xpod 正在运行且登录仍有效,然后重试。'
@@ -936,22 +978,52 @@ function markMissingSelectedModelsUnavailable(current, provider, selectedModels,
936
978
  }
937
979
  return merged;
938
980
  }
939
- function mergeProviderModelCatalog(catalog, selectedModels) {
940
- const merged = [...catalog];
941
- for (const selectedModel of selectedModels) {
981
+ /**
982
+ * Fold the discovered catalog and the Pod's pinned selections into one list.
983
+ *
984
+ * Entries are folded onto the model id they name first (`withCatalogModelId`),
985
+ * so a pinned selection which only carries its stored resource reference lands
986
+ * on the catalog row it belongs to instead of being listed a second time. The
987
+ * two rows still keep their own resource ids: the card collects every
988
+ * `selectionId` it sees, so unpicking the model releases all of them.
989
+ *
990
+ * `projection` carries the capability evidence. Capabilities are not Pod data -
991
+ * a row records what was discovered and nothing more - so they are derived from
992
+ * the model id against the provider catalog, and the Gateway projection is that
993
+ * derivation for the models it publishes.
994
+ */
995
+ function mergeProviderModelCatalog(catalog, selectedModels, projection = []) {
996
+ const evidence = new Map();
997
+ for (const model of projection) {
998
+ evidence.set(`${model.provider}\0${modelCatalogId(model)}`, model);
999
+ }
1000
+ const withEvidence = (model) => {
1001
+ const source = evidence.get(`${model.provider}\0${modelCatalogId(model)}`);
1002
+ const capabilities = [...new Set([...(model.capabilities ?? []), ...(source?.capabilities ?? [])])];
1003
+ const inputModalities = model.inputModalities ?? source?.inputModalities;
1004
+ if (capabilities.length === 0 && !inputModalities)
1005
+ return model;
1006
+ return compactModel({
1007
+ ...model,
1008
+ ...(capabilities.length > 0 ? { capabilities } : {}),
1009
+ ...(inputModalities ? { inputModalities } : {}),
1010
+ });
1011
+ };
1012
+ const merged = catalog.map((model) => withEvidence(withCatalogModelId(model)));
1013
+ for (const selectedModel of selectedModels.map(withCatalogModelId)) {
942
1014
  const index = merged.findIndex((model) => modelSelectionId(model) === modelSelectionId(selectedModel));
943
1015
  if (index === -1) {
944
- merged.push(selectedModel);
1016
+ merged.push(withEvidence(selectedModel));
945
1017
  continue;
946
1018
  }
947
- merged[index] = compactModel({
1019
+ merged[index] = withEvidence(compactModel({
948
1020
  ...merged[index],
949
1021
  ...selectedModel,
950
1022
  displayName: merged[index].displayName ?? selectedModel.displayName,
951
1023
  availability: merged[index].availability === 'unavailable'
952
1024
  ? 'unavailable'
953
1025
  : selectedModel.availability,
954
- });
1026
+ }));
955
1027
  }
956
1028
  return merged;
957
1029
  }
@@ -0,0 +1,35 @@
1
+ import { type ComponentProps } from 'react';
2
+ import { Button } from '@undefineds.co/shared-ui';
3
+ /**
4
+ * One clipboard affordance for the whole applet: the copied flag, its reset
5
+ * timer, and the "no clipboard access" failure all live here instead of being
6
+ * re-implemented by every caller that hands a value to the user.
7
+ */
8
+ export declare function useCopyToClipboard(resetMs?: number): {
9
+ copied: boolean;
10
+ copy: (value: string) => Promise<void>;
11
+ };
12
+ /**
13
+ * Copy button carrying the applet's established affordance: a `复制 <what>`
14
+ * accessible name, the visible label swapping to `已复制`, and a check glyph in
15
+ * place of the copy glyph for the length of the feedback window.
16
+ */
17
+ export declare function AiCopyButton({ value, label, text, title, variant, size, disabled, className, iconClassName, copiedIconClassName, onError, }: {
18
+ /**
19
+ * Text to copy. A function is resolved when the button is pressed, for
20
+ * secrets that only exist for the length of one flow.
21
+ */
22
+ value: string | (() => string);
23
+ /** What is copied; the accessible name becomes `复制 ${label}`. */
24
+ label: string;
25
+ /** Visible label. Omit for an icon-only button. */
26
+ text?: string;
27
+ title?: string;
28
+ variant?: ComponentProps<typeof Button>['variant'];
29
+ size?: ComponentProps<typeof Button>['size'];
30
+ disabled?: boolean;
31
+ className?: string;
32
+ iconClassName?: string;
33
+ copiedIconClassName?: string;
34
+ onError?: (cause: unknown) => void;
35
+ }): import("react").JSX.Element;
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { Button, cn } from '@undefineds.co/shared-ui';
4
+ import { Check, Copy } from 'lucide-react';
5
+ const CLIPBOARD_UNAVAILABLE = '当前浏览器无法访问剪贴板,请允许剪贴板访问后重试。';
6
+ /**
7
+ * One clipboard affordance for the whole applet: the copied flag, its reset
8
+ * timer, and the "no clipboard access" failure all live here instead of being
9
+ * re-implemented by every caller that hands a value to the user.
10
+ */
11
+ export function useCopyToClipboard(resetMs = 1500) {
12
+ const [copied, setCopied] = useState(false);
13
+ const timer = useRef(undefined);
14
+ useEffect(() => () => clearTimeout(timer.current), []);
15
+ const copy = useCallback(async (value) => {
16
+ if (!navigator.clipboard?.writeText)
17
+ throw new Error(CLIPBOARD_UNAVAILABLE);
18
+ await navigator.clipboard.writeText(value);
19
+ setCopied(true);
20
+ clearTimeout(timer.current);
21
+ timer.current = setTimeout(() => setCopied(false), resetMs);
22
+ }, [resetMs]);
23
+ return { copied, copy };
24
+ }
25
+ /**
26
+ * Copy button carrying the applet's established affordance: a `复制 <what>`
27
+ * accessible name, the visible label swapping to `已复制`, and a check glyph in
28
+ * place of the copy glyph for the length of the feedback window.
29
+ */
30
+ export function AiCopyButton({ value, label, text, title, variant = 'outline', size = 'sm', disabled = false, className, iconClassName = 'h-3.5 w-3.5', copiedIconClassName, onError, }) {
31
+ const { copied, copy } = useCopyToClipboard();
32
+ const iconOnly = text === undefined;
33
+ const Icon = copied ? Check : Copy;
34
+ const handleClick = () => {
35
+ void (async () => {
36
+ try {
37
+ await copy(typeof value === 'function' ? value() : value);
38
+ }
39
+ catch (cause) {
40
+ onError?.(cause);
41
+ }
42
+ })();
43
+ };
44
+ return (_jsxs(Button, { type: "button", variant: variant, size: size, disabled: disabled, className: className, "aria-label": `复制 ${label}`, title: title ?? `复制 ${label}`, onClick: handleClick, children: [_jsx(Icon, { "aria-hidden": "true", className: cn(iconClassName, copied && copiedIconClassName) }), _jsx("span", { className: iconOnly ? 'sr-only' : 'ml-1.5', children: copied ? '已复制' : text ?? '复制' })] }));
45
+ }