ai-agent-router 0.2.0 → 0.2.2

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 (354) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +14 -0
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/fallback-build-manifest.json +2 -2
  5. package/.next/next-server.js.nft.json +1 -1
  6. package/.next/prerender-manifest.json +3 -3
  7. package/.next/required-server-files.js +4 -4
  8. package/.next/required-server-files.json +4 -4
  9. package/.next/routes-manifest.json +84 -0
  10. package/.next/server/app/_global-error/page.js +1 -1
  11. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  12. package/.next/server/app/_global-error.html +2 -2
  13. package/.next/server/app/_global-error.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/server/app/_not-found/page.js +2 -2
  20. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/.next/server/app/_not-found.html +1 -1
  23. package/.next/server/app/_not-found.rsc +2 -2
  24. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  25. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  26. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  27. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  28. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  29. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  30. package/.next/server/app/api/config/route.js.nft.json +1 -1
  31. package/.next/server/app/api/gateway/[...path]/route.js.nft.json +1 -1
  32. package/.next/server/app/api/gateway/models/route.js.nft.json +1 -1
  33. package/.next/server/app/api/gateway/route.js.nft.json +1 -1
  34. package/.next/server/app/api/ide/claude/apply/route.js.nft.json +1 -1
  35. package/.next/server/app/api/ide/claude/available-models/route.js.nft.json +1 -1
  36. package/.next/server/app/api/ide/claude/restore/route.js.nft.json +1 -1
  37. package/.next/server/app/api/ide/claude/save/route.js.nft.json +1 -1
  38. package/.next/server/app/api/ide/claude/status/route.js.nft.json +1 -1
  39. package/.next/server/app/api/ide/claude/test/route.js +1 -1
  40. package/.next/server/app/api/ide/claude/test/route.js.nft.json +1 -1
  41. package/.next/server/app/api/ide/openclaw/apply/route/app-paths-manifest.json +3 -0
  42. package/.next/server/app/api/ide/openclaw/apply/route/build-manifest.json +11 -0
  43. package/.next/server/app/api/ide/openclaw/apply/route/server-reference-manifest.json +4 -0
  44. package/.next/server/app/api/ide/openclaw/apply/route.js +7 -0
  45. package/.next/server/app/api/ide/openclaw/apply/route.js.map +5 -0
  46. package/.next/server/app/api/ide/openclaw/apply/route.js.nft.json +1 -0
  47. package/.next/server/app/api/ide/openclaw/apply/route_client-reference-manifest.js +2 -0
  48. package/.next/server/app/api/ide/openclaw/available-models/route/app-paths-manifest.json +3 -0
  49. package/.next/server/app/api/ide/openclaw/available-models/route/build-manifest.json +11 -0
  50. package/.next/server/app/api/ide/openclaw/available-models/route/server-reference-manifest.json +4 -0
  51. package/.next/server/app/api/ide/openclaw/available-models/route.js +7 -0
  52. package/.next/server/app/api/ide/openclaw/available-models/route.js.map +5 -0
  53. package/.next/server/app/api/ide/openclaw/available-models/route.js.nft.json +1 -0
  54. package/.next/server/app/api/ide/openclaw/available-models/route_client-reference-manifest.js +2 -0
  55. package/.next/server/app/api/ide/openclaw/preview/route/app-paths-manifest.json +3 -0
  56. package/.next/server/app/api/ide/openclaw/preview/route/build-manifest.json +11 -0
  57. package/.next/server/app/api/ide/openclaw/preview/route/server-reference-manifest.json +4 -0
  58. package/.next/server/app/api/ide/openclaw/preview/route.js +7 -0
  59. package/.next/server/app/api/ide/openclaw/preview/route.js.map +5 -0
  60. package/.next/server/app/api/ide/openclaw/preview/route.js.nft.json +1 -0
  61. package/.next/server/app/api/ide/openclaw/preview/route_client-reference-manifest.js +2 -0
  62. package/.next/server/app/api/ide/openclaw/restore/route/app-paths-manifest.json +3 -0
  63. package/.next/server/app/api/ide/openclaw/restore/route/build-manifest.json +11 -0
  64. package/.next/server/app/api/ide/openclaw/restore/route/server-reference-manifest.json +4 -0
  65. package/.next/server/app/api/ide/openclaw/restore/route.js +6 -0
  66. package/.next/server/app/api/ide/openclaw/restore/route.js.map +5 -0
  67. package/.next/server/app/api/ide/openclaw/restore/route.js.nft.json +1 -0
  68. package/.next/server/app/api/ide/openclaw/restore/route_client-reference-manifest.js +2 -0
  69. package/.next/server/app/api/ide/openclaw/save/route/app-paths-manifest.json +3 -0
  70. package/.next/server/app/api/ide/openclaw/save/route/build-manifest.json +11 -0
  71. package/.next/server/app/api/ide/openclaw/save/route/server-reference-manifest.json +4 -0
  72. package/.next/server/app/api/ide/openclaw/save/route.js +7 -0
  73. package/.next/server/app/api/ide/openclaw/save/route.js.map +5 -0
  74. package/.next/server/app/api/ide/openclaw/save/route.js.nft.json +1 -0
  75. package/.next/server/app/api/ide/openclaw/save/route_client-reference-manifest.js +2 -0
  76. package/.next/server/app/api/ide/openclaw/status/route/app-paths-manifest.json +3 -0
  77. package/.next/server/app/api/ide/openclaw/status/route/build-manifest.json +11 -0
  78. package/.next/server/app/api/ide/openclaw/status/route/server-reference-manifest.json +4 -0
  79. package/.next/server/app/api/ide/openclaw/status/route.js +7 -0
  80. package/.next/server/app/api/ide/openclaw/status/route.js.map +5 -0
  81. package/.next/server/app/api/ide/openclaw/status/route.js.nft.json +1 -0
  82. package/.next/server/app/api/ide/openclaw/status/route_client-reference-manifest.js +2 -0
  83. package/.next/server/app/api/ide/openclaw/test/route/app-paths-manifest.json +3 -0
  84. package/.next/server/app/api/ide/openclaw/test/route/build-manifest.json +11 -0
  85. package/.next/server/app/api/ide/openclaw/test/route/server-reference-manifest.json +4 -0
  86. package/.next/server/app/api/ide/openclaw/test/route.js +6 -0
  87. package/.next/server/app/api/ide/openclaw/test/route.js.map +5 -0
  88. package/.next/server/app/api/ide/openclaw/test/route.js.nft.json +1 -0
  89. package/.next/server/app/api/ide/openclaw/test/route_client-reference-manifest.js +2 -0
  90. package/.next/server/app/api/ide/opencode/apply/route/app-paths-manifest.json +3 -0
  91. package/.next/server/app/api/ide/opencode/apply/route/build-manifest.json +11 -0
  92. package/.next/server/app/api/ide/opencode/apply/route/server-reference-manifest.json +4 -0
  93. package/.next/server/app/api/ide/opencode/apply/route.js +7 -0
  94. package/.next/server/app/api/ide/opencode/apply/route.js.map +5 -0
  95. package/.next/server/app/api/ide/opencode/apply/route.js.nft.json +1 -0
  96. package/.next/server/app/api/ide/opencode/apply/route_client-reference-manifest.js +2 -0
  97. package/.next/server/app/api/ide/opencode/available-models/route/app-paths-manifest.json +3 -0
  98. package/.next/server/app/api/ide/opencode/available-models/route/build-manifest.json +11 -0
  99. package/.next/server/app/api/ide/opencode/available-models/route/server-reference-manifest.json +4 -0
  100. package/.next/server/app/api/ide/opencode/available-models/route.js +7 -0
  101. package/.next/server/app/api/ide/opencode/available-models/route.js.map +5 -0
  102. package/.next/server/app/api/ide/opencode/available-models/route.js.nft.json +1 -0
  103. package/.next/server/app/api/ide/opencode/available-models/route_client-reference-manifest.js +2 -0
  104. package/.next/server/app/api/ide/opencode/preview/route/app-paths-manifest.json +3 -0
  105. package/.next/server/app/api/ide/opencode/preview/route/build-manifest.json +11 -0
  106. package/.next/server/app/api/ide/opencode/preview/route/server-reference-manifest.json +4 -0
  107. package/.next/server/app/api/ide/opencode/preview/route.js +7 -0
  108. package/.next/server/app/api/ide/opencode/preview/route.js.map +5 -0
  109. package/.next/server/app/api/ide/opencode/preview/route.js.nft.json +1 -0
  110. package/.next/server/app/api/ide/opencode/preview/route_client-reference-manifest.js +2 -0
  111. package/.next/server/app/api/ide/opencode/restore/route/app-paths-manifest.json +3 -0
  112. package/.next/server/app/api/ide/opencode/restore/route/build-manifest.json +11 -0
  113. package/.next/server/app/api/ide/opencode/restore/route/server-reference-manifest.json +4 -0
  114. package/.next/server/app/api/ide/opencode/restore/route.js +6 -0
  115. package/.next/server/app/api/ide/opencode/restore/route.js.map +5 -0
  116. package/.next/server/app/api/ide/opencode/restore/route.js.nft.json +1 -0
  117. package/.next/server/app/api/ide/opencode/restore/route_client-reference-manifest.js +2 -0
  118. package/.next/server/app/api/ide/opencode/save/route/app-paths-manifest.json +3 -0
  119. package/.next/server/app/api/ide/opencode/save/route/build-manifest.json +11 -0
  120. package/.next/server/app/api/ide/opencode/save/route/server-reference-manifest.json +4 -0
  121. package/.next/server/app/api/ide/opencode/save/route.js +7 -0
  122. package/.next/server/app/api/ide/opencode/save/route.js.map +5 -0
  123. package/.next/server/app/api/ide/opencode/save/route.js.nft.json +1 -0
  124. package/.next/server/app/api/ide/opencode/save/route_client-reference-manifest.js +2 -0
  125. package/.next/server/app/api/ide/opencode/status/route/app-paths-manifest.json +3 -0
  126. package/.next/server/app/api/ide/opencode/status/route/build-manifest.json +11 -0
  127. package/.next/server/app/api/ide/opencode/status/route/server-reference-manifest.json +4 -0
  128. package/.next/server/app/api/ide/opencode/status/route.js +7 -0
  129. package/.next/server/app/api/ide/opencode/status/route.js.map +5 -0
  130. package/.next/server/app/api/ide/opencode/status/route.js.nft.json +1 -0
  131. package/.next/server/app/api/ide/opencode/status/route_client-reference-manifest.js +2 -0
  132. package/.next/server/app/api/ide/opencode/test/route/app-paths-manifest.json +3 -0
  133. package/.next/server/app/api/ide/opencode/test/route/build-manifest.json +11 -0
  134. package/.next/server/app/api/ide/opencode/test/route/server-reference-manifest.json +4 -0
  135. package/.next/server/app/api/ide/opencode/test/route.js +6 -0
  136. package/.next/server/app/api/ide/opencode/test/route.js.map +5 -0
  137. package/.next/server/app/api/ide/opencode/test/route.js.nft.json +1 -0
  138. package/.next/server/app/api/ide/opencode/test/route_client-reference-manifest.js +2 -0
  139. package/.next/server/app/api/logs/route.js.nft.json +1 -1
  140. package/.next/server/app/api/models/route.js.nft.json +1 -1
  141. package/.next/server/app/api/providers/route.js.nft.json +1 -1
  142. package/.next/server/app/api/providers/test/route.js.nft.json +1 -1
  143. package/.next/server/app/api/service/force-stop/route.js.nft.json +1 -1
  144. package/.next/server/app/api/service/start/route.js.nft.json +1 -1
  145. package/.next/server/app/api/service/status/route.js.nft.json +1 -1
  146. package/.next/server/app/api/service/stop/route.js.nft.json +1 -1
  147. package/.next/server/app/ide/page.js +2 -2
  148. package/.next/server/app/ide/page.js.nft.json +1 -1
  149. package/.next/server/app/ide/page_client-reference-manifest.js +1 -1
  150. package/.next/server/app/ide.html +1 -1
  151. package/.next/server/app/ide.rsc +3 -3
  152. package/.next/server/app/ide.segments/_full.segment.rsc +3 -3
  153. package/.next/server/app/ide.segments/_head.segment.rsc +1 -1
  154. package/.next/server/app/ide.segments/_index.segment.rsc +2 -2
  155. package/.next/server/app/ide.segments/_tree.segment.rsc +2 -2
  156. package/.next/server/app/ide.segments/ide/__PAGE__.segment.rsc +2 -2
  157. package/.next/server/app/ide.segments/ide.segment.rsc +1 -1
  158. package/.next/server/app/index.html +1 -1
  159. package/.next/server/app/index.rsc +2 -2
  160. package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  161. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  162. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  163. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  164. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  165. package/.next/server/app/logs/page.js +2 -2
  166. package/.next/server/app/logs/page.js.nft.json +1 -1
  167. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  168. package/.next/server/app/logs.html +1 -1
  169. package/.next/server/app/logs.rsc +3 -3
  170. package/.next/server/app/logs.segments/_full.segment.rsc +3 -3
  171. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  172. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  173. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  174. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +2 -2
  175. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  176. package/.next/server/app/models/page.js +2 -2
  177. package/.next/server/app/models/page.js.nft.json +1 -1
  178. package/.next/server/app/models/page_client-reference-manifest.js +1 -1
  179. package/.next/server/app/models.html +1 -1
  180. package/.next/server/app/models.rsc +2 -2
  181. package/.next/server/app/models.segments/_full.segment.rsc +2 -2
  182. package/.next/server/app/models.segments/_head.segment.rsc +1 -1
  183. package/.next/server/app/models.segments/_index.segment.rsc +2 -2
  184. package/.next/server/app/models.segments/_tree.segment.rsc +2 -2
  185. package/.next/server/app/models.segments/models/__PAGE__.segment.rsc +1 -1
  186. package/.next/server/app/models.segments/models.segment.rsc +1 -1
  187. package/.next/server/app/page.js +2 -2
  188. package/.next/server/app/page.js.nft.json +1 -1
  189. package/.next/server/app/page_client-reference-manifest.js +1 -1
  190. package/.next/server/app/providers/page.js +2 -2
  191. package/.next/server/app/providers/page.js.nft.json +1 -1
  192. package/.next/server/app/providers/page_client-reference-manifest.js +1 -1
  193. package/.next/server/app/providers.html +1 -1
  194. package/.next/server/app/providers.rsc +2 -2
  195. package/.next/server/app/providers.segments/_full.segment.rsc +2 -2
  196. package/.next/server/app/providers.segments/_head.segment.rsc +1 -1
  197. package/.next/server/app/providers.segments/_index.segment.rsc +2 -2
  198. package/.next/server/app/providers.segments/_tree.segment.rsc +2 -2
  199. package/.next/server/app/providers.segments/providers/__PAGE__.segment.rsc +1 -1
  200. package/.next/server/app/providers.segments/providers.segment.rsc +1 -1
  201. package/.next/server/app-paths-manifest.json +14 -0
  202. package/.next/server/chunks/[root-of-the-server]__001d5756._.js +3 -0
  203. package/.next/server/chunks/[root-of-the-server]__001d5756._.js.map +1 -0
  204. package/.next/server/chunks/[root-of-the-server]__05f8578b._.js +3 -0
  205. package/.next/server/chunks/[root-of-the-server]__05f8578b._.js.map +1 -0
  206. package/.next/server/chunks/[root-of-the-server]__3a204d25._.js +1 -1
  207. package/.next/server/chunks/[root-of-the-server]__3a204d25._.js.map +1 -1
  208. package/.next/server/chunks/[root-of-the-server]__43810962._.js +3 -0
  209. package/.next/server/chunks/[root-of-the-server]__43810962._.js.map +1 -0
  210. package/.next/server/chunks/[root-of-the-server]__4a8f9bc7._.js +1 -1
  211. package/.next/server/chunks/[root-of-the-server]__4a8f9bc7._.js.map +1 -1
  212. package/.next/server/chunks/[root-of-the-server]__55cd88b8._.js +3 -0
  213. package/.next/server/chunks/[root-of-the-server]__55cd88b8._.js.map +1 -0
  214. package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js +1 -1
  215. package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js.map +1 -1
  216. package/.next/server/chunks/[root-of-the-server]__760eaa16._.js +3 -0
  217. package/.next/server/chunks/[root-of-the-server]__760eaa16._.js.map +1 -0
  218. package/.next/server/chunks/[root-of-the-server]__7c298a19._.js +3 -0
  219. package/.next/server/chunks/[root-of-the-server]__7c298a19._.js.map +1 -0
  220. package/.next/server/chunks/[root-of-the-server]__85540228._.js +3 -0
  221. package/.next/server/chunks/[root-of-the-server]__85540228._.js.map +1 -0
  222. package/.next/server/chunks/[root-of-the-server]__94fe8d3c._.js +3 -0
  223. package/.next/server/chunks/[root-of-the-server]__94fe8d3c._.js.map +1 -0
  224. package/.next/server/chunks/[root-of-the-server]__97622908._.js +3 -0
  225. package/.next/server/chunks/[root-of-the-server]__97622908._.js.map +1 -0
  226. package/.next/server/chunks/[root-of-the-server]__a02e6618._.js +3 -0
  227. package/.next/server/chunks/[root-of-the-server]__a02e6618._.js.map +1 -0
  228. package/.next/server/chunks/[root-of-the-server]__a32a20a7._.js +3 -0
  229. package/.next/server/chunks/[root-of-the-server]__a32a20a7._.js.map +1 -0
  230. package/.next/server/chunks/[root-of-the-server]__af5b556a._.js +3 -0
  231. package/.next/server/chunks/[root-of-the-server]__af5b556a._.js.map +1 -0
  232. package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js +16 -16
  233. package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js.map +1 -1
  234. package/.next/server/chunks/[root-of-the-server]__cafe113e._.js +3 -0
  235. package/.next/server/chunks/[root-of-the-server]__cafe113e._.js.map +1 -0
  236. package/.next/server/chunks/[root-of-the-server]__dc8b0bed._.js +3 -0
  237. package/.next/server/chunks/[root-of-the-server]__dc8b0bed._.js.map +1 -0
  238. package/.next/server/chunks/[root-of-the-server]__f0461b8d._.js +1 -1
  239. package/.next/server/chunks/[root-of-the-server]__f0461b8d._.js.map +1 -1
  240. package/.next/server/chunks/[root-of-the-server]__f8949f88._.js +1 -1
  241. package/.next/server/chunks/[root-of-the-server]__f8949f88._.js.map +1 -1
  242. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_apply_route_actions_2cb9e4b4.js +3 -0
  243. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_apply_route_actions_2cb9e4b4.js.map +1 -0
  244. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_preview_route_actions_9814a8e4.js +3 -0
  245. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_preview_route_actions_9814a8e4.js.map +1 -0
  246. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_restore_route_actions_10ad8f9d.js +3 -0
  247. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_restore_route_actions_10ad8f9d.js.map +1 -0
  248. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_save_route_actions_044ad081.js +3 -0
  249. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_save_route_actions_044ad081.js.map +1 -0
  250. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_status_route_actions_ed9786d2.js +3 -0
  251. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_status_route_actions_ed9786d2.js.map +1 -0
  252. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_test_route_actions_ce2cb808.js +3 -0
  253. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_test_route_actions_ce2cb808.js.map +1 -0
  254. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_apply_route_actions_6c422244.js +3 -0
  255. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_apply_route_actions_6c422244.js.map +1 -0
  256. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_preview_route_actions_256c82e0.js +3 -0
  257. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_preview_route_actions_256c82e0.js.map +1 -0
  258. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_restore_route_actions_371993d3.js +3 -0
  259. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_restore_route_actions_371993d3.js.map +1 -0
  260. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_save_route_actions_6e4c9c41.js +3 -0
  261. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_save_route_actions_6e4c9c41.js.map +1 -0
  262. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_status_route_actions_498ad77b.js +3 -0
  263. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_status_route_actions_498ad77b.js.map +1 -0
  264. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_test_route_actions_c71be510.js +3 -0
  265. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_test_route_actions_c71be510.js.map +1 -0
  266. package/.next/server/chunks/ce889_server_app_api_ide_openclaw_available-models_route_actions_e568e70b.js +3 -0
  267. package/.next/server/chunks/ce889_server_app_api_ide_openclaw_available-models_route_actions_e568e70b.js.map +1 -0
  268. package/.next/server/chunks/ce889_server_app_api_ide_opencode_available-models_route_actions_95230db3.js +3 -0
  269. package/.next/server/chunks/ce889_server_app_api_ide_opencode_available-models_route_actions_95230db3.js.map +1 -0
  270. package/.next/server/chunks/ssr/{[root-of-the-server]__bec95712._.js → [root-of-the-server]__81937253._.js} +2 -2
  271. package/.next/server/chunks/ssr/{[root-of-the-server]__bec95712._.js.map → [root-of-the-server]__81937253._.js.map} +1 -1
  272. package/.next/server/chunks/ssr/{[root-of-the-server]__71c85955._.js → [root-of-the-server]__976ad963._.js} +2 -2
  273. package/.next/server/chunks/ssr/{[root-of-the-server]__71c85955._.js.map → [root-of-the-server]__976ad963._.js.map} +1 -1
  274. package/.next/server/chunks/ssr/_69468f4c._.js +3 -0
  275. package/.next/server/chunks/ssr/_69468f4c._.js.map +1 -0
  276. package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js +1 -1
  277. package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js.map +1 -1
  278. package/.next/server/chunks/ssr/src_app_logs_page_tsx_7b7b7b83._.js +1 -1
  279. package/.next/server/chunks/ssr/src_app_logs_page_tsx_7b7b7b83._.js.map +1 -1
  280. package/.next/server/pages/404.html +1 -1
  281. package/.next/server/pages/500.html +2 -2
  282. package/.next/server/server-reference-manifest.js +1 -1
  283. package/.next/server/server-reference-manifest.json +1 -1
  284. package/.next/static/chunks/64f547b3bcd3aef4.js +1 -0
  285. package/.next/static/chunks/6992685fe009e8fd.css +1 -0
  286. package/.next/static/chunks/{b6b258e8582e47c4.js → 7c8b7cbb3339f139.js} +1 -1
  287. package/.next/static/chunks/8ccc14e022ff6de3.js +1 -0
  288. package/.next/types/routes.d.ts +15 -1
  289. package/.next/types/validator.ts +126 -0
  290. package/README.md +31 -4
  291. package/dist/.next/dev/types/validator.js +56 -0
  292. package/dist/.next/types/validator.js +56 -0
  293. package/dist/package.json +55 -0
  294. package/dist/src/app/api/config/route.js +17 -0
  295. package/dist/src/app/api/ide/claude/apply/route.js +126 -31
  296. package/dist/src/app/api/ide/claude/restore/route.js +30 -0
  297. package/dist/src/app/api/ide/claude/save/route.js +79 -0
  298. package/dist/src/app/api/ide/claude/status/route.js +6 -1
  299. package/dist/src/app/api/ide/openclaw/apply/route.js +92 -0
  300. package/dist/src/app/api/ide/openclaw/available-models/route.js +46 -0
  301. package/dist/src/app/api/ide/openclaw/build-config.js +101 -0
  302. package/dist/src/app/api/ide/openclaw/preview/route.js +49 -0
  303. package/dist/src/app/api/ide/openclaw/restore/route.js +24 -0
  304. package/dist/src/app/api/ide/openclaw/save/route.js +54 -0
  305. package/dist/src/app/api/ide/openclaw/status/route.js +139 -0
  306. package/dist/src/app/api/ide/openclaw/test/route.js +158 -0
  307. package/dist/src/app/api/ide/opencode/apply/route.js +89 -0
  308. package/dist/src/app/api/ide/opencode/available-models/route.js +46 -0
  309. package/dist/src/app/api/ide/opencode/build-config.js +54 -0
  310. package/dist/src/app/api/ide/opencode/preview/route.js +36 -0
  311. package/dist/src/app/api/ide/opencode/restore/route.js +40 -0
  312. package/dist/src/app/api/ide/opencode/save/route.js +123 -0
  313. package/dist/src/app/api/ide/opencode/status/route.js +106 -0
  314. package/dist/src/app/api/ide/opencode/test/route.js +136 -0
  315. package/dist/src/app/components/Footer.js +11 -0
  316. package/dist/src/app/ide/page.js +651 -81
  317. package/dist/src/app/layout.js +5 -1
  318. package/dist/src/app/logs/page.js +4 -2
  319. package/dist/src/db/database.js +44 -8
  320. package/package.json +1 -1
  321. package/src/app/api/config/route.ts +17 -0
  322. package/src/app/api/ide/claude/apply/route.ts +140 -31
  323. package/src/app/api/ide/claude/restore/route.ts +36 -1
  324. package/src/app/api/ide/claude/save/route.ts +91 -1
  325. package/src/app/api/ide/claude/status/route.ts +12 -2
  326. package/src/app/api/ide/openclaw/apply/route.ts +103 -0
  327. package/src/app/api/ide/openclaw/available-models/route.ts +59 -0
  328. package/src/app/api/ide/openclaw/build-config.ts +152 -0
  329. package/src/app/api/ide/openclaw/preview/route.ts +57 -0
  330. package/src/app/api/ide/openclaw/restore/route.ts +27 -0
  331. package/src/app/api/ide/openclaw/save/route.ts +67 -0
  332. package/src/app/api/ide/openclaw/status/route.ts +178 -0
  333. package/src/app/api/ide/openclaw/test/route.ts +194 -0
  334. package/src/app/api/ide/opencode/apply/route.ts +92 -0
  335. package/src/app/api/ide/opencode/available-models/route.ts +59 -0
  336. package/src/app/api/ide/opencode/build-config.ts +69 -0
  337. package/src/app/api/ide/opencode/preview/route.ts +40 -0
  338. package/src/app/api/ide/opencode/restore/route.ts +52 -0
  339. package/src/app/api/ide/opencode/save/route.ts +131 -0
  340. package/src/app/api/ide/opencode/status/route.ts +128 -0
  341. package/src/app/api/ide/opencode/test/route.ts +145 -0
  342. package/src/app/components/Footer.tsx +9 -0
  343. package/src/app/globals.css +17 -0
  344. package/src/app/ide/page.tsx +1587 -118
  345. package/src/app/layout.tsx +3 -1
  346. package/src/app/logs/page.tsx +4 -2
  347. package/src/db/database.ts +55 -8
  348. package/.next/server/chunks/ssr/src_app_b2b1d928._.js +0 -3
  349. package/.next/server/chunks/ssr/src_app_b2b1d928._.js.map +0 -1
  350. package/.next/static/chunks/6418ca50028376b7.css +0 -1
  351. package/.next/static/chunks/9ec3b97741b6575e.js +0 -1
  352. /package/.next/static/{ryTeHAYUvjT1bYolc-x9Z → cf2SWIkI5HVbnDjLExI42}/_buildManifest.js +0 -0
  353. /package/.next/static/{ryTeHAYUvjT1bYolc-x9Z → cf2SWIkI5HVbnDjLExI42}/_clientMiddlewareManifest.json +0 -0
  354. /package/.next/static/{ryTeHAYUvjT1bYolc-x9Z → cf2SWIkI5HVbnDjLExI42}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState, useEffect, useRef } from 'react';
