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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +14 -0
- package/.next/build-manifest.json +2 -2
- package/.next/fallback-build-manifest.json +2 -2
- package/.next/routes-manifest.json +84 -0
- package/.next/server/app/_global-error/page.js +1 -1
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error.html +2 -2
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/gateway/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/gateway/models/route.js.nft.json +1 -1
- package/.next/server/app/api/gateway/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/apply/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/available-models/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/save/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/status/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/claude/test/route.js +1 -1
- package/.next/server/app/api/ide/claude/test/route.js.nft.json +1 -1
- package/.next/server/app/api/ide/openclaw/apply/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/openclaw/apply/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/openclaw/apply/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/openclaw/apply/route.js +7 -0
- package/.next/server/app/api/ide/openclaw/apply/route.js.map +5 -0
- package/.next/server/app/api/ide/openclaw/apply/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/openclaw/apply/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/openclaw/available-models/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/openclaw/available-models/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/openclaw/available-models/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/openclaw/available-models/route.js +7 -0
- package/.next/server/app/api/ide/openclaw/available-models/route.js.map +5 -0
- package/.next/server/app/api/ide/openclaw/available-models/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/openclaw/available-models/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/openclaw/preview/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/openclaw/preview/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/openclaw/preview/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/openclaw/preview/route.js +7 -0
- package/.next/server/app/api/ide/openclaw/preview/route.js.map +5 -0
- package/.next/server/app/api/ide/openclaw/preview/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/openclaw/preview/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/openclaw/restore/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/openclaw/restore/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/openclaw/restore/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/openclaw/restore/route.js +6 -0
- package/.next/server/app/api/ide/openclaw/restore/route.js.map +5 -0
- package/.next/server/app/api/ide/openclaw/restore/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/openclaw/restore/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/openclaw/save/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/openclaw/save/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/openclaw/save/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/openclaw/save/route.js +7 -0
- package/.next/server/app/api/ide/openclaw/save/route.js.map +5 -0
- package/.next/server/app/api/ide/openclaw/save/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/openclaw/save/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/openclaw/status/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/openclaw/status/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/openclaw/status/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/openclaw/status/route.js +7 -0
- package/.next/server/app/api/ide/openclaw/status/route.js.map +5 -0
- package/.next/server/app/api/ide/openclaw/status/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/openclaw/status/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/openclaw/test/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/openclaw/test/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/openclaw/test/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/openclaw/test/route.js +6 -0
- package/.next/server/app/api/ide/openclaw/test/route.js.map +5 -0
- package/.next/server/app/api/ide/openclaw/test/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/openclaw/test/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/opencode/apply/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/opencode/apply/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/opencode/apply/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/opencode/apply/route.js +7 -0
- package/.next/server/app/api/ide/opencode/apply/route.js.map +5 -0
- package/.next/server/app/api/ide/opencode/apply/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/opencode/apply/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/opencode/available-models/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/opencode/available-models/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/opencode/available-models/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/opencode/available-models/route.js +7 -0
- package/.next/server/app/api/ide/opencode/available-models/route.js.map +5 -0
- package/.next/server/app/api/ide/opencode/available-models/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/opencode/available-models/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/opencode/preview/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/opencode/preview/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/opencode/preview/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/opencode/preview/route.js +7 -0
- package/.next/server/app/api/ide/opencode/preview/route.js.map +5 -0
- package/.next/server/app/api/ide/opencode/preview/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/opencode/preview/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/opencode/restore/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/opencode/restore/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/opencode/restore/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/opencode/restore/route.js +6 -0
- package/.next/server/app/api/ide/opencode/restore/route.js.map +5 -0
- package/.next/server/app/api/ide/opencode/restore/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/opencode/restore/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/opencode/save/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/opencode/save/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/opencode/save/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/opencode/save/route.js +7 -0
- package/.next/server/app/api/ide/opencode/save/route.js.map +5 -0
- package/.next/server/app/api/ide/opencode/save/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/opencode/save/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/opencode/status/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/opencode/status/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/opencode/status/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/opencode/status/route.js +7 -0
- package/.next/server/app/api/ide/opencode/status/route.js.map +5 -0
- package/.next/server/app/api/ide/opencode/status/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/opencode/status/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/ide/opencode/test/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/ide/opencode/test/route/build-manifest.json +11 -0
- package/.next/server/app/api/ide/opencode/test/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/ide/opencode/test/route.js +6 -0
- package/.next/server/app/api/ide/opencode/test/route.js.map +5 -0
- package/.next/server/app/api/ide/opencode/test/route.js.nft.json +1 -0
- package/.next/server/app/api/ide/opencode/test/route_client-reference-manifest.js +2 -0
- package/.next/server/app/api/logs/route.js.nft.json +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/providers/route.js.nft.json +1 -1
- package/.next/server/app/api/providers/test/route.js.nft.json +1 -1
- package/.next/server/app/api/service/force-stop/route.js.nft.json +1 -1
- package/.next/server/app/api/service/start/route.js.nft.json +1 -1
- package/.next/server/app/api/service/status/route.js.nft.json +1 -1
- package/.next/server/app/api/service/stop/route.js.nft.json +1 -1
- package/.next/server/app/ide/page.js +1 -1
- package/.next/server/app/ide/page.js.nft.json +1 -1
- package/.next/server/app/ide/page_client-reference-manifest.js +1 -1
- package/.next/server/app/ide.html +1 -1
- package/.next/server/app/ide.rsc +3 -3
- package/.next/server/app/ide.segments/_full.segment.rsc +3 -3
- package/.next/server/app/ide.segments/_head.segment.rsc +1 -1
- package/.next/server/app/ide.segments/_index.segment.rsc +2 -2
- package/.next/server/app/ide.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/ide.segments/ide/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/ide.segments/ide.segment.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs/page.js +1 -1
- package/.next/server/app/logs/page.js.nft.json +1 -1
- package/.next/server/app/logs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/logs.html +1 -1
- package/.next/server/app/logs.rsc +3 -3
- package/.next/server/app/logs.segments/_full.segment.rsc +3 -3
- package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_index.segment.rsc +2 -2
- package/.next/server/app/logs.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
- package/.next/server/app/models/page.js +1 -1
- package/.next/server/app/models/page.js.nft.json +1 -1
- package/.next/server/app/models/page_client-reference-manifest.js +1 -1
- package/.next/server/app/models.html +1 -1
- package/.next/server/app/models.rsc +2 -2
- package/.next/server/app/models.segments/_full.segment.rsc +2 -2
- package/.next/server/app/models.segments/_head.segment.rsc +1 -1
- package/.next/server/app/models.segments/_index.segment.rsc +2 -2
- package/.next/server/app/models.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/models.segments/models/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/models.segments/models.segment.rsc +1 -1
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/providers/page.js +1 -1
- package/.next/server/app/providers/page.js.nft.json +1 -1
- package/.next/server/app/providers/page_client-reference-manifest.js +1 -1
- package/.next/server/app/providers.html +1 -1
- package/.next/server/app/providers.rsc +2 -2
- package/.next/server/app/providers.segments/_full.segment.rsc +2 -2
- package/.next/server/app/providers.segments/_head.segment.rsc +1 -1
- package/.next/server/app/providers.segments/_index.segment.rsc +2 -2
- package/.next/server/app/providers.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/providers.segments/providers/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/providers.segments/providers.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +14 -0
- package/.next/server/chunks/[root-of-the-server]__001d5756._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__001d5756._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__05f8578b._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__05f8578b._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__1480f018._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1480f018._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__1909f3aa._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1909f3aa._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__1d4b7fc5._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1d4b7fc5._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__372ef2bf._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__372ef2bf._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__3aaf963c._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__3aaf963c._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__43810962._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__43810962._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__55cd88b8._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__55cd88b8._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__5e8276bc._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__6ce199d2._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__6ce199d2._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__760eaa16._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__760eaa16._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__772134c6._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__772134c6._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__7b77f523._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__7b77f523._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__7c298a19._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__7c298a19._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__85540228._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__85540228._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__94fe8d3c._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__94fe8d3c._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__97622908._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__97622908._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__a02e6618._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__a02e6618._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__a32a20a7._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__a32a20a7._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__af5b556a._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__af5b556a._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js +18 -18
- package/.next/server/chunks/[root-of-the-server]__c1b4b601._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__cafe113e._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__cafe113e._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__ccfc7f1d._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__ccfc7f1d._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__dc8b0bed._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__dc8b0bed._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__f8949f88._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__f8949f88._.js.map +1 -1
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_apply_route_actions_2cb9e4b4.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_apply_route_actions_2cb9e4b4.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_preview_route_actions_9814a8e4.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_preview_route_actions_9814a8e4.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_restore_route_actions_10ad8f9d.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_restore_route_actions_10ad8f9d.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_save_route_actions_044ad081.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_save_route_actions_044ad081.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_status_route_actions_ed9786d2.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_status_route_actions_ed9786d2.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_test_route_actions_ce2cb808.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_openclaw_test_route_actions_ce2cb808.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_apply_route_actions_6c422244.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_apply_route_actions_6c422244.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_preview_route_actions_256c82e0.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_preview_route_actions_256c82e0.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_restore_route_actions_371993d3.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_restore_route_actions_371993d3.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_save_route_actions_6e4c9c41.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_save_route_actions_6e4c9c41.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_status_route_actions_498ad77b.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_status_route_actions_498ad77b.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_test_route_actions_c71be510.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_ide_opencode_test_route_actions_c71be510.js.map +1 -0
- package/.next/server/chunks/ce889_server_app_api_ide_openclaw_available-models_route_actions_e568e70b.js +3 -0
- package/.next/server/chunks/ce889_server_app_api_ide_openclaw_available-models_route_actions_e568e70b.js.map +1 -0
- package/.next/server/chunks/ce889_server_app_api_ide_opencode_available-models_route_actions_95230db3.js +3 -0
- package/.next/server/chunks/ce889_server_app_api_ide_opencode_available-models_route_actions_95230db3.js.map +1 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__bec95712._.js → [root-of-the-server]__81937253._.js} +2 -2
- package/.next/server/chunks/ssr/{[root-of-the-server]__bec95712._.js.map → [root-of-the-server]__81937253._.js.map} +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__71c85955._.js → [root-of-the-server]__976ad963._.js} +2 -2
- package/.next/server/chunks/ssr/{[root-of-the-server]__71c85955._.js.map → [root-of-the-server]__976ad963._.js.map} +1 -1
- package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js +1 -1
- package/.next/server/chunks/ssr/src_app_ide_page_tsx_8962793b._.js.map +1 -1
- package/.next/server/chunks/ssr/src_app_logs_page_tsx_7b7b7b83._.js +1 -1
- package/.next/server/chunks/ssr/src_app_logs_page_tsx_7b7b7b83._.js.map +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/static/chunks/0f120c117962200b.css +1 -0
- package/.next/static/chunks/64f547b3bcd3aef4.js +1 -0
- package/.next/static/chunks/{81c904164fe81379.js → 7c8b7cbb3339f139.js} +1 -1
- package/.next/static/chunks/8ed839b2e4948968.js +1 -0
- package/.next/types/routes.d.ts +15 -1
- package/.next/types/validator.ts +126 -0
- package/README.md +100 -111
- package/dist/.next/dev/types/validator.js +56 -0
- package/dist/.next/types/validator.js +56 -0
- package/dist/src/app/api/gateway/[...path]/route.js +1 -1
- package/dist/src/app/api/gateway/route.js +1 -1
- package/dist/src/app/api/ide/claude/apply/route.js +42 -31
- package/dist/src/app/api/ide/claude/status/route.js +6 -1
- package/dist/src/app/api/ide/openclaw/apply/route.js +92 -0
- package/dist/src/app/api/ide/openclaw/available-models/route.js +46 -0
- package/dist/src/app/api/ide/openclaw/build-config.js +101 -0
- package/dist/src/app/api/ide/openclaw/preview/route.js +49 -0
- package/dist/src/app/api/ide/openclaw/restore/route.js +24 -0
- package/dist/src/app/api/ide/openclaw/save/route.js +54 -0
- package/dist/src/app/api/ide/openclaw/status/route.js +139 -0
- package/dist/src/app/api/ide/openclaw/test/route.js +158 -0
- package/dist/src/app/api/ide/opencode/apply/route.js +89 -0
- package/dist/src/app/api/ide/opencode/available-models/route.js +46 -0
- package/dist/src/app/api/ide/opencode/build-config.js +54 -0
- package/dist/src/app/api/ide/opencode/preview/route.js +36 -0
- package/dist/src/app/api/ide/opencode/restore/route.js +40 -0
- package/dist/src/app/api/ide/opencode/save/route.js +123 -0
- package/dist/src/app/api/ide/opencode/status/route.js +106 -0
- package/dist/src/app/api/ide/opencode/test/route.js +136 -0
- package/dist/src/app/api/logs/route.js +2 -2
- package/dist/src/app/api/models/route.js +5 -5
- package/dist/src/app/api/providers/route.js +4 -4
- package/dist/src/app/api/providers/test/route.js +1 -1
- package/dist/src/app/api/service/start/route.js +1 -1
- package/dist/src/app/api/service/status/route.js +1 -1
- package/dist/src/app/api/service/stop/route.js +1 -1
- package/dist/src/app/ide/page.js +591 -81
- package/dist/src/app/logs/page.js +15 -1
- package/dist/src/cli/index.js +218 -20
- package/dist/src/db/database.js +56 -5
- package/dist/src/db/queries.js +6 -6
- package/dist/src/server/logger.js +22 -4
- package/package.json +2 -1
- package/src/app/api/gateway/[...path]/route.ts +1 -1
- package/src/app/api/gateway/route.ts +1 -1
- package/src/app/api/ide/claude/apply/route.ts +46 -31
- package/src/app/api/ide/claude/status/route.ts +12 -2
- package/src/app/api/ide/openclaw/apply/route.ts +103 -0
- package/src/app/api/ide/openclaw/available-models/route.ts +59 -0
- package/src/app/api/ide/openclaw/build-config.ts +152 -0
- package/src/app/api/ide/openclaw/preview/route.ts +57 -0
- package/src/app/api/ide/openclaw/restore/route.ts +27 -0
- package/src/app/api/ide/openclaw/save/route.ts +67 -0
- package/src/app/api/ide/openclaw/status/route.ts +178 -0
- package/src/app/api/ide/openclaw/test/route.ts +194 -0
- package/src/app/api/ide/opencode/apply/route.ts +92 -0
- package/src/app/api/ide/opencode/available-models/route.ts +59 -0
- package/src/app/api/ide/opencode/build-config.ts +69 -0
- package/src/app/api/ide/opencode/preview/route.ts +40 -0
- package/src/app/api/ide/opencode/restore/route.ts +52 -0
- package/src/app/api/ide/opencode/save/route.ts +131 -0
- package/src/app/api/ide/opencode/status/route.ts +128 -0
- package/src/app/api/ide/opencode/test/route.ts +145 -0
- package/src/app/api/logs/route.ts +2 -2
- package/src/app/api/models/route.ts +5 -5
- package/src/app/api/providers/route.ts +4 -4
- package/src/app/api/providers/test/route.ts +1 -1
- package/src/app/api/service/start/route.ts +1 -1
- package/src/app/api/service/status/route.ts +1 -1
- package/src/app/api/service/stop/route.ts +1 -1
- package/src/app/globals.css +17 -0
- package/src/app/ide/page.tsx +1535 -132
- package/src/app/logs/page.tsx +17 -5
- package/src/cli/index.ts +228 -25
- package/src/db/database.ts +60 -8
- package/src/db/queries.ts +6 -6
- package/src/server/logger.ts +19 -4
- package/.next/static/chunks/6418ca50028376b7.css +0 -1
- package/.next/static/chunks/9ec3b97741b6575e.js +0 -1
- /package/.next/static/{PkfqdzwOZgX-UhSNUuhdp → dYin74gcpdlg8TGoGv-_d}/_buildManifest.js +0 -0
- /package/.next/static/{PkfqdzwOZgX-UhSNUuhdp → dYin74gcpdlg8TGoGv-_d}/_clientMiddlewareManifest.json +0 -0
- /package/.next/static/{PkfqdzwOZgX-UhSNUuhdp → dYin74gcpdlg8TGoGv-_d}/_ssgManifest.js +0 -0
package/src/app/ide/page.tsx
CHANGED
|
@@ -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 和
|
|
728
|
+
<p className="text-sm text-slate-500">配置 Claude Code、OpenCode 和 OpenClaw 的模型代理设置</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=
|
|
952
|
+
<div className={`grid gap-3 pt-1 ${status?.backupExists ? 'grid-cols-3' : 'grid-cols-2'}`}>
|
|
459
953
|
<button
|
|
460
|
-
onClick={
|
|
461
|
-
disabled={applying
|
|
462
|
-
className=
|
|
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
|
-
|
|
964
|
+
应用中
|
|
475
965
|
</>
|
|
476
966
|
) : (
|
|
477
967
|
<>
|
|
478
|
-
|
|
479
|
-
<
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
|
1159
|
+
<span>配置</span>
|
|
701
1160
|
</summary>
|
|
702
1161
|
<div className="mt-3">
|
|
703
|
-
<div className="
|
|
704
|
-
<
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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
|
-
</
|
|
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
|
|
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
|
|
1587
|
+
<span>选择默认模型后,在「预览」中查看合并结果,点击「应用配置」写入</span>
|
|
754
1588
|
</li>
|
|
755
1589
|
<li className="flex gap-2">
|
|
756
1590
|
<span className="text-slate-400">3.</span>
|
|
757
|
-
<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
|
|
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">
|
|
765
|
-
<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">
|
|
769
|
-
<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">
|
|
773
|
-
<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>
|