@yancyyu/openhermit 1.6.28 → 1.6.30

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.
Files changed (177) hide show
  1. package/dist-renderer/assets/ProjectEditorOverlay-DsQt4FHy.js +52 -0
  2. package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CrWocIjq.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-B6d8ysWi.js} +1 -1
  5. package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-DAIYCFP8.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
  9. package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DZou1667.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
  20. package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
  30. package/dist-renderer/assets/{graph-Enirf-f8.js → graph-DY3qbzqj.js} +1 -1
  31. package/dist-renderer/assets/{index-DY1zqsb6.js → index-BlOrAXp3.js} +551 -537
  32. package/dist-renderer/assets/{index-AjxP_rE_.js → index-Bs27J5gB.js} +1 -1
  33. package/dist-renderer/assets/{index-CtlzGepK.js → index-C8B_nKOF.js} +1 -1
  34. package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
  35. package/dist-renderer/assets/{index-COZPUWJW.js → index-DLKyDr4T.js} +1 -1
  36. package/dist-renderer/assets/{index-DdhqolqE.js → index-Dhsk3_DD.js} +1 -1
  37. package/dist-renderer/assets/{index-ChR1D6ZF.js → index-GpUvV2xs.js} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
  41. package/dist-renderer/assets/{layout-CPFgj98r.js → layout-BZLlNmbr.js} +1 -1
  42. package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-qz6v45xy.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
  52. package/dist-renderer/assets/{treemap-GDKQZRPO-CVd5GNDw.js → treemap-GDKQZRPO-CGKpOUF2.js} +1 -1
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-t4-rwdAw.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +4 -1
  56. package/src/main/ipc/extensions.ts +353 -0
  57. package/src/main/server.ts +907 -184
  58. package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
  59. package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
  60. package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
  61. package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
  62. package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
  63. package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
  64. package/src/main/services/extensions/install/McpInstallService.ts +407 -0
  65. package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
  66. package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
  67. package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
  68. package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
  69. package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
  70. package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
  71. package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
  72. package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
  73. package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
  74. package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
  75. package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
  76. package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
  77. package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
  78. package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
  79. package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
  80. package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
  81. package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
  82. package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
  83. package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
  84. package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
  85. package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
  86. package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
  87. package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
  88. package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
  89. package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
  90. package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
  91. package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
  92. package/src/main/services/session-intelligence/UsageTelemetryService.ts +33 -18
  93. package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
  94. package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
  95. package/src/main/services/team/cliFlavor.ts +54 -0
  96. package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
  97. package/src/main/services/teams-mvp/TaskDispatchService.ts +883 -95
  98. package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
  99. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
  100. package/src/main/services/teams-mvp/index.ts +3 -0
  101. package/src/main/utils/atomicWrite.ts +72 -0
  102. package/src/main/utils/childProcess.ts +554 -0
  103. package/src/main/utils/cliEnv.ts +54 -0
  104. package/src/main/utils/cliPathMerge.ts +97 -0
  105. package/src/main/utils/pathDecoder.ts +664 -0
  106. package/src/main/utils/pathValidation.ts +432 -0
  107. package/src/main/utils/shellEnv.ts +331 -0
  108. package/src/renderer/App.tsx +5 -0
  109. package/src/renderer/api/httpClient.ts +128 -0
  110. package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
  111. package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
  112. package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
  113. package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
  114. package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
  115. package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
  116. package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
  117. package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
  118. package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
  119. package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
  120. package/src/renderer/components/layout/PaneContent.tsx +2 -0
  121. package/src/renderer/components/layout/SortableTab.tsx +1 -0
  122. package/src/renderer/components/layout/TabBarActions.tsx +12 -12
  123. package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
  124. package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
  125. package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
  126. package/src/renderer/components/settings/sections/TaskBusSection.tsx +144 -84
  127. package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
  128. package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
  129. package/src/renderer/components/tasks/TasksView.tsx +343 -0
  130. package/src/renderer/components/team/HarnessSelect.tsx +71 -0
  131. package/src/renderer/components/team/TeamDetailView.tsx +55 -98
  132. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
  133. package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
  134. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
  135. package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
  136. package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
  137. package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
  138. package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
  139. package/src/renderer/components/team/kanban/KanbanBoard.tsx +31 -65
  140. package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
  141. package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
  142. package/src/renderer/components/team/messages/MessagesPanel.tsx +100 -26
  143. package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
  144. package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
  145. package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
  146. package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
  147. package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
  148. package/src/renderer/store/slices/extensionsSlice.ts +42 -107
  149. package/src/renderer/store/slices/scheduleSlice.ts +21 -0
  150. package/src/renderer/store/slices/teamSlice.ts +67 -25
  151. package/src/renderer/types/tabs.ts +1 -0
  152. package/src/shared/types/api.ts +58 -0
  153. package/src/shared/types/extensions/index.ts +1 -0
  154. package/src/shared/types/extensions/mcp.ts +2 -0
  155. package/src/shared/types/extensions/plugin.ts +2 -1
  156. package/src/shared/types/extensions/skill.ts +7 -0
  157. package/src/shared/types/team.ts +104 -1
  158. package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
  159. package/dist-renderer/assets/ProjectEditorOverlay-A4DZTvSy.js +0 -57
  160. package/dist-renderer/assets/channel-Pre42N5O.js +0 -1
  161. package/dist-renderer/assets/classDiagram-2ON5EDUG-CdJsTJsj.js +0 -1
  162. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CdJsTJsj.js +0 -1
  163. package/dist-renderer/assets/clone-BjQBiNfj.js +0 -1
  164. package/dist-renderer/assets/index-BIOJremZ.css +0 -1
  165. package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
  166. package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
  167. package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
  168. package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
  169. package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
  170. package/src/features/recent-projects/main/index.ts +0 -3
  171. package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
  172. package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
  173. package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
  174. package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
  175. package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
  176. package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
  177. package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
