@geminilight/mindos 0.6.63 → 0.6.65

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 (268) hide show
  1. package/_standalone/.mindos-build-version +1 -1
  2. package/_standalone/.next/BUILD_ID +1 -1
  3. package/_standalone/.next/app-path-routes-manifest.json +21 -21
  4. package/_standalone/.next/build-manifest.json +2 -2
  5. package/_standalone/.next/cache/.previewinfo +1 -1
  6. package/_standalone/.next/cache/.rscinfo +1 -1
  7. package/_standalone/.next/cache/config.json +3 -3
  8. package/_standalone/.next/prerender-manifest.json +3 -3
  9. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  10. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error.html +2 -2
  12. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  25. package/_standalone/.next/server/app/agents/page.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  27. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  28. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/agents/copy-skill/route.js.nft.json +1 -1
  39. package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
  42. package/_standalone/.next/server/app/api/ask/route.js +48 -42
  43. package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
  44. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
  48. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
  50. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
  52. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
  54. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/file/import/route.js +1 -1
  57. package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
  58. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/file/raw/route.js.nft.json +1 -1
  60. package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
  62. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  64. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  65. package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  66. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
  68. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
  71. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
  73. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/mcp/agents/route.js +1 -1
  75. package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
  76. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
  83. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
  85. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  86. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  87. package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  88. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/api/settings/list-models/route.js +1 -1
  90. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  91. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/api/settings/route.js +1 -1
  93. package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  94. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/api/settings/test-key/route.js +1 -1
  96. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  97. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  99. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/api/setup/route.js +1 -1
  102. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  103. package/_standalone/.next/server/app/api/skills/route.js +1 -1
  104. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  105. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  106. package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
  107. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  108. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  109. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  110. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  111. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  112. package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
  113. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  114. package/_standalone/.next/server/app/changes/page.js +1 -1
  115. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  116. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  117. package/_standalone/.next/server/app/echo/[segment]/page.js +2 -2
  118. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  119. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  120. package/_standalone/.next/server/app/echo/page.js +1 -1
  121. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  122. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  123. package/_standalone/.next/server/app/explore/page.js +1 -1
  124. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  125. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  126. package/_standalone/.next/server/app/help/page.js +1 -1
  127. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  128. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  129. package/_standalone/.next/server/app/inbox/history/page.js +1 -1
  130. package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
  131. package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
  132. package/_standalone/.next/server/app/login/page.js +1 -1
  133. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  134. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  135. package/_standalone/.next/server/app/page.js +1 -1
  136. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  137. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  138. package/_standalone/.next/server/app/setup/page.js +2 -2
  139. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  140. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  141. package/_standalone/.next/server/app/trash/page.js +3 -3
  142. package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
  143. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  144. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  145. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  146. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  147. package/_standalone/.next/server/app/wiki/page.js +1 -1
  148. package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
  149. package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
  150. package/_standalone/.next/server/app-paths-manifest.json +21 -21
  151. package/_standalone/.next/server/chunks/122.js +222 -0
  152. package/_standalone/.next/server/chunks/3113.js +52 -0
  153. package/_standalone/.next/server/chunks/6539.js +1 -1
  154. package/_standalone/.next/server/chunks/8388.js +2 -2
  155. package/_standalone/.next/server/chunks/953.js +3 -3
  156. package/_standalone/.next/server/chunks/9787.js +2 -0
  157. package/_standalone/.next/server/pages/500.html +2 -2
  158. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  159. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  160. package/_standalone/.next/static/chunks/1001-99da82ec8d8c136f.js +1 -0
  161. package/_standalone/.next/static/chunks/5149-4d828886dda479fa.js +1 -0
  162. package/_standalone/.next/static/chunks/{5581-82e5db227f8e9393.js → 5581-c671163a2fe1b312.js} +2 -2
  163. package/_standalone/.next/static/chunks/6636-53238eff89503f03.js +6 -0
  164. package/_standalone/.next/static/chunks/6757-1c1a89720fdda8f0.js +1 -0
  165. package/_standalone/.next/static/chunks/7129-20e9d2463a9da646.js +1 -0
  166. package/_standalone/.next/static/chunks/{3674-be69a8b858ceacdd.js → 7294-cac25d97869afadc.js} +1 -1
  167. package/_standalone/.next/static/chunks/8225-21e5cebc3731ddf0.js +1 -0
  168. package/_standalone/.next/static/chunks/8520-b51810e66293ceb8.js +22 -0
  169. package/_standalone/.next/static/chunks/9207-dc9c31b351a2ed78.js +1 -0
  170. package/_standalone/.next/static/chunks/app/agents/[agentKey]/{page-b0dabe793500383d.js → page-2f5cf97e03dc1cc9.js} +1 -1
  171. package/_standalone/.next/static/chunks/app/agents/{page-1f1ac330c8177cf6.js → page-50eac58d511dcc6e.js} +1 -1
  172. package/_standalone/.next/static/chunks/app/echo/[segment]/page-2a00f4686adf3885.js +11 -0
  173. package/_standalone/.next/static/chunks/app/{layout-50a6b1164ee98ab9.js → layout-2cb7a6602d2e5d5f.js} +62 -58
  174. package/_standalone/.next/static/chunks/app/{page-73802bd31d7f6c9f.js → page-5ab911b2226f6ff7.js} +1 -1
  175. package/_standalone/.next/static/chunks/app/setup/page-907b7c57fad2292b.js +1 -0
  176. package/_standalone/.next/static/chunks/app/trash/page-11a511b065ea84c2.js +1 -0
  177. package/_standalone/.next/static/chunks/app/view/[...path]/{page-808f39963bf04715.js → page-26e47dd4c533a58c.js} +2 -2
  178. package/_standalone/.next/static/css/67e7918f5ed7d147.css +1 -0
  179. package/_standalone/.next/trace +65 -65
  180. package/_standalone/__tests__/api/ask-attachments.test.ts +194 -0
  181. package/_standalone/__tests__/api/settings.test.ts +16 -12
  182. package/_standalone/__tests__/api/setup.test.ts +11 -9
  183. package/_standalone/__tests__/api/test-key.test.ts +0 -10
  184. package/_standalone/__tests__/components/UpdateToast.test.ts +344 -0
  185. package/_standalone/__tests__/core/context.test.ts +48 -426
  186. package/_standalone/__tests__/lib/pi-skills.test.ts +4 -4
  187. package/_standalone/__tests__/lib/settings-ai-client.test.ts +32 -12
  188. package/_standalone/__tests__/setup.ts +5 -5
  189. package/_standalone/components/ask/AskContent.tsx +70 -40
  190. package/_standalone/components/ask/AskHeader.tsx +8 -1
  191. package/_standalone/components/ask/MessageList.tsx +37 -3
  192. package/_standalone/components/ask/ProviderModelCapsule.tsx +51 -129
  193. package/_standalone/components/settings/AiTab.tsx +270 -347
  194. package/_standalone/components/settings/CustomProviderFields.tsx +121 -0
  195. package/_standalone/components/settings/CustomProvidersCard.tsx +2 -2
  196. package/_standalone/components/settings/KnowledgeTab.tsx +6 -20
  197. package/_standalone/components/settings/McpAgentInstall.tsx +7 -2
  198. package/_standalone/components/settings/Primitives.tsx +48 -104
  199. package/_standalone/components/settings/ProviderModal.tsx +38 -221
  200. package/_standalone/components/settings/SettingsContent.tsx +5 -12
  201. package/_standalone/components/settings/TestButton.tsx +64 -0
  202. package/_standalone/components/settings/types.ts +3 -12
  203. package/_standalone/components/settings/useCustomProviderForm.ts +132 -0
  204. package/_standalone/components/setup/StepAI.tsx +3 -3
  205. package/_standalone/components/shared/ModelInput.tsx +18 -4
  206. package/_standalone/components/shared/ProviderSelect.tsx +126 -134
  207. package/_standalone/hooks/useAskChat.ts +97 -13
  208. package/_standalone/hooks/useAskPanel.ts +17 -1
  209. package/_standalone/lib/settings-ai-client.ts +17 -8
  210. package/_standalone/tsconfig.tsbuildinfo +1 -1
  211. package/app/app/api/ask/route.ts +124 -44
  212. package/app/app/api/mcp/agents/route.ts +3 -3
  213. package/app/app/api/settings/list-models/route.ts +15 -26
  214. package/app/app/api/settings/route.ts +14 -59
  215. package/app/app/api/settings/test-key/route.ts +47 -12
  216. package/app/app/api/setup/route.ts +36 -18
  217. package/app/app/api/skills/route.ts +1 -1
  218. package/app/app/layout.tsx +5 -3
  219. package/app/components/HomeContent.tsx +11 -0
  220. package/app/components/UpdateToast.tsx +255 -0
  221. package/app/components/ask/AskContent.tsx +70 -40
  222. package/app/components/ask/AskHeader.tsx +8 -1
  223. package/app/components/ask/MessageList.tsx +37 -3
  224. package/app/components/ask/ProviderModelCapsule.tsx +51 -129
  225. package/app/components/settings/AiTab.tsx +270 -347
  226. package/app/components/settings/CustomProviderFields.tsx +121 -0
  227. package/app/components/settings/CustomProvidersCard.tsx +2 -2
  228. package/app/components/settings/KnowledgeTab.tsx +6 -20
  229. package/app/components/settings/McpAgentInstall.tsx +7 -2
  230. package/app/components/settings/Primitives.tsx +48 -104
  231. package/app/components/settings/ProviderModal.tsx +38 -221
  232. package/app/components/settings/SettingsContent.tsx +5 -12
  233. package/app/components/settings/TestButton.tsx +64 -0
  234. package/app/components/settings/types.ts +3 -12
  235. package/app/components/settings/useCustomProviderForm.ts +132 -0
  236. package/app/components/setup/StepAI.tsx +3 -3
  237. package/app/components/shared/ModelInput.tsx +18 -4
  238. package/app/components/shared/ProviderSelect.tsx +126 -134
  239. package/app/hooks/useAskChat.ts +97 -13
  240. package/app/hooks/useAskPanel.ts +17 -1
  241. package/app/lib/agent/context.ts +65 -0
  242. package/app/lib/agent/providers.ts +25 -0
  243. package/app/lib/agent/tools.ts +1 -1
  244. package/app/lib/custom-endpoints.ts +129 -29
  245. package/app/lib/i18n/modules/settings.ts +20 -0
  246. package/app/lib/pi-integration/skills.ts +16 -4
  247. package/app/lib/settings-ai-client.ts +17 -8
  248. package/app/lib/settings.ts +64 -90
  249. package/app/lib/types.ts +4 -0
  250. package/package.json +1 -1
  251. package/_standalone/.next/server/chunks/530.js +0 -218
  252. package/_standalone/.next/server/chunks/9007.js +0 -2
  253. package/_standalone/.next/server/chunks/9137.js +0 -52
  254. package/_standalone/.next/static/chunks/1309-373ade1b40aea186.js +0 -1
  255. package/_standalone/.next/static/chunks/3165-9189a38fd9ebf6f2.js +0 -1
  256. package/_standalone/.next/static/chunks/4587-5d06728133fff222.js +0 -1
  257. package/_standalone/.next/static/chunks/6261-5ce86db54b19ae46.js +0 -1
  258. package/_standalone/.next/static/chunks/6636-9bbc90fb3b8731fe.js +0 -6
  259. package/_standalone/.next/static/chunks/7637-904b0a381dc3ec02.js +0 -1
  260. package/_standalone/.next/static/chunks/8520-84e607f33c409f91.js +0 -22
  261. package/_standalone/.next/static/chunks/9207-9a4a1a1ede4f8e6e.js +0 -1
  262. package/_standalone/.next/static/chunks/app/echo/[segment]/page-bc5e104eb7ae6327.js +0 -11
  263. package/_standalone/.next/static/chunks/app/setup/page-79acb0baf38184c6.js +0 -1
  264. package/_standalone/.next/static/chunks/app/trash/page-d040db56863da504.js +0 -1
  265. package/_standalone/.next/static/css/1287672978833d07.css +0 -1
  266. package/_standalone/lib/agent/context.ts +0 -403
  267. /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_buildManifest.js +0 -0
  268. /package/_standalone/.next/static/{X86rF8dKEO0InosOw4a2_ → eIlwbGas1iRGonlPyEwj7}/_ssgManifest.js +0 -0
