@mrpatronz/nexusflow 0.2.13 → 0.2.18
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/.agents/skills/ccc/SKILL.md +71 -0
- package/.agents/skills/ccc/references/management.md +110 -0
- package/.agents/skills/ccc/references/settings.md +126 -0
- package/.github/workflows/ci.yml +44 -0
- package/.github/workflows/dependabot-merge.yml +53 -0
- package/.github/workflows/release-desktop.yml +67 -0
- package/.github/workflows/release-npm.yml +65 -0
- package/.github/workflows/release-vscode.yml +61 -0
- package/GETTING_STARTED.md +5 -0
- package/README.md +53 -0
- package/desktop/build-installer.js +228 -0
- package/desktop/build.js +118 -0
- package/desktop/installer/nexusflow.iss +35 -0
- package/desktop/neutralino.config.json +39 -0
- package/desktop/package-lock.json +1060 -0
- package/desktop/package.json +14 -0
- package/dist/commands/adapter.d.ts +27 -0
- package/dist/commands/adapter.d.ts.map +1 -0
- package/dist/commands/adapter.js +318 -0
- package/dist/commands/adapter.js.map +1 -0
- package/dist/commands/add-repo.d.ts.map +1 -1
- package/dist/commands/add-repo.js +0 -1
- package/dist/commands/add-repo.js.map +1 -1
- package/dist/commands/commands.test.js +2 -2
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/config.d.ts +22 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +50 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +14 -17
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/desktop.d.ts +12 -0
- package/dist/commands/desktop.d.ts.map +1 -0
- package/dist/commands/desktop.js +119 -0
- package/dist/commands/desktop.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +11 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/refresh.d.ts +1 -0
- package/dist/commands/refresh.d.ts.map +1 -1
- package/dist/commands/refresh.js +2 -14
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +1 -8
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tui.d.ts +8 -0
- package/dist/commands/tui.d.ts.map +1 -0
- package/dist/commands/tui.js +439 -0
- package/dist/commands/tui.js.map +1 -0
- package/dist/commands/ui.d.ts +3 -1
- package/dist/commands/ui.d.ts.map +1 -1
- package/dist/commands/ui.js +76 -19
- package/dist/commands/ui.js.map +1 -1
- package/dist/core/adapters/local-storage.d.ts +14 -0
- package/dist/core/adapters/local-storage.d.ts.map +1 -0
- package/dist/core/adapters/local-storage.js +58 -0
- package/dist/core/adapters/local-storage.js.map +1 -0
- package/dist/core/adapters/obsidian-storage.d.ts +28 -0
- package/dist/core/adapters/obsidian-storage.d.ts.map +1 -0
- package/dist/core/adapters/obsidian-storage.js +135 -0
- package/dist/core/adapters/obsidian-storage.js.map +1 -0
- package/dist/core/adapters/registry.d.ts +8 -0
- package/dist/core/adapters/registry.d.ts.map +1 -0
- package/dist/core/adapters/registry.js +37 -0
- package/dist/core/adapters/registry.js.map +1 -0
- package/dist/core/adapters/storage.test.d.ts +2 -0
- package/dist/core/adapters/storage.test.d.ts.map +1 -0
- package/dist/core/adapters/storage.test.js +106 -0
- package/dist/core/adapters/storage.test.js.map +1 -0
- package/dist/core/adapters/vault-storage.d.ts +16 -0
- package/dist/core/adapters/vault-storage.d.ts.map +1 -0
- package/dist/core/adapters/vault-storage.js +77 -0
- package/dist/core/adapters/vault-storage.js.map +1 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +28 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/plugins/index.d.ts +14 -0
- package/dist/core/plugins/index.d.ts.map +1 -0
- package/dist/core/plugins/index.js +2 -0
- package/dist/core/plugins/index.js.map +1 -0
- package/dist/core/plugins/loader.d.ts +2 -0
- package/dist/core/plugins/loader.d.ts.map +1 -0
- package/dist/core/plugins/loader.js +34 -0
- package/dist/core/plugins/loader.js.map +1 -0
- package/dist/core/ports/storage.d.ts +59 -0
- package/dist/core/ports/storage.d.ts.map +1 -0
- package/dist/core/ports/storage.js +10 -0
- package/dist/core/ports/storage.js.map +1 -0
- package/dist/core/storage.d.ts +10 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +29 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +110 -16
- package/dist/core/workspace.js.map +1 -1
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +19 -6
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/diff-context.d.ts +6 -0
- package/dist/generators/diff-context.d.ts.map +1 -0
- package/dist/generators/diff-context.js +68 -0
- package/dist/generators/diff-context.js.map +1 -0
- package/dist/generators/index.d.ts +1 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +75 -18
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/map-generator.d.ts.map +1 -1
- package/dist/generators/map-generator.js +7 -11
- package/dist/generators/map-generator.js.map +1 -1
- package/dist/generators/plan-generator.d.ts.map +1 -1
- package/dist/generators/plan-generator.js +3 -5
- package/dist/generators/plan-generator.js.map +1 -1
- package/dist/gui/app_logo.png +0 -0
- package/dist/gui/assets/index-CB-jWded.css +1 -0
- package/dist/gui/assets/index-DDXpZZu3.js +25 -0
- package/dist/gui/index.html +2 -2
- package/dist/index.js +174 -20
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +212 -28
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +173 -38
- package/dist/server.test.js.map +1 -1
- package/dist/types.d.ts +11 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/git.d.ts +16 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +52 -0
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/local-ai.d.ts.map +1 -1
- package/dist/utils/local-ai.js +7 -3
- package/dist/utils/local-ai.js.map +1 -1
- package/dist/utils/multi-git.d.ts +2 -0
- package/dist/utils/multi-git.d.ts.map +1 -1
- package/dist/utils/multi-git.js +5 -2
- package/dist/utils/multi-git.js.map +1 -1
- package/dist/utils/update-check.d.ts +3 -1
- package/dist/utils/update-check.d.ts.map +1 -1
- package/dist/utils/update-check.js +23 -34
- package/dist/utils/update-check.js.map +1 -1
- package/dist/utils/workflow-advisor.d.ts +16 -0
- package/dist/utils/workflow-advisor.d.ts.map +1 -0
- package/dist/utils/workflow-advisor.js +100 -0
- package/dist/utils/workflow-advisor.js.map +1 -0
- package/dist/utils/workflows.d.ts +11 -0
- package/dist/utils/workflows.d.ts.map +1 -0
- package/dist/utils/workflows.js +156 -0
- package/dist/utils/workflows.js.map +1 -0
- package/dist/utils/workflows.test.d.ts +2 -0
- package/dist/utils/workflows.test.d.ts.map +1 -0
- package/dist/utils/workflows.test.js +86 -0
- package/dist/utils/workflows.test.js.map +1 -0
- package/extension/media/icon.svg +1 -0
- package/extension/package-lock.json +4893 -1660
- package/extension/package.json +58 -11
- package/extension/src/extension.ts +476 -27
- package/extension/tsconfig.json +4 -2
- package/gui/e2e/wizard.spec.ts +19 -0
- package/gui/package-lock.json +342 -381
- package/gui/package.json +12 -12
- package/gui/public/app_logo.png +0 -0
- package/gui/src/App.tsx +991 -81
- package/gui/src/features/workspace/WorkspaceList.tsx +30 -30
- package/gui/src/index.css +64 -22
- package/gui/src/main.tsx +89 -5
- package/package.json +11 -12
- package/resources/workflows/plan-implement-review.md +8 -0
- package/resources/workflows/research-verify.md +6 -0
- package/resources/workflows/solo-developer.md +3 -0
- package/skills-lock.json +11 -0
- package/src/commands/adapter.ts +348 -0
- package/src/commands/add-repo.ts +0 -1
- package/src/commands/commands.test.ts +3 -2
- package/src/commands/config.ts +52 -0
- package/src/commands/create.ts +14 -17
- package/src/commands/desktop.ts +128 -0
- package/src/commands/doctor.ts +13 -0
- package/src/commands/mcp.ts +2 -0
- package/src/commands/refresh.ts +4 -14
- package/src/commands/sync.ts +2 -8
- package/src/commands/tui.ts +484 -0
- package/src/commands/ui.ts +83 -19
- package/src/core/adapters/local-storage.ts +66 -0
- package/src/core/adapters/obsidian-storage.ts +151 -0
- package/src/core/adapters/registry.ts +44 -0
- package/src/core/adapters/storage.test.ts +174 -0
- package/src/core/adapters/vault-storage.ts +86 -0
- package/src/core/config.ts +29 -4
- package/src/core/plugins/index.ts +17 -0
- package/src/core/plugins/loader.ts +36 -0
- package/src/core/ports/storage.ts +71 -0
- package/src/core/storage.ts +37 -0
- package/src/core/workspace.ts +122 -15
- package/src/generators/base.ts +21 -5
- package/src/generators/diff-context.ts +75 -0
- package/src/generators/index.ts +93 -21
- package/src/generators/map-generator.ts +7 -10
- package/src/generators/plan-generator.ts +6 -4
- package/src/index.ts +178 -21
- package/src/server.test.ts +193 -39
- package/src/server.ts +238 -28
- package/src/types.ts +14 -2
- package/src/utils/git.ts +56 -0
- package/src/utils/local-ai.ts +8 -3
- package/src/utils/multi-git.ts +16 -9
- package/src/utils/update-check.ts +42 -31
- package/src/utils/workflow-advisor.ts +118 -0
- package/src/utils/workflows.test.ts +114 -0
- package/src/utils/workflows.ts +178 -0
- package/.github/workflows/release.yml +0 -100
- package/dist/commands/pack.d.ts +0 -7
- package/dist/commands/pack.d.ts.map +0 -1
- package/dist/commands/pack.js +0 -64
- package/dist/commands/pack.js.map +0 -1
- package/dist/core/packer.d.ts +0 -14
- package/dist/core/packer.d.ts.map +0 -1
- package/dist/core/packer.js +0 -87
- package/dist/core/packer.js.map +0 -1
- package/dist/core/packer.test.d.ts +0 -2
- package/dist/core/packer.test.d.ts.map +0 -1
- package/dist/core/packer.test.js +0 -82
- package/dist/core/packer.test.js.map +0 -1
- package/dist/gui/assets/index-CFWwqFux.js +0 -23
- package/dist/gui/assets/index-Dvc9QMvl.css +0 -2
- package/src/commands/pack.ts +0 -73
- package/src/core/packer.test.ts +0 -99
- package/src/core/packer.ts +0 -107
package/gui/src/App.tsx
CHANGED
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
Copy,
|
|
18
18
|
X,
|
|
19
19
|
Cpu,
|
|
20
|
+
Workflow,
|
|
21
|
+
Trash2,
|
|
22
|
+
CheckCircle,
|
|
20
23
|
} from 'lucide-react';
|
|
21
24
|
import './App.css';
|
|
22
25
|
import { WorkspaceList } from './features/workspace/WorkspaceList.js';
|
|
@@ -28,6 +31,7 @@ interface LocalLlmConfig {
|
|
|
28
31
|
provider: 'ollama' | 'openai-compatible';
|
|
29
32
|
endpoint: string;
|
|
30
33
|
model: string;
|
|
34
|
+
apiKey?: string;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
interface NexusFlowConfig {
|
|
@@ -38,7 +42,25 @@ interface NexusFlowConfig {
|
|
|
38
42
|
defaultEditor?: string | null;
|
|
39
43
|
scanDepth: number;
|
|
40
44
|
localLlm?: LocalLlmConfig;
|
|
41
|
-
|
|
45
|
+
storageProvider?: string;
|
|
46
|
+
adapterConfig?: Record<string, Record<string, any>>;
|
|
47
|
+
plugins?: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface AdapterConfigField {
|
|
51
|
+
key: string;
|
|
52
|
+
label: string;
|
|
53
|
+
type: 'string' | 'boolean' | 'number' | 'path';
|
|
54
|
+
required?: boolean;
|
|
55
|
+
default?: any;
|
|
56
|
+
description?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface StorageAdapterMeta {
|
|
60
|
+
name: string;
|
|
61
|
+
displayName: string;
|
|
62
|
+
description: string;
|
|
63
|
+
configFields: AdapterConfigField[];
|
|
42
64
|
}
|
|
43
65
|
|
|
44
66
|
interface DetectedAI {
|
|
@@ -93,12 +115,12 @@ interface RunningService {
|
|
|
93
115
|
startedAt: string;
|
|
94
116
|
}
|
|
95
117
|
|
|
96
|
-
const API_BASE = import.meta.env.DEV ? 'http://localhost:3000' : '';
|
|
118
|
+
const API_BASE = (import.meta.env.DEV || (typeof window !== 'undefined' && (window as any).Neutralino)) ? 'http://localhost:3000' : '';
|
|
97
119
|
const isVsCode = new URLSearchParams(window.location.search).get('env') === 'vscode';
|
|
98
120
|
let toastIdCounter = 0;
|
|
99
121
|
|
|
100
122
|
export default function App() {
|
|
101
|
-
const [view, setView] = useState<'guide' | 'create' | 'workspaces' | 'settings'>('guide');
|
|
123
|
+
const [view, setView] = useState<'guide' | 'create' | 'workspaces' | 'settings' | 'workflows'>('guide');
|
|
102
124
|
|
|
103
125
|
// Toast State
|
|
104
126
|
interface Toast {
|
|
@@ -121,12 +143,15 @@ export default function App() {
|
|
|
121
143
|
const [configLoading, setConfigLoading] = useState(true);
|
|
122
144
|
const [configExists, setConfigExists] = useState<boolean>(true);
|
|
123
145
|
const [saveStatus, setSaveStatus] = useState<'success' | 'error' | null>(null);
|
|
146
|
+
const [adapters, setAdapters] = useState<StorageAdapterMeta[]>([]);
|
|
124
147
|
|
|
125
148
|
// Update Check State
|
|
126
149
|
const [updateStatus, setUpdateStatus] = useState<{
|
|
127
150
|
currentVersion: string;
|
|
128
151
|
latestVersion: string;
|
|
129
152
|
updateAvailable: boolean;
|
|
153
|
+
downloadUrl?: string | null;
|
|
154
|
+
releaseNotes?: string;
|
|
130
155
|
} | null>(null);
|
|
131
156
|
const [appVersion, setAppVersion] = useState('0.1.5');
|
|
132
157
|
const [defaultPaths, setDefaultPaths] = useState<{ devDir: string; workspacesDir: string } | null>(null);
|
|
@@ -153,13 +178,37 @@ export default function App() {
|
|
|
153
178
|
{ id: 'worktrees', name: 'Create Git Worktrees', status: 'pending', message: 'Waiting...' },
|
|
154
179
|
{ id: 'analysis', name: 'Analyze Repositories', status: 'pending', message: 'Waiting...' },
|
|
155
180
|
{ id: 'context', name: 'Generate AI Context Files', status: 'pending', message: 'Waiting...' },
|
|
156
|
-
{ id: 'pack', name: 'Pack Codebase Context', status: 'pending', message: 'Waiting...' },
|
|
157
181
|
]);
|
|
158
182
|
|
|
159
183
|
const [toolsStatus, setToolsStatus] = useState<any[]>([]);
|
|
160
184
|
const [toolsLoading, setToolsLoading] = useState(false);
|
|
161
185
|
const [updatingToolId, setUpdatingToolId] = useState<string | null>(null);
|
|
162
186
|
|
|
187
|
+
// App Autoupdate State
|
|
188
|
+
const [updatingApp, setUpdatingApp] = useState(false);
|
|
189
|
+
const [updateStep, setUpdateStep] = useState<'idle' | 'downloading' | 'applying' | 'error'>('idle');
|
|
190
|
+
|
|
191
|
+
// Workflow Strategy State
|
|
192
|
+
const [workflowTemplates, setWorkflowTemplates] = useState<any[]>([]);
|
|
193
|
+
const [selectedWorkflowId, setSelectedWorkflowId] = useState<string>('plan-implement-review');
|
|
194
|
+
const [customTeamworkInstructions, setCustomTeamworkInstructions] = useState<string>('');
|
|
195
|
+
const [suggestingWorkflow, setSuggestingWorkflow] = useState(false);
|
|
196
|
+
const [suggestedDifficulty, setSuggestedDifficulty] = useState<'simple' | 'moderate' | 'complex' | null>(null);
|
|
197
|
+
const [suggestedRationale, setSuggestedRationale] = useState('');
|
|
198
|
+
|
|
199
|
+
// Workflow Strategy Management State
|
|
200
|
+
const [selectedMgtTemplateId, setSelectedMgtTemplateId] = useState<string | null>(null);
|
|
201
|
+
const [isEditingTemplate, setIsEditingTemplate] = useState(false);
|
|
202
|
+
const [mgtTemplateName, setMgtTemplateName] = useState('');
|
|
203
|
+
const [mgtTemplateContent, setMgtTemplateContent] = useState('');
|
|
204
|
+
const [analysisResult, setAnalysisResult] = useState<string | null>(null);
|
|
205
|
+
const [analyzingTemplate, setAnalyzingTemplate] = useState(false);
|
|
206
|
+
const [savingTemplate, setSavingTemplate] = useState(false);
|
|
207
|
+
const [deletingTemplate, setDeletingTemplate] = useState(false);
|
|
208
|
+
const [selectedInspectAssistant, setSelectedInspectAssistant] = useState<string>('antigravity');
|
|
209
|
+
const [suggestedImprovement, setSuggestedImprovement] = useState<string | null>(null);
|
|
210
|
+
const [mgtAnalysisComment, setMgtAnalysisComment] = useState('');
|
|
211
|
+
|
|
163
212
|
// Resumption Commands State
|
|
164
213
|
const [testCommand, setTestCommand] = useState('npm run test');
|
|
165
214
|
const [mockCommand, setMockCommand] = useState('');
|
|
@@ -296,6 +345,55 @@ export default function App() {
|
|
|
296
345
|
}
|
|
297
346
|
};
|
|
298
347
|
|
|
348
|
+
const handleAutoUpdate = async () => {
|
|
349
|
+
if (!updateStatus || !updateStatus.downloadUrl) {
|
|
350
|
+
showToast('No update download URL found.', 'error');
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
setUpdatingApp(true);
|
|
355
|
+
setUpdateStep('downloading');
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
// 1. Download the installer
|
|
359
|
+
const downloadRes = await fetch(`${API_BASE}/api/updates/download`, {
|
|
360
|
+
method: 'POST',
|
|
361
|
+
headers: { 'Content-Type': 'application/json' },
|
|
362
|
+
body: JSON.stringify({ downloadUrl: updateStatus.downloadUrl }),
|
|
363
|
+
});
|
|
364
|
+
const downloadData = await downloadRes.json();
|
|
365
|
+
|
|
366
|
+
if (!downloadRes.ok || !downloadData.success) {
|
|
367
|
+
throw new Error(downloadData.error || 'Failed to download installer binary');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 2. Apply the installer
|
|
371
|
+
setUpdateStep('applying');
|
|
372
|
+
const applyRes = await fetch(`${API_BASE}/api/updates/apply`, {
|
|
373
|
+
method: 'POST',
|
|
374
|
+
});
|
|
375
|
+
const applyData = await applyRes.json();
|
|
376
|
+
|
|
377
|
+
if (!applyRes.ok || !applyData.success) {
|
|
378
|
+
throw new Error(applyData.error || 'Failed to trigger application update');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
showToast('Update downloaded! App is restarting...', 'success');
|
|
382
|
+
|
|
383
|
+
// 3. Exit Neutralino client window to unlock files on disk
|
|
384
|
+
if (typeof window !== 'undefined' && (window as any).Neutralino) {
|
|
385
|
+
setTimeout(() => {
|
|
386
|
+
(window as any).Neutralino.app.exit();
|
|
387
|
+
}, 800);
|
|
388
|
+
}
|
|
389
|
+
} catch (err: any) {
|
|
390
|
+
console.error('Update failed:', err);
|
|
391
|
+
setUpdateStep('error');
|
|
392
|
+
showToast(`Update Failed: ${err.message || 'Unknown error'}`, 'error');
|
|
393
|
+
setUpdatingApp(false);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
299
397
|
const fetchLlmRecommendation = async () => {
|
|
300
398
|
try {
|
|
301
399
|
const res = await fetch(`${API_BASE}/api/local-llm/recommend`);
|
|
@@ -331,7 +429,20 @@ export default function App() {
|
|
|
331
429
|
}
|
|
332
430
|
};
|
|
333
431
|
|
|
432
|
+
const fetchAdapters = async () => {
|
|
433
|
+
try {
|
|
434
|
+
const res = await fetch(`${API_BASE}/api/adapters`);
|
|
435
|
+
const data = await res.json();
|
|
436
|
+
if (data.adapters) {
|
|
437
|
+
setAdapters(data.adapters);
|
|
438
|
+
}
|
|
439
|
+
} catch (e) {
|
|
440
|
+
console.error('Failed to fetch adapters:', e);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
334
444
|
const fetchConfig = async () => {
|
|
445
|
+
fetchAdapters();
|
|
335
446
|
try {
|
|
336
447
|
const res = await fetch(`${API_BASE}/api/config`);
|
|
337
448
|
const data = await res.json();
|
|
@@ -404,6 +515,10 @@ export default function App() {
|
|
|
404
515
|
const res = await fetch(`${API_BASE}/api/ai-detect`);
|
|
405
516
|
const data = await res.json();
|
|
406
517
|
setAiAssistants(data);
|
|
518
|
+
const firstHarness = data.find((ai: any) => ai.detected && ai.command);
|
|
519
|
+
if (firstHarness) {
|
|
520
|
+
setSelectedInspectAssistant(firstHarness.name);
|
|
521
|
+
}
|
|
407
522
|
setSelectedAI(data.filter((ai: DetectedAI) => ai.detected).map((ai: DetectedAI) => ai.name));
|
|
408
523
|
} catch (e) {
|
|
409
524
|
console.error(e);
|
|
@@ -442,6 +557,109 @@ export default function App() {
|
|
|
442
557
|
}
|
|
443
558
|
};
|
|
444
559
|
|
|
560
|
+
const fetchWorkflowTemplates = async () => {
|
|
561
|
+
try {
|
|
562
|
+
const res = await fetch(`${API_BASE}/api/workflows/templates`);
|
|
563
|
+
if (res.ok) {
|
|
564
|
+
const data = await res.json();
|
|
565
|
+
setWorkflowTemplates(data.templates || []);
|
|
566
|
+
|
|
567
|
+
// Default to plan-implement-review template
|
|
568
|
+
const pir = data.templates?.find((t: any) => t.id === 'plan-implement-review');
|
|
569
|
+
if (pir) {
|
|
570
|
+
setCustomTeamworkInstructions(pir.content);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
} catch (e) {
|
|
574
|
+
console.error('Failed to fetch workflow templates:', e);
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const handleSaveTemplate = async () => {
|
|
579
|
+
if (!mgtTemplateName.trim() || !mgtTemplateContent.trim()) {
|
|
580
|
+
showToast('Name and content are required.', 'error');
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
setSavingTemplate(true);
|
|
584
|
+
try {
|
|
585
|
+
const res = await fetch(`${API_BASE}/api/workflows/templates`, {
|
|
586
|
+
method: 'POST',
|
|
587
|
+
headers: { 'Content-Type': 'application/json' },
|
|
588
|
+
body: JSON.stringify({
|
|
589
|
+
id: selectedMgtTemplateId,
|
|
590
|
+
name: mgtTemplateName,
|
|
591
|
+
content: mgtTemplateContent,
|
|
592
|
+
}),
|
|
593
|
+
});
|
|
594
|
+
if (res.ok) {
|
|
595
|
+
const data = await res.json();
|
|
596
|
+
showToast('Strategy template saved successfully!', 'success');
|
|
597
|
+
await fetchWorkflowTemplates();
|
|
598
|
+
setSelectedMgtTemplateId(data.template.id);
|
|
599
|
+
setIsEditingTemplate(false);
|
|
600
|
+
setAnalysisResult(null);
|
|
601
|
+
} else {
|
|
602
|
+
const err = await res.json();
|
|
603
|
+
showToast(err.error || 'Failed to save template', 'error');
|
|
604
|
+
}
|
|
605
|
+
} catch (e: any) {
|
|
606
|
+
showToast(e.message || 'Error saving template', 'error');
|
|
607
|
+
} finally {
|
|
608
|
+
setSavingTemplate(false);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const handleDeleteTemplate = async (id: string) => {
|
|
613
|
+
if (!window.confirm('Are you sure you want to delete this custom strategy template?')) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
setDeletingTemplate(true);
|
|
617
|
+
try {
|
|
618
|
+
const res = await fetch(`${API_BASE}/api/workflows/templates/${encodeURIComponent(id)}`, {
|
|
619
|
+
method: 'DELETE',
|
|
620
|
+
});
|
|
621
|
+
if (res.ok) {
|
|
622
|
+
showToast('Strategy template deleted successfully!', 'success');
|
|
623
|
+
await fetchWorkflowTemplates();
|
|
624
|
+
setSelectedMgtTemplateId(null);
|
|
625
|
+
setAnalysisResult(null);
|
|
626
|
+
} else {
|
|
627
|
+
const err = await res.json();
|
|
628
|
+
showToast(err.error || 'Failed to delete template', 'error');
|
|
629
|
+
}
|
|
630
|
+
} catch (e: any) {
|
|
631
|
+
showToast(e.message || 'Error deleting template', 'error');
|
|
632
|
+
} finally {
|
|
633
|
+
setDeletingTemplate(false);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
const handleAnalyzeTemplate = async (id: string, content: string, assistant: string) => {
|
|
638
|
+
setAnalyzingTemplate(true);
|
|
639
|
+
setAnalysisResult(null);
|
|
640
|
+
setSuggestedImprovement(null);
|
|
641
|
+
try {
|
|
642
|
+
const res = await fetch(`${API_BASE}/api/workflows/templates/${encodeURIComponent(id)}/analyze`, {
|
|
643
|
+
method: 'POST',
|
|
644
|
+
headers: { 'Content-Type': 'application/json' },
|
|
645
|
+
body: JSON.stringify({ content, assistant, comment: mgtAnalysisComment }),
|
|
646
|
+
});
|
|
647
|
+
if (res.ok) {
|
|
648
|
+
const data = await res.json();
|
|
649
|
+
setAnalysisResult(data.analysis);
|
|
650
|
+
setSuggestedImprovement(data.suggestedImprovement || null);
|
|
651
|
+
showToast('Strategy analysis completed successfully!', 'success');
|
|
652
|
+
} else {
|
|
653
|
+
const err = await res.json();
|
|
654
|
+
showToast(err.error || 'Failed to analyze template using the selected assistant harness.', 'error');
|
|
655
|
+
}
|
|
656
|
+
} catch (e: any) {
|
|
657
|
+
showToast(e.message || 'Error analyzing template', 'error');
|
|
658
|
+
} finally {
|
|
659
|
+
setAnalyzingTemplate(false);
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
|
|
445
663
|
const fetchWorkspaceServices = async (wsId: string, silent = false) => {
|
|
446
664
|
if (!silent) setServicesLoading(true);
|
|
447
665
|
try {
|
|
@@ -623,6 +841,41 @@ export default function App() {
|
|
|
623
841
|
}
|
|
624
842
|
};
|
|
625
843
|
|
|
844
|
+
const handleSuggestWorkflow = async () => {
|
|
845
|
+
if (!description) {
|
|
846
|
+
showToast('Please enter a description for the workspace details first.', 'info');
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
setSuggestingWorkflow(true);
|
|
850
|
+
setSuggestedDifficulty(null);
|
|
851
|
+
setSuggestedRationale('');
|
|
852
|
+
try {
|
|
853
|
+
const res = await fetch(`${API_BASE}/api/workspace/suggest-workflow`, {
|
|
854
|
+
method: 'POST',
|
|
855
|
+
headers: { 'Content-Type': 'application/json' },
|
|
856
|
+
body: JSON.stringify({
|
|
857
|
+
description,
|
|
858
|
+
repos: selectedRepos,
|
|
859
|
+
}),
|
|
860
|
+
});
|
|
861
|
+
const data = await res.json();
|
|
862
|
+
if (res.ok && data.success) {
|
|
863
|
+
setSuggestedDifficulty(data.difficulty);
|
|
864
|
+
setSuggestedRationale(data.rationale);
|
|
865
|
+
setSelectedWorkflowId(data.suggestedWorkflowId);
|
|
866
|
+
setCustomTeamworkInstructions(data.customInstructions);
|
|
867
|
+
showToast(`Suggested strategy populated successfully: ${data.difficulty.toUpperCase()} difficulty.`, 'success');
|
|
868
|
+
} else {
|
|
869
|
+
showToast(data.error || 'Failed to auto-suggest strategy.', 'error');
|
|
870
|
+
}
|
|
871
|
+
} catch (e) {
|
|
872
|
+
console.error(e);
|
|
873
|
+
showToast('Network error while requesting strategy suggestion.', 'error');
|
|
874
|
+
} finally {
|
|
875
|
+
setSuggestingWorkflow(false);
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
|
|
626
879
|
const handleCreateWorkspace = async () => {
|
|
627
880
|
if (!branchName || selectedRepos.length === 0) return;
|
|
628
881
|
setCreating(true);
|
|
@@ -633,7 +886,6 @@ export default function App() {
|
|
|
633
886
|
{ id: 'worktrees', name: 'Create Git Worktrees', status: 'pending', message: 'Waiting...' },
|
|
634
887
|
{ id: 'analysis', name: 'Analyze Repositories', status: 'pending', message: 'Waiting...' },
|
|
635
888
|
{ id: 'context', name: 'Generate AI Context Files', status: 'pending', message: 'Waiting...' },
|
|
636
|
-
{ id: 'pack', name: 'Pack Codebase Context', status: 'pending', message: 'Waiting...' },
|
|
637
889
|
]);
|
|
638
890
|
|
|
639
891
|
try {
|
|
@@ -646,6 +898,7 @@ export default function App() {
|
|
|
646
898
|
repos: selectedRepos,
|
|
647
899
|
assistants: selectedAI,
|
|
648
900
|
localLlmEnabled,
|
|
901
|
+
teamworkInstructions: customTeamworkInstructions || undefined,
|
|
649
902
|
resumption: {
|
|
650
903
|
testCommand,
|
|
651
904
|
mockCommand: mockCommand || undefined,
|
|
@@ -669,7 +922,7 @@ export default function App() {
|
|
|
669
922
|
if (job.status === 'completed') {
|
|
670
923
|
eventSource.close();
|
|
671
924
|
setCreatedWorkspace({ path: job.workspacePath });
|
|
672
|
-
setActiveStep(
|
|
925
|
+
setActiveStep(4);
|
|
673
926
|
setCreating(false);
|
|
674
927
|
fetchWorkspaces();
|
|
675
928
|
|
|
@@ -834,6 +1087,7 @@ export default function App() {
|
|
|
834
1087
|
fetchAIAssistants();
|
|
835
1088
|
fetchWorkspaces();
|
|
836
1089
|
fetchUpdateStatus();
|
|
1090
|
+
fetchWorkflowTemplates();
|
|
837
1091
|
|
|
838
1092
|
// Support auto-loading workspace from VS Code or URL params
|
|
839
1093
|
const queryParams = new URLSearchParams(window.location.search);
|
|
@@ -979,7 +1233,7 @@ Core Instructions:
|
|
|
979
1233
|
fetchGitChanges(wsName);
|
|
980
1234
|
fetchWorkspaceSessions(wsName);
|
|
981
1235
|
}
|
|
982
|
-
showToast('Repository successfully added
|
|
1236
|
+
showToast('Repository successfully added and configurations updated.', 'success');
|
|
983
1237
|
} else {
|
|
984
1238
|
showToast(`Failed to add repository: ${data.error || 'Unknown error'}`, 'error');
|
|
985
1239
|
}
|
|
@@ -1157,20 +1411,45 @@ Core Instructions:
|
|
|
1157
1411
|
</div>
|
|
1158
1412
|
|
|
1159
1413
|
<div className="pt-2">
|
|
1160
|
-
<
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1414
|
+
<label htmlFor="onboardingStorageProvider" className="text-xs font-semibold text-slate-200 block mb-1">
|
|
1415
|
+
Storage Provider
|
|
1416
|
+
</label>
|
|
1417
|
+
<select
|
|
1418
|
+
id="onboardingStorageProvider"
|
|
1419
|
+
className="w-full text-xs bg-slate-950/60 border border-slate-800 rounded-lg p-2 text-slate-200 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/50"
|
|
1420
|
+
value={config.storageProvider || 'local'}
|
|
1421
|
+
onChange={(e) => {
|
|
1422
|
+
const val = e.target.value;
|
|
1423
|
+
const newConf = { ...config, storageProvider: val };
|
|
1424
|
+
const selectedAdapter = adapters.find(a => a.name === val);
|
|
1425
|
+
if (selectedAdapter?.configFields?.length) {
|
|
1426
|
+
if (!newConf.adapterConfig) newConf.adapterConfig = {};
|
|
1427
|
+
if (!newConf.adapterConfig[val]) newConf.adapterConfig[val] = {};
|
|
1428
|
+
selectedAdapter.configFields.forEach(f => {
|
|
1429
|
+
if (newConf.adapterConfig![val][f.key] === undefined && f.default !== undefined) {
|
|
1430
|
+
newConf.adapterConfig![val][f.key] = f.default;
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
setConfig(newConf);
|
|
1435
|
+
}}
|
|
1436
|
+
>
|
|
1437
|
+
{adapters.length > 0 ? (
|
|
1438
|
+
adapters.map((a) => (
|
|
1439
|
+
<option key={a.name} value={a.name}>
|
|
1440
|
+
{a.displayName}
|
|
1441
|
+
</option>
|
|
1442
|
+
))
|
|
1443
|
+
) : (
|
|
1444
|
+
<>
|
|
1445
|
+
<option value="local">Local Workspace (Folders)</option>
|
|
1446
|
+
<option value="central-vault">Obsidian Central Vault</option>
|
|
1447
|
+
</>
|
|
1448
|
+
)}
|
|
1449
|
+
</select>
|
|
1450
|
+
<span className="text-[10px] text-slate-500 mt-1 block leading-normal">
|
|
1451
|
+
{adapters.find(a => a.name === (config.storageProvider || 'local'))?.description ||
|
|
1452
|
+
'Choose where to store maps, plans, and knowledge files. Centralized vault keeps repositories 100% clean and allows Obsidian integration.'}
|
|
1174
1453
|
</span>
|
|
1175
1454
|
</div>
|
|
1176
1455
|
|
|
@@ -1428,40 +1707,43 @@ Core Instructions:
|
|
|
1428
1707
|
}
|
|
1429
1708
|
|
|
1430
1709
|
return (
|
|
1431
|
-
<div className="flex min-h-screen bg-[#
|
|
1710
|
+
<div className="flex min-h-screen bg-[#05070c] text-slate-300 font-mono crt-screen">
|
|
1432
1711
|
{/* Sidebar Navigation */}
|
|
1433
|
-
<aside className="w-64 bg-
|
|
1712
|
+
<aside className="w-64 bg-[#05070c] border-r border-slate-900 flex flex-col p-6 shrink-0 relative z-10 neon-border">
|
|
1434
1713
|
<div className="flex items-center gap-3 mb-10 px-2">
|
|
1435
|
-
<div className="w-9 h-9 bg-gradient-to-tr from-cyan-
|
|
1436
|
-
|
|
1714
|
+
<div className="w-9 h-9 bg-gradient-to-tr from-cyan-500 to-violet-600 rounded-lg flex items-center justify-center font-bold text-white shadow-lg shadow-cyan-500/10 text-lg select-none">
|
|
1715
|
+
NF
|
|
1716
|
+
</div>
|
|
1717
|
+
<div>
|
|
1718
|
+
<span className="text-sm font-bold tracking-wider text-white">
|
|
1719
|
+
NexusFlow
|
|
1720
|
+
</span>
|
|
1721
|
+
<span className="block text-[8px] text-cyan-400 font-semibold tracking-widest uppercase mt-0.5">COMMAND_CENTER</span>
|
|
1437
1722
|
</div>
|
|
1438
|
-
<span className="text-xl font-bold tracking-tight bg-gradient-to-r from-white via-slate-200 to-slate-400 bg-clip-text text-transparent">
|
|
1439
|
-
NexusFlow
|
|
1440
|
-
</span>
|
|
1441
1723
|
</div>
|
|
1442
1724
|
<nav className="flex-1">
|
|
1443
|
-
<ul className="flex flex-col gap-2
|
|
1725
|
+
<ul className="flex flex-col gap-2">
|
|
1444
1726
|
<li>
|
|
1445
1727
|
<button
|
|
1446
|
-
className={`w-full flex items-center gap-3 p-3 rounded-
|
|
1728
|
+
className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
|
|
1447
1729
|
view === 'guide'
|
|
1448
|
-
? 'text-
|
|
1449
|
-
: 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40
|
|
1730
|
+
? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
|
|
1731
|
+
: 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
|
|
1450
1732
|
}`}
|
|
1451
1733
|
onClick={() => {
|
|
1452
1734
|
setView('guide');
|
|
1453
1735
|
}}
|
|
1454
1736
|
>
|
|
1455
|
-
<Sparkles size={
|
|
1737
|
+
<Sparkles size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
|
|
1456
1738
|
Getting Started
|
|
1457
1739
|
</button>
|
|
1458
1740
|
</li>
|
|
1459
1741
|
<li>
|
|
1460
1742
|
<button
|
|
1461
|
-
className={`w-full flex items-center gap-3 p-3 rounded-
|
|
1743
|
+
className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
|
|
1462
1744
|
view === 'create'
|
|
1463
|
-
? 'text-
|
|
1464
|
-
: 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40
|
|
1745
|
+
? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
|
|
1746
|
+
: 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
|
|
1465
1747
|
}`}
|
|
1466
1748
|
onClick={() => {
|
|
1467
1749
|
setView('create');
|
|
@@ -1473,51 +1755,74 @@ Core Instructions:
|
|
|
1473
1755
|
setLocalLlmEnabled(config?.localLlm?.enabled || false);
|
|
1474
1756
|
}}
|
|
1475
1757
|
>
|
|
1476
|
-
<PlusCircle size={
|
|
1758
|
+
<PlusCircle size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
|
|
1477
1759
|
New Workspace
|
|
1478
1760
|
</button>
|
|
1479
1761
|
</li>
|
|
1480
1762
|
<li>
|
|
1481
1763
|
<button
|
|
1482
|
-
className={`w-full flex items-center gap-3 p-3 rounded-
|
|
1764
|
+
className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
|
|
1483
1765
|
view === 'workspaces'
|
|
1484
|
-
? 'text-
|
|
1485
|
-
: 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40
|
|
1766
|
+
? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
|
|
1767
|
+
: 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
|
|
1486
1768
|
}`}
|
|
1487
1769
|
onClick={() => {
|
|
1488
1770
|
setView('workspaces');
|
|
1489
1771
|
fetchWorkspaces();
|
|
1490
1772
|
}}
|
|
1491
1773
|
>
|
|
1492
|
-
<FolderGit2 size={
|
|
1774
|
+
<FolderGit2 size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
|
|
1493
1775
|
Active Workspaces
|
|
1494
1776
|
</button>
|
|
1495
1777
|
</li>
|
|
1496
1778
|
<li>
|
|
1497
1779
|
<button
|
|
1498
|
-
className={`w-full flex items-center gap-3 p-3 rounded-
|
|
1780
|
+
className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
|
|
1781
|
+
view === 'workflows'
|
|
1782
|
+
? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
|
|
1783
|
+
: 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
|
|
1784
|
+
}`}
|
|
1785
|
+
onClick={() => {
|
|
1786
|
+
setView('workflows');
|
|
1787
|
+
fetchWorkflowTemplates();
|
|
1788
|
+
if (workflowTemplates.length > 0) {
|
|
1789
|
+
const first = workflowTemplates[0];
|
|
1790
|
+
setSelectedMgtTemplateId(first.id);
|
|
1791
|
+
setMgtTemplateName(first.name);
|
|
1792
|
+
setMgtTemplateContent(first.content);
|
|
1793
|
+
}
|
|
1794
|
+
setAnalysisResult(null);
|
|
1795
|
+
}}
|
|
1796
|
+
>
|
|
1797
|
+
<Workflow size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
|
|
1798
|
+
Team Strategies
|
|
1799
|
+
</button>
|
|
1800
|
+
</li>
|
|
1801
|
+
<li>
|
|
1802
|
+
<button
|
|
1803
|
+
className={`w-full flex items-center gap-3 p-3 rounded-lg text-xs font-bold transition-all cursor-pointer border relative overflow-hidden group ${
|
|
1499
1804
|
view === 'settings'
|
|
1500
|
-
? 'text-
|
|
1501
|
-
: 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40
|
|
1805
|
+
? 'text-cyan-400 bg-cyan-950/30 border-cyan-900/60 shadow-[0_0_15px_rgba(6,182,212,0.15)]'
|
|
1806
|
+
: 'text-slate-400 border-transparent hover:text-white hover:bg-slate-900/40'
|
|
1502
1807
|
}`}
|
|
1503
1808
|
onClick={() => {
|
|
1504
1809
|
setView('settings');
|
|
1505
1810
|
fetchConfig();
|
|
1506
1811
|
}}
|
|
1507
1812
|
>
|
|
1508
|
-
<SettingsIcon size={
|
|
1813
|
+
<SettingsIcon size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
|
|
1509
1814
|
Settings
|
|
1510
1815
|
</button>
|
|
1511
1816
|
</li>
|
|
1512
1817
|
</ul>
|
|
1513
1818
|
</nav>
|
|
1514
1819
|
<div className="pt-6 border-t border-slate-900 text-[10px] text-slate-500 text-center tracking-wider font-semibold uppercase">
|
|
1515
|
-
|
|
1820
|
+
SYSTEM_UI: ONLINE v{appVersion}
|
|
1516
1821
|
</div>
|
|
1517
1822
|
</aside>
|
|
1518
1823
|
|
|
1519
1824
|
{/* Main Content Area */}
|
|
1520
|
-
<main className="flex-1 p-
|
|
1825
|
+
<main className="flex-1 p-8 overflow-y-auto max-w-7xl w-full mx-auto">
|
|
1521
1826
|
{configLoading ? (
|
|
1522
1827
|
<div className="flex flex-col items-center justify-center py-40 gap-4 text-gray-400">
|
|
1523
1828
|
<RefreshCw className="animate-spin text-indigo-400" size={32} />
|
|
@@ -1530,24 +1835,46 @@ Core Instructions:
|
|
|
1530
1835
|
<div className="mb-6 p-4 bg-gradient-to-r from-amber-500/10 to-orange-600/10 border border-amber-500/30 rounded-xl shadow-lg flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 backdrop-blur-sm">
|
|
1531
1836
|
<div className="flex items-center gap-3">
|
|
1532
1837
|
<div className="w-10 h-10 rounded-lg bg-amber-500/10 border border-amber-500/20 flex items-center justify-center text-amber-400 shrink-0">
|
|
1533
|
-
|
|
1838
|
+
{updatingApp ? (
|
|
1839
|
+
<RefreshCw size={20} className="animate-spin text-amber-400" />
|
|
1840
|
+
) : (
|
|
1841
|
+
<Sparkles size={20} />
|
|
1842
|
+
)}
|
|
1534
1843
|
</div>
|
|
1535
1844
|
<div>
|
|
1536
1845
|
<h4 className="text-sm font-bold text-amber-300">
|
|
1537
|
-
|
|
1846
|
+
{updatingApp ? (
|
|
1847
|
+
updateStep === 'downloading' ? 'Downloading Update...' : 'Applying Update...'
|
|
1848
|
+
) : (
|
|
1849
|
+
'A new version of NexusFlow is available!'
|
|
1850
|
+
)}
|
|
1538
1851
|
</h4>
|
|
1539
1852
|
<p className="text-xs text-gray-400 mt-0.5">
|
|
1540
|
-
|
|
1853
|
+
{updatingApp ? (
|
|
1854
|
+
updateStep === 'downloading' ? 'Fetching installer from GitHub Releases...' : 'Closing app and launching silent installer setup...'
|
|
1855
|
+
) : (
|
|
1856
|
+
`Upgrade from v${updateStatus.currentVersion} to v${updateStatus.latestVersion} to get the latest features and bug fixes.`
|
|
1857
|
+
)}
|
|
1541
1858
|
</p>
|
|
1542
1859
|
</div>
|
|
1543
1860
|
</div>
|
|
1544
1861
|
<div className="flex items-center gap-3 shrink-0">
|
|
1862
|
+
{typeof window !== 'undefined' && (window as any).Neutralino && updateStatus.downloadUrl && (
|
|
1863
|
+
<button
|
|
1864
|
+
onClick={handleAutoUpdate}
|
|
1865
|
+
disabled={updatingApp}
|
|
1866
|
+
className="px-4 py-2 bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-600 hover:to-teal-700 text-white font-bold text-xs rounded-lg transition-all shadow-md disabled:opacity-50 cursor-pointer flex items-center gap-1.5"
|
|
1867
|
+
>
|
|
1868
|
+
{updatingApp ? 'Installing...' : 'Install Automatically'}
|
|
1869
|
+
</button>
|
|
1870
|
+
)}
|
|
1545
1871
|
<button
|
|
1546
1872
|
onClick={() => {
|
|
1547
1873
|
navigator.clipboard.writeText('npm install -g @mrpatronz/nexusflow');
|
|
1548
1874
|
showToast('Update command copied to clipboard!', 'success');
|
|
1549
1875
|
}}
|
|
1550
|
-
|
|
1876
|
+
disabled={updatingApp}
|
|
1877
|
+
className="px-4 py-2 bg-amber-500 hover:bg-amber-600 text-[#060813] font-bold text-xs rounded-lg transition-all shadow-md shadow-amber-500/10 disabled:opacity-50 cursor-pointer flex items-center gap-1.5"
|
|
1551
1878
|
>
|
|
1552
1879
|
Copy Update Command
|
|
1553
1880
|
</button>
|
|
@@ -1612,17 +1939,7 @@ Core Instructions:
|
|
|
1612
1939
|
</div>
|
|
1613
1940
|
</div>
|
|
1614
1941
|
|
|
1615
|
-
|
|
1616
|
-
<div className="w-8 h-8 rounded-full bg-indigo-500/10 border border-indigo-500/20 text-indigo-400 flex items-center justify-center font-bold text-sm shrink-0">
|
|
1617
|
-
4
|
|
1618
|
-
</div>
|
|
1619
|
-
<div>
|
|
1620
|
-
<h3 className="text-sm font-semibold text-white">Repomix Packing (Optional)</h3>
|
|
1621
|
-
<p className="text-xs text-gray-400 mt-1">
|
|
1622
|
-
If enabled, NexusFlow aggregates the entire multi-repo codebase using <code>Repomix</code> into a token-efficient XML file <code>nexusflow-context.xml</code>, giving AI immediate access to the full codebase state. You can toggle this setting in Settings.
|
|
1623
|
-
</p>
|
|
1624
|
-
</div>
|
|
1625
|
-
</div>
|
|
1942
|
+
|
|
1626
1943
|
</div>
|
|
1627
1944
|
|
|
1628
1945
|
{/* Right: Quick actions and Telemetry info */}
|
|
@@ -1722,11 +2039,11 @@ Core Instructions:
|
|
|
1722
2039
|
</header>
|
|
1723
2040
|
|
|
1724
2041
|
{/* Progress Circle bar */}
|
|
1725
|
-
<div className="flex justify-between items-center max-w-
|
|
2042
|
+
<div className="flex justify-between items-center max-w-2xl mx-auto mb-14 relative px-4">
|
|
1726
2043
|
<div className="absolute top-4 left-6 right-6 h-[2px] bg-gray-800 -z-10"></div>
|
|
1727
2044
|
<div
|
|
1728
2045
|
className="absolute top-4 left-6 h-[2px] bg-gradient-to-r from-cyan-400 to-indigo-500 -z-10 transition-all duration-300"
|
|
1729
|
-
style={{ width: `${(activeStep /
|
|
2046
|
+
style={{ width: `${(activeStep / 4) * 95}%` }}
|
|
1730
2047
|
></div>
|
|
1731
2048
|
<div className="flex flex-col items-center gap-2">
|
|
1732
2049
|
<div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
|
|
@@ -1754,11 +2071,19 @@ Core Instructions:
|
|
|
1754
2071
|
</div>
|
|
1755
2072
|
<div className="flex flex-col items-center gap-2">
|
|
1756
2073
|
<div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
|
|
1757
|
-
activeStep
|
|
2074
|
+
activeStep > 3 ? 'bg-emerald-500 border-emerald-500 text-white' : activeStep === 3 ? 'border-indigo-500 bg-[#0b0f19] text-white shadow-lg shadow-indigo-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
|
|
2075
|
+
}`}>
|
|
2076
|
+
{activeStep > 3 ? <Check size={14} /> : '4'}
|
|
2077
|
+
</div>
|
|
2078
|
+
<span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 3 ? 'text-white' : 'text-gray-500'}`}>Strategy</span>
|
|
2079
|
+
</div>
|
|
2080
|
+
<div className="flex flex-col items-center gap-2">
|
|
2081
|
+
<div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
|
|
2082
|
+
activeStep === 4 ? 'border-emerald-500 bg-[#0b0f19] text-emerald-400 shadow-lg shadow-emerald-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
|
|
1758
2083
|
}`}>
|
|
1759
|
-
|
|
2084
|
+
5
|
|
1760
2085
|
</div>
|
|
1761
|
-
<span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep ===
|
|
2086
|
+
<span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 4 ? 'text-emerald-400' : 'text-gray-500'}`}>Complete</span>
|
|
1762
2087
|
</div>
|
|
1763
2088
|
</div>
|
|
1764
2089
|
|
|
@@ -2120,6 +2445,189 @@ Core Instructions:
|
|
|
2120
2445
|
>
|
|
2121
2446
|
<ArrowLeft size={16} /> Back
|
|
2122
2447
|
</button>
|
|
2448
|
+
<button
|
|
2449
|
+
className="inline-flex items-center justify-center gap-2 px-6 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-lg shadow-indigo-500/20 transition-all cursor-pointer hover:-translate-y-0.5 active:translate-y-0"
|
|
2450
|
+
onClick={() => setActiveStep(3)}
|
|
2451
|
+
>
|
|
2452
|
+
Next Step <ArrowRight size={16} />
|
|
2453
|
+
</button>
|
|
2454
|
+
</div>
|
|
2455
|
+
</>
|
|
2456
|
+
)}
|
|
2457
|
+
</div>
|
|
2458
|
+
)}
|
|
2459
|
+
|
|
2460
|
+
{/* Step 3: Team Strategy */}
|
|
2461
|
+
{activeStep === 3 && (
|
|
2462
|
+
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm">
|
|
2463
|
+
{creating ? (
|
|
2464
|
+
<div className="flex flex-col items-center py-6">
|
|
2465
|
+
<h3 className="text-lg font-bold text-white mb-2 flex items-center gap-2.5">
|
|
2466
|
+
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
2467
|
+
Building Workspace...
|
|
2468
|
+
</h3>
|
|
2469
|
+
<p className="text-xs text-gray-400 mb-8">
|
|
2470
|
+
Setting up your multi-repo workspace. This will take a moment.
|
|
2471
|
+
</p>
|
|
2472
|
+
|
|
2473
|
+
<div className="w-full max-w-md space-y-4">
|
|
2474
|
+
{creationSteps.map((step) => {
|
|
2475
|
+
const isPending = step.status === 'pending';
|
|
2476
|
+
const isRunning = step.status === 'running';
|
|
2477
|
+
const isCompleted = step.status === 'completed';
|
|
2478
|
+
const isFailed = step.status === 'failed';
|
|
2479
|
+
|
|
2480
|
+
return (
|
|
2481
|
+
<div
|
|
2482
|
+
key={step.id}
|
|
2483
|
+
className={`flex items-start gap-4 p-4 rounded-xl border transition-all duration-300 ${
|
|
2484
|
+
isRunning
|
|
2485
|
+
? 'bg-indigo-500/10 border-indigo-500/50 shadow-md shadow-indigo-500/5'
|
|
2486
|
+
: isCompleted
|
|
2487
|
+
? 'bg-emerald-500/5 border-emerald-500/20 opacity-80'
|
|
2488
|
+
: isFailed
|
|
2489
|
+
? 'bg-rose-500/5 border-rose-500/30'
|
|
2490
|
+
: 'bg-gray-900/20 border-gray-800/40 opacity-40'
|
|
2491
|
+
}`}
|
|
2492
|
+
>
|
|
2493
|
+
<div className="mt-0.5">
|
|
2494
|
+
{isRunning && (
|
|
2495
|
+
<div className="relative flex h-5 w-5 items-center justify-center">
|
|
2496
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-indigo-400 opacity-75"></span>
|
|
2497
|
+
<RefreshCw className="animate-spin text-indigo-400 relative" size={16} />
|
|
2498
|
+
</div>
|
|
2499
|
+
)}
|
|
2500
|
+
{isCompleted && (
|
|
2501
|
+
<div className="h-5 w-5 rounded-full bg-emerald-500/20 border border-emerald-500/40 flex items-center justify-center text-emerald-400">
|
|
2502
|
+
<Check size={12} />
|
|
2503
|
+
</div>
|
|
2504
|
+
)}
|
|
2505
|
+
{isFailed && (
|
|
2506
|
+
<div className="h-5 w-5 rounded-full bg-rose-500/20 border border-rose-500/40 flex items-center justify-center text-rose-400">
|
|
2507
|
+
<AlertTriangle size={12} />
|
|
2508
|
+
</div>
|
|
2509
|
+
)}
|
|
2510
|
+
{isPending && (
|
|
2511
|
+
<div className="h-5 w-5 rounded-full border-2 border-gray-800 flex items-center justify-center text-gray-600">
|
|
2512
|
+
<div className="w-1.5 h-1.5 rounded-full bg-gray-800"></div>
|
|
2513
|
+
</div>
|
|
2514
|
+
)}
|
|
2515
|
+
</div>
|
|
2516
|
+
<div className="flex-1 min-w-0">
|
|
2517
|
+
<h4 className={`text-sm font-bold truncate ${
|
|
2518
|
+
isRunning ? 'text-indigo-400' : isCompleted ? 'text-emerald-400' : isFailed ? 'text-rose-400' : 'text-gray-500'
|
|
2519
|
+
}`}>
|
|
2520
|
+
{step.name}
|
|
2521
|
+
</h4>
|
|
2522
|
+
<p className="text-xs text-gray-400 mt-1 font-mono break-words leading-relaxed">
|
|
2523
|
+
{step.message}
|
|
2524
|
+
</p>
|
|
2525
|
+
</div>
|
|
2526
|
+
</div>
|
|
2527
|
+
);
|
|
2528
|
+
})}
|
|
2529
|
+
</div>
|
|
2530
|
+
|
|
2531
|
+
{creationError && (
|
|
2532
|
+
<div className="mt-6 p-4 bg-rose-500/10 border border-rose-500/20 text-rose-450 rounded-xl text-xs w-full max-w-md flex flex-col gap-3">
|
|
2533
|
+
<span className="font-bold flex items-center gap-1.5">
|
|
2534
|
+
<AlertCircle size={14} className="text-rose-450" /> Build Failed
|
|
2535
|
+
</span>
|
|
2536
|
+
<span className="font-mono">{creationError}</span>
|
|
2537
|
+
<button
|
|
2538
|
+
className="w-full mt-2 py-2 px-4 rounded-lg bg-rose-600 hover:bg-rose-700 text-white font-bold text-[11px] transition-colors cursor-pointer"
|
|
2539
|
+
onClick={() => {
|
|
2540
|
+
setCreating(false);
|
|
2541
|
+
setCreationError(null);
|
|
2542
|
+
}}
|
|
2543
|
+
>
|
|
2544
|
+
Dismiss
|
|
2545
|
+
</button>
|
|
2546
|
+
</div>
|
|
2547
|
+
)}
|
|
2548
|
+
</div>
|
|
2549
|
+
) : (
|
|
2550
|
+
<>
|
|
2551
|
+
<div className="mb-8">
|
|
2552
|
+
<div className="flex justify-between items-center mb-2">
|
|
2553
|
+
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">Team Collaboration Strategy</label>
|
|
2554
|
+
<button
|
|
2555
|
+
type="button"
|
|
2556
|
+
className="px-3 py-1.5 rounded-lg bg-indigo-650 hover:bg-indigo-700 text-white font-bold text-[11px] transition-all cursor-pointer flex items-center gap-1.5 disabled:opacity-50"
|
|
2557
|
+
onClick={handleSuggestWorkflow}
|
|
2558
|
+
disabled={suggestingWorkflow}
|
|
2559
|
+
>
|
|
2560
|
+
<Sparkles size={12} className={suggestingWorkflow ? "animate-spin" : ""} />
|
|
2561
|
+
{suggestingWorkflow ? "Analyzing..." : "Auto-Suggest Strategy"}
|
|
2562
|
+
</button>
|
|
2563
|
+
</div>
|
|
2564
|
+
<p className="text-xs text-gray-500 mb-4">
|
|
2565
|
+
Select an agent cooperation pattern. This writes instructions to <code>AGENTS.md</code> directing how the team coordinates.
|
|
2566
|
+
</p>
|
|
2567
|
+
|
|
2568
|
+
{suggestedDifficulty && (
|
|
2569
|
+
<div className="mb-6 p-4 bg-indigo-950/20 border border-indigo-500/30 rounded-xl text-xs flex flex-col gap-2 shadow-inner">
|
|
2570
|
+
<div className="flex items-center gap-2">
|
|
2571
|
+
<span className="font-bold text-gray-300">Suggested Task Difficulty:</span>
|
|
2572
|
+
<span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wide ${
|
|
2573
|
+
suggestedDifficulty === 'simple'
|
|
2574
|
+
? 'bg-emerald-500/20 border border-emerald-500/30 text-emerald-400'
|
|
2575
|
+
: suggestedDifficulty === 'moderate'
|
|
2576
|
+
? 'bg-amber-500/20 border border-amber-500/30 text-amber-400'
|
|
2577
|
+
: 'bg-rose-500/20 border border-rose-500/30 text-rose-450'
|
|
2578
|
+
}`}>
|
|
2579
|
+
{suggestedDifficulty}
|
|
2580
|
+
</span>
|
|
2581
|
+
</div>
|
|
2582
|
+
<p className="text-gray-400 leading-relaxed font-sans">
|
|
2583
|
+
{suggestedRationale}
|
|
2584
|
+
</p>
|
|
2585
|
+
</div>
|
|
2586
|
+
)}
|
|
2587
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
2588
|
+
{workflowTemplates.map((template) => {
|
|
2589
|
+
const isSelected = selectedWorkflowId === template.id;
|
|
2590
|
+
return (
|
|
2591
|
+
<div
|
|
2592
|
+
key={template.id}
|
|
2593
|
+
className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col gap-2 cursor-pointer hover:border-gray-700 transition-all ${
|
|
2594
|
+
isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
|
|
2595
|
+
}`}
|
|
2596
|
+
onClick={() => {
|
|
2597
|
+
setSelectedWorkflowId(template.id);
|
|
2598
|
+
setCustomTeamworkInstructions(template.content);
|
|
2599
|
+
}}
|
|
2600
|
+
>
|
|
2601
|
+
<span className="text-sm font-bold text-white">{template.name}</span>
|
|
2602
|
+
<p className="text-[11px] text-gray-500 leading-relaxed font-sans">
|
|
2603
|
+
{template.description}
|
|
2604
|
+
</p>
|
|
2605
|
+
</div>
|
|
2606
|
+
);
|
|
2607
|
+
})}
|
|
2608
|
+
</div>
|
|
2609
|
+
</div>
|
|
2610
|
+
|
|
2611
|
+
<div className="mb-8">
|
|
2612
|
+
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Cooperation Instructions (AGENTS.md)</label>
|
|
2613
|
+
<p className="text-xs text-gray-500 mb-3 font-sans">
|
|
2614
|
+
You can customize these instructions directly. They will be saved in the workspace context.
|
|
2615
|
+
</p>
|
|
2616
|
+
<textarea
|
|
2617
|
+
className="w-full bg-[#111827] border border-gray-850 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-605 transition-all outline-none text-xs font-mono min-h-[220px] resize-y shadow-inner leading-relaxed"
|
|
2618
|
+
value={customTeamworkInstructions}
|
|
2619
|
+
onChange={(e) => setCustomTeamworkInstructions(e.target.value)}
|
|
2620
|
+
placeholder="Enter custom instructions for how the AI agents should coordinate..."
|
|
2621
|
+
/>
|
|
2622
|
+
</div>
|
|
2623
|
+
|
|
2624
|
+
<div className="flex justify-between">
|
|
2625
|
+
<button
|
|
2626
|
+
className="inline-flex items-center justify-center gap-2 px-5 py-3 rounded-lg text-sm font-semibold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
|
|
2627
|
+
onClick={() => setActiveStep(2)}
|
|
2628
|
+
>
|
|
2629
|
+
<ArrowLeft size={16} /> Back
|
|
2630
|
+
</button>
|
|
2123
2631
|
<button
|
|
2124
2632
|
className="inline-flex items-center justify-center gap-2 px-6 py-3 rounded-lg text-sm font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-lg shadow-indigo-500/20 transition-all cursor-pointer hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-40 disabled:cursor-not-allowed"
|
|
2125
2633
|
disabled={creating}
|
|
@@ -2141,8 +2649,8 @@ Core Instructions:
|
|
|
2141
2649
|
</div>
|
|
2142
2650
|
)}
|
|
2143
2651
|
|
|
2144
|
-
{/* Step
|
|
2145
|
-
{activeStep ===
|
|
2652
|
+
{/* Step 4: Success Screen */}
|
|
2653
|
+
{activeStep === 4 && createdWorkspace && (
|
|
2146
2654
|
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-10 text-center shadow-xl backdrop-blur-sm">
|
|
2147
2655
|
<div className="inline-flex items-center justify-center p-3 rounded-full bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 mb-6">
|
|
2148
2656
|
<Check size={36} />
|
|
@@ -2252,6 +2760,298 @@ Core Instructions:
|
|
|
2252
2760
|
/>
|
|
2253
2761
|
)}
|
|
2254
2762
|
|
|
2763
|
+
{/* View: Workflows View */}
|
|
2764
|
+
{view === 'workflows' && (
|
|
2765
|
+
<div className="max-w-6xl mx-auto">
|
|
2766
|
+
<header className="mb-8">
|
|
2767
|
+
<h1 className="text-3xl font-extrabold tracking-tight text-white mb-2">Team Collaboration Strategies</h1>
|
|
2768
|
+
<p className="text-sm text-gray-400 font-sans">Predefine and review custom teamwork guidelines injected into workspaces' AGENTS.md files.</p>
|
|
2769
|
+
</header>
|
|
2770
|
+
|
|
2771
|
+
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 text-left">
|
|
2772
|
+
{/* Left sidebar: Strategy List */}
|
|
2773
|
+
<div className="lg:col-span-4 flex flex-col gap-4">
|
|
2774
|
+
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-5 shadow-md flex flex-col gap-4">
|
|
2775
|
+
<div className="flex justify-between items-center">
|
|
2776
|
+
<h3 className="text-xs font-semibold uppercase tracking-wider text-gray-500">Strategies</h3>
|
|
2777
|
+
<button
|
|
2778
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-bold bg-indigo-500 hover:bg-indigo-600 text-white transition-all cursor-pointer shadow-md shadow-indigo-500/10"
|
|
2779
|
+
onClick={() => {
|
|
2780
|
+
setIsEditingTemplate(true);
|
|
2781
|
+
setMgtTemplateName('New Strategy');
|
|
2782
|
+
setMgtTemplateContent('# New Strategy\n\nWrite custom teamwork rules here for subagent orchestration...');
|
|
2783
|
+
setSelectedMgtTemplateId(null);
|
|
2784
|
+
setAnalysisResult(null);
|
|
2785
|
+
}}
|
|
2786
|
+
>
|
|
2787
|
+
<PlusCircle size={12} /> Add New
|
|
2788
|
+
</button>
|
|
2789
|
+
</div>
|
|
2790
|
+
|
|
2791
|
+
<div className="flex flex-col gap-2 max-h-[480px] overflow-y-auto pr-1">
|
|
2792
|
+
{workflowTemplates.map((template) => {
|
|
2793
|
+
const isSelected = selectedMgtTemplateId === template.id;
|
|
2794
|
+
return (
|
|
2795
|
+
<div
|
|
2796
|
+
key={template.id}
|
|
2797
|
+
className={`p-3 rounded-lg border text-left cursor-pointer transition-all flex flex-col gap-1.5 ${
|
|
2798
|
+
isSelected
|
|
2799
|
+
? 'border-indigo-500 bg-indigo-500/5'
|
|
2800
|
+
: 'border-gray-800/80 bg-gray-900/10 hover:border-gray-700/60 hover:bg-gray-800/10'
|
|
2801
|
+
}`}
|
|
2802
|
+
onClick={() => {
|
|
2803
|
+
setSelectedMgtTemplateId(template.id);
|
|
2804
|
+
setIsEditingTemplate(false);
|
|
2805
|
+
setMgtTemplateName(template.name);
|
|
2806
|
+
setMgtTemplateContent(template.content);
|
|
2807
|
+
setAnalysisResult(null);
|
|
2808
|
+
}}
|
|
2809
|
+
>
|
|
2810
|
+
<div className="flex items-center justify-between gap-2">
|
|
2811
|
+
<span className={`text-xs font-bold ${isSelected ? 'text-indigo-400' : 'text-white'}`}>
|
|
2812
|
+
{template.name}
|
|
2813
|
+
</span>
|
|
2814
|
+
<span
|
|
2815
|
+
className={`text-[9px] px-1.5 py-0.5 rounded font-mono uppercase tracking-wider font-semibold border ${
|
|
2816
|
+
template.custom
|
|
2817
|
+
? 'text-cyan-400 border-cyan-500/20 bg-cyan-500/5'
|
|
2818
|
+
: 'text-gray-400 border-gray-800 bg-gray-800/20'
|
|
2819
|
+
}`}
|
|
2820
|
+
>
|
|
2821
|
+
{template.custom ? 'Custom' : 'Built-in'}
|
|
2822
|
+
</span>
|
|
2823
|
+
</div>
|
|
2824
|
+
<p className="text-[10px] text-gray-500 line-clamp-2 leading-relaxed font-sans">
|
|
2825
|
+
{template.description}
|
|
2826
|
+
</p>
|
|
2827
|
+
</div>
|
|
2828
|
+
);
|
|
2829
|
+
})}
|
|
2830
|
+
</div>
|
|
2831
|
+
</div>
|
|
2832
|
+
</div>
|
|
2833
|
+
|
|
2834
|
+
{/* Right side: Strategy Detail / Edit Panel */}
|
|
2835
|
+
<div className="lg:col-span-8 flex flex-col gap-6">
|
|
2836
|
+
{(selectedMgtTemplateId || isEditingTemplate) ? (
|
|
2837
|
+
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-6 shadow-md flex flex-col gap-5">
|
|
2838
|
+
<div className="flex justify-between items-start gap-4">
|
|
2839
|
+
<div className="flex-1">
|
|
2840
|
+
{isEditingTemplate ? (
|
|
2841
|
+
<div className="flex flex-col gap-2">
|
|
2842
|
+
<label className="text-xs font-semibold text-gray-400 uppercase tracking-wider">Strategy Name</label>
|
|
2843
|
+
<input
|
|
2844
|
+
type="text"
|
|
2845
|
+
className="w-full bg-[#111827] border border-gray-850 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-2 text-white text-sm outline-none transition-all"
|
|
2846
|
+
value={mgtTemplateName}
|
|
2847
|
+
onChange={(e) => setMgtTemplateName(e.target.value)}
|
|
2848
|
+
placeholder="e.g. Test-Driven Development"
|
|
2849
|
+
/>
|
|
2850
|
+
</div>
|
|
2851
|
+
) : (
|
|
2852
|
+
<div>
|
|
2853
|
+
<h2 className="text-xl font-bold text-white flex items-center gap-2">
|
|
2854
|
+
{mgtTemplateName}
|
|
2855
|
+
{workflowTemplates.find(t => t.id === selectedMgtTemplateId)?.custom && (
|
|
2856
|
+
<span className="text-[10px] px-2 py-0.5 rounded font-mono bg-cyan-500/10 border border-cyan-500/20 text-cyan-400 uppercase">
|
|
2857
|
+
Custom Template
|
|
2858
|
+
</span>
|
|
2859
|
+
)}
|
|
2860
|
+
</h2>
|
|
2861
|
+
<p className="text-xs text-gray-500 mt-1 font-sans">
|
|
2862
|
+
{workflowTemplates.find(t => t.id === selectedMgtTemplateId)?.description || 'Custom strategy template.'}
|
|
2863
|
+
</p>
|
|
2864
|
+
</div>
|
|
2865
|
+
)}
|
|
2866
|
+
</div>
|
|
2867
|
+
|
|
2868
|
+
<div className="flex gap-2">
|
|
2869
|
+
{isEditingTemplate ? (
|
|
2870
|
+
<>
|
|
2871
|
+
<button
|
|
2872
|
+
className="inline-flex items-center gap-1.5 px-3 py-2 rounded-lg text-xs font-bold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
|
|
2873
|
+
onClick={() => {
|
|
2874
|
+
if (selectedMgtTemplateId) {
|
|
2875
|
+
const original = workflowTemplates.find(t => t.id === selectedMgtTemplateId);
|
|
2876
|
+
if (original) {
|
|
2877
|
+
setMgtTemplateName(original.name);
|
|
2878
|
+
setMgtTemplateContent(original.content);
|
|
2879
|
+
}
|
|
2880
|
+
setIsEditingTemplate(false);
|
|
2881
|
+
} else {
|
|
2882
|
+
setIsEditingTemplate(false);
|
|
2883
|
+
if (workflowTemplates.length > 0) {
|
|
2884
|
+
const first = workflowTemplates[0];
|
|
2885
|
+
setSelectedMgtTemplateId(first.id);
|
|
2886
|
+
setMgtTemplateName(first.name);
|
|
2887
|
+
setMgtTemplateContent(first.content);
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
}}
|
|
2891
|
+
>
|
|
2892
|
+
Cancel
|
|
2893
|
+
</button>
|
|
2894
|
+
<button
|
|
2895
|
+
className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-xs font-bold bg-indigo-500 hover:bg-indigo-600 text-white transition-all cursor-pointer shadow-md shadow-indigo-500/10"
|
|
2896
|
+
onClick={handleSaveTemplate}
|
|
2897
|
+
disabled={savingTemplate}
|
|
2898
|
+
>
|
|
2899
|
+
{savingTemplate ? 'Saving...' : 'Save Strategy'}
|
|
2900
|
+
</button>
|
|
2901
|
+
</>
|
|
2902
|
+
) : (
|
|
2903
|
+
<>
|
|
2904
|
+
{workflowTemplates.find(t => t.id === selectedMgtTemplateId)?.custom && (
|
|
2905
|
+
<>
|
|
2906
|
+
<button
|
|
2907
|
+
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-xs font-bold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
|
|
2908
|
+
onClick={() => setIsEditingTemplate(true)}
|
|
2909
|
+
>
|
|
2910
|
+
Edit
|
|
2911
|
+
</button>
|
|
2912
|
+
<button
|
|
2913
|
+
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-xs font-bold bg-rose-500/10 border border-rose-500/20 hover:bg-rose-500/20 text-rose-400 transition-all cursor-pointer"
|
|
2914
|
+
onClick={() => handleDeleteTemplate(selectedMgtTemplateId!)}
|
|
2915
|
+
disabled={deletingTemplate}
|
|
2916
|
+
>
|
|
2917
|
+
<Trash2 size={12} /> Delete
|
|
2918
|
+
</button>
|
|
2919
|
+
</>
|
|
2920
|
+
)}
|
|
2921
|
+
{!workflowTemplates.find(t => t.id === selectedMgtTemplateId)?.custom && (
|
|
2922
|
+
<button
|
|
2923
|
+
className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-xs font-bold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
|
|
2924
|
+
onClick={() => {
|
|
2925
|
+
setIsEditingTemplate(true);
|
|
2926
|
+
setMgtTemplateName(`${mgtTemplateName} Copy`);
|
|
2927
|
+
setMgtTemplateContent(mgtTemplateContent);
|
|
2928
|
+
setSelectedMgtTemplateId(null);
|
|
2929
|
+
setAnalysisResult(null);
|
|
2930
|
+
}}
|
|
2931
|
+
>
|
|
2932
|
+
Duplicate & Customize
|
|
2933
|
+
</button>
|
|
2934
|
+
)}
|
|
2935
|
+
</>
|
|
2936
|
+
)}
|
|
2937
|
+
</div>
|
|
2938
|
+
</div>
|
|
2939
|
+
|
|
2940
|
+
<div className="flex flex-col gap-2">
|
|
2941
|
+
<label className="text-xs font-semibold text-gray-400 uppercase tracking-wider">Guidelines Markdown</label>
|
|
2942
|
+
<textarea
|
|
2943
|
+
className="w-full bg-[#111827] border border-gray-850 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-xs font-mono min-h-[250px] resize-y shadow-inner leading-relaxed"
|
|
2944
|
+
value={mgtTemplateContent}
|
|
2945
|
+
onChange={(e) => setMgtTemplateContent(e.target.value)}
|
|
2946
|
+
disabled={!isEditingTemplate}
|
|
2947
|
+
placeholder="Write cooperation guidelines in Markdown..."
|
|
2948
|
+
/>
|
|
2949
|
+
</div>
|
|
2950
|
+
|
|
2951
|
+
{!isEditingTemplate && selectedMgtTemplateId && (
|
|
2952
|
+
<div className="border-t border-gray-800/60 pt-5 flex flex-col gap-4">
|
|
2953
|
+
<div className="flex flex-col gap-4">
|
|
2954
|
+
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
|
2955
|
+
<div className="flex flex-col">
|
|
2956
|
+
<span className="text-xs font-bold text-white flex items-center gap-1.5">
|
|
2957
|
+
<Sparkles size={14} className="text-indigo-400" /> AI Strategy Analysis
|
|
2958
|
+
</span>
|
|
2959
|
+
<span className="text-[10px] text-gray-500 mt-0.5 font-sans">Select an AI assistant harness installed on your system to inspect these guidelines.</span>
|
|
2960
|
+
</div>
|
|
2961
|
+
|
|
2962
|
+
<div className="flex items-center gap-3 w-full sm:w-auto justify-end">
|
|
2963
|
+
<div className="flex items-center gap-2">
|
|
2964
|
+
<span className="text-[10px] text-gray-400 font-semibold uppercase tracking-wider font-sans">Harness:</span>
|
|
2965
|
+
<select
|
|
2966
|
+
className="bg-gray-900 border border-gray-800 text-xs text-white rounded-lg px-2.5 py-1.5 focus:border-indigo-500 transition-all outline-none disabled:opacity-40"
|
|
2967
|
+
value={selectedInspectAssistant}
|
|
2968
|
+
onChange={(e) => setSelectedInspectAssistant(e.target.value)}
|
|
2969
|
+
disabled={aiAssistants.filter(ai => ai.detected && ai.command).length === 0}
|
|
2970
|
+
>
|
|
2971
|
+
{aiAssistants.filter(ai => ai.detected && ai.command).length > 0 ? (
|
|
2972
|
+
aiAssistants
|
|
2973
|
+
.filter(ai => ai.detected && ai.command)
|
|
2974
|
+
.map(ai => (
|
|
2975
|
+
<option key={ai.name} value={ai.name}>
|
|
2976
|
+
{ai.displayName}
|
|
2977
|
+
</option>
|
|
2978
|
+
))
|
|
2979
|
+
) : (
|
|
2980
|
+
<option value="">No Harness Found</option>
|
|
2981
|
+
)}
|
|
2982
|
+
</select>
|
|
2983
|
+
</div>
|
|
2984
|
+
|
|
2985
|
+
<button
|
|
2986
|
+
className="inline-flex items-center gap-1.5 px-4 py-2 rounded-lg text-xs font-bold bg-indigo-500 hover:bg-indigo-600 text-white transition-all cursor-pointer shadow-md shadow-indigo-500/10 disabled:opacity-40"
|
|
2987
|
+
onClick={() => handleAnalyzeTemplate(selectedMgtTemplateId, mgtTemplateContent, selectedInspectAssistant)}
|
|
2988
|
+
disabled={analyzingTemplate || aiAssistants.filter(ai => ai.detected && ai.command).length === 0}
|
|
2989
|
+
>
|
|
2990
|
+
{analyzingTemplate ? (
|
|
2991
|
+
<>
|
|
2992
|
+
<RefreshCw className="animate-spin" size={12} /> Inspecting...
|
|
2993
|
+
</>
|
|
2994
|
+
) : (
|
|
2995
|
+
<>
|
|
2996
|
+
<Cpu size={12} /> Inspect Strategy
|
|
2997
|
+
</>
|
|
2998
|
+
)}
|
|
2999
|
+
</button>
|
|
3000
|
+
</div>
|
|
3001
|
+
</div>
|
|
3002
|
+
|
|
3003
|
+
<div className="flex flex-col gap-2">
|
|
3004
|
+
<label className="text-[10px] font-semibold text-gray-400 uppercase tracking-wider text-left">Evaluation Focus / Instructions (Optional)</label>
|
|
3005
|
+
<textarea
|
|
3006
|
+
className="w-full bg-[#111827]/40 border border-gray-850 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-2 text-white placeholder-gray-600 transition-all outline-none text-xs min-h-[60px] resize-y leading-relaxed font-sans"
|
|
3007
|
+
value={mgtAnalysisComment}
|
|
3008
|
+
onChange={(e) => setMgtAnalysisComment(e.target.value)}
|
|
3009
|
+
placeholder="e.g. Focus on checking if timeouts are handled well, check subagent roles coordination..."
|
|
3010
|
+
disabled={analyzingTemplate}
|
|
3011
|
+
/>
|
|
3012
|
+
</div>
|
|
3013
|
+
</div>
|
|
3014
|
+
|
|
3015
|
+
{analysisResult && (
|
|
3016
|
+
<div className="flex flex-col gap-4">
|
|
3017
|
+
<div className="bg-[#1e1e38]/20 border border-indigo-500/10 rounded-xl p-5 text-xs text-gray-300 leading-relaxed font-sans max-h-[300px] overflow-y-auto text-left whitespace-pre-wrap select-text">
|
|
3018
|
+
{analysisResult}
|
|
3019
|
+
</div>
|
|
3020
|
+
{suggestedImprovement && (
|
|
3021
|
+
<div className="flex justify-end">
|
|
3022
|
+
<button
|
|
3023
|
+
className="inline-flex items-center gap-1.5 px-4 py-2.5 rounded-lg text-xs font-bold bg-green-600 hover:bg-green-700 text-white transition-all cursor-pointer shadow-md shadow-green-600/10"
|
|
3024
|
+
onClick={() => {
|
|
3025
|
+
setMgtTemplateContent(suggestedImprovement);
|
|
3026
|
+
setIsEditingTemplate(true);
|
|
3027
|
+
setSuggestedImprovement(null);
|
|
3028
|
+
setAnalysisResult(null);
|
|
3029
|
+
showToast('Suggested improvements applied! Click Save to persist changes.', 'success');
|
|
3030
|
+
}}
|
|
3031
|
+
>
|
|
3032
|
+
<CheckCircle size={14} /> Apply Suggested Improvements
|
|
3033
|
+
</button>
|
|
3034
|
+
</div>
|
|
3035
|
+
)}
|
|
3036
|
+
</div>
|
|
3037
|
+
)}
|
|
3038
|
+
</div>
|
|
3039
|
+
)}
|
|
3040
|
+
</div>
|
|
3041
|
+
) : (
|
|
3042
|
+
<div className="h-[400px] bg-[#111827]/10 border border-gray-800/60 border-dashed rounded-xl flex flex-col items-center justify-center text-center p-6">
|
|
3043
|
+
<FolderOpen size={36} className="text-gray-600 mb-3" />
|
|
3044
|
+
<span className="text-sm font-semibold text-gray-400">No strategy template selected</span>
|
|
3045
|
+
<p className="text-xs text-gray-500 mt-1 max-w-sm font-sans">
|
|
3046
|
+
Select a template from the list to view, edit, or analyze it, or add a new custom teamwork workflow template.
|
|
3047
|
+
</p>
|
|
3048
|
+
</div>
|
|
3049
|
+
)}
|
|
3050
|
+
</div>
|
|
3051
|
+
</div>
|
|
3052
|
+
</div>
|
|
3053
|
+
)}
|
|
3054
|
+
|
|
2255
3055
|
{/* View 3: Settings View */}
|
|
2256
3056
|
{view === 'settings' && config && (
|
|
2257
3057
|
<div className="max-w-4xl mx-auto">
|
|
@@ -2342,22 +3142,115 @@ Core Instructions:
|
|
|
2342
3142
|
</div>
|
|
2343
3143
|
|
|
2344
3144
|
<div className="flex flex-col md:col-span-2 border-t border-gray-800/60 pt-6 mt-2">
|
|
2345
|
-
<h3 className="text-sm font-bold text-white mb-2">
|
|
2346
|
-
<div className="flex
|
|
2347
|
-
<
|
|
2348
|
-
|
|
2349
|
-
id="packContextXml"
|
|
2350
|
-
className="w-4 h-4 rounded border-gray-800 bg-[#111827] text-indigo-600 focus:ring-indigo-500 cursor-pointer"
|
|
2351
|
-
checked={config.packContextXml || false}
|
|
2352
|
-
onChange={(e) => setConfig({ ...config, packContextXml: e.target.checked })}
|
|
2353
|
-
/>
|
|
2354
|
-
<label htmlFor="packContextXml" className="text-xs font-semibold text-white cursor-pointer select-none">
|
|
2355
|
-
Pack Codebase Context with Repomix (XML)
|
|
3145
|
+
<h3 className="text-sm font-bold text-white mb-2">Workspace Storage Settings</h3>
|
|
3146
|
+
<div className="flex flex-col gap-1.5">
|
|
3147
|
+
<label htmlFor="storageProvider" className="text-xs font-semibold text-white select-none">
|
|
3148
|
+
Storage Provider
|
|
2356
3149
|
</label>
|
|
3150
|
+
<select
|
|
3151
|
+
id="storageProvider"
|
|
3152
|
+
className="w-full max-w-md text-xs bg-[#111827] border border-gray-800 rounded-lg p-2 text-white focus:outline-none focus:border-indigo-500"
|
|
3153
|
+
value={config.storageProvider || 'local'}
|
|
3154
|
+
onChange={(e) => {
|
|
3155
|
+
const val = e.target.value;
|
|
3156
|
+
const newConf = { ...config, storageProvider: val };
|
|
3157
|
+
const selectedAdapter = adapters.find(a => a.name === val);
|
|
3158
|
+
if (selectedAdapter?.configFields?.length) {
|
|
3159
|
+
if (!newConf.adapterConfig) newConf.adapterConfig = {};
|
|
3160
|
+
if (!newConf.adapterConfig[val]) newConf.adapterConfig[val] = {};
|
|
3161
|
+
selectedAdapter.configFields.forEach(f => {
|
|
3162
|
+
if (newConf.adapterConfig![val][f.key] === undefined && f.default !== undefined) {
|
|
3163
|
+
newConf.adapterConfig![val][f.key] = f.default;
|
|
3164
|
+
}
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
3167
|
+
setConfig(newConf);
|
|
3168
|
+
}}
|
|
3169
|
+
>
|
|
3170
|
+
{adapters.length > 0 ? (
|
|
3171
|
+
adapters.map((a) => (
|
|
3172
|
+
<option key={a.name} value={a.name}>
|
|
3173
|
+
{a.displayName}
|
|
3174
|
+
</option>
|
|
3175
|
+
))
|
|
3176
|
+
) : (
|
|
3177
|
+
<>
|
|
3178
|
+
<option value="local">Local Workspace (Folders)</option>
|
|
3179
|
+
<option value="central-vault">Obsidian Central Vault</option>
|
|
3180
|
+
</>
|
|
3181
|
+
)}
|
|
3182
|
+
</select>
|
|
2357
3183
|
</div>
|
|
2358
3184
|
<span className="text-[10px] text-gray-500 mt-1">
|
|
2359
|
-
|
|
3185
|
+
{adapters.find(a => a.name === (config.storageProvider || 'local'))?.description ||
|
|
3186
|
+
'Choose where to store maps, plans, and knowledge files. Centralized vault keeps repositories 100% clean and allows Obsidian integration.'}
|
|
2360
3187
|
</span>
|
|
3188
|
+
|
|
3189
|
+
{/* Dynamic config fields */}
|
|
3190
|
+
{(() => {
|
|
3191
|
+
const activeProv = config.storageProvider || 'local';
|
|
3192
|
+
const selectedAdapter = adapters.find((a) => a.name === activeProv);
|
|
3193
|
+
if (!selectedAdapter || !selectedAdapter.configFields || selectedAdapter.configFields.length === 0) {
|
|
3194
|
+
return null;
|
|
3195
|
+
}
|
|
3196
|
+
return (
|
|
3197
|
+
<div className="mt-4 p-4 rounded-lg bg-gray-950/40 border border-gray-800/80 max-w-md flex flex-col gap-4">
|
|
3198
|
+
<h4 className="text-xs font-semibold text-gray-300 uppercase tracking-wider">
|
|
3199
|
+
{selectedAdapter.displayName} Settings
|
|
3200
|
+
</h4>
|
|
3201
|
+
{selectedAdapter.configFields.map((field) => {
|
|
3202
|
+
const value = config.adapterConfig?.[activeProv]?.[field.key] ?? field.default ?? '';
|
|
3203
|
+
const updateField = (val: any) => {
|
|
3204
|
+
const newAdapterConfig = {
|
|
3205
|
+
...(config.adapterConfig || {}),
|
|
3206
|
+
[activeProv]: {
|
|
3207
|
+
...(config.adapterConfig?.[activeProv] || {}),
|
|
3208
|
+
[field.key]: val,
|
|
3209
|
+
},
|
|
3210
|
+
};
|
|
3211
|
+
setConfig({ ...config, adapterConfig: newAdapterConfig });
|
|
3212
|
+
};
|
|
3213
|
+
|
|
3214
|
+
if (field.type === 'boolean') {
|
|
3215
|
+
return (
|
|
3216
|
+
<label key={field.key} className="flex items-start gap-3 cursor-pointer select-none">
|
|
3217
|
+
<input
|
|
3218
|
+
type="checkbox"
|
|
3219
|
+
className="mt-0.5 rounded border-gray-800 bg-gray-950 text-indigo-500 focus:ring-0 focus:ring-offset-0"
|
|
3220
|
+
checked={!!value}
|
|
3221
|
+
onChange={(e) => updateField(e.target.checked)}
|
|
3222
|
+
/>
|
|
3223
|
+
<div className="flex flex-col">
|
|
3224
|
+
<span className="text-xs font-medium text-slate-200">{field.label}</span>
|
|
3225
|
+
{field.description && (
|
|
3226
|
+
<span className="text-[10px] text-gray-500 leading-normal mt-0.5">{field.description}</span>
|
|
3227
|
+
)}
|
|
3228
|
+
</div>
|
|
3229
|
+
</label>
|
|
3230
|
+
);
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
return (
|
|
3234
|
+
<div key={field.key} className="flex flex-col gap-1">
|
|
3235
|
+
<label className="text-[11px] font-semibold text-slate-300">{field.label}</label>
|
|
3236
|
+
<input
|
|
3237
|
+
type={field.type === 'number' ? 'number' : 'text'}
|
|
3238
|
+
className="w-full text-xs bg-slate-950 border border-gray-800 rounded p-2 text-white focus:outline-none focus:border-indigo-500"
|
|
3239
|
+
value={value}
|
|
3240
|
+
onChange={(e) => {
|
|
3241
|
+
const val = field.type === 'number' ? Number(e.target.value) : e.target.value;
|
|
3242
|
+
updateField(val);
|
|
3243
|
+
}}
|
|
3244
|
+
/>
|
|
3245
|
+
{field.description && (
|
|
3246
|
+
<span className="text-[10px] text-gray-500 leading-normal mt-0.5">{field.description}</span>
|
|
3247
|
+
)}
|
|
3248
|
+
</div>
|
|
3249
|
+
);
|
|
3250
|
+
})}
|
|
3251
|
+
</div>
|
|
3252
|
+
);
|
|
3253
|
+
})()}
|
|
2361
3254
|
</div>
|
|
2362
3255
|
</div>
|
|
2363
3256
|
|
|
@@ -2449,6 +3342,23 @@ Core Instructions:
|
|
|
2449
3342
|
/>
|
|
2450
3343
|
<span className="text-[10px] text-gray-500 mt-1">Exact name of the model registered on the server (e.g., <code>qwen2.5-coder:1.5b</code>).</span>
|
|
2451
3344
|
</div>
|
|
3345
|
+
|
|
3346
|
+
{config.localLlm.provider === 'openai-compatible' && (
|
|
3347
|
+
<div className="flex flex-col md:col-span-2">
|
|
3348
|
+
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">API Key (Optional)</label>
|
|
3349
|
+
<input
|
|
3350
|
+
type="password"
|
|
3351
|
+
className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg px-4 py-3 text-white placeholder-gray-605 transition-all outline-none text-sm shadow-inner"
|
|
3352
|
+
value={config.localLlm.apiKey || ''}
|
|
3353
|
+
placeholder="sk-..."
|
|
3354
|
+
onChange={(e) => setConfig({
|
|
3355
|
+
...config,
|
|
3356
|
+
localLlm: { ...config.localLlm!, apiKey: e.target.value }
|
|
3357
|
+
})}
|
|
3358
|
+
/>
|
|
3359
|
+
<span className="text-[10px] text-gray-500 mt-1">Bearer token for authenticating with cloud LLM providers (e.g. OpenAI, DeepSeek, OpenRouter).</span>
|
|
3360
|
+
</div>
|
|
3361
|
+
)}
|
|
2452
3362
|
</div>
|
|
2453
3363
|
|
|
2454
3364
|
<div className="flex items-center justify-between pt-4 border-t border-gray-800/40">
|