ai-agent-router 0.1.21 → 0.2.1

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 (368) 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/routes-manifest.json +84 -0
  6. package/.next/server/app/_global-error/page.js +1 -1
  7. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/server/app/_global-error.html +2 -2
  9. package/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/server/app/_not-found/page.js +1 -1
  16. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  23. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  26. package/.next/server/app/api/config/route.js.nft.json +1 -1
  27. package/.next/server/app/api/gateway/[...path]/route.js.nft.json +1 -1
  28. package/.next/server/app/api/gateway/models/route.js.nft.json +1 -1
  29. package/.next/server/app/api/gateway/route.js.nft.json +1 -1
  30. package/.next/server/app/api/ide/claude/apply/route.js.nft.json +1 -1
  31. package/.next/server/app/api/ide/claude/available-models/route.js.nft.json +1 -1
  32. package/.next/server/app/api/ide/claude/save/route.js.nft.json +1 -1
  33. package/.next/server/app/api/ide/claude/status/route.js.nft.json +1 -1
  34. package/.next/server/app/api/ide/claude/test/route.js +1 -1
  35. package/.next/server/app/api/ide/claude/test/route.js.nft.json +1 -1
  36. package/.next/server/app/api/ide/openclaw/apply/route/app-paths-manifest.json +3 -0
  37. package/.next/server/app/api/ide/openclaw/apply/route/build-manifest.json +11 -0
  38. package/.next/server/app/api/ide/openclaw/apply/route/server-reference-manifest.json +4 -0
  39. package/.next/server/app/api/ide/openclaw/apply/route.js +7 -0
  40. package/.next/server/app/api/ide/openclaw/apply/route.js.map +5 -0
  41. package/.next/server/app/api/ide/openclaw/apply/route.js.nft.json +1 -0
  42. package/.next/server/app/api/ide/openclaw/apply/route_client-reference-manifest.js +2 -0
  43. package/.next/server/app/api/ide/openclaw/available-models/route/app-paths-manifest.json +3 -0
  44. package/.next/server/app/api/ide/openclaw/available-models/route/build-manifest.json +11 -0
  45. package/.next/server/app/api/ide/openclaw/available-models/route/server-reference-manifest.json +4 -0
  46. package/.next/server/app/api/ide/openclaw/available-models/route.js +7 -0
  47. package/.next/server/app/api/ide/openclaw/available-models/route.js.map +5 -0
  48. package/.next/server/app/api/ide/openclaw/available-models/route.js.nft.json +1 -0
  49. package/.next/server/app/api/ide/openclaw/available-models/route_client-reference-manifest.js +2 -0
  50. package/.next/server/app/api/ide/openclaw/preview/route/app-paths-manifest.json +3 -0
  51. package/.next/server/app/api/ide/openclaw/preview/route/build-manifest.json +11 -0
  52. package/.next/server/app/api/ide/openclaw/preview/route/server-reference-manifest.json +4 -0
  53. package/.next/server/app/api/ide/openclaw/preview/route.js +7 -0
  54. package/.next/server/app/api/ide/openclaw/preview/route.js.map +5 -0
  55. package/.next/server/app/api/ide/openclaw/preview/route.js.nft.json +1 -0
  56. package/.next/server/app/api/ide/openclaw/preview/route_client-reference-manifest.js +2 -0
  57. package/.next/server/app/api/ide/openclaw/restore/route/app-paths-manifest.json +3 -0
  58. package/.next/server/app/api/ide/openclaw/restore/route/build-manifest.json +11 -0
  59. package/.next/server/app/api/ide/openclaw/restore/route/server-reference-manifest.json +4 -0
  60. package/.next/server/app/api/ide/openclaw/restore/route.js +6 -0
  61. package/.next/server/app/api/ide/openclaw/restore/route.js.map +5 -0
  62. package/.next/server/app/api/ide/openclaw/restore/route.js.nft.json +1 -0
  63. package/.next/server/app/api/ide/openclaw/restore/route_client-reference-manifest.js +2 -0
  64. package/.next/server/app/api/ide/openclaw/save/route/app-paths-manifest.json +3 -0
  65. package/.next/server/app/api/ide/openclaw/save/route/build-manifest.json +11 -0
  66. package/.next/server/app/api/ide/openclaw/save/route/server-reference-manifest.json +4 -0
  67. package/.next/server/app/api/ide/openclaw/save/route.js +7 -0
  68. package/.next/server/app/api/ide/openclaw/save/route.js.map +5 -0
  69. package/.next/server/app/api/ide/openclaw/save/route.js.nft.json +1 -0
  70. package/.next/server/app/api/ide/openclaw/save/route_client-reference-manifest.js +2 -0
  71. package/.next/server/app/api/ide/openclaw/status/route/app-paths-manifest.json +3 -0
  72. package/.next/server/app/api/ide/openclaw/status/route/build-manifest.json +11 -0
  73. package/.next/server/app/api/ide/openclaw/status/route/server-reference-manifest.json +4 -0
  74. package/.next/server/app/api/ide/openclaw/status/route.js +7 -0
  75. package/.next/server/app/api/ide/openclaw/status/route.js.map +5 -0
  76. package/.next/server/app/api/ide/openclaw/status/route.js.nft.json +1 -0
  77. package/.next/server/app/api/ide/openclaw/status/route_client-reference-manifest.js +2 -0
  78. package/.next/server/app/api/ide/openclaw/test/route/app-paths-manifest.json +3 -0
  79. package/.next/server/app/api/ide/openclaw/test/route/build-manifest.json +11 -0
  80. package/.next/server/app/api/ide/openclaw/test/route/server-reference-manifest.json +4 -0
  81. package/.next/server/app/api/ide/openclaw/test/route.js +6 -0
  82. package/.next/server/app/api/ide/openclaw/test/route.js.map +5 -0
  83. package/.next/server/app/api/ide/openclaw/test/route.js.nft.json +1 -0
  84. package/.next/server/app/api/ide/openclaw/test/route_client-reference-manifest.js +2 -0
  85. package/.next/server/app/api/ide/opencode/apply/route/app-paths-manifest.json +3 -0
  86. package/.next/server/app/api/ide/opencode/apply/route/build-manifest.json +11 -0
  87. package/.next/server/app/api/ide/opencode/apply/route/server-reference-manifest.json +4 -0
  88. package/.next/server/app/api/ide/opencode/apply/route.js +7 -0
  89. package/.next/server/app/api/ide/opencode/apply/route.js.map +5 -0
  90. package/.next/server/app/api/ide/opencode/apply/route.js.nft.json +1 -0
  91. package/.next/server/app/api/ide/opencode/apply/route_client-reference-manifest.js +2 -0
  92. package/.next/server/app/api/ide/opencode/available-models/route/app-paths-manifest.json +3 -0
  93. package/.next/server/app/api/ide/opencode/available-models/route/build-manifest.json +11 -0
  94. package/.next/server/app/api/ide/opencode/available-models/route/server-reference-manifest.json +4 -0
  95. package/.next/server/app/api/ide/opencode/available-models/route.js +7 -0
  96. package/.next/server/app/api/ide/opencode/available-models/route.js.map +5 -0
  97. package/.next/server/app/api/ide/opencode/available-models/route.js.nft.json +1 -0
  98. package/.next/server/app/api/ide/opencode/available-models/route_client-reference-manifest.js +2 -0
  99. package/.next/server/app/api/ide/opencode/preview/route/app-paths-manifest.json +3 -0
  100. package/.next/server/app/api/ide/opencode/preview/route/build-manifest.json +11 -0
  101. package/.next/server/app/api/ide/opencode/preview/route/server-reference-manifest.json +4 -0
  102. package/.next/server/app/api/ide/opencode/preview/route.js +7 -0
  103. package/.next/server/app/api/ide/opencode/preview/route.js.map +5 -0
  104. package/.next/server/app/api/ide/opencode/preview/route.js.nft.json +1 -0
  105. package/.next/server/app/api/ide/opencode/preview/route_client-reference-manifest.js +2 -0
  106. package/.next/server/app/api/ide/opencode/restore/route/app-paths-manifest.json +3 -0
  107. package/.next/server/app/api/ide/opencode/restore/route/build-manifest.json +11 -0
  108. package/.next/server/app/api/ide/opencode/restore/route/server-reference-manifest.json +4 -0
  109. package/.next/server/app/api/ide/opencode/restore/route.js +6 -0
  110. package/.next/server/app/api/ide/opencode/restore/route.js.map +5 -0
  111. package/.next/server/app/api/ide/opencode/restore/route.js.nft.json +1 -0
  112. package/.next/server/app/api/ide/opencode/restore/route_client-reference-manifest.js +2 -0
  113. package/.next/server/app/api/ide/opencode/save/route/app-paths-manifest.json +3 -0
  114. package/.next/server/app/api/ide/opencode/save/route/build-manifest.json +11 -0
  115. package/.next/server/app/api/ide/opencode/save/route/server-reference-manifest.json +4 -0
  116. package/.next/server/app/api/ide/opencode/save/route.js +7 -0
  117. package/.next/server/app/api/ide/opencode/save/route.js.map +5 -0
  118. package/.next/server/app/api/ide/opencode/save/route.js.nft.json +1 -0
  119. package/.next/server/app/api/ide/opencode/save/route_client-reference-manifest.js +2 -0
  120. package/.next/server/app/api/ide/opencode/status/route/app-paths-manifest.json +3 -0
  121. package/.next/server/app/api/ide/opencode/status/route/build-manifest.json +11 -0
  122. package/.next/server/app/api/ide/opencode/status/route/server-reference-manifest.json +4 -0
  123. package/.next/server/app/api/ide/opencode/status/route.js +7 -0
  124. package/.next/server/app/api/ide/opencode/status/route.js.map +5 -0
  125. package/.next/server/app/api/ide/opencode/status/route.js.nft.json +1 -0
  126. package/.next/server/app/api/ide/opencode/status/route_client-reference-manifest.js +2 -0
  127. package/.next/server/app/api/ide/opencode/test/route/app-paths-manifest.json +3 -0
  128. package/.next/server/app/api/ide/opencode/test/route/build-manifest.json +11 -0
  129. package/.next/server/app/api/ide/opencode/test/route/server-reference-manifest.json +4 -0
  130. package/.next/server/app/api/ide/opencode/test/route.js +6 -0
  131. package/.next/server/app/api/ide/opencode/test/route.js.map +5 -0
  132. package/.next/server/app/api/ide/opencode/test/route.js.nft.json +1 -0
  133. package/.next/server/app/api/ide/opencode/test/route_client-reference-manifest.js +2 -0
  134. package/.next/server/app/api/logs/route.js.nft.json +1 -1
  135. package/.next/server/app/api/models/route.js.nft.json +1 -1
  136. package/.next/server/app/api/providers/route.js.nft.json +1 -1
  137. package/.next/server/app/api/providers/test/route.js.nft.json +1 -1
  138. package/.next/server/app/api/service/force-stop/route.js.nft.json +1 -1
  139. package/.next/server/app/api/service/start/route.js.nft.json +1 -1
  140. package/.next/server/app/api/service/status/route.js.nft.json +1 -1
  141. package/.next/server/app/api/service/stop/route.js.nft.json +1 -1
  142. package/.next/server/app/ide/page.js +1 -1
  143. package/.next/server/app/ide/page.js.nft.json +1 -1
  144. package/.next/server/app/ide/page_client-reference-manifest.js +1 -1
  145. package/.next/server/app/ide.html +1 -1
  146. package/.next/server/app/ide.rsc +3 -3
  147. package/.next/server/app/ide.segments/_full.segment.rsc +3 -3
  148. package/.next/server/app/ide.segments/_head.segment.rsc +1 -1
  149. package/.next/server/app/ide.segments/_index.segment.rsc +2 -2
  150. package/.next/server/app/ide.segments/_tree.segment.rsc +2 -2
  151. package/.next/server/app/ide.segments/ide/__PAGE__.segment.rsc +2 -2
  152. package/.next/server/app/ide.segments/ide.segment.rsc +1 -1
  153. package/.next/server/app/index.html +1 -1
  154. package/.next/server/app/index.rsc +2 -2
  155. package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  156. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  157. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  158. package/.next/server/app/index.segments/_index.segment.rsc +2 -2
  159. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  160. package/.next/server/app/logs/page.js +1 -1
  161. package/.next/server/app/logs/page.js.nft.json +1 -1
  162. package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
  163. package/.next/server/app/logs.html +1 -1
  164. package/.next/server/app/logs.rsc +3 -3
  165. package/.next/server/app/logs.segments/_full.segment.rsc +3 -3
  166. package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
  167. package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
  168. package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
  169. package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +2 -2
  170. package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
  171. package/.next/server/app/models/page.js +1 -1
  172. package/.next/server/app/models/page.js.nft.json +1 -1
  173. package/.next/server/app/models/page_client-reference-manifest.js +1 -1
  174. package/.next/server/app/models.html +1 -1
  175. package/.next/server/app/models.rsc +2 -2
  176. package/.next/server/app/models.segments/_full.segment.rsc +2 -2
  177. package/.next/server/app/models.segments/_head.segment.rsc +1 -1
  178. package/.next/server/app/models.segments/_index.segment.rsc +2 -2
  179. package/.next/server/app/models.segments/_tree.segment.rsc +2 -2
  180. package/.next/server/app/models.segments/models/__PAGE__.segment.rsc +1 -1
  181. package/.next/server/app/models.segments/models.segment.rsc +1 -1
  182. package/.next/server/app/page.js +1 -1
  183. package/.next/server/app/page.js.nft.json +1 -1
  184. package/.next/server/app/page_client-reference-manifest.js +1 -1
  185. package/.next/server/app/providers/page.js +1 -1
  186. package/.next/server/app/providers/page.js.nft.json +1 -1
  187. package/.next/server/app/providers/page_client-reference-manifest.js +1 -1
  188. package/.next/server/app/providers.html +1 -1
  189. package/.next/server/app/providers.rsc +2 -2
  190. package/.next/server/app/providers.segments/_full.segment.rsc +2 -2
  191. package/.next/server/app/providers.segments/_head.segment.rsc +1 -1
  192. package/.next/server/app/providers.segments/_index.segment.rsc +2 -2
  193. package/.next/server/app/providers.segments/_tree.segment.rsc +2 -2
  194. package/.next/server/app/providers.segments/providers/__PAGE__.segment.rsc +1 -1
  195. package/.next/server/app/providers.segments/providers.segment.rsc +1 -1
  196. package/.next/server/app-paths-manifest.json +14 -0
  197. package/.next/server/chunks/[root-of-the-server]__001d5756._.js +3 -0
  198. package/.next/server/chunks/[root-of-the-server]__001d5756._.js.map +1 -0
  199. package/.next/server/chunks/[root-of-the-server]__05f8578b._.js +3 -0
  200. package/.next/server/chunks/[root-of-the-server]__05f8578b._.js.map +1 -0
  201. package/.next/server/chunks/[root-of-the-server]__1480f018._.js +1 -1
  202. package/.next/server/chunks/[root-of-the-server]__1480f018._.js.map +1 -1
  203. package/.next/server/chunks/[root-of-the-server]__1909f3aa._.js +1 -1
  204. package/.next/server/chunks/[root-of-the-server]__1909f3aa._.js.map +1 -1
  205. package/.next/server/chunks/[root-of-the-server]__1d4b7fc5._.js +1 -1
  206. package/.next/server/chunks/[root-of-the-server]__1d4b7fc5._.js.map +1 -1
  207. package/.next/server/chunks/[root-of-the-server]__372ef2bf._.js +1 -1
  208. package/.next/server/chunks/[root-of-the-server]__372ef2bf._.js.map +1 -1
  209. package/.next/server/chunks/[root-of-the-server]__3aaf963c._.js +1 -1
  210. package/.next/server/chunks/[root-of-the-server]__3aaf963c._.js.map +1 -1
  211. package/.next/server/chunks/[root-of-the-server]__43810962._.js +3 -0
  212. package/.next/server/chunks/[root-of-the-server]__43810962._.js.map +1 -0
  213. package/.next/server/chunks/[root-of-the-server]__55cd88b8._.js +3 -0
  214. package/.next/server/chunks/[root-of-the-server]__55cd88b8._.js.map +1 -0
  215. package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js +1 -1
  216. package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js.map +1 -1
  217. package/.next/server/chunks/[root-of-the-server]__6ce199d2._.js +1 -1
  218. package/.next/server/chunks/[root-of-the-server]__6ce199d2._.js.map +1 -1
  219. package/.next/server/chunks/[root-of-the-server]__760eaa16._.js +3 -0
  220. package/.next/server/chunks/[root-of-the-server]__760eaa16._.js.map +1 -0
  221. package/.next/server/chunks/[root-of-the-server]__772134c6._.js +1 -1
  222. package/.next/server/chunks/[root-of-the-server]__772134c6._.js.map +1 -1
  223. package/.next/server/chunks/[root-of-the-server]__7b77f523._.js +1 -1
  224. package/.next/server/chunks/[root-of-the-server]__7b77f523._.js.map +1 -1
  225. package/.next/server/chunks/[root-of-the-server]__7c298a19._.js +3 -0
  226. package/.next/server/chunks/[root-of-the-server]__7c298a19._.js.map +1 -0
  227. package/.next/server/chunks/[root-of-the-server]__85540228._.js +3 -0
  228. package/.next/server/chunks/[root-of-the-server]__85540228._.js.map +1 -0
  229. package/.next/server/chunks/[root-of-the-server]__94fe8d3c._.js +3 -0
  230. package/.next/server/chunks/[root-of-the-server]__94fe8d3c._.js.map +1 -0
  231. package/.next/server/chunks/[root-of-the-server]__97622908._.js +3 -0
  232. package/.next/server/chunks/[root-of-the-server]__97622908._.js.map +1 -0
  233. package/.next/server/chunks/[root-of-the-server]__a02e6618._.js +3 -0
  234. package/.next/server/chunks/[root-of-the-server]__a02e6618._.js.map +1 -0
  235. package/.next/server/chunks/[root-of-the-server]__a32a20a7._.js +3 -0
  236. package/.next/server/chunks/[root-of-the-server]__a32a20a7._.js.map +1 -0
  237. package/.next/server/chunks/[root-of-the-server]__af5b556a._.js +3 -0
  238. package/.next/server/chunks/[root-of-the-server]__af5b556a._.js.map +1 -0
  239. package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js +18 -18
  240. package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js.map +1 -1
  241. package/.next/server/chunks/[root-of-the-server]__cafe113e._.js +3 -0
  242. package/.next/server/chunks/[root-of-the-server]__cafe113e._.js.map +1 -0
  243. package/.next/server/chunks/[root-of-the-server]__ccfc7f1d._.js +1 -1
  244. package/.next/server/chunks/[root-of-the-server]__ccfc7f1d._.js.map +1 -1
  245. package/.next/server/chunks/[root-of-the-server]__dc8b0bed._.js +3 -0
  246. package/.next/server/chunks/[root-of-the-server]__dc8b0bed._.js.map +1 -0
  247. package/.next/server/chunks/[root-of-the-server]__f8949f88._.js +1 -1
  248. package/.next/server/chunks/[root-of-the-server]__f8949f88._.js.map +1 -1
  249. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_apply_route_actions_2cb9e4b4.js +3 -0
  250. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_apply_route_actions_2cb9e4b4.js.map +1 -0
  251. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_preview_route_actions_9814a8e4.js +3 -0
  252. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_preview_route_actions_9814a8e4.js.map +1 -0
  253. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_restore_route_actions_10ad8f9d.js +3 -0
  254. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_restore_route_actions_10ad8f9d.js.map +1 -0
  255. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_save_route_actions_044ad081.js +3 -0
  256. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_save_route_actions_044ad081.js.map +1 -0
  257. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_status_route_actions_ed9786d2.js +3 -0
  258. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_status_route_actions_ed9786d2.js.map +1 -0
  259. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_test_route_actions_ce2cb808.js +3 -0
  260. package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_test_route_actions_ce2cb808.js.map +1 -0
  261. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_apply_route_actions_6c422244.js +3 -0
  262. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_apply_route_actions_6c422244.js.map +1 -0
  263. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_preview_route_actions_256c82e0.js +3 -0
  264. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_preview_route_actions_256c82e0.js.map +1 -0
  265. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_restore_route_actions_371993d3.js +3 -0
  266. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_restore_route_actions_371993d3.js.map +1 -0
  267. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_save_route_actions_6e4c9c41.js +3 -0
  268. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_save_route_actions_6e4c9c41.js.map +1 -0
  269. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_status_route_actions_498ad77b.js +3 -0
  270. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_status_route_actions_498ad77b.js.map +1 -0
  271. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_test_route_actions_c71be510.js +3 -0
  272. package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_test_route_actions_c71be510.js.map +1 -0
  273. package/.next/server/chunks/ce889_server_app_api_ide_openclaw_available-models_route_actions_e568e70b.js +3 -0
  274. package/.next/server/chunks/ce889_server_app_api_ide_openclaw_available-models_route_actions_e568e70b.js.map +1 -0
  275. package/.next/server/chunks/ce889_server_app_api_ide_opencode_available-models_route_actions_95230db3.js +3 -0
  276. package/.next/server/chunks/ce889_server_app_api_ide_opencode_available-models_route_actions_95230db3.js.map +1 -0
  277. package/.next/server/chunks/ssr/{[root-of-the-server]__bec95712._.js → [root-of-the-server]__81937253._.js} +2 -2
  278. package/.next/server/chunks/ssr/{[root-of-the-server]__bec95712._.js.map → [root-of-the-server]__81937253._.js.map} +1 -1
  279. package/.next/server/chunks/ssr/{[root-of-the-server]__71c85955._.js → [root-of-the-server]__976ad963._.js} +2 -2
  280. package/.next/server/chunks/ssr/{[root-of-the-server]__71c85955._.js.map → [root-of-the-server]__976ad963._.js.map} +1 -1
  281. package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js +1 -1
  282. package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js.map +1 -1
  283. package/.next/server/chunks/ssr/src_app_logs_page_tsx_7b7b7b83._.js +1 -1
  284. package/.next/server/chunks/ssr/src_app_logs_page_tsx_7b7b7b83._.js.map +1 -1
  285. package/.next/server/pages/404.html +1 -1
  286. package/.next/server/pages/500.html +2 -2
  287. package/.next/static/chunks/0f120c117962200b.css +1 -0
  288. package/.next/static/chunks/64f547b3bcd3aef4.js +1 -0
  289. package/.next/static/chunks/{81c904164fe81379.js → 7c8b7cbb3339f139.js} +1 -1
  290. package/.next/static/chunks/8ed839b2e4948968.js +1 -0
  291. package/.next/types/routes.d.ts +15 -1
  292. package/.next/types/validator.ts +126 -0
  293. package/README.md +100 -111
  294. package/dist/.next/dev/types/validator.js +56 -0
  295. package/dist/.next/types/validator.js +56 -0
  296. package/dist/src/app/api/gateway/[...path]/route.js +1 -1
  297. package/dist/src/app/api/gateway/route.js +1 -1
  298. package/dist/src/app/api/ide/claude/apply/route.js +42 -31
  299. package/dist/src/app/api/ide/claude/status/route.js +6 -1
  300. package/dist/src/app/api/ide/openclaw/apply/route.js +92 -0
  301. package/dist/src/app/api/ide/openclaw/available-models/route.js +46 -0
  302. package/dist/src/app/api/ide/openclaw/build-config.js +101 -0
  303. package/dist/src/app/api/ide/openclaw/preview/route.js +49 -0
  304. package/dist/src/app/api/ide/openclaw/restore/route.js +24 -0
  305. package/dist/src/app/api/ide/openclaw/save/route.js +54 -0
  306. package/dist/src/app/api/ide/openclaw/status/route.js +139 -0
  307. package/dist/src/app/api/ide/openclaw/test/route.js +158 -0
  308. package/dist/src/app/api/ide/opencode/apply/route.js +89 -0
  309. package/dist/src/app/api/ide/opencode/available-models/route.js +46 -0
  310. package/dist/src/app/api/ide/opencode/build-config.js +54 -0
  311. package/dist/src/app/api/ide/opencode/preview/route.js +36 -0
  312. package/dist/src/app/api/ide/opencode/restore/route.js +40 -0
  313. package/dist/src/app/api/ide/opencode/save/route.js +123 -0
  314. package/dist/src/app/api/ide/opencode/status/route.js +106 -0
  315. package/dist/src/app/api/ide/opencode/test/route.js +136 -0
  316. package/dist/src/app/api/logs/route.js +2 -2
  317. package/dist/src/app/api/models/route.js +5 -5
  318. package/dist/src/app/api/providers/route.js +4 -4
  319. package/dist/src/app/api/providers/test/route.js +1 -1
  320. package/dist/src/app/api/service/start/route.js +1 -1
  321. package/dist/src/app/api/service/status/route.js +1 -1
  322. package/dist/src/app/api/service/stop/route.js +1 -1
  323. package/dist/src/app/ide/page.js +591 -81
  324. package/dist/src/app/logs/page.js +15 -1
  325. package/dist/src/cli/index.js +218 -20
  326. package/dist/src/db/database.js +56 -5
  327. package/dist/src/db/queries.js +6 -6
  328. package/dist/src/server/logger.js +22 -4
  329. package/package.json +2 -1
  330. package/src/app/api/gateway/[...path]/route.ts +1 -1
  331. package/src/app/api/gateway/route.ts +1 -1
  332. package/src/app/api/ide/claude/apply/route.ts +46 -31
  333. package/src/app/api/ide/claude/status/route.ts +12 -2
  334. package/src/app/api/ide/openclaw/apply/route.ts +103 -0
  335. package/src/app/api/ide/openclaw/available-models/route.ts +59 -0
  336. package/src/app/api/ide/openclaw/build-config.ts +152 -0
  337. package/src/app/api/ide/openclaw/preview/route.ts +57 -0
  338. package/src/app/api/ide/openclaw/restore/route.ts +27 -0
  339. package/src/app/api/ide/openclaw/save/route.ts +67 -0
  340. package/src/app/api/ide/openclaw/status/route.ts +178 -0
  341. package/src/app/api/ide/openclaw/test/route.ts +194 -0
  342. package/src/app/api/ide/opencode/apply/route.ts +92 -0
  343. package/src/app/api/ide/opencode/available-models/route.ts +59 -0
  344. package/src/app/api/ide/opencode/build-config.ts +69 -0
  345. package/src/app/api/ide/opencode/preview/route.ts +40 -0
  346. package/src/app/api/ide/opencode/restore/route.ts +52 -0
  347. package/src/app/api/ide/opencode/save/route.ts +131 -0
  348. package/src/app/api/ide/opencode/status/route.ts +128 -0
  349. package/src/app/api/ide/opencode/test/route.ts +145 -0
  350. package/src/app/api/logs/route.ts +2 -2
  351. package/src/app/api/models/route.ts +5 -5
  352. package/src/app/api/providers/route.ts +4 -4
  353. package/src/app/api/providers/test/route.ts +1 -1
  354. package/src/app/api/service/start/route.ts +1 -1
  355. package/src/app/api/service/status/route.ts +1 -1
  356. package/src/app/api/service/stop/route.ts +1 -1
  357. package/src/app/globals.css +17 -0
  358. package/src/app/ide/page.tsx +1535 -132
  359. package/src/app/logs/page.tsx +17 -5
  360. package/src/cli/index.ts +228 -25
  361. package/src/db/database.ts +60 -8
  362. package/src/db/queries.ts +6 -6
  363. package/src/server/logger.ts +19 -4
  364. package/.next/static/chunks/6418ca50028376b7.css +0 -1
  365. package/.next/static/chunks/9ec3b97741b6575e.js +0 -1
  366. /package/.next/static/{PkfqdzwOZgX-UhSNUuhdp → dYin74gcpdlg8TGoGv-_d}/_buildManifest.js +0 -0
  367. /package/.next/static/{PkfqdzwOZgX-UhSNUuhdp → dYin74gcpdlg8TGoGv-_d}/_clientMiddlewareManifest.json +0 -0
  368. /package/.next/static/{PkfqdzwOZgX-UhSNUuhdp → dYin74gcpdlg8TGoGv-_d}/_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,160 @@ 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 Handlers
