create-ekka-desktop-app 0.2.2
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 +137 -0
- package/bin/cli.js +72 -0
- package/package.json +23 -0
- package/template/branding/app.json +6 -0
- package/template/branding/icon.icns +0 -0
- package/template/eslint.config.js +98 -0
- package/template/index.html +29 -0
- package/template/package.json +40 -0
- package/template/src/app/App.tsx +24 -0
- package/template/src/demo/DemoApp.tsx +260 -0
- package/template/src/demo/components/Banner.tsx +82 -0
- package/template/src/demo/components/EmptyState.tsx +61 -0
- package/template/src/demo/components/InfoPopover.tsx +171 -0
- package/template/src/demo/components/InfoTooltip.tsx +76 -0
- package/template/src/demo/components/LearnMore.tsx +98 -0
- package/template/src/demo/components/NodeCredentialsOnboarding.tsx +219 -0
- package/template/src/demo/components/SetupWizard.tsx +48 -0
- package/template/src/demo/components/StatusBadge.tsx +83 -0
- package/template/src/demo/components/index.ts +10 -0
- package/template/src/demo/hooks/index.ts +6 -0
- package/template/src/demo/hooks/useAuditEvents.ts +30 -0
- package/template/src/demo/layout/Shell.tsx +110 -0
- package/template/src/demo/layout/Sidebar.tsx +192 -0
- package/template/src/demo/pages/AuditLogPage.tsx +235 -0
- package/template/src/demo/pages/DocGenPage.tsx +874 -0
- package/template/src/demo/pages/HomeSetupPage.tsx +182 -0
- package/template/src/demo/pages/LoginPage.tsx +192 -0
- package/template/src/demo/pages/PathPermissionsPage.tsx +873 -0
- package/template/src/demo/pages/RunnerPage.tsx +445 -0
- package/template/src/demo/pages/SystemPage.tsx +557 -0
- package/template/src/demo/pages/VaultPage.tsx +805 -0
- package/template/src/ekka/__tests__/demo-backend.test.ts +187 -0
- package/template/src/ekka/audit/index.ts +7 -0
- package/template/src/ekka/audit/store.ts +68 -0
- package/template/src/ekka/audit/types.ts +22 -0
- package/template/src/ekka/auth/client.ts +212 -0
- package/template/src/ekka/auth/index.ts +30 -0
- package/template/src/ekka/auth/storage.ts +114 -0
- package/template/src/ekka/auth/types.ts +67 -0
- package/template/src/ekka/backend/demo.ts +151 -0
- package/template/src/ekka/backend/interface.ts +36 -0
- package/template/src/ekka/config.ts +48 -0
- package/template/src/ekka/constants.ts +143 -0
- package/template/src/ekka/errors.ts +54 -0
- package/template/src/ekka/index.ts +516 -0
- package/template/src/ekka/internal/backend.ts +156 -0
- package/template/src/ekka/internal/index.ts +7 -0
- package/template/src/ekka/ops/auth.ts +29 -0
- package/template/src/ekka/ops/debug.ts +68 -0
- package/template/src/ekka/ops/home.ts +101 -0
- package/template/src/ekka/ops/index.ts +16 -0
- package/template/src/ekka/ops/nodeCredentials.ts +131 -0
- package/template/src/ekka/ops/nodeSession.ts +145 -0
- package/template/src/ekka/ops/paths.ts +183 -0
- package/template/src/ekka/ops/runner.ts +86 -0
- package/template/src/ekka/ops/runtime.ts +31 -0
- package/template/src/ekka/ops/setup.ts +47 -0
- package/template/src/ekka/ops/vault.ts +459 -0
- package/template/src/ekka/ops/workflowRuns.ts +116 -0
- package/template/src/ekka/types.ts +82 -0
- package/template/src/ekka/utils/idempotency.ts +14 -0
- package/template/src/ekka/utils/index.ts +7 -0
- package/template/src/ekka/utils/time.ts +77 -0
- package/template/src/main.tsx +12 -0
- package/template/src/vite-env.d.ts +12 -0
- package/template/src-tauri/Cargo.toml +41 -0
- package/template/src-tauri/build.rs +3 -0
- package/template/src-tauri/capabilities/default.json +11 -0
- package/template/src-tauri/icons/icon.icns +0 -0
- package/template/src-tauri/icons/icon.png +0 -0
- package/template/src-tauri/resources/ekka-engine-bootstrap +0 -0
- package/template/src-tauri/src/bootstrap.rs +37 -0
- package/template/src-tauri/src/commands.rs +1215 -0
- package/template/src-tauri/src/device_secret.rs +111 -0
- package/template/src-tauri/src/engine_process.rs +538 -0
- package/template/src-tauri/src/grants.rs +129 -0
- package/template/src-tauri/src/handlers/home.rs +65 -0
- package/template/src-tauri/src/handlers/mod.rs +7 -0
- package/template/src-tauri/src/handlers/paths.rs +128 -0
- package/template/src-tauri/src/handlers/vault.rs +680 -0
- package/template/src-tauri/src/main.rs +243 -0
- package/template/src-tauri/src/node_auth.rs +858 -0
- package/template/src-tauri/src/node_credentials.rs +541 -0
- package/template/src-tauri/src/node_runner.rs +882 -0
- package/template/src-tauri/src/node_vault_crypto.rs +113 -0
- package/template/src-tauri/src/node_vault_store.rs +267 -0
- package/template/src-tauri/src/ops/auth.rs +50 -0
- package/template/src-tauri/src/ops/home.rs +251 -0
- package/template/src-tauri/src/ops/mod.rs +7 -0
- package/template/src-tauri/src/ops/runtime.rs +21 -0
- package/template/src-tauri/src/state.rs +639 -0
- package/template/src-tauri/src/types.rs +84 -0
- package/template/src-tauri/tauri.conf.json +41 -0
- package/template/tsconfig.json +26 -0
- package/template/tsconfig.tsbuildinfo +1 -0
- package/template/vite.config.ts +34 -0
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation Generation Page
|
|
3
|
+
*
|
|
4
|
+
* Allows users to select a source folder and generate documentation
|
|
5
|
+
* using the ekka-docgen-basic prompt via wf_prompt_run workflow.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useEffect, useRef, type CSSProperties, type ReactElement } from 'react';
|
|
9
|
+
import {
|
|
10
|
+
createWorkflowRun,
|
|
11
|
+
getWorkflowRun,
|
|
12
|
+
type WorkflowRun,
|
|
13
|
+
type DebugBundleInfo,
|
|
14
|
+
} from '../../ekka/ops/workflowRuns';
|
|
15
|
+
import * as debugOps from '../../ekka/ops/debug';
|
|
16
|
+
|
|
17
|
+
interface DocGenPageProps {
|
|
18
|
+
darkMode: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type GenerationStatus = 'idle' | 'queued' | 'running' | 'completed' | 'failed';
|
|
22
|
+
|
|
23
|
+
// Hardcoded prompt configuration - NO prompt selection UI
|
|
24
|
+
const PROMPT_CONFIG = {
|
|
25
|
+
provider: 'opik',
|
|
26
|
+
prompt_slug: 'ekka-docgen-basic',
|
|
27
|
+
prompt_version: '1',
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export function DocGenPage({ darkMode }: DocGenPageProps): ReactElement {
|
|
31
|
+
const [selectedFolder, setSelectedFolder] = useState<string | null>(null);
|
|
32
|
+
const [status, setStatus] = useState<GenerationStatus>('idle');
|
|
33
|
+
const [workflowRunId, setWorkflowRunId] = useState<string | null>(null);
|
|
34
|
+
const [workflowRun, setWorkflowRun] = useState<WorkflowRun | null>(null);
|
|
35
|
+
const [error, setError] = useState<string | null>(null);
|
|
36
|
+
const [copySuccess, setCopySuccess] = useState(false);
|
|
37
|
+
const [isDevMode, setIsDevMode] = useState(false);
|
|
38
|
+
const [pathCopySuccess, setPathCopySuccess] = useState(false);
|
|
39
|
+
const pollingRef = useRef<number | null>(null);
|
|
40
|
+
|
|
41
|
+
const colors = {
|
|
42
|
+
text: darkMode ? '#ffffff' : '#1d1d1f',
|
|
43
|
+
textMuted: darkMode ? '#98989d' : '#6e6e73',
|
|
44
|
+
textDim: darkMode ? '#636366' : '#aeaeb2',
|
|
45
|
+
bg: darkMode ? '#2c2c2e' : '#fafafa',
|
|
46
|
+
bgAlt: darkMode ? '#1c1c1e' : '#ffffff',
|
|
47
|
+
bgInput: darkMode ? '#3a3a3c' : '#ffffff',
|
|
48
|
+
border: darkMode ? '#3a3a3c' : '#e5e5e5',
|
|
49
|
+
accent: darkMode ? '#0a84ff' : '#007aff',
|
|
50
|
+
green: darkMode ? '#30d158' : '#34c759',
|
|
51
|
+
orange: darkMode ? '#ff9f0a' : '#ff9500',
|
|
52
|
+
red: darkMode ? '#ff453a' : '#ff3b30',
|
|
53
|
+
purple: darkMode ? '#bf5af2' : '#af52de',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const styles: Record<string, CSSProperties> = {
|
|
57
|
+
container: {
|
|
58
|
+
width: '100%',
|
|
59
|
+
maxWidth: '900px',
|
|
60
|
+
},
|
|
61
|
+
header: {
|
|
62
|
+
marginBottom: '32px',
|
|
63
|
+
},
|
|
64
|
+
title: {
|
|
65
|
+
fontSize: '28px',
|
|
66
|
+
fontWeight: 700,
|
|
67
|
+
color: colors.text,
|
|
68
|
+
marginBottom: '8px',
|
|
69
|
+
letterSpacing: '-0.02em',
|
|
70
|
+
},
|
|
71
|
+
subtitle: {
|
|
72
|
+
fontSize: '14px',
|
|
73
|
+
color: colors.textMuted,
|
|
74
|
+
lineHeight: 1.6,
|
|
75
|
+
maxWidth: '600px',
|
|
76
|
+
},
|
|
77
|
+
section: {
|
|
78
|
+
marginBottom: '28px',
|
|
79
|
+
},
|
|
80
|
+
sectionHeader: {
|
|
81
|
+
display: 'flex',
|
|
82
|
+
alignItems: 'center',
|
|
83
|
+
gap: '8px',
|
|
84
|
+
marginBottom: '12px',
|
|
85
|
+
},
|
|
86
|
+
sectionTitle: {
|
|
87
|
+
fontSize: '11px',
|
|
88
|
+
fontWeight: 600,
|
|
89
|
+
color: colors.textMuted,
|
|
90
|
+
textTransform: 'uppercase' as const,
|
|
91
|
+
letterSpacing: '0.05em',
|
|
92
|
+
},
|
|
93
|
+
sectionLine: {
|
|
94
|
+
flex: 1,
|
|
95
|
+
height: '1px',
|
|
96
|
+
background: colors.border,
|
|
97
|
+
},
|
|
98
|
+
card: {
|
|
99
|
+
background: colors.bg,
|
|
100
|
+
border: `1px solid ${colors.border}`,
|
|
101
|
+
borderRadius: '12px',
|
|
102
|
+
padding: '20px',
|
|
103
|
+
},
|
|
104
|
+
folderSelector: {
|
|
105
|
+
display: 'flex',
|
|
106
|
+
gap: '12px',
|
|
107
|
+
alignItems: 'center',
|
|
108
|
+
},
|
|
109
|
+
selectedFolderBox: {
|
|
110
|
+
flex: 1,
|
|
111
|
+
padding: '12px 16px',
|
|
112
|
+
background: colors.bgInput,
|
|
113
|
+
border: `1px solid ${colors.border}`,
|
|
114
|
+
borderRadius: '8px',
|
|
115
|
+
fontSize: '13px',
|
|
116
|
+
fontFamily: 'SF Mono, Monaco, Consolas, monospace',
|
|
117
|
+
color: colors.text,
|
|
118
|
+
overflow: 'hidden',
|
|
119
|
+
textOverflow: 'ellipsis',
|
|
120
|
+
whiteSpace: 'nowrap' as const,
|
|
121
|
+
},
|
|
122
|
+
placeholderText: {
|
|
123
|
+
color: colors.textMuted,
|
|
124
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif',
|
|
125
|
+
},
|
|
126
|
+
button: {
|
|
127
|
+
padding: '10px 20px',
|
|
128
|
+
fontSize: '13px',
|
|
129
|
+
fontWeight: 600,
|
|
130
|
+
color: '#ffffff',
|
|
131
|
+
background: colors.accent,
|
|
132
|
+
border: 'none',
|
|
133
|
+
borderRadius: '8px',
|
|
134
|
+
cursor: 'pointer',
|
|
135
|
+
transition: 'opacity 0.15s ease',
|
|
136
|
+
whiteSpace: 'nowrap' as const,
|
|
137
|
+
},
|
|
138
|
+
buttonSecondary: {
|
|
139
|
+
padding: '10px 20px',
|
|
140
|
+
fontSize: '13px',
|
|
141
|
+
fontWeight: 600,
|
|
142
|
+
color: colors.accent,
|
|
143
|
+
background: darkMode ? 'rgba(10, 132, 255, 0.15)' : 'rgba(0, 122, 255, 0.1)',
|
|
144
|
+
border: 'none',
|
|
145
|
+
borderRadius: '8px',
|
|
146
|
+
cursor: 'pointer',
|
|
147
|
+
transition: 'opacity 0.15s ease',
|
|
148
|
+
whiteSpace: 'nowrap' as const,
|
|
149
|
+
},
|
|
150
|
+
buttonDisabled: {
|
|
151
|
+
opacity: 0.5,
|
|
152
|
+
cursor: 'not-allowed',
|
|
153
|
+
},
|
|
154
|
+
error: {
|
|
155
|
+
marginBottom: '20px',
|
|
156
|
+
padding: '12px 14px',
|
|
157
|
+
background: darkMode ? '#3c1618' : '#fef2f2',
|
|
158
|
+
border: `1px solid ${darkMode ? '#7f1d1d' : '#fecaca'}`,
|
|
159
|
+
borderRadius: '8px',
|
|
160
|
+
fontSize: '13px',
|
|
161
|
+
color: darkMode ? '#fca5a5' : '#991b1b',
|
|
162
|
+
},
|
|
163
|
+
progressCard: {
|
|
164
|
+
padding: '20px',
|
|
165
|
+
background: colors.bg,
|
|
166
|
+
border: `1px solid ${colors.border}`,
|
|
167
|
+
borderRadius: '12px',
|
|
168
|
+
},
|
|
169
|
+
progressHeader: {
|
|
170
|
+
display: 'flex',
|
|
171
|
+
alignItems: 'center',
|
|
172
|
+
gap: '12px',
|
|
173
|
+
marginBottom: '16px',
|
|
174
|
+
},
|
|
175
|
+
progressSpinner: {
|
|
176
|
+
width: '20px',
|
|
177
|
+
height: '20px',
|
|
178
|
+
border: `2px solid ${colors.border}`,
|
|
179
|
+
borderTopColor: colors.accent,
|
|
180
|
+
borderRadius: '50%',
|
|
181
|
+
animation: 'spin 1s linear infinite',
|
|
182
|
+
},
|
|
183
|
+
progressTitle: {
|
|
184
|
+
fontSize: '15px',
|
|
185
|
+
fontWeight: 600,
|
|
186
|
+
color: colors.text,
|
|
187
|
+
},
|
|
188
|
+
progressSteps: {
|
|
189
|
+
display: 'flex',
|
|
190
|
+
gap: '8px',
|
|
191
|
+
flexWrap: 'wrap' as const,
|
|
192
|
+
},
|
|
193
|
+
progressStep: {
|
|
194
|
+
display: 'flex',
|
|
195
|
+
alignItems: 'center',
|
|
196
|
+
gap: '6px',
|
|
197
|
+
padding: '6px 12px',
|
|
198
|
+
borderRadius: '6px',
|
|
199
|
+
fontSize: '12px',
|
|
200
|
+
fontWeight: 500,
|
|
201
|
+
},
|
|
202
|
+
stepActive: {
|
|
203
|
+
background: darkMode ? 'rgba(10, 132, 255, 0.15)' : 'rgba(0, 122, 255, 0.1)',
|
|
204
|
+
color: colors.accent,
|
|
205
|
+
},
|
|
206
|
+
stepComplete: {
|
|
207
|
+
background: darkMode ? 'rgba(48, 209, 88, 0.15)' : 'rgba(52, 199, 89, 0.1)',
|
|
208
|
+
color: colors.green,
|
|
209
|
+
},
|
|
210
|
+
stepPending: {
|
|
211
|
+
background: darkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.03)',
|
|
212
|
+
color: colors.textMuted,
|
|
213
|
+
},
|
|
214
|
+
stepFailed: {
|
|
215
|
+
background: darkMode ? 'rgba(255, 69, 58, 0.15)' : 'rgba(255, 59, 48, 0.1)',
|
|
216
|
+
color: colors.red,
|
|
217
|
+
},
|
|
218
|
+
outputCard: {
|
|
219
|
+
background: colors.bg,
|
|
220
|
+
border: `1px solid ${colors.border}`,
|
|
221
|
+
borderRadius: '12px',
|
|
222
|
+
overflow: 'hidden',
|
|
223
|
+
},
|
|
224
|
+
outputHeader: {
|
|
225
|
+
display: 'flex',
|
|
226
|
+
alignItems: 'center',
|
|
227
|
+
justifyContent: 'space-between',
|
|
228
|
+
padding: '12px 16px',
|
|
229
|
+
borderBottom: `1px solid ${colors.border}`,
|
|
230
|
+
background: darkMode ? 'rgba(255, 255, 255, 0.02)' : 'rgba(0, 0, 0, 0.02)',
|
|
231
|
+
},
|
|
232
|
+
outputMeta: {
|
|
233
|
+
display: 'flex',
|
|
234
|
+
gap: '16px',
|
|
235
|
+
fontSize: '12px',
|
|
236
|
+
color: colors.textMuted,
|
|
237
|
+
},
|
|
238
|
+
outputMetaItem: {
|
|
239
|
+
display: 'flex',
|
|
240
|
+
alignItems: 'center',
|
|
241
|
+
gap: '4px',
|
|
242
|
+
},
|
|
243
|
+
outputContent: {
|
|
244
|
+
padding: '20px',
|
|
245
|
+
maxHeight: '500px',
|
|
246
|
+
overflowY: 'auto' as const,
|
|
247
|
+
fontSize: '14px',
|
|
248
|
+
lineHeight: 1.7,
|
|
249
|
+
color: colors.text,
|
|
250
|
+
whiteSpace: 'pre-wrap' as const,
|
|
251
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif',
|
|
252
|
+
},
|
|
253
|
+
badge: {
|
|
254
|
+
display: 'inline-flex',
|
|
255
|
+
alignItems: 'center',
|
|
256
|
+
gap: '4px',
|
|
257
|
+
padding: '4px 8px',
|
|
258
|
+
borderRadius: '4px',
|
|
259
|
+
fontSize: '11px',
|
|
260
|
+
fontWeight: 600,
|
|
261
|
+
},
|
|
262
|
+
badgeGreen: {
|
|
263
|
+
background: darkMode ? 'rgba(48, 209, 88, 0.15)' : 'rgba(52, 199, 89, 0.12)',
|
|
264
|
+
color: colors.green,
|
|
265
|
+
},
|
|
266
|
+
badgeRed: {
|
|
267
|
+
background: darkMode ? 'rgba(255, 69, 58, 0.15)' : 'rgba(255, 59, 48, 0.12)',
|
|
268
|
+
color: colors.red,
|
|
269
|
+
},
|
|
270
|
+
copyButton: {
|
|
271
|
+
padding: '6px 12px',
|
|
272
|
+
fontSize: '12px',
|
|
273
|
+
fontWeight: 500,
|
|
274
|
+
color: colors.accent,
|
|
275
|
+
background: 'transparent',
|
|
276
|
+
border: `1px solid ${colors.border}`,
|
|
277
|
+
borderRadius: '6px',
|
|
278
|
+
cursor: 'pointer',
|
|
279
|
+
display: 'flex',
|
|
280
|
+
alignItems: 'center',
|
|
281
|
+
gap: '4px',
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Check dev mode on mount
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
debugOps.isDevMode().then(setIsDevMode).catch(() => setIsDevMode(false));
|
|
288
|
+
}, []);
|
|
289
|
+
|
|
290
|
+
// Cleanup polling on unmount
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
return () => {
|
|
293
|
+
if (pollingRef.current) {
|
|
294
|
+
clearInterval(pollingRef.current);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}, []);
|
|
298
|
+
|
|
299
|
+
// Handle folder selection
|
|
300
|
+
const handleSelectFolder = async () => {
|
|
301
|
+
setError(null);
|
|
302
|
+
try {
|
|
303
|
+
const { open } = await import('@tauri-apps/plugin-dialog');
|
|
304
|
+
const selected = await open({ directory: true, multiple: false });
|
|
305
|
+
if (selected && typeof selected === 'string') {
|
|
306
|
+
setSelectedFolder(selected);
|
|
307
|
+
// Reset state when new folder selected
|
|
308
|
+
setStatus('idle');
|
|
309
|
+
setWorkflowRunId(null);
|
|
310
|
+
setWorkflowRun(null);
|
|
311
|
+
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
314
|
+
setError(`Failed to open folder picker: ${message}`);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// Start generation
|
|
319
|
+
const handleGenerate = async () => {
|
|
320
|
+
if (!selectedFolder) return;
|
|
321
|
+
|
|
322
|
+
// Stop any existing polling
|
|
323
|
+
if (pollingRef.current) {
|
|
324
|
+
clearInterval(pollingRef.current);
|
|
325
|
+
pollingRef.current = null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
setError(null);
|
|
329
|
+
setStatus('queued');
|
|
330
|
+
setWorkflowRun(null);
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
// Create workflow run - DO NOT log the input
|
|
334
|
+
// JWT is handled internally by the ops layer
|
|
335
|
+
const response = await createWorkflowRun({
|
|
336
|
+
type: 'wf_prompt_run',
|
|
337
|
+
confidentiality: 'confidential',
|
|
338
|
+
context: {
|
|
339
|
+
prompt_ref: PROMPT_CONFIG,
|
|
340
|
+
variables: { input: selectedFolder },
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
setWorkflowRunId(response.id);
|
|
345
|
+
|
|
346
|
+
// Start polling
|
|
347
|
+
startPolling(response.id);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
const message = err instanceof Error ? err.message : 'Failed to start generation';
|
|
350
|
+
setError(message);
|
|
351
|
+
setStatus('failed');
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Poll for status updates
|
|
356
|
+
const startPolling = (id: string) => {
|
|
357
|
+
const poll = async () => {
|
|
358
|
+
try {
|
|
359
|
+
// JWT is handled internally by the ops layer
|
|
360
|
+
const run = await getWorkflowRun(id);
|
|
361
|
+
setWorkflowRun(run);
|
|
362
|
+
|
|
363
|
+
// Update status based on workflow state
|
|
364
|
+
if (run.status === 'completed') {
|
|
365
|
+
setStatus('completed');
|
|
366
|
+
if (pollingRef.current) {
|
|
367
|
+
clearInterval(pollingRef.current);
|
|
368
|
+
pollingRef.current = null;
|
|
369
|
+
}
|
|
370
|
+
} else if (run.status === 'failed') {
|
|
371
|
+
setStatus('failed');
|
|
372
|
+
setError(run.error || 'Workflow failed');
|
|
373
|
+
if (pollingRef.current) {
|
|
374
|
+
clearInterval(pollingRef.current);
|
|
375
|
+
pollingRef.current = null;
|
|
376
|
+
}
|
|
377
|
+
} else if (run.status === 'running' || run.progress > 0) {
|
|
378
|
+
setStatus('running');
|
|
379
|
+
} else {
|
|
380
|
+
setStatus('queued');
|
|
381
|
+
}
|
|
382
|
+
} catch (err) {
|
|
383
|
+
const message = err instanceof Error ? err.message : 'Failed to fetch status';
|
|
384
|
+
setError(message);
|
|
385
|
+
setStatus('failed');
|
|
386
|
+
if (pollingRef.current) {
|
|
387
|
+
clearInterval(pollingRef.current);
|
|
388
|
+
pollingRef.current = null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// Initial poll
|
|
394
|
+
void poll();
|
|
395
|
+
|
|
396
|
+
// Poll every 1.5 seconds
|
|
397
|
+
pollingRef.current = window.setInterval(poll, 1500);
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// Copy output to clipboard
|
|
401
|
+
const handleCopyOutput = async () => {
|
|
402
|
+
if (!workflowRun?.result?.output_text) return;
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
await navigator.clipboard.writeText(workflowRun.result?.output_text || '');
|
|
406
|
+
setCopySuccess(true);
|
|
407
|
+
setTimeout(() => setCopySuccess(false), 2000);
|
|
408
|
+
} catch {
|
|
409
|
+
setError('Failed to copy to clipboard');
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const isGenerating = status === 'queued' || status === 'running';
|
|
414
|
+
const canGenerate = selectedFolder && !isGenerating;
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<div style={styles.container}>
|
|
418
|
+
{/* CSS for spinner animation */}
|
|
419
|
+
<style>
|
|
420
|
+
{`
|
|
421
|
+
@keyframes spin {
|
|
422
|
+
to { transform: rotate(360deg); }
|
|
423
|
+
}
|
|
424
|
+
`}
|
|
425
|
+
</style>
|
|
426
|
+
|
|
427
|
+
<header style={styles.header}>
|
|
428
|
+
<h1 style={styles.title}>Generate Documentation</h1>
|
|
429
|
+
<p style={styles.subtitle}>
|
|
430
|
+
Select a source folder to automatically generate documentation using AI.
|
|
431
|
+
The generated documentation will be displayed below.
|
|
432
|
+
</p>
|
|
433
|
+
</header>
|
|
434
|
+
|
|
435
|
+
{error && <div style={styles.error}>{error}</div>}
|
|
436
|
+
|
|
437
|
+
{/* Section: Folder Selection */}
|
|
438
|
+
<div style={styles.section}>
|
|
439
|
+
<div style={styles.sectionHeader}>
|
|
440
|
+
<span style={styles.sectionTitle}>Source Folder</span>
|
|
441
|
+
<div style={styles.sectionLine} />
|
|
442
|
+
</div>
|
|
443
|
+
<div style={styles.card}>
|
|
444
|
+
<div style={styles.folderSelector}>
|
|
445
|
+
<div style={styles.selectedFolderBox}>
|
|
446
|
+
{selectedFolder ? (
|
|
447
|
+
selectedFolder
|
|
448
|
+
) : (
|
|
449
|
+
<span style={styles.placeholderText}>No folder selected</span>
|
|
450
|
+
)}
|
|
451
|
+
</div>
|
|
452
|
+
<button
|
|
453
|
+
onClick={() => void handleSelectFolder()}
|
|
454
|
+
style={styles.buttonSecondary}
|
|
455
|
+
disabled={isGenerating}
|
|
456
|
+
>
|
|
457
|
+
Browse...
|
|
458
|
+
</button>
|
|
459
|
+
</div>
|
|
460
|
+
|
|
461
|
+
<div style={{ marginTop: '16px' }}>
|
|
462
|
+
<button
|
|
463
|
+
onClick={() => void handleGenerate()}
|
|
464
|
+
style={{
|
|
465
|
+
...styles.button,
|
|
466
|
+
...(!canGenerate ? styles.buttonDisabled : {}),
|
|
467
|
+
}}
|
|
468
|
+
disabled={!canGenerate}
|
|
469
|
+
>
|
|
470
|
+
{isGenerating ? 'Generating...' : 'Generate Documentation'}
|
|
471
|
+
</button>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
|
|
476
|
+
{/* Section: Progress */}
|
|
477
|
+
{(status !== 'idle' || workflowRunId) && (
|
|
478
|
+
<div style={styles.section}>
|
|
479
|
+
<div style={styles.sectionHeader}>
|
|
480
|
+
<span style={styles.sectionTitle}>Progress</span>
|
|
481
|
+
<div style={styles.sectionLine} />
|
|
482
|
+
</div>
|
|
483
|
+
<div style={styles.progressCard}>
|
|
484
|
+
<div style={styles.progressHeader}>
|
|
485
|
+
{isGenerating && <div style={styles.progressSpinner} />}
|
|
486
|
+
{status === 'completed' && <CheckIcon color={colors.green} />}
|
|
487
|
+
{status === 'failed' && <ErrorIcon color={colors.red} />}
|
|
488
|
+
<span style={styles.progressTitle}>
|
|
489
|
+
{status === 'queued' && 'Queued'}
|
|
490
|
+
{status === 'running' && 'Processing...'}
|
|
491
|
+
{status === 'completed' && 'Completed'}
|
|
492
|
+
{status === 'failed' && 'Failed'}
|
|
493
|
+
</span>
|
|
494
|
+
{workflowRunId && (
|
|
495
|
+
<span style={{ fontSize: '11px', color: colors.textMuted, fontFamily: 'monospace' }}>
|
|
496
|
+
ID: {workflowRunId.slice(0, 8)}...
|
|
497
|
+
</span>
|
|
498
|
+
)}
|
|
499
|
+
</div>
|
|
500
|
+
<div style={styles.progressSteps}>
|
|
501
|
+
<div
|
|
502
|
+
style={{
|
|
503
|
+
...styles.progressStep,
|
|
504
|
+
...(status === 'queued' ? styles.stepActive : styles.stepComplete),
|
|
505
|
+
}}
|
|
506
|
+
>
|
|
507
|
+
{status !== 'queued' && <CheckIcon color={colors.green} size={12} />}
|
|
508
|
+
Queued
|
|
509
|
+
</div>
|
|
510
|
+
<div
|
|
511
|
+
style={{
|
|
512
|
+
...styles.progressStep,
|
|
513
|
+
...(status === 'running'
|
|
514
|
+
? styles.stepActive
|
|
515
|
+
: status === 'completed' || status === 'failed'
|
|
516
|
+
? status === 'failed'
|
|
517
|
+
? styles.stepFailed
|
|
518
|
+
: styles.stepComplete
|
|
519
|
+
: styles.stepPending),
|
|
520
|
+
}}
|
|
521
|
+
>
|
|
522
|
+
{status === 'completed' && <CheckIcon color={colors.green} size={12} />}
|
|
523
|
+
{status === 'failed' && <ErrorIcon color={colors.red} size={12} />}
|
|
524
|
+
Running
|
|
525
|
+
</div>
|
|
526
|
+
<div
|
|
527
|
+
style={{
|
|
528
|
+
...styles.progressStep,
|
|
529
|
+
...(status === 'completed'
|
|
530
|
+
? styles.stepComplete
|
|
531
|
+
: status === 'failed'
|
|
532
|
+
? styles.stepFailed
|
|
533
|
+
: styles.stepPending),
|
|
534
|
+
}}
|
|
535
|
+
>
|
|
536
|
+
{status === 'completed' && <CheckIcon color={colors.green} size={12} />}
|
|
537
|
+
{status === 'failed' && <ErrorIcon color={colors.red} size={12} />}
|
|
538
|
+
{status === 'completed' ? 'Completed' : status === 'failed' ? 'Failed' : 'Complete'}
|
|
539
|
+
</div>
|
|
540
|
+
</div>
|
|
541
|
+
{workflowRun && (
|
|
542
|
+
<div style={{ marginTop: '12px', fontSize: '12px', color: colors.textMuted }}>
|
|
543
|
+
Progress: {workflowRun.progress}%
|
|
544
|
+
</div>
|
|
545
|
+
)}
|
|
546
|
+
</div>
|
|
547
|
+
</div>
|
|
548
|
+
)}
|
|
549
|
+
|
|
550
|
+
{/* Section: Output */}
|
|
551
|
+
{status === 'completed' && workflowRun?.result?.output_text && (
|
|
552
|
+
<div style={styles.section}>
|
|
553
|
+
<div style={styles.sectionHeader}>
|
|
554
|
+
<span style={styles.sectionTitle}>Generated Documentation</span>
|
|
555
|
+
<div style={styles.sectionLine} />
|
|
556
|
+
</div>
|
|
557
|
+
<div style={styles.outputCard}>
|
|
558
|
+
<div style={styles.outputHeader}>
|
|
559
|
+
<div style={styles.outputMeta}>
|
|
560
|
+
<div style={styles.outputMetaItem}>
|
|
561
|
+
<span style={{ ...styles.badge, ...styles.badgeGreen }}>Completed</span>
|
|
562
|
+
</div>
|
|
563
|
+
<div style={styles.outputMetaItem}>
|
|
564
|
+
<span style={{ color: colors.textDim }}>ID:</span>
|
|
565
|
+
<code style={{ fontFamily: 'monospace', fontSize: '11px' }}>
|
|
566
|
+
{workflowRunId}
|
|
567
|
+
</code>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
<button
|
|
571
|
+
onClick={() => void handleCopyOutput()}
|
|
572
|
+
style={styles.copyButton}
|
|
573
|
+
>
|
|
574
|
+
{copySuccess ? (
|
|
575
|
+
<>
|
|
576
|
+
<CheckIcon color={colors.green} size={14} />
|
|
577
|
+
Copied!
|
|
578
|
+
</>
|
|
579
|
+
) : (
|
|
580
|
+
<>
|
|
581
|
+
<CopyIcon />
|
|
582
|
+
Copy Output
|
|
583
|
+
</>
|
|
584
|
+
)}
|
|
585
|
+
</button>
|
|
586
|
+
</div>
|
|
587
|
+
<div style={styles.outputContent}>{workflowRun.result?.output_text}</div>
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
)}
|
|
591
|
+
|
|
592
|
+
{/* Section: Error Details */}
|
|
593
|
+
{status === 'failed' && workflowRun && (
|
|
594
|
+
<div style={styles.section}>
|
|
595
|
+
<div style={styles.sectionHeader}>
|
|
596
|
+
<span style={styles.sectionTitle}>Error Details</span>
|
|
597
|
+
<div style={styles.sectionLine} />
|
|
598
|
+
</div>
|
|
599
|
+
<div style={{ ...styles.card, borderColor: colors.red }}>
|
|
600
|
+
<div style={{ marginBottom: '8px' }}>
|
|
601
|
+
<span style={{ ...styles.badge, ...styles.badgeRed }}>
|
|
602
|
+
ERROR
|
|
603
|
+
</span>
|
|
604
|
+
</div>
|
|
605
|
+
<p style={{ fontSize: '14px', color: colors.text, margin: 0 }}>
|
|
606
|
+
{workflowRun.error || 'An unknown error occurred'}
|
|
607
|
+
</p>
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
)}
|
|
611
|
+
|
|
612
|
+
{/* Section: Debug Bundle (Dev Mode Only) */}
|
|
613
|
+
{status === 'failed' && isDevMode && workflowRun?.result?.debug_bundle && (
|
|
614
|
+
<DebugBundleSection
|
|
615
|
+
debugBundle={workflowRun.result.debug_bundle}
|
|
616
|
+
darkMode={darkMode}
|
|
617
|
+
colors={colors}
|
|
618
|
+
pathCopySuccess={pathCopySuccess}
|
|
619
|
+
onCopyPath={async () => {
|
|
620
|
+
try {
|
|
621
|
+
await navigator.clipboard.writeText(workflowRun.result?.debug_bundle?.debug_bundle_ref || '');
|
|
622
|
+
setPathCopySuccess(true);
|
|
623
|
+
setTimeout(() => setPathCopySuccess(false), 2000);
|
|
624
|
+
} catch {
|
|
625
|
+
setError('Failed to copy path to clipboard');
|
|
626
|
+
}
|
|
627
|
+
}}
|
|
628
|
+
onOpenFolder={async () => {
|
|
629
|
+
try {
|
|
630
|
+
await debugOps.openFolder(workflowRun.result?.debug_bundle?.debug_bundle_ref || '');
|
|
631
|
+
} catch (err) {
|
|
632
|
+
setError(err instanceof Error ? err.message : 'Failed to open folder');
|
|
633
|
+
}
|
|
634
|
+
}}
|
|
635
|
+
/>
|
|
636
|
+
)}
|
|
637
|
+
</div>
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// =============================================================================
|
|
642
|
+
// Debug Bundle Section (Dev Mode Only)
|
|
643
|
+
// =============================================================================
|
|
644
|
+
|
|
645
|
+
interface DebugBundleSectionProps {
|
|
646
|
+
debugBundle: DebugBundleInfo;
|
|
647
|
+
darkMode: boolean;
|
|
648
|
+
colors: Record<string, string>;
|
|
649
|
+
pathCopySuccess: boolean;
|
|
650
|
+
onCopyPath: () => void;
|
|
651
|
+
onOpenFolder: () => void;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function DebugBundleSection({
|
|
655
|
+
debugBundle,
|
|
656
|
+
darkMode,
|
|
657
|
+
colors,
|
|
658
|
+
pathCopySuccess,
|
|
659
|
+
onCopyPath,
|
|
660
|
+
onOpenFolder,
|
|
661
|
+
}: DebugBundleSectionProps): ReactElement {
|
|
662
|
+
const styles: Record<string, CSSProperties> = {
|
|
663
|
+
section: {
|
|
664
|
+
marginBottom: '28px',
|
|
665
|
+
},
|
|
666
|
+
sectionHeader: {
|
|
667
|
+
display: 'flex',
|
|
668
|
+
alignItems: 'center',
|
|
669
|
+
gap: '8px',
|
|
670
|
+
marginBottom: '12px',
|
|
671
|
+
},
|
|
672
|
+
sectionTitle: {
|
|
673
|
+
fontSize: '11px',
|
|
674
|
+
fontWeight: 600,
|
|
675
|
+
color: colors.textMuted,
|
|
676
|
+
textTransform: 'uppercase' as const,
|
|
677
|
+
letterSpacing: '0.05em',
|
|
678
|
+
},
|
|
679
|
+
sectionLine: {
|
|
680
|
+
flex: 1,
|
|
681
|
+
height: '1px',
|
|
682
|
+
background: colors.border,
|
|
683
|
+
},
|
|
684
|
+
card: {
|
|
685
|
+
background: darkMode ? '#1c1c1e' : '#fafafa',
|
|
686
|
+
border: `1px solid ${darkMode ? '#48484a' : '#d1d1d6'}`,
|
|
687
|
+
borderRadius: '12px',
|
|
688
|
+
padding: '16px',
|
|
689
|
+
},
|
|
690
|
+
devBadge: {
|
|
691
|
+
display: 'inline-flex',
|
|
692
|
+
alignItems: 'center',
|
|
693
|
+
gap: '4px',
|
|
694
|
+
padding: '3px 8px',
|
|
695
|
+
borderRadius: '4px',
|
|
696
|
+
fontSize: '10px',
|
|
697
|
+
fontWeight: 600,
|
|
698
|
+
background: darkMode ? 'rgba(191, 90, 242, 0.15)' : 'rgba(175, 82, 222, 0.12)',
|
|
699
|
+
color: darkMode ? '#bf5af2' : '#af52de',
|
|
700
|
+
marginBottom: '12px',
|
|
701
|
+
},
|
|
702
|
+
pathBox: {
|
|
703
|
+
padding: '10px 12px',
|
|
704
|
+
background: darkMode ? '#2c2c2e' : '#ffffff',
|
|
705
|
+
border: `1px solid ${darkMode ? '#3a3a3c' : '#e5e5e5'}`,
|
|
706
|
+
borderRadius: '6px',
|
|
707
|
+
fontSize: '12px',
|
|
708
|
+
fontFamily: 'SF Mono, Monaco, Consolas, monospace',
|
|
709
|
+
color: colors.text,
|
|
710
|
+
marginBottom: '12px',
|
|
711
|
+
wordBreak: 'break-all' as const,
|
|
712
|
+
},
|
|
713
|
+
row: {
|
|
714
|
+
display: 'flex',
|
|
715
|
+
gap: '12px',
|
|
716
|
+
marginBottom: '12px',
|
|
717
|
+
},
|
|
718
|
+
metaItem: {
|
|
719
|
+
fontSize: '12px',
|
|
720
|
+
color: colors.textMuted,
|
|
721
|
+
},
|
|
722
|
+
metaLabel: {
|
|
723
|
+
fontWeight: 500,
|
|
724
|
+
marginRight: '4px',
|
|
725
|
+
},
|
|
726
|
+
metaValue: {
|
|
727
|
+
fontFamily: 'SF Mono, Monaco, Consolas, monospace',
|
|
728
|
+
fontSize: '11px',
|
|
729
|
+
},
|
|
730
|
+
filesList: {
|
|
731
|
+
display: 'flex',
|
|
732
|
+
flexWrap: 'wrap' as const,
|
|
733
|
+
gap: '6px',
|
|
734
|
+
marginBottom: '16px',
|
|
735
|
+
},
|
|
736
|
+
fileTag: {
|
|
737
|
+
padding: '4px 8px',
|
|
738
|
+
background: darkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.04)',
|
|
739
|
+
borderRadius: '4px',
|
|
740
|
+
fontSize: '11px',
|
|
741
|
+
fontFamily: 'SF Mono, Monaco, Consolas, monospace',
|
|
742
|
+
color: colors.textMuted,
|
|
743
|
+
},
|
|
744
|
+
buttonRow: {
|
|
745
|
+
display: 'flex',
|
|
746
|
+
gap: '8px',
|
|
747
|
+
},
|
|
748
|
+
button: {
|
|
749
|
+
padding: '8px 14px',
|
|
750
|
+
fontSize: '12px',
|
|
751
|
+
fontWeight: 500,
|
|
752
|
+
color: colors.accent,
|
|
753
|
+
background: 'transparent',
|
|
754
|
+
border: `1px solid ${colors.border}`,
|
|
755
|
+
borderRadius: '6px',
|
|
756
|
+
cursor: 'pointer',
|
|
757
|
+
display: 'flex',
|
|
758
|
+
alignItems: 'center',
|
|
759
|
+
gap: '6px',
|
|
760
|
+
},
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
// Format bytes to human readable
|
|
764
|
+
const formatBytes = (bytes: number): string => {
|
|
765
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
766
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
767
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
return (
|
|
771
|
+
<div style={styles.section}>
|
|
772
|
+
<div style={styles.sectionHeader}>
|
|
773
|
+
<span style={styles.sectionTitle}>Debug Bundle</span>
|
|
774
|
+
<div style={styles.sectionLine} />
|
|
775
|
+
</div>
|
|
776
|
+
<div style={styles.card}>
|
|
777
|
+
<div style={styles.devBadge}>
|
|
778
|
+
<span>🔧</span>
|
|
779
|
+
DEV MODE ONLY
|
|
780
|
+
</div>
|
|
781
|
+
|
|
782
|
+
<div style={{ fontSize: '11px', color: colors.textMuted, marginBottom: '8px' }}>
|
|
783
|
+
Path (Local)
|
|
784
|
+
</div>
|
|
785
|
+
<div style={styles.pathBox}>
|
|
786
|
+
{debugBundle.debug_bundle_ref}
|
|
787
|
+
</div>
|
|
788
|
+
|
|
789
|
+
<div style={styles.row}>
|
|
790
|
+
<div style={styles.metaItem}>
|
|
791
|
+
<span style={styles.metaLabel}>Size:</span>
|
|
792
|
+
<span style={styles.metaValue}>{formatBytes(debugBundle.raw_output_len)}</span>
|
|
793
|
+
</div>
|
|
794
|
+
<div style={styles.metaItem}>
|
|
795
|
+
<span style={styles.metaLabel}>SHA256:</span>
|
|
796
|
+
<span style={styles.metaValue}>{debugBundle.raw_output_sha256.slice(0, 16)}...</span>
|
|
797
|
+
</div>
|
|
798
|
+
</div>
|
|
799
|
+
|
|
800
|
+
<div style={{ fontSize: '11px', color: colors.textMuted, marginBottom: '6px' }}>
|
|
801
|
+
Files
|
|
802
|
+
</div>
|
|
803
|
+
<div style={styles.filesList}>
|
|
804
|
+
{debugBundle.files.map((file) => (
|
|
805
|
+
<span key={file} style={styles.fileTag}>{file}</span>
|
|
806
|
+
))}
|
|
807
|
+
</div>
|
|
808
|
+
|
|
809
|
+
<div style={styles.buttonRow}>
|
|
810
|
+
<button onClick={onOpenFolder} style={styles.button}>
|
|
811
|
+
<FolderIcon />
|
|
812
|
+
Open Folder
|
|
813
|
+
</button>
|
|
814
|
+
<button onClick={onCopyPath} style={styles.button}>
|
|
815
|
+
{pathCopySuccess ? (
|
|
816
|
+
<>
|
|
817
|
+
<CheckIcon color={colors.green} size={14} />
|
|
818
|
+
Copied!
|
|
819
|
+
</>
|
|
820
|
+
) : (
|
|
821
|
+
<>
|
|
822
|
+
<CopyIcon />
|
|
823
|
+
Copy Path
|
|
824
|
+
</>
|
|
825
|
+
)}
|
|
826
|
+
</button>
|
|
827
|
+
</div>
|
|
828
|
+
|
|
829
|
+
<div style={{ marginTop: '12px', fontSize: '11px', color: colors.textDim, fontStyle: 'italic' }}>
|
|
830
|
+
Raw output is NOT displayed here. Open the folder to inspect files.
|
|
831
|
+
</div>
|
|
832
|
+
</div>
|
|
833
|
+
</div>
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// =============================================================================
|
|
838
|
+
// Icons
|
|
839
|
+
// =============================================================================
|
|
840
|
+
|
|
841
|
+
function FolderIcon(): ReactElement {
|
|
842
|
+
return (
|
|
843
|
+
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
|
|
844
|
+
<path d="M.54 3.87.5 14a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1V4.5a1 1 0 0 0-1-1H7.414A2 2 0 0 1 6 2.5L5.5 2a2 2 0 0 0-1.414-.586H1.5a1 1 0 0 0-1 1l.04 1.456z"/>
|
|
845
|
+
</svg>
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Icons
|
|
850
|
+
function CheckIcon({ color, size = 16 }: { color: string; size?: number }): ReactElement {
|
|
851
|
+
return (
|
|
852
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill={color}>
|
|
853
|
+
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
|
854
|
+
</svg>
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function ErrorIcon({ color, size = 16 }: { color: string; size?: number }): ReactElement {
|
|
859
|
+
return (
|
|
860
|
+
<svg width={size} height={size} viewBox="0 0 16 16" fill={color}>
|
|
861
|
+
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
|
|
862
|
+
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z" />
|
|
863
|
+
</svg>
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function CopyIcon(): ReactElement {
|
|
868
|
+
return (
|
|
869
|
+
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
|
|
870
|
+
<path d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H6z" />
|
|
871
|
+
<path d="M2 6a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H4z" />
|
|
872
|
+
</svg>
|
|
873
|
+
);
|
|
874
|
+
}
|