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