108
353
  const handleApply = async () => {
109
354
  setApplying(true);
110
355
  try {
@@ -145,6 +390,7 @@ export default function IDEConfigPage() {
145
390
 
146
391
  if (data.success) {
147
392
  showToast('配置已还原', 'success');
393
+ setClaudeConfigSubTab('current');
148
394
  await loadStatus();
149
395
  } else {
150
396
  showToast('还原失败: ' + (data.error || '未知错误'), 'error');
@@ -234,6 +480,228 @@ export default function IDEConfigPage() {
234
480
  }
235
481
  };
236
482
 
483
+ // OpenCode Handlers
484
+ const opencodeEnabled = opencodeStatus?.applied && opencodeStatus?.routerProvider === 'aar';
485
+
486
+ const handleOpenCodeApply = async () => {
487
+ setOpencodeApplying(true);
488
+ try {
489
+ const res = await fetch('/api/ide/opencode/apply', {
490
+ method: 'POST',
491
+ headers: { 'Content-Type': 'application/json' },
492
+ body: JSON.stringify({
493
+ default_model: opencodeDefaultModel,
494
+ ...(opencodePreviewText.trim() ? { config: opencodePreviewText } : {}),
495
+ }),
496
+ });
497
+ const data = await res.json();
498
+ if (data.success) {
499
+ showToast(data.message || 'OpenCode 配置应用成功', 'success');
500
+ await loadOpenCodeStatus();
501
+ } else {
502
+ showToast('应用失败: ' + (data.error || '未知错误'), 'error');
503
+ }
504
+ } catch (error) {
505
+ console.error('Failed to apply OpenCode config:', error);
506
+ showToast('应用失败: 网络错误', 'error');
507
+ } finally {
508
+ setOpencodeApplying(false);
509
+ }
510
+ };
511
+
512
+ const handleOpenCodeRestore = async () => {
513
+ setOpencodeRestoring(true);
514
+ try {
515
+ const res = await fetch('/api/ide/opencode/restore', { method: 'POST' });
516
+ const data = await res.json();
517
+ if (data.success) {
518
+ showToast('OpenCode 配置已还原', 'success');
519
+ setOpencodeConfigSubTab('current');
520
+ await loadOpenCodeStatus();
521
+ } else {
522
+ showToast('还原失败: ' + (data.error || '未知错误'), 'error');
523
+ }
524
+ } catch (error) {
525
+ console.error('Failed to restore OpenCode config:', error);
526
+ showToast('还原失败: 网络错误', 'error');
527
+ } finally {
528
+ setOpencodeRestoring(false);
529
+ }
530
+ };
531
+
532
+ const handleOpenCodeTest = async () => {
533
+ if (opencodeAbortRef.current) {
534
+ opencodeAbortRef.current.abort();
535
+ opencodeAbortRef.current = null;
536
+ }
537
+ opencodeAbortRef.current = new AbortController();
538
+ setOpencodeTesting(true);
539
+ setOpencodeTestResult(null);
540
+ try {
541
+ const res = await fetch('/api/ide/opencode/test', { signal: opencodeAbortRef.current.signal });
542
+ const data = await res.json();
543
+ setOpencodeTestResult(data);
544
+ if (data.success) showToast('OpenCode 配置测试通过', 'success');
545
+ else showToast('配置验证失败: ' + (data.error || data.suggestion || '请检查配置'), 'error');
546
+ } catch (err: any) {
547
+ if (err.name === 'AbortError') showToast('测试已取消', 'info');
548
+ else {
549
+ showToast('测试失败: 网络错误', 'error');
550
+ setOpencodeTestResult({ error: 'Network error' });
551
+ }
552
+ } finally {
553
+ if (opencodeAbortRef.current?.signal.aborted === false) opencodeAbortRef.current = null;
554
+ setOpencodeTesting(false);
555
+ }
556
+ };
557
+
558
+ const handleOpenCodeSave = async () => {
559
+ setOpencodeSaving(true);
560
+ try {
561
+ const res = await fetch('/api/ide/opencode/save', {
562
+ method: 'POST',
563
+ headers: { 'Content-Type': 'application/json' },
564
+ body: JSON.stringify({
565
+ default_model: opencodeDefaultModel,
566
+ ...(opencodePreviewText.trim() ? { config: opencodePreviewText } : {}),
567
+ }),
568
+ });
569
+ const data = await res.json();
570
+ if (data.success) {
571
+ showToast(data.saveType === 'temp' ? '配置已保存(临时)' : '配置已更新到 OpenCode', 'success');
572
+ await loadOpenCodeStatus();
573
+ } else {
574
+ showToast('保存失败: ' + (data.error || '未知错误'), 'error');
575
+ }
576
+ } catch (error) {
577
+ console.error('Failed to save OpenCode config:', error);
578
+ showToast('保存失败: 网络错误', 'error');
579
+ } finally {
580
+ setOpencodeSaving(false);
581
+ }
582
+ };
583
+
584
+ const handleOpenCodeCancelTest = () => {
585
+ if (opencodeAbortRef.current) {
586
+ opencodeAbortRef.current.abort();
587
+ opencodeAbortRef.current = null;
588
+ }
589
+ };
590
+
591
+ // OpenClaw Handlers
592
+ const openclawEnabled = openclawStatus?.applied && openclawStatus?.routerProvider === 'aar';
593
+
594
+ const handleOpenClawApply = async () => {
595
+ setOpenclawApplying(true);
596
+ try {
597
+ const res = await fetch('/api/ide/openclaw/apply', {
598
+ method: 'POST',
599
+ headers: { 'Content-Type': 'application/json' },
600
+ body: JSON.stringify({
601
+ default_model: openclawDefaultModel,
602
+ default_fallbacks: openclawDefaultFallbacks,
603
+ image_model: openclawImageModel || undefined,
604
+ image_fallbacks: openclawImageFallbacks,
605
+ ...(openclawPreviewText.trim() ? { config: openclawPreviewText } : {}),
606
+ }),
607
+ });
608
+ const data = await res.json();
609
+ if (data.success) {
610
+ showToast(data.message || 'OpenClaw 配置应用成功', 'success');
611
+ await loadOpenClawStatus();
612
+ } else {
613
+ showToast('应用失败: ' + (data.error || '未知错误'), 'error');
614
+ }
615
+ } catch (error) {
616
+ console.error('Failed to apply OpenClaw config:', error);
617
+ showToast('应用失败: 网络错误', 'error');
618
+ } finally {
619
+ setOpenclawApplying(false);
620
+ }
621
+ };
622
+
623
+ const handleOpenClawRestore = async () => {
624
+ setOpenclawRestoring(true);
625
+ try {
626
+ const res = await fetch('/api/ide/openclaw/restore', { method: 'POST' });
627
+ const data = await res.json();
628
+ if (data.success) {
629
+ showToast('OpenClaw 配置已还原', 'success');
630
+ setOpenclawConfigSubTab('current');
631
+ await loadOpenClawStatus();
632
+ } else {
633
+ showToast('还原失败: ' + (data.error || '未知错误'), 'error');
634
+ }
635
+ } catch (error) {
636
+ console.error('Failed to restore OpenClaw config:', error);
637
+ showToast('还原失败: 网络错误', 'error');
638
+ } finally {
639
+ setOpenclawRestoring(false);
640
+ }
641
+ };
642
+
643
+ const handleOpenClawTest = async () => {
644
+ if (openclawAbortRef.current) {
645
+ openclawAbortRef.current.abort();
646
+ openclawAbortRef.current = null;
647
+ }
648
+ openclawAbortRef.current = new AbortController();
649
+ setOpenclawTesting(true);
650
+ setOpenclawTestResult(null);
651
+ try {
652
+ const res = await fetch('/api/ide/openclaw/test', { signal: openclawAbortRef.current.signal });
653
+ const data = await res.json();
654
+ setOpenclawTestResult(data);
655
+ if (data.success) showToast('OpenClaw 配置测试通过', 'success');
656
+ else showToast('配置验证失败: ' + (data.error || data.suggestion || '请检查配置'), 'error');
657
+ } catch (err: any) {
658
+ if (err.name === 'AbortError') showToast('测试已取消', 'info');
659
+ else {
660
+ showToast('测试失败: 网络错误', 'error');
661
+ setOpenclawTestResult({ error: 'Network error' });
662
+ }
663
+ } finally {
664
+ if (openclawAbortRef.current?.signal.aborted === false) openclawAbortRef.current = null;
665
+ setOpenclawTesting(false);
666
+ }
667
+ };
668
+
669
+ const handleOpenClawSave = async () => {
670
+ setOpenclawSaving(true);
671
+ try {
672
+ const res = await fetch('/api/ide/openclaw/save', {
673
+ method: 'POST',
674
+ headers: { 'Content-Type': 'application/json' },
675
+ body: JSON.stringify({
676
+ default_model: openclawDefaultModel,
677
+ default_fallbacks: openclawDefaultFallbacks,
678
+ image_model: openclawImageModel || undefined,
679
+ image_fallbacks: openclawImageFallbacks,
680
+ ...(openclawPreviewText.trim() ? { config: openclawPreviewText } : {}),
681
+ }),
682
+ });
683
+ const data = await res.json();
684
+ if (data.success) {
685
+ showToast(data.saveType === 'temp' ? '配置已保存(临时)' : '配置已更新到 OpenClaw', 'success');
686
+ await loadOpenClawStatus();
687
+ } else {
688
+ showToast('保存失败: ' + (data.error || '未知错误'), 'error');
689
+ }
690
+ } catch (error) {
691
+ console.error('Failed to save OpenClaw config:', error);
692
+ showToast('保存失败: 网络错误', 'error');
693
+ } finally {
694
+ setOpenclawSaving(false);
695
+ }
696
+ };
697
+
698
+ const handleOpenClawCancelTest = () => {
699
+ if (openclawAbortRef.current) {
700
+ openclawAbortRef.current.abort();
701
+ openclawAbortRef.current = null;
702
+ }
703
+ };
704
+
237
705
  if (loading) {
238
706
  return (
239
707
  <div className="min-h-screen flex items-center justify-center bg-slate-50/80">
@@ -257,7 +725,7 @@ export default function IDEConfigPage() {
257
725
  v1.0
258
726
  </span>
259
727
  </div>
260
- <p className="text-sm text-slate-500">配置 Claude Code 和 Cursor 的模型代理设置</p>
728
+ <p className="text-sm text-slate-500">配置 Claude Code、OpenCodeOpenClaw 的模型代理设置</p>
261
729
  </div>
262
730
 
263
731
  <div className="bg-white/60 backdrop-blur-md rounded-xl border border-slate-200/50 shadow-sm">
@@ -276,6 +744,32 @@ export default function IDEConfigPage() {
276
744
  <span className="absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full"></span>
277
745
  )}
278
746
  </button>
747
+ <button
748
+ onClick={() => setActiveTab('opencode')}
749
+ className={`relative px-4 py-3.5 text-sm font-medium transition-all duration-200 ${
750
+ activeTab === 'opencode'
751
+ ? 'text-slate-900'
752
+ : 'text-slate-400 hover:text-slate-600'
753
+ }`}
754
+ >
755
+ OpenCode
756
+ {activeTab === 'opencode' && (
757
+ <span className="absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full"></span>
758
+ )}
759
+ </button>
760
+ <button
761
+ onClick={() => setActiveTab('openclaw')}
762
+ className={`relative px-4 py-3.5 text-sm font-medium transition-all duration-200 ${
763
+ activeTab === 'openclaw'
764
+ ? 'text-slate-900'
765
+ : 'text-slate-400 hover:text-slate-600'
766
+ }`}
767
+ >
768
+ OpenClaw
769
+ {activeTab === 'openclaw' && (
770
+ <span className="absolute bottom-0 left-4 right-4 h-[2px] bg-slate-900 rounded-full"></span>
771
+ )}
772
+ </button>
279
773
  <button
280
774
  onClick={() => setActiveTab('cursor')}
281
775
  disabled
@@ -316,13 +810,13 @@ export default function IDEConfigPage() {
316
810
  <span className="text-sm font-medium text-slate-700">
317
811
  {status.applied ? '已配置' : '未配置'}
318
812
  </span>
319
- {status.applied && status.matchCurrentGateway !== undefined && (
813
+ {status.applied && status.routerProvider === 'aar' && status.matchCurrentGateway !== undefined && (
320
814
  <span className={`inline-flex items-center px-2 py-0.5 text-[10px] font-medium rounded-full border ${
321
815
  status.matchCurrentGateway
322
816
  ? 'bg-emerald-50/80 text-emerald-700 border-emerald-200/60'
323
817
  : 'bg-amber-50/80 text-amber-700 border-amber-200/60'
324
818
  }`}>
325
- {status.matchCurrentGateway ? '匹配当前网关' : '不匹配'}
819
+ {status.matchCurrentGateway ? '匹配配置' : '不匹配'}
326
820
  </span>
327
821
  )}
328
822
  </div>
@@ -455,15 +949,11 @@ export default function IDEConfigPage() {
455
949
  </div>
456
950
  </div>
457
951
 
458
- <div className="grid grid-cols-3 gap-3 pt-1">
952
+ <div className={`grid gap-3 pt-1 ${status?.backupExists ? 'grid-cols-3' : 'grid-cols-2'}`}>
459
953
  <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
- }`}
954
+ onClick={handleApply}
955
+ disabled={applying}
956
+ 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
957
  >
468
958
  {applying ? (
469
959
  <>
@@ -471,24 +961,43 @@ export default function IDEConfigPage() {
471
961
  <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
472
962
  <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
963
  </svg>
474
- {enabled ? '还原中' : '应用中'}
964
+ 应用中
475
965
  </>
476
966
  ) : (
477
967
  <>
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 ? '还原配置' : '应用配置'}
968
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
969
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
970
+ </svg>
971
+ {enabled ? '更新配置' : '应用配置'}
488
972
  </>
489
973
  )}
490
974
  </button>
491
975
 
976
+ {status?.backupExists && (
977
+ <button
978
+ onClick={handleRestore}
979
+ disabled={restoring}
980
+ 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"
981
+ >
982
+ {restoring ? (
983
+ <>
984
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
985
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
986
+ <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>
987
+ </svg>
988
+ 还原中
989
+ </>
990
+ ) : (
991
+ <>
992
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
993
+ <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" />
994
+ </svg>
995
+ 还原配置
996
+ </>
997
+ )}
998
+ </button>
999
+ )}
1000
+
492
1001
  {testing ? (
493
1002
  <button
494
1003
  onClick={handleCancelTest}
@@ -511,75 +1020,25 @@ export default function IDEConfigPage() {
511
1020
  测试配置
512
1021
  </button>
513
1022
  )}
1023
+ </div>
514
1024
 
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>
1025
+ {testResult && (
1026
+ <div className={`rounded-lg border px-4 py-3 ${
1027
+ testResult.success
1028
+ ? 'bg-emerald-50/60 border-emerald-200/60'
1029
+ : 'bg-rose-50/60 border-rose-200/60'
1030
+ }`}>
1031
+ <div className="flex items-start gap-3">
1032
+ <div className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 ${
1033
+ testResult.success ? 'bg-emerald-500' : 'bg-rose-500'
1034
+ }`}>
1035
+ {testResult.success ? (
1036
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1037
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
526
1038
  </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>
565
-
566
- {testResult && (
567
- <div className={`rounded-lg border px-4 py-3 ${
568
- testResult.success
569
- ? 'bg-emerald-50/60 border-emerald-200/60'
570
- : 'bg-rose-50/60 border-rose-200/60'
571
- }`}>
572
- <div className="flex items-start gap-3">
573
- <div className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 ${
574
- testResult.success ? 'bg-emerald-500' : 'bg-rose-500'
575
- }`}>
576
- {testResult.success ? (
577
- <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
578
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
579
- </svg>
580
- ) : (
581
- <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
582
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
1039
+ ) : (
1040
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1041
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
583
1042
  </svg>
584
1043
  )}
585
1044
  </div>
@@ -692,46 +1151,421 @@ export default function IDEConfigPage() {
692
1151
  </div>
693
1152
  )}
694
1153
 
695
- <details className="group">
1154
+ <details className="group" open>
696
1155
  <summary className="cursor-pointer text-[11px] text-slate-500 hover:text-slate-700 list-none flex items-center gap-1.5 py-1">
697
1156
  <svg className="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
698
1157
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
699
1158
  </svg>
700
- <span>配置预览</span>
1159
+ <span>配置</span>
701
1160
  </summary>
702
1161
  <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>
1162
+ <div className="flex border border-slate-200 rounded-t-lg overflow-hidden bg-slate-100/90">
1163
+ <button
1164
+ type="button"
1165
+ onClick={() => setClaudeConfigSubTab('preview')}
1166
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
1167
+ claudeConfigSubTab === 'preview'
1168
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
1169
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
1170
+ }`}
1171
+ >
1172
+ 预览
1173
+ </button>
1174
+ <button
1175
+ type="button"
1176
+ onClick={() => setClaudeConfigSubTab('current')}
1177
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
1178
+ claudeConfigSubTab === 'current'
1179
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
1180
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
1181
+ }`}
1182
+ >
1183
+ 生效中
1184
+ </button>
1185
+ </div>
1186
+ <div className="relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0">
1187
+ {claudeConfigSubTab === 'preview' ? (
1188
+ <>
1189
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
1190
+ <div className="flex gap-1.5">
1191
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1192
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1193
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1194
+ </div>
1195
+ <span className="text-[10px] font-medium text-slate-400 ml-2">settings.json(仅当前网关相关,应用时会合并进现有配置)</span>
1196
+ </div>
1197
+ <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">
1198
+ {JSON.stringify(
1199
+ {
1200
+ router_provider: 'aar',
1201
+ env: {
1202
+ ANTHROPIC_AUTH_TOKEN: status?.currentGatewayApiKey ?? 'your-gateway-api-key',
1203
+ ANTHROPIC_BASE_URL: status?.currentGatewayAddress ?? 'http://localhost:1357',
1204
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: haikuModel,
1205
+ ANTHROPIC_DEFAULT_SONNET_MODEL: sonnetModel,
1206
+ ANTHROPIC_DEFAULT_OPUS_MODEL: opusModel,
1207
+ ANTHROPIC_MODEL: defaultModel,
1208
+ ANTHROPIC_REASONING_MODEL: reasoningModel,
1209
+ API_TIMEOUT_MS: '3000000',
1210
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1,
1211
+ hasCompletedOnboarding: true,
1212
+ },
1213
+ },
1214
+ null,
1215
+ 2
1216
+ )}
1217
+ </pre>
1218
+ </>
1219
+ ) : (
1220
+ <>
1221
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
1222
+ <div className="flex gap-1.5">
1223
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1224
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1225
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1226
+ </div>
1227
+ <span className="text-[10px] font-medium text-slate-400 ml-2">settings.json(当前磁盘内容,还原后即更新)</span>
1228
+ </div>
1229
+ {status?.config != null ? (
1230
+ <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">
1231
+ {JSON.stringify(status.config, null, 2)}
1232
+ </pre>
1233
+ ) : (
1234
+ <div className="p-3.5 text-[10px] text-slate-500 font-mono">暂无配置文件</div>
1235
+ )}
1236
+ </>
1237
+ )}
1238
+ </div>
1239
+ </div>
1240
+ </details>
1241
+
1242
+ <div className="rounded-lg border border-slate-200/50 bg-slate-50/60 px-4 py-3">
1243
+ <div className="flex items-center gap-2 mb-2.5">
1244
+ <svg className="w-4 h-4 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1245
+ <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" />
1246
+ </svg>
1247
+ <h3 className="text-xs font-semibold text-slate-700">使用说明</h3>
1248
+ </div>
1249
+ <ul className="space-y-1.5 text-[10px] text-slate-600">
1250
+ <li className="flex gap-2">
1251
+ <span className="text-slate-400">1.</span>
1252
+ <span>「生效中」为当前磁盘上的 settings.json;「预览」为应用后将写入的内容</span>
1253
+ </li>
1254
+ <li className="flex gap-2">
1255
+ <span className="text-slate-400">2.</span>
1256
+ <span>为每个模型类别选择路由模型后,在「预览」中查看,点击「应用配置」或「更新配置」写入</span>
1257
+ </li>
1258
+ <li className="flex gap-2">
1259
+ <span className="text-slate-400">3.</span>
1260
+ <span>配置文件位于 ~/.claude/settings.json</span>
1261
+ </li>
1262
+ <li className="flex gap-2">
1263
+ <span className="text-slate-400">4.</span>
1264
+ <span>若已应用过 AAR,会显示「更新配置」;有备份时「还原配置」可恢复,还原后请在「生效中」查看</span>
1265
+ </li>
1266
+ </ul>
1267
+ </div>
1268
+ </div>
1269
+ )}
1270
+
1271
+ {activeTab === 'opencode' && (
1272
+ <div className="space-y-6">
1273
+ {opencodeStatus && (
1274
+ <div className="flex items-center justify-between py-3 px-4 bg-white/60 rounded-lg border border-slate-200/50">
1275
+ <div className="flex items-center gap-3">
1276
+ <div className={`w-6 h-6 rounded-full flex items-center justify-center ${
1277
+ opencodeStatus.applied
1278
+ ? 'bg-emerald-100 text-emerald-600'
1279
+ : 'bg-slate-100 text-slate-400'
1280
+ }`}>
1281
+ {opencodeStatus.applied ? (
1282
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1283
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
1284
+ </svg>
1285
+ ) : (
1286
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1287
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01" />
1288
+ </svg>
1289
+ )}
1290
+ </div>
1291
+ <div className="flex items-center gap-2.5">
1292
+ <span className="text-sm font-medium text-slate-700">
1293
+ {opencodeStatus.applied ? '已配置' : '未配置'}
1294
+ </span>
1295
+ {opencodeStatus.applied && opencodeStatus.matchCurrentGateway !== undefined && (
1296
+ <span className={`inline-flex items-center px-2 py-0.5 text-[10px] font-medium rounded-full border ${
1297
+ opencodeStatus.matchCurrentGateway
1298
+ ? 'bg-emerald-50/80 text-emerald-700 border-emerald-200/60'
1299
+ : 'bg-amber-50/80 text-amber-700 border-amber-200/60'
1300
+ }`}>
1301
+ {opencodeStatus.matchCurrentGateway ? '匹配当前网关' : '不匹配'}
1302
+ </span>
1303
+ )}
1304
+ </div>
1305
+ </div>
1306
+ {opencodeStatus.lastUpdated && (
1307
+ <div className="text-[11px] text-slate-400 font-mono">
1308
+ {new Date(opencodeStatus.lastUpdated).toLocaleString('zh-CN', {
1309
+ month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit'
1310
+ })}
1311
+ </div>
1312
+ )}
1313
+ </div>
1314
+ )}
1315
+
1316
+ <div>
1317
+ <h2 className="text-[11px] font-medium text-slate-400 mb-5">默认模型</h2>
1318
+ <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">
1319
+ <label htmlFor="opencode-default-model" className="cursor-pointer">
1320
+ <div className="flex items-center gap-2.5 mb-2.5">
1321
+ <div className="w-7 h-7 rounded-lg bg-slate-100/80 text-slate-600 flex items-center justify-center">
1322
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1323
+ <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" />
1324
+ </svg>
1325
+ </div>
1326
+ <span className="text-[11px] font-medium text-slate-700">OpenCode 使用的默认模型</span>
1327
+ </div>
1328
+ <div className="relative">
1329
+ <select
1330
+ id="opencode-default-model"
1331
+ value={opencodeDefaultModel}
1332
+ onChange={(e) => setOpencodeDefaultModel(e.target.value)}
1333
+ 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"
1334
+ >
1335
+ {Object.entries(opencodeModels).map(([provider, providerModels]) => (
1336
+ <optgroup key={provider} label={provider}>
1337
+ {providerModels.map((model) => (
1338
+ <option key={model.id} value={model.model_id}>
1339
+ {model.name}
1340
+ </option>
1341
+ ))}
1342
+ </optgroup>
1343
+ ))}
1344
+ </select>
1345
+ <div className="absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none">
1346
+ <svg className="w-3 h-3 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1347
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1348
+ </svg>
709
1349
  </div>
710
- <span className="text-[10px] font-medium text-slate-400 ml-2">settings.json</span>
711
1350
  </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
1351
+ </label>
1352
+ </div>
1353
+ </div>
1354
+
1355
+ <div className={`grid gap-3 pt-1 ${opencodeEnabled ? 'grid-cols-3' : 'grid-cols-2'}`}>
1356
+ <button
1357
+ onClick={handleOpenCodeApply}
1358
+ disabled={opencodeApplying}
1359
+ 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 ${
1360
+ opencodeEnabled
1361
+ ? 'bg-white border-slate-200/80 text-slate-700 hover:border-slate-300 hover:bg-slate-50'
1362
+ : 'bg-slate-900 border-slate-900 text-white hover:bg-slate-800'
1363
+ }`}
1364
+ >
1365
+ {opencodeApplying ? (
1366
+ <>
1367
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
1368
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
1369
+ <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>
1370
+ </svg>
1371
+ 应用中
1372
+ </>
1373
+ ) : (
1374
+ <>
1375
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1376
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
1377
+ </svg>
1378
+ {opencodeEnabled ? '更新配置' : '应用配置'}
1379
+ </>
1380
+ )}
1381
+ </button>
1382
+
1383
+ {opencodeEnabled && (
1384
+ <button
1385
+ onClick={handleOpenCodeRestore}
1386
+ disabled={opencodeRestoring}
1387
+ 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"
1388
+ >
1389
+ {opencodeRestoring ? (
1390
+ <>
1391
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
1392
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
1393
+ <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>
1394
+ </svg>
1395
+ 还原中
1396
+ </>
1397
+ ) : (
1398
+ <>
1399
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1400
+ <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" />
1401
+ </svg>
1402
+ 还原配置
1403
+ </>
1404
+ )}
1405
+ </button>
1406
+ )}
1407
+
1408
+ {opencodeTesting ? (
1409
+ <button
1410
+ onClick={handleOpenCodeCancelTest}
1411
+ 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"
1412
+ >
1413
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
1414
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
1415
+ <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>
1416
+ </svg>
1417
+ 取消测试
1418
+ </button>
1419
+ ) : (
1420
+ <button
1421
+ onClick={handleOpenCodeTest}
1422
+ 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"
1423
+ >
1424
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1425
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
1426
+ </svg>
1427
+ 测试配置
1428
+ </button>
1429
+ )}
1430
+ </div>
1431
+
1432
+ {opencodeTestResult && (
1433
+ <div className={`rounded-lg border px-4 py-3 ${
1434
+ opencodeTestResult.success
1435
+ ? 'bg-emerald-50/60 border-emerald-200/60'
1436
+ : 'bg-rose-50/60 border-rose-200/60'
1437
+ }`}>
1438
+ <div className="flex items-start gap-3">
1439
+ <div className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 ${
1440
+ opencodeTestResult.success ? 'bg-emerald-500' : 'bg-rose-500'
1441
+ }`}>
1442
+ {opencodeTestResult.success ? (
1443
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1444
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
1445
+ </svg>
1446
+ ) : (
1447
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1448
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
1449
+ </svg>
1450
+ )}
1451
+ </div>
1452
+ <div className="flex-1">
1453
+ <div className="text-xs font-medium mb-1">
1454
+ {opencodeTestResult.success ? '配置验证成功' : '配置验证失败'}
1455
+ </div>
1456
+ <p className={`text-[11px] ${opencodeTestResult.success ? 'text-emerald-700/80' : 'text-rose-700/80'}`}>
1457
+ {opencodeTestResult.success
1458
+ ? (opencodeTestResult.opencodeTest?.message || 'OpenCode 可以正常工作')
1459
+ : (opencodeTestResult.opencodeTest?.error || opencodeTestResult.error || opencodeTestResult.suggestion || '请检查配置')}
1460
+ </p>
1461
+ {opencodeTestResult.checks && (
1462
+ <div className="mt-3 pt-3 border-t border-white/40">
1463
+ <div className="grid grid-cols-3 gap-2">
1464
+ {Object.entries(opencodeTestResult.checks).map(([key, value]) => {
1465
+ const labels: Record<string, string> = {
1466
+ hasBaseUrl: '网关',
1467
+ hasApiKey: 'API Key',
1468
+ hasDefaultModel: '默认模型',
1469
+ hasModels: '模型列表',
1470
+ };
1471
+ return (
1472
+ <div key={key} className="flex items-center gap-1.5">
1473
+ {value ? (
1474
+ <svg className="w-3 h-3 text-emerald-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1475
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
1476
+ </svg>
1477
+ ) : (
1478
+ <svg className="w-3 h-3 text-rose-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1479
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
1480
+ </svg>
1481
+ )}
1482
+ <span className="text-[10px] text-slate-600">{labels[key] || key}</span>
1483
+ </div>
1484
+ );
1485
+ })}
1486
+ </div>
1487
+ </div>
733
1488
  )}
734
- </pre>
1489
+ </div>
1490
+ </div>
1491
+ </div>
1492
+ )}
1493
+
1494
+ <details className="group" open>
1495
+ <summary className="cursor-pointer text-[11px] text-slate-500 hover:text-slate-700 list-none flex items-center gap-1.5 py-1">
1496
+ <svg className="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1497
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
1498
+ </svg>
1499
+ <span>配置</span>
1500
+ </summary>
1501
+ <div className="mt-3">
1502
+ <div className="flex border border-slate-200 rounded-t-lg overflow-hidden bg-slate-100/90">
1503
+ <button
1504
+ type="button"
1505
+ onClick={() => setOpencodeConfigSubTab('preview')}
1506
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
1507
+ opencodeConfigSubTab === 'preview'
1508
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
1509
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
1510
+ }`}
1511
+ >
1512
+ 预览
1513
+ </button>
1514
+ <button
1515
+ type="button"
1516
+ onClick={() => setOpencodeConfigSubTab('current')}
1517
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
1518
+ opencodeConfigSubTab === 'current'
1519
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
1520
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
1521
+ }`}
1522
+ >
1523
+ 生效中
1524
+ </button>
1525
+ </div>
1526
+ <div className="relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0">
1527
+ {opencodeConfigSubTab === 'current' ? (
1528
+ <>
1529
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
1530
+ <div className="flex gap-1.5">
1531
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1532
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1533
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1534
+ </div>
1535
+ <span className="text-[10px] font-medium text-slate-400 ml-2">opencode.json(当前磁盘内容,还原后即更新)</span>
1536
+ </div>
1537
+ {opencodeStatus?.config != null ? (
1538
+ <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">
1539
+ {JSON.stringify(opencodeStatus.config, null, 2)}
1540
+ </pre>
1541
+ ) : (
1542
+ <div className="p-3.5 text-[10px] text-slate-500 font-mono">暂无配置文件</div>
1543
+ )}
1544
+ </>
1545
+ ) : (
1546
+ <>
1547
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
1548
+ <div className="flex gap-1.5">
1549
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1550
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1551
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
1552
+ </div>
1553
+ <span className="text-[10px] font-medium text-slate-400 ml-2">opencode.json(应用后将写入的合并结果)</span>
1554
+ </div>
1555
+ {opencodePreviewLoading ? (
1556
+ <div className="p-3.5 text-[10px] text-slate-400 font-mono">加载中...</div>
1557
+ ) : (
1558
+ <textarea
1559
+ 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
1560
+ [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full"
1561
+ placeholder="暂无预览"
1562
+ spellCheck={false}
1563
+ value={opencodePreviewText}
1564
+ onChange={(e) => setOpencodePreviewText(e.target.value)}
1565
+ />
1566
+ )}
1567
+ </>
1568
+ )}
735
1569
  </div>
736
1570
  </div>
737
1571
  </details>
@@ -746,36 +1580,605 @@ export default function IDEConfigPage() {
746
1580
  <ul className="space-y-1.5 text-[10px] text-slate-600">
747
1581
  <li className="flex gap-2">
748
1582
  <span className="text-slate-400">1.</span>
749
- <span>为每个模型类别选择对应的路由模型</span>
1583
+ <span>「生效中」为当前磁盘上的 opencode.json;「预览」为点击应用后将写入的合并结果(含 AAR 网关)</span>
750
1584
  </li>
751
1585
  <li className="flex gap-2">
752
1586
  <span className="text-slate-400">2.</span>
753
- <span>下方配置预览会实时更新</span>
1587
+ <span>选择默认模型后,在「预览」中查看合并结果,点击「应用配置」写入</span>
754
1588
  </li>
755
1589
  <li className="flex gap-2">
756
1590
  <span className="text-slate-400">3.</span>
757
- <span>点击"保存配置"可临时保存当前选择</span>
1591
+ <span>配置文件位于 ~/.config/opencode/opencode.json(或 Windows %APPDATA%/opencode)</span>
758
1592
  </li>
759
1593
  <li className="flex gap-2">
760
1594
  <span className="text-slate-400">4.</span>
761
- <span>点击"应用配置"将设置写入 Claude Code</span>
1595
+ <span>「还原配置」恢复为应用前的备份;还原后请在「生效中」查看当前内容</span>
1596
+ </li>
1597
+ </ul>
1598
+ </div>
1599
+ </div>
1600
+ )}
1601
+
1602
+ {activeTab === 'openclaw' && (
1603
+ <div className="space-y-6">
1604
+ {openclawStatus && (
1605
+ <div className="flex items-center justify-between py-3 px-4 bg-white/60 rounded-lg border border-slate-200/50">
1606
+ <div className="flex items-center gap-3">
1607
+ <div className={`w-6 h-6 rounded-full flex items-center justify-center ${
1608
+ openclawStatus.applied
1609
+ ? 'bg-emerald-100 text-emerald-600'
1610
+ : 'bg-slate-100 text-slate-400'
1611
+ }`}>
1612
+ {openclawStatus.applied ? (
1613
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1614
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
1615
+ </svg>
1616
+ ) : (
1617
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1618
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01" />
1619
+ </svg>
1620
+ )}
1621
+ </div>
1622
+ <div className="flex items-center gap-2.5">
1623
+ <span className="text-sm font-medium text-slate-700">
1624
+ {openclawStatus.applied ? '已配置' : '未配置'}
1625
+ </span>
1626
+ {openclawStatus.applied && openclawStatus.matchCurrentGateway !== undefined && (
1627
+ <span className={`inline-flex items-center px-2 py-0.5 text-[10px] font-medium rounded-full border ${
1628
+ openclawStatus.matchCurrentGateway
1629
+ ? 'bg-emerald-50/80 text-emerald-700 border-emerald-200/60'
1630
+ : 'bg-amber-50/80 text-amber-700 border-amber-200/60'
1631
+ }`}>
1632
+ {openclawStatus.matchCurrentGateway ? '匹配当前网关' : '不匹配'}
1633
+ </span>
1634
+ )}
1635
+ </div>
1636
+ </div>
1637
+ {openclawStatus.lastUpdated && (
1638
+ <div className="text-[11px] text-slate-400 font-mono">
1639
+ {new Date(openclawStatus.lastUpdated).toLocaleString('zh-CN', {
1640
+ month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit'
1641
+ })}
1642
+ </div>
1643
+ )}
1644
+ </div>
1645
+ )}
1646
+
1647
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-4" ref={openClawDropdownRef}>
1648
+ {/* 默认模型 - 可搜索单选 */}
1649
+ <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">
1650
+ <div className="flex items-center gap-2.5 mb-2.5">
1651
+ <div className="w-7 h-7 rounded-lg bg-indigo-100/80 text-indigo-600 flex items-center justify-center">
1652
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1653
+ <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" />
1654
+ </svg>
1655
+ </div>
1656
+ <span className="text-[11px] font-medium text-slate-700">默认模型</span>
1657
+ </div>
1658
+ <div className="relative">
1659
+ <button
1660
+ type="button"
1661
+ onClick={() => { setOpenClawSearch(''); setOpenClawDropdown(openClawDropdown === 'default' ? null : 'default'); }}
1662
+ 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"
1663
+ >
1664
+ <span className="truncate">
1665
+ {openclawModelsFlat.find((m) => m.model_id === openclawDefaultModel)?.name || openclawDefaultModel || '选择模型'}
1666
+ </span>
1667
+ <svg className="w-3 h-3 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1668
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1669
+ </svg>
1670
+ </button>
1671
+ {openClawDropdown === 'default' && (
1672
+ <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">
1673
+ <input
1674
+ type="text"
1675
+ placeholder="搜索模型..."
1676
+ value={openClawSearch}
1677
+ onChange={(e) => setOpenClawSearch(e.target.value)}
1678
+ onKeyDown={(e) => e.stopPropagation()}
1679
+ 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"
1680
+ />
1681
+ <ul className="overflow-y-auto overscroll-contain max-h-44">
1682
+ {openclawModelsFiltered.map((model) => (
1683
+ <li key={model.id}>
1684
+ <button
1685
+ type="button"
1686
+ onClick={() => {
1687
+ setOpenclawDefaultModel(model.model_id);
1688
+ setOpenClawDropdown(null);
1689
+ setOpenClawSearch('');
1690
+ }}
1691
+ 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'}`}
1692
+ >
1693
+ {model.name}
1694
+ <span className="text-slate-400 ml-1">({model.provider})</span>
1695
+ </button>
1696
+ </li>
1697
+ ))}
1698
+ </ul>
1699
+ </div>
1700
+ )}
1701
+ </div>
1702
+ <p className="text-[10px] text-slate-500 mt-2">主要使用的模型</p>
1703
+ </div>
1704
+
1705
+ {/* 默认 Fallback - 可搜索多选,芯片展示,不可重复 */}
1706
+ <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">
1707
+ <div className="flex items-center gap-2.5 mb-2.5">
1708
+ <div className="w-7 h-7 rounded-lg bg-amber-100/80 text-amber-600 flex items-center justify-center">
1709
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1710
+ <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" />
1711
+ </svg>
1712
+ </div>
1713
+ <span className="text-[11px] font-medium text-slate-700">默认 Fallback</span>
1714
+ </div>
1715
+ <div className="relative">
1716
+ <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">
1717
+ {openclawDefaultFallbacks.length === 0 ? (
1718
+ <span className="text-[11px] text-slate-400">从下方搜索并添加备用模型</span>
1719
+ ) : (
1720
+ openclawDefaultFallbacks.map((modelId) => {
1721
+ const m = openclawModelsFlat.find((x) => x.model_id === modelId);
1722
+ return (
1723
+ <span
1724
+ key={modelId}
1725
+ className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-amber-100 text-amber-800 text-[10px] font-medium"
1726
+ >
1727
+ {m?.name || modelId}
1728
+ <button
1729
+ type="button"
1730
+ onClick={() => setOpenclawDefaultFallbacks((prev) => prev.filter((id) => id !== modelId))}
1731
+ className="hover:bg-amber-200 rounded p-0.5"
1732
+ aria-label="移除"
1733
+ >
1734
+ <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1735
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
1736
+ </svg>
1737
+ </button>
1738
+ </span>
1739
+ );
1740
+ })
1741
+ )}
1742
+ </div>
1743
+ <button
1744
+ type="button"
1745
+ onClick={() => { setOpenClawSearch(''); setOpenClawDropdown(openClawDropdown === 'defaultFallbacks' ? null : 'defaultFallbacks'); }}
1746
+ 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"
1747
+ >
1748
+ + 搜索并添加 Fallback 模型
1749
+ </button>
1750
+ {openClawDropdown === 'defaultFallbacks' && (
1751
+ <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">
1752
+ <input
1753
+ type="text"
1754
+ placeholder="搜索模型..."
1755
+ value={openClawSearch}
1756
+ onChange={(e) => setOpenClawSearch(e.target.value)}
1757
+ onKeyDown={(e) => e.stopPropagation()}
1758
+ 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"
1759
+ />
1760
+ <ul className="overflow-y-auto overscroll-contain max-h-44">
1761
+ {openclawModelsFiltered
1762
+ .filter((m) => !openclawDefaultFallbacks.includes(m.model_id))
1763
+ .map((model) => (
1764
+ <li key={model.id}>
1765
+ <button
1766
+ type="button"
1767
+ onClick={() => {
1768
+ setOpenclawDefaultFallbacks((prev) => (prev.includes(model.model_id) ? prev : [...prev, model.model_id]));
1769
+ setOpenClawSearch('');
1770
+ }}
1771
+ className="w-full text-left px-3 py-2 text-[11px] text-slate-700 hover:bg-amber-50"
1772
+ >
1773
+ {model.name}
1774
+ <span className="text-slate-400 ml-1">({model.provider})</span>
1775
+ </button>
1776
+ </li>
1777
+ ))}
1778
+ {openclawModelsFiltered.filter((m) => !openclawDefaultFallbacks.includes(m.model_id)).length === 0 && (
1779
+ <li className="px-3 py-2 text-[11px] text-slate-400">无更多可选或已全部添加</li>
1780
+ )}
1781
+ </ul>
1782
+ </div>
1783
+ )}
1784
+ </div>
1785
+ <p className="text-[10px] text-slate-500 mt-2">默认模型的备用模型,可多选,不可重复</p>
1786
+ </div>
1787
+
1788
+ {/* 视觉模型 - 可搜索单选 */}
1789
+ <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">
1790
+ <div className="flex items-center gap-2.5 mb-2.5">
1791
+ <div className="w-7 h-7 rounded-lg bg-rose-100/80 text-rose-600 flex items-center justify-center">
1792
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1793
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
1794
+ <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" />
1795
+ </svg>
1796
+ </div>
1797
+ <span className="text-[11px] font-medium text-slate-700">视觉模型</span>
1798
+ </div>
1799
+ <div className="relative">
1800
+ <button
1801
+ type="button"
1802
+ onClick={() => { setOpenClawSearch(''); setOpenClawDropdown(openClawDropdown === 'image' ? null : 'image'); }}
1803
+ 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"
1804
+ >
1805
+ <span className="truncate">
1806
+ {openclawImageModel
1807
+ ? (openclawModelsFlat.find((m) => m.model_id === openclawImageModel)?.name || openclawImageModel)
1808
+ : '未设置'}
1809
+ </span>
1810
+ <svg className="w-3 h-3 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1811
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
1812
+ </svg>
1813
+ </button>
1814
+ {openClawDropdown === 'image' && (
1815
+ <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">
1816
+ <input
1817
+ type="text"
1818
+ placeholder="搜索模型..."
1819
+ value={openClawSearch}
1820
+ onChange={(e) => setOpenClawSearch(e.target.value)}
1821
+ onKeyDown={(e) => e.stopPropagation()}
1822
+ 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"
1823
+ />
1824
+ <ul className="overflow-y-auto overscroll-contain max-h-44">
1825
+ <li>
1826
+ <button
1827
+ type="button"
1828
+ onClick={() => {
1829
+ setOpenclawImageModel('');
1830
+ setOpenClawDropdown(null);
1831
+ setOpenClawSearch('');
1832
+ }}
1833
+ 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'}`}
1834
+ >
1835
+ 未设置
1836
+ </button>
1837
+ </li>
1838
+ {openclawModelsFiltered.map((model) => (
1839
+ <li key={model.id}>
1840
+ <button
1841
+ type="button"
1842
+ onClick={() => {
1843
+ setOpenclawImageModel(model.model_id);
1844
+ setOpenClawDropdown(null);
1845
+ setOpenClawSearch('');
1846
+ }}
1847
+ 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'}`}
1848
+ >
1849
+ {model.name}
1850
+ <span className="text-slate-400 ml-1">({model.provider})</span>
1851
+ </button>
1852
+ </li>
1853
+ ))}
1854
+ </ul>
1855
+ </div>
1856
+ )}
1857
+ </div>
1858
+ <p className="text-[10px] text-slate-500 mt-2">用于图像理解(可选)</p>
1859
+ </div>
1860
+
1861
+ {/* 视觉 Fallback - 可搜索多选,芯片展示,不可重复 */}
1862
+ <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">
1863
+ <div className="flex items-center gap-2.5 mb-2.5">
1864
+ <div className="w-7 h-7 rounded-lg bg-emerald-100/80 text-emerald-600 flex items-center justify-center">
1865
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1866
+ <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" />
1867
+ </svg>
1868
+ </div>
1869
+ <span className="text-[11px] font-medium text-slate-700">视觉 Fallback</span>
1870
+ </div>
1871
+ <div className="relative">
1872
+ <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">
1873
+ {openclawImageFallbacks.length === 0 ? (
1874
+ <span className="text-[11px] text-slate-400">从下方搜索并添加备用模型</span>
1875
+ ) : (
1876
+ openclawImageFallbacks.map((modelId) => {
1877
+ const m = openclawModelsFlat.find((x) => x.model_id === modelId);
1878
+ return (
1879
+ <span
1880
+ key={modelId}
1881
+ className="inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-emerald-100 text-emerald-800 text-[10px] font-medium"
1882
+ >
1883
+ {m?.name || modelId}
1884
+ <button
1885
+ type="button"
1886
+ onClick={() => setOpenclawImageFallbacks((prev) => prev.filter((id) => id !== modelId))}
1887
+ className="hover:bg-emerald-200 rounded p-0.5"
1888
+ aria-label="移除"
1889
+ >
1890
+ <svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1891
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
1892
+ </svg>
1893
+ </button>
1894
+ </span>
1895
+ );
1896
+ })
1897
+ )}
1898
+ </div>
1899
+ <button
1900
+ type="button"
1901
+ onClick={() => { setOpenClawSearch(''); setOpenClawDropdown(openClawDropdown === 'imageFallbacks' ? null : 'imageFallbacks'); }}
1902
+ 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"
1903
+ >
1904
+ + 搜索并添加 Fallback 模型
1905
+ </button>
1906
+ {openClawDropdown === 'imageFallbacks' && (
1907
+ <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">
1908
+ <input
1909
+ type="text"
1910
+ placeholder="搜索模型..."
1911
+ value={openClawSearch}
1912
+ onChange={(e) => setOpenClawSearch(e.target.value)}
1913
+ onKeyDown={(e) => e.stopPropagation()}
1914
+ 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"
1915
+ />
1916
+ <ul className="overflow-y-auto overscroll-contain max-h-44">
1917
+ {openclawModelsFiltered
1918
+ .filter((m) => !openclawImageFallbacks.includes(m.model_id))
1919
+ .map((model) => (
1920
+ <li key={model.id}>
1921
+ <button
1922
+ type="button"
1923
+ onClick={() => {
1924
+ setOpenclawImageFallbacks((prev) => (prev.includes(model.model_id) ? prev : [...prev, model.model_id]));
1925
+ setOpenClawSearch('');
1926
+ }}
1927
+ className="w-full text-left px-3 py-2 text-[11px] text-slate-700 hover:bg-emerald-50"
1928
+ >
1929
+ {model.name}
1930
+ <span className="text-slate-400 ml-1">({model.provider})</span>
1931
+ </button>
1932
+ </li>
1933
+ ))}
1934
+ {openclawModelsFiltered.filter((m) => !openclawImageFallbacks.includes(m.model_id)).length === 0 && (
1935
+ <li className="px-3 py-2 text-[11px] text-slate-400">无更多可选或已全部添加</li>
1936
+ )}
1937
+ </ul>
1938
+ </div>
1939
+ )}
1940
+ </div>
1941
+ <p className="text-[10px] text-slate-500 mt-2">视觉模型的备用模型,可多选,不可重复</p>
1942
+ </div>
1943
+ </div>
1944
+
1945
+ <div className={`grid gap-3 pt-1 ${openclawEnabled ? 'grid-cols-3' : 'grid-cols-2'}`}>
1946
+ <button
1947
+ onClick={handleOpenClawApply}
1948
+ disabled={openclawApplying}
1949
+ 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 ${
1950
+ openclawEnabled
1951
+ ? 'bg-white border-slate-200/80 text-slate-700 hover:border-slate-300 hover:bg-slate-50'
1952
+ : 'bg-slate-900 border-slate-900 text-white hover:bg-slate-800'
1953
+ }`}
1954
+ >
1955
+ {openclawApplying ? (
1956
+ <>
1957
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
1958
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
1959
+ <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>
1960
+ </svg>
1961
+ 应用中
1962
+ </>
1963
+ ) : (
1964
+ <>
1965
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1966
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
1967
+ </svg>
1968
+ {openclawEnabled ? '更新配置' : '应用配置'}
1969
+ </>
1970
+ )}
1971
+ </button>
1972
+
1973
+ {openclawEnabled && (
1974
+ <button
1975
+ onClick={handleOpenClawRestore}
1976
+ disabled={openclawRestoring}
1977
+ 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"
1978
+ >
1979
+ {openclawRestoring ? (
1980
+ <>
1981
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
1982
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
1983
+ <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>
1984
+ </svg>
1985
+ 还原中
1986
+ </>
1987
+ ) : (
1988
+ <>
1989
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
1990
+ <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" />
1991
+ </svg>
1992
+ 还原配置
1993
+ </>
1994
+ )}
1995
+ </button>
1996
+ )}
1997
+
1998
+ {openclawTesting ? (
1999
+ <button
2000
+ onClick={handleOpenClawCancelTest}
2001
+ 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"
2002
+ >
2003
+ <svg className="animate-spin w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
2004
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3"></circle>
2005
+ <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>
2006
+ </svg>
2007
+ 取消测试
2008
+ </button>
2009
+ ) : (
2010
+ <button
2011
+ onClick={handleOpenClawTest}
2012
+ 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"
2013
+ >
2014
+ <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2015
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
2016
+ </svg>
2017
+ 测试配置
2018
+ </button>
2019
+ )}
2020
+ </div>
2021
+
2022
+ {openclawTestResult && (
2023
+ <div className={`rounded-lg border px-4 py-3 ${
2024
+ openclawTestResult.success
2025
+ ? 'bg-emerald-50/60 border-emerald-200/60'
2026
+ : 'bg-rose-50/60 border-rose-200/60'
2027
+ }`}>
2028
+ <div className="flex items-start gap-3">
2029
+ <div className={`w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5 ${
2030
+ openclawTestResult.success ? 'bg-emerald-500' : 'bg-rose-500'
2031
+ }`}>
2032
+ {openclawTestResult.success ? (
2033
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2034
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
2035
+ </svg>
2036
+ ) : (
2037
+ <svg className="w-3 h-3 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2038
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M6 18L18 6M6 6l12 12" />
2039
+ </svg>
2040
+ )}
2041
+ </div>
2042
+ <div className="flex-1">
2043
+ <div className="text-xs font-medium mb-1">
2044
+ {openclawTestResult.success ? '配置验证成功' : '配置验证失败'}
2045
+ </div>
2046
+ <p className={`text-[11px] ${openclawTestResult.success ? 'text-emerald-700/80' : 'text-rose-700/80'}`}>
2047
+ {openclawTestResult.success
2048
+ ? (openclawTestResult.openclawTest?.message || 'OpenClaw 可以正常工作')
2049
+ : (openclawTestResult.openclawTest?.error || openclawTestResult.error || openclawTestResult.suggestion || '请检查配置')}
2050
+ </p>
2051
+ {openclawTestResult.openclawTest?.stdout && (
2052
+ <div className="mt-2 text-[10px] text-slate-500 font-mono bg-white/50 p-2 rounded">
2053
+ {openclawTestResult.openclawTest.stdout}
2054
+ </div>
2055
+ )}
2056
+ </div>
2057
+ </div>
2058
+ </div>
2059
+ )}
2060
+
2061
+ <details className="group" open>
2062
+ <summary className="cursor-pointer text-[11px] text-slate-500 hover:text-slate-700 list-none flex items-center gap-1.5 py-1">
2063
+ <svg className="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2064
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
2065
+ </svg>
2066
+ <span>配置</span>
2067
+ </summary>
2068
+ <div className="mt-3">
2069
+ <div className="flex border border-slate-200 rounded-t-lg overflow-hidden bg-slate-100/90">
2070
+ <button
2071
+ type="button"
2072
+ onClick={() => setOpenclawConfigSubTab('preview')}
2073
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
2074
+ openclawConfigSubTab === 'preview'
2075
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
2076
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
2077
+ }`}
2078
+ >
2079
+ 预览
2080
+ </button>
2081
+ <button
2082
+ type="button"
2083
+ onClick={() => setOpenclawConfigSubTab('current')}
2084
+ className={`flex-1 px-4 py-2.5 text-[11px] transition-all ${
2085
+ openclawConfigSubTab === 'current'
2086
+ ? 'bg-white text-slate-800 font-semibold shadow-sm'
2087
+ : 'text-slate-500 font-medium hover:bg-slate-200/80 hover:text-slate-600'
2088
+ }`}
2089
+ >
2090
+ 生效中
2091
+ </button>
2092
+ </div>
2093
+ <div className="relative overflow-hidden rounded-b-lg bg-slate-950 border border-slate-200 border-t-0">
2094
+ {openclawConfigSubTab === 'preview' ? (
2095
+ <>
2096
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
2097
+ <div className="flex gap-1.5">
2098
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2099
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2100
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2101
+ </div>
2102
+ <span className="text-[10px] font-medium text-slate-400 ml-2">openclaw.json(应用后将写入的合并结果)</span>
2103
+ </div>
2104
+ {openclawPreviewLoading ? (
2105
+ <div className="p-3.5 text-[10px] text-slate-400 font-mono">加载中...</div>
2106
+ ) : (
2107
+ <textarea
2108
+ 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
2109
+ [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-900 [&::-webkit-scrollbar-thumb]:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full"
2110
+ placeholder="暂无预览"
2111
+ spellCheck={false}
2112
+ value={openclawPreviewText}
2113
+ onChange={(e) => setOpenclawPreviewText(e.target.value)}
2114
+ />
2115
+ )}
2116
+ </>
2117
+ ) : (
2118
+ <>
2119
+ <div className="flex items-center gap-1.5 px-3 py-2 bg-slate-900 border-b border-slate-800">
2120
+ <div className="flex gap-1.5">
2121
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2122
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2123
+ <div className="w-2.5 h-2.5 rounded-full bg-slate-600"></div>
2124
+ </div>
2125
+ <span className="text-[10px] font-medium text-slate-400 ml-2">openclaw.json(当前磁盘内容,还原后即更新)</span>
2126
+ </div>
2127
+ {openclawStatus?.config != null ? (
2128
+ <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">
2129
+ {JSON.stringify(openclawStatus.config, null, 2)}
2130
+ </pre>
2131
+ ) : (
2132
+ <div className="p-3.5 text-[10px] text-slate-500 font-mono">暂无配置文件</div>
2133
+ )}
2134
+ </>
2135
+ )}
2136
+ </div>
2137
+ </div>
2138
+ </details>
2139
+
2140
+ <div className="rounded-lg border border-slate-200/50 bg-slate-50/60 px-4 py-3">
2141
+ <div className="flex items-center gap-2 mb-2.5">
2142
+ <svg className="w-4 h-4 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2143
+ <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" />
2144
+ </svg>
2145
+ <h3 className="text-xs font-semibold text-slate-700">使用说明</h3>
2146
+ </div>
2147
+ <ul className="space-y-1.5 text-[10px] text-slate-600">
2148
+ <li className="flex gap-2">
2149
+ <span className="text-slate-400">1.</span>
2150
+ <span>「生效中」为当前磁盘上的 openclaw.json;「预览」为应用后将写入的合并结果(含 AAR 网关)</span>
762
2151
  </li>
763
2152
  <li className="flex gap-2">
764
- <span className="text-slate-400">5.</span>
765
- <span>配置文件位于 ~/.claude/settings.json</span>
2153
+ <span className="text-slate-400">2.</span>
2154
+ <span>选择默认模型等后,在「预览」中查看合并结果,点击「应用配置」写入</span>
766
2155
  </li>
767
2156
  <li className="flex gap-2">
768
- <span className="text-slate-400">6.</span>
769
- <span>临时配置保存在 ~/.aar/settings.tmp.json</span>
2157
+ <span className="text-slate-400">3.</span>
2158
+ <span>配置文件位于 ~/.openclaw/openclaw.json</span>
770
2159
  </li>
771
2160
  <li className="flex gap-2">
772
- <span className="text-slate-400">7.</span>
773
- <span>点击"还原配置"可恢复到应用前的状态</span>
2161
+ <span className="text-slate-400">4.</span>
2162
+ <span>「还原配置」恢复为应用前的备份;还原后请在「生效中」查看当前内容</span>
774
2163
  </li>
775
2164
  </ul>
776
2165
  </div>
777
2166
  </div>
778
2167
  )}
2168
+
2169
+ {activeTab === 'cursor' && (
2170
+ <div className="flex flex-col items-center justify-center py-12 text-center">
2171
+ <div className="w-16 h-16 bg-slate-100 rounded-full flex items-center justify-center mb-4">
2172
+ <svg className="w-8 h-8 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2173
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
2174
+ </svg>
2175
+ </div>
2176
+ <h3 className="text-sm font-medium text-slate-900 mb-1">Cursor 支持即将推出</h3>
2177
+ <p className="text-xs text-slate-500 max-w-xs">
2178
+ 我们将很快添加对 Cursor 编辑器的支持,敬请期待。
2179
+ </p>
2180
+ </div>
2181
+ )}
779
2182
  </div>
780
2183
  </div>
781
2184
  </main>