@@ -1,128 +1,52 @@
1
1
  'use client';
2
2
 
3
- import { useState, useEffect, useCallback, useMemo } from 'react';
4
- import { X, AlertCircle, Loader2 } from 'lucide-react';
3
+ import { X } from 'lucide-react';
5
4
  import { useLocale } from '@/lib/stores/locale-store';
6
5
  import { type Messages } from '@/lib/i18n';
7
- import { type CustomProvider, generateCustomProviderId } from '@/lib/custom-endpoints';
8
- import { PROVIDER_PRESETS, type ProviderId, groupedProviders } from '@/lib/agent/providers';
9
- import { Field, Input, Select } from './Primitives';
10
- import ProviderSelect from '@/components/shared/ProviderSelect';
6
+ import { type CustomProvider } from '@/lib/custom-endpoints';
7
+ import { useCustomProviderForm } from './useCustomProviderForm';
8
+ import CustomProviderFields from './CustomProviderFields';
9
+ import { TestButton } from './TestButton';
11
10
 
12
11
  interface ProviderModalProps {
13
12
  isOpen: boolean;
14
13
  onClose: () => void;
15
14
  onSave: (provider: CustomProvider) => void;
16
15
  initialProvider?: CustomProvider;
16
+ existingNames?: string[];
17
17
  t: Messages;
18
18
  }