3
+ import { useState, useEffect, useRef, useMemo } from 'react';
4
4
  import Nav from '../components/Nav';
5
5
  import { useToast } from '../components/ToastProvider';
6
6
 
@@ -31,7 +31,50 @@ interface ConfigStatus {
31
31
  opus?: string;
32
32
  default?: string;
33
33
  reasoning?: string;
34
- }; // 临时配置模型映射
34
+ };
35
+ config?: Record<string, unknown>; // 当前配置文件完整内容,非 AAR 时用于展示
36
+ /** 当前网关地址(来自数据库),用于预览 */
37
+ currentGatewayAddress?: string;
38
+ /** 当前网关 API Key(来自数据库,未脱敏),用于预览 */
39
+ currentGatewayApiKey?: string;
40
+ }
41
+
42
+ interface OpenCodeStatus {
43
+ applied: boolean;
44
+ defaultModel?: string;
45
+ gatewayAddress?: string;
46
+ apiKey?: string;
47
+ lastUpdated?: string | null;
48
+ backupExists: boolean;
49
+ matchCurrentGateway?: boolean;
50
+ routerProvider?: string;
51
+ tempDefaultModel?: string;
52
+ providerName?: string;
53
+ modelsCount?: number;
54
+ /** 当前磁盘上 opencode.json 的完整内容,用于「生效中」展示 */
55
+ config?: Record<string, unknown> | null;
56
+ }
57
+
58
+ interface OpenClawStatus {
59
+ applied: boolean;
60
+ defaultModel?: string;
61
+ defaultFallbacks?: string[];
62
+ imageModel?: string;
63
+ imageFallbacks?: string[];
64
+ gatewayAddress?: string;
65
+ apiKey?: string;
66
+ lastUpdated?: string | null;
67
+ backupExists: boolean;
68
+ matchCurrentGateway?: boolean;
69
+ routerProvider?: string;
70
+ tempDefaultModel?: string;
71
+ tempDefaultFallbacks?: string[];
72
+ tempImageModel?: string;
73
+ tempImageFallbacks?: string[];
74
+ providerName?: string;
75
+ modelsCount?: number;
76
+ /** 当前磁盘上 openclaw.json 的完整内容,用于「生效中」展示 */
77
+ config?: Record<string, unknown> | null;
35
78
  }
