@yancyyu/openhermit 1.6.37 → 1.6.39
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/dist-renderer/assets/ProjectEditorOverlay-krO5vQxX.js +58 -0
- package/dist-renderer/assets/{TeamGraphOverlay-DYT3bwFR.js → TeamGraphOverlay-DqhQzcTr.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-Dbt_EU-e.js → _basePickBy-B7kSYPxr.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DWo68sXI.js → _baseUniq-CnjxqwAk.js} +1 -1
- package/dist-renderer/assets/{arc-DXH1iZQK.js → arc-CLeZuINP.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-cjffS2Qr.js → architectureDiagram-VXUJARFQ-QKtqaqdY.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-BKdZF02Y.js → blockDiagram-VD42YOAC-BqdrzO_f.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CN27pqaI.js → c4Diagram-YG6GDRKO-gwPlCxDC.js} +1 -1
- package/dist-renderer/assets/channel-DpMHF50r.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CXPCI7g_.js → chunk-4BX2VUAB-C6XLurL4.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-BGAXQZRC.js → chunk-55IACEB6-Ds6quhEP.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-TPDaA_KQ.js → chunk-B4BG7PRW-5UlA1_e9.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-D1ADe_tq.js → chunk-DI55MBZ5-ywFrqIsY.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-Beimtg3a.js → chunk-FMBD7UC4-C7ifUA17.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-OjNBu854.js → chunk-QN33PNHL-BxGCo80U.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DinqvbH8.js → chunk-QZHKN3VN-B2CuaZs6.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BfFtlPSZ.js → chunk-TZMSLE5B-Ds1hInvp.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CBYCBVRl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CBYCBVRl.js +1 -0
- package/dist-renderer/assets/clone-DcMF6Psb.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-D9z9Dgt7.js → cose-bilkent-S5V4N54A-Cz1GVtLp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-n1g-DhEE.js → dagre-6UL2VRFP-BrmR-P4h.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BvxFq-BE.js → diagram-PSM6KHXK-DbNjC5Rg.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-wVnJuwza.js → diagram-QEK2KX5R-qkRX5_Mq.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-B707WJQw.js → diagram-S2PKOQOG-CyL5rCv2.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-C-_1dGHs.js → erDiagram-Q2GNP2WA-Dox3-bA5.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-CMTSi3H6.js → flowDiagram-NV44I4VS-BtkaxlDL.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-DZ0bNrAA.js → ganttDiagram-JELNMOA3-Dhy_d9GK.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DNVfGooQ.js → gitGraphDiagram-V2S2FVAM-B5XRhIQA.js} +1 -1
- package/dist-renderer/assets/{graph-865j_tM_.js → graph-CsoEwUhS.js} +1 -1
- package/dist-renderer/assets/{index-C_F9N5x-.js → index-BWPWmJNo.js} +1 -1
- package/dist-renderer/assets/{index-LwDIsXJN.js → index-Bu2R-Se7.js} +586 -740
- package/dist-renderer/assets/index-CnWV3BhG.css +32 -0
- package/dist-renderer/assets/{index-DuUaf8at.js → index-D-3KgskL.js} +1 -1
- package/dist-renderer/assets/{index-BTx1nc4T.js → index-DGEBzLNT.js} +1 -1
- package/dist-renderer/assets/{index-2EW-eu3q.js → index-NhHNs2Oo.js} +1 -1
- package/dist-renderer/assets/{index-4dEMStJj.js → index-h17WuEyf.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-CyqtElLq.js → infoDiagram-HS3SLOUP-hMGmNojH.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BvjQ0Hm0.js → journeyDiagram-XKPGCS4Q-DXV2rBDl.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CJJ-k0zT.js → kanban-definition-3W4ZIXB7-Bf99WLRy.js} +1 -1
- package/dist-renderer/assets/{layout-CnV6rQAG.js → layout-C3XWrpwo.js} +1 -1
- package/dist-renderer/assets/{linear-Cw3UQgyX.js → linear-OEEcn8KN.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-C5tDaGSK.js → mindmap-definition-VGOIOE7T-Dpi3S2x4.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-CiIpPsau.js → pieDiagram-ADFJNKIX-xTPPhtNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-C3gtowNj.js → quadrantDiagram-AYHSOK5B-euniyDlz.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CXBTrAnU.js → requirementDiagram-UZGBJVZJ-D9Uiw4kF.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-wziX77xG.js → sankeyDiagram-TZEHDZUN-CySU4nED.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-sYqopcrj.js → sequenceDiagram-WL72ISMW-JVGpET6V.js} +1 -1
- package/dist-renderer/assets/splashScene-D0YB9uxm.js +17 -0
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bl1-0_Cp.js → stateDiagram-FKZM4ZOC-B2FY5qqi.js} +1 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DcoMiR8H.js +1 -0
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CIRjJUBo.js → timeline-definition-IT6M3QCI-DmycNUUe.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CVPuNe1n.js → treemap-GDKQZRPO-DPq4gZuB.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-3nT9yHwp.js → xychartDiagram-PRI3JC2R-J6VVJzRq.js} +1 -1
- package/dist-renderer/index.html +20 -53
- package/package.json +25 -18
- package/src/main/ipc/extensions.ts +30 -50
- package/src/main/server.ts +890 -247
- package/src/main/services/extensions/ExtensionFacadeService.ts +4 -56
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +4 -2
- package/src/main/services/extensions/library/McpLibraryService.ts +243 -0
- package/src/main/services/session-intelligence/ConversationTelemetryService.ts +1101 -0
- package/src/main/services/session-intelligence/LocalSessionScanner.ts +512 -0
- package/src/main/services/session-intelligence/SessionUsageParser.ts +4 -4
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +14 -1
- package/src/main/services/system-manager/SystemManagerConfigService.ts +122 -0
- package/src/main/services/system-manager/SystemManagerPtyService.ts +233 -0
- package/src/main/services/system-manager/WorkflowPromptService.ts +75 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +32 -8
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +39 -2
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +22 -4
- package/src/main/utils/teamProjectResolution.ts +15 -0
- package/src/renderer/App.tsx +8 -4
- package/src/renderer/api/httpClient.ts +174 -38
- package/src/renderer/api/providers.ts +23 -2
- package/src/renderer/assets/participant-avatars/01.svg +3 -0
- package/src/renderer/assets/participant-avatars/02.svg +3 -0
- package/src/renderer/assets/participant-avatars/03.svg +3 -0
- package/src/renderer/assets/participant-avatars/04.svg +3 -0
- package/src/renderer/assets/participant-avatars/05.svg +3 -0
- package/src/renderer/assets/participant-avatars/06.svg +3 -0
- package/src/renderer/assets/participant-avatars/07.svg +3 -0
- package/src/renderer/assets/participant-avatars/08.svg +3 -0
- package/src/renderer/assets/participant-avatars/09.svg +3 -0
- package/src/renderer/assets/participant-avatars/10.svg +3 -0
- package/src/renderer/assets/participant-avatars/11.svg +3 -0
- package/src/renderer/assets/participant-avatars/12.svg +3 -0
- package/src/renderer/assets/participant-avatars/13.svg +3 -0
- package/src/renderer/components/common/TerminalPane.tsx +213 -0
- package/src/renderer/components/dashboard/DashboardView.tsx +9 -36
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +12 -221
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/mcp/McpLibraryEnableDialog.tsx +305 -0
- package/src/renderer/components/extensions/mcp/McpLibraryEntryDialog.tsx +418 -0
- package/src/renderer/components/extensions/mcp/McpLibraryPanel.tsx +404 -0
- package/src/renderer/components/extensions/plugins/PluginCard.tsx +10 -2
- package/src/renderer/components/extensions/plugins/PluginsPanel.tsx +40 -22
- package/src/renderer/components/extensions/skills/SkillsLibraryPanel.tsx +335 -0
- package/src/renderer/components/layout/PaneContent.tsx +8 -1
- package/src/renderer/components/layout/Sidebar.tsx +11 -54
- package/src/renderer/components/layout/SortableTab.tsx +20 -31
- package/src/renderer/components/layout/TabBar.tsx +1 -1
- package/src/renderer/components/layout/TabContextMenu.tsx +1 -1
- package/src/renderer/components/runtime/ProviderRuntimeSettingsDialog.tsx +768 -157
- package/src/renderer/components/schedules/SchedulesView.tsx +51 -462
- package/src/renderer/components/schedules/calendar/CalendarDayView.tsx +173 -0
- package/src/renderer/components/schedules/calendar/CalendarEventBlock.tsx +113 -0
- package/src/renderer/components/schedules/calendar/CalendarHeader.tsx +148 -0
- package/src/renderer/components/schedules/calendar/CalendarMonthView.tsx +142 -0
- package/src/renderer/components/schedules/calendar/CalendarWeekView.tsx +219 -0
- package/src/renderer/components/schedules/calendar/ScheduleCalendarBoard.tsx +41 -0
- package/src/renderer/components/schedules/calendar/TeamGanttView.tsx +405 -0
- package/src/renderer/components/schedules/calendar/computeOccurrences.ts +234 -0
- package/src/renderer/components/schedules/calendar/index.ts +2 -0
- package/src/renderer/components/schedules/calendar/types.ts +44 -0
- package/src/renderer/components/settings/SettingsTabs.tsx +50 -55
- package/src/renderer/components/settings/SettingsView.tsx +30 -35
- package/src/renderer/components/settings/components/SettingsSectionHeader.tsx +5 -1
- package/src/renderer/components/settings/components/SettingsSelect.tsx +5 -3
- package/src/renderer/components/settings/components/SettingsToggle.tsx +2 -2
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +11 -42
- package/src/renderer/components/settings/sections/CliStatusSection.tsx +71 -112
- package/src/renderer/components/settings/sections/ConfigEditorDialog.tsx +1 -1
- package/src/renderer/components/settings/sections/GeneralSection.tsx +11 -3
- package/src/renderer/components/settings/sections/HarnessSection.tsx +18 -14
- package/src/renderer/components/settings/sections/PlatformsSection.tsx +3 -3
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +33 -40
- package/src/renderer/components/settings/sections/index.ts +0 -1
- package/src/renderer/components/sidebar/SidebarSessions.tsx +182 -4
- package/src/renderer/components/sidebar/SidebarTaskItem.tsx +4 -4
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +39 -4
- package/src/renderer/components/splash/splashScene.ts +121 -929
- package/src/renderer/components/system-manager/FolderBrowser.tsx +163 -0
- package/src/renderer/components/system-manager/SystemManagerView.tsx +351 -0
- package/src/renderer/components/tasks/TasksView.tsx +112 -134
- package/src/renderer/components/team/CcSessionsSection.tsx +431 -89
- package/src/renderer/components/team/CollapsibleTeamSection.tsx +17 -32
- package/src/renderer/components/team/TeamDetailView.tsx +325 -114
- package/src/renderer/components/team/TeamListView.tsx +108 -123
- package/src/renderer/components/team/dialogs/CreateTaskDialog.tsx +2 -2
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +84 -306
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +259 -342
- package/src/renderer/components/team/dialogs/GlobalTaskDetailDialog.tsx +1 -1
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +17 -15
- package/src/renderer/components/team/dialogs/PlatformBindingDialog.tsx +221 -0
- package/src/renderer/components/team/dialogs/PlatformManualForm.tsx +7 -0
- package/src/renderer/components/team/dialogs/PlatformSetupQR.tsx +1 -1
- package/src/renderer/components/team/dialogs/RuntimeConfigDialog.tsx +361 -0
- package/src/renderer/components/team/dialogs/platformMeta.ts +122 -11
- package/src/renderer/components/team/dialogs/useTeamEditForm.ts +17 -5
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +9 -9
- package/src/renderer/components/team/members/MemberCard.tsx +14 -47
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +3 -95
- package/src/renderer/components/team/members/MemberDetailStats.tsx +50 -65
- package/src/renderer/components/team/messages/MessageComposer.tsx +8 -110
- package/src/renderer/components/team/messages/MessagesPanel.tsx +131 -114
- package/src/renderer/components/team/schedule/ScheduleStatusBadge.tsx +2 -2
- package/src/renderer/components/team/tools/AddMcpInline.tsx +57 -0
- package/src/renderer/components/team/tools/AddSkillInline.tsx +61 -0
- package/src/renderer/components/team/tools/McpChip.tsx +45 -0
- package/src/renderer/components/team/tools/SkillChip.tsx +35 -0
- package/src/renderer/components/team/tools/ToolsSection.tsx +556 -0
- package/src/renderer/hooks/useExtensionsTabState.ts +3 -114
- package/src/renderer/index.css +39 -22
- package/src/renderer/index.html +17 -50
- package/src/renderer/store/index.ts +2 -1
- package/src/renderer/store/slices/scheduleSlice.ts +1 -1
- package/src/renderer/store/slices/teamSlice.ts +45 -168
- package/src/renderer/utils/claudeCodeOnlyProviders.ts +3 -10
- package/src/renderer/utils/memberHelpers.ts +5 -17
- package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +4 -2
- package/src/renderer/utils/providerSlashCommands.ts +0 -5
- package/src/renderer/utils/scheduleFormatters.ts +3 -1
- package/src/renderer/utils/teamMessageFiltering.ts +14 -1
- package/src/renderer/utils/teamModelAvailability.ts +18 -2
- package/src/shared/types/api.ts +121 -2
- package/src/shared/types/ccConnect.ts +2 -0
- package/src/shared/types/extensions/api.ts +9 -0
- package/src/shared/types/extensions/index.ts +4 -0
- package/src/shared/types/extensions/mcp.ts +41 -0
- package/src/shared/types/index.ts +3 -0
- package/src/shared/types/systemManager.ts +49 -0
- package/src/shared/types/team.ts +29 -0
- package/src/shared/types/terminal.ts +4 -2
- package/src/shared/utils/extensionNormalizers.ts +29 -0
- package/src/shared/utils/providerExtensionCapabilities.ts +2 -2
- package/dist-renderer/assets/ProjectEditorOverlay-Va_Vz-zz.js +0 -52
- package/dist-renderer/assets/channel-5dJIx68e.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-BMGXWJ2d.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BMGXWJ2d.js +0 -1
- package/dist-renderer/assets/clone-D7FWfGY9.js +0 -1
- package/dist-renderer/assets/index-B2z_IyRH.css +0 -1
- package/dist-renderer/assets/splashScene-C8lWNnm4.js +0 -1
- package/dist-renderer/assets/stateDiagram-v2-4FDKWEC3-DOYYvDbi.js +0 -1
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +0 -190
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +0 -150
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +0 -381
- package/src/main/services/extensions/install/McpInstallService.ts +0 -407
- package/src/main/services/extensions/state/McpInstallationStateService.ts +0 -42
- package/src/renderer/components/extensions/mcp/McpServerCard.tsx +0 -314
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +0 -765
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +0 -593
- package/src/renderer/components/extensions/skills/SkillDetailDialog.tsx +0 -372
- package/src/renderer/components/extensions/skills/SkillImportDialog.tsx +0 -343
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +0 -659
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
2
|
|
|
3
3
|
import { providersApi } from '@renderer/api/providers';
|
|
4
|
+
import { ProviderBrandLogo } from '@renderer/components/common/ProviderBrandLogo';
|
|
5
|
+
import { AGENT_TYPE_LABELS, ALL_AGENT_TYPES } from '@renderer/components/team/HarnessCards';
|
|
6
|
+
import { Badge } from '@renderer/components/ui/badge';
|
|
4
7
|
import { Button } from '@renderer/components/ui/button';
|
|
8
|
+
import { Checkbox } from '@renderer/components/ui/checkbox';
|
|
5
9
|
import {
|
|
6
10
|
Dialog,
|
|
7
11
|
DialogContent,
|
|
@@ -10,11 +14,18 @@ import {
|
|
|
10
14
|
DialogTitle,
|
|
11
15
|
} from '@renderer/components/ui/dialog';
|
|
12
16
|
import { Input } from '@renderer/components/ui/input';
|
|
17
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
|
|
13
18
|
import { emitOpenHermitEvent, OPEN_HERMIT_EVENTS } from '@renderer/utils/openHermitEvents';
|
|
14
|
-
import { Loader2, RefreshCw } from 'lucide-react';
|
|
19
|
+
import { CheckCircle2, Download, Loader2, Pencil, Plus, RefreshCw, Trash2 } from 'lucide-react';
|
|
15
20
|
|
|
16
21
|
import type { CliProviderId, CliProviderStatus } from '@shared/types';
|
|
17
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
AgentType,
|
|
24
|
+
CCSwitchProvider,
|
|
25
|
+
GlobalProvider,
|
|
26
|
+
ProviderModelEntry,
|
|
27
|
+
ProviderPreset,
|
|
28
|
+
} from '@shared/types/providers';
|
|
18
29
|
|
|
19
30
|
interface Props {
|
|
20
31
|
readonly open: boolean;
|
|
@@ -29,6 +40,20 @@ interface Props {
|
|
|
29
40
|
readonly onRequestLogin?: (providerId: CliProviderId) => void;
|
|
30
41
|
}
|
|
31
42
|
|
|
43
|
+
interface ProviderFormState {
|
|
44
|
+
name: string;
|
|
45
|
+
apiKey: string;
|
|
46
|
+
baseUrl: string;
|
|
47
|
+
model: string;
|
|
48
|
+
modelsText: string;
|
|
49
|
+
thinking: string;
|
|
50
|
+
agentTypes: AgentType[];
|
|
51
|
+
endpoints: Partial<Record<AgentType, string>>;
|
|
52
|
+
agentModels: Partial<Record<AgentType, string>>;
|
|
53
|
+
codexWireApi: string;
|
|
54
|
+
codexHeadersText: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
const AGENT_TYPE_BY_CLI_PROVIDER: Record<CliProviderId, AgentType> = {
|
|
33
58
|
anthropic: 'claudecode',
|
|
34
59
|
codex: 'codex',
|
|
@@ -43,6 +68,32 @@ const CLI_PROVIDER_LABELS: Record<CliProviderId, string> = {
|
|
|
43
68
|
opencode: 'OpenCode',
|
|
44
69
|
};
|
|
45
70
|
|
|
71
|
+
const CORE_AGENT_TYPES: AgentType[] = ['claudecode', 'codex', 'gemini', 'opencode'];
|
|
72
|
+
|
|
73
|
+
const PRESET_AGENT_KEY_MAP: Record<string, AgentType> = {
|
|
74
|
+
claude: 'claudecode',
|
|
75
|
+
anthropic: 'claudecode',
|
|
76
|
+
claudecode: 'claudecode',
|
|
77
|
+
codex: 'codex',
|
|
78
|
+
openai: 'codex',
|
|
79
|
+
gemini: 'gemini',
|
|
80
|
+
opencode: 'opencode',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function normalizeAgentType(value: string): AgentType | null {
|
|
84
|
+
const mapped = PRESET_AGENT_KEY_MAP[value.toLowerCase()];
|
|
85
|
+
if (mapped) return mapped;
|
|
86
|
+
return (ALL_AGENT_TYPES as readonly string[]).includes(value) ? (value as AgentType) : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function providerSupportsAgent(provider: GlobalProvider, agentType: AgentType): boolean {
|
|
90
|
+
return (
|
|
91
|
+
!provider.agent_types ||
|
|
92
|
+
provider.agent_types.length === 0 ||
|
|
93
|
+
provider.agent_types.includes(agentType)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
46
97
|
function resolveProviderEndpoint(provider: GlobalProvider, agentType: AgentType): string {
|
|
47
98
|
return provider.endpoints?.[agentType] ?? provider.base_url ?? '默认端点';
|
|
48
99
|
}
|
|
@@ -56,6 +107,161 @@ function resolveProviderModel(provider: GlobalProvider, agentType: AgentType): s
|
|
|
56
107
|
);
|
|
57
108
|
}
|
|
58
109
|
|
|
110
|
+
function formatModels(models?: ProviderModelEntry[]): string {
|
|
111
|
+
return (models ?? [])
|
|
112
|
+
.map((entry) => (entry.alias ? `${entry.model}:${entry.alias}` : entry.model))
|
|
113
|
+
.join(', ');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function parseModels(text: string): ProviderModelEntry[] | undefined {
|
|
117
|
+
const entries = text
|
|
118
|
+
.split(/[\n,]/)
|
|
119
|
+
.map((part) => part.trim())
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.map((part) => {
|
|
122
|
+
const [model, alias] = part.split(':').map((segment) => segment.trim());
|
|
123
|
+
return alias ? { model, alias } : { model };
|
|
124
|
+
})
|
|
125
|
+
.filter((entry) => entry.model.length > 0);
|
|
126
|
+
return entries.length > 0 ? entries : undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function parseKeyValueText(text: string): Record<string, string> | undefined {
|
|
130
|
+
const out: Record<string, string> = {};
|
|
131
|
+
for (const line of text.split('\n')) {
|
|
132
|
+
const trimmed = line.trim();
|
|
133
|
+
if (!trimmed) continue;
|
|
134
|
+
const separatorIndex = trimmed.indexOf('=');
|
|
135
|
+
if (separatorIndex <= 0) continue;
|
|
136
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
137
|
+
const value = trimmed.slice(separatorIndex + 1).trim();
|
|
138
|
+
if (key) out[key] = value;
|
|
139
|
+
}
|
|
140
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function formatKeyValue(record?: Record<string, string>): string {
|
|
144
|
+
return Object.entries(record ?? {})
|
|
145
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
146
|
+
.join('\n');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function emptyForm(agentType: AgentType): ProviderFormState {
|
|
150
|
+
return {
|
|
151
|
+
name: '',
|
|
152
|
+
apiKey: '',
|
|
153
|
+
baseUrl: '',
|
|
154
|
+
model: '',
|
|
155
|
+
modelsText: '',
|
|
156
|
+
thinking: '',
|
|
157
|
+
agentTypes: [agentType],
|
|
158
|
+
endpoints: {},
|
|
159
|
+
agentModels: {},
|
|
160
|
+
codexWireApi: '',
|
|
161
|
+
codexHeadersText: '',
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function formFromProvider(
|
|
166
|
+
provider: GlobalProvider,
|
|
167
|
+
fallbackAgentType: AgentType
|
|
168
|
+
): ProviderFormState {
|
|
169
|
+
return {
|
|
170
|
+
name: provider.name,
|
|
171
|
+
apiKey: provider.api_key ?? '',
|
|
172
|
+
baseUrl: provider.base_url ?? '',
|
|
173
|
+
model: provider.model ?? '',
|
|
174
|
+
modelsText: formatModels(provider.models),
|
|
175
|
+
thinking: provider.thinking ?? '',
|
|
176
|
+
agentTypes: provider.agent_types?.length ? provider.agent_types : [fallbackAgentType],
|
|
177
|
+
endpoints: provider.endpoints ?? {},
|
|
178
|
+
agentModels: provider.agent_models ?? {},
|
|
179
|
+
codexWireApi: provider.codex?.wire_api ?? '',
|
|
180
|
+
codexHeadersText: formatKeyValue(provider.codex?.http_headers),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function formFromPreset(preset: ProviderPreset, fallbackAgentType: AgentType): ProviderFormState {
|
|
185
|
+
const agentEntries = Object.entries(preset.agents ?? {})
|
|
186
|
+
.map(([rawKey, config]) => ({ agentType: normalizeAgentType(rawKey), config }))
|
|
187
|
+
.filter(
|
|
188
|
+
(
|
|
189
|
+
entry
|
|
190
|
+
): entry is { agentType: AgentType; config: NonNullable<ProviderPreset['agents'][string]> } =>
|
|
191
|
+
entry.agentType != null
|
|
192
|
+
);
|
|
193
|
+
const agentTypes =
|
|
194
|
+
agentEntries.length > 0 ? agentEntries.map((entry) => entry.agentType) : [fallbackAgentType];
|
|
195
|
+
const firstConfig = agentEntries[0]?.config;
|
|
196
|
+
const endpoints: Partial<Record<AgentType, string>> = {};
|
|
197
|
+
const agentModels: Partial<Record<AgentType, string>> = {};
|
|
198
|
+
for (const entry of agentEntries) {
|
|
199
|
+
if (entry.config.base_url) endpoints[entry.agentType] = entry.config.base_url;
|
|
200
|
+
if (entry.config.model) agentModels[entry.agentType] = entry.config.model;
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
name: preset.display_name || preset.name,
|
|
204
|
+
apiKey: '',
|
|
205
|
+
baseUrl: firstConfig?.base_url ?? '',
|
|
206
|
+
model: firstConfig?.model ?? '',
|
|
207
|
+
modelsText: (firstConfig?.models ?? []).join(', '),
|
|
208
|
+
thinking: preset.thinking ?? '',
|
|
209
|
+
agentTypes,
|
|
210
|
+
endpoints,
|
|
211
|
+
agentModels,
|
|
212
|
+
codexWireApi: preset.agents?.codex?.codex_config?.wire_api ?? '',
|
|
213
|
+
codexHeadersText: formatKeyValue(preset.agents?.codex?.codex_config?.http_headers),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function formFromCCSwitch(
|
|
218
|
+
provider: CCSwitchProvider,
|
|
219
|
+
fallbackAgentType: AgentType
|
|
220
|
+
): ProviderFormState {
|
|
221
|
+
const agentType = normalizeAgentType(provider.app_type) ?? fallbackAgentType;
|
|
222
|
+
return {
|
|
223
|
+
...emptyForm(agentType),
|
|
224
|
+
name: provider.name,
|
|
225
|
+
apiKey: provider.api_key ?? '',
|
|
226
|
+
baseUrl: provider.base_url ?? '',
|
|
227
|
+
model: provider.model ?? '',
|
|
228
|
+
agentTypes: [agentType],
|
|
229
|
+
agentModels: provider.model ? { [agentType]: provider.model } : {},
|
|
230
|
+
endpoints: provider.base_url ? { [agentType]: provider.base_url } : {},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function formToProvider(form: ProviderFormState, originalName?: string): GlobalProvider {
|
|
235
|
+
const agentTypes = form.agentTypes.length > 0 ? form.agentTypes : undefined;
|
|
236
|
+
const endpoints = Object.fromEntries(
|
|
237
|
+
Object.entries(form.endpoints).filter(([, value]) => value?.trim())
|
|
238
|
+
) as Partial<Record<AgentType, string>>;
|
|
239
|
+
const agentModels = Object.fromEntries(
|
|
240
|
+
Object.entries(form.agentModels).filter(([, value]) => value?.trim())
|
|
241
|
+
) as Partial<Record<AgentType, string>>;
|
|
242
|
+
const codexHeaders = parseKeyValueText(form.codexHeadersText);
|
|
243
|
+
const codex =
|
|
244
|
+
form.codexWireApi.trim() || codexHeaders
|
|
245
|
+
? {
|
|
246
|
+
...(form.codexWireApi.trim() ? { wire_api: form.codexWireApi.trim() } : {}),
|
|
247
|
+
...(codexHeaders ? { http_headers: codexHeaders } : {}),
|
|
248
|
+
}
|
|
249
|
+
: undefined;
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
name: form.name.trim() || originalName || '',
|
|
253
|
+
...(form.apiKey.trim() ? { api_key: form.apiKey.trim() } : {}),
|
|
254
|
+
...(form.baseUrl.trim() ? { base_url: form.baseUrl.trim() } : {}),
|
|
255
|
+
...(form.model.trim() ? { model: form.model.trim() } : {}),
|
|
256
|
+
...(form.thinking.trim() ? { thinking: form.thinking.trim() } : {}),
|
|
257
|
+
...(agentTypes ? { agent_types: agentTypes } : {}),
|
|
258
|
+
...(parseModels(form.modelsText) ? { models: parseModels(form.modelsText) } : {}),
|
|
259
|
+
...(Object.keys(endpoints).length > 0 ? { endpoints } : {}),
|
|
260
|
+
...(Object.keys(agentModels).length > 0 ? { agent_models: agentModels } : {}),
|
|
261
|
+
...(codex ? { codex } : {}),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
59
265
|
export const ProviderRuntimeSettingsDialog = ({
|
|
60
266
|
open,
|
|
61
267
|
onOpenChange,
|
|
@@ -64,17 +270,20 @@ export const ProviderRuntimeSettingsDialog = ({
|
|
|
64
270
|
const agentType = AGENT_TYPE_BY_CLI_PROVIDER[initialProviderId];
|
|
65
271
|
const harnessLabel = CLI_PROVIDER_LABELS[initialProviderId] ?? initialProviderId;
|
|
66
272
|
const [providers, setProviders] = useState<GlobalProvider[]>([]);
|
|
273
|
+
const [presets, setPresets] = useState<ProviderPreset[]>([]);
|
|
274
|
+
const [ccSwitchProviders, setCcSwitchProviders] = useState<CCSwitchProvider[]>([]);
|
|
275
|
+
const [ccSwitchAvailable, setCcSwitchAvailable] = useState<boolean | null>(null);
|
|
67
276
|
const [loading, setLoading] = useState(false);
|
|
277
|
+
const [presetsLoading, setPresetsLoading] = useState(false);
|
|
278
|
+
const [ccSwitchLoading, setCcSwitchLoading] = useState(false);
|
|
279
|
+
const [saving, setSaving] = useState(false);
|
|
68
280
|
const [error, setError] = useState<string | null>(null);
|
|
69
|
-
const [
|
|
70
|
-
const [
|
|
71
|
-
const [
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const harnessProviders = useMemo(
|
|
77
|
-
() => providers.filter((provider) => provider.agent_types?.includes(agentType)),
|
|
281
|
+
const [formError, setFormError] = useState<string | null>(null);
|
|
282
|
+
const [editingName, setEditingName] = useState<string | null>(null);
|
|
283
|
+
const [form, setForm] = useState<ProviderFormState>(() => emptyForm(agentType));
|
|
284
|
+
|
|
285
|
+
const compatibleProviders = useMemo(
|
|
286
|
+
() => providers.filter((provider) => providerSupportsAgent(provider, agentType)),
|
|
78
287
|
[agentType, providers]
|
|
79
288
|
);
|
|
80
289
|
|
|
@@ -92,194 +301,596 @@ export const ProviderRuntimeSettingsDialog = ({
|
|
|
92
301
|
}
|
|
93
302
|
}, []);
|
|
94
303
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
304
|
+
const refreshPresets = useCallback(async (): Promise<void> => {
|
|
305
|
+
setPresetsLoading(true);
|
|
306
|
+
try {
|
|
307
|
+
const result = await providersApi.fetchPresets();
|
|
308
|
+
setPresets(result.providers ?? []);
|
|
309
|
+
} catch {
|
|
310
|
+
setPresets([]);
|
|
311
|
+
} finally {
|
|
312
|
+
setPresetsLoading(false);
|
|
98
313
|
}
|
|
99
|
-
}, [
|
|
314
|
+
}, []);
|
|
100
315
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
316
|
+
const refreshCCSwitch = useCallback(async (): Promise<void> => {
|
|
317
|
+
setCcSwitchLoading(true);
|
|
318
|
+
try {
|
|
319
|
+
const result = await providersApi.listCCSwitch();
|
|
320
|
+
setCcSwitchProviders(result.providers ?? []);
|
|
321
|
+
setCcSwitchAvailable(result.available);
|
|
322
|
+
} catch {
|
|
323
|
+
setCcSwitchProviders([]);
|
|
324
|
+
setCcSwitchAvailable(false);
|
|
325
|
+
} finally {
|
|
326
|
+
setCcSwitchLoading(false);
|
|
108
327
|
}
|
|
109
|
-
}, [
|
|
328
|
+
}, []);
|
|
329
|
+
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
if (!open) return;
|
|
332
|
+
setForm(emptyForm(agentType));
|
|
333
|
+
setEditingName(null);
|
|
334
|
+
setFormError(null);
|
|
335
|
+
void refreshProviders();
|
|
336
|
+
void refreshPresets();
|
|
337
|
+
void refreshCCSwitch();
|
|
338
|
+
}, [agentType, open, refreshCCSwitch, refreshPresets, refreshProviders]);
|
|
339
|
+
|
|
340
|
+
const updateForm = (patch: Partial<ProviderFormState>): void => {
|
|
341
|
+
setForm((prev) => ({ ...prev, ...patch }));
|
|
342
|
+
setFormError(null);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const toggleAgentType = (nextAgentType: AgentType): void => {
|
|
346
|
+
setForm((prev) => {
|
|
347
|
+
const exists = prev.agentTypes.includes(nextAgentType);
|
|
348
|
+
const next = exists
|
|
349
|
+
? prev.agentTypes.filter((value) => value !== nextAgentType)
|
|
350
|
+
: [...prev.agentTypes, nextAgentType];
|
|
351
|
+
return { ...prev, agentTypes: next.length > 0 ? next : [nextAgentType] };
|
|
352
|
+
});
|
|
353
|
+
setFormError(null);
|
|
354
|
+
};
|
|
110
355
|
|
|
111
|
-
const
|
|
112
|
-
if (!
|
|
113
|
-
|
|
356
|
+
const handleSave = async (): Promise<void> => {
|
|
357
|
+
if (!form.name.trim()) {
|
|
358
|
+
setFormError('请填写 Provider 名称');
|
|
114
359
|
return;
|
|
115
360
|
}
|
|
116
|
-
|
|
117
|
-
|
|
361
|
+
setSaving(true);
|
|
362
|
+
setFormError(null);
|
|
118
363
|
try {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
364
|
+
const payload = formToProvider(form, editingName ?? undefined);
|
|
365
|
+
if (editingName) {
|
|
366
|
+
const existingProvider = providers.find((provider) => provider.name === editingName);
|
|
367
|
+
const { name: _ignoredName, ...patch } = payload;
|
|
368
|
+
await providersApi.update(editingName, {
|
|
369
|
+
...patch,
|
|
370
|
+
...(existingProvider?.env ? { env: existingProvider.env } : {}),
|
|
371
|
+
...(existingProvider?.api_key && !patch.api_key ? { api_key: undefined } : {}),
|
|
372
|
+
...(existingProvider?.base_url && !patch.base_url ? { base_url: undefined } : {}),
|
|
373
|
+
...(existingProvider?.model && !patch.model ? { model: undefined } : {}),
|
|
374
|
+
...(existingProvider?.thinking && !patch.thinking ? { thinking: undefined } : {}),
|
|
375
|
+
...(existingProvider?.models && !patch.models ? { models: undefined } : {}),
|
|
376
|
+
...(existingProvider?.endpoints && !patch.endpoints ? { endpoints: undefined } : {}),
|
|
377
|
+
...(existingProvider?.agent_models && !patch.agent_models
|
|
378
|
+
? { agent_models: undefined }
|
|
379
|
+
: {}),
|
|
380
|
+
...(existingProvider?.codex && !patch.codex ? { codex: undefined } : {}),
|
|
381
|
+
});
|
|
382
|
+
} else {
|
|
383
|
+
await providersApi.add(payload);
|
|
384
|
+
}
|
|
385
|
+
setForm(emptyForm(agentType));
|
|
386
|
+
setEditingName(null);
|
|
130
387
|
await refreshProviders();
|
|
131
388
|
emitOpenHermitEvent(OPEN_HERMIT_EVENTS.providersChanged);
|
|
132
389
|
} catch (err) {
|
|
133
|
-
|
|
390
|
+
setFormError(err instanceof Error ? err.message : '保存 Provider 失败');
|
|
134
391
|
} finally {
|
|
135
|
-
|
|
392
|
+
setSaving(false);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const handleDelete = async (providerName: string): Promise<void> => {
|
|
397
|
+
setSaving(true);
|
|
398
|
+
try {
|
|
399
|
+
await providersApi.remove(providerName);
|
|
400
|
+
if (editingName === providerName) {
|
|
401
|
+
setEditingName(null);
|
|
402
|
+
setForm(emptyForm(agentType));
|
|
403
|
+
}
|
|
404
|
+
await refreshProviders();
|
|
405
|
+
emitOpenHermitEvent(OPEN_HERMIT_EVENTS.providersChanged);
|
|
406
|
+
} catch (err) {
|
|
407
|
+
setError(err instanceof Error ? err.message : '删除 Provider 失败');
|
|
408
|
+
} finally {
|
|
409
|
+
setSaving(false);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const handleImportCCSwitch = async (providerName: string): Promise<void> => {
|
|
414
|
+
setSaving(true);
|
|
415
|
+
try {
|
|
416
|
+
await providersApi.importCCSwitch([providerName]);
|
|
417
|
+
await refreshProviders();
|
|
418
|
+
emitOpenHermitEvent(OPEN_HERMIT_EVENTS.providersChanged);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
setError(err instanceof Error ? err.message : '导入 cc-switch Provider 失败');
|
|
421
|
+
} finally {
|
|
422
|
+
setSaving(false);
|
|
136
423
|
}
|
|
137
424
|
};
|
|
138
425
|
|
|
139
426
|
return (
|
|
140
427
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
141
|
-
<DialogContent className="w-[min(
|
|
428
|
+
<DialogContent className="flex max-h-[88vh] w-[min(96vw,1120px)] max-w-[min(96vw,1120px)] flex-col overflow-hidden">
|
|
142
429
|
<DialogHeader>
|
|
143
|
-
<DialogTitle>{harnessLabel}
|
|
430
|
+
<DialogTitle>{harnessLabel} / 全局 Provider</DialogTitle>
|
|
144
431
|
<DialogDescription>
|
|
145
|
-
|
|
432
|
+
Provider 是全局资源:先在这里配置网关、模型和适用 Harness,再在团队里选择绑定。
|
|
146
433
|
</DialogDescription>
|
|
147
434
|
</DialogHeader>
|
|
148
435
|
|
|
149
|
-
<div className="
|
|
150
|
-
<div
|
|
151
|
-
className="
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
436
|
+
<div className="grid min-h-0 flex-1 gap-4 overflow-hidden lg:grid-cols-[minmax(0,1.05fr)_minmax(360px,0.95fr)]">
|
|
437
|
+
<div className="min-h-0 overflow-y-auto pr-1">
|
|
438
|
+
<Tabs defaultValue="providers" className="min-h-0">
|
|
439
|
+
<TabsList className="mb-3">
|
|
440
|
+
<TabsTrigger value="providers">Provider 库</TabsTrigger>
|
|
441
|
+
<TabsTrigger value="presets">预设</TabsTrigger>
|
|
442
|
+
<TabsTrigger value="cc-switch">cc-switch</TabsTrigger>
|
|
443
|
+
</TabsList>
|
|
444
|
+
|
|
445
|
+
<TabsContent value="providers" className="mt-0 space-y-3">
|
|
446
|
+
<div className="flex items-center justify-between gap-3">
|
|
447
|
+
<div>
|
|
448
|
+
<div className="text-sm font-medium text-[var(--color-text)]">
|
|
449
|
+
全局 Provider
|
|
450
|
+
</div>
|
|
451
|
+
<div className="text-xs text-[var(--color-text-muted)]">
|
|
452
|
+
当前 {compatibleProviders.length} 个适用于 {harnessLabel},共{' '}
|
|
453
|
+
{providers.length} 个。
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
<Button
|
|
457
|
+
size="sm"
|
|
458
|
+
variant="outline"
|
|
459
|
+
disabled={loading}
|
|
460
|
+
onClick={() => void refreshProviders()}
|
|
461
|
+
>
|
|
462
|
+
<RefreshCw
|
|
463
|
+
className={loading ? 'mr-1 size-3.5 animate-spin' : 'mr-1 size-3.5'}
|
|
464
|
+
/>
|
|
465
|
+
刷新
|
|
466
|
+
</Button>
|
|
467
|
+
</div>
|
|
468
|
+
|
|
469
|
+
{error ? (
|
|
470
|
+
<div className="rounded-md border border-red-500/30 bg-red-500/10 px-3 py-2 text-xs text-red-400">
|
|
471
|
+
{error}
|
|
472
|
+
</div>
|
|
473
|
+
) : null}
|
|
474
|
+
|
|
475
|
+
{loading && providers.length === 0 ? (
|
|
476
|
+
<div className="flex items-center gap-2 text-xs text-[var(--color-text-muted)]">
|
|
477
|
+
<Loader2 className="size-3 animate-spin" />
|
|
478
|
+
正在加载 Provider...
|
|
479
|
+
</div>
|
|
480
|
+
) : providers.length === 0 ? (
|
|
481
|
+
<div className="rounded-xl border border-dashed border-[var(--color-border)] p-6 text-center text-sm text-[var(--color-text-muted)]">
|
|
482
|
+
还没有全局 Provider。可以从右侧新建,或从预设/cc-switch 导入。
|
|
483
|
+
</div>
|
|
484
|
+
) : (
|
|
485
|
+
<div className="space-y-2">
|
|
486
|
+
{providers.map((provider) => {
|
|
487
|
+
const isCompatible = providerSupportsAgent(provider, agentType);
|
|
488
|
+
return (
|
|
489
|
+
<div
|
|
490
|
+
key={provider.name}
|
|
491
|
+
className={`rounded-xl border px-3 py-3 ${
|
|
492
|
+
isCompatible
|
|
493
|
+
? 'border-[var(--color-border-subtle)] bg-white/[0.025]'
|
|
494
|
+
: 'border-[var(--color-border)] bg-black/10 opacity-70'
|
|
495
|
+
}`}
|
|
496
|
+
>
|
|
497
|
+
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
498
|
+
<div className="min-w-0">
|
|
499
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
500
|
+
<span className="truncate text-sm font-medium text-[var(--color-text)]">
|
|
501
|
+
{provider.name}
|
|
502
|
+
</span>
|
|
503
|
+
{isCompatible ? (
|
|
504
|
+
<Badge
|
|
505
|
+
variant="secondary"
|
|
506
|
+
className="text-[10px] text-emerald-300"
|
|
507
|
+
>
|
|
508
|
+
适用于当前 Harness
|
|
509
|
+
</Badge>
|
|
510
|
+
) : null}
|
|
511
|
+
<Badge variant="outline" className="text-[10px]">
|
|
512
|
+
{provider.api_key ? 'Key 已配置' : '未配置 Key'}
|
|
513
|
+
</Badge>
|
|
514
|
+
</div>
|
|
515
|
+
<div className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-[var(--color-text-muted)]">
|
|
516
|
+
<span>端点:{resolveProviderEndpoint(provider, agentType)}</span>
|
|
517
|
+
<span>模型:{resolveProviderModel(provider, agentType)}</span>
|
|
518
|
+
</div>
|
|
519
|
+
<div className="mt-2 flex flex-wrap gap-1.5">
|
|
520
|
+
{(provider.agent_types?.length
|
|
521
|
+
? provider.agent_types
|
|
522
|
+
: CORE_AGENT_TYPES
|
|
523
|
+
).map((type) => (
|
|
524
|
+
<Badge key={type} variant="secondary" className="text-[10px]">
|
|
525
|
+
{AGENT_TYPE_LABELS[type] ?? type}
|
|
526
|
+
</Badge>
|
|
527
|
+
))}
|
|
528
|
+
</div>
|
|
529
|
+
</div>
|
|
530
|
+
<div className="flex shrink-0 items-center gap-1">
|
|
531
|
+
<Button
|
|
532
|
+
size="sm"
|
|
533
|
+
variant="ghost"
|
|
534
|
+
className="h-7 px-2 text-xs"
|
|
535
|
+
onClick={() => {
|
|
536
|
+
setEditingName(provider.name);
|
|
537
|
+
setForm(formFromProvider(provider, agentType));
|
|
538
|
+
setFormError(null);
|
|
539
|
+
}}
|
|
540
|
+
>
|
|
541
|
+
<Pencil className="mr-1 size-3" />
|
|
542
|
+
编辑
|
|
543
|
+
</Button>
|
|
544
|
+
<Button
|
|
545
|
+
size="sm"
|
|
546
|
+
variant="ghost"
|
|
547
|
+
className="h-7 px-2 text-xs text-red-400 hover:bg-red-500/10 hover:text-red-300"
|
|
548
|
+
disabled={saving}
|
|
549
|
+
onClick={() => void handleDelete(provider.name)}
|
|
550
|
+
>
|
|
551
|
+
<Trash2 className="mr-1 size-3" />
|
|
552
|
+
删除
|
|
553
|
+
</Button>
|
|
554
|
+
</div>
|
|
555
|
+
</div>
|
|
556
|
+
</div>
|
|
557
|
+
);
|
|
558
|
+
})}
|
|
559
|
+
</div>
|
|
560
|
+
)}
|
|
561
|
+
</TabsContent>
|
|
562
|
+
|
|
563
|
+
<TabsContent value="presets" className="mt-0 space-y-3">
|
|
564
|
+
<div className="flex items-center justify-between gap-3">
|
|
565
|
+
<div>
|
|
566
|
+
<div className="text-sm font-medium text-[var(--color-text)]">从预设开始</div>
|
|
567
|
+
<div className="text-xs text-[var(--color-text-muted)]">
|
|
568
|
+
参考 cc-switch 的交互:先选网关预设,再补 Key、模型和适用 Harness。
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
<Button
|
|
572
|
+
size="sm"
|
|
573
|
+
variant="outline"
|
|
574
|
+
disabled={presetsLoading}
|
|
575
|
+
onClick={() => void refreshPresets()}
|
|
576
|
+
>
|
|
577
|
+
<RefreshCw
|
|
578
|
+
className={presetsLoading ? 'mr-1 size-3.5 animate-spin' : 'mr-1 size-3.5'}
|
|
579
|
+
/>
|
|
580
|
+
刷新
|
|
581
|
+
</Button>
|
|
582
|
+
</div>
|
|
583
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
584
|
+
<button
|
|
585
|
+
type="button"
|
|
586
|
+
className="rounded-xl border border-dashed border-[var(--color-border)] p-3 text-left transition hover:border-[var(--color-border-emphasis)] hover:bg-white/[0.035]"
|
|
587
|
+
onClick={() => {
|
|
588
|
+
setEditingName(null);
|
|
589
|
+
setForm({ ...emptyForm(agentType), agentTypes: CORE_AGENT_TYPES });
|
|
590
|
+
setFormError(null);
|
|
591
|
+
}}
|
|
592
|
+
>
|
|
593
|
+
<div className="flex items-center gap-2 text-sm font-medium text-[var(--color-text)]">
|
|
594
|
+
<Plus className="size-4" />
|
|
595
|
+
自定义网关
|
|
596
|
+
</div>
|
|
597
|
+
<p className="mt-1 text-xs text-[var(--color-text-muted)]">
|
|
598
|
+
手动配置一个可用于多个 Harness 的 API 网关。
|
|
599
|
+
</p>
|
|
600
|
+
</button>
|
|
601
|
+
{presets.map((preset) => (
|
|
602
|
+
<button
|
|
603
|
+
key={preset.name}
|
|
604
|
+
type="button"
|
|
605
|
+
className="rounded-xl border border-[var(--color-border-subtle)] bg-white/[0.025] p-3 text-left transition hover:border-[var(--color-border-emphasis)] hover:bg-white/[0.045]"
|
|
606
|
+
onClick={() => {
|
|
607
|
+
setEditingName(null);
|
|
608
|
+
setForm(formFromPreset(preset, agentType));
|
|
609
|
+
setFormError(null);
|
|
610
|
+
}}
|
|
611
|
+
>
|
|
612
|
+
<div className="flex items-start justify-between gap-2">
|
|
613
|
+
<div className="text-sm font-medium text-[var(--color-text)]">
|
|
614
|
+
{preset.display_name || preset.name}
|
|
615
|
+
</div>
|
|
616
|
+
{preset.featured ? <Badge className="text-[10px]">推荐</Badge> : null}
|
|
617
|
+
</div>
|
|
618
|
+
<p className="mt-1 line-clamp-2 text-xs text-[var(--color-text-muted)]">
|
|
619
|
+
{preset.description_zh ||
|
|
620
|
+
preset.description ||
|
|
621
|
+
preset.website ||
|
|
622
|
+
'Provider 预设'}
|
|
623
|
+
</p>
|
|
624
|
+
</button>
|
|
625
|
+
))}
|
|
626
|
+
</div>
|
|
627
|
+
</TabsContent>
|
|
628
|
+
|
|
629
|
+
<TabsContent value="cc-switch" className="mt-0 space-y-3">
|
|
630
|
+
<div className="flex items-center justify-between gap-3">
|
|
631
|
+
<div>
|
|
632
|
+
<div className="text-sm font-medium text-[var(--color-text)]">
|
|
633
|
+
从 cc-switch 导入
|
|
634
|
+
</div>
|
|
635
|
+
<div className="text-xs text-[var(--color-text-muted)]">
|
|
636
|
+
可导入已有 cc-switch Provider,再在右侧按 Hermit 字段调整。
|
|
637
|
+
</div>
|
|
638
|
+
</div>
|
|
639
|
+
<Button
|
|
640
|
+
size="sm"
|
|
641
|
+
variant="outline"
|
|
642
|
+
disabled={ccSwitchLoading}
|
|
643
|
+
onClick={() => void refreshCCSwitch()}
|
|
644
|
+
>
|
|
645
|
+
<RefreshCw
|
|
646
|
+
className={ccSwitchLoading ? 'mr-1 size-3.5 animate-spin' : 'mr-1 size-3.5'}
|
|
647
|
+
/>
|
|
648
|
+
刷新
|
|
649
|
+
</Button>
|
|
650
|
+
</div>
|
|
651
|
+
{ccSwitchAvailable === false ? (
|
|
652
|
+
<div className="rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-300">
|
|
653
|
+
没有检测到可导入的 Provider,或服务未返回导入数据。
|
|
654
|
+
</div>
|
|
655
|
+
) : null}
|
|
656
|
+
<div className="space-y-2">
|
|
657
|
+
{ccSwitchProviders.map((provider) => (
|
|
658
|
+
<div
|
|
659
|
+
key={`${provider.app_type}:${provider.name}`}
|
|
660
|
+
className="rounded-xl border border-[var(--color-border-subtle)] bg-white/[0.025] px-3 py-3"
|
|
661
|
+
>
|
|
662
|
+
<div className="flex flex-wrap items-start justify-between gap-2">
|
|
663
|
+
<div className="min-w-0">
|
|
664
|
+
<div className="flex items-center gap-2 text-sm font-medium text-[var(--color-text)]">
|
|
665
|
+
{provider.is_current ? (
|
|
666
|
+
<CheckCircle2 className="size-3.5 text-emerald-400" />
|
|
667
|
+
) : null}
|
|
668
|
+
{provider.name}
|
|
669
|
+
</div>
|
|
670
|
+
<div className="mt-1 text-[11px] text-[var(--color-text-muted)]">
|
|
671
|
+
{provider.app_type} · {provider.base_url || '默认端点'} ·{' '}
|
|
672
|
+
{provider.model || '未指定模型'}
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
<div className="flex items-center gap-1">
|
|
676
|
+
<Button
|
|
677
|
+
size="sm"
|
|
678
|
+
variant="ghost"
|
|
679
|
+
className="h-7 px-2 text-xs"
|
|
680
|
+
onClick={() => {
|
|
681
|
+
setEditingName(null);
|
|
682
|
+
setForm(formFromCCSwitch(provider, agentType));
|
|
683
|
+
setFormError(null);
|
|
684
|
+
}}
|
|
685
|
+
>
|
|
686
|
+
填入表单
|
|
687
|
+
</Button>
|
|
688
|
+
<Button
|
|
689
|
+
size="sm"
|
|
690
|
+
variant="outline"
|
|
691
|
+
className="h-7 px-2 text-xs"
|
|
692
|
+
disabled={saving}
|
|
693
|
+
onClick={() => void handleImportCCSwitch(provider.name)}
|
|
694
|
+
>
|
|
695
|
+
<Download className="mr-1 size-3" />
|
|
696
|
+
直接导入
|
|
697
|
+
</Button>
|
|
698
|
+
</div>
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
))}
|
|
702
|
+
{ccSwitchProviders.length === 0 && !ccSwitchLoading ? (
|
|
703
|
+
<div className="rounded-xl border border-dashed border-[var(--color-border)] p-5 text-center text-xs text-[var(--color-text-muted)]">
|
|
704
|
+
暂无 cc-switch Provider 可导入。
|
|
705
|
+
</div>
|
|
706
|
+
) : null}
|
|
707
|
+
</div>
|
|
708
|
+
</TabsContent>
|
|
709
|
+
</Tabs>
|
|
197
710
|
</div>
|
|
198
711
|
|
|
199
|
-
<div
|
|
200
|
-
className="
|
|
201
|
-
style={{ borderColor: 'var(--color-border-subtle)' }}
|
|
202
|
-
>
|
|
203
|
-
<div className="flex items-center justify-between gap-3">
|
|
712
|
+
<div className="min-h-0 overflow-y-auto rounded-xl border border-[var(--color-border-subtle)] bg-white/[0.025] p-4">
|
|
713
|
+
<div className="flex items-start justify-between gap-3">
|
|
204
714
|
<div>
|
|
205
|
-
<div className="text-sm font-
|
|
206
|
-
|
|
207
|
-
</div>
|
|
208
|
-
<div className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
|
|
209
|
-
Agent 类型:{agentType}
|
|
715
|
+
<div className="text-sm font-semibold text-[var(--color-text)]">
|
|
716
|
+
{editingName ? `编辑 ${editingName}` : 'Provider 表单'}
|
|
210
717
|
</div>
|
|
718
|
+
<p className="mt-0.5 text-xs text-[var(--color-text-muted)]">
|
|
719
|
+
这里配置全局 Provider;团队里只选择启用,不重复填写。
|
|
720
|
+
</p>
|
|
211
721
|
</div>
|
|
212
722
|
<Button
|
|
213
723
|
size="sm"
|
|
214
724
|
variant="ghost"
|
|
215
|
-
|
|
216
|
-
onClick={() =>
|
|
725
|
+
className="h-7 px-2 text-xs"
|
|
726
|
+
onClick={() => {
|
|
727
|
+
setEditingName(null);
|
|
728
|
+
setForm(emptyForm(agentType));
|
|
729
|
+
setFormError(null);
|
|
730
|
+
}}
|
|
217
731
|
>
|
|
218
|
-
|
|
219
|
-
刷新
|
|
732
|
+
清空
|
|
220
733
|
</Button>
|
|
221
734
|
</div>
|
|
222
735
|
|
|
223
|
-
|
|
224
|
-
<div className="
|
|
225
|
-
|
|
736
|
+
<div className="mt-4 space-y-4">
|
|
737
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
738
|
+
<label className="space-y-1 text-xs text-[var(--color-text-secondary)]">
|
|
739
|
+
<span>Provider 名称</span>
|
|
740
|
+
<Input
|
|
741
|
+
value={form.name}
|
|
742
|
+
disabled={editingName != null}
|
|
743
|
+
onChange={(event) => updateForm({ name: event.target.value })}
|
|
744
|
+
placeholder="NewAPI / n1n.ai / custom"
|
|
745
|
+
/>
|
|
746
|
+
</label>
|
|
747
|
+
<label className="space-y-1 text-xs text-[var(--color-text-secondary)]">
|
|
748
|
+
<span>默认模型</span>
|
|
749
|
+
<Input
|
|
750
|
+
value={form.model}
|
|
751
|
+
onChange={(event) => updateForm({ model: event.target.value })}
|
|
752
|
+
placeholder="claude-sonnet-4 / gpt-4o / gemini-2.5-pro"
|
|
753
|
+
/>
|
|
754
|
+
</label>
|
|
755
|
+
<label className="space-y-1 text-xs text-[var(--color-text-secondary)]">
|
|
756
|
+
<span>Base URL</span>
|
|
757
|
+
<Input
|
|
758
|
+
value={form.baseUrl}
|
|
759
|
+
onChange={(event) => updateForm({ baseUrl: event.target.value })}
|
|
760
|
+
placeholder="https://api.example.com/v1"
|
|
761
|
+
/>
|
|
762
|
+
</label>
|
|
763
|
+
<label className="space-y-1 text-xs text-[var(--color-text-secondary)]">
|
|
764
|
+
<span>API Key</span>
|
|
765
|
+
<Input
|
|
766
|
+
type="password"
|
|
767
|
+
value={form.apiKey}
|
|
768
|
+
onChange={(event) => updateForm({ apiKey: event.target.value })}
|
|
769
|
+
placeholder="sk-..."
|
|
770
|
+
/>
|
|
771
|
+
</label>
|
|
226
772
|
</div>
|
|
227
|
-
) : null}
|
|
228
773
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
774
|
+
<div className="space-y-2">
|
|
775
|
+
<div className="text-xs font-medium text-[var(--color-text-secondary)]">
|
|
776
|
+
适用 Harness
|
|
777
|
+
</div>
|
|
778
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
779
|
+
{ALL_AGENT_TYPES.map((type) => (
|
|
780
|
+
<label
|
|
781
|
+
key={type}
|
|
782
|
+
className="flex cursor-pointer items-center gap-2 rounded-lg border border-[var(--color-border-subtle)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)] hover:bg-white/[0.03]"
|
|
783
|
+
>
|
|
784
|
+
<Checkbox
|
|
785
|
+
checked={form.agentTypes.includes(type as AgentType)}
|
|
786
|
+
onCheckedChange={() => toggleAgentType(type as AgentType)}
|
|
787
|
+
/>
|
|
788
|
+
<span>{AGENT_TYPE_LABELS[type] ?? type}</span>
|
|
789
|
+
</label>
|
|
790
|
+
))}
|
|
791
|
+
</div>
|
|
233
792
|
</div>
|
|
234
|
-
|
|
793
|
+
|
|
794
|
+
<label className="block space-y-1 text-xs text-[var(--color-text-secondary)]">
|
|
795
|
+
<span>模型列表(逗号或换行分隔,支持 model:alias)</span>
|
|
796
|
+
<textarea
|
|
797
|
+
value={form.modelsText}
|
|
798
|
+
onChange={(event) => updateForm({ modelsText: event.target.value })}
|
|
799
|
+
className="min-h-16 w-full rounded-md border border-[var(--color-border)] bg-transparent px-3 py-2 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
|
|
800
|
+
placeholder="claude-sonnet-4, claude-opus-4:Opus"
|
|
801
|
+
/>
|
|
802
|
+
</label>
|
|
803
|
+
|
|
235
804
|
<div className="space-y-2">
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
borderColor: 'var(--color-border-subtle)',
|
|
242
|
-
backgroundColor: 'rgba(255, 255, 255, 0.025)',
|
|
243
|
-
}}
|
|
244
|
-
>
|
|
245
|
-
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
246
|
-
<div className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>
|
|
247
|
-
{provider.name}
|
|
248
|
-
</div>
|
|
249
|
-
<span
|
|
250
|
-
className="rounded-full px-2 py-0.5 text-[11px]"
|
|
251
|
-
style={{
|
|
252
|
-
color: provider.api_key ? '#86efac' : '#fbbf24',
|
|
253
|
-
backgroundColor: provider.api_key
|
|
254
|
-
? 'rgba(74, 222, 128, 0.14)'
|
|
255
|
-
: 'rgba(245, 158, 11, 0.12)',
|
|
256
|
-
}}
|
|
257
|
-
>
|
|
258
|
-
{provider.api_key ? 'API Key 已配置' : '未配置 Key'}
|
|
259
|
-
</span>
|
|
260
|
-
</div>
|
|
805
|
+
<div className="text-xs font-medium text-[var(--color-text-secondary)]">
|
|
806
|
+
每个 Harness 的覆盖配置
|
|
807
|
+
</div>
|
|
808
|
+
<div className="space-y-2">
|
|
809
|
+
{form.agentTypes.map((type) => (
|
|
261
810
|
<div
|
|
262
|
-
|
|
263
|
-
|
|
811
|
+
key={type}
|
|
812
|
+
className="rounded-lg border border-[var(--color-border-subtle)] p-2"
|
|
264
813
|
>
|
|
265
|
-
<
|
|
266
|
-
|
|
267
|
-
|
|
814
|
+
<div className="mb-2 flex items-center gap-2 text-xs font-medium text-[var(--color-text)]">
|
|
815
|
+
{type === 'claudecode' ? (
|
|
816
|
+
<ProviderBrandLogo providerId="anthropic" className="size-3.5" />
|
|
817
|
+
) : null}
|
|
818
|
+
{type === 'codex' ? (
|
|
819
|
+
<ProviderBrandLogo providerId="codex" className="size-3.5" />
|
|
820
|
+
) : null}
|
|
821
|
+
{type === 'gemini' ? (
|
|
822
|
+
<ProviderBrandLogo providerId="gemini" className="size-3.5" />
|
|
823
|
+
) : null}
|
|
824
|
+
{type === 'opencode' ? (
|
|
825
|
+
<ProviderBrandLogo providerId="opencode" className="size-3.5" />
|
|
826
|
+
) : null}
|
|
827
|
+
{AGENT_TYPE_LABELS[type] ?? type}
|
|
828
|
+
</div>
|
|
829
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
830
|
+
<Input
|
|
831
|
+
value={form.endpoints[type] ?? ''}
|
|
832
|
+
onChange={(event) =>
|
|
833
|
+
updateForm({
|
|
834
|
+
endpoints: { ...form.endpoints, [type]: event.target.value },
|
|
835
|
+
})
|
|
836
|
+
}
|
|
837
|
+
placeholder="专用 endpoint(可选)"
|
|
838
|
+
/>
|
|
839
|
+
<Input
|
|
840
|
+
value={form.agentModels[type] ?? ''}
|
|
841
|
+
onChange={(event) =>
|
|
842
|
+
updateForm({
|
|
843
|
+
agentModels: { ...form.agentModels, [type]: event.target.value },
|
|
844
|
+
})
|
|
845
|
+
}
|
|
846
|
+
placeholder="专用默认模型(可选)"
|
|
847
|
+
/>
|
|
848
|
+
</div>
|
|
268
849
|
</div>
|
|
269
|
-
|
|
270
|
-
|
|
850
|
+
))}
|
|
851
|
+
</div>
|
|
271
852
|
</div>
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
className="rounded-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
853
|
+
|
|
854
|
+
{form.agentTypes.includes('codex') ? (
|
|
855
|
+
<div className="space-y-2 rounded-lg border border-[var(--color-border-subtle)] p-3">
|
|
856
|
+
<div className="text-xs font-medium text-[var(--color-text-secondary)]">
|
|
857
|
+
Codex 高级配置
|
|
858
|
+
</div>
|
|
859
|
+
<Input
|
|
860
|
+
value={form.codexWireApi}
|
|
861
|
+
onChange={(event) => updateForm({ codexWireApi: event.target.value })}
|
|
862
|
+
placeholder="wire_api(可选)"
|
|
863
|
+
/>
|
|
864
|
+
<textarea
|
|
865
|
+
value={form.codexHeadersText}
|
|
866
|
+
onChange={(event) => updateForm({ codexHeadersText: event.target.value })}
|
|
867
|
+
className="min-h-14 w-full rounded-md border border-[var(--color-border)] bg-transparent px-3 py-2 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
|
|
868
|
+
placeholder="HTTP headers,每行 KEY=VALUE"
|
|
869
|
+
/>
|
|
870
|
+
</div>
|
|
871
|
+
) : null}
|
|
872
|
+
|
|
873
|
+
<label className="block space-y-1 text-xs text-[var(--color-text-secondary)]">
|
|
874
|
+
<span>Thinking 设置(可选)</span>
|
|
875
|
+
<Input
|
|
876
|
+
value={form.thinking}
|
|
877
|
+
onChange={(event) => updateForm({ thinking: event.target.value })}
|
|
878
|
+
placeholder="enabled / disabled / 留空"
|
|
879
|
+
/>
|
|
880
|
+
</label>
|
|
881
|
+
|
|
882
|
+
{formError ? <div className="text-xs text-red-400">{formError}</div> : null}
|
|
883
|
+
|
|
884
|
+
<div className="flex justify-end gap-2 border-t border-[var(--color-border-subtle)] pt-3">
|
|
885
|
+
<Button variant="outline" disabled={saving} onClick={() => onOpenChange(false)}>
|
|
886
|
+
关闭
|
|
887
|
+
</Button>
|
|
888
|
+
<Button disabled={saving} onClick={() => void handleSave()}>
|
|
889
|
+
{saving ? <Loader2 className="mr-1 size-3.5 animate-spin" /> : null}
|
|
890
|
+
{editingName ? '保存修改' : '保存 Provider'}
|
|
891
|
+
</Button>
|
|
281
892
|
</div>
|
|
282
|
-
|
|
893
|
+
</div>
|
|
283
894
|
</div>
|
|
284
895
|
</div>
|
|
285
896
|
</DialogContent>
|