19
19
 
20
- type TestState = 'idle' | 'testing' | 'ok' | 'error';
21
-
20
+ /**
21
+ * Modal wrapper — renders nothing when closed, remounts inner form
22
+ * on open so hook state resets cleanly.
23
+ */
22
24
  export default function ProviderModal({
23
- isOpen,
24
- onClose,
25
- onSave,
26
- initialProvider,
27
- t,
25
+ isOpen, onClose, onSave, initialProvider, existingNames, t,
28
26
  }: ProviderModalProps) {
29
- const { locale } = useLocale();
30
- const [name, setName] = useState('');
31
- const [baseProviderId, setBaseProviderId] = useState<ProviderId>('openai');
32
- const [apiKey, setApiKey] = useState('');
33
- const [model, setModel] = useState('');
34
- const [baseUrl, setBaseUrl] = useState('');
35
- const [showApiKey, setShowApiKey] = useState(false);
36
- const [testState, setTestState] = useState<TestState>('idle');
37
- const [testError, setTestError] = useState('');
38
- const [isSaving, setIsSaving] = useState(false);
39
-
40
- useEffect(() => {
41
- if (initialProvider) {
42
- setName(initialProvider.name);
43
- setBaseProviderId(initialProvider.baseProviderId);
44
- setApiKey(initialProvider.apiKey === '***set***' ? '' : initialProvider.apiKey);
45
- setModel(initialProvider.model);
46
- setBaseUrl(initialProvider.baseUrl);
47
- } else {
48
- setName('');
49
- setBaseProviderId('openai');
50
- setApiKey('');
51
- setModel('');
52
- setBaseUrl('');
53
- }
54
- setShowApiKey(false);
55
- setTestState('idle');
56
- setTestError('');
57
- }, [initialProvider, isOpen]);
58
-
59
- const handleTest = useCallback(async () => {
60
- if (!name.trim() || !baseUrl.trim() || !model.trim()) {
61
- setTestError('Name, base URL, and model are required');
62
- return;
63
- }
64
-
65
- setTestState('testing');
66
- setTestError('');
67
-
68
- try {
69
- const res = await fetch('/api/settings/test-key', {
70
- method: 'POST',
71
- headers: { 'Content-Type': 'application/json' },
72
- body: JSON.stringify({
73
- customProviderId: initialProvider?.id,
74
- apiKey,
75
- model,
76
- baseUrl,
77
- baseProviderId,
78
- }),
79
- });
80
-
81
- const json = await res.json();
82
-
83
- if (json.ok) {
84
- setTestState('ok');
85
- } else {
86
- setTestState('error');
87
- setTestError(json.error || 'Test failed');
88
- }
89
- } catch (err) {
90
- setTestState('error');
91
- setTestError('Network error');
92
- }
93
- }, [name, baseUrl, model, apiKey, baseProviderId, initialProvider?.id]);
94
-
95
- const handleSave = useCallback(async () => {
96
- if (!name.trim() || !baseUrl.trim() || !model.trim()) {
97
- setTestError('Name, base URL, and model are required');
98
- return;
99
- }
100
-
101
- setIsSaving(true);
102
-
103
- try {
104
- const provider: CustomProvider = {
105
- id: initialProvider?.id || generateCustomProviderId(),
106
- name: name.trim(),
107
- baseProviderId,
108
- apiKey,
109
- model: model.trim(),
110
- baseUrl: baseUrl.trim(),
111
- };
27
+ if (!isOpen) return null;
112
28
 
113
- onSave(provider);
114
- } finally {
115
- setIsSaving(false);
116
- }
117
- }, [name, baseUrl, model, apiKey, baseProviderId, initialProvider, onSave]);
29
+ return (
30
+ <ProviderModalInner
31
+ key={initialProvider?.id ?? '__new__'}
32
+ onClose={onClose}
33
+ onSave={onSave}
34
+ initialProvider={initialProvider}
35
+ existingNames={existingNames}
36
+ t={t}
37
+ />
38
+ );
39
+ }
118
40
 