36
79
 
37
80
  interface ModelMappingInput {
@@ -52,6 +95,7 @@ export default function IDEConfigPage() {
52
95
  const [reasoningModel, setReasoningModel] = useState('GLM-4.7');
53
96
  const [enabled, setEnabled] = useState(false);
54
97
  const [showPreview, setShowPreview] = useState(false);
98
+ const [claudeConfigSubTab, setClaudeConfigSubTab] = useState<'current' | 'preview'>('preview');
55
99
  const [applying, setApplying] = useState(false);
56
100
  const [restoring, setRestoring] = useState(false);
57
101
  const [testing, setTesting] = useState(false);
@@ -60,10 +104,62 @@ export default function IDEConfigPage() {
60
104
  const [models, setModels] = useState<Record<string, Model[]>>({});
61
105
  const [loading, setLoading] = useState(true);
62
106
  const [testResult, setTestResult] = useState<null | any>(null);
107
+
108
+ // OpenCode
109
+ const [opencodeDefaultModel, setOpencodeDefaultModel] = useState('GLM-4.7');
110
+ const [opencodeStatus, setOpencodeStatus] = useState<OpenCodeStatus | null>(null);
111
+ const [opencodeApplying, setOpencodeApplying] = useState(false);
112
+ const [opencodeRestoring, setOpencodeRestoring] = useState(false);
113
+ const [opencodeTesting, setOpencodeTesting] = useState(false);
114
+ const [opencodeSaving, setOpencodeSaving] = useState(false);
115
+ const [opencodeTestResult, setOpencodeTestResult] = useState<null | any>(null);
116
+ const [opencodeModels, setOpencodeModels] = useState<Record<string, Model[]>>({});
117
+ const [opencodePreviewConfig, setOpencodePreviewConfig] = useState<Record<string, unknown> | null>(null);
118
+ const [opencodePreviewText, setOpencodePreviewText] = useState<string>('');
119
+ const [opencodePreviewLoading, setOpencodePreviewLoading] = useState(false);
120
+ const [opencodeConfigSubTab, setOpencodeConfigSubTab] = useState<'current' | 'preview'>('current');
121
+
122
+ // OpenClaw
123
+ const [openclawDefaultModel, setOpenclawDefaultModel] = useState('GLM-4.7');
124
+ const [openclawDefaultFallbacks, setOpenclawDefaultFallbacks] = useState<string[]>([]);
125
+ const [openclawImageModel, setOpenclawImageModel] = useState('');
126
+ const [openclawImageFallbacks, setOpenclawImageFallbacks] = useState<string[]>([]);
127
+ const [openclawStatus, setOpenclawStatus] = useState<OpenClawStatus | null>(null);
128
+ const [openclawApplying, setOpenclawApplying] = useState(false);
129
+ const [openclawRestoring, setOpenclawRestoring] = useState(false);
130
+ const [openclawTesting, setOpenclawTesting] = useState(false);
131
+ const [openclawSaving, setOpenclawSaving] = useState(false);
132
+ const [openclawTestResult, setOpenclawTestResult] = useState<null | any>(null);
133
+ const [openclawModels, setOpenclawModels] = useState<Record<string, Model[]>>({});
134
+ const [openclawPreviewConfig, setOpenclawPreviewConfig] = useState<Record<string, unknown> | null>(null);
135
+ const [openclawPreviewText, setOpenclawPreviewText] = useState<string>('');
136
+ const [openclawPreviewLoading, setOpenclawPreviewLoading] = useState(false);
137
+ const [openClawDropdown, setOpenClawDropdown] = useState<'default' | 'defaultFallbacks' | 'image' | 'imageFallbacks' | null>(null);
138
+ const [openClawSearch, setOpenClawSearch] = useState('');
139
+ const [openclawConfigSubTab, setOpenclawConfigSubTab] = useState<'current' | 'preview'>('preview');
140
+ const openClawDropdownRef = useRef<HTMLDivElement | null>(null);
141
+
63
142
  const { showToast } = useToast();
64
143
 
144
+ const openclawModelsFlat = useMemo(() =>
145
+ Object.entries(openclawModels).flatMap(([provider, arr]) =>
146
+ arr.map((m) => ({ ...m, provider } as Model & { provider: string }))
147
+ ),
148
+ [openclawModels]
149
+ );
150
+ const openclawModelsFiltered = useMemo(() => {
151
+ const q = openClawSearch.trim().toLowerCase();
152
+ if (!q) return openclawModelsFlat;
153
+ return openclawModelsFlat.filter(
154
+ (m) =>
155
+ m.name.toLowerCase().includes(q) || m.model_id.toLowerCase().includes(q)
156
+ );
157
+ }, [openclawModelsFlat, openClawSearch]);
158
+
65
159
  // Abort controller for cancelling test
66
160
  const abortControllerRef = useRef<AbortController | null>(null);
161
+ const opencodeAbortRef = useRef<AbortController | null>(null);
162
+ const openclawAbortRef = useRef<AbortController | null>(null);
67
163
 
68
164
  const loadStatus = async () => {
69
165
  try {
@@ -100,11 +196,226 @@ export default function IDEConfigPage() {
100
196
  }
101
197
  };
102
198
 
199
+ const loadOpenCodeStatus = async () => {
200
+ try {
201
+ const res = await fetch('/api/ide/opencode/status');
202
+ const data: OpenCodeStatus = await res.json();
203
+ setOpencodeStatus(data);
204
+ const toUse = (data.matchCurrentGateway === false && data.tempDefaultModel)
205
+ ? data.tempDefaultModel
206
+ : data.defaultModel;
207
+ if (toUse) setOpencodeDefaultModel(toUse);
208
+ else if (data.defaultModel) setOpencodeDefaultModel(data.defaultModel);
209
+ } catch (error) {
210
+ console.error('Failed to load OpenCode status:', error);
211
+ }
212
+ };
213
+
214
+ const loadOpenCodeModels = async () => {
215
+ try {
216
+ const res = await fetch('/api/ide/opencode/available-models');
217
+ const data = await res.json();
218
+ setOpencodeModels(data.models || {});
219
+ } catch (error) {
220
+ console.error('Failed to load OpenCode models:', error);
221
+ }
222
+ };
223
+
224
+ const loadOpenClawStatus = async () => {
225
+ try {
226
+ const res = await fetch('/api/ide/openclaw/status');
227
+ const data: OpenClawStatus = await res.json();
228
+ setOpenclawStatus(data);
229
+
230
+ // Determine which values to use (temp or applied)
231
+ const useTemp = data.matchCurrentGateway === false && data.tempDefaultModel;
232
+
233
+ if (useTemp) {
234
+ setOpenclawDefaultModel(data.tempDefaultModel || 'GLM-4.7');
235
+ setOpenclawDefaultFallbacks(data.tempDefaultFallbacks || []);
236
+ setOpenclawImageModel(data.tempImageModel || '');
237
+ setOpenclawImageFallbacks(data.tempImageFallbacks || []);
238
+ } else {
239
+ setOpenclawDefaultModel(data.defaultModel || 'GLM-4.7');
240
+ setOpenclawDefaultFallbacks(data.defaultFallbacks || []);
241
+ setOpenclawImageModel(data.imageModel || '');
242
+ setOpenclawImageFallbacks(data.imageFallbacks || []);
243
+ }
244
+ } catch (error) {
245
+ console.error('Failed to load OpenClaw status:', error);
246
+ }
247
+ };
248
+
249
+ const loadOpenClawModels = async () => {
250
+ try {
251
+ const res = await fetch('/api/ide/openclaw/available-models');
252
+ const data = await res.json();
253
+ setOpenclawModels(data.models || {});
254
+ } catch (error) {
255
+ console.error('Failed to load OpenClaw models:', error);
256
+ }
257
+ };
258
+
103
259
  useEffect(() => {
104
260
  loadStatus();
105
261
  loadModels();
262
+ loadOpenCodeStatus();
263
+ loadOpenCodeModels();
264
+ loadOpenClawStatus();
265
+ loadOpenClawModels();
106
266
  }, []);
107
267
 
268
+ // OpenCode Preview
269
+ useEffect(() => {
270
+ if (activeTab !== 'opencode' || !opencodeDefaultModel) {
271
+ setOpencodePreviewConfig(null);
272
+ setOpencodePreviewText('');
273
+ return;
274
+ }
275
+ let cancelled = false;
276
+ setOpencodePreviewLoading(true);
277
+ setOpencodePreviewConfig(null);
278
+ setOpencodePreviewText('');
279
+ fetch(`/api/ide/opencode/preview?default_model=${encodeURIComponent(opencodeDefaultModel)}`)
280
+ .then((res) => res.json())
281
+ .then((data) => {
282
+ if (!cancelled && data.config) {
283
+ setOpencodePreviewConfig(data.config);
284
+ setOpencodePreviewText(JSON.stringify(data.config, null, 2));
285
+ }
286
+ })
287
+ .catch(() => {
288
+ if (!cancelled) setOpencodePreviewConfig(null);
289
+ if (!cancelled) setOpencodePreviewText('');
290
+ })
291
+ .finally(() => {
292
+ if (!cancelled) setOpencodePreviewLoading(false);
293
+ });
294
+ return () => {
295
+ cancelled = true;
296
+ };
297
+ }, [activeTab, opencodeDefaultModel]);
298
+
299
+ // OpenClaw Preview
300
+ useEffect(() => {
301
+ if (activeTab !== 'openclaw' || !openclawDefaultModel) {
302
+ setOpenclawPreviewConfig(null);
303
+ setOpenclawPreviewText('');
304
+ return;
305
+ }
306
+ let cancelled = false;
307
+ setOpenclawPreviewLoading(true);
308
+ setOpenclawPreviewConfig(null);
309
+ setOpenclawPreviewText('');
310
+
311
+ const params = new URLSearchParams({
312
+ default_model: openclawDefaultModel,
313
+ default_fallbacks: openclawDefaultFallbacks.join(','),
314
+ });
315
+ if (openclawImageModel) {
316
+ params.set('image_model', openclawImageModel);
317
+ if (openclawImageFallbacks.length > 0) {
318
+ params.set('image_fallbacks', openclawImageFallbacks.join(','));
319
+ }
320
+ }
321
+ fetch(`/api/ide/openclaw/preview?${params.toString()}`)
322
+ .then((res) => res.json())
323
+ .then((data) => {
324
+ if (!cancelled && data.config) {
325
+ setOpenclawPreviewConfig(data.config);
326
+ setOpenclawPreviewText(JSON.stringify(data.config, null, 2));
327
+ }
328
+ })
329
+ .catch(() => {
330
+ if (!cancelled) setOpenclawPreviewConfig(null);
331
+ if (!cancelled) setOpenclawPreviewText('');
332
+ })
333
+ .finally(() => {
334
+ if (!cancelled) setOpenclawPreviewLoading(false);
335
+ });
336
+ return () => {
337
+ cancelled = true;
338
+ };
339
+ }, [activeTab, openclawDefaultModel, openclawDefaultFallbacks, openclawImageModel, openclawImageFallbacks]);
340
+
341
+ useEffect(() => {
342
+ if (openClawDropdown == null) return;
343
+ const onDocClick = (e: MouseEvent) => {
344
+ if (openClawDropdownRef.current && !openClawDropdownRef.current.contains(e.target as Node)) {
345
+ setOpenClawDropdown(null);
346
+ }
347
+ };
348
+ document.addEventListener('mousedown', onDocClick);
349
+ return () => document.removeEventListener('mousedown', onDocClick);
350
+ }, [openClawDropdown]);
351
+
352
+ // 同步 Claude 下拉框默认值:确保状态值在可用模型列表中
353
+ useEffect(() => {
354
+ const allModels = Object.values(models).flat();
355
+ if (allModels.length === 0) return;
356
+
357
+ const modelIds = allModels.map(m => m.model_id);
358
+ const firstModelId = allModels[0].model_id;
359
+
360
+ // 如果当前状态值不在可用模型列表中,更新为第一个可用模型
361
+ if (!modelIds.includes(haikuModel)) {
362
+ setHaikuModel(firstModelId);
363
+ }
364
+ if (!modelIds.includes(sonnetModel)) {
365
+ setSonnetModel(firstModelId);
366
+ }
367
+ if (!modelIds.includes(opusModel)) {
368
+ setOpusModel(firstModelId);
369
+ }
370
+ if (!modelIds.includes(defaultModel)) {
371
+ setDefaultModel(firstModelId);
372
+ }
373
+ if (!modelIds.includes(reasoningModel)) {
374
+ setReasoningModel(firstModelId);
375
+ }
376
+ }, [models]);
377
+
378
+ // 同步 OpenCode 下拉框默认值
379
+ useEffect(() => {
380
+ const allModels = Object.values(opencodeModels).flat();
381
+ if (allModels.length === 0) return;
382
+
383
+ const modelIds = allModels.map(m => m.model_id);
384
+ const firstModelId = allModels[0].model_id;
385
+
386
+ if (!modelIds.includes(opencodeDefaultModel)) {
387
+ setOpencodeDefaultModel(firstModelId);
388
+ }
389
+ }, [opencodeModels]);
390
+
391
+ // 同步 OpenClaw 下拉框默认值
392
+ useEffect(() => {
393
+ const allModels = Object.values(openclawModels).flat();
394
+ if (allModels.length === 0) return;
395
+
396
+ const modelIds = allModels.map(m => m.model_id);
397
+ const firstModelId = allModels[0].model_id;
398
+
399
+ if (!modelIds.includes(openclawDefaultModel)) {
400
+ setOpenclawDefaultModel(firstModelId);
401
+ }
402
+ // 过滤无效的 fallback 模型
403
+ const validFallbacks = openclawDefaultFallbacks.filter(id => modelIds.includes(id));
404
+ if (validFallbacks.length !== openclawDefaultFallbacks.length) {
405
+ setOpenclawDefaultFallbacks(validFallbacks);
406
+ }
407
+ // 检查 image model
408
+ if (openclawImageModel && !modelIds.includes(openclawImageModel)) {
409
+ setOpenclawImageModel('');
410
+ }
411
+ // 过滤无效的 image fallback 模型
412
+ const validImageFallbacks = openclawImageFallbacks.filter(id => modelIds.includes(id));
413
+ if (validImageFallbacks.length !== openclawImageFallbacks.length) {
414
+ setOpenclawImageFallbacks(validImageFallbacks);
415
+ }
416
+ }, [openclawModels]);
417
+
418
+ // Claude Handlers
108
419
  const handleApply = async () => {
109
420
  setApplying(true);
110
421
  try {
@@ -145,6 +456,7 @@ export default function IDEConfigPage() {
145
456
 
146
457
  if (data.success) {
147
458
  showToast('配置已还原', 'success');
459
+ setClaudeConfigSubTab('current');
148
460
  await loadStatus();
149
461
  } else {
150
462
  showToast('还原失败: ' + (data.error || '未知错误'), 'error');
@@ -234,6 +546,228 @@ export default function IDEConfigPage() {
234
546
  }
235
547
  };
236
548
 
549
+ // OpenCode Handlers
550
+ const opencodeEnabled = opencodeStatus?.applied && opencodeStatus?.routerProvider === 'aar';
551
+
552
+ const handleOpenCodeApply = async () => {
553
+ setOpencodeApplying(true);
554
+ try {
555
+ const res = await fetch('/api/ide/opencode/apply', {
556
+ method: 'POST',
557
+ headers: { 'Content-Type': 'application/json' },
558
+ body: JSON.stringify({
559
+ default_model: opencodeDefaultModel,
560
+ ...(opencodePreviewText.trim() ? { config: opencodePreviewText } : {}),
561
+ }),
562
+ });
563
+ const data = await res.json();
564
+ if (data.success) {
565
+ showToast(data.message || 'OpenCode 配置应用成功', 'success');
566
+ await loadOpenCodeStatus();
567
+ } else {
568
+ showToast('应用失败: ' + (data.error || '未知错误'), 'error');
569
+ }
570
+ } catch (error) {
571
+ console.error('Failed to apply OpenCode config:', error);
572
+ showToast('应用失败: 网络错误', 'error');
573
+ } finally {
574
+ setOpencodeApplying(false);
575
+ }
576
+ };
577
+
578
+ const handleOpenCodeRestore = async () => {
579
+ setOpencodeRestoring(true);
580
+ try {
581
+ const res = await fetch('/api/ide/opencode/restore', { method: 'POST' });
582
+ const data = await res.json();
583
+ if (data.success) {
584
+ showToast('OpenCode 配置已还原', 'success');
585
+ setOpencodeConfigSubTab('current');
586
+ await loadOpenCodeStatus();
587
+ } else {
588
+ showToast('还原失败: ' + (data.error || '未知错误'), 'error');
589
+ }
590
+ } catch (error) {
591
+ console.error('Failed to restore OpenCode config:', error);
592
+ showToast('还原失败: 网络错误', 'error');
593
+ } finally {
594
+ setOpencodeRestoring(false);
595
+ }
596
+ };
597
+
598
+ const handleOpenCodeTest = async () => {
599
+ if (opencodeAbortRef.current) {
600
+ opencodeAbortRef.current.abort();
601
+ opencodeAbortRef.current = null;
602
+ }
603
+ opencodeAbortRef.current = new AbortController();
604
+ setOpencodeTesting(true);
605
+ setOpencodeTestResult(null);
606
+ try {
607
+ const res = await fetch('/api/ide/opencode/test', { signal: opencodeAbortRef.current.signal });
608
+ const data = await res.json();
609
+ setOpencodeTestResult(data);
610
+ if (data.success) showToast('OpenCode 配置测试通过', 'success');
611
+ else showToast('配置验证失败: ' + (data.error || data.suggestion || '请检查配置'), 'error');
612
+ } catch (err: any) {
613
+ if (err.name === 'AbortError') showToast('测试已取消', 'info');
614
+ else {
615
+ showToast('测试失败: 网络错误', 'error');
616
+ setOpencodeTestResult({ error: 'Network error' });
617
+ }
618
+ } finally {
619
+ if (opencodeAbortRef.current?.signal.aborted === false) opencodeAbortRef.current = null;
620
+ setOpencodeTesting(false);
621
+ }
622
+ };
623
+
624
+ const handleOpenCodeSave = async () => {
625
+ setOpencodeSaving(true);
626
+ try {
627
+ const res = await fetch('/api/ide/opencode/save', {
628
+ method: 'POST',
629
+ headers: { 'Content-Type': 'application/json' },
630
+ body: JSON.stringify({
631
+ default_model: opencodeDefaultModel,
632
+ ...(opencodePreviewText.trim() ? { config: opencodePreviewText } : {}),
633
+ }),
634
+ });
635
+ const data = await res.json();
636
+ if (data.success) {
637
+ showToast(data.saveType === 'temp' ? '配置已保存(临时)' : '配置已更新到 OpenCode', 'success');
638
+ await loadOpenCodeStatus();
639
+ } else {
640
+ showToast('保存失败: ' + (data.error || '未知错误'), 'error');
641
+ }
642
+ } catch (error) {
643
+ console.error('Failed to save OpenCode config:', error);
644
+ showToast('保存失败: 网络错误', 'error');
645
+ } finally {
646
+ setOpencodeSaving(false);
647
+ }
648
+ };
649
+
650
+ const handleOpenCodeCancelTest = () => {
651
+ if (opencodeAbortRef.current) {
652
+ opencodeAbortRef.current.abort();
653
+ opencodeAbortRef.current = null;
654
+ }
655
+ };
656
+
657
+ // OpenClaw Handlers
658
+ const openclawEnabled = openclawStatus?.applied && openclawStatus?.routerProvider === 'aar';
659
+
660
+ const handleOpenClawApply = async () => {
661
+ setOpenclawApplying(true);
662
+ try {
663
+ const res = await fetch('/api/ide/openclaw/apply', {
664
+ method: 'POST',
665
+ headers: { 'Content-Type': 'application/json' },
666
+ body: JSON.stringify({
667
+ default_model: openclawDefaultModel,
668
+ default_fallbacks: openclawDefaultFallbacks,
669
+ image_model: openclawImageModel || undefined,
670
+ image_fallbacks: openclawImageFallbacks,
671
+ ...(openclawPreviewText.trim() ? { config: openclawPreviewText } : {}),
672
+ }),
673
+ });
674
+ const data = await res.json();
675
+ if (data.success) {
676
+ showToast(data.message || 'OpenClaw 配置应用成功', 'success');
677
+ await loadOpenClawStatus();
678
+ } else {
679
+ showToast('应用失败: ' + (data.error || '未知错误'), 'error');
680
+ }
681
+ } catch (error) {
682
+ console.error('Failed to apply OpenClaw config:', error);
683
+ showToast('应用失败: 网络错误', 'error');
684
+ } finally {
685
+ setOpenclawApplying(false);
686
+ }
687
+ };
688
+
689
+ const handleOpenClawRestore = async () => {
690
+ setOpenclawRestoring(true);
691
+ try {
692
+ const res = await fetch('/api/ide/openclaw/restore', { method: 'POST' });
693
+ const data = await res.json();
694
+ if (data.success) {
695
+ showToast('OpenClaw 配置已还原', 'success');
696
+ setOpenclawConfigSubTab('current');
697
+ await loadOpenClawStatus();
698
+ } else {
699
+ showToast('还原失败: ' + (data.error || '未知错误'), 'error');
700
+ }
701
+ } catch (error) {
702
+ console.error('Failed to restore OpenClaw config:', error);
703
+ showToast('还原失败: 网络错误', 'error');
704
+ } finally {
705
+ setOpenclawRestoring(false);
706
+ }
707
+ };
708
+
709
+ const handleOpenClawTest = async () => {
710
+ if (openclawAbortRef.current) {
711
+ openclawAbortRef.current.abort();
712
+ openclawAbortRef.current = null;
713
+ }
714
+ openclawAbortRef.current = new AbortController();
715
+ setOpenclawTesting(true);
716
+ setOpenclawTestResult(null);
717
+ try {
718
+ const res = await fetch('/api/ide/openclaw/test', { signal: openclawAbortRef.current.signal });
719
+ const data = await res.json();
720
+ setOpenclawTestResult(data);
721
+ if (data.success) showToast('OpenClaw 配置测试通过', 'success');
722
+ else showToast('配置验证失败: ' + (data.error || data.suggestion || '请检查配置'), 'error');
723
+ } catch (err: any) {
724
+ if (err.name === 'AbortError') showToast('测试已取消', 'info');
725
+ else {
726
+ showToast('测试失败: 网络错误', 'error');
727
+ setOpenclawTestResult({ error: 'Network error' });
728
+ }
729
+ } finally {
730
+ if (openclawAbortRef.current?.signal.aborted === false) openclawAbortRef.current = null;
731
+ setOpenclawTesting(false);
732
+ }
733
+ };
734
+
735
+ const handleOpenClawSave = async () => {
736
+ setOpenclawSaving(true);
737
+ try {
738
+ const res = await fetch('/api/ide/openclaw/save', {
739
+ method: 'POST',
740
+ headers: { 'Content-Type': 'application/json' },
741
+ body: JSON.stringify({
742
+ default_model: openclawDefaultModel,
743
+ default_fallbacks: openclawDefaultFallbacks,
744
+ image_model: openclawImageModel || undefined,
745
+ image_fallbacks: openclawImageFallbacks,
746
+ ...(openclawPreviewText.trim() ? { config: openclawPreviewText } : {}),
747
+ }),
748
+ });
749
+ const data = await res.json();
750
+ if (data.success) {
751
+ showToast(data.saveType === 'temp' ? '配置已保存(临时)' : '配置已更新到 OpenClaw', 'success');
752
+ await loadOpenClawStatus();
753
+ } else {
754
+ showToast('保存失败: ' + (data.error || '未知错误'), 'error');
755
+ }
756
+ } catch (error) {
757
+ console.error('Failed to save OpenClaw config:', error);
758
+ showToast('保存失败: 网络错误', 'error');
759
+ } finally {
760
+ setOpenclawSaving(false);
761
+ }
762
+ };
763
+
764
+ const handleOpenClawCancelTest = () => {
765
+ if (openclawAbortRef.current) {
766
+ openclawAbortRef.current.abort();
767
+ openclawAbortRef.current = null;
768
+ }
769
+ };
770
+
237
771
  if (loading) {
238
772
  return (
239
773
  <div className="min-h-screen flex items-center justify-center bg-slate-50/80">
@@ -257,7 +791,7 @@ export default function IDEConfigPage() {
257
791
  v1.0
258
792
  </span>
259
793
  </div>
260
- <p className="text-sm text-slate-500">配置 Claude Code 和 Cursor 的模型代理设置</p>
794
+ <p className="text-sm text-slate-500">配置 Claude Code、OpenCodeOpenClaw 的模型代理设置</p>
261
795
  </div>
262
796
 
263
797
  <div className="bg-white/60 backdrop-blur-md rounded-xl border border-slate-200/50 shadow-sm">
@@ -277,12 +811,38 @@ export default function IDEConfigPage() {
277
811
  )}
278
812
  </button>
279
813
  <button
280
- onClick={() => setActiveTab('cursor')}
814
+ onClick={() => setActiveTab('opencode')}
815
+ className={`relative px-4 py-3.5 text-sm font-medium transition-all duration-200 ${
816
+ activeTab === 'opencode'
817
+ ? 'text-slate-900'
818
+ : 'text-slate-400 hover:text-slate-600'
819
+ }`}
820
+ >
821
+ OpenCode
822
+ {activeTab === 'opencode' && (
823
+ <span className="absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full"></span>
824
+ )}
825
+ </button>
826
+ <button
827
+ onClick={() => setActiveTab('openclaw')}
828
+ className={`relative px-4 py-3.5 text-sm font-medium transition-all duration-200 ${
829
+ activeTab === 'openclaw'
830
+ ? 'text-slate-900'
831
+ : 'text-slate-400 hover:text-slate-600'
832
+ }`}
833
+ >
834
+ OpenClaw
835
+ {activeTab === 'openclaw' && (
836
+ <span className="absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full"></span>
837
+ )}
838
+ </button>
839
+ <button
840
+ onClick={() => setActiveTab('picoclaw')}
281
841
  disabled
282
842
  className="relative px-4 py-3.5 text-sm font-medium text-slate-300 disabled:cursor-not-allowed"
283
843
  >
284
844
  <div className="flex items-center gap-2">
285
- Cursor
845
+ PicoClaw
286
846
  <span className="px-2 py-0.5 text-[10px] font-medium text-slate-400 bg-slate-100/60 rounded-full border border-slate-200/50">
287
847
  即将推出
288
848
  </span>
@@ -316,13 +876,13 @@ export default function IDEConfigPage() {
316
876
  <span className="text-sm font-medium text-slate-700">
317
877
  {status.applied ? '已配置' : '未配置'}
318
878
  </span>
319
- {status.applied && status.matchCurrentGateway !== undefined && (
879
+ {status.applied && status.routerProvider === 'aar' && status.matchCurrentGateway !== undefined && (
320
880
  <span className={`inline-flex items-center px-2 py-0.5 text-[10px] font-medium rounded-full border ${
321
881
  status.matchCurrentGateway
322
882
  ? 'bg-emerald-50/80 text-emerald-700 border-emerald-200/60'
323
883
  : 'bg-amber-50/80 text-amber-700 border-amber-200/60'
324
884
  }`}>
325
- {status.matchCurrentGateway ? '匹配当前网关' : '不匹配'}
885
+ {status.matchCurrentGateway ? '匹配配置' : '不匹配'}
326
886
  </span>
327
887
  )}
328
888
  </div>
@@ -455,15 +1015,11 @@ export default function IDEConfigPage() {
455
1015
  </div>
456
1016
  </div>
457
1017
 
458
- <div className="grid grid-cols-3 gap-3 pt-1">
1018
+ <div className={`grid gap-3 pt-1 ${status?.backupExists ? 'grid-cols-3' : 'grid-cols-2'}`}>
459
1019
  <button
460
- onClick={enabled ? handleRestore : handleApply}
461
- disabled={applying || restoring}
462
- className={`w-full px-4 py-2.5 text-xs font-medium rounded-lg border transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 ${
463
- enabled
464
- ? 'bg-white border-slate-200/80 text-slate-700 hover:border-slate-300 hover:bg-slate-50'
465
- : 'bg-slate-900 border-slate-900 text-white hover:bg-slate-800'
466
- }`}
1020
+ onClick={handleApply}
1021
+ disabled={applying}
1022
+ className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-slate-900 bg-slate-900 text-white hover:bg-slate-800 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
467
1023
  >
468
1024
  {applying ? (
469
1025
  <>
@@ -471,24 +1027,43 @@ export default function IDEConfigPage() {
471
1027
  <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
472
1028
  <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
473
1029
  </svg>
474
- {enabled ? '还原中' : '应用中'}
1030
+ 应用中
475
1031
  </>
476
1032
  ) : (
477
1033
  <>
478
- {enabled ? (
479
- <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
480
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
481
- </svg>
482
- ) : (
483
- <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
484
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
485
- </svg>
486
- )}
487
- {enabled ? '还原配置' : '应用配置'}
1034
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1035
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
1036
+ </svg>
1037
+ {enabled ? '更新配置' : '应用配置'}
488
1038
  </>
489
1039
  )}
490
1040
  </button>
491
1041
 
1042
+ {status?.backupExists && (
1043
+ <button
1044
+ onClick={handleRestore}
1045
+ disabled={restoring}
1046
+ className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-slate-200/80 bg-white text-slate-700 hover:border-slate-300 hover:bg-slate-50 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
1047
+ >
1048
+ {restoring ? (
1049
+ <>
1050
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
1051
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
1052
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
1053
+ </svg>
1054
+ 还原中
1055
+ </>
1056
+ ) : (
1057
+ <>
1058
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1059
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
1060
+ </svg>
1061
+ 还原配置
1062
+ </>
1063
+ )}
1064
+ </button>
1065
+ )}
1066
+
492
1067
  {testing ? (
493
1068
  <button
494
1069
  onClick={handleCancelTest}
@@ -511,57 +1086,7 @@ export default function IDEConfigPage() {
511
1086
  测试配置
512
1087
  </button>
513
1088
  )}
514
-
515
- {enabled ? (
516
- <button
517
- onClick={handleSave}
518
- disabled={saving}
519
- className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-emerald-200/80 bg-emerald-50 text-emerald-700 hover:bg-emerald-100 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
520
- >
521
- {saving ? (
522
- <>
523
- <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
524
- <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
525
- <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
526
- </svg>
527
- 保存中
528
- </>
529
- ) : (
530
- <>
531
- <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
532
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z" />
533
- <polyline points="17 21 17 13 7 13 7 21" />
534
- </svg>
535
- 保存配置
536
- </>
537
- )}
538
- </button>
539
- ) : (
540
- <button
541
- onClick={handleSave}
542
- disabled={saving}
543
- className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-indigo-200/80 bg-indigo-50 text-indigo-700 hover:bg-indigo-100 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
544
- >
545
- {saving ? (
546
- <>
547
- <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
548
- <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
549
- <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
550
- </svg>
551
- 保存中
552
- </>
553
- ) : (
554
- <>
555
- <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
556
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z" />
557
- <polyline points="17 21 17 13 7 13 7 21" />
558
- </svg>
559
- 保存配置
560
- </>
561
- )}
562
- </button>
563
- )}
564
- </div>
1089
+ </div>
565
1090
 
566
1091
  {testResult && (
567
1092
  <div className={`rounded-lg border px-4 py-3 ${
@@ -692,46 +1217,421 @@ export default function IDEConfigPage() {
692
1217
  </div>
693
1218
  )}
694
1219
 
695
- <details className="group">
1220
+ <details className="group" open>
696
1221
  <summary className="cursor-pointer text-[11px] text-slate-500 hover:text-slate-700 list-none flex items-center gap-1.5 py-1">
697
1222
  <svg className="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
698
1223
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
699
1224
  </svg>
700
- <span>配置预览</span>
1225
+ <span>配置</span>
701
1226
  </summary>
702
1227
  <div className="mt-3">
703
- <div className="relative overflow-hidden rounded-lg bg-slate-950 border border-slate-800">
704
- <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
705
- <div className="flex gap-1.5">
706
- <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
707
- <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
708
- <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1228
+ <div className="flex border border-slate-200 rounded-t-lg overflow-hidden bg-slate-100/90">
1229
+ <button
1230
+ type="button"
1231
+ onClick={() => setClaudeConfigSubTab('preview')}
1232
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
1233
+ claudeConfigSubTab === 'preview'
1234
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
1235
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
1236
+ }`}
1237
+ >
1238
+ 预览
1239
+ </button>
1240
+ <button
1241
+ type="button"
1242
+ onClick={() => setClaudeConfigSubTab('current')}
1243
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
1244
+ claudeConfigSubTab === 'current'
1245
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
1246
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
1247
+ }`}
1248
+ >
1249
+ 生效中
1250
+ </button>
1251
+ </div>
1252
+ <div className="relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0">
1253
+ {claudeConfigSubTab === 'preview' ? (
1254
+ <>
1255
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
1256
+ <div className="flex gap-1.5">
1257
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1258
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1259
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1260
+ </div>
1261
+ <span className="text-[10px] font-medium text-slate-400 ml-2">settings.json(仅当前网关相关,应用时会合并进现有配置)</span>
1262
+ </div>
1263
+ <pre className="w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono overflow-auto bg-slate-950 max-h-96 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full">
1264
+ {JSON.stringify(
1265
+ {
1266
+ router_provider: 'aar',
1267
+ env: {
1268
+ ANTHROPIC_AUTH_TOKEN: status?.currentGatewayApiKey ?? 'your-gateway-api-key',
1269
+ ANTHROPIC_BASE_URL: status?.currentGatewayAddress ?? 'http://localhost:1357',
1270
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: haikuModel,
1271
+ ANTHROPIC_DEFAULT_SONNET_MODEL: sonnetModel,
1272
+ ANTHROPIC_DEFAULT_OPUS_MODEL: opusModel,
1273
+ ANTHROPIC_MODEL: defaultModel,
1274
+ ANTHROPIC_REASONING_MODEL: reasoningModel,
1275
+ API_TIMEOUT_MS: '3000000',
1276
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1,
1277
+ hasCompletedOnboarding: true,
1278
+ },
1279
+ },
1280
+ null,
1281
+ 2
1282
+ )}
1283
+ </pre>
1284
+ </>
1285
+ ) : (
1286
+ <>
1287
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
1288
+ <div className="flex gap-1.5">
1289
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1290
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1291
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1292
+ </div>
1293
+ <span className="text-[10px] font-medium text-slate-400 ml-2">settings.json(当前磁盘内容,还原后即更新)</span>
1294
+ </div>
1295
+ {status?.config != null ? (
1296
+ <pre className="w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono overflow-auto bg-slate-950 max-h-96 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full">
1297
+ {JSON.stringify(status.config, null, 2)}
1298
+ </pre>
1299
+ ) : (
1300
+ <div className="p-3.5 text-[10px] text-slate-500 font-mono">暂无配置文件</div>
1301
+ )}
1302
+ </>
1303
+ )}
1304
+ </div>
1305
+ </div>
1306
+ </details>
1307
+
1308
+ <div className="rounded-lg border border-slate-200/50 bg-slate-50/60 px-4 py-3">
1309
+ <div className="flex items-center gap-2 mb-2.5">
1310
+ <svg className="w-4 h-4 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1311
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
1312
+ </svg>
1313
+ <h3 className="text-xs font-semibold text-slate-700">使用说明</h3>
1314
+ </div>
1315
+ <ul className="space-y-1.5 text-[10px] text-slate-600">
1316
+ <li className="flex gap-2">
1317
+ <span className="text-slate-400">1.</span>
1318
+ <span>「生效中」为当前磁盘上的 settings.json;「预览」为应用后将写入的内容</span>
1319
+ </li>
1320
+ <li className="flex gap-2">
1321
+ <span className="text-slate-400">2.</span>
1322
+ <span>为每个模型类别选择路由模型后,在「预览」中查看,点击「应用配置」或「更新配置」写入</span>
1323
+ </li>
1324
+ <li className="flex gap-2">
1325
+ <span className="text-slate-400">3.</span>
1326
+ <span>配置文件位于 ~/.claude/settings.json</span>
1327
+ </li>
1328
+ <li className="flex gap-2">
1329
+ <span className="text-slate-400">4.</span>
1330
+ <span>若已应用过 AAR,会显示「更新配置」;有备份时「还原配置」可恢复,还原后请在「生效中」查看</span>
1331
+ </li>
1332
+ </ul>
1333
+ </div>
1334
+ </div>
1335
+ )}
1336
+
1337
+ {activeTab === 'opencode' && (
1338
+ <div className="space-y-6">
1339
+ {opencodeStatus && (
1340
+ <div className="flex items-center justify-between py-3 px-4 bg-white/60 rounded-lg border border-slate-200/50">
1341
+ <div className="flex items-center gap-3">
1342
+ <div className={`w-6 h-6 rounded-full flex items-center justify-center ${
1343
+ opencodeStatus.applied
1344
+ ? 'bg-emerald-100 text-emerald-600'
1345
+ : 'bg-slate-100 text-slate-400'
1346
+ }`}>
1347
+ {opencodeStatus.applied ? (
1348
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1349
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
1350
+ </svg>
1351
+ ) : (
1352
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1353
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01" />
1354
+ </svg>
1355
+ )}
1356
+ </div>
1357
+ <div className="flex items-center gap-2.5">
1358
+ <span className="text-sm font-medium text-slate-700">
1359
+ {opencodeStatus.applied ? '已配置' : '未配置'}
1360
+ </span>
1361
+ {opencodeStatus.applied && opencodeStatus.matchCurrentGateway !== undefined && (
1362
+ <span className={`inline-flex items-center px-2 py-0.5 text-[10px] font-medium rounded-full border ${
1363
+ opencodeStatus.matchCurrentGateway
1364
+ ? 'bg-emerald-50/80 text-emerald-700 border-emerald-200/60'
1365
+ : 'bg-amber-50/80 text-amber-700 border-amber-200/60'
1366
+ }`}>
1367
+ {opencodeStatus.matchCurrentGateway ? '匹配当前网关' : '不匹配'}
1368
+ </span>
1369
+ )}
1370
+ </div>
1371
+ </div>
1372
+ {opencodeStatus.lastUpdated && (
1373
+ <div className="text-[11px] text-slate-400 font-mono">
1374
+ {new Date(opencodeStatus.lastUpdated).toLocaleString('zh-CN', {
1375
+ month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit'
1376
+ })}
1377
+ </div>
1378
+ )}
1379
+ </div>
1380
+ )}
1381
+
1382
+ <div>
1383
+ <h2 className="text-[11px] font-medium text-slate-400 mb-5">默认模型</h2>
1384
+ <div className="group relative bg-gradient-to-br from-white/50 to-transparent hover:from-slate-50/60 rounded-xl border border-slate-200/50 p-4 transition-all duration-300 hover:border-slate-300/60 hover:shadow-sm">
1385
+ <label htmlFor="opencode-default-model" className="cursor-pointer">
1386
+ <div className="flex items-center gap-2.5 mb-2.5">
1387
+ <div className="w-7 h-7 rounded-lg bg-slate-100/80 text-slate-600 flex items-center justify-center">
1388
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1389
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
1390
+ </svg>
1391
+ </div>
1392
+ <span className="text-[11px] font-medium text-slate-700">OpenCode 使用的默认模型</span>
1393
+ </div>
1394
+ <div className="relative">
1395
+ <select
1396
+ id="opencode-default-model"
1397
+ value={opencodeDefaultModel}
1398
+ onChange={(e) => setOpencodeDefaultModel(e.target.value)}
1399
+ className="w-full appearance-none rounded-lg border border-slate-200/80 bg-white/90 backdrop-blur-sm px-3 py-2 pr-7 text-[11px] font-medium text-slate-700 shadow-sm transition-all duration-200 hover:border-slate-300/80 hover:bg-white focus:border-slate-400/80 focus:ring-2 focus:ring-slate-200/50 focus:outline-none cursor-pointer"
1400
+ >
1401
+ {Object.entries(opencodeModels).map(([provider, providerModels]) => (
1402
+ <optgroup key={provider} label={provider}>
1403
+ {providerModels.map((model) => (
1404
+ <option key={model.id} value={model.model_id}>
1405
+ {model.name}
1406
+ </option>
1407
+ ))}
1408
+ </optgroup>
1409
+ ))}
1410
+ </select>
1411
+ <div className="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
1412
+ <svg className="w-3 h-3 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1413
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1414
+ </svg>
709
1415
  </div>
710
- <span className="text-[10px] font-medium text-slate-400 ml-2">settings.json</span>
711
1416
  </div>
712
- <pre className="p-3.5 text-[10px] text-slate-300 font-mono overflow-x-auto bg-slate-950">
713
- {JSON.stringify(
714
- {
715
- router_provider: 'aar',
716
- env: {
717
- ANTHROPIC_AUTH_TOKEN: status?.apiKey && status.apiKey !== 'your-gateway-api-key'
718
- ? '****' + status.apiKey.slice(-8)
719
- : 'your-gateway-api-key',
720
- ANTHROPIC_BASE_URL: status?.gatewayAddress || 'http://localhost:3000',
721
- ANTHROPIC_DEFAULT_HAIKU_MODEL: haikuModel,
722
- ANTHROPIC_DEFAULT_SONNET_MODEL: sonnetModel,
723
- ANTHROPIC_DEFAULT_OPUS_MODEL: opusModel,
724
- ANTHROPIC_MODEL: defaultModel,
725
- ANTHROPIC_REASONING_MODEL: reasoningModel,
726
- API_TIMEOUT_MS: '3000000',
727
- CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1,
728
- hasCompletedOnboarding: true,
729
- },
730
- },
731
- null,
732
- 2
1417
+ </label>
1418
+ </div>
1419
+ </div>
1420
+
1421
+ <div className={`grid gap-3 pt-1 ${opencodeEnabled ? 'grid-cols-3' : 'grid-cols-2'}`}>
1422
+ <button
1423
+ onClick={handleOpenCodeApply}
1424
+ disabled={opencodeApplying}
1425
+ className={`w-full px-4 py-2.5 text-xs font-medium rounded-lg border transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 ${
1426
+ opencodeEnabled
1427
+ ? 'bg-white border-slate-200/80 text-slate-700 hover:border-slate-300 hover:bg-slate-50'
1428
+ : 'bg-slate-900 border-slate-900 text-white hover:bg-slate-800'
1429
+ }`}
1430
+ >
1431
+ {opencodeApplying ? (
1432
+ <>
1433
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
1434
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
1435
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
1436
+ </svg>
1437
+ 应用中
1438
+ </>
1439
+ ) : (
1440
+ <>
1441
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1442
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
1443
+ </svg>
1444
+ {opencodeEnabled ? '更新配置' : '应用配置'}
1445
+ </>
1446
+ )}
1447
+ </button>
1448
+
1449
+ {opencodeEnabled && (
1450
+ <button
1451
+ onClick={handleOpenCodeRestore}
1452
+ disabled={opencodeRestoring}
1453
+ className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-slate-200/80 bg-white text-slate-700 hover:border-slate-300 hover:bg-slate-50 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
1454
+ >
1455
+ {opencodeRestoring ? (
1456
+ <>
1457
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
1458
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
1459
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
1460
+ </svg>
1461
+ 还原中
1462
+ </>
1463
+ ) : (
1464
+ <>
1465
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1466
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
1467
+ </svg>
1468
+ 还原配置
1469
+ </>
1470
+ )}
1471
+ </button>
1472
+ )}
1473
+
1474
+ {opencodeTesting ? (
1475
+ <button
1476
+ onClick={handleOpenCodeCancelTest}
1477
+ className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-amber-200/80 bg-amber-50 text-amber-700 hover:bg-amber-100 transition-all duration-200 flex items-center justify-center gap-2"
1478
+ >
1479
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
1480
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
1481
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
1482
+ </svg>
1483
+ 取消测试
1484
+ </button>
1485
+ ) : (
1486
+ <button
1487
+ onClick={handleOpenCodeTest}
1488
+ className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-slate-200/80 bg-white text-slate-700 hover:bg-slate-50 transition-all duration-200 flex items-center justify-center gap-2"
1489
+ >
1490
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1491
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
1492
+ </svg>
1493
+ 测试配置
1494
+ </button>
1495
+ )}
1496
+ </div>
1497
+
1498
+ {opencodeTestResult && (
1499
+ <div className={`rounded-lg border px-4 py-3 ${
1500
+ opencodeTestResult.success
1501
+ ? 'bg-emerald-50/60 border-emerald-200/60'
1502
+ : 'bg-rose-50/60 border-rose-200/60'
1503
+ }`}>
1504
+ <div className="flex items-start gap-3">
1505
+ <div className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 ${
1506
+ opencodeTestResult.success ? 'bg-emerald-500' : 'bg-rose-500'
1507
+ }`}>
1508
+ {opencodeTestResult.success ? (
1509
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1510
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
1511
+ </svg>
1512
+ ) : (
1513
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1514
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
1515
+ </svg>
1516
+ )}
1517
+ </div>
1518
+ <div className="flex-1">
1519
+ <div className="text-xs font-medium mb-1">
1520
+ {opencodeTestResult.success ? '配置验证成功' : '配置验证失败'}
1521
+ </div>
1522
+ <p className={`text-[11px] ${opencodeTestResult.success ? 'text-emerald-700/80' : 'text-rose-700/80'}`}>
1523
+ {opencodeTestResult.success
1524
+ ? (opencodeTestResult.opencodeTest?.message || 'OpenCode 可以正常工作')
1525
+ : (opencodeTestResult.opencodeTest?.error || opencodeTestResult.error || opencodeTestResult.suggestion || '请检查配置')}
1526
+ </p>
1527
+ {opencodeTestResult.checks && (
1528
+ <div className="mt-3 pt-3 border-t border-white/40">
1529
+ <div className="grid grid-cols-3 gap-2">
1530
+ {Object.entries(opencodeTestResult.checks).map(([key, value]) => {
1531
+ const labels: Record<string, string> = {
1532
+ hasBaseUrl: '网关',
1533
+ hasApiKey: 'API Key',
1534
+ hasDefaultModel: '默认模型',
1535
+ hasModels: '模型列表',
1536
+ };
1537
+ return (
1538
+ <div key={key} className="flex items-center gap-1.5">
1539
+ {value ? (
1540
+ <svg className="w-3 h-3 text-emerald-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1541
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
1542
+ </svg>
1543
+ ) : (
1544
+ <svg className="w-3 h-3 text-rose-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1545
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
1546
+ </svg>
1547
+ )}
1548
+ <span className="text-[10px] text-slate-600">{labels[key] || key}</span>
1549
+ </div>
1550
+ );
1551
+ })}
1552
+ </div>
1553
+ </div>
733
1554
  )}
734
- </pre>
1555
+ </div>
1556
+ </div>
1557
+ </div>
1558
+ )}
1559
+
1560
+ <details className="group" open>
1561
+ <summary className="cursor-pointer text-[11px] text-slate-500 hover:text-slate-700 list-none flex items-center gap-1.5 py-1">
1562
+ <svg className="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1563
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
1564
+ </svg>
1565
+ <span>配置</span>
1566
+ </summary>
1567
+ <div className="mt-3">
1568
+ <div className="flex border border-slate-200 rounded-t-lg overflow-hidden bg-slate-100/90">
1569
+ <button
1570
+ type="button"
1571
+ onClick={() => setOpencodeConfigSubTab('preview')}
1572
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
1573
+ opencodeConfigSubTab === 'preview'
1574
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
1575
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
1576
+ }`}
1577
+ >
1578
+ 预览
1579
+ </button>
1580
+ <button
1581
+ type="button"
1582
+ onClick={() => setOpencodeConfigSubTab('current')}
1583
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
1584
+ opencodeConfigSubTab === 'current'
1585
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
1586
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
1587
+ }`}
1588
+ >
1589
+ 生效中
1590
+ </button>
1591
+ </div>
1592
+ <div className="relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0">
1593
+ {opencodeConfigSubTab === 'current' ? (
1594
+ <>
1595
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
1596
+ <div className="flex gap-1.5">
1597
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1598
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1599
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1600
+ </div>
1601
+ <span className="text-[10px] font-medium text-slate-400 ml-2">opencode.json(当前磁盘内容,还原后即更新)</span>
1602
+ </div>
1603
+ {opencodeStatus?.config != null ? (
1604
+ <pre className="w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono overflow-auto max-h-96 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full">
1605
+ {JSON.stringify(opencodeStatus.config, null, 2)}
1606
+ </pre>
1607
+ ) : (
1608
+ <div className="p-3.5 text-[10px] text-slate-500 font-mono">暂无配置文件</div>
1609
+ )}
1610
+ </>
1611
+ ) : (
1612
+ <>
1613
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
1614
+ <div className="flex gap-1.5">
1615
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1616
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1617
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1618
+ </div>
1619
+ <span className="text-[10px] font-medium text-slate-400 ml-2">opencode.json(应用后将写入的合并结果)</span>
1620
+ </div>
1621
+ {opencodePreviewLoading ? (
1622
+ <div className="p-3.5 text-[10px] text-slate-400 font-mono">加载中...</div>
1623
+ ) : (
1624
+ <textarea
1625
+ className="opencode-preview-textarea w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono bg-slate-950 border-0 focus:ring-0 focus:ring-offset-0 focus:outline-none resize-y max-h-96 overflow-y-auto placeholder:text-slate-500
1626
+ [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full"
1627
+ placeholder="暂无预览"
1628
+ spellCheck={false}
1629
+ value={opencodePreviewText}
1630
+ onChange={(e) => setOpencodePreviewText(e.target.value)}
1631
+ />
1632
+ )}
1633
+ </>
1634
+ )}
735
1635
  </div>
736
1636
  </div>
737
1637
  </details>
@@ -746,36 +1646,605 @@ export default function IDEConfigPage() {
746
1646
  <ul className="space-y-1.5 text-[10px] text-slate-600">
747
1647
  <li className="flex gap-2">
748
1648
  <span className="text-slate-400">1.</span>
749
- <span>为每个模型类别选择对应的路由模型</span>
1649
+ <span>「生效中」为当前磁盘上的 opencode.json;「预览」为点击应用后将写入的合并结果(含 AAR 网关)</span>
750
1650
  </li>
751
1651
  <li className="flex gap-2">
752
1652
  <span className="text-slate-400">2.</span>
753
- <span>下方配置预览会实时更新</span>
1653
+ <span>选择默认模型后,在「预览」中查看合并结果,点击「应用配置」写入</span>
754
1654
  </li>
755
1655
  <li className="flex gap-2">
756
1656
  <span className="text-slate-400">3.</span>
757
- <span>点击"保存配置"可临时保存当前选择</span>
1657
+ <span>配置文件位于 ~/.config/opencode/opencode.json(或 Windows %APPDATA%/opencode)</span>
758
1658
  </li>
759
1659
  <li className="flex gap-2">
760
1660
  <span className="text-slate-400">4.</span>
761
- <span>点击"应用配置"将设置写入 Claude Code</span>
1661
+ <span>「还原配置」恢复为应用前的备份;还原后请在「生效中」查看当前内容</span>
1662
+ </li>
1663
+ </ul>
1664
+ </div>
1665
+ </div>
1666
+ )}
1667
+
1668
+ {activeTab === 'openclaw' && (
1669
+ <div className="space-y-6">
1670
+ {openclawStatus && (
1671
+ <div className="flex items-center justify-between py-3 px-4 bg-white/60 rounded-lg border border-slate-200/50">
1672
+ <div className="flex items-center gap-3">
1673
+ <div className={`w-6 h-6 rounded-full flex items-center justify-center ${
1674
+ openclawStatus.applied
1675
+ ? 'bg-emerald-100 text-emerald-600'
1676
+ : 'bg-slate-100 text-slate-400'
1677
+ }`}>
1678
+ {openclawStatus.applied ? (
1679
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1680
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
1681
+ </svg>
1682
+ ) : (
1683
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1684
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01" />
1685
+ </svg>
1686
+ )}
1687
+ </div>
1688
+ <div className="flex items-center gap-2.5">
1689
+ <span className="text-sm font-medium text-slate-700">
1690
+ {openclawStatus.applied ? '已配置' : '未配置'}
1691
+ </span>
1692
+ {openclawStatus.applied && openclawStatus.matchCurrentGateway !== undefined && (
1693
+ <span className={`inline-flex items-center px-2 py-0.5 text-[10px] font-medium rounded-full border ${
1694
+ openclawStatus.matchCurrentGateway
1695
+ ? 'bg-emerald-50/80 text-emerald-700 border-emerald-200/60'
1696
+ : 'bg-amber-50/80 text-amber-700 border-amber-200/60'
1697
+ }`}>
1698
+ {openclawStatus.matchCurrentGateway ? '匹配当前网关' : '不匹配'}
1699
+ </span>
1700
+ )}
1701
+ </div>
1702
+ </div>
1703
+ {openclawStatus.lastUpdated && (
1704
+ <div className="text-[11px] text-slate-400 font-mono">
1705
+ {new Date(openclawStatus.lastUpdated).toLocaleString('zh-CN', {
1706
+ month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit'
1707
+ })}
1708
+ </div>
1709
+ )}
1710
+ </div>
1711
+ )}
1712
+
1713
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4" ref={openClawDropdownRef}>
1714
+ {/* 默认模型 - 可搜索单选 */}
1715
+ <div className="bg-gradient-to-br from-white/50 to-transparent rounded-xl border border-slate-200/50 p-4 transition-all duration-300 hover:border-slate-300/60 hover:shadow-sm">
1716
+ <div className="flex items-center gap-2.5 mb-2.5">
1717
+ <div className="w-7 h-7 rounded-lg bg-indigo-100/80 text-indigo-600 flex items-center justify-center">
1718
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1719
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
1720
+ </svg>
1721
+ </div>
1722
+ <span className="text-[11px] font-medium text-slate-700">默认模型</span>
1723
+ </div>
1724
+ <div className="relative">
1725
+ <button
1726
+ type="button"
1727
+ onClick={() => { setOpenClawSearch(''); setOpenClawDropdown(openClawDropdown === 'default' ? null : 'default'); }}
1728
+ className="w-full text-left appearance-none rounded-lg border border-slate-200/80 bg-white/90 px-3 py-2 pr-8 text-[11px] font-medium text-slate-700 shadow-sm focus:border-indigo-400 focus:ring-2 focus:ring-indigo-200/50 focus:outline-none cursor-pointer flex items-center justify-between"
1729
+ >
1730
+ <span className="truncate">
1731
+ {openclawModelsFlat.find((m) => m.model_id === openclawDefaultModel)?.name || openclawDefaultModel || '选择模型'}
1732
+ </span>
1733
+ <svg className="w-3 h-3 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1734
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1735
+ </svg>
1736
+ </button>
1737
+ {openClawDropdown === 'default' && (
1738
+ <div className="absolute z-20 mt-1 w-full rounded-lg border border-slate-200 bg-white shadow-lg py-1 max-h-56 overflow-hidden flex flex-col">
1739
+ <input
1740
+ type="text"
1741
+ placeholder="搜索模型..."
1742
+ value={openClawSearch}
1743
+ onChange={(e) => setOpenClawSearch(e.target.value)}
1744
+ onKeyDown={(e) => e.stopPropagation()}
1745
+ className="mx-2 mb-1 px-2 py-1.5 text-[11px] border border-slate-200 rounded-md focus:ring-2 focus:ring-indigo-200 focus:border-indigo-400 focus:outline-none"
1746
+ />
1747
+ <ul className="overflow-y-auto overscroll-contain max-h-44">
1748
+ {openclawModelsFiltered.map((model) => (
1749
+ <li key={model.id}>
1750
+ <button
1751
+ type="button"
1752
+ onClick={() => {
1753
+ setOpenclawDefaultModel(model.model_id);
1754
+ setOpenClawDropdown(null);
1755
+ setOpenClawSearch('');
1756
+ }}
1757
+ className={`w-full text-left px-3 py-2 text-[11px] hover:bg-indigo-50 ${model.model_id === openclawDefaultModel ? 'bg-indigo-100 text-indigo-700 font-medium' : 'text-slate-700'}`}
1758
+ >
1759
+ {model.name}
1760
+ <span className="text-slate-400 ml-1">({model.provider})</span>
1761
+ </button>
1762
+ </li>
1763
+ ))}
1764
+ </ul>
1765
+ </div>
1766
+ )}
1767
+ </div>
1768
+ <p className="text-[10px] text-slate-500 mt-2">主要使用的模型</p>
1769
+ </div>
1770
+
1771
+ {/* 默认 Fallback - 可搜索多选,芯片展示,不可重复 */}
1772
+ <div className="bg-gradient-to-br from-white/50 to-transparent rounded-xl border border-slate-200/50 p-4 transition-all duration-300 hover:border-slate-300/60 hover:shadow-sm">
1773
+ <div className="flex items-center gap-2.5 mb-2.5">
1774
+ <div className="w-7 h-7 rounded-lg bg-amber-100/80 text-amber-600 flex items-center justify-center">
1775
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1776
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
1777
+ </svg>
1778
+ </div>
1779
+ <span className="text-[11px] font-medium text-slate-700">默认 Fallback</span>
1780
+ </div>
1781
+ <div className="relative">
1782
+ <div className="min-h-[38px] rounded-lg border border-slate-200/80 bg-white/90 px-3 py-2 flex flex-wrap gap-1.5 items-center">
1783
+ {openclawDefaultFallbacks.length === 0 ? (
1784
+ <span className="text-[11px] text-slate-400">从下方搜索并添加备用模型</span>
1785
+ ) : (
1786
+ openclawDefaultFallbacks.map((modelId) => {
1787
+ const m = openclawModelsFlat.find((x) => x.model_id === modelId);
1788
+ return (
1789
+ <span
1790
+ key={modelId}
1791
+ className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-amber-100 text-amber-800 text-[10px] font-medium"
1792
+ >
1793
+ {m?.name || modelId}
1794
+ <button
1795
+ type="button"
1796
+ onClick={() => setOpenclawDefaultFallbacks((prev) => prev.filter((id) => id !== modelId))}
1797
+ className="hover:bg-amber-200 rounded p-0.5"
1798
+ aria-label="移除"
1799
+ >
1800
+ <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1801
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
1802
+ </svg>
1803
+ </button>
1804
+ </span>
1805
+ );
1806
+ })
1807
+ )}
1808
+ </div>
1809
+ <button
1810
+ type="button"
1811
+ onClick={() => { setOpenClawSearch(''); setOpenClawDropdown(openClawDropdown === 'defaultFallbacks' ? null : 'defaultFallbacks'); }}
1812
+ className="mt-1.5 w-full text-left px-3 py-1.5 text-[11px] text-slate-500 border border-dashed border-slate-200 rounded-md hover:border-amber-300 hover:text-amber-700 hover:bg-amber-50/50"
1813
+ >
1814
+ + 搜索并添加 Fallback 模型
1815
+ </button>
1816
+ {openClawDropdown === 'defaultFallbacks' && (
1817
+ <div className="absolute z-20 mt-1 w-full rounded-lg border border-slate-200 bg-white shadow-lg py-1 max-h-56 overflow-hidden flex flex-col">
1818
+ <input
1819
+ type="text"
1820
+ placeholder="搜索模型..."
1821
+ value={openClawSearch}
1822
+ onChange={(e) => setOpenClawSearch(e.target.value)}
1823
+ onKeyDown={(e) => e.stopPropagation()}
1824
+ className="mx-2 mb-1 px-2 py-1.5 text-[11px] border border-slate-200 rounded-md focus:ring-2 focus:ring-amber-200 focus:border-amber-400 focus:outline-none"
1825
+ />
1826
+ <ul className="overflow-y-auto overscroll-contain max-h-44">
1827
+ {openclawModelsFiltered
1828
+ .filter((m) => !openclawDefaultFallbacks.includes(m.model_id))
1829
+ .map((model) => (
1830
+ <li key={model.id}>
1831
+ <button
1832
+ type="button"
1833
+ onClick={() => {
1834
+ setOpenclawDefaultFallbacks((prev) => (prev.includes(model.model_id) ? prev : [...prev, model.model_id]));
1835
+ setOpenClawSearch('');
1836
+ }}
1837
+ className="w-full text-left px-3 py-2 text-[11px] text-slate-700 hover:bg-amber-50"
1838
+ >
1839
+ {model.name}
1840
+ <span className="text-slate-400 ml-1">({model.provider})</span>
1841
+ </button>
1842
+ </li>
1843
+ ))}
1844
+ {openclawModelsFiltered.filter((m) => !openclawDefaultFallbacks.includes(m.model_id)).length === 0 && (
1845
+ <li className="px-3 py-2 text-[11px] text-slate-400">无更多可选或已全部添加</li>
1846
+ )}
1847
+ </ul>
1848
+ </div>
1849
+ )}
1850
+ </div>
1851
+ <p className="text-[10px] text-slate-500 mt-2">默认模型的备用模型,可多选,不可重复</p>
1852
+ </div>
1853
+
1854
+ {/* 视觉模型 - 可搜索单选 */}
1855
+ <div className="bg-gradient-to-br from-white/50 to-transparent rounded-xl border border-slate-200/50 p-4 transition-all duration-300 hover:border-slate-300/60 hover:shadow-sm">
1856
+ <div className="flex items-center gap-2.5 mb-2.5">
1857
+ <div className="w-7 h-7 rounded-lg bg-rose-100/80 text-rose-600 flex items-center justify-center">
1858
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1859
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
1860
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
1861
+ </svg>
1862
+ </div>
1863
+ <span className="text-[11px] font-medium text-slate-700">视觉模型</span>
1864
+ </div>
1865
+ <div className="relative">
1866
+ <button
1867
+ type="button"
1868
+ onClick={() => { setOpenClawSearch(''); setOpenClawDropdown(openClawDropdown === 'image' ? null : 'image'); }}
1869
+ className="w-full text-left appearance-none rounded-lg border border-slate-200/80 bg-white/90 px-3 py-2 pr-8 text-[11px] font-medium text-slate-700 shadow-sm focus:border-rose-400 focus:ring-2 focus:ring-rose-200/50 focus:outline-none cursor-pointer flex items-center justify-between"
1870
+ >
1871
+ <span className="truncate">
1872
+ {openclawImageModel
1873
+ ? (openclawModelsFlat.find((m) => m.model_id === openclawImageModel)?.name || openclawImageModel)
1874
+ : '未设置'}
1875
+ </span>
1876
+ <svg className="w-3 h-3 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1877
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1878
+ </svg>
1879
+ </button>
1880
+ {openClawDropdown === 'image' && (
1881
+ <div className="absolute z-20 mt-1 w-full rounded-lg border border-slate-200 bg-white shadow-lg py-1 max-h-56 overflow-hidden flex flex-col">
1882
+ <input
1883
+ type="text"
1884
+ placeholder="搜索模型..."
1885
+ value={openClawSearch}
1886
+ onChange={(e) => setOpenClawSearch(e.target.value)}
1887
+ onKeyDown={(e) => e.stopPropagation()}
1888
+ className="mx-2 mb-1 px-2 py-1.5 text-[11px] border border-slate-200 rounded-md focus:ring-2 focus:ring-rose-200 focus:border-rose-400 focus:outline-none"
1889
+ />
1890
+ <ul className="overflow-y-auto overscroll-contain max-h-44">
1891
+ <li>
1892
+ <button
1893
+ type="button"
1894
+ onClick={() => {
1895
+ setOpenclawImageModel('');
1896
+ setOpenClawDropdown(null);
1897
+ setOpenClawSearch('');
1898
+ }}
1899
+ className={`w-full text-left px-3 py-2 text-[11px] hover:bg-rose-50 ${!openclawImageModel ? 'bg-rose-100 text-rose-700 font-medium' : 'text-slate-500'}`}
1900
+ >
1901
+ 未设置
1902
+ </button>
1903
+ </li>
1904
+ {openclawModelsFiltered.map((model) => (
1905
+ <li key={model.id}>
1906
+ <button
1907
+ type="button"
1908
+ onClick={() => {
1909
+ setOpenclawImageModel(model.model_id);
1910
+ setOpenClawDropdown(null);
1911
+ setOpenClawSearch('');
1912
+ }}
1913
+ className={`w-full text-left px-3 py-2 text-[11px] hover:bg-rose-50 ${model.model_id === openclawImageModel ? 'bg-rose-100 text-rose-700 font-medium' : 'text-slate-700'}`}
1914
+ >
1915
+ {model.name}
1916
+ <span className="text-slate-400 ml-1">({model.provider})</span>
1917
+ </button>
1918
+ </li>
1919
+ ))}
1920
+ </ul>
1921
+ </div>
1922
+ )}
1923
+ </div>
1924
+ <p className="text-[10px] text-slate-500 mt-2">用于图像理解(可选)</p>
1925
+ </div>
1926
+
1927
+ {/* 视觉 Fallback - 可搜索多选,芯片展示,不可重复 */}
1928
+ <div className="bg-gradient-to-br from-white/50 to-transparent rounded-xl border border-slate-200/50 p-4 transition-all duration-300 hover:border-slate-300/60 hover:shadow-sm">
1929
+ <div className="flex items-center gap-2.5 mb-2.5">
1930
+ <div className="w-7 h-7 rounded-lg bg-emerald-100/80 text-emerald-600 flex items-center justify-center">
1931
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1932
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
1933
+ </svg>
1934
+ </div>
1935
+ <span className="text-[11px] font-medium text-slate-700">视觉 Fallback</span>
1936
+ </div>
1937
+ <div className="relative">
1938
+ <div className="min-h-[38px] rounded-lg border border-slate-200/80 bg-white/90 px-3 py-2 flex flex-wrap gap-1.5 items-center">
1939
+ {openclawImageFallbacks.length === 0 ? (
1940
+ <span className="text-[11px] text-slate-400">从下方搜索并添加备用模型</span>
1941
+ ) : (
1942
+ openclawImageFallbacks.map((modelId) => {
1943
+ const m = openclawModelsFlat.find((x) => x.model_id === modelId);
1944
+ return (
1945
+ <span
1946
+ key={modelId}
1947
+ className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-emerald-100 text-emerald-800 text-[10px] font-medium"
1948
+ >
1949
+ {m?.name || modelId}
1950
+ <button
1951
+ type="button"
1952
+ onClick={() => setOpenclawImageFallbacks((prev) => prev.filter((id) => id !== modelId))}
1953
+ className="hover:bg-emerald-200 rounded p-0.5"
1954
+ aria-label="移除"
1955
+ >
1956
+ <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1957
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
1958
+ </svg>
1959
+ </button>
1960
+ </span>
1961
+ );
1962
+ })
1963
+ )}
1964
+ </div>
1965
+ <button
1966
+ type="button"
1967
+ onClick={() => { setOpenClawSearch(''); setOpenClawDropdown(openClawDropdown === 'imageFallbacks' ? null : 'imageFallbacks'); }}
1968
+ className="mt-1.5 w-full text-left px-3 py-1.5 text-[11px] text-slate-500 border border-dashed border-slate-200 rounded-md hover:border-emerald-300 hover:text-emerald-700 hover:bg-emerald-50/50"
1969
+ >
1970
+ + 搜索并添加 Fallback 模型
1971
+ </button>
1972
+ {openClawDropdown === 'imageFallbacks' && (
1973
+ <div className="absolute z-20 mt-1 w-full rounded-lg border border-slate-200 bg-white shadow-lg py-1 max-h-56 overflow-hidden flex flex-col">
1974
+ <input
1975
+ type="text"
1976
+ placeholder="搜索模型..."
1977
+ value={openClawSearch}
1978
+ onChange={(e) => setOpenClawSearch(e.target.value)}
1979
+ onKeyDown={(e) => e.stopPropagation()}
1980
+ className="mx-2 mb-1 px-2 py-1.5 text-[11px] border border-slate-200 rounded-md focus:ring-2 focus:ring-emerald-200 focus:border-emerald-400 focus:outline-none"
1981
+ />
1982
+ <ul className="overflow-y-auto overscroll-contain max-h-44">
1983
+ {openclawModelsFiltered
1984
+ .filter((m) => !openclawImageFallbacks.includes(m.model_id))
1985
+ .map((model) => (
1986
+ <li key={model.id}>
1987
+ <button
1988
+ type="button"
1989
+ onClick={() => {
1990
+ setOpenclawImageFallbacks((prev) => (prev.includes(model.model_id) ? prev : [...prev, model.model_id]));
1991
+ setOpenClawSearch('');
1992
+ }}
1993
+ className="w-full text-left px-3 py-2 text-[11px] text-slate-700 hover:bg-emerald-50"
1994
+ >
1995
+ {model.name}
1996
+ <span className="text-slate-400 ml-1">({model.provider})</span>
1997
+ </button>
1998
+ </li>
1999
+ ))}
2000
+ {openclawModelsFiltered.filter((m) => !openclawImageFallbacks.includes(m.model_id)).length === 0 && (
2001
+ <li className="px-3 py-2 text-[11px] text-slate-400">无更多可选或已全部添加</li>
2002
+ )}
2003
+ </ul>
2004
+ </div>
2005
+ )}
2006
+ </div>
2007
+ <p className="text-[10px] text-slate-500 mt-2">视觉模型的备用模型,可多选,不可重复</p>
2008
+ </div>
2009
+ </div>
2010
+
2011
+ <div className={`grid gap-3 pt-1 ${openclawEnabled ? 'grid-cols-3' : 'grid-cols-2'}`}>
2012
+ <button
2013
+ onClick={handleOpenClawApply}
2014
+ disabled={openclawApplying}
2015
+ className={`w-full px-4 py-2.5 text-xs font-medium rounded-lg border transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 ${
2016
+ openclawEnabled
2017
+ ? 'bg-white border-slate-200/80 text-slate-700 hover:border-slate-300 hover:bg-slate-50'
2018
+ : 'bg-slate-900 border-slate-900 text-white hover:bg-slate-800'
2019
+ }`}
2020
+ >
2021
+ {openclawApplying ? (
2022
+ <>
2023
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
2024
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
2025
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
2026
+ </svg>
2027
+ 应用中
2028
+ </>
2029
+ ) : (
2030
+ <>
2031
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2032
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
2033
+ </svg>
2034
+ {openclawEnabled ? '更新配置' : '应用配置'}
2035
+ </>
2036
+ )}
2037
+ </button>
2038
+
2039
+ {openclawEnabled && (
2040
+ <button
2041
+ onClick={handleOpenClawRestore}
2042
+ disabled={openclawRestoring}
2043
+ className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-slate-200/80 bg-white text-slate-700 hover:border-slate-300 hover:bg-slate-50 transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
2044
+ >
2045
+ {openclawRestoring ? (
2046
+ <>
2047
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
2048
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
2049
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
2050
+ </svg>
2051
+ 还原中
2052
+ </>
2053
+ ) : (
2054
+ <>
2055
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2056
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
2057
+ </svg>
2058
+ 还原配置
2059
+ </>
2060
+ )}
2061
+ </button>
2062
+ )}
2063
+
2064
+ {openclawTesting ? (
2065
+ <button
2066
+ onClick={handleOpenClawCancelTest}
2067
+ className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-amber-200/80 bg-amber-50 text-amber-700 hover:bg-amber-100 transition-all duration-200 flex items-center justify-center gap-2"
2068
+ >
2069
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
2070
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
2071
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
2072
+ </svg>
2073
+ 取消测试
2074
+ </button>
2075
+ ) : (
2076
+ <button
2077
+ onClick={handleOpenClawTest}
2078
+ className="w-full px-4 py-2.5 text-xs font-medium rounded-lg border border-slate-200/80 bg-white text-slate-700 hover:bg-slate-50 transition-all duration-200 flex items-center justify-center gap-2"
2079
+ >
2080
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2081
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
2082
+ </svg>
2083
+ 测试配置
2084
+ </button>
2085
+ )}
2086
+ </div>
2087
+
2088
+ {openclawTestResult && (
2089
+ <div className={`rounded-lg border px-4 py-3 ${
2090
+ openclawTestResult.success
2091
+ ? 'bg-emerald-50/60 border-emerald-200/60'
2092
+ : 'bg-rose-50/60 border-rose-200/60'
2093
+ }`}>
2094
+ <div className="flex items-start gap-3">
2095
+ <div className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 ${
2096
+ openclawTestResult.success ? 'bg-emerald-500' : 'bg-rose-500'
2097
+ }`}>
2098
+ {openclawTestResult.success ? (
2099
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2100
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
2101
+ </svg>
2102
+ ) : (
2103
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2104
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
2105
+ </svg>
2106
+ )}
2107
+ </div>
2108
+ <div className="flex-1">
2109
+ <div className="text-xs font-medium mb-1">
2110
+ {openclawTestResult.success ? '配置验证成功' : '配置验证失败'}
2111
+ </div>
2112
+ <p className={`text-[11px] ${openclawTestResult.success ? 'text-emerald-700/80' : 'text-rose-700/80'}`}>
2113
+ {openclawTestResult.success
2114
+ ? (openclawTestResult.openclawTest?.message || 'OpenClaw 可以正常工作')
2115
+ : (openclawTestResult.openclawTest?.error || openclawTestResult.error || openclawTestResult.suggestion || '请检查配置')}
2116
+ </p>
2117
+ {openclawTestResult.openclawTest?.stdout && (
2118
+ <div className="mt-2 text-[10px] text-slate-500 font-mono bg-white/50 p-2 rounded">
2119
+ {openclawTestResult.openclawTest.stdout}
2120
+ </div>
2121
+ )}
2122
+ </div>
2123
+ </div>
2124
+ </div>
2125
+ )}
2126
+
2127
+ <details className="group" open>
2128
+ <summary className="cursor-pointer text-[11px] text-slate-500 hover:text-slate-700 list-none flex items-center gap-1.5 py-1">
2129
+ <svg className="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2130
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
2131
+ </svg>
2132
+ <span>配置</span>
2133
+ </summary>
2134
+ <div className="mt-3">
2135
+ <div className="flex border border-slate-200 rounded-t-lg overflow-hidden bg-slate-100/90">
2136
+ <button
2137
+ type="button"
2138
+ onClick={() => setOpenclawConfigSubTab('preview')}
2139
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
2140
+ openclawConfigSubTab === 'preview'
2141
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
2142
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
2143
+ }`}
2144
+ >
2145
+ 预览
2146
+ </button>
2147
+ <button
2148
+ type="button"
2149
+ onClick={() => setOpenclawConfigSubTab('current')}
2150
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
2151
+ openclawConfigSubTab === 'current'
2152
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
2153
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
2154
+ }`}
2155
+ >
2156
+ 生效中
2157
+ </button>
2158
+ </div>
2159
+ <div className="relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0">
2160
+ {openclawConfigSubTab === 'preview' ? (
2161
+ <>
2162
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
2163
+ <div className="flex gap-1.5">
2164
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2165
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2166
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2167
+ </div>
2168
+ <span className="text-[10px] font-medium text-slate-400 ml-2">openclaw.json(应用后将写入的合并结果)</span>
2169
+ </div>
2170
+ {openclawPreviewLoading ? (
2171
+ <div className="p-3.5 text-[10px] text-slate-400 font-mono">加载中...</div>
2172
+ ) : (
2173
+ <textarea
2174
+ className="openclaw-preview-textarea w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono bg-slate-950 border-0 focus:ring-0 focus:ring-offset-0 focus:outline-none resize-y max-h-96 overflow-y-auto placeholder:text-slate-500
2175
+ [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full"
2176
+ placeholder="暂无预览"
2177
+ spellCheck={false}
2178
+ value={openclawPreviewText}
2179
+ onChange={(e) => setOpenclawPreviewText(e.target.value)}
2180
+ />
2181
+ )}
2182
+ </>
2183
+ ) : (
2184
+ <>
2185
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
2186
+ <div className="flex gap-1.5">
2187
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2188
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2189
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2190
+ </div>
2191
+ <span className="text-[10px] font-medium text-slate-400 ml-2">openclaw.json(当前磁盘内容,还原后即更新)</span>
2192
+ </div>
2193
+ {openclawStatus?.config != null ? (
2194
+ <pre className="w-full min-h-[12rem] p-3.5 text-[10px] text-slate-300 font-mono overflow-auto max-h-96 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full">
2195
+ {JSON.stringify(openclawStatus.config, null, 2)}
2196
+ </pre>
2197
+ ) : (
2198
+ <div className="p-3.5 text-[10px] text-slate-500 font-mono">暂无配置文件</div>
2199
+ )}
2200
+ </>
2201
+ )}
2202
+ </div>
2203
+ </div>
2204
+ </details>
2205
+
2206
+ <div className="rounded-lg border border-slate-200/50 bg-slate-50/60 px-4 py-3">
2207
+ <div className="flex items-center gap-2 mb-2.5">
2208
+ <svg className="w-4 h-4 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2209
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
2210
+ </svg>
2211
+ <h3 className="text-xs font-semibold text-slate-700">使用说明</h3>
2212
+ </div>
2213
+ <ul className="space-y-1.5 text-[10px] text-slate-600">
2214
+ <li className="flex gap-2">
2215
+ <span className="text-slate-400">1.</span>
2216
+ <span>「生效中」为当前磁盘上的 openclaw.json;「预览」为应用后将写入的合并结果(含 AAR 网关)</span>
762
2217
  </li>
763
2218
  <li className="flex gap-2">
764
- <span className="text-slate-400">5.</span>
765
- <span>配置文件位于 ~/.claude/settings.json</span>
2219
+ <span className="text-slate-400">2.</span>
2220
+ <span>选择默认模型等后,在「预览」中查看合并结果,点击「应用配置」写入</span>
766
2221
  </li>
767
2222
  <li className="flex gap-2">
768
- <span className="text-slate-400">6.</span>
769
- <span>临时配置保存在 ~/.aar/settings.tmp.json</span>
2223
+ <span className="text-slate-400">3.</span>
2224
+ <span>配置文件位于 ~/.openclaw/openclaw.json</span>
770
2225
  </li>
771
2226
  <li className="flex gap-2">
772
- <span className="text-slate-400">7.</span>
773
- <span>点击"还原配置"可恢复到应用前的状态</span>
2227
+ <span className="text-slate-400">4.</span>
2228
+ <span>「还原配置」恢复为应用前的备份;还原后请在「生效中」查看当前内容</span>
774
2229
  </li>
775
2230
  </ul>
776
2231
  </div>
777
2232
  </div>
778
2233
  )}
2234
+
2235
+ {activeTab === 'picoclaw' && (
2236
+ <div className="flex flex-col items-center justify-center py-12 text-center">
2237
+ <div className="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-4">
2238
+ <svg className="w-8 h-8 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2239
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
2240
+ </svg>
2241
+ </div>
2242
+ <h3 className="text-sm font-medium text-slate-900 mb-1">PicoClaw 支持即将推出</h3>
2243
+ <p className="text-xs text-slate-500 max-w-xs">
2244
+ 我们将很快添加对 PicoClaw 的支持,敬请期待。
2245
+ </p>
2246
+ </div>
2247
+ )}
779
2248
  </div>
780
2249
  </div>
781
2250
  </main>