@@ -1,282 +0,0 @@
1
- /**
2
- * ApiKeyFormDialog — create or edit an API key entry.
3
- * Edit mode pre-fills all fields except the value (which must be re-entered).
4
- */
5
-
6
- import { useEffect, useState } from 'react';
7
-
8
- import { Button } from '@renderer/components/ui/button';
9
- import {
10
- Dialog,
11
- DialogContent,
12
- DialogDescription,
13
- DialogHeader,
14
- DialogTitle,
15
- } from '@renderer/components/ui/dialog';
16
- import { Input } from '@renderer/components/ui/input';
17
- import { Label } from '@renderer/components/ui/label';
18
- import {
19
- Select,
20
- SelectContent,
21
- SelectItem,
22
- SelectTrigger,
23
- SelectValue,
24
- } from '@renderer/components/ui/select';
25
- import { useStore } from '@renderer/store';
26
- import { AlertTriangle, Key } from 'lucide-react';
27
-
28
- import type { ApiKeyEntry } from '@shared/types/extensions';
29
-
30
- const ENV_KEY_RE = /^[A-Z_][A-Z0-9_]{0,100}$/i;
31
-
32
- interface ApiKeyFormDialogProps {
33
- open: boolean;
34
- editingKey: ApiKeyEntry | null;
35
- currentProjectPath: string | null;
36
- currentProjectLabel: string | null;
37
- onClose: () => void;
38
- }
39
-
40
- type Scope = 'user' | 'project';
41
-
42
- const SCOPE_OPTIONS: { value: Scope; label: string }[] = [
43
- { value: 'user', label: '个人(全局)' },
44
- { value: 'project', label: '项目' },
45
- ];
46
-
47
- export const ApiKeyFormDialog = ({
48
- open,
49
- editingKey,
50
- currentProjectPath,
51
- currentProjectLabel,
52
- onClose,
53
- }: ApiKeyFormDialogProps): React.JSX.Element => {
54
- const saveApiKey = useStore((s) => s.saveApiKey);
55
- const apiKeySaving = useStore((s) => s.apiKeySaving);
56
- const storageStatus = useStore((s) => s.apiKeyStorageStatus);
57
-
58
- const [name, setName] = useState('');
59
- const [envVarName, setEnvVarName] = useState('');
60
- const [value, setValue] = useState('');
61
- const [scope, setScope] = useState<Scope>('user');
62
- const [error, setError] = useState<string | null>(null);
63
- const [envVarError, setEnvVarError] = useState<string | null>(null);
64
- const editingProjectPath =
65
- editingKey?.scope === 'project' ? (editingKey.projectPath ?? null) : null;
66
- const effectiveProjectPath = editingProjectPath ?? currentProjectPath;
67
- const effectiveProjectLabel =
68
- effectiveProjectPath && effectiveProjectPath === currentProjectPath
69
- ? currentProjectLabel
70
- : effectiveProjectPath;
71
- const canUseProjectScope = Boolean(effectiveProjectPath);
72
-
73
- // Reset form when dialog opens/closes or editing key changes
74
- useEffect(() => {
75
- if (open) {
76
- if (editingKey) {
77
- setName(editingKey.name);
78
- setEnvVarName(editingKey.envVarName);
79
- setScope(editingKey.scope);
80
- setValue('');
81
- } else {
82
- setName('');
83
- setEnvVarName('');
84
- setValue('');
85
- setScope('user');
86
- }
87
- setError(null);
88
- setEnvVarError(null);
89
- }
90
- }, [open, editingKey]);
91
-
92
- useEffect(() => {
93
- if (open && scope === 'project' && !canUseProjectScope) {
94
- setScope('user');
95
- }
96
- }, [canUseProjectScope, open, scope]);
97
-
98
- const validateEnvVar = (v: string) => {
99
- if (!v.trim()) {
100
- setEnvVarError(null);
101
- return;
102
- }
103
- if (!ENV_KEY_RE.test(v)) {
104
- setEnvVarError('Use letters, digits, underscores. Must start with a letter or underscore.');
105
- } else {
106
- setEnvVarError(null);
107
- }
108
- };
109
-
110
- const handleSubmit = async (e: React.FormEvent) => {
111
- e.preventDefault();
112
- setError(null);
113
-
114
- if (!name.trim()) {
115
- setError('Name is required');
116
- return;
117
- }
118
- if (!envVarName.trim()) {
119
- setError('Environment variable name is required');
120
- return;
121
- }
122
- if (!ENV_KEY_RE.test(envVarName)) {
123
- setError('Invalid environment variable name');
124
- return;
125
- }
126
- if (!value) {
127
- setError('Key value is required');
128
- return;
129
- }
130
- if (scope === 'project' && !effectiveProjectPath) {
131
- setError('Project-scoped API keys require an active project');
132
- return;
133
- }
134
-
135
- try {
136
- await saveApiKey({
137
- id: editingKey?.id,
138
- name: name.trim(),
139
- envVarName: envVarName.trim(),
140
- value,
141
- scope,
142
- projectPath: scope === 'project' ? (effectiveProjectPath ?? undefined) : undefined,
143
- });
144
- onClose();
145
- } catch (err) {
146
- setError(err instanceof Error ? err.message : 'Failed to save');
147
- }
148
- };
149
-
150
- const isEdit = editingKey !== null;
151
- const canSubmit =
152
- name.trim() &&
153
- envVarName.trim() &&
154
- value &&
155
- !envVarError &&
156
- !apiKeySaving &&
157
- (scope !== 'project' || canUseProjectScope);
158
-
159
- return (
160
- <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
161
- <DialogContent className="max-w-md">
162
- <DialogHeader>
163
- <div className="flex items-center gap-2">
164
- <div className="flex size-8 items-center justify-center rounded-lg border border-border bg-surface-raised">
165
- <Key className="size-4 text-text-muted" />
166
- </div>
167
- <div>
168
- <DialogTitle>{isEdit ? '编辑 API Key' : '添加 API Key'}</DialogTitle>
169
- <DialogDescription>
170
- {isEdit
171
- ? '更新 Key 信息。你需要重新输入 Key 值。'
172
- : '保存 API Key,用于安装 MCP 服务器时自动填充。'}
173
- </DialogDescription>
174
- </div>
175
- </div>
176
- </DialogHeader>
177
-
178
- {storageStatus && storageStatus.encryptionMethod !== 'os-keychain' && (
179
- <div className="flex items-center gap-2 rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-2 text-xs text-amber-400">
180
- <AlertTriangle className="size-3.5 shrink-0" />
181
- 系统钥匙串不可用,Key 会用 AES-256 在本地加密。安装 gnome-keyring 可获得系统级保护。
182
- </div>
183
- )}
184
-
185
- <form onSubmit={(e) => void handleSubmit(e)} className="space-y-4">
186
- {/* Name */}
187
- <div className="space-y-1.5">
188
- <Label htmlFor="apikey-name" className="text-xs">
189
- 名称
190
- </Label>
191
- <Input
192
- id="apikey-name"
193
- value={name}
194
- onChange={(e) => setName(e.target.value)}
195
- placeholder="e.g. OpenAI Production"
196
- className="h-8 text-sm"
197
- autoFocus
198
- />
199
- </div>
200
-
201
- {/* Env var name */}
202
- <div className="space-y-1.5">
203
- <Label htmlFor="apikey-envvar" className="text-xs">
204
- 环境变量名
205
- </Label>
206
- <Input
207
- id="apikey-envvar"
208
- value={envVarName}
209
- onChange={(e) => {
210
- setEnvVarName(e.target.value);
211
- validateEnvVar(e.target.value);
212
- }}
213
- placeholder="e.g. OPENAI_API_KEY"
214
- className={`h-8 font-mono text-sm ${envVarError ? 'border-red-500/50' : ''}`}
215
- />
216
- {envVarError && <p className="text-xs text-red-400">{envVarError}</p>}
217
- </div>
218
-
219
- {/* Value */}
220
- <div className="space-y-1.5">
221
- <Label htmlFor="apikey-value" className="text-xs">
222
-
223
- </Label>
224
- <Input
225
- id="apikey-value"
226
- type="password"
227
- value={value}
228
- onChange={(e) => setValue(e.target.value)}
229
- placeholder={isEdit ? '重新输入 Key 值' : 'sk-...'}
230
- className="h-8 text-sm"
231
- />
232
- </div>
233
-
234
- {/* Scope */}
235
- <div className="space-y-1.5">
236
- <Label className="text-xs">作用域</Label>
237
- <Select value={scope} onValueChange={(v) => setScope(v as Scope)}>
238
- <SelectTrigger className="h-8 text-sm">
239
- <SelectValue />
240
- </SelectTrigger>
241
- <SelectContent>
242
- {SCOPE_OPTIONS.map((opt) => (
243
- <SelectItem
244
- key={opt.value}
245
- value={opt.value}
246
- disabled={opt.value === 'project' && !canUseProjectScope}
247
- >
248
- {opt.value === 'project'
249
- ? effectiveProjectPath
250
- ? `项目:${effectiveProjectLabel}`
251
- : '当前无可用项目'
252
- : opt.label}
253
- </SelectItem>
254
- ))}
255
- </SelectContent>
256
- </Select>
257
- {scope === 'project' && effectiveProjectPath && (
258
- <p className="text-xs text-text-muted">绑定到 {effectiveProjectPath}</p>
259
- )}
260
- </div>
261
-
262
- {/* Error display */}
263
- {error && (
264
- <div className="rounded-md border border-red-500/30 bg-red-500/5 px-3 py-2 text-xs text-red-400">
265
- {error}
266
- </div>
267
- )}
268
-
269
- {/* Actions */}
270
- <div className="flex justify-end gap-2 pt-2">
271
- <Button type="button" variant="ghost" size="sm" onClick={onClose}>
272
- 取消
273
- </Button>
274
- <Button type="submit" size="sm" disabled={!canSubmit}>
275
- {apiKeySaving ? '保存中...' : isEdit ? '更新' : '保存'}
276
- </Button>
277
- </div>
278
- </form>
279
- </DialogContent>
280
- </Dialog>
281
- );
282
- };
@@ -1,280 +0,0 @@
1
- /**
2
- * ApiKeysPanel — grid of saved API keys with add button and empty state.
3
- */
4
-
5
- import { useEffect, useMemo, useState } from 'react';
6
-
7
- // Stubs for removed codex-account feature
8
- function useCodexAccountSnapshot(_opts: { enabled: boolean }) {
9
- return { snapshot: null, loading: false };
10
- }
11
- function mergeCodexProviderStatusWithSnapshot<T>(provider: T, _snapshot: unknown): T {
12
- return provider;
13
- }
14
-
15
- import { Button } from '@renderer/components/ui/button';
16
- import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
17
- import { useStore } from '@renderer/store';
18
- import { createLoadingMultimodelCliStatus } from '@renderer/store/slices/cliInstallerSlice';
19
- import { AlertTriangle, Info, Key, Plus } from 'lucide-react';
20
- import { useShallow } from 'zustand/react/shallow';
21
-
22
- import { ApiKeyCard } from './ApiKeyCard';
23
- import { ApiKeyFormDialog } from './ApiKeyFormDialog';
24
-
25
- import type { ApiKeyEntry } from '@shared/types/extensions';
26
-
27
- interface ApiKeysPanelProps {
28
- projectPath: string | null;
29
- projectLabel: string | null;
30
- }
31
-
32
- export const ApiKeysPanel = ({
33
- projectPath,
34
- projectLabel,
35
- }: ApiKeysPanelProps): React.JSX.Element => {
36
- const {
37
- apiKeys,
38
- apiKeysLoading,
39
- apiKeysError,
40
- storageStatus,
41
- fetchStorageStatus,
42
- cliStatus,
43
- cliStatusLoading,
44
- appConfig,
45
- } = useStore(
46
- useShallow((s) => ({
47
- apiKeys: s.apiKeys,
48
- apiKeysLoading: s.apiKeysLoading,
49
- apiKeysError: s.apiKeysError,
50
- storageStatus: s.apiKeyStorageStatus,
51
- fetchStorageStatus: s.fetchApiKeyStorageStatus,
52
- cliStatus: s.cliStatus,
53
- cliStatusLoading: s.cliStatusLoading,
54
- appConfig: s.appConfig,
55
- }))
56
- );
57
- const multimodelEnabled = appConfig?.general?.multimodelEnabled ?? false;
58
- const loadingCliStatus = useMemo(
59
- () =>
60
- !cliStatus && cliStatusLoading && multimodelEnabled
61
- ? createLoadingMultimodelCliStatus()
62
- : cliStatus,
63
- [cliStatus, cliStatusLoading, multimodelEnabled]
64
- );
65
- const codexAccount = useCodexAccountSnapshot({
66
- enabled:
67
- loadingCliStatus?.flavor === 'agent_teams_orchestrator' &&
68
- Boolean(loadingCliStatus?.providers.some((provider) => provider.providerId === 'codex')),
69
- });
70
- const effectiveCliStatus = useMemo(
71
- () =>
72
- loadingCliStatus
73
- ? {
74
- ...loadingCliStatus,
75
- providers: loadingCliStatus.providers.map((provider) =>
76
- provider.providerId === 'codex'
77
- ? mergeCodexProviderStatusWithSnapshot(provider, codexAccount.snapshot)
78
- : provider
79
- ),
80
- }
81
- : loadingCliStatus,
82
- [loadingCliStatus, codexAccount.snapshot]
83
- );
84
-
85
- const [dialogOpen, setDialogOpen] = useState(false);
86
- const [editingKey, setEditingKey] = useState<ApiKeyEntry | null>(null);
87
-
88
- useEffect(() => {
89
- void fetchStorageStatus();
90
- }, [fetchStorageStatus]);
91
-
92
- const handleAdd = () => {
93
- setEditingKey(null);
94
- setDialogOpen(true);
95
- };
96
-
97
- const handleEdit = (key: ApiKeyEntry) => {
98
- setEditingKey(key);
99
- setDialogOpen(true);
100
- };
101
-
102
- const handleDialogClose = () => {
103
- setDialogOpen(false);
104
- setEditingKey(null);
105
- };
106
-
107
- const isOsKeychain = storageStatus?.encryptionMethod === 'os-keychain';
108
- const providerKeyCards = useMemo(() => {
109
- if (!effectiveCliStatus?.providers?.length) {
110
- return [];
111
- }
112
-
113
- return (
114
- [
115
- {
116
- providerId: 'anthropic',
117
- label: 'Anthropic runtime',
118
- envVar: 'ANTHROPIC_API_KEY',
119
- },
120
- {
121
- providerId: 'codex',
122
- label: 'Codex runtime',
123
- envVar: 'OPENAI_API_KEY',
124
- },
125
- ] as const
126
- ).flatMap((item) => {
127
- const provider = effectiveCliStatus.providers.find(
128
- (entry) => entry.providerId === item.providerId
129
- );
130
- if (!provider) {
131
- return [];
132
- }
133
-
134
- return [
135
- {
136
- ...item,
137
- authenticated: provider.authenticated,
138
- apiKeyConfigured: provider.connection?.apiKeyConfigured ?? false,
139
- sourceLabel: provider.connection?.apiKeySourceLabel ?? null,
140
- statusMessage: provider.statusMessage ?? null,
141
- },
142
- ];
143
- });
144
- }, [effectiveCliStatus]);
145
-
146
- return (
147
- <div className="flex flex-col gap-4">
148
- {providerKeyCards.length > 0 && (
149
- <div className="grid grid-cols-1 gap-3 lg:grid-cols-2">
150
- {providerKeyCards.map((provider) => (
151
- <div
152
- key={provider.providerId}
153
- className="bg-surface-raised/30 rounded-lg border border-border p-4"
154
- >
155
- <div className="flex items-start justify-between gap-3">
156
- <div>
157
- <p className="text-sm font-medium text-text">{provider.label}</p>
158
- <p className="mt-0.5 font-mono text-[11px] text-text-muted">{provider.envVar}</p>
159
- </div>
160
- <span
161
- className={`rounded-full px-2 py-0.5 text-[11px] ${
162
- provider.authenticated
163
- ? 'bg-emerald-500/10 text-emerald-300'
164
- : provider.apiKeyConfigured
165
- ? 'bg-blue-500/10 text-blue-300'
166
- : 'bg-amber-500/10 text-amber-300'
167
- }`}
168
- >
169
- {provider.authenticated
170
- ? '已连接'
171
- : provider.apiKeyConfigured
172
- ? '已配置密钥'
173
- : '缺少密钥'}
174
- </span>
175
- </div>
176
- <p className="mt-2 text-xs text-text-muted">
177
- {provider.sourceLabel
178
- ? `当前来源:${provider.sourceLabel}。`
179
- : '未检测到此提供商的已保存密钥或环境变量密钥。'}
180
- {provider.statusMessage ? ` ${provider.statusMessage}` : ''}
181
- </p>
182
- </div>
183
- ))}
184
- </div>
185
- )}
186
- {/* Header row */}
187
- <div className="flex items-center justify-between">
188
- <p className="flex items-center gap-1.5 text-sm text-text-secondary">
189
- 安全保存 API 密钥,用于安装 MCP 服务器时自动填充。
190
- {storageStatus && (
191
- <Tooltip>
192
- <TooltipTrigger asChild>
193
- {isOsKeychain ? (
194
- <Info className="size-3.5 shrink-0 cursor-help text-text-muted" />
195
- ) : (
196
- <AlertTriangle className="size-3.5 shrink-0 cursor-help text-amber-400" />
197
- )}
198
- </TooltipTrigger>
199
- <TooltipContent side="bottom" className="max-w-xs">
200
- {isOsKeychain ? (
201
- <p>
202
- 密钥通过 {storageStatus.backend} 加密,并以受限文件权限保存(仅所有者可读写)。
203
- </p>
204
- ) : (
205
- <p>
206
- 系统钥匙串不可用,密钥会使用 AES-256 在本地加密。若需要更强保护,请安装 keyring
207
- 服务(gnome-keyring、kwallet)。
208
- </p>
209
- )}
210
- </TooltipContent>
211
- </Tooltip>
212
- )}
213
- </p>
214
- <Button variant="outline" size="sm" onClick={handleAdd} className="gap-1.5">
215
- <Plus className="size-3.5" />
216
- 添加 API 密钥
217
- </Button>
218
- </div>
219
-
220
- {/* Error */}
221
- {apiKeysError && (
222
- <div className="rounded-md border border-red-500/30 bg-red-500/5 p-4 text-sm text-red-400">
223
- {apiKeysError}
224
- </div>
225
- )}
226
-
227
- {/* Skeleton loading */}
228
- {apiKeysLoading && apiKeys.length === 0 && (
229
- <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
230
- {Array.from({ length: 3 }, (_, i) => (
231
- <div
232
- key={i}
233
- className="skeleton-card flex flex-col gap-2 rounded-lg border border-border p-4"
234
- style={{ animationDelay: `${i * 80}ms` }}
235
- >
236
- <div className="h-4 w-32 rounded bg-surface-raised" />
237
- <div className="h-3 w-24 rounded bg-surface-raised" />
238
- <div className="h-3 w-40 rounded bg-surface-raised" />
239
- </div>
240
- ))}
241
- </div>
242
- )}
243
-
244
- {/* Empty state */}
245
- {!apiKeysLoading && apiKeys.length === 0 && (
246
- <div className="flex flex-col items-center gap-3 rounded-sm border border-dashed border-border px-8 py-16">
247
- <div className="flex size-10 items-center justify-center rounded-lg border border-border bg-surface-raised">
248
- <Key className="size-5 text-text-muted" />
249
- </div>
250
- <p className="text-sm text-text-secondary">尚未保存 API 密钥</p>
251
- <p className="text-xs text-text-muted">
252
- 添加密钥后,安装 MCP 服务器时可自动填充环境变量。
253
- </p>
254
- <Button variant="outline" size="sm" onClick={handleAdd} className="mt-2 gap-1.5">
255
- <Plus className="size-3.5" />
256
- 添加第一个密钥
257
- </Button>
258
- </div>
259
- )}
260
-
261
- {/* Key cards grid */}
262
- {apiKeys.length > 0 && (
263
- <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
264
- {apiKeys.map((key) => (
265
- <ApiKeyCard key={key.id} apiKey={key} onEdit={handleEdit} />
266
- ))}
267
- </div>
268
- )}
269
-
270
- {/* Form dialog */}
271
- <ApiKeyFormDialog
272
- open={dialogOpen}
273
- editingKey={editingKey}
274
- currentProjectPath={projectPath}
275
- currentProjectLabel={projectLabel}
276
- onClose={handleDialogClose}
277
- />
278
- </div>
279
- );
280
- };