119
- if (!isOpen) return null;
41
+ function ProviderModalInner({
42
+ onClose, onSave, initialProvider, existingNames, t,
43
+ }: Omit<ProviderModalProps, 'isOpen'>) {
44
+ const { locale } = useLocale();
45
+ const form = useCustomProviderForm({ initial: initialProvider, onSave, locale, existingNames });
120
46
 
121
- const preset = PROVIDER_PRESETS[baseProviderId];
122
- const displayName = locale === 'zh' ? preset.nameZh : preset.name;
123
47
  const title = initialProvider
124
- ? t.settings?.customProviders?.modal?.titleEdit
125
- : t.settings?.customProviders?.modal?.titleAdd;
48
+ ? (locale === 'zh' ? '编辑 Provider' : 'Edit Provider')
49
+ : (locale === 'zh' ? '添加 Provider' : 'Add Provider');
126
50
 
127
51
  return (
128
52
  <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40" onClick={onClose}>
@@ -130,138 +54,31 @@ export default function ProviderModal({
130
54
  className="bg-card border border-border rounded-lg shadow-lg p-6 max-w-md w-full mx-4 max-h-[90vh] overflow-y-auto"
131
55
  onClick={e => e.stopPropagation()}
132
56
  >
133
- {/* Header */}
134
57
  <div className="flex items-center justify-between mb-4">
135
58
  <h2 className="font-semibold text-foreground">{title}</h2>
136
- <button
137
- type="button"
138
- onClick={onClose}
139
- className="p-1 hover:bg-muted rounded transition-colors"
140
- >
59
+ <button type="button" onClick={onClose} className="p-1 hover:bg-muted rounded transition-colors">
141
60
  <X size={16} />
142
61
  </button>
143
62
  </div>
144
63
 
145
- {/* Form */}
146
- <div className="space-y-3">
147
- {/* Name */}
148
- <Field
149
- label={t.settings?.customProviders?.modal?.fieldName ?? 'Name'}
150
- hint={t.settings?.customProviders?.modal?.fieldNameHint}
151
- >
152
- <Input
153
- value={name}
154
- onChange={e => setName(e.target.value)}
155
- placeholder="Company GPT-4"
156
- />
157
- </Field>
158
-
159
- {/* Protocol */}
160
- <div>
161
- <label className="text-xs font-medium text-foreground block mb-2">
162
- {t.settings?.customProviders?.modal?.fieldProtocol ?? 'Protocol'}
163
- </label>
164
- <p className="text-2xs text-muted-foreground mb-2">
165
- {t.settings?.customProviders?.modal?.fieldProtocolHint}
166
- </p>
167
- <ProviderSelect
168
- value={baseProviderId}
169
- onChange={id => id !== 'skip' && setBaseProviderId(id as ProviderId)}
170
- compact
171
- />
172
- </div>
173
-
174
- {/* Base URL */}
175
- <Field
176
- label={t.settings?.customProviders?.modal?.fieldBaseUrl ?? 'Base URL'}
177
- hint={t.settings?.customProviders?.modal?.fieldBaseUrlHint}
178
- >
179
- <Input
180
- value={baseUrl}
181
- onChange={e => setBaseUrl(e.target.value)}
182
- placeholder="https://api.example.com/v1"
183
- />
184
- </Field>
185
-
186
- {/* API Key */}
187
- <Field
188
- label={t.settings?.customProviders?.modal?.fieldApiKey ?? 'API Key'}
189
- hint={t.settings?.customProviders?.modal?.fieldApiKeyHint}
190
- >
191
- <div className="flex gap-2 items-center">
192
- <Input
193
- type={showApiKey ? 'text' : 'password'}
194
- value={apiKey}
195
- onChange={e => setApiKey(e.target.value)}
196
- placeholder="••••••••"
197
- />
198
- <button
199
- type="button"
200
- onClick={() => setShowApiKey(!showApiKey)}
201
- className="text-xs text-muted-foreground hover:text-foreground transition-colors px-2 py-1"
202
- >
203
- {showApiKey ? 'Hide' : 'Show'}
204
- </button>
205
- </div>
206
- </Field>
207
-
208
- {/* Model */}
209
- <Field
210
- label={t.settings?.customProviders?.modal?.fieldModel ?? 'Model'}
211
- hint={t.settings?.customProviders?.modal?.fieldModelHint}
212
- >
213
- <Input
214
- value={model}
215
- onChange={e => setModel(e.target.value)}
216
- placeholder="gpt-4-turbo"
217
- />
218
- </Field>
219
-
220
- {/* Error message */}
221
- {testError && testState !== 'ok' && (
222
- <div className="flex items-start gap-2 text-xs text-destructive/80 bg-destructive/8 border border-destructive/20 rounded-lg px-3 py-2">
223
- <AlertCircle size={13} className="shrink-0 mt-0.5" />
224
- <span>{testError}</span>
225
- </div>
226
- )}
227
-
228
- {/* Success message */}
229
- {testState === 'ok' && (
230
- <div className="flex items-center gap-2 text-xs text-success bg-success/10 border border-success/20 rounded-lg px-3 py-2">
231
- <span>✓ {t.settings?.customProviders?.modal?.success ?? 'Connected'}</span>
232
- </div>
233
- )}
234
- </div>
64
+ <CustomProviderFields form={form} t={t} locale={locale} layout="full" />
235
65
 
236
- {/* Buttons */}
237
66
  <div className="flex gap-2 mt-6">
238
67
  <button
239
68
  type="button"
240
69
  onClick={onClose}
241
- disabled={isSaving}
242
- className="flex-1 px-3 py-2 text-sm rounded border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors disabled:opacity-40"
70
+ className="flex-1 px-3 py-2 text-sm rounded-lg border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors"
243
71
  >
244
72
  {t.settings?.customProviders?.modal?.buttonCancel ?? 'Cancel'}
245
73
  </button>
74
+ <TestButton result={form.testResult} disabled={!form.canSave} onTest={form.handleTest} t={t} />
246
75
  <button
247
76
  type="button"
248
- onClick={handleTest}
249
- disabled={isSaving || testState === 'testing'}
250
- className="flex-1 px-3 py-2 text-sm rounded border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors disabled:opacity-40 inline-flex items-center justify-center gap-1"
251
- >
252
- {testState === 'testing' && <Loader2 size={12} className="animate-spin" />}
253
- {testState === 'testing'
254
- ? (t.settings?.customProviders?.modal?.validating ?? 'Testing...')
255
- : (t.settings?.customProviders?.modal?.buttonSave ?? 'Save & Test')}
256
- </button>
257
- <button
258
- type="button"
259
- onClick={handleSave}
260
- disabled={isSaving}
261
- className="flex-1 px-3 py-2 text-sm rounded font-medium bg-[var(--amber)] text-[var(--amber-foreground)] hover:bg-[var(--amber)]/90 transition-colors disabled:opacity-40 inline-flex items-center justify-center gap-1"
77
+ onClick={form.handleSave}
78
+ disabled={!form.canSave}
79
+ className="flex-1 px-4 py-2 text-sm font-medium rounded-lg bg-[var(--amber)] text-[var(--amber-foreground)] hover:bg-[var(--amber)]/90 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
262
80
  >
263
- {isSaving && <Loader2 size={12} className="animate-spin" />}
264
- Save
81
+ {locale === 'zh' ? '保存' : 'Save'}
265
82
  </button>
266
83
  </div>
267
84
  </div>
@@ -121,7 +121,7 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
121
121
  await apiFetch('/api/settings', {
122
122
  method: 'POST',
123
123
  headers: { 'Content-Type': 'application/json' },
124
- body: JSON.stringify({ ai: d.ai, agent: d.agent, mindRoot: d.mindRoot, webPassword: d.webPassword, authToken: d.authToken, customProviders: d.customProviders }),
124
+ body: JSON.stringify({ ai: d.ai, agent: d.agent, mindRoot: d.mindRoot, webPassword: d.webPassword, authToken: d.authToken }),
125
125
  });
126
126
  setStatus('saved');
127
127
  window.dispatchEvent(new Event('mindos:settings-changed'));
@@ -168,7 +168,7 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
168
168
  apiFetch('/api/settings', {
169
169
  method: 'POST',
170
170
  headers: { 'Content-Type': 'application/json' },
171
- body: JSON.stringify({ ai: d.ai, agent: d.agent, mindRoot: d.mindRoot, webPassword: d.webPassword, authToken: d.authToken, customProviders: d.customProviders }),
171
+ body: JSON.stringify({ ai: d.ai, agent: d.agent, mindRoot: d.mindRoot, webPassword: d.webPassword, authToken: d.authToken }),
172
172
  }).catch(() => {});
173
173
  }
174
174
  };
@@ -179,10 +179,6 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
179
179
  setData(d => d ? { ...d, ai: { ...d.ai, ...patch } } : d);
180
180
  }, []);
