@yancyyu/openhermit 1.6.30 → 1.6.32
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-DsQt4FHy.js → ProjectEditorOverlay-DwWYwUf8.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-BjZC53xf.js → TeamGraphOverlay-BK6PUN3W.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-CrWocIjq.js → _basePickBy-DdNwxEcj.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-B6d8ysWi.js → _baseUniq-BKO88SUv.js} +1 -1
- package/dist-renderer/assets/{arc-DAIYCFP8.js → arc-8OCcRtx5.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-B3UudXJh.js → architectureDiagram-VXUJARFQ-BaVzqHNU.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-DbptKQ4W.js → blockDiagram-VD42YOAC-BlD3aS2M.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-C4WQuZpV.js → c4Diagram-YG6GDRKO-CWhysgWg.js} +1 -1
- package/dist-renderer/assets/channel-VSASRd7w.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-Dp7fVpI_.js → chunk-4BX2VUAB-39vXfQp7.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-B8KGfbAy.js → chunk-55IACEB6-uCvPl6T8.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-BG1oJrjA.js → chunk-B4BG7PRW-BrGj559B.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-DRmxNjht.js → chunk-DI55MBZ5-Djfr1KmT.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-D6VLvy16.js → chunk-FMBD7UC4-Cv2iCCiq.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-DZou1667.js → chunk-QN33PNHL-CXzDZbbd.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-CghmasSh.js → chunk-QZHKN3VN-CRO6vWKS.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-B7apcMPK.js → chunk-TZMSLE5B-D5Luxvqw.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CZKOiI95.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CZKOiI95.js +1 -0
- package/dist-renderer/assets/clone-CEVt7zS8.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-05e5uQDp.js → cose-bilkent-S5V4N54A-BrIj9YHY.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-B06bRykF.js → dagre-6UL2VRFP-BFPvK4JJ.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-CY7VYQ7c.js → diagram-PSM6KHXK-CTyUB42n.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-BjKEH7dD.js → diagram-QEK2KX5R-V9DMzwyh.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-Bf4ELS1_.js → diagram-S2PKOQOG-D8fWNJWp.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-DJ753_L9.js → erDiagram-Q2GNP2WA-C69oTboq.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-B71S-lC-.js → flowDiagram-NV44I4VS-C8MnYH3t.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-C_U42mSZ.js → ganttDiagram-JELNMOA3-DnRfBK-T.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js → gitGraphDiagram-V2S2FVAM-BzUxkq9s.js} +1 -1
- package/dist-renderer/assets/{graph-DY3qbzqj.js → graph-DUpjXmIN.js} +1 -1
- package/dist-renderer/assets/{index-C8B_nKOF.js → index-BnyC9eDn.js} +1 -1
- package/dist-renderer/assets/{index-BlOrAXp3.js → index-CP1a4BYJ.js} +569 -569
- package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
- package/dist-renderer/assets/{index-Bs27J5gB.js → index-CWP6WvZl.js} +1 -1
- package/dist-renderer/assets/{index-DLKyDr4T.js → index-D_f0E90u.js} +1 -1
- package/dist-renderer/assets/{index-Dhsk3_DD.js → index-F0-beTLg.js} +1 -1
- package/dist-renderer/assets/{index-GpUvV2xs.js → index-pIDl3FPP.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-BNs0y3IG.js → infoDiagram-HS3SLOUP-B43WMCmB.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-CqPnw4UV.js → journeyDiagram-XKPGCS4Q-DjfJCd1x.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-SLlzcUJ2.js → kanban-definition-3W4ZIXB7-CjRQqAUU.js} +1 -1
- package/dist-renderer/assets/{layout-BZLlNmbr.js → layout-DmcOdDIQ.js} +1 -1
- package/dist-renderer/assets/{linear-qz6v45xy.js → linear-DP0CqQZK.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-B1-kmEWV.js → mindmap-definition-VGOIOE7T-BMvf_dPT.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-B8a02iNx.js → pieDiagram-ADFJNKIX-C2eg65Te.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-BKv1Xfou.js → quadrantDiagram-AYHSOK5B-DsWbG3ez.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-B3DUpZi2.js → requirementDiagram-UZGBJVZJ-DU1uyMjP.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-DmPzuTsy.js → sankeyDiagram-TZEHDZUN-DJVBJwOK.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-Bo7RelRb.js → sequenceDiagram-WL72ISMW-CfRnIrs0.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-1epX98gV.js → stateDiagram-FKZM4ZOC-cNHRId0N.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-03Ym9PTr.js → stateDiagram-v2-4FDKWEC3-SBUDuLpP.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-r6isC62H.js → timeline-definition-IT6M3QCI-PwVrIKR_.js} +1 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-Dtx0XOre.js +162 -0
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-t4-rwdAw.js → xychartDiagram-PRI3JC2R-CqS3H24Y.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +4 -1
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +100 -15
- package/src/renderer/components/team/TeamDetailView.tsx +55 -139
- package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
- package/src/renderer/components/team/TeamListView.tsx +7 -32
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +283 -409
- package/src/renderer/components/team/dialogs/useTeamEditForm.ts +280 -0
- package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
- package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
- package/dist-renderer/assets/channel-DbjZvWii.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +0 -1
- package/dist-renderer/assets/clone-CJ1kxO2J.js +0 -1
- package/dist-renderer/assets/index-CmZPUEhS.css +0 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-CGKpOUF2.js +0 -162
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { useEffect
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
|
|
3
|
-
import { api } from '@renderer/api';
|
|
4
|
-
import { AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
|
|
5
|
-
import { HarnessSelect } from '@renderer/components/team/HarnessSelect';
|
|
6
3
|
import { Button } from '@renderer/components/ui/button';
|
|
7
4
|
import { Checkbox } from '@renderer/components/ui/checkbox';
|
|
8
5
|
import {
|
|
@@ -13,496 +10,375 @@ import {
|
|
|
13
10
|
DialogHeader,
|
|
14
11
|
DialogTitle,
|
|
15
12
|
} from '@renderer/components/ui/dialog';
|
|
13
|
+
import { AGENT_TYPE_LABELS } from '@renderer/components/team/HarnessCards';
|
|
14
|
+
import { HarnessSelect } from '@renderer/components/team/HarnessSelect';
|
|
16
15
|
import { Loader2, Trash2 } from 'lucide-react';
|
|
17
16
|
|
|
18
|
-
import type { ResolvedTeamMember } from '@shared/types';
|
|
19
17
|
import type { CcAgentType } from '@shared/types/ccConnect';
|
|
20
|
-
|
|
18
|
+
|
|
19
|
+
import { PERMISSION_MODE_OPTIONS, useTeamEditForm } from './useTeamEditForm';
|
|
21
20
|
|
|
22
21
|
interface EditTeamDialogProps {
|
|
23
22
|
open: boolean;
|
|
24
23
|
teamName: string;
|
|
25
|
-
currentName: string;
|
|
26
|
-
currentDescription: string;
|
|
27
|
-
currentColor: string;
|
|
28
|
-
currentAgentType?: string;
|
|
29
|
-
currentWorkDir?: string;
|
|
30
|
-
currentPermissionMode?: string;
|
|
31
|
-
currentLanguage?: string;
|
|
32
|
-
currentShowContextIndicator?: boolean;
|
|
33
|
-
currentReplyFooter?: boolean;
|
|
34
|
-
currentInjectSender?: boolean;
|
|
35
|
-
currentManagedSources?: string;
|
|
36
|
-
currentDisabledCommands?: string[];
|
|
37
|
-
currentPlatformAllowFrom?: Record<string, string>;
|
|
38
|
-
currentProviderRefs?: string[];
|
|
39
|
-
globalProviders?: GlobalProvider[];
|
|
40
|
-
currentMembers: ResolvedTeamMember[];
|
|
41
|
-
leadMember?: ResolvedTeamMember | null;
|
|
42
|
-
resolvedMemberColorMap?: ReadonlyMap<string, string>;
|
|
43
|
-
isTeamAlive?: boolean;
|
|
44
|
-
isTeamProvisioning?: boolean;
|
|
45
|
-
projectPath?: string | null;
|
|
46
|
-
/** Deprecated in cc-connect mode: runtime edits are managed from Harness configuration. */
|
|
47
|
-
savedLaunchRequest?: unknown;
|
|
48
24
|
onClose: () => void;
|
|
49
|
-
onSaved: () => Promise<void> | void;
|
|
50
|
-
onRestartTeam?: () => Promise<void> | void;
|
|
51
25
|
onDeleteTeam?: () => void;
|
|
52
26
|
}
|
|
53
27
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
28
|
+
// ── Section wrapper ──────────────────────────────────────────
|
|
29
|
+
function FormSection({
|
|
30
|
+
title,
|
|
31
|
+
description,
|
|
32
|
+
variant = 'default',
|
|
33
|
+
children,
|
|
34
|
+
}: {
|
|
35
|
+
title: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
variant?: 'default' | 'danger';
|
|
38
|
+
children: React.ReactNode;
|
|
39
|
+
}): React.JSX.Element {
|
|
40
|
+
const border = variant === 'danger' ? 'border-red-500/40' : 'border-[var(--color-border)]';
|
|
41
|
+
const bg = variant === 'danger' ? 'bg-red-500/5' : '';
|
|
42
|
+
const titleColor = variant === 'danger' ? 'text-red-300' : 'text-[var(--color-text)]';
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className={`rounded-md border ${border} ${bg} p-3`}>
|
|
46
|
+
<h3 className={`text-sm font-medium ${titleColor}`}>{title}</h3>
|
|
47
|
+
{description && (
|
|
48
|
+
<p className="mt-0.5 text-xs text-[var(--color-text-muted)]">{description}</p>
|
|
49
|
+
)}
|
|
50
|
+
<div className="mt-3 space-y-3">{children}</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Shared input class ───────────────────────────────────────
|
|
56
|
+
const inputCls =
|
|
57
|
+
'w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]';
|
|
58
|
+
|
|
59
|
+
const labelCls = 'mb-1 block text-xs font-medium text-[var(--color-text-secondary)]';
|
|
60
60
|
|
|
61
|
+
// ── Main component ───────────────────────────────────────────
|
|
61
62
|
export const EditTeamDialog = ({
|
|
62
63
|
open,
|
|
63
64
|
teamName,
|
|
64
|
-
currentName,
|
|
65
|
-
currentDescription,
|
|
66
|
-
currentColor,
|
|
67
|
-
currentAgentType = 'cursor',
|
|
68
|
-
currentWorkDir = '',
|
|
69
|
-
currentPermissionMode = 'default',
|
|
70
|
-
currentLanguage = 'zh',
|
|
71
|
-
currentShowContextIndicator = true,
|
|
72
|
-
currentReplyFooter = true,
|
|
73
|
-
currentInjectSender = false,
|
|
74
|
-
currentManagedSources = '*',
|
|
75
|
-
currentDisabledCommands = [],
|
|
76
|
-
currentPlatformAllowFrom = {},
|
|
77
|
-
currentProviderRefs = [],
|
|
78
|
-
globalProviders = [],
|
|
79
|
-
isTeamAlive = false,
|
|
80
|
-
isTeamProvisioning = false,
|
|
81
65
|
onClose,
|
|
82
|
-
onSaved,
|
|
83
|
-
onRestartTeam,
|
|
84
66
|
onDeleteTeam,
|
|
85
67
|
}: EditTeamDialogProps): React.JSX.Element => {
|
|
86
|
-
const
|
|
87
|
-
const [description, setDescription] = useState(currentDescription);
|
|
88
|
-
const [agentType, setAgentType] = useState(currentAgentType);
|
|
89
|
-
const [teamWorkDir, setTeamWorkDir] = useState(currentWorkDir);
|
|
90
|
-
const [permissionMode, setPermissionMode] = useState(currentPermissionMode);
|
|
91
|
-
const [language, setLanguage] = useState(currentLanguage);
|
|
92
|
-
const [showContextIndicator, setShowContextIndicator] = useState(currentShowContextIndicator);
|
|
93
|
-
const [replyFooter, setReplyFooter] = useState(currentReplyFooter);
|
|
94
|
-
const [injectSender, setInjectSender] = useState(currentInjectSender);
|
|
95
|
-
const [managedSources, setManagedSources] = useState(currentManagedSources);
|
|
96
|
-
const [disabledCommandsInput, setDisabledCommandsInput] = useState(
|
|
97
|
-
currentDisabledCommands.join(', ')
|
|
98
|
-
);
|
|
99
|
-
const [feishuAllowFrom, setFeishuAllowFrom] = useState(currentPlatformAllowFrom.feishu ?? '*');
|
|
100
|
-
const [providerRefs, setProviderRefs] = useState<string>(currentProviderRefs[0] ?? '');
|
|
101
|
-
const [saving, setSaving] = useState(false);
|
|
102
|
-
const [error, setError] = useState<string | null>(null);
|
|
68
|
+
const form = useTeamEditForm(teamName, open);
|
|
103
69
|
|
|
104
|
-
|
|
105
|
-
if (!open) return;
|
|
106
|
-
setName(currentName);
|
|
107
|
-
setDescription(currentDescription);
|
|
108
|
-
setAgentType(currentAgentType);
|
|
109
|
-
setTeamWorkDir(currentWorkDir);
|
|
110
|
-
setPermissionMode(currentPermissionMode);
|
|
111
|
-
setLanguage(currentLanguage);
|
|
112
|
-
setShowContextIndicator(currentShowContextIndicator);
|
|
113
|
-
setReplyFooter(currentReplyFooter);
|
|
114
|
-
setInjectSender(currentInjectSender);
|
|
115
|
-
setManagedSources(currentManagedSources);
|
|
116
|
-
setDisabledCommandsInput(currentDisabledCommands.join(', '));
|
|
117
|
-
setFeishuAllowFrom(currentPlatformAllowFrom.feishu ?? '*');
|
|
118
|
-
setProviderRefs(currentProviderRefs[0] ?? '');
|
|
119
|
-
setError(null);
|
|
120
|
-
}, [
|
|
121
|
-
open,
|
|
122
|
-
currentName,
|
|
123
|
-
currentDescription,
|
|
124
|
-
currentAgentType,
|
|
125
|
-
currentWorkDir,
|
|
126
|
-
currentPermissionMode,
|
|
127
|
-
currentLanguage,
|
|
128
|
-
currentShowContextIndicator,
|
|
129
|
-
currentReplyFooter,
|
|
130
|
-
currentInjectSender,
|
|
131
|
-
currentManagedSources,
|
|
132
|
-
currentDisabledCommands,
|
|
133
|
-
currentPlatformAllowFrom,
|
|
134
|
-
currentProviderRefs,
|
|
135
|
-
]);
|
|
136
|
-
|
|
137
|
-
const clearError = (): void => setError(null);
|
|
138
|
-
|
|
139
|
-
const handleSave = (): void => {
|
|
140
|
-
if (!name.trim()) {
|
|
141
|
-
setError('团队名称不能为空');
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (isTeamProvisioning) {
|
|
145
|
-
setError('团队仍在启动准备中,暂时不能编辑设置。请等待启动完成后再试。');
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const disabledCommands = disabledCommandsInput
|
|
150
|
-
.split(',')
|
|
151
|
-
.map((entry) => entry.trim())
|
|
152
|
-
.filter((entry) => entry.length > 0);
|
|
153
|
-
const feishu = feishuAllowFrom.trim();
|
|
154
|
-
|
|
155
|
-
setSaving(true);
|
|
156
|
-
setError(null);
|
|
157
|
-
void (async () => {
|
|
158
|
-
try {
|
|
159
|
-
await api.teams.updateConfig(teamName, {
|
|
160
|
-
name: name.trim(),
|
|
161
|
-
description: description.trim(),
|
|
162
|
-
color: currentColor,
|
|
163
|
-
agentType: agentType.trim() || undefined,
|
|
164
|
-
workDir: teamWorkDir.trim() || undefined,
|
|
165
|
-
permissionMode: permissionMode.trim() || undefined,
|
|
166
|
-
showContextIndicator,
|
|
167
|
-
replyFooter,
|
|
168
|
-
injectSender,
|
|
169
|
-
language: language.trim() || undefined,
|
|
170
|
-
managedSources: managedSources.trim() || undefined,
|
|
171
|
-
disabledCommands,
|
|
172
|
-
platformAllowFrom: feishu ? { feishu } : {},
|
|
173
|
-
providerRefs: providerRefs ? [providerRefs] : [],
|
|
174
|
-
});
|
|
175
|
-
await Promise.resolve(onSaved());
|
|
176
|
-
onClose();
|
|
177
|
-
} catch (err) {
|
|
178
|
-
setError(err instanceof Error ? err.message : '保存失败');
|
|
179
|
-
} finally {
|
|
180
|
-
setSaving(false);
|
|
181
|
-
}
|
|
182
|
-
})();
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const compatibleProviders = globalProviders.filter(
|
|
186
|
-
(provider) =>
|
|
187
|
-
!provider.agent_types ||
|
|
188
|
-
provider.agent_types.length === 0 ||
|
|
189
|
-
(provider.agent_types as string[]).includes(agentType)
|
|
190
|
-
);
|
|
70
|
+
// No auto-close — user closes manually after seeing "保存成功"
|
|
191
71
|
|
|
192
72
|
const toggleProviderRef = (providerName: string): void => {
|
|
193
|
-
clearError();
|
|
194
|
-
|
|
73
|
+
form.clearError();
|
|
74
|
+
const next = form.providerRef === providerName ? '' : providerName;
|
|
75
|
+
form.setProviderRef(next);
|
|
195
76
|
};
|
|
196
77
|
|
|
78
|
+
const saveLabel =
|
|
79
|
+
form.savePhase === 'done'
|
|
80
|
+
? '保存成功'
|
|
81
|
+
: form.savePhase === 'restarting'
|
|
82
|
+
? '重启服务中...'
|
|
83
|
+
: form.savePhase === 'saving'
|
|
84
|
+
? '保存中...'
|
|
85
|
+
: '保存并重启';
|
|
86
|
+
|
|
197
87
|
return (
|
|
198
|
-
<Dialog
|
|
88
|
+
<Dialog
|
|
89
|
+
open={open}
|
|
90
|
+
onOpenChange={(nextOpen) => {
|
|
91
|
+
if (form.saving) return;
|
|
92
|
+
if (!nextOpen) onClose();
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
199
95
|
<DialogContent className="max-w-2xl">
|
|
200
96
|
<DialogHeader>
|
|
201
97
|
<DialogTitle>编辑团队</DialogTitle>
|
|
202
|
-
<DialogDescription
|
|
98
|
+
<DialogDescription>修改团队名称、描述和运行参数</DialogDescription>
|
|
203
99
|
</DialogHeader>
|
|
204
100
|
|
|
205
|
-
<div className="space-y-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
htmlFor="edit-team-name"
|
|
209
|
-
className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]"
|
|
210
|
-
>
|
|
211
|
-
名称
|
|
212
|
-
</label>
|
|
213
|
-
<input
|
|
214
|
-
id="edit-team-name"
|
|
215
|
-
type="text"
|
|
216
|
-
value={name}
|
|
217
|
-
onChange={(event) => {
|
|
218
|
-
clearError();
|
|
219
|
-
setName(event.target.value);
|
|
220
|
-
}}
|
|
221
|
-
onKeyDown={(event) => {
|
|
222
|
-
if (event.key === 'Enter' && !saving && name.trim()) handleSave();
|
|
223
|
-
}}
|
|
224
|
-
className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
|
|
225
|
-
placeholder="团队名称"
|
|
226
|
-
/>
|
|
227
|
-
</div>
|
|
228
|
-
|
|
229
|
-
<div>
|
|
230
|
-
<label
|
|
231
|
-
htmlFor="edit-team-description"
|
|
232
|
-
className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]"
|
|
233
|
-
>
|
|
234
|
-
描述
|
|
235
|
-
</label>
|
|
236
|
-
<textarea
|
|
237
|
-
id="edit-team-description"
|
|
238
|
-
value={description}
|
|
239
|
-
onChange={(event) => {
|
|
240
|
-
clearError();
|
|
241
|
-
setDescription(event.target.value);
|
|
242
|
-
}}
|
|
243
|
-
rows={3}
|
|
244
|
-
className="w-full resize-none rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-1.5 text-sm text-[var(--color-text)] outline-none focus:border-[var(--color-border-emphasis)]"
|
|
245
|
-
placeholder="团队描述(可选)"
|
|
246
|
-
/>
|
|
247
|
-
</div>
|
|
248
|
-
|
|
249
|
-
<div className="space-y-3 rounded-md border border-[var(--color-border)] p-3">
|
|
101
|
+
<div className="space-y-4">
|
|
102
|
+
{/* ── Section 1: 基本信息 ─────────────────────────── */}
|
|
103
|
+
<FormSection title="基本信息">
|
|
250
104
|
<div>
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
105
|
+
<label htmlFor="edit-team-name" className={labelCls}>
|
|
106
|
+
名称
|
|
107
|
+
</label>
|
|
108
|
+
<input
|
|
109
|
+
id="edit-team-name"
|
|
110
|
+
type="text"
|
|
111
|
+
value={form.name}
|
|
112
|
+
onChange={(e) => {
|
|
113
|
+
form.clearError();
|
|
114
|
+
form.setName(e.target.value);
|
|
115
|
+
}}
|
|
116
|
+
onKeyDown={(e) => {
|
|
117
|
+
if (e.key === 'Enter' && !form.saving && form.name.trim()) form.handleSave();
|
|
118
|
+
}}
|
|
119
|
+
className={inputCls}
|
|
120
|
+
placeholder="团队名称"
|
|
121
|
+
/>
|
|
256
122
|
</div>
|
|
123
|
+
<div>
|
|
124
|
+
<label htmlFor="edit-team-description" className={labelCls}>
|
|
125
|
+
描述
|
|
126
|
+
</label>
|
|
127
|
+
<textarea
|
|
128
|
+
id="edit-team-description"
|
|
129
|
+
value={form.description}
|
|
130
|
+
onChange={(e) => {
|
|
131
|
+
form.clearError();
|
|
132
|
+
form.setDescription(e.target.value);
|
|
133
|
+
}}
|
|
134
|
+
rows={2}
|
|
135
|
+
className={`${inputCls} resize-none`}
|
|
136
|
+
placeholder="团队描述(可选)"
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
</FormSection>
|
|
257
140
|
|
|
141
|
+
{/* ── Section 2: Agent 配置 ───────────────────────── */}
|
|
142
|
+
<FormSection
|
|
143
|
+
title="Agent 配置"
|
|
144
|
+
description="运行参数配置。运行时模型和 Provider 请到 Harness 配置中管理。"
|
|
145
|
+
>
|
|
258
146
|
<div className="grid gap-3 md:grid-cols-2">
|
|
259
147
|
<div>
|
|
260
|
-
<label className=
|
|
261
|
-
Agent 类型
|
|
262
|
-
</label>
|
|
148
|
+
<label className={labelCls}>Agent 类型</label>
|
|
263
149
|
<HarnessSelect
|
|
264
|
-
value={agentType as CcAgentType}
|
|
150
|
+
value={form.agentType as CcAgentType}
|
|
265
151
|
onChange={(v) => {
|
|
266
|
-
clearError();
|
|
267
|
-
setAgentType(v);
|
|
152
|
+
form.clearError();
|
|
153
|
+
form.setAgentType(v);
|
|
268
154
|
}}
|
|
269
155
|
className="w-full"
|
|
270
156
|
/>
|
|
271
157
|
</div>
|
|
272
|
-
|
|
273
158
|
<div>
|
|
274
|
-
<label className=
|
|
275
|
-
权限模式
|
|
276
|
-
</label>
|
|
159
|
+
<label className={labelCls}>权限模式</label>
|
|
277
160
|
<select
|
|
278
|
-
value={permissionMode}
|
|
279
|
-
onChange={(
|
|
280
|
-
clearError();
|
|
281
|
-
setPermissionMode(
|
|
161
|
+
value={form.permissionMode}
|
|
162
|
+
onChange={(e) => {
|
|
163
|
+
form.clearError();
|
|
164
|
+
form.setPermissionMode(e.target.value);
|
|
282
165
|
}}
|
|
283
|
-
className=
|
|
166
|
+
className={inputCls}
|
|
284
167
|
>
|
|
285
|
-
{PERMISSION_MODE_OPTIONS.map((
|
|
286
|
-
<option key={
|
|
287
|
-
{
|
|
168
|
+
{PERMISSION_MODE_OPTIONS.map((opt) => (
|
|
169
|
+
<option key={opt.value} value={opt.value}>
|
|
170
|
+
{opt.label}
|
|
288
171
|
</option>
|
|
289
172
|
))}
|
|
290
173
|
</select>
|
|
291
174
|
</div>
|
|
292
175
|
</div>
|
|
293
|
-
|
|
294
176
|
<div>
|
|
295
|
-
<label className=
|
|
296
|
-
工作目录
|
|
297
|
-
</label>
|
|
177
|
+
<label className={labelCls}>工作目录</label>
|
|
298
178
|
<input
|
|
299
179
|
type="text"
|
|
300
|
-
value={
|
|
301
|
-
onChange={(
|
|
302
|
-
clearError();
|
|
303
|
-
|
|
180
|
+
value={form.workDir}
|
|
181
|
+
onChange={(e) => {
|
|
182
|
+
form.clearError();
|
|
183
|
+
form.setWorkDir(e.target.value);
|
|
304
184
|
}}
|
|
305
|
-
className=
|
|
185
|
+
className={`${inputCls} font-mono`}
|
|
306
186
|
placeholder="/Users/you/code/project"
|
|
307
187
|
/>
|
|
308
188
|
</div>
|
|
189
|
+
</FormSection>
|
|
309
190
|
|
|
191
|
+
{/* ── Section 3: 通信与平台 ───────────────────────── */}
|
|
192
|
+
<FormSection title="通信与平台">
|
|
310
193
|
<div className="grid gap-3 md:grid-cols-2">
|
|
311
194
|
<div>
|
|
312
|
-
<label className=
|
|
313
|
-
语言
|
|
314
|
-
</label>
|
|
195
|
+
<label className={labelCls}>语言</label>
|
|
315
196
|
<input
|
|
316
197
|
type="text"
|
|
317
|
-
value={language}
|
|
318
|
-
onChange={(
|
|
319
|
-
clearError();
|
|
320
|
-
setLanguage(
|
|
198
|
+
value={form.language}
|
|
199
|
+
onChange={(e) => {
|
|
200
|
+
form.clearError();
|
|
201
|
+
form.setLanguage(e.target.value);
|
|
321
202
|
}}
|
|
322
|
-
className=
|
|
203
|
+
className={inputCls}
|
|
323
204
|
placeholder="zh"
|
|
324
205
|
/>
|
|
325
206
|
</div>
|
|
326
|
-
|
|
327
207
|
<div>
|
|
328
|
-
<label className=
|
|
329
|
-
管理来源
|
|
330
|
-
</label>
|
|
208
|
+
<label className={labelCls}>管理来源</label>
|
|
331
209
|
<input
|
|
332
210
|
type="text"
|
|
333
|
-
value={managedSources}
|
|
334
|
-
onChange={(
|
|
335
|
-
clearError();
|
|
336
|
-
setManagedSources(
|
|
211
|
+
value={form.managedSources}
|
|
212
|
+
onChange={(e) => {
|
|
213
|
+
form.clearError();
|
|
214
|
+
form.setManagedSources(e.target.value);
|
|
337
215
|
}}
|
|
338
|
-
className=
|
|
216
|
+
className={inputCls}
|
|
339
217
|
placeholder="user1,user2 或 *"
|
|
340
218
|
/>
|
|
341
219
|
</div>
|
|
342
220
|
</div>
|
|
343
|
-
|
|
344
221
|
<div>
|
|
345
|
-
<label className=
|
|
346
|
-
已禁用命令
|
|
347
|
-
</label>
|
|
222
|
+
<label className={labelCls}>平台访问控制(Feishu 允许的用户)</label>
|
|
348
223
|
<input
|
|
349
224
|
type="text"
|
|
350
|
-
value={
|
|
351
|
-
onChange={(
|
|
352
|
-
clearError();
|
|
353
|
-
|
|
225
|
+
value={form.feishuAllowFrom}
|
|
226
|
+
onChange={(e) => {
|
|
227
|
+
form.clearError();
|
|
228
|
+
form.setFeishuAllowFrom(e.target.value);
|
|
354
229
|
}}
|
|
355
|
-
className=
|
|
356
|
-
placeholder="
|
|
230
|
+
className={inputCls}
|
|
231
|
+
placeholder="*"
|
|
357
232
|
/>
|
|
358
233
|
</div>
|
|
234
|
+
</FormSection>
|
|
359
235
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
236
|
+
{/* ── Section 4: 高级开关 ─────────────────────────── */}
|
|
237
|
+
<div className="grid gap-2 md:grid-cols-3">
|
|
238
|
+
<label
|
|
239
|
+
htmlFor="edit-team-show-context-indicator"
|
|
240
|
+
className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
|
|
241
|
+
>
|
|
242
|
+
<Checkbox
|
|
243
|
+
id="edit-team-show-context-indicator"
|
|
244
|
+
checked={form.showContextIndicator}
|
|
245
|
+
onCheckedChange={(checked) => {
|
|
246
|
+
form.clearError();
|
|
247
|
+
form.setShowContextIndicator(checked === true);
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
上下文指示
|
|
251
|
+
</label>
|
|
252
|
+
<label
|
|
253
|
+
htmlFor="edit-team-reply-footer"
|
|
254
|
+
className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
|
|
255
|
+
>
|
|
256
|
+
<Checkbox
|
|
257
|
+
id="edit-team-reply-footer"
|
|
258
|
+
checked={form.replyFooter}
|
|
259
|
+
onCheckedChange={(checked) => {
|
|
260
|
+
form.clearError();
|
|
261
|
+
form.setReplyFooter(checked === true);
|
|
262
|
+
}}
|
|
263
|
+
/>
|
|
264
|
+
回复尾部信息
|
|
265
|
+
</label>
|
|
266
|
+
<label
|
|
267
|
+
htmlFor="edit-team-inject-sender"
|
|
268
|
+
className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
|
|
269
|
+
>
|
|
270
|
+
<Checkbox
|
|
271
|
+
id="edit-team-inject-sender"
|
|
272
|
+
checked={form.injectSender}
|
|
273
|
+
onCheckedChange={(checked) => {
|
|
274
|
+
form.clearError();
|
|
275
|
+
form.setInjectSender(checked === true);
|
|
276
|
+
}}
|
|
277
|
+
/>
|
|
278
|
+
注入发送者
|
|
279
|
+
</label>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
{/* ── Section 5: Provider 绑定 ────────────────────── */}
|
|
283
|
+
<div className="rounded-lg border border-[var(--color-border-subtle)] bg-white/[0.02] p-3">
|
|
284
|
+
<div className="flex items-start justify-between gap-3">
|
|
285
|
+
<div>
|
|
286
|
+
<p className="text-xs font-medium text-[var(--color-text)]">Provider(可选)</p>
|
|
287
|
+
<p className="mt-1 text-[11px] leading-relaxed text-[var(--color-text-muted)]">
|
|
288
|
+
留空时使用本机{' '}
|
|
289
|
+
{AGENT_TYPE_LABELS[form.agentType as CcAgentType] ?? form.agentType}{' '}
|
|
290
|
+
默认配置和登录状态。只有需要给该团队指定模型供应商时,才绑定下面的全局 Provider。
|
|
291
|
+
</p>
|
|
379
292
|
</div>
|
|
293
|
+
{form.providerRef ? (
|
|
294
|
+
<button
|
|
295
|
+
type="button"
|
|
296
|
+
className="shrink-0 rounded-md border border-[var(--color-border)] px-2 py-1 text-[11px] text-[var(--color-text-muted)] hover:bg-white/5"
|
|
297
|
+
onClick={() => form.setProviderRef('')}
|
|
298
|
+
>
|
|
299
|
+
使用本机默认
|
|
300
|
+
</button>
|
|
301
|
+
) : null}
|
|
302
|
+
</div>
|
|
380
303
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
</div>
|
|
412
|
-
<span
|
|
413
|
-
className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] ${
|
|
414
|
-
checked
|
|
415
|
-
? 'bg-indigo-400/20 text-indigo-200'
|
|
416
|
-
: 'bg-white/5 text-[var(--color-text-muted)]'
|
|
417
|
-
}`}
|
|
418
|
-
>
|
|
419
|
-
{checked ? '已绑定' : '可绑定'}
|
|
420
|
-
</span>
|
|
304
|
+
<div className="mt-3 space-y-2">
|
|
305
|
+
{form.compatibleProviders.length > 0 ? (
|
|
306
|
+
form.compatibleProviders.map((provider) => {
|
|
307
|
+
const checked = form.providerRef === provider.name;
|
|
308
|
+
const at = form.agentType as CcAgentType;
|
|
309
|
+
const endpoint = provider.endpoints?.[at] ?? provider.base_url ?? '默认端点';
|
|
310
|
+
const model =
|
|
311
|
+
provider.agent_models?.[at] ??
|
|
312
|
+
provider.model ??
|
|
313
|
+
provider.models?.[0]?.model ??
|
|
314
|
+
'未指定模型';
|
|
315
|
+
return (
|
|
316
|
+
<button
|
|
317
|
+
key={provider.name}
|
|
318
|
+
type="button"
|
|
319
|
+
onClick={() => toggleProviderRef(provider.name)}
|
|
320
|
+
className={`w-full rounded-lg border px-3 py-2 text-left transition-colors ${
|
|
321
|
+
checked
|
|
322
|
+
? 'border-indigo-400/60 bg-indigo-500/10'
|
|
323
|
+
: 'border-[var(--color-border-subtle)] bg-black/10 hover:border-[var(--color-border)] hover:bg-white/[0.04]'
|
|
324
|
+
}`}
|
|
325
|
+
>
|
|
326
|
+
<div className="flex items-center justify-between gap-3">
|
|
327
|
+
<div className="min-w-0">
|
|
328
|
+
<p className="truncate text-xs font-medium text-[var(--color-text)]">
|
|
329
|
+
{provider.name}
|
|
330
|
+
</p>
|
|
331
|
+
<p className="mt-0.5 truncate text-[11px] text-[var(--color-text-muted)]">
|
|
332
|
+
{model} · {endpoint}
|
|
333
|
+
</p>
|
|
421
334
|
</div>
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
335
|
+
<span
|
|
336
|
+
className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] ${
|
|
337
|
+
checked
|
|
338
|
+
? 'bg-indigo-400/20 text-indigo-200'
|
|
339
|
+
: 'bg-white/5 text-[var(--color-text-muted)]'
|
|
340
|
+
}`}
|
|
341
|
+
>
|
|
342
|
+
{checked ? '已绑定' : '可绑定'}
|
|
343
|
+
</span>
|
|
344
|
+
</div>
|
|
345
|
+
</button>
|
|
346
|
+
);
|
|
347
|
+
})
|
|
348
|
+
) : (
|
|
349
|
+
<div className="rounded-md border border-dashed border-[var(--color-border)] px-3 py-3 text-xs text-[var(--color-text-muted)]">
|
|
350
|
+
暂无适用于 {AGENT_TYPE_LABELS[form.agentType as CcAgentType] ?? form.agentType}{' '}
|
|
351
|
+
的全局 Provider。可先在「设置 → Harness
|
|
352
|
+
配置」中添加;不添加也会使用本机默认登录态。
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
432
355
|
</div>
|
|
356
|
+
</div>
|
|
433
357
|
|
|
358
|
+
{/* ── Section 6: 危险操作 ─────────────────────────── */}
|
|
359
|
+
<FormSection title="危险操作" variant="danger">
|
|
434
360
|
<div>
|
|
435
|
-
<label className=
|
|
436
|
-
平台访问控制(Feishu 允许的用户)
|
|
437
|
-
</label>
|
|
361
|
+
<label className={labelCls}>已禁用命令</label>
|
|
438
362
|
<input
|
|
439
363
|
type="text"
|
|
440
|
-
value={
|
|
441
|
-
onChange={(
|
|
442
|
-
clearError();
|
|
443
|
-
|
|
364
|
+
value={form.disabledCommandsInput}
|
|
365
|
+
onChange={(e) => {
|
|
366
|
+
form.clearError();
|
|
367
|
+
form.setDisabledCommandsInput(e.target.value);
|
|
444
368
|
}}
|
|
445
|
-
className=
|
|
446
|
-
placeholder="
|
|
369
|
+
className={inputCls}
|
|
370
|
+
placeholder="restart, upgrade, cron"
|
|
447
371
|
/>
|
|
448
372
|
</div>
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
htmlFor="edit-team-show-context-indicator"
|
|
453
|
-
className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
|
|
454
|
-
>
|
|
455
|
-
<Checkbox
|
|
456
|
-
id="edit-team-show-context-indicator"
|
|
457
|
-
checked={showContextIndicator}
|
|
458
|
-
onCheckedChange={(checked) => {
|
|
459
|
-
clearError();
|
|
460
|
-
setShowContextIndicator(checked === true);
|
|
461
|
-
}}
|
|
462
|
-
/>
|
|
463
|
-
上下文指示
|
|
464
|
-
</label>
|
|
465
|
-
<label
|
|
466
|
-
htmlFor="edit-team-reply-footer"
|
|
467
|
-
className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
|
|
468
|
-
>
|
|
469
|
-
<Checkbox
|
|
470
|
-
id="edit-team-reply-footer"
|
|
471
|
-
checked={replyFooter}
|
|
472
|
-
onCheckedChange={(checked) => {
|
|
473
|
-
clearError();
|
|
474
|
-
setReplyFooter(checked === true);
|
|
475
|
-
}}
|
|
476
|
-
/>
|
|
477
|
-
回复尾部信息
|
|
478
|
-
</label>
|
|
479
|
-
<label
|
|
480
|
-
htmlFor="edit-team-inject-sender"
|
|
481
|
-
className="flex cursor-pointer items-center gap-2 rounded-md border border-[var(--color-border)] px-2 py-1.5 text-xs text-[var(--color-text-secondary)]"
|
|
482
|
-
>
|
|
483
|
-
<Checkbox
|
|
484
|
-
id="edit-team-inject-sender"
|
|
485
|
-
checked={injectSender}
|
|
486
|
-
onCheckedChange={(checked) => {
|
|
487
|
-
clearError();
|
|
488
|
-
setInjectSender(checked === true);
|
|
489
|
-
}}
|
|
490
|
-
/>
|
|
491
|
-
注入发送者
|
|
492
|
-
</label>
|
|
493
|
-
</div>
|
|
494
|
-
|
|
495
|
-
{onDeleteTeam && (
|
|
496
|
-
<div className="rounded-md border border-red-500/40 bg-red-500/5 p-3">
|
|
497
|
-
<p className="text-xs font-medium text-red-300">危险操作</p>
|
|
498
|
-
<p className="mt-1 text-xs text-[var(--color-text-muted)]">
|
|
373
|
+
{onDeleteTeam && form.canDelete && (
|
|
374
|
+
<>
|
|
375
|
+
<p className="text-xs text-[var(--color-text-muted)]">
|
|
499
376
|
删除项目会将团队从当前控制面板移除。
|
|
500
377
|
</p>
|
|
501
378
|
<Button
|
|
502
379
|
type="button"
|
|
503
380
|
variant="destructive"
|
|
504
381
|
size="sm"
|
|
505
|
-
className="mt-2"
|
|
506
382
|
onClick={() => {
|
|
507
383
|
onClose();
|
|
508
384
|
window.setTimeout(onDeleteTeam, 0);
|
|
@@ -511,27 +387,25 @@ export const EditTeamDialog = ({
|
|
|
511
387
|
<Trash2 size={14} className="mr-1.5" />
|
|
512
388
|
删除项目
|
|
513
389
|
</Button>
|
|
514
|
-
|
|
390
|
+
</>
|
|
515
391
|
)}
|
|
516
|
-
</
|
|
392
|
+
</FormSection>
|
|
517
393
|
|
|
518
|
-
{
|
|
519
|
-
|
|
520
|
-
) : null}
|
|
521
|
-
{error ? <p className="text-xs text-red-400">{error}</p> : null}
|
|
394
|
+
{/* ── Status messages ──────────────────────────────── */}
|
|
395
|
+
{form.error && <p className="text-xs text-red-400">{form.error}</p>}
|
|
522
396
|
</div>
|
|
523
397
|
|
|
524
398
|
<DialogFooter>
|
|
525
|
-
<Button variant="outline" size="sm" onClick={onClose} disabled={saving}>
|
|
526
|
-
取消
|
|
399
|
+
<Button variant="outline" size="sm" onClick={onClose} disabled={form.saving}>
|
|
400
|
+
{form.savePhase === 'done' ? '关闭' : '取消'}
|
|
527
401
|
</Button>
|
|
528
402
|
<Button
|
|
529
403
|
size="sm"
|
|
530
|
-
onClick={handleSave}
|
|
531
|
-
disabled={saving ||
|
|
404
|
+
onClick={form.handleSave}
|
|
405
|
+
disabled={form.saving || form.savePhase === 'done' || !form.name.trim()}
|
|
532
406
|
>
|
|
533
|
-
{saving && <Loader2 size={14} className="mr-1.5 animate-spin" />}
|
|
534
|
-
|
|
407
|
+
{form.saving && <Loader2 size={14} className="mr-1.5 animate-spin" />}
|
|
408
|
+
{saveLabel}
|
|
535
409
|
</Button>
|
|
536
410
|
</DialogFooter>
|
|
537
411
|
</DialogContent>
|