@yancyyu/openhermit 1.6.4 → 1.6.5
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/README.md +52 -140
- package/bin/hermit.mjs +39 -258
- package/dist-renderer/assets/{ProjectEditorOverlay-BcjkdR8y.js → ProjectEditorOverlay-14yC9eQy.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-B9PP0b_t.js → TeamGraphOverlay-RAoJDOnS.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-CPquAmj5.js → _basePickBy-BhDOA0cG.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-A66EsJn2.js → _baseUniq-DjjY0tMN.js} +1 -1
- package/dist-renderer/assets/{arc-YLxbV3Qw.js → arc-CzoaaE90.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-wwpiLSwy.js → architectureDiagram-VXUJARFQ-D7ZTVCML.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-3CHE3NYR.js → blockDiagram-VD42YOAC-DDVOvV1H.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-K8hDNmEC.js → c4Diagram-YG6GDRKO-CMswQy_R.js} +1 -1
- package/dist-renderer/assets/channel-DjoT-21b.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-5OabZrhH.js → chunk-4BX2VUAB-aYfdMo75.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-v2kdM_aT.js → chunk-55IACEB6-DUhZJ0mV.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-C0Ju56SH.js → chunk-B4BG7PRW-BrGjG-E6.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-DPTWTKRm.js → chunk-DI55MBZ5-CfPUMKlq.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DSkYppkv.js → chunk-FMBD7UC4-BMr0Vrdu.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-C_4cCLCl.js → chunk-QN33PNHL-C9gTfFZV.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-ojL7PmOD.js → chunk-QZHKN3VN-TTPdfwHP.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-D1g7Vl_v.js → chunk-TZMSLE5B-DPh3DBqf.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-C5mL3TLG.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-C5mL3TLG.js +1 -0
- package/dist-renderer/assets/clone-cS8bapaK.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-TJnGh924.js → cose-bilkent-S5V4N54A-BFbgRWOS.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-cPgfHhoX.js → dagre-6UL2VRFP-CfXdU7Il.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BS5Y-RR6.js → diagram-PSM6KHXK-MdOyrxZl.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-D9AF7AGJ.js → diagram-QEK2KX5R-DpmnBR-A.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DTFUadMS.js → diagram-S2PKOQOG-JXgp2H5I.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-DB_StEwC.js → erDiagram-Q2GNP2WA-CRfYO8W3.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-DGn40aPj.js → flowDiagram-NV44I4VS-BJvmgply.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-9NiFCSBT.js → ganttDiagram-JELNMOA3-BLXnpZat.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-BdveeU3c.js → gitGraphDiagram-V2S2FVAM-BKtbxazQ.js} +1 -1
- package/dist-renderer/assets/{graph-aQYbgTDH.js → graph-D6n4zNVe.js} +1 -1
- package/dist-renderer/assets/{index-DmgKTZAa.js → index-3JdA9Dab.js} +529 -524
- package/dist-renderer/assets/{index-CaG9mf8s.css → index-C4x095x4.css} +1 -1
- package/dist-renderer/assets/{index-CWqPn0NY.js → index-CVdwMXdQ.js} +1 -1
- package/dist-renderer/assets/{index-DyEKO6GV.js → index-CkO1A9ft.js} +1 -1
- package/dist-renderer/assets/{index-oyepEosi.js → index-ar0tAtBS.js} +1 -1
- package/dist-renderer/assets/{index-CrCHolXN.js → index-c2GABSvo.js} +1 -1
- package/dist-renderer/assets/{index-DiAK42nd.js → index-trDFOqz-.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-Dmc_xn8U.js → infoDiagram-HS3SLOUP-Bqq_toop.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-D9LJr-B5.js → journeyDiagram-XKPGCS4Q-BRQs07r0.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CjOWoNys.js → kanban-definition-3W4ZIXB7-DHQnAijJ.js} +1 -1
- package/dist-renderer/assets/{layout-D6GzYK4K.js → layout-BljiazG5.js} +1 -1
- package/dist-renderer/assets/{linear-Dt3GyUQf.js → linear-fx8cDfux.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-XwY2hZr8.js → mindmap-definition-VGOIOE7T-DCfQbCFK.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BU4nfYd7.js → pieDiagram-ADFJNKIX-DyAFYy6H.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-BYk6f63x.js → quadrantDiagram-AYHSOK5B-CCvqn9gd.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-kbadr_bU.js → requirementDiagram-UZGBJVZJ-JYde-Xl2.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-ZstP2Vth.js → sankeyDiagram-TZEHDZUN-C2Im6-aG.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-obK_-ssz.js → sequenceDiagram-WL72ISMW-X6JGIoEB.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BgZDg0VT.js → stateDiagram-FKZM4ZOC-BJTDs8MY.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CMa5sz7x.js → stateDiagram-v2-4FDKWEC3-DUrYslPS.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-BOmCNnab.js → timeline-definition-IT6M3QCI-C7ECznev.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-BU0ha0Ww.js → treemap-GDKQZRPO-BRg3Zpk4.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-BzAHNASi.js → xychartDiagram-PRI3JC2R-CoZGyc2f.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +23 -17
- package/src/main/server.ts +179 -23
- package/src/main/services/teams-mvp/TaskDispatchService.ts +440 -0
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +36 -33
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +2 -0
- package/src/renderer/components/settings/SettingsTabs.tsx +8 -2
- package/src/renderer/components/settings/SettingsView.tsx +4 -0
- package/src/renderer/components/settings/sections/GeneralSection.tsx +168 -206
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +176 -0
- package/src/renderer/components/sidebar/SidebarSessions.tsx +31 -4
- package/src/renderer/components/team/kanban/KanbanTaskCard.tsx +37 -0
- package/src/renderer/components/team/messages/MessageComposer.tsx +36 -228
- package/src/renderer/components/team/messages/MessagesPanel.tsx +0 -3
- package/src/renderer/store/slices/teamSlice.ts +30 -1
- package/src/shared/types/team.ts +73 -0
- package/dist-renderer/assets/channel-BSWYOYIc.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-mw4yABob.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-mw4yABob.js +0 -1
- package/dist-renderer/assets/clone-KtZfFt-o.js +0 -1
|
@@ -15,27 +15,31 @@ import { Check, Copy, FolderOpen, Laptop, Loader2, RotateCcw } from 'lucide-reac
|
|
|
15
15
|
import { useShallow } from 'zustand/react/shallow';
|
|
16
16
|
|
|
17
17
|
import { Button } from '@renderer/components/ui/button';
|
|
18
|
-
import { SettingRow, SettingsSectionHeader, SettingsToggle } from '../components';
|
|
18
|
+
import { SettingRow, SettingsSectionHeader, SettingsSelect, SettingsToggle } from '../components';
|
|
19
19
|
|
|
20
20
|
import type { SafeConfig } from '../hooks/useSettingsConfig';
|
|
21
21
|
import type { ClaudeRootInfo, WslClaudeRootCandidate } from '@shared/types';
|
|
22
22
|
import type { HttpServerStatus } from '@shared/types/api';
|
|
23
23
|
import type { AppConfig } from '@shared/types/notifications';
|
|
24
24
|
|
|
25
|
-
// Theme options
|
|
26
25
|
const THEME_OPTIONS = [
|
|
27
26
|
{ value: 'dark', label: '深色' },
|
|
28
27
|
{ value: 'light', label: '浅色' },
|
|
29
28
|
{ value: 'system', label: '跟随系统' },
|
|
30
29
|
] as const;
|
|
31
30
|
|
|
32
|
-
const
|
|
33
|
-
|
|
31
|
+
const CC_LOG_LEVEL_OPTIONS = [
|
|
32
|
+
{ value: 'debug', label: 'Debug' },
|
|
33
|
+
{ value: 'info', label: 'Info' },
|
|
34
|
+
{ value: 'warn', label: 'Warn' },
|
|
35
|
+
{ value: 'error', label: 'Error' },
|
|
36
|
+
];
|
|
37
|
+
|
|
34
38
|
const CC_ATTACHMENT_OPTIONS = [
|
|
35
39
|
{ value: '', label: '默认' },
|
|
36
40
|
{ value: 'on', label: '开启' },
|
|
37
41
|
{ value: 'off', label: '关闭' },
|
|
38
|
-
]
|
|
42
|
+
];
|
|
39
43
|
|
|
40
44
|
interface CcGlobalSettingsState {
|
|
41
45
|
language: string;
|
|
@@ -52,6 +56,38 @@ interface CcGlobalSettingsState {
|
|
|
52
56
|
rate_limit_window_secs: number;
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
/** Compact number input for SettingRow right side */
|
|
60
|
+
const CompactNum = ({
|
|
61
|
+
value,
|
|
62
|
+
onChange,
|
|
63
|
+
onSave,
|
|
64
|
+
min,
|
|
65
|
+
className,
|
|
66
|
+
}: {
|
|
67
|
+
value: number;
|
|
68
|
+
onChange: (v: number) => void;
|
|
69
|
+
onSave: () => void;
|
|
70
|
+
min?: number;
|
|
71
|
+
className?: string;
|
|
72
|
+
}): React.JSX.Element => (
|
|
73
|
+
<input
|
|
74
|
+
type="number"
|
|
75
|
+
min={min}
|
|
76
|
+
value={value}
|
|
77
|
+
onChange={(e) => onChange(Number(e.target.value) || 0)}
|
|
78
|
+
onBlur={onSave}
|
|
79
|
+
onKeyDown={(e) => {
|
|
80
|
+
if (e.key === 'Enter') onSave();
|
|
81
|
+
}}
|
|
82
|
+
className={cn(
|
|
83
|
+
'h-8 w-24 rounded-md border bg-transparent px-2 text-right text-xs tabular-nums',
|
|
84
|
+
'focus:outline-none focus:ring-1 focus:ring-zinc-700',
|
|
85
|
+
className
|
|
86
|
+
)}
|
|
87
|
+
style={{ borderColor: 'var(--color-border-subtle)', color: 'var(--color-text)' }}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
|
|
55
91
|
interface GeneralSectionProps {
|
|
56
92
|
readonly safeConfig: SafeConfig;
|
|
57
93
|
readonly saving: boolean;
|
|
@@ -75,7 +111,6 @@ export const GeneralSection = ({
|
|
|
75
111
|
const [serverError, setServerError] = useState<string | null>(null);
|
|
76
112
|
const [copied, setCopied] = useState(false);
|
|
77
113
|
|
|
78
|
-
// Claude Root state
|
|
79
114
|
const { connectionMode, fetchProjects, fetchRepositoryGroups } = useStore(
|
|
80
115
|
useShallow((s) => ({
|
|
81
116
|
connectionMode: s.connectionMode,
|
|
@@ -105,10 +140,7 @@ export const GeneralSection = ({
|
|
|
105
140
|
rate_limit_window_secs: 60,
|
|
106
141
|
});
|
|
107
142
|
const [ccSettingsLoading, setCcSettingsLoading] = useState(false);
|
|
108
|
-
const [ccSettingsSaving, setCcSettingsSaving] = useState(false);
|
|
109
|
-
const [ccSettingsMessage, setCcSettingsMessage] = useState<string | null>(null);
|
|
110
143
|
|
|
111
|
-
// Fetch server status and Claude root info on mount
|
|
112
144
|
useEffect(() => {
|
|
113
145
|
void api.httpServer
|
|
114
146
|
.getStatus()
|
|
@@ -136,8 +168,8 @@ export const GeneralSection = ({
|
|
|
136
168
|
try {
|
|
137
169
|
const settings = await api.ccSettings.get();
|
|
138
170
|
setCcSettings((prev) => ({ ...prev, ...(settings as Partial<CcGlobalSettingsState>) }));
|
|
139
|
-
} catch
|
|
140
|
-
|
|
171
|
+
} catch {
|
|
172
|
+
// best-effort
|
|
141
173
|
} finally {
|
|
142
174
|
setCcSettingsLoading(false);
|
|
143
175
|
}
|
|
@@ -151,19 +183,17 @@ export const GeneralSection = ({
|
|
|
151
183
|
setCcSettings((prev) => ({ ...prev, ...patch }));
|
|
152
184
|
}, []);
|
|
153
185
|
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
}, [ccSettings]);
|
|
186
|
+
const autoSaveCcSetting = useCallback(
|
|
187
|
+
async <K extends keyof CcGlobalSettingsState>(key: K, value: CcGlobalSettingsState[K]) => {
|
|
188
|
+
patchCcSettings({ [key]: value });
|
|
189
|
+
try {
|
|
190
|
+
await api.ccSettings.patch({ [key]: value } as unknown as Record<string, unknown>);
|
|
191
|
+
} catch {
|
|
192
|
+
// silent
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
[patchCcSettings]
|
|
196
|
+
);
|
|
167
197
|
|
|
168
198
|
const handleServerToggle = useCallback(async (enabled: boolean) => {
|
|
169
199
|
setServerLoading(true);
|
|
@@ -186,7 +216,6 @@ export const GeneralSection = ({
|
|
|
186
216
|
setTimeout(() => setCopied(false), 2000);
|
|
187
217
|
}, [serverUrl]);
|
|
188
218
|
|
|
189
|
-
// Claude Root handlers
|
|
190
219
|
const resetWorkspaceForRootChange = useCallback((): void => {
|
|
191
220
|
useStore.setState({
|
|
192
221
|
projects: [],
|
|
@@ -369,8 +398,20 @@ export const GeneralSection = ({
|
|
|
369
398
|
[]
|
|
370
399
|
);
|
|
371
400
|
|
|
401
|
+
const saveRateLimit = useCallback(async () => {
|
|
402
|
+
try {
|
|
403
|
+
await api.ccSettings.patch({
|
|
404
|
+
rate_limit_max_messages: ccSettings.rate_limit_max_messages,
|
|
405
|
+
rate_limit_window_secs: ccSettings.rate_limit_window_secs,
|
|
406
|
+
} as Record<string, unknown>);
|
|
407
|
+
} catch {
|
|
408
|
+
// silent
|
|
409
|
+
}
|
|
410
|
+
}, [ccSettings.rate_limit_max_messages, ccSettings.rate_limit_window_secs]);
|
|
411
|
+
|
|
372
412
|
return (
|
|
373
413
|
<div>
|
|
414
|
+
{/* Language */}
|
|
374
415
|
<SettingsSectionHeader title="Agent 语言" />
|
|
375
416
|
<SettingRow label="语言" description={agentLanguageDescription}>
|
|
376
417
|
<Combobox
|
|
@@ -386,6 +427,7 @@ export const GeneralSection = ({
|
|
|
386
427
|
/>
|
|
387
428
|
</SettingRow>
|
|
388
429
|
|
|
430
|
+
{/* Appearance */}
|
|
389
431
|
<SettingsSectionHeader title="外观" />
|
|
390
432
|
<SettingRow label="主题" description="选择你偏好的界面主题">
|
|
391
433
|
<div className="inline-flex rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] p-0.5">
|
|
@@ -417,6 +459,8 @@ export const GeneralSection = ({
|
|
|
417
459
|
disabled={saving}
|
|
418
460
|
/>
|
|
419
461
|
</SettingRow>
|
|
462
|
+
|
|
463
|
+
{/* Server Status */}
|
|
420
464
|
<SettingsSectionHeader title="服务状态" />
|
|
421
465
|
<div
|
|
422
466
|
className="mb-2 flex items-center gap-3 rounded-md px-3 py-2.5"
|
|
@@ -456,192 +500,110 @@ export const GeneralSection = ({
|
|
|
456
500
|
当前为 Web 控制台模式。服务由 Hermit 后端托管,不能在浏览器内启动或关闭。
|
|
457
501
|
</p>
|
|
458
502
|
|
|
503
|
+
{/* Runtime Settings */}
|
|
459
504
|
<SettingsSectionHeader title="运行设置" />
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
<
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
<
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
505
|
+
{ccSettingsLoading ? (
|
|
506
|
+
<div className="flex items-center gap-2 py-3 text-xs text-[var(--color-text-muted)]">
|
|
507
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
508
|
+
正在加载...
|
|
509
|
+
</div>
|
|
510
|
+
) : (
|
|
511
|
+
<>
|
|
512
|
+
<SettingRow label="附件回传" description="控制是否将对话中的附件文件回传给 Agent">
|
|
513
|
+
<SettingsSelect
|
|
514
|
+
value={ccSettings.attachment_send}
|
|
515
|
+
options={CC_ATTACHMENT_OPTIONS}
|
|
516
|
+
onChange={(v) => void autoSaveCcSetting('attachment_send', v)}
|
|
517
|
+
/>
|
|
518
|
+
</SettingRow>
|
|
519
|
+
<SettingRow label="空闲超时" description="Agent 空闲多久后自动断开(分钟)">
|
|
520
|
+
<CompactNum
|
|
521
|
+
value={ccSettings.idle_timeout_mins}
|
|
522
|
+
onChange={(v) => patchCcSettings({ idle_timeout_mins: v })}
|
|
523
|
+
onSave={() =>
|
|
524
|
+
void autoSaveCcSetting('idle_timeout_mins', ccSettings.idle_timeout_mins)
|
|
525
|
+
}
|
|
526
|
+
min={0}
|
|
527
|
+
/>
|
|
528
|
+
</SettingRow>
|
|
529
|
+
<SettingRow label="日志等级" description="cc-connect 日志输出级别">
|
|
530
|
+
<SettingsSelect
|
|
531
|
+
value={ccSettings.log_level}
|
|
532
|
+
options={CC_LOG_LEVEL_OPTIONS}
|
|
533
|
+
onChange={(v) => void autoSaveCcSetting('log_level', v)}
|
|
534
|
+
/>
|
|
535
|
+
</SettingRow>
|
|
536
|
+
<SettingRow label="显示 Thinking 消息" description="在对话中展示 Agent 的思考过程">
|
|
537
|
+
<SettingsToggle
|
|
538
|
+
enabled={ccSettings.thinking_messages}
|
|
539
|
+
onChange={(v) => void autoSaveCcSetting('thinking_messages', v)}
|
|
540
|
+
/>
|
|
541
|
+
</SettingRow>
|
|
542
|
+
<SettingRow label="显示工具进度" description="在对话中展示 Agent 调用工具的详细信息">
|
|
543
|
+
<SettingsToggle
|
|
544
|
+
enabled={ccSettings.tool_messages}
|
|
545
|
+
onChange={(v) => void autoSaveCcSetting('tool_messages', v)}
|
|
546
|
+
/>
|
|
547
|
+
</SettingRow>
|
|
548
|
+
<SettingRow label="启用流式预览" description="实时预览 Agent 的流式输出内容">
|
|
549
|
+
<SettingsToggle
|
|
550
|
+
enabled={ccSettings.stream_preview_enabled}
|
|
551
|
+
onChange={(v) => void autoSaveCcSetting('stream_preview_enabled', v)}
|
|
552
|
+
/>
|
|
553
|
+
</SettingRow>
|
|
554
|
+
<SettingRow label="Thinking 最大长度" description="截断展示的 Thinking 消息最大字符数">
|
|
555
|
+
<CompactNum
|
|
556
|
+
value={ccSettings.thinking_max_len}
|
|
557
|
+
onChange={(v) => patchCcSettings({ thinking_max_len: v })}
|
|
558
|
+
onSave={() => void autoSaveCcSetting('thinking_max_len', ccSettings.thinking_max_len)}
|
|
559
|
+
min={0}
|
|
560
|
+
/>
|
|
561
|
+
</SettingRow>
|
|
562
|
+
<SettingRow label="工具消息最大长度" description="截断展示的工具消息最大字符数">
|
|
563
|
+
<CompactNum
|
|
564
|
+
value={ccSettings.tool_max_len}
|
|
565
|
+
onChange={(v) => patchCcSettings({ tool_max_len: v })}
|
|
566
|
+
onSave={() => void autoSaveCcSetting('tool_max_len', ccSettings.tool_max_len)}
|
|
567
|
+
min={0}
|
|
568
|
+
/>
|
|
569
|
+
</SettingRow>
|
|
570
|
+
<SettingRow label="预览间隔" description="流式预览刷新间隔(毫秒)">
|
|
571
|
+
<CompactNum
|
|
572
|
+
value={ccSettings.stream_preview_interval_ms}
|
|
573
|
+
onChange={(v) => patchCcSettings({ stream_preview_interval_ms: v })}
|
|
574
|
+
onSave={() =>
|
|
575
|
+
void autoSaveCcSetting(
|
|
576
|
+
'stream_preview_interval_ms',
|
|
577
|
+
ccSettings.stream_preview_interval_ms
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
min={100}
|
|
581
|
+
/>
|
|
582
|
+
</SettingRow>
|
|
583
|
+
<SettingRow label="频率限制" description="限制时间窗口内发送的最大消息数">
|
|
584
|
+
<div className="flex items-center gap-1.5 text-xs text-[var(--color-text-muted)]">
|
|
585
|
+
<CompactNum
|
|
586
|
+
value={ccSettings.rate_limit_max_messages}
|
|
587
|
+
onChange={(v) => patchCcSettings({ rate_limit_max_messages: v })}
|
|
588
|
+
onSave={() => void saveRateLimit()}
|
|
589
|
+
min={0}
|
|
590
|
+
className="w-20"
|
|
542
591
|
/>
|
|
543
|
-
<span
|
|
544
|
-
<
|
|
545
|
-
|
|
546
|
-
onChange={(
|
|
592
|
+
<span>条 /</span>
|
|
593
|
+
<CompactNum
|
|
594
|
+
value={ccSettings.rate_limit_window_secs}
|
|
595
|
+
onChange={(v) => patchCcSettings({ rate_limit_window_secs: v })}
|
|
596
|
+
onSave={() => void saveRateLimit()}
|
|
597
|
+
min={1}
|
|
598
|
+
className="w-20"
|
|
547
599
|
/>
|
|
548
|
-
<span
|
|
549
|
-
</div>
|
|
550
|
-
|
|
551
|
-
<div className="grid gap-3 md:grid-cols-2">
|
|
552
|
-
<div>
|
|
553
|
-
<label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
|
|
554
|
-
Thinking 最大长度
|
|
555
|
-
</label>
|
|
556
|
-
<input
|
|
557
|
-
type="number"
|
|
558
|
-
min={0}
|
|
559
|
-
value={ccSettings.thinking_max_len}
|
|
560
|
-
onChange={(event) =>
|
|
561
|
-
patchCcSettings({ thinking_max_len: Number(event.target.value) || 0 })
|
|
562
|
-
}
|
|
563
|
-
className="h-8 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
|
|
564
|
-
/>
|
|
565
|
-
</div>
|
|
566
|
-
<div>
|
|
567
|
-
<label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
|
|
568
|
-
工具消息最大长度
|
|
569
|
-
</label>
|
|
570
|
-
<input
|
|
571
|
-
type="number"
|
|
572
|
-
min={0}
|
|
573
|
-
value={ccSettings.tool_max_len}
|
|
574
|
-
onChange={(event) =>
|
|
575
|
-
patchCcSettings({ tool_max_len: Number(event.target.value) || 0 })
|
|
576
|
-
}
|
|
577
|
-
className="h-8 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
|
|
578
|
-
/>
|
|
579
|
-
</div>
|
|
580
|
-
<div>
|
|
581
|
-
<label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
|
|
582
|
-
预览间隔(毫秒)
|
|
583
|
-
</label>
|
|
584
|
-
<input
|
|
585
|
-
type="number"
|
|
586
|
-
min={100}
|
|
587
|
-
value={ccSettings.stream_preview_interval_ms}
|
|
588
|
-
onChange={(event) =>
|
|
589
|
-
patchCcSettings({
|
|
590
|
-
stream_preview_interval_ms: Number(event.target.value) || 100,
|
|
591
|
-
})
|
|
592
|
-
}
|
|
593
|
-
className="h-8 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
|
|
594
|
-
/>
|
|
595
|
-
</div>
|
|
596
|
-
<div>
|
|
597
|
-
<label className="mb-1 block text-xs font-medium text-[var(--color-text-secondary)]">
|
|
598
|
-
频率限制(条/窗口)
|
|
599
|
-
</label>
|
|
600
|
-
<div className="grid grid-cols-2 gap-2">
|
|
601
|
-
<input
|
|
602
|
-
type="number"
|
|
603
|
-
min={0}
|
|
604
|
-
value={ccSettings.rate_limit_max_messages}
|
|
605
|
-
onChange={(event) =>
|
|
606
|
-
patchCcSettings({ rate_limit_max_messages: Number(event.target.value) || 0 })
|
|
607
|
-
}
|
|
608
|
-
className="h-8 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
|
|
609
|
-
placeholder="数量"
|
|
610
|
-
/>
|
|
611
|
-
<input
|
|
612
|
-
type="number"
|
|
613
|
-
min={1}
|
|
614
|
-
value={ccSettings.rate_limit_window_secs}
|
|
615
|
-
onChange={(event) =>
|
|
616
|
-
patchCcSettings({ rate_limit_window_secs: Number(event.target.value) || 1 })
|
|
617
|
-
}
|
|
618
|
-
className="h-8 rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
|
|
619
|
-
placeholder="秒"
|
|
620
|
-
/>
|
|
621
|
-
</div>
|
|
622
|
-
</div>
|
|
600
|
+
<span>秒</span>
|
|
623
601
|
</div>
|
|
602
|
+
</SettingRow>
|
|
603
|
+
</>
|
|
604
|
+
)}
|
|
624
605
|
|
|
625
|
-
|
|
626
|
-
<Button size="sm" onClick={() => void saveCcSettings()} disabled={ccSettingsSaving}>
|
|
627
|
-
{ccSettingsSaving ? <Loader2 className="mr-1.5 size-3.5 animate-spin" /> : null}
|
|
628
|
-
保存运行设置
|
|
629
|
-
</Button>
|
|
630
|
-
{ccSettingsMessage ? (
|
|
631
|
-
<span
|
|
632
|
-
className={`text-xs ${
|
|
633
|
-
ccSettingsMessage === '已保存' ? 'text-emerald-400' : 'text-red-400'
|
|
634
|
-
}`}
|
|
635
|
-
>
|
|
636
|
-
{ccSettingsMessage}
|
|
637
|
-
</span>
|
|
638
|
-
) : null}
|
|
639
|
-
</div>
|
|
640
|
-
</>
|
|
641
|
-
)}
|
|
642
|
-
</div>
|
|
643
|
-
|
|
644
|
-
{/* Privacy / Telemetry — only visible when Sentry DSN is baked into the build */}
|
|
606
|
+
{/* Privacy */}
|
|
645
607
|
{import.meta.env.VITE_SENTRY_DSN && (
|
|
646
608
|
<>
|
|
647
609
|
<SettingsSectionHeader title="隐私" />
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@renderer/components/ui/button';
|
|
4
|
+
import { SettingRow, SettingsSectionHeader, SettingsToggle } from '../components';
|
|
5
|
+
import type { TaskBusConfig } from '@shared/types/team';
|
|
6
|
+
import { Loader2, Radio, Wifi, WifiOff } from 'lucide-react';
|
|
7
|
+
|
|
8
|
+
export function TaskBusSection(): React.JSX.Element {
|
|
9
|
+
const [enabled, setEnabled] = useState(false);
|
|
10
|
+
const [host, setHost] = useState('127.0.0.1');
|
|
11
|
+
const [port, setPort] = useState(6379);
|
|
12
|
+
const [password, setPassword] = useState('');
|
|
13
|
+
const [loading, setLoading] = useState(true);
|
|
14
|
+
const [connecting, setConnecting] = useState(false);
|
|
15
|
+
const [connected, setConnected] = useState(false);
|
|
16
|
+
const [message, setMessage] = useState<string | null>(null);
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
fetch('/api/settings/task-bus')
|
|
20
|
+
.then((r) => r.json())
|
|
21
|
+
.then((data: TaskBusConfig) => {
|
|
22
|
+
setEnabled(data.enabled);
|
|
23
|
+
if (data.redis) {
|
|
24
|
+
setHost(data.redis.host ?? '127.0.0.1');
|
|
25
|
+
setPort(data.redis.port ?? 6379);
|
|
26
|
+
setPassword(data.redis.password ?? '');
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
.catch(() => {})
|
|
30
|
+
.finally(() => setLoading(false));
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const save = async (connectRedis = false) => {
|
|
34
|
+
setMessage(null);
|
|
35
|
+
if (connectRedis) setConnecting(true);
|
|
36
|
+
const config: TaskBusConfig = {
|
|
37
|
+
enabled,
|
|
38
|
+
redis: { host, port, password: password || undefined },
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch('/api/settings/task-bus', {
|
|
42
|
+
method: 'PUT',
|
|
43
|
+
headers: { 'Content-Type': 'application/json' },
|
|
44
|
+
body: JSON.stringify(config),
|
|
45
|
+
});
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
if (connectRedis) {
|
|
48
|
+
setConnected(!!data.connected);
|
|
49
|
+
setMessage(
|
|
50
|
+
data.connected ? 'Redis 连接成功,分布式派发已启用' : 'Redis 连接失败,仅本地派发'
|
|
51
|
+
);
|
|
52
|
+
} else {
|
|
53
|
+
setConnected(false);
|
|
54
|
+
setMessage(enabled ? '已开启,指令已注入到团队工作目录' : '已关闭');
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
setMessage(`操作失败: ${err}`);
|
|
58
|
+
} finally {
|
|
59
|
+
setConnecting(false);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const toggle = (value: boolean) => {
|
|
64
|
+
setEnabled(value);
|
|
65
|
+
const config: TaskBusConfig = {
|
|
66
|
+
enabled: value,
|
|
67
|
+
redis: { host, port, password: password || undefined },
|
|
68
|
+
};
|
|
69
|
+
fetch('/api/settings/task-bus', {
|
|
70
|
+
method: 'PUT',
|
|
71
|
+
headers: { 'Content-Type': 'application/json' },
|
|
72
|
+
body: JSON.stringify(config),
|
|
73
|
+
})
|
|
74
|
+
.then((r) => r.json())
|
|
75
|
+
.then(() => setMessage(value ? '已开启,指令已注入到团队工作目录' : '已关闭'))
|
|
76
|
+
.catch(() => setMessage('操作失败'));
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (loading) {
|
|
80
|
+
return (
|
|
81
|
+
<div className="flex items-center justify-center py-12">
|
|
82
|
+
<Loader2 size={16} className="animate-spin text-[var(--color-text-muted)]" />
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div>
|
|
89
|
+
<SettingsSectionHeader title="任务总线" icon={<Radio size={12} />} />
|
|
90
|
+
|
|
91
|
+
<SettingRow
|
|
92
|
+
label="启用任务总线"
|
|
93
|
+
description="开启后自动为所有团队注入跨团队任务派发指令到 CLAUDE.md"
|
|
94
|
+
>
|
|
95
|
+
<SettingsToggle enabled={enabled} onChange={toggle} />
|
|
96
|
+
</SettingRow>
|
|
97
|
+
|
|
98
|
+
{/* Redis */}
|
|
99
|
+
<SettingRow label="Redis" description="可选,配置后启用跨主机分布式派发">
|
|
100
|
+
<div className="flex items-center gap-2">
|
|
101
|
+
{connected ? (
|
|
102
|
+
<span className="flex items-center gap-1 text-xs text-emerald-500">
|
|
103
|
+
<Wifi size={12} />
|
|
104
|
+
已连接
|
|
105
|
+
</span>
|
|
106
|
+
) : enabled ? (
|
|
107
|
+
<span className="flex items-center gap-1 text-xs text-[var(--color-text-muted)]">
|
|
108
|
+
<WifiOff size={12} />
|
|
109
|
+
本地模式
|
|
110
|
+
</span>
|
|
111
|
+
) : null}
|
|
112
|
+
</div>
|
|
113
|
+
</SettingRow>
|
|
114
|
+
|
|
115
|
+
{enabled && (
|
|
116
|
+
<div className="border-b pb-4" style={{ borderColor: 'var(--color-border-subtle)' }}>
|
|
117
|
+
<div className="space-y-3 px-1 pt-2">
|
|
118
|
+
<div className="flex gap-3">
|
|
119
|
+
<div className="flex-1">
|
|
120
|
+
<label className="mb-1 block text-xs text-[var(--color-text-muted)]">主机</label>
|
|
121
|
+
<input
|
|
122
|
+
type="text"
|
|
123
|
+
value={host}
|
|
124
|
+
onChange={(e) => setHost(e.target.value)}
|
|
125
|
+
className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-bg)] px-2.5 py-1.5 text-sm outline-none focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/30"
|
|
126
|
+
placeholder="127.0.0.1"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
<div className="w-24">
|
|
130
|
+
<label className="mb-1 block text-xs text-[var(--color-text-muted)]">端口</label>
|
|
131
|
+
<input
|
|
132
|
+
type="number"
|
|
133
|
+
value={port}
|
|
134
|
+
onChange={(e) => setPort(Number(e.target.value))}
|
|
135
|
+
className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-bg)] px-2.5 py-1.5 text-sm outline-none focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/30"
|
|
136
|
+
placeholder="6379"
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
<div>
|
|
141
|
+
<label className="mb-1 block text-xs text-[var(--color-text-muted)]">密码</label>
|
|
142
|
+
<input
|
|
143
|
+
type="password"
|
|
144
|
+
value={password}
|
|
145
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
146
|
+
className="w-full rounded-md border border-[var(--color-border)] bg-[var(--color-bg)] px-2.5 py-1.5 text-sm outline-none focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/30"
|
|
147
|
+
placeholder="可选"
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
<div className="flex items-center gap-3 pt-1">
|
|
151
|
+
<Button
|
|
152
|
+
size="sm"
|
|
153
|
+
onClick={() => save(true)}
|
|
154
|
+
disabled={connecting}
|
|
155
|
+
className="gap-1.5"
|
|
156
|
+
>
|
|
157
|
+
{connecting && <Loader2 size={12} className="animate-spin" />}
|
|
158
|
+
{connecting ? '连接中...' : '测试连接'}
|
|
159
|
+
</Button>
|
|
160
|
+
{message && <span className="text-xs text-[var(--color-text-muted)]">{message}</span>}
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
{!enabled && message && (
|
|
167
|
+
<div
|
|
168
|
+
className="border-b py-2 text-xs text-[var(--color-text-muted)]"
|
|
169
|
+
style={{ borderColor: 'var(--color-border-subtle)' }}
|
|
170
|
+
>
|
|
171
|
+
{message}
|
|
172
|
+
</div>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|