181
181
 
182
- const updateCustomProviders = useCallback((providers: import('@/lib/custom-endpoints').CustomProvider[]) => {
183
- setData(d => d ? { ...d, customProviders: providers } : d);
184
- }, []);
185
-
186
182
  const updateAgent = useCallback((patch: Partial<AgentSettings>) => {
187
183
  setData(d => d ? { ...d, agent: { ...(d.agent ?? {}), ...patch } } : d);
188
184
  }, []);
@@ -190,11 +186,8 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
190
186
  const restoreFromEnv = useCallback(async () => {
191
187
  if (!data) return;
192
188
  const defaults: AiSettings = {
193
- provider: 'anthropic',
194
- providers: {
195
- anthropic: { apiKey: '', model: 'claude-sonnet-4-6' },
196
- openai: { apiKey: '', model: 'gpt-5.4', baseUrl: '' },
197
- },
189
+ activeProvider: '',
190
+ providers: [],
198
191
  };
199
192
  setData(d => d ? { ...d, ai: defaults } : d);
200
193
  const DEBOUNCE_DELAY = 800;
@@ -234,7 +227,7 @@ export default function SettingsContent({ visible, initialTab, variant, onClose
234
227
  </div>
235
228
  ) : (
236
229
  <>
237
- {tab === 'ai' && data?.ai && <AiTab data={data} updateAi={updateAi} updateAgent={updateAgent} updateCustomProviders={updateCustomProviders} t={t} />}
230
+ {tab === 'ai' && data?.ai && <AiTab data={data} updateAi={updateAi} updateAgent={updateAgent} t={t} />}
238
231
  {tab === 'appearance' && <AppearanceTab font={font} setFont={setFont} fontSize={fontSize} setFontSize={setFontSize} contentWidth={contentWidth} setContentWidth={setContentWidth} dark={dark} setDark={setDark} locale={locale} setLocale={setLocale} t={t} />}
239
232
  {tab === 'knowledge' && data && <KnowledgeTab data={data} setData={setData} t={t} />}
240
233
  {tab === 'sync' && <SyncTab t={t} />}
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+
3
+ import { AlertCircle, Loader2, Check, Zap } from 'lucide-react';
4
+ import type { TestResult, ErrorCode } from './useCustomProviderForm';
5
+ import type { AiTabProps } from './types';
6
+
7
+ function errorMessage(t: AiTabProps['t'], code?: ErrorCode): string {
8
+ switch (code) {
9
+ case 'auth_error': return t.settings.ai.testKeyAuthError;
10
+ case 'model_not_found': return t.settings.ai.testKeyModelNotFound;
11
+ case 'rate_limited': return t.settings.ai.testKeyRateLimited;
12
+ case 'network_error': return t.settings.ai.testKeyNetworkError;
13
+ default: return t.settings.ai.testKeyUnknown;
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Shared test-connection button used by both built-in and custom provider forms.
19
+ * Shows contextual icon + text based on test result state (idle/testing/ok/error).
20
+ */
21
+ export function TestButton({
22
+ result, disabled, onTest, t,
23
+ }: {
24
+ result: TestResult;
25
+ disabled: boolean;
26
+ onTest: () => void;
27
+ t: AiTabProps['t'];
28
+ }) {
29
+ const isTesting = result.state === 'testing';
30
+ const isOk = result.state === 'ok';
31
+ const isError = result.state === 'error';
32
+
33
+ return (
34
+ <button
35
+ type="button"
36
+ disabled={disabled || isTesting}
37
+ onClick={onTest}
38
+ className={`inline-flex items-center gap-1.5 px-3.5 py-1.5 text-sm font-medium rounded-lg transition-all duration-200 disabled:cursor-not-allowed ${
39
+ isOk
40
+ ? 'bg-success/10 text-success border border-success/20'
41
+ : isError
42
+ ? 'bg-destructive/8 text-destructive border border-destructive/20 hover:bg-destructive/12'
43
+ : 'border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 disabled:opacity-40'
44
+ }`}
45
+ >
46
+ {isTesting ? (
47
+ <Loader2 size={13} className="animate-spin" />
48
+ ) : isOk ? (
49
+ <Check size={13} />
50
+ ) : isError ? (
51
+ <AlertCircle size={13} />
52
+ ) : (
53
+ <Zap size={13} />
54
+ )}
55
+ {isTesting
56
+ ? t.settings.ai.testKeyTesting
57
+ : isOk && result.latency != null
58
+ ? t.settings.ai.testKeyOk(result.latency)
59
+ : isError
60
+ ? errorMessage(t, result.code)
61
+ : t.settings.ai.testKey}
62
+ </button>
63
+ );
64
+ }
@@ -1,17 +1,10 @@
1
1
  import type { Locale, Messages } from '@/lib/i18n';
2
2
  import type React from 'react';
3
- import type { ProviderId } from '@/lib/agent/providers';
4
- import type { CustomProvider } from '@/lib/custom-endpoints';
5
-
6
- export interface ProviderConfig {
7
- apiKey: string;
8
- model: string;
9
- baseUrl?: string;
10
- }
3
+ import type { Provider } from '@/lib/custom-endpoints';
11
4
 
12
5
  export interface AiSettings {
13
- provider: ProviderId;
14
- providers: Partial<Record<ProviderId, ProviderConfig>>;
6
+ activeProvider: string;
7
+ providers: Provider[];
15
8
  }
16
9
 
17
10
  export interface AgentSettings {
@@ -31,7 +24,6 @@ export interface SettingsData {
31
24
  mcpPort?: number;
32
25
  envOverrides?: Record<string, boolean>;
33
26
  envValues?: Record<string, string>;
34
- customProviders?: CustomProvider[];
35
27
  }
36
28
 
37
29
  export type Tab = 'ai' | 'appearance' | 'knowledge' | 'mcp' | 'sync' | 'update' | 'uninstall';
@@ -161,7 +153,6 @@ export interface AiTabProps {
161
153
  data: SettingsData;
162
154
  updateAi: (patch: Partial<AiSettings>) => void;
163
155
  updateAgent: (patch: Partial<AgentSettings>) => void;
164
- updateCustomProviders: (providers: CustomProvider[]) => void;
165
156
  t: Messages;
166
157
  }
167
158
 
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+
3
+ import { useState, useCallback } from 'react';
4
+ import { type ProviderId } from '@/lib/agent/providers';
5
+ import { type Provider, generateProviderId } from '@/lib/custom-endpoints';
6
+
7
+ export type TestState = 'idle' | 'testing' | 'ok' | 'error';
8
+ export type ErrorCode = 'auth_error' | 'model_not_found' | 'rate_limited' | 'network_error' | 'unknown';
9
+
10
+ export interface TestResult {
11
+ state: TestState;
12
+ latency?: number;
13
+ error?: string;
14
+ code?: ErrorCode;
15
+ }
16
+
17
+ export interface CustomProviderFormState {
18
+ name: string;
19
+ setName: (v: string) => void;
20
+ protocol: ProviderId;
21
+ setProtocol: (v: ProviderId) => void;
22
+ apiKey: string;
23
+ setApiKey: (v: string) => void;
24
+ model: string;
25
+ setModel: (v: string) => void;
26
+ baseUrl: string;
27
+ setBaseUrl: (v: string) => void;
28
+ testResult: TestResult;
29
+ canSave: boolean;
30
+ isDuplicateName: boolean;
31
+ handleTest: () => Promise<void>;
32
+ handleSave: () => void;
33
+ }
34
+
35
+ /**
36
+ * Shared form state + test/save logic for provider forms.
37
+ * Used by both the inline form (AiTab) and the modal (ProviderModal).
38
+ */
39
+ export function useCustomProviderForm({
40
+ initial,
41
+ onSave,
42
+ locale,
43
+ existingNames,
44
+ }: {
45
+ initial?: Provider;
46
+ onSave: (provider: Provider) => void;
47
+ locale: string;
48
+ existingNames?: string[];
49
+ }): CustomProviderFormState {
50
+ const [name, setName] = useState(initial?.name ?? '');
51
+ const [protocol, setProtocol] = useState<ProviderId>(initial?.protocol ?? 'openai');
52
+ const [apiKey, setApiKey] = useState(initial?.apiKey ?? '');
53
+ const [model, setModel] = useState(initial?.model ?? '');
54
+ const [baseUrl, setBaseUrl] = useState(initial?.baseUrl ?? '');
55
+ const [testResult, setTestResult] = useState<TestResult>({ state: 'idle' });
56
+
57
+ // Check for duplicate name (exclude the provider being edited)
58
+ const trimmedName = name.trim();
59
+ const isDuplicateName = !!(trimmedName && existingNames?.some(
60
+ n => n.toLowerCase() === trimmedName.toLowerCase(),
61
+ ));
62
+
63
+ const canSave = !!(trimmedName && baseUrl.trim() && model.trim() && !isDuplicateName);
64
+
65
+ const handleTest = useCallback(async () => {
66
+ if (!canSave) {
67
+ setTestResult({
68
+ state: 'error',
69
+ error: locale === 'zh' ? '名称、接口地址和模型为必填' : 'Name, base URL, and model are required',
70
+ });
71
+ return;
72
+ }
73
+ setTestResult({ state: 'testing' });
74
+ try {
75
+ const res = await fetch('/api/settings/test-key', {
76
+ method: 'POST',
77
+ headers: { 'Content-Type': 'application/json' },
78
+ body: JSON.stringify(
79
+ initial?.id
80
+ ? { provider: initial.id, apiKey, model, baseUrl }
81
+ : { protocol, apiKey, model, baseUrl },
82
+ ),
83
+ });
84
+ const json = await res.json();
85
+ if (json.ok) {
86
+ setTestResult({ state: 'ok', latency: json.latency });
87
+ } else {
88
+ setTestResult({ state: 'error', error: json.error || 'Test failed', code: json.code });
89
+ }
90
+ } catch {
91
+ setTestResult({ state: 'error', code: 'network_error', error: 'Network error' });
92
+ }
93
+ }, [canSave, apiKey, model, baseUrl, protocol, locale, initial?.id]);
94
+
95
+ const handleSave = useCallback(() => {
96
+ if (isDuplicateName) {
97
+ setTestResult({
98
+ state: 'error',
99
+ error: locale === 'zh' ? '名称已存在,请使用其他名称' : 'Name already exists, please use a different name',
100
+ });
101
+ return;
102
+ }
103
+ if (!canSave) {
104
+ setTestResult({
105
+ state: 'error',
106
+ error: locale === 'zh' ? '名称、接口地址和模型为必填' : 'Name, base URL, and model are required',
107
+ });
108
+ return;
109
+ }
110
+ onSave({
111
+ id: initial?.id || generateProviderId(),
112
+ name: name.trim(),
113
+ protocol,
114
+ apiKey,
115
+ model: model.trim(),
116
+ baseUrl: baseUrl.trim(),
117
+ });
118
+ }, [canSave, isDuplicateName, name, protocol, apiKey, model, baseUrl, initial?.id, onSave, locale]);
119
+
120
+ return {
121
+ name, setName,
122
+ protocol, setProtocol,
123
+ apiKey, setApiKey,
124
+ model, setModel,
125
+ baseUrl, setBaseUrl,
126
+ testResult,
127
+ canSave,
128
+ isDuplicateName,
129
+ handleTest,
130
+ handleSave,
131
+ };
132
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
4
  import { ChevronDown, ChevronRight, Copy, ExternalLink } from 'lucide-react';
5
- import { Field, Input, ApiKeyInput } from '@/components/settings/Primitives';
5
+ import { Field, Input, PasswordInput } from '@/components/settings/Primitives';
6
6
  import type { SetupState, SetupMessages, PortStatus, ProviderSetupConfig } from './types';
7
7
  import type { ProviderId } from '@/lib/agent/providers';
8
8
  import { PROVIDER_PRESETS, isProviderId, getApiKeyEnvVar, getDefaultBaseUrl } from '@/lib/agent/providers';
@@ -58,7 +58,7 @@ export default function StepAI({ state, update, s, onCopyToken, webPortStatus, m
58
58
  <div className="space-y-5">
59
59
  <ProviderSelect
60
60
  value={state.provider}
61
- onChange={id => update('provider', id)}
61
+ onChange={id => update('provider', id as ProviderId | 'skip')}
62
62
  showSkip
63
63
  compact
64
64
  configuredProviders={configuredProviders}
@@ -68,7 +68,7 @@ export default function StepAI({ state, update, s, onCopyToken, webPortStatus, m
68
68
  <div className="space-y-4 pt-2">
69
69
  {/* API Key */}
70
70
  <Field label={s.apiKey}>
71
- <ApiKeyInput
71
+ <PasswordInput
72
72
  value={currentConfig.apiKey}
73
73
  onChange={v => patchConfig({ apiKey: v })}
74
74
  placeholder={currentConfig.apiKeyMask || `${getApiKeyEnvVar(currentProvider as ProviderId) ?? 'API Key'}...`}
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
3
+ import { useState, useRef, useCallback, useEffect, useMemo, type CSSProperties } from 'react';
4
4
  import { ChevronDown, Loader2 } from 'lucide-react';
5
5
  import { Input } from '@/components/settings/Primitives';
6
6
  import { type ProviderId, PROVIDER_PRESETS } from '@/lib/agent/providers';
@@ -32,7 +32,9 @@ export default function ModelInput({
32
32
  const [focused, setFocused] = useState(false);
33
33
  const [highlightIdx, setHighlightIdx] = useState(-1);
34
34
  const containerRef = useRef<HTMLDivElement>(null);
35
+ const inputRowRef = useRef<HTMLDivElement>(null);
35
36
  const listRef = useRef<HTMLDivElement>(null);
37
+ const [dropdownStyle, setDropdownStyle] = useState<CSSProperties>({});
36
38
  const fetchedRef = useRef(false);
37
39
  const fetchVersionRef = useRef(0);
38
40
  const loadingRef = useRef(false);
@@ -107,6 +109,18 @@ export default function ModelInput({
107
109
 
108
110
  const showDropdown = open || (focused && models !== null && value.trim().length > 0 && filtered.length > 0);
109
111
 
112
+ // Compute fixed position for dropdown so it escapes overflow:auto ancestors
113
+ useEffect(() => {
114
+ if (!showDropdown || !inputRowRef.current) return;
115
+ const rect = inputRowRef.current.getBoundingClientRect();
116
+ setDropdownStyle({
117
+ position: 'fixed',
118
+ top: rect.bottom + 4,
119
+ left: rect.left,
120
+ width: rect.width,
121
+ });
122
+ }, [showDropdown]);
123
+
110
124
  useEffect(() => { setHighlightIdx(-1); }, [filtered]);
111
125
 
112
126
  useEffect(() => {
@@ -150,7 +164,7 @@ export default function ModelInput({
150
164
 
151
165
  return (
152
166
  <div ref={containerRef} className="relative">
153
- <div className="flex gap-1.5">
167
+ <div ref={inputRowRef} className="flex gap-1.5">
154
168
  <Input
155
169
  value={value}
156
170
  onChange={e => { onChange(e.target.value); if (!open) setFocused(true); }}
@@ -175,7 +189,7 @@ export default function ModelInput({
175
189
  </div>
176
190
  {error && <p className="text-xs text-error mt-1">{error}</p>}
177
191
  {showDropdown && filtered.length > 0 && (
178
- <div ref={listRef} className="absolute z-50 mt-1 w-full max-h-48 overflow-y-auto rounded-lg border border-border bg-popover shadow-lg">
192
+ <div ref={listRef} style={dropdownStyle} className="fixed z-[9999] max-h-48 overflow-y-auto rounded-lg border border-border bg-popover shadow-lg">
179
193
  {filtered.map((m, i) => {
180
194
  const isMatch = !value.trim() || m.toLowerCase().includes(value.toLowerCase());
181
195
  return (
@@ -197,7 +211,7 @@ export default function ModelInput({
197
211
  </div>
198
212
  )}
199
213
  {open && filtered.length === 0 && (
200
- <div className="absolute z-50 mt-1 w-full rounded-lg border border-border bg-popover shadow-lg px-3 py-2 text-xs text-muted-foreground">
214
+ <div style={dropdownStyle} className="fixed z-[9999] rounded-lg border border-border bg-popover shadow-lg px-3 py-2 text-xs text-muted-foreground">
201
215
  {noModelsLabel}
202
216
  </div>
203
217
  )}