@yancyyu/openhermit 1.6.29 → 1.6.31
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-CQm6jUR1.js → ProjectEditorOverlay-DkXfi2pg.js} +1 -1
- package/dist-renderer/assets/{TeamGraphOverlay-h0WDfifv.js → TeamGraphOverlay-CHNNVraw.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-CgG_tjgX.js → _basePickBy-Do-Ff83V.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DwPTU9lP.js → _baseUniq-nDLhSuJI.js} +1 -1
- package/dist-renderer/assets/{arc-7nIrGRzY.js → arc-Bp7fA6sx.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BYhA6Ev2.js → architectureDiagram-VXUJARFQ-CPC1HdGy.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-BVpZUGDg.js → blockDiagram-VD42YOAC-DTVKyNTO.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-DsdreMQ9.js → c4Diagram-YG6GDRKO-XVu-AN00.js} +1 -1
- package/dist-renderer/assets/channel-CIwbNcUO.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CcoAs7Jd.js → chunk-4BX2VUAB-BcWmVyA-.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-CGGAOoXd.js → chunk-55IACEB6-Co4Z2jsE.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-FhpTEPvD.js → chunk-B4BG7PRW-C8q9gfDT.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-DoYySbm1.js → chunk-DI55MBZ5-qDgb1gxO.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-e9l2tGHG.js → chunk-FMBD7UC4-Cm8Gu2gu.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-DeiXVTCy.js → chunk-QN33PNHL-DYji1BRS.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DC2UJLJM.js → chunk-QZHKN3VN-DWAS568H.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BHFD9eZI.js → chunk-TZMSLE5B-CLFzXLA8.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-04A-pvql.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-04A-pvql.js +1 -0
- package/dist-renderer/assets/clone-DQnvTIEM.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BdybQraU.js → cose-bilkent-S5V4N54A-CZdGhX_3.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DdF3pwM3.js → dagre-6UL2VRFP-BVY-G6nO.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-B9Ldd3nh.js → diagram-PSM6KHXK-CUACvAwG.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-XEqkrbpu.js → diagram-QEK2KX5R-3SfnesSG.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-CipwtY59.js → diagram-S2PKOQOG-E3ksXClJ.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-BB-2ISGo.js → erDiagram-Q2GNP2WA-aYjGXss7.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-B8XmJ0u2.js → flowDiagram-NV44I4VS-JMHrrTQs.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-D-8XglBb.js → ganttDiagram-JELNMOA3-CVQ-R5rN.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DL4ChakD.js → gitGraphDiagram-V2S2FVAM-OLn9jq61.js} +1 -1
- package/dist-renderer/assets/{graph-BiFNoBjP.js → graph-BAb2J0l8.js} +1 -1
- package/dist-renderer/assets/{index-qNBNjW4K.js → index-BSoCjBWn.js} +1 -1
- package/dist-renderer/assets/{index-6m1ZAymG.js → index-BtG3HbqP.js} +1 -1
- package/dist-renderer/assets/{index-BowUl0Jb.js → index-CH8e7g1f.js} +583 -573
- package/dist-renderer/assets/index-CSt8DTcn.css +1 -0
- package/dist-renderer/assets/{index-Dp3kJTEe.js → index-Ca4iNkRA.js} +1 -1
- package/dist-renderer/assets/{index-vAykq1H1.js → index-DU9PGgZJ.js} +1 -1
- package/dist-renderer/assets/{index-TOpt_T7A.js → index-DtMzIS9o.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DRIBfHDi.js → infoDiagram-HS3SLOUP-CY_ptQNL.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-BOMiigU4.js → journeyDiagram-XKPGCS4Q-C2vuHEo_.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-DDxeyjod.js → kanban-definition-3W4ZIXB7-mbdNfu8h.js} +1 -1
- package/dist-renderer/assets/{layout-DNANbrI4.js → layout-Do_ArEB1.js} +1 -1
- package/dist-renderer/assets/{linear-DxEJi1yT.js → linear-BMlMKyiq.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-nBfGriW8.js → mindmap-definition-VGOIOE7T-Dfntn-o2.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Din5j6sV.js → pieDiagram-ADFJNKIX-LiWHsGMV.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-DMVK2BEQ.js → quadrantDiagram-AYHSOK5B-D87St8AF.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-6SC94Gg_.js → requirementDiagram-UZGBJVZJ-DAa6lHBx.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-CD2gghhu.js → sankeyDiagram-TZEHDZUN-VOUngars.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BnhkN7nZ.js → sequenceDiagram-WL72ISMW-BzwzmFr2.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-Bn8XdYX-.js → stateDiagram-FKZM4ZOC-BjAQEJ52.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-1b6sI1_g.js → stateDiagram-v2-4FDKWEC3-BDwy4GJm.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-CNs3RPoa.js → timeline-definition-IT6M3QCI-Y5XBZt3W.js} +1 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DzkdUEow.js +162 -0
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-B8o5J2f3.js → xychartDiagram-PRI3JC2R-D-zbvJOv.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +4 -1
- package/src/main/ipc/extensions.ts +353 -0
- package/src/main/server.ts +209 -6
- package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
- package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
- package/src/main/services/extensions/install/McpInstallService.ts +407 -0
- package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
- package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
- package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
- package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
- package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
- package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
- package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
- package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
- package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
- package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
- package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
- package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
- package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
- package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
- package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
- package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
- package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
- package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
- package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
- package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
- package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
- package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
- package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
- package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
- package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
- package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
- package/src/main/services/team/cliFlavor.ts +54 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +3 -0
- package/src/main/utils/atomicWrite.ts +72 -0
- package/src/main/utils/childProcess.ts +554 -0
- package/src/main/utils/cliEnv.ts +54 -0
- package/src/main/utils/cliPathMerge.ts +97 -0
- package/src/main/utils/pathDecoder.ts +664 -0
- package/src/main/utils/pathValidation.ts +432 -0
- package/src/main/utils/shellEnv.ts +331 -0
- package/src/renderer/api/httpClient.ts +61 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +63 -35
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
- package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
- package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
- package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
- package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +111 -15
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +17 -7
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +74 -123
- package/src/renderer/components/team/TeamListFilterPopover.tsx +0 -16
- package/src/renderer/components/team/TeamListView.tsx +7 -32
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +287 -418
- package/src/renderer/components/team/dialogs/useTeamEditForm.ts +283 -0
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +26 -64
- package/src/renderer/components/team/messages/MessagesPanel.tsx +28 -24
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/teamSlice.ts +8 -2
- package/src/renderer/utils/multimodelProviderVisibility.ts +17 -0
- package/src/renderer/utils/openCodeRuntimeDeliveryDiagnostics.ts +29 -9
- package/src/shared/types/api.ts +29 -0
- package/src/shared/types/extensions/index.ts +1 -0
- package/src/shared/types/extensions/mcp.ts +2 -0
- package/src/shared/types/extensions/plugin.ts +2 -1
- package/src/shared/types/extensions/skill.ts +7 -0
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/channel-C0SqeFU7.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +0 -1
- package/dist-renderer/assets/clone-Dm-k63Yr.js +0 -1
- package/dist-renderer/assets/index-BhellmRb.css +0 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +0 -162
- package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
- package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
- package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
- package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
- package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
- package/src/features/recent-projects/main/index.ts +0 -3
- package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
- package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
- package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
- package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
- package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
- package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
- package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { api } from '@renderer/api';
|
|
4
|
+
import { useStore } from '@renderer/store';
|
|
5
|
+
import { isTeamProvisioningActive } from '@renderer/store/slices/teamSlice';
|
|
6
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
7
|
+
|
|
8
|
+
import type { GlobalProvider } from '@shared/types';
|
|
9
|
+
import type { CcAgentType } from '@shared/types/ccConnect';
|
|
10
|
+
|
|
11
|
+
type SavePhase = 'idle' | 'saving' | 'restarting' | 'done';
|
|
12
|
+
|
|
13
|
+
export interface UseTeamEditFormReturn {
|
|
14
|
+
loading: boolean;
|
|
15
|
+
isProvisioning: boolean;
|
|
16
|
+
name: string;
|
|
17
|
+
setName: (v: string) => void;
|
|
18
|
+
description: string;
|
|
19
|
+
setDescription: (v: string) => void;
|
|
20
|
+
agentType: string;
|
|
21
|
+
setAgentType: (v: string) => void;
|
|
22
|
+
permissionMode: string;
|
|
23
|
+
setPermissionMode: (v: string) => void;
|
|
24
|
+
workDir: string;
|
|
25
|
+
setWorkDir: (v: string) => void;
|
|
26
|
+
language: string;
|
|
27
|
+
setLanguage: (v: string) => void;
|
|
28
|
+
managedSources: string;
|
|
29
|
+
setManagedSources: (v: string) => void;
|
|
30
|
+
feishuAllowFrom: string;
|
|
31
|
+
setFeishuAllowFrom: (v: string) => void;
|
|
32
|
+
disabledCommandsInput: string;
|
|
33
|
+
setDisabledCommandsInput: (v: string) => void;
|
|
34
|
+
providerRef: string;
|
|
35
|
+
setProviderRef: (v: string) => void;
|
|
36
|
+
showContextIndicator: boolean;
|
|
37
|
+
setShowContextIndicator: (v: boolean) => void;
|
|
38
|
+
replyFooter: boolean;
|
|
39
|
+
setReplyFooter: (v: boolean) => void;
|
|
40
|
+
injectSender: boolean;
|
|
41
|
+
setInjectSender: (v: boolean) => void;
|
|
42
|
+
color: string;
|
|
43
|
+
compatibleProviders: GlobalProvider[];
|
|
44
|
+
canDelete: boolean;
|
|
45
|
+
/** Current phase of the save+restart lifecycle */
|
|
46
|
+
savePhase: SavePhase;
|
|
47
|
+
/** true while saving or restarting (button spinner / disable) */
|
|
48
|
+
saving: boolean;
|
|
49
|
+
error: string | null;
|
|
50
|
+
clearError: () => void;
|
|
51
|
+
handleSave: () => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const PERMISSION_MODE_OPTIONS = [
|
|
55
|
+
{ value: 'default', label: '默认' },
|
|
56
|
+
{ value: 'acceptEdits', label: '自动接受编辑' },
|
|
57
|
+
{ value: 'bypassPermissions', label: '跳过权限确认' },
|
|
58
|
+
{ value: 'plan', label: '计划模式' },
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
export { PERMISSION_MODE_OPTIONS };
|
|
62
|
+
|
|
63
|
+
export function useTeamEditForm(teamName: string, open: boolean): UseTeamEditFormReturn {
|
|
64
|
+
// ── Store reads ──────────────────────────────────────────────
|
|
65
|
+
const { data, fetchTeams, selectTeam } = useStore(
|
|
66
|
+
useShallow((s) => ({
|
|
67
|
+
data: s.selectedTeamName === teamName ? s.selectedTeamData : null,
|
|
68
|
+
fetchTeams: s.fetchTeams,
|
|
69
|
+
selectTeam: s.selectTeam,
|
|
70
|
+
}))
|
|
71
|
+
);
|
|
72
|
+
const isProvisioning = useStore((s) => isTeamProvisioningActive(s, teamName));
|
|
73
|
+
|
|
74
|
+
// ── Derived defaults ─────────────────────────────────────────
|
|
75
|
+
const rawSettings = useMemo(
|
|
76
|
+
() => (data?.settings ?? {}) as Record<string, unknown>,
|
|
77
|
+
[data?.settings]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const defaults = useMemo(() => {
|
|
81
|
+
const cfg = data?.config;
|
|
82
|
+
const d = data as Record<string, unknown> | null;
|
|
83
|
+
return {
|
|
84
|
+
name: cfg?.name ?? '',
|
|
85
|
+
description: cfg?.description ?? '',
|
|
86
|
+
color: cfg?.color ?? '',
|
|
87
|
+
agentType: cfg?.agentType ?? (d?.harness as string | undefined) ?? 'cursor',
|
|
88
|
+
workDir: (d?.workDir as string | undefined) ?? cfg?.projectPath ?? '',
|
|
89
|
+
permissionMode: cfg?.permissionMode ?? (d?.permissionMode as string | undefined) ?? 'default',
|
|
90
|
+
language:
|
|
91
|
+
cfg?.language ?? (typeof rawSettings.language === 'string' ? rawSettings.language : 'zh'),
|
|
92
|
+
managedSources:
|
|
93
|
+
cfg?.managedSources ??
|
|
94
|
+
(typeof rawSettings.admin_from === 'string' ? rawSettings.admin_from : '*'),
|
|
95
|
+
disabledCommands: Array.isArray(cfg?.disabledCommands)
|
|
96
|
+
? cfg.disabledCommands
|
|
97
|
+
: Array.isArray(rawSettings.disabled_commands)
|
|
98
|
+
? (rawSettings.disabled_commands as unknown[]).filter(
|
|
99
|
+
(entry): entry is string => typeof entry === 'string' && entry.trim().length > 0
|
|
100
|
+
)
|
|
101
|
+
: [],
|
|
102
|
+
platformAllowFrom:
|
|
103
|
+
cfg?.platformAllowFrom ??
|
|
104
|
+
(typeof rawSettings.platform_allow_from === 'object' &&
|
|
105
|
+
rawSettings.platform_allow_from !== null &&
|
|
106
|
+
!Array.isArray(rawSettings.platform_allow_from)
|
|
107
|
+
? (rawSettings.platform_allow_from as Record<string, string>)
|
|
108
|
+
: {}),
|
|
109
|
+
providerRefs: data?.providerRefs ?? [],
|
|
110
|
+
globalProviders: data?.globalProviders ?? [],
|
|
111
|
+
showContextIndicator:
|
|
112
|
+
cfg?.showContextIndicator ??
|
|
113
|
+
(typeof rawSettings.show_context_indicator === 'boolean'
|
|
114
|
+
? rawSettings.show_context_indicator
|
|
115
|
+
: true),
|
|
116
|
+
replyFooter:
|
|
117
|
+
cfg?.replyFooter ??
|
|
118
|
+
(typeof rawSettings.reply_footer === 'boolean' ? rawSettings.reply_footer : true),
|
|
119
|
+
injectSender:
|
|
120
|
+
cfg?.injectSender ??
|
|
121
|
+
(typeof rawSettings.inject_sender === 'boolean' ? rawSettings.inject_sender : false),
|
|
122
|
+
};
|
|
123
|
+
}, [data, rawSettings]);
|
|
124
|
+
|
|
125
|
+
// ── Local form state ─────────────────────────────────────────
|
|
126
|
+
const [name, setName] = useState(defaults.name);
|
|
127
|
+
const [description, setDescription] = useState(defaults.description);
|
|
128
|
+
const [agentType, setAgentType] = useState(defaults.agentType);
|
|
129
|
+
const [permissionMode, setPermissionMode] = useState(defaults.permissionMode);
|
|
130
|
+
const [workDir, setWorkDir] = useState(defaults.workDir);
|
|
131
|
+
const [language, setLanguage] = useState(defaults.language);
|
|
132
|
+
const [managedSources, setManagedSources] = useState(defaults.managedSources);
|
|
133
|
+
const [disabledCommandsInput, setDisabledCommandsInput] = useState(
|
|
134
|
+
defaults.disabledCommands.join(', ')
|
|
135
|
+
);
|
|
136
|
+
const [feishuAllowFrom, setFeishuAllowFrom] = useState(defaults.platformAllowFrom.feishu ?? '*');
|
|
137
|
+
const [providerRef, setProviderRef] = useState(defaults.providerRefs[0] ?? '');
|
|
138
|
+
const [showContextIndicator, setShowContextIndicator] = useState(defaults.showContextIndicator);
|
|
139
|
+
const [replyFooter, setReplyFooter] = useState(defaults.replyFooter);
|
|
140
|
+
const [injectSender, setInjectSender] = useState(defaults.injectSender);
|
|
141
|
+
|
|
142
|
+
// ── Single async lifecycle state ─────────────────────────────
|
|
143
|
+
const [savePhase, setSavePhase] = useState<SavePhase>('idle');
|
|
144
|
+
const [error, setError] = useState<string | null>(null);
|
|
145
|
+
const saving = savePhase === 'saving' || savePhase === 'restarting';
|
|
146
|
+
|
|
147
|
+
// ── Refs ─────────────────────────────────────────────────────
|
|
148
|
+
const defaultsRef = useRef(defaults);
|
|
149
|
+
if (defaults.name) {
|
|
150
|
+
defaultsRef.current = defaults;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Reset form when dialog opens ─────────────────────────────
|
|
154
|
+
const prevOpenRef = useRef(false);
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!open || prevOpenRef.current) {
|
|
157
|
+
prevOpenRef.current = open;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
prevOpenRef.current = true;
|
|
161
|
+
const d = defaultsRef.current;
|
|
162
|
+
setSavePhase('idle');
|
|
163
|
+
setError(null);
|
|
164
|
+
setName(d.name);
|
|
165
|
+
setDescription(d.description);
|
|
166
|
+
setAgentType(d.agentType);
|
|
167
|
+
setPermissionMode(d.permissionMode);
|
|
168
|
+
setWorkDir(d.workDir);
|
|
169
|
+
setLanguage(d.language);
|
|
170
|
+
setManagedSources(d.managedSources);
|
|
171
|
+
setDisabledCommandsInput(d.disabledCommands.join(', '));
|
|
172
|
+
setFeishuAllowFrom(d.platformAllowFrom.feishu ?? '*');
|
|
173
|
+
setProviderRef(d.providerRefs[0] ?? '');
|
|
174
|
+
setShowContextIndicator(d.showContextIndicator);
|
|
175
|
+
setReplyFooter(d.replyFooter);
|
|
176
|
+
setInjectSender(d.injectSender);
|
|
177
|
+
}, [open]);
|
|
178
|
+
|
|
179
|
+
// ── Computed values ──────────────────────────────────────────
|
|
180
|
+
const compatibleProviders = useMemo(
|
|
181
|
+
() =>
|
|
182
|
+
defaults.globalProviders.filter(
|
|
183
|
+
(p) =>
|
|
184
|
+
!p.agent_types ||
|
|
185
|
+
p.agent_types.length === 0 ||
|
|
186
|
+
(p.agent_types as string[]).includes(agentType)
|
|
187
|
+
),
|
|
188
|
+
[defaults.globalProviders, agentType]
|
|
189
|
+
);
|
|
190
|
+
const canDelete = teamName !== 'default' && teamName !== 'my-project';
|
|
191
|
+
|
|
192
|
+
// ── Actions ──────────────────────────────────────────────────
|
|
193
|
+
const clearError = (): void => setError(null);
|
|
194
|
+
|
|
195
|
+
const handleSave = (): void => {
|
|
196
|
+
if (!name.trim()) {
|
|
197
|
+
setError('团队名称不能为空');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (savePhase !== 'idle') return;
|
|
201
|
+
|
|
202
|
+
const disabledCommands = disabledCommandsInput
|
|
203
|
+
.split(',')
|
|
204
|
+
.map((e) => e.trim())
|
|
205
|
+
.filter((e) => e.length > 0);
|
|
206
|
+
const feishu = feishuAllowFrom.trim();
|
|
207
|
+
|
|
208
|
+
setSavePhase('saving');
|
|
209
|
+
setError(null);
|
|
210
|
+
|
|
211
|
+
void (async () => {
|
|
212
|
+
try {
|
|
213
|
+
await api.teams.updateConfig(teamName, {
|
|
214
|
+
name: name.trim(),
|
|
215
|
+
description: description.trim(),
|
|
216
|
+
color: defaultsRef.current.color,
|
|
217
|
+
agentType: agentType.trim() || undefined,
|
|
218
|
+
workDir: workDir.trim() || undefined,
|
|
219
|
+
permissionMode: permissionMode.trim() || undefined,
|
|
220
|
+
showContextIndicator,
|
|
221
|
+
replyFooter,
|
|
222
|
+
injectSender,
|
|
223
|
+
language: language.trim() || undefined,
|
|
224
|
+
managedSources: managedSources.trim() || undefined,
|
|
225
|
+
disabledCommands,
|
|
226
|
+
platformAllowFrom: feishu ? { feishu } : {},
|
|
227
|
+
providerRefs: providerRef ? [providerRef] : [],
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
setSavePhase('restarting');
|
|
231
|
+
await api.ccSettings.restart();
|
|
232
|
+
|
|
233
|
+
// Small grace period so the user sees "restarting" feedback
|
|
234
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
235
|
+
|
|
236
|
+
await Promise.all([fetchTeams(), selectTeam(teamName)]);
|
|
237
|
+
setSavePhase('done');
|
|
238
|
+
} catch (err) {
|
|
239
|
+
setError(err instanceof Error ? err.message : '保存失败');
|
|
240
|
+
setSavePhase('idle');
|
|
241
|
+
}
|
|
242
|
+
})();
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
loading: !data,
|
|
247
|
+
isProvisioning,
|
|
248
|
+
name,
|
|
249
|
+
setName,
|
|
250
|
+
description,
|
|
251
|
+
setDescription,
|
|
252
|
+
agentType,
|
|
253
|
+
setAgentType,
|
|
254
|
+
permissionMode,
|
|
255
|
+
setPermissionMode,
|
|
256
|
+
workDir,
|
|
257
|
+
setWorkDir,
|
|
258
|
+
language,
|
|
259
|
+
setLanguage,
|
|
260
|
+
managedSources,
|
|
261
|
+
setManagedSources,
|
|
262
|
+
feishuAllowFrom,
|
|
263
|
+
setFeishuAllowFrom,
|
|
264
|
+
disabledCommandsInput,
|
|
265
|
+
setDisabledCommandsInput,
|
|
266
|
+
providerRef,
|
|
267
|
+
setProviderRef,
|
|
268
|
+
showContextIndicator,
|
|
269
|
+
setShowContextIndicator,
|
|
270
|
+
replyFooter,
|
|
271
|
+
setReplyFooter,
|
|
272
|
+
injectSender,
|
|
273
|
+
setInjectSender,
|
|
274
|
+
color: defaults.color,
|
|
275
|
+
compatibleProviders,
|
|
276
|
+
canDelete,
|
|
277
|
+
savePhase,
|
|
278
|
+
saving,
|
|
279
|
+
error,
|
|
280
|
+
clearError,
|
|
281
|
+
handleSave,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
@@ -361,49 +361,26 @@ export const KanbanBoard = ({
|
|
|
361
361
|
columnTasks: TeamTask[],
|
|
362
362
|
compact?: boolean
|
|
363
363
|
): React.JSX.Element => {
|
|
364
|
-
const addHandler =
|
|
365
|
-
onAddTask && columnId === 'todo'
|
|
366
|
-
? () => onAddTask(false)
|
|
367
|
-
: onAddTask && columnId === 'in_progress'
|
|
368
|
-
? () => onAddTask(true)
|
|
369
|
-
: undefined;
|
|
370
|
-
|
|
371
|
-
const addButton = addHandler ? (
|
|
372
|
-
<button
|
|
373
|
-
type="button"
|
|
374
|
-
onClick={addHandler}
|
|
375
|
-
className="flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed border-[var(--color-border)] p-3 text-xs text-[var(--color-text-muted)] transition-colors hover:border-[var(--color-border-emphasis)] hover:text-[var(--color-text-secondary)]"
|
|
376
|
-
>
|
|
377
|
-
<Plus size={13} />
|
|
378
|
-
Add task
|
|
379
|
-
</button>
|
|
380
|
-
) : null;
|
|
381
|
-
|
|
382
364
|
if (columnTasks.length === 0) {
|
|
383
365
|
return (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
</div>
|
|
388
|
-
)
|
|
366
|
+
<div className="rounded-md border border-dashed border-[var(--color-border)] p-3 text-xs text-[var(--color-text-muted)]">
|
|
367
|
+
No tasks
|
|
368
|
+
</div>
|
|
389
369
|
);
|
|
390
370
|
}
|
|
391
371
|
if (enableTaskSorting) {
|
|
392
372
|
const itemIds = columnTasks.map((t) => t.id);
|
|
393
373
|
return (
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
</SortableContext>
|
|
405
|
-
{addButton}
|
|
406
|
-
</>
|
|
374
|
+
<SortableContext items={itemIds} strategy={verticalListSortingStrategy}>
|
|
375
|
+
{columnTasks.map((task) => (
|
|
376
|
+
<SortableKanbanTaskCard
|
|
377
|
+
key={task.id}
|
|
378
|
+
task={task}
|
|
379
|
+
columnId={columnId}
|
|
380
|
+
memberColorMap={memberColorMap}
|
|
381
|
+
/>
|
|
382
|
+
))}
|
|
383
|
+
</SortableContext>
|
|
407
384
|
);
|
|
408
385
|
}
|
|
409
386
|
return (
|
|
@@ -432,7 +409,6 @@ export const KanbanBoard = ({
|
|
|
432
409
|
onDeleteTask={onDeleteTask}
|
|
433
410
|
/>
|
|
434
411
|
))}
|
|
435
|
-
{addButton}
|
|
436
412
|
</>
|
|
437
413
|
);
|
|
438
414
|
};
|
|
@@ -614,36 +590,22 @@ export const KanbanBoard = ({
|
|
|
614
590
|
</div>
|
|
615
591
|
) : (
|
|
616
592
|
<div className="w-full min-w-0 max-w-full overflow-x-auto overflow-y-hidden px-1 pb-6 pr-4 pt-2">
|
|
617
|
-
<div className="flex
|
|
618
|
-
{visibleColumns.map((column
|
|
593
|
+
<div className="flex w-full items-start gap-3">
|
|
594
|
+
{visibleColumns.map((column) => {
|
|
619
595
|
const columnTasks = groupedOrdered.get(column.id) ?? [];
|
|
620
596
|
const accent = COLUMN_ACCENTS[column.id as 'todo' | 'in_progress' | 'done'];
|
|
621
|
-
const width = columnWidths.get(column.id) ?? 256;
|
|
622
|
-
const handleProps = getHandleProps(column.id);
|
|
623
597
|
return (
|
|
624
|
-
<div key={column.id} className="flex
|
|
625
|
-
<
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
</KanbanColumn>
|
|
636
|
-
</div>
|
|
637
|
-
{index < visibleColumns.length - 1 ? (
|
|
638
|
-
<div
|
|
639
|
-
className="group relative mx-0.5 flex items-center justify-center"
|
|
640
|
-
onPointerDown={handleProps.onPointerDown}
|
|
641
|
-
style={handleProps.style}
|
|
642
|
-
aria-label={handleProps['aria-label']}
|
|
643
|
-
>
|
|
644
|
-
<div className="h-full w-px bg-[var(--color-border)] transition-colors group-hover:bg-blue-500/50 group-active:bg-blue-500" />
|
|
645
|
-
</div>
|
|
646
|
-
) : null}
|
|
598
|
+
<div key={column.id} className="min-w-0 flex-1">
|
|
599
|
+
<KanbanColumn
|
|
600
|
+
title={column.title}
|
|
601
|
+
count={columnTasks.length}
|
|
602
|
+
icon={accent.icon}
|
|
603
|
+
headerBg={accent.headerBg}
|
|
604
|
+
bodyBg={accent.bodyBg}
|
|
605
|
+
bodyClassName="max-h-none overflow-visible"
|
|
606
|
+
>
|
|
607
|
+
{renderCards(column.id, columnTasks, true)}
|
|
608
|
+
</KanbanColumn>
|
|
647
609
|
</div>
|
|
648
610
|
);
|
|
649
611
|
})}
|
|
@@ -208,7 +208,8 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
208
208
|
teams,
|
|
209
209
|
openTeamTab,
|
|
210
210
|
messages,
|
|
211
|
-
|
|
211
|
+
hasMore,
|
|
212
|
+
loadingOlderMessages,
|
|
212
213
|
loadOlderTeamMessages,
|
|
213
214
|
refreshTeamMessagesHead,
|
|
214
215
|
addOptimisticTeamMessage,
|
|
@@ -224,7 +225,13 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
224
225
|
teams: s.teams,
|
|
225
226
|
openTeamTab: s.openTeamTab,
|
|
226
227
|
messages: selectTeamMessages(s, teamName),
|
|
227
|
-
|
|
228
|
+
// Subscribe to only the primitive flags the panel renders. The full
|
|
229
|
+
// cache entry object is rebuilt on every (even no-op) head refresh —
|
|
230
|
+
// selecting it wholesale would re-render this heavy panel every poll.
|
|
231
|
+
hasMore: teamName ? (s.teamMessagesByName[teamName]?.hasMore ?? false) : false,
|
|
232
|
+
loadingOlderMessages: teamName
|
|
233
|
+
? (s.teamMessagesByName[teamName]?.loadingOlder ?? false)
|
|
234
|
+
: false,
|
|
228
235
|
loadOlderTeamMessages: s.loadOlderTeamMessages,
|
|
229
236
|
refreshTeamMessagesHead: s.refreshTeamMessagesHead,
|
|
230
237
|
addOptimisticTeamMessage: s.addOptimisticTeamMessage,
|
|
@@ -233,16 +240,15 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
233
240
|
const bootstrapHeadRefreshAttemptedForTeamRef = useRef<string | null>(null);
|
|
234
241
|
|
|
235
242
|
const loadOlderMessages = useCallback(async () => {
|
|
236
|
-
|
|
243
|
+
// Read the live cache entry instead of subscribing to it — loadingHead
|
|
244
|
+
// toggles on every background head refresh and must not re-render us.
|
|
245
|
+
const entry = useStore.getState().teamMessagesByName[teamName];
|
|
246
|
+
if (!entry?.hasMore || entry.loadingHead || entry.loadingOlder) {
|
|
237
247
|
return;
|
|
238
248
|
}
|
|
239
249
|
await loadOlderTeamMessages(teamName);
|
|
240
|
-
}, [loadOlderTeamMessages,
|
|
250
|
+
}, [loadOlderTeamMessages, teamName]);
|
|
241
251
|
|
|
242
|
-
const messagesLoading =
|
|
243
|
-
(messagesState?.loadingHead ?? false) || (messagesState?.loadingOlder ?? false);
|
|
244
|
-
const loadingOlderMessages = messagesState?.loadingOlder ?? false;
|
|
245
|
-
const hasMore = messagesState?.hasMore ?? false;
|
|
246
252
|
const effectiveMessages = messages;
|
|
247
253
|
const loadedMessageCount = effectiveMessages.length;
|
|
248
254
|
const autoLoadOlderLockRef = useRef(false);
|
|
@@ -271,12 +277,11 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
271
277
|
|
|
272
278
|
const maybeAutoLoadOlderMessages = useCallback(
|
|
273
279
|
(scrollTop: number) => {
|
|
274
|
-
if (
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
) {
|
|
280
|
+
if (scrollTop > AUTO_LOAD_OLDER_SCROLL_TOP_PX || !hasMore || loadingOlderMessages) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// loadingHead is read live (not subscribed) to avoid per-poll re-renders.
|
|
284
|
+
if (useStore.getState().teamMessagesByName[teamName]?.loadingHead) {
|
|
280
285
|
return;
|
|
281
286
|
}
|
|
282
287
|
if (autoLoadOlderLockRef.current) {
|
|
@@ -285,7 +290,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
285
290
|
autoLoadOlderLockRef.current = true;
|
|
286
291
|
void loadOlderMessages();
|
|
287
292
|
},
|
|
288
|
-
[hasMore, loadOlderMessages, loadingOlderMessages,
|
|
293
|
+
[hasMore, loadOlderMessages, loadingOlderMessages, teamName]
|
|
289
294
|
);
|
|
290
295
|
|
|
291
296
|
useEffect(() => {
|
|
@@ -379,7 +384,9 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
379
384
|
return () => {
|
|
380
385
|
cancelled = true;
|
|
381
386
|
};
|
|
382
|
-
|
|
387
|
+
// Refetch when the lead session id changes (e.g. a new session is spawned)
|
|
388
|
+
// so the session list/selector reflects the updated id without a remount.
|
|
389
|
+
}, [teamName, currentLeadSessionId]);
|
|
383
390
|
|
|
384
391
|
const selectedSession = useMemo(
|
|
385
392
|
() => teamSessions.find((session) => session.sessionKey === selectedSessionKey) ?? null,
|
|
@@ -455,7 +462,10 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
455
462
|
bootstrapHeadRefreshAttemptedForTeamRef.current = null;
|
|
456
463
|
return;
|
|
457
464
|
}
|
|
458
|
-
|
|
465
|
+
// Read loading flags live rather than subscribing — they toggle on every
|
|
466
|
+
// background head refresh and must not drive this bootstrap effect.
|
|
467
|
+
const entry = useStore.getState().teamMessagesByName[teamName];
|
|
468
|
+
if (entry?.loadingHead || entry?.loadingOlder) {
|
|
459
469
|
return;
|
|
460
470
|
}
|
|
461
471
|
if (bootstrapHeadRefreshAttemptedForTeamRef.current === teamName) {
|
|
@@ -463,13 +473,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
463
473
|
}
|
|
464
474
|
bootstrapHeadRefreshAttemptedForTeamRef.current = teamName;
|
|
465
475
|
void refreshTeamMessagesHead(teamName).catch(() => undefined);
|
|
466
|
-
}, [
|
|
467
|
-
effectiveMessages.length,
|
|
468
|
-
messagesState?.loadingHead,
|
|
469
|
-
messagesState?.loadingOlder,
|
|
470
|
-
refreshTeamMessagesHead,
|
|
471
|
-
teamName,
|
|
472
|
-
]);
|
|
476
|
+
}, [effectiveMessages.length, refreshTeamMessagesHead, teamName]);
|
|
473
477
|
|
|
474
478
|
useLayoutEffect(() => {
|
|
475
479
|
if (position !== 'sidebar') return;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TerminalPanel — a good-looking, read-only terminal-style panel for rendering
|
|
3
|
+
* command / CLI output faithfully.
|
|
4
|
+
*
|
|
5
|
+
* Unlike the structured markdown renderers, this preserves the raw terminal feel:
|
|
6
|
+
* - full ANSI color / decoration fidelity (via `anser`)
|
|
7
|
+
* - monospace, whitespace-exact output
|
|
8
|
+
* - carriage-return (\r) overwrite handling so progress bars settle to their
|
|
9
|
+
* final frame instead of dumping every intermediate line
|
|
10
|
+
* - optional `$ command` prompt line so a Bash call reads like a real terminal
|
|
11
|
+
*
|
|
12
|
+
* It is intentionally lightweight (no xterm.js / node-pty): the session view is a
|
|
13
|
+
* read-only viewer of recorded output, so we only need faithful rendering, not a
|
|
14
|
+
* live PTY.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useMemo, useState } from 'react';
|
|
18
|
+
|
|
19
|
+
import Anser, { type AnserJsonEntry } from 'anser';
|
|
20
|
+
import { Check, Copy } from 'lucide-react';
|
|
21
|
+
|
|
22
|
+
interface TerminalPanelProps {
|
|
23
|
+
/** Raw output text, may contain ANSI escape sequences. */
|
|
24
|
+
text: string;
|
|
25
|
+
/** Optional command to render as a `$ command` prompt line above the output. */
|
|
26
|
+
command?: string;
|
|
27
|
+
/** Optional label shown in the header bar (e.g. a short description). */
|
|
28
|
+
title?: string;
|
|
29
|
+
/** Max body height in px before scrolling. Defaults to 384. */
|
|
30
|
+
maxHeight?: number;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Collapse carriage-return overwrites within each line and strip non-color
|
|
36
|
+
* escape sequences (cursor moves, screen clears, OSC) that would otherwise
|
|
37
|
+
* render as garbage. SGR color codes are left intact for `anser`.
|
|
38
|
+
*/
|
|
39
|
+
function normalizeTerminalText(raw: string): string {
|
|
40
|
+
// Strip OSC sequences: ESC ] ... BEL or ESC ] ... ESC \
|
|
41
|
+
let out = raw.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '');
|
|
42
|
+
// Strip CSI sequences that are NOT SGR ("m"): cursor movement, erase, etc.
|
|
43
|
+
out = out.replace(/\x1b\[[0-9;?]*[A-Za-ln-z]/g, '');
|
|
44
|
+
// Collapse \r overwrites per line: later segments overwrite earlier from col 0.
|
|
45
|
+
out = out
|
|
46
|
+
.split('\n')
|
|
47
|
+
.map((line) => {
|
|
48
|
+
if (!line.includes('\r')) return line;
|
|
49
|
+
let acc = '';
|
|
50
|
+
for (const seg of line.split('\r')) {
|
|
51
|
+
acc = seg.length >= acc.length ? seg : seg + acc.slice(seg.length);
|
|
52
|
+
}
|
|
53
|
+
return acc;
|
|
54
|
+
})
|
|
55
|
+
.join('\n');
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function styleForSegment(seg: AnserJsonEntry): React.CSSProperties {
|
|
60
|
+
const style: React.CSSProperties = {};
|
|
61
|
+
if (seg.fg) style.color = `rgb(${seg.fg})`;
|
|
62
|
+
if (seg.bg) style.backgroundColor = `rgb(${seg.bg})`;
|
|
63
|
+
const decorations = seg.decorations ?? [];
|
|
64
|
+
if (decorations.includes('bold')) style.fontWeight = 600;
|
|
65
|
+
if (decorations.includes('italic')) style.fontStyle = 'italic';
|
|
66
|
+
if (decorations.includes('underline')) style.textDecoration = 'underline';
|
|
67
|
+
if (decorations.includes('dim')) style.opacity = 0.6;
|
|
68
|
+
return style;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const MONO_FONT =
|
|
72
|
+
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
|
73
|
+
|
|
74
|
+
export const TerminalPanel = ({
|
|
75
|
+
text,
|
|
76
|
+
command,
|
|
77
|
+
title,
|
|
78
|
+
maxHeight = 384,
|
|
79
|
+
className,
|
|
80
|
+
}: TerminalPanelProps): React.JSX.Element => {
|
|
81
|
+
const [copied, setCopied] = useState(false);
|
|
82
|
+
|
|
83
|
+
const segments = useMemo<AnserJsonEntry[]>(
|
|
84
|
+
() =>
|
|
85
|
+
Anser.ansiToJson(normalizeTerminalText(text ?? ''), {
|
|
86
|
+
json: true,
|
|
87
|
+
use_classes: false,
|
|
88
|
+
remove_empty: false,
|
|
89
|
+
}),
|
|
90
|
+
[text]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const handleCopy = (): void => {
|
|
94
|
+
const payload = command ? `$ ${command}\n${text ?? ''}` : (text ?? '');
|
|
95
|
+
void navigator.clipboard?.writeText(payload).then(() => {
|
|
96
|
+
setCopied(true);
|
|
97
|
+
setTimeout(() => setCopied(false), 1500);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
className={`overflow-hidden rounded-lg border ${className ?? ''}`}
|
|
104
|
+
style={{ borderColor: 'rgba(255,255,255,0.08)', backgroundColor: '#0c0c0f' }}
|
|
105
|
+
>
|
|
106
|
+
{/* Header / window chrome */}
|
|
107
|
+
<div
|
|
108
|
+
className="flex items-center gap-2 px-3 py-1.5"
|
|
109
|
+
style={{
|
|
110
|
+
backgroundColor: '#16161b',
|
|
111
|
+
borderBottom: '1px solid rgba(255,255,255,0.06)',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<span className="flex items-center gap-1.5">
|
|
115
|
+
<span className="size-2.5 rounded-full" style={{ backgroundColor: '#ff5f57' }} />
|
|
116
|
+
<span className="size-2.5 rounded-full" style={{ backgroundColor: '#febc2e' }} />
|
|
117
|
+
<span className="size-2.5 rounded-full" style={{ backgroundColor: '#28c840' }} />
|
|
118
|
+
</span>
|
|
119
|
+
<span
|
|
120
|
+
className="ml-1 flex-1 truncate text-[11px]"
|
|
121
|
+
style={{ color: 'rgba(255,255,255,0.45)', fontFamily: MONO_FONT }}
|
|
122
|
+
>
|
|
123
|
+
{title ?? (command ? command : 'terminal')}
|
|
124
|
+
</span>
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
onClick={handleCopy}
|
|
128
|
+
className="flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] transition-colors"
|
|
129
|
+
style={{ color: 'rgba(255,255,255,0.45)' }}
|
|
130
|
+
title="复制"
|
|
131
|
+
>
|
|
132
|
+
{copied ? <Check className="size-3" /> : <Copy className="size-3" />}
|
|
133
|
+
{copied ? '已复制' : '复制'}
|
|
134
|
+
</button>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{/* Body */}
|
|
138
|
+
<pre
|
|
139
|
+
className="overflow-auto whitespace-pre-wrap break-all px-3 py-2.5 text-xs leading-relaxed"
|
|
140
|
+
style={{ maxHeight, fontFamily: MONO_FONT, color: '#d4d4d4', margin: 0 }}
|
|
141
|
+
>
|
|
142
|
+
{command && (
|
|
143
|
+
<div className="mb-1">
|
|
144
|
+
<span style={{ color: '#28c840' }}>$ </span>
|
|
145
|
+
<span style={{ color: '#e8e8e8' }}>{command}</span>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
{segments.map((seg, i) => (
|
|
149
|
+
<span key={i} style={styleForSegment(seg)}>
|
|
150
|
+
{seg.content}
|
|
151
|
+
</span>
|
|
152
|
+
))}
|
|
153
|
+
</pre>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
PluginSortField,
|
|
17
17
|
} from '@shared/types/extensions';
|
|
18
18
|
|
|
19
|
-
export type ExtensionsSubTab = 'plugins' | 'mcp-servers' | 'skills' | '
|
|
19
|
+
export type ExtensionsSubTab = 'plugins' | 'mcp-servers' | 'skills' | 'env-vars';
|
|
20
20
|
export type SkillsSortState = 'name-asc' | 'recent-desc';
|
|
21
21
|
|
|
22
22
|
interface PluginSortState {
|
|
@@ -33,7 +33,7 @@ const DEFAULT_FILTERS: PluginFilters = {
|
|
|
33
33
|
|
|
34
34
|
export function useExtensionsTabState() {
|
|
35
35
|
// ── Sub-tab navigation ──
|
|
36
|
-
const [activeSubTab, setActiveSubTab] = useState<ExtensionsSubTab>('
|
|
36
|
+
const [activeSubTab, setActiveSubTab] = useState<ExtensionsSubTab>('plugins');
|
|
37
37
|
|
|
38
38
|
// ── Plugin filters & sort ──
|
|
39
39
|
const [pluginFilters, setPluginFilters] = useState<PluginFilters>(DEFAULT_FILTERS);
|