@mrpatronz/nexusflow 0.2.18 → 1.3.0
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 +27 -3
- package/dist/analyzers/index.d.ts +23 -0
- package/dist/analyzers/index.d.ts.map +1 -1
- package/dist/analyzers/index.js +62 -0
- package/dist/analyzers/index.js.map +1 -1
- package/dist/commands/commands.test.js +20 -6
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/commit.d.ts +2 -0
- package/dist/commands/commit.d.ts.map +1 -1
- package/dist/commands/commit.js +27 -5
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/diff.d.ts +9 -2
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +36 -12
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/handoff.js +2 -2
- package/dist/commands/handoff.js.map +1 -1
- package/dist/commands/refresh.d.ts +4 -2
- package/dist/commands/refresh.d.ts.map +1 -1
- package/dist/commands/refresh.js +20 -36
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/schedule.d.ts +35 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +151 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +58 -50
- package/dist/commands/sync.js.map +1 -1
- package/dist/core/analysis-cache.d.ts +58 -0
- package/dist/core/analysis-cache.d.ts.map +1 -0
- package/dist/core/analysis-cache.js +116 -0
- package/dist/core/analysis-cache.js.map +1 -0
- package/dist/core/analysis-cache.test.d.ts +2 -0
- package/dist/core/analysis-cache.test.d.ts.map +1 -0
- package/dist/core/analysis-cache.test.js +108 -0
- package/dist/core/analysis-cache.test.js.map +1 -0
- package/dist/core/refresh.d.ts +41 -0
- package/dist/core/refresh.d.ts.map +1 -0
- package/dist/core/refresh.js +60 -0
- package/dist/core/refresh.js.map +1 -0
- package/dist/core/scheduler.d.ts +117 -0
- package/dist/core/scheduler.d.ts.map +1 -0
- package/dist/core/scheduler.js +247 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/core/scheduler.test.d.ts +2 -0
- package/dist/core/scheduler.test.d.ts.map +1 -0
- package/dist/core/scheduler.test.js +111 -0
- package/dist/core/scheduler.test.js.map +1 -0
- package/dist/core/sync.d.ts +50 -0
- package/dist/core/sync.d.ts.map +1 -0
- package/dist/core/sync.js +92 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/core/workspace-state.d.ts +51 -0
- package/dist/core/workspace-state.d.ts.map +1 -0
- package/dist/core/workspace-state.js +108 -0
- package/dist/core/workspace-state.js.map +1 -0
- package/dist/core/workspace-state.test.d.ts +2 -0
- package/dist/core/workspace-state.test.d.ts.map +1 -0
- package/dist/core/workspace-state.test.js +84 -0
- package/dist/core/workspace-state.test.js.map +1 -0
- package/dist/generators/index.d.ts +7 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +14 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/gui/assets/index-Bdbld6yY.css +1 -0
- package/dist/gui/assets/index-D8iJPvnk.js +26 -0
- package/dist/gui/index.html +2 -2
- package/dist/index.js +107 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +42 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +175 -13
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +66 -0
- package/dist/server.test.js.map +1 -1
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/multi-git.d.ts +54 -8
- package/dist/utils/multi-git.d.ts.map +1 -1
- package/dist/utils/multi-git.js +129 -21
- package/dist/utils/multi-git.js.map +1 -1
- package/dist/utils/multi-git.test.d.ts +2 -0
- package/dist/utils/multi-git.test.d.ts.map +1 -0
- package/dist/utils/multi-git.test.js +125 -0
- package/dist/utils/multi-git.test.js.map +1 -0
- package/package.json +13 -1
- package/.agents/skills/ccc/SKILL.md +0 -71
- package/.agents/skills/ccc/references/management.md +0 -110
- package/.agents/skills/ccc/references/settings.md +0 -126
- package/.github/dependabot.yml +0 -52
- package/.github/workflows/ci.yml +0 -44
- package/.github/workflows/dependabot-merge.yml +0 -53
- package/.github/workflows/release-desktop.yml +0 -67
- package/.github/workflows/release-npm.yml +0 -65
- package/.github/workflows/release-vscode.yml +0 -61
- package/.vscode/launch.json +0 -17
- package/.vscode/tasks.json +0 -17
- package/GETTING_STARTED.md +0 -114
- package/desktop/build-installer.js +0 -228
- package/desktop/build.js +0 -118
- package/desktop/installer/nexusflow.iss +0 -35
- package/desktop/neutralino.config.json +0 -39
- package/desktop/package-lock.json +0 -1060
- package/desktop/package.json +0 -14
- package/dist/gui/assets/index-CB-jWded.css +0 -1
- package/dist/gui/assets/index-DDXpZZu3.js +0 -25
- package/extension/media/icon.svg +0 -1
- package/extension/package-lock.json +0 -6044
- package/extension/package.json +0 -117
- package/extension/src/extension.ts +0 -653
- package/extension/tsconfig.json +0 -21
- package/gui/README.md +0 -73
- package/gui/e2e/wizard.spec.ts +0 -346
- package/gui/eslint.config.js +0 -26
- package/gui/index.html +0 -17
- package/gui/package-lock.json +0 -3079
- package/gui/package.json +0 -36
- package/gui/playwright.config.ts +0 -41
- package/gui/public/app_logo.png +0 -0
- package/gui/public/favicon.svg +0 -1
- package/gui/public/icons.svg +0 -24
- package/gui/src/App.css +0 -1
- package/gui/src/App.tsx +0 -3632
- package/gui/src/assets/hero.png +0 -0
- package/gui/src/assets/react.svg +0 -1
- package/gui/src/assets/vite.svg +0 -1
- package/gui/src/features/changes/ChangesViewer.tsx +0 -388
- package/gui/src/features/knowledge/KnowledgeBase.tsx +0 -84
- package/gui/src/features/onboarding/OnboardingWizard.tsx +0 -230
- package/gui/src/features/plan/ImplementationPlan.tsx +0 -32
- package/gui/src/features/services/ServiceConsole.tsx +0 -250
- package/gui/src/features/sessions/SessionHistory.tsx +0 -195
- package/gui/src/features/workspace/WorkspaceList.tsx +0 -666
- package/gui/src/index.css +0 -152
- package/gui/src/main.tsx +0 -94
- package/gui/src/types.ts +0 -62
- package/gui/tsconfig.app.json +0 -25
- package/gui/tsconfig.json +0 -7
- package/gui/tsconfig.node.json +0 -24
- package/gui/vite.config.ts +0 -12
- package/resources/workflows/plan-implement-review.md +0 -8
- package/resources/workflows/research-verify.md +0 -6
- package/resources/workflows/solo-developer.md +0 -3
- package/scripts/simulate-workspaces.ts +0 -55
- package/skills-lock.json +0 -11
- package/src/analyzers/detect-apis.ts +0 -290
- package/src/analyzers/detect-deps.ts +0 -315
- package/src/analyzers/detect-existing.ts +0 -74
- package/src/analyzers/detect-ports.ts +0 -110
- package/src/analyzers/index.ts +0 -97
- package/src/analyzers/messaging-analyzer.ts +0 -254
- package/src/analyzers/readme-summarizer.ts +0 -102
- package/src/analyzers/run-analyzer.ts +0 -269
- package/src/analyzers/tech-stack.ts +0 -283
- package/src/commands/adapter.ts +0 -348
- package/src/commands/add-repo.ts +0 -156
- package/src/commands/commands.test.ts +0 -156
- package/src/commands/commit.ts +0 -131
- package/src/commands/config.ts +0 -52
- package/src/commands/create.ts +0 -204
- package/src/commands/desktop.ts +0 -128
- package/src/commands/diff.ts +0 -125
- package/src/commands/doctor.ts +0 -370
- package/src/commands/handoff.ts +0 -266
- package/src/commands/init.ts +0 -134
- package/src/commands/list.ts +0 -46
- package/src/commands/logs.ts +0 -63
- package/src/commands/mcp.ts +0 -94
- package/src/commands/open.ts +0 -129
- package/src/commands/refresh.ts +0 -137
- package/src/commands/remove.ts +0 -98
- package/src/commands/start.ts +0 -117
- package/src/commands/status.ts +0 -54
- package/src/commands/stop.ts +0 -55
- package/src/commands/sync.ts +0 -147
- package/src/commands/tui.ts +0 -484
- package/src/commands/ui.ts +0 -111
- package/src/core/adapters/local-storage.ts +0 -66
- package/src/core/adapters/obsidian-storage.ts +0 -151
- package/src/core/adapters/registry.ts +0 -44
- package/src/core/adapters/storage.test.ts +0 -174
- package/src/core/adapters/vault-storage.ts +0 -86
- package/src/core/config.test.ts +0 -96
- package/src/core/config.ts +0 -140
- package/src/core/graph.ts +0 -344
- package/src/core/plugins/index.ts +0 -17
- package/src/core/plugins/loader.ts +0 -36
- package/src/core/ports/storage.ts +0 -71
- package/src/core/scanner.test.ts +0 -61
- package/src/core/scanner.ts +0 -91
- package/src/core/storage.ts +0 -37
- package/src/core/workspace.ts +0 -487
- package/src/core/worktree.ts +0 -120
- package/src/generators/antigravity.ts +0 -32
- package/src/generators/base.ts +0 -243
- package/src/generators/claude.ts +0 -34
- package/src/generators/codex.ts +0 -48
- package/src/generators/copilot.ts +0 -56
- package/src/generators/cursor.ts +0 -44
- package/src/generators/diff-context.ts +0 -75
- package/src/generators/index.ts +0 -322
- package/src/generators/map-generator.test.ts +0 -159
- package/src/generators/map-generator.ts +0 -539
- package/src/generators/plan-generator.ts +0 -483
- package/src/generators/skills-generator.ts +0 -259
- package/src/index.ts +0 -539
- package/src/mcp/server.ts +0 -294
- package/src/orchestration/detect.ts +0 -313
- package/src/orchestration/index.ts +0 -7
- package/src/orchestration/runner.ts +0 -283
- package/src/server.test.ts +0 -618
- package/src/server.ts +0 -1314
- package/src/types.ts +0 -415
- package/src/utils/detect-ai.test.ts +0 -44
- package/src/utils/detect-ai.ts +0 -84
- package/src/utils/detect-editors.test.ts +0 -48
- package/src/utils/detect-editors.ts +0 -57
- package/src/utils/git.test.ts +0 -74
- package/src/utils/git.ts +0 -117
- package/src/utils/local-ai.test.ts +0 -130
- package/src/utils/local-ai.ts +0 -116
- package/src/utils/multi-git.ts +0 -313
- package/src/utils/prompts.ts +0 -209
- package/src/utils/session-finder.ts +0 -483
- package/src/utils/system-scanner.test.ts +0 -89
- package/src/utils/system-scanner.ts +0 -96
- package/src/utils/update-check.ts +0 -286
- package/src/utils/workflow-advisor.ts +0 -118
- package/src/utils/workflows.test.ts +0 -114
- package/src/utils/workflows.ts +0 -178
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -14
package/gui/src/App.tsx
DELETED
|
@@ -1,3632 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
PlusCircle,
|
|
4
|
-
FolderGit2,
|
|
5
|
-
Settings as SettingsIcon,
|
|
6
|
-
Terminal,
|
|
7
|
-
Play,
|
|
8
|
-
Check,
|
|
9
|
-
AlertTriangle,
|
|
10
|
-
AlertCircle,
|
|
11
|
-
FolderOpen,
|
|
12
|
-
ArrowRight,
|
|
13
|
-
ArrowLeft,
|
|
14
|
-
RefreshCw,
|
|
15
|
-
Search,
|
|
16
|
-
Sparkles,
|
|
17
|
-
Copy,
|
|
18
|
-
X,
|
|
19
|
-
Cpu,
|
|
20
|
-
Workflow,
|
|
21
|
-
Trash2,
|
|
22
|
-
CheckCircle,
|
|
23
|
-
} from 'lucide-react';
|
|
24
|
-
import './App.css';
|
|
25
|
-
import { WorkspaceList } from './features/workspace/WorkspaceList.js';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// Types matched with src/types.ts
|
|
29
|
-
interface LocalLlmConfig {
|
|
30
|
-
enabled: boolean;
|
|
31
|
-
provider: 'ollama' | 'openai-compatible';
|
|
32
|
-
endpoint: string;
|
|
33
|
-
model: string;
|
|
34
|
-
apiKey?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
interface NexusFlowConfig {
|
|
38
|
-
version: string;
|
|
39
|
-
devDir: string;
|
|
40
|
-
workspacesDir: string;
|
|
41
|
-
defaultAssistant: string | null;
|
|
42
|
-
defaultEditor?: string | null;
|
|
43
|
-
scanDepth: number;
|
|
44
|
-
localLlm?: LocalLlmConfig;
|
|
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[];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface DetectedAI {
|
|
67
|
-
name: string;
|
|
68
|
-
displayName: string;
|
|
69
|
-
detected: boolean;
|
|
70
|
-
command?: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
interface DetectedEditor {
|
|
74
|
-
name: string;
|
|
75
|
-
command: string;
|
|
76
|
-
detected: boolean;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
interface RepoInfo {
|
|
80
|
-
name: string;
|
|
81
|
-
path: string;
|
|
82
|
-
defaultBranch: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
interface Feature {
|
|
86
|
-
id: string;
|
|
87
|
-
branchName: string;
|
|
88
|
-
description: string;
|
|
89
|
-
repos: string[];
|
|
90
|
-
assistants: string[];
|
|
91
|
-
workspacePath: string;
|
|
92
|
-
createdAt: string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
interface ServiceConfig {
|
|
96
|
-
name: string;
|
|
97
|
-
cwd: string;
|
|
98
|
-
command: string;
|
|
99
|
-
args: string[];
|
|
100
|
-
port?: number;
|
|
101
|
-
source: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
interface OrchestrationDetection {
|
|
105
|
-
tool: string;
|
|
106
|
-
configPath: string;
|
|
107
|
-
startCommand: string;
|
|
108
|
-
stopCommand: string;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
interface RunningService {
|
|
112
|
-
name: string;
|
|
113
|
-
pid: number;
|
|
114
|
-
config: ServiceConfig;
|
|
115
|
-
startedAt: string;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const API_BASE = (import.meta.env.DEV || (typeof window !== 'undefined' && (window as any).Neutralino)) ? 'http://localhost:3000' : '';
|
|
119
|
-
const isVsCode = new URLSearchParams(window.location.search).get('env') === 'vscode';
|
|
120
|
-
let toastIdCounter = 0;
|
|
121
|
-
|
|
122
|
-
export default function App() {
|
|
123
|
-
const [view, setView] = useState<'guide' | 'create' | 'workspaces' | 'settings' | 'workflows'>('guide');
|
|
124
|
-
|
|
125
|
-
// Toast State
|
|
126
|
-
interface Toast {
|
|
127
|
-
id: string;
|
|
128
|
-
message: string;
|
|
129
|
-
type: 'success' | 'error' | 'info';
|
|
130
|
-
duration?: number;
|
|
131
|
-
}
|
|
132
|
-
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
133
|
-
const showToast = (message: string, type: 'success' | 'error' | 'info' = 'info', duration = 5000) => {
|
|
134
|
-
const id = `toast-${++toastIdCounter}`;
|
|
135
|
-
setToasts((prev) => [...prev, { id, message, type, duration }]);
|
|
136
|
-
setTimeout(() => {
|
|
137
|
-
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
138
|
-
}, duration);
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// App Config
|
|
142
|
-
const [config, setConfig] = useState<NexusFlowConfig | null>(null);
|
|
143
|
-
const [configLoading, setConfigLoading] = useState(true);
|
|
144
|
-
const [configExists, setConfigExists] = useState<boolean>(true);
|
|
145
|
-
const [saveStatus, setSaveStatus] = useState<'success' | 'error' | null>(null);
|
|
146
|
-
const [adapters, setAdapters] = useState<StorageAdapterMeta[]>([]);
|
|
147
|
-
|
|
148
|
-
// Update Check State
|
|
149
|
-
const [updateStatus, setUpdateStatus] = useState<{
|
|
150
|
-
currentVersion: string;
|
|
151
|
-
latestVersion: string;
|
|
152
|
-
updateAvailable: boolean;
|
|
153
|
-
downloadUrl?: string | null;
|
|
154
|
-
releaseNotes?: string;
|
|
155
|
-
} | null>(null);
|
|
156
|
-
const [appVersion, setAppVersion] = useState('0.1.5');
|
|
157
|
-
const [defaultPaths, setDefaultPaths] = useState<{ devDir: string; workspacesDir: string } | null>(null);
|
|
158
|
-
|
|
159
|
-
// Repos & Tools
|
|
160
|
-
const [repos, setRepos] = useState<RepoInfo[]>([]);
|
|
161
|
-
const [reposLoading, setReposLoading] = useState(false);
|
|
162
|
-
const [repoSearch, setRepoSearch] = useState('');
|
|
163
|
-
const [aiAssistants, setAiAssistants] = useState<DetectedAI[]>([]);
|
|
164
|
-
const [editors, setEditors] = useState<DetectedEditor[]>([]);
|
|
165
|
-
|
|
166
|
-
// Wizard State
|
|
167
|
-
const [activeStep, setActiveStep] = useState(0);
|
|
168
|
-
const [branchName, setBranchName] = useState('');
|
|
169
|
-
const [description, setDescription] = useState('');
|
|
170
|
-
const [selectedRepos, setSelectedRepos] = useState<RepoInfo[]>([]);
|
|
171
|
-
const [selectedAI, setSelectedAI] = useState<string[]>([]);
|
|
172
|
-
const [selectedEditor, setSelectedEditor] = useState<DetectedEditor | null>(null);
|
|
173
|
-
const [localLlmEnabled, setLocalLlmEnabled] = useState(false);
|
|
174
|
-
const [creating, setCreating] = useState(false);
|
|
175
|
-
const [createdWorkspace, setCreatedWorkspace] = useState<{ path: string } | null>(null);
|
|
176
|
-
const [creationError, setCreationError] = useState<string | null>(null);
|
|
177
|
-
const [creationSteps, setCreationSteps] = useState<any[]>([
|
|
178
|
-
{ id: 'worktrees', name: 'Create Git Worktrees', status: 'pending', message: 'Waiting...' },
|
|
179
|
-
{ id: 'analysis', name: 'Analyze Repositories', status: 'pending', message: 'Waiting...' },
|
|
180
|
-
{ id: 'context', name: 'Generate AI Context Files', status: 'pending', message: 'Waiting...' },
|
|
181
|
-
]);
|
|
182
|
-
|
|
183
|
-
const [toolsStatus, setToolsStatus] = useState<any[]>([]);
|
|
184
|
-
const [toolsLoading, setToolsLoading] = useState(false);
|
|
185
|
-
const [updatingToolId, setUpdatingToolId] = useState<string | null>(null);
|
|
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
|
-
|
|
212
|
-
// Resumption Commands State
|
|
213
|
-
const [testCommand, setTestCommand] = useState('npm run test');
|
|
214
|
-
const [mockCommand, setMockCommand] = useState('');
|
|
215
|
-
const [startCommand, setStartCommand] = useState('');
|
|
216
|
-
const [resumingWs, setResumingWs] = useState<string | null>(null);
|
|
217
|
-
|
|
218
|
-
// Suggest defaults based on selected repos
|
|
219
|
-
useEffect(() => {
|
|
220
|
-
if (selectedRepos.length === 0) return;
|
|
221
|
-
const isDotNet = selectedRepos.some(r => r.name.toLowerCase().includes('dotnet') || r.name.toLowerCase().includes('csharp') || r.name.toLowerCase().includes('microsoft'));
|
|
222
|
-
const isPython = selectedRepos.some(r => r.name.toLowerCase().includes('django') || r.name.toLowerCase().includes('flask') || r.name.toLowerCase().includes('fastapi') || r.name.toLowerCase().includes('python'));
|
|
223
|
-
|
|
224
|
-
if (isDotNet) {
|
|
225
|
-
setTestCommand('dotnet test');
|
|
226
|
-
} else if (isPython) {
|
|
227
|
-
setTestCommand('pytest');
|
|
228
|
-
} else {
|
|
229
|
-
setTestCommand('npm run test');
|
|
230
|
-
}
|
|
231
|
-
}, [selectedRepos]);
|
|
232
|
-
|
|
233
|
-
// Workspaces List
|
|
234
|
-
const [workspaces, setWorkspaces] = useState<Feature[]>([]);
|
|
235
|
-
const [workspacesLoading, setWorkspacesLoading] = useState(false);
|
|
236
|
-
|
|
237
|
-
// Active Workspace Services / Orchestration Details
|
|
238
|
-
const [activeWsId, setActiveWsId] = useState<string | null>(null);
|
|
239
|
-
const [services, setServices] = useState<ServiceConfig[]>([]);
|
|
240
|
-
const [orchTools, setOrchTools] = useState<OrchestrationDetection[]>([]);
|
|
241
|
-
const [runningServices, setRunningServices] = useState<RunningService[]>([]);
|
|
242
|
-
const [servicesLoading, setServicesLoading] = useState(false);
|
|
243
|
-
const [subTab, setSubTab] = useState<'overview' | 'services' | 'changes' | 'sessions' | 'knowledge' | 'plan'>('overview');
|
|
244
|
-
const [sessions, setSessions] = useState<any[]>([]);
|
|
245
|
-
const [sessionsLoading, setSessionsLoading] = useState(false);
|
|
246
|
-
const [activeSession, setActiveSession] = useState<any | null>(null);
|
|
247
|
-
const [transcript, setTranscript] = useState<any[]>([]);
|
|
248
|
-
const [transcriptLoading, setTranscriptLoading] = useState(false);
|
|
249
|
-
const [gitChanges, setGitChanges] = useState<any[]>([]);
|
|
250
|
-
const [gitChangesLoading, setGitChangesLoading] = useState(false);
|
|
251
|
-
const [knowledgeContent, setKnowledgeContent] = useState<string>('');
|
|
252
|
-
const [knowledgeLoading, setKnowledgeLoading] = useState<boolean>(false);
|
|
253
|
-
const [isEditingKnowledge, setIsEditingKnowledge] = useState<boolean>(false);
|
|
254
|
-
const [editedKnowledge, setEditedKnowledge] = useState<string>('');
|
|
255
|
-
const [saveKnowledgeLoading, setSaveKnowledgeLoading] = useState<boolean>(false);
|
|
256
|
-
const [planContent, setPlanContent] = useState<string>('');
|
|
257
|
-
const [planLoading, setPlanLoading] = useState<boolean>(false);
|
|
258
|
-
const [syncLoading, setSyncLoading] = useState<boolean>(false);
|
|
259
|
-
const [syncResults, setSyncResults] = useState<any[] | null>(null);
|
|
260
|
-
const [commitMessage, setCommitMessage] = useState<string>('');
|
|
261
|
-
const [showCommitModal, setShowCommitModal] = useState<boolean>(false);
|
|
262
|
-
const [commitLoading, setCommitLoading] = useState<boolean>(false);
|
|
263
|
-
const [commitResults, setCommitResults] = useState<any[] | null>(null);
|
|
264
|
-
const [deleteWsLoading, setDeleteWsLoading] = useState<string | null>(null);
|
|
265
|
-
const [addRepoLoading, setAddRepoLoading] = useState<boolean>(false);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// Log Viewer
|
|
269
|
-
const [selectedLogService, setSelectedLogService] = useState<string | null>(null);
|
|
270
|
-
const [serviceLogs, setServiceLogs] = useState<string>('');
|
|
271
|
-
const logsEndRef = useRef<HTMLDivElement | null>(null);
|
|
272
|
-
|
|
273
|
-
// Local LLM states
|
|
274
|
-
const [recommendation, setRecommendation] = useState<{ totalRamGb: number; gpuName: string; recommendedModel: string } | null>(null);
|
|
275
|
-
const [testStatus, setTestStatus] = useState<{ success: boolean; message: string } | null>(null);
|
|
276
|
-
const [testingLlm, setTestingLlm] = useState(false);
|
|
277
|
-
|
|
278
|
-
const isSettingsFormValid = (() => {
|
|
279
|
-
if (!config) return false;
|
|
280
|
-
if (!config.devDir || config.devDir.trim() === '' || !config.workspacesDir || config.workspacesDir.trim() === '') return false;
|
|
281
|
-
if (config.localLlm?.enabled) {
|
|
282
|
-
const endpoint = config.localLlm.endpoint?.trim() || '';
|
|
283
|
-
if (!endpoint.startsWith('http://') && !endpoint.startsWith('https://')) {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return true;
|
|
288
|
-
})();
|
|
289
|
-
|
|
290
|
-
// Effects moved below helper functions to resolve lexical declaration order
|
|
291
|
-
|
|
292
|
-
// ─── API Fetches ────────────────────────────────────────────────────────
|
|
293
|
-
|
|
294
|
-
const fetchUpdateStatus = async () => {
|
|
295
|
-
try {
|
|
296
|
-
const res = await fetch(`${API_BASE}/api/update-status`);
|
|
297
|
-
if (res.ok) {
|
|
298
|
-
const data = await res.json();
|
|
299
|
-
setUpdateStatus(data);
|
|
300
|
-
if (data.currentVersion) {
|
|
301
|
-
setAppVersion(data.currentVersion);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
} catch (e) {
|
|
305
|
-
console.error('Failed to fetch update status:', e);
|
|
306
|
-
}
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
const fetchToolsStatus = async (force = false) => {
|
|
310
|
-
setToolsLoading(true);
|
|
311
|
-
try {
|
|
312
|
-
const res = await fetch(`${API_BASE}/api/updates/tools${force ? '?force=true' : ''}`);
|
|
313
|
-
if (res.ok) {
|
|
314
|
-
const data = await res.json();
|
|
315
|
-
setToolsStatus(data);
|
|
316
|
-
}
|
|
317
|
-
} catch (e) {
|
|
318
|
-
console.error('Failed to fetch tools status:', e);
|
|
319
|
-
} finally {
|
|
320
|
-
setToolsLoading(false);
|
|
321
|
-
}
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
const handleUpdateTool = async (toolId: string) => {
|
|
325
|
-
setUpdatingToolId(toolId);
|
|
326
|
-
try {
|
|
327
|
-
const res = await fetch(`${API_BASE}/api/updates/install`, {
|
|
328
|
-
method: 'POST',
|
|
329
|
-
headers: { 'Content-Type': 'application/json' },
|
|
330
|
-
body: JSON.stringify({ toolId }),
|
|
331
|
-
});
|
|
332
|
-
const data = await res.json();
|
|
333
|
-
if (res.ok && data.success) {
|
|
334
|
-
showToast(`${toolId} updated successfully!\n\nOutput:\n${data.output}`, 'success');
|
|
335
|
-
fetchToolsStatus(true);
|
|
336
|
-
fetchUpdateStatus();
|
|
337
|
-
} else {
|
|
338
|
-
showToast(`Error: ${data.error || 'Failed to update tool'}`, 'error');
|
|
339
|
-
}
|
|
340
|
-
} catch (e) {
|
|
341
|
-
console.error(e);
|
|
342
|
-
showToast('Network error when updating tool.', 'error');
|
|
343
|
-
} finally {
|
|
344
|
-
setUpdatingToolId(null);
|
|
345
|
-
}
|
|
346
|
-
};
|
|
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
|
-
|
|
397
|
-
const fetchLlmRecommendation = async () => {
|
|
398
|
-
try {
|
|
399
|
-
const res = await fetch(`${API_BASE}/api/local-llm/recommend`);
|
|
400
|
-
if (res.ok) {
|
|
401
|
-
const data = await res.json();
|
|
402
|
-
setRecommendation(data);
|
|
403
|
-
}
|
|
404
|
-
} catch (e) {
|
|
405
|
-
console.error('Error fetching LLM recommendation:', e);
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
const testLlmConnection = async () => {
|
|
410
|
-
if (!config?.localLlm) return;
|
|
411
|
-
setTestingLlm(true);
|
|
412
|
-
setTestStatus(null);
|
|
413
|
-
try {
|
|
414
|
-
const res = await fetch(`${API_BASE}/api/local-llm/test`, {
|
|
415
|
-
method: 'POST',
|
|
416
|
-
headers: { 'Content-Type': 'application/json' },
|
|
417
|
-
body: JSON.stringify(config.localLlm),
|
|
418
|
-
});
|
|
419
|
-
const data = await res.json();
|
|
420
|
-
if (data.success) {
|
|
421
|
-
setTestStatus({ success: true, message: data.message });
|
|
422
|
-
} else {
|
|
423
|
-
setTestStatus({ success: false, message: data.error || 'Connection failed.' });
|
|
424
|
-
}
|
|
425
|
-
} catch (e: any) {
|
|
426
|
-
setTestStatus({ success: false, message: e.message || 'Network error.' });
|
|
427
|
-
} finally {
|
|
428
|
-
setTestingLlm(false);
|
|
429
|
-
}
|
|
430
|
-
};
|
|
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
|
-
|
|
444
|
-
const fetchConfig = async () => {
|
|
445
|
-
fetchAdapters();
|
|
446
|
-
try {
|
|
447
|
-
const res = await fetch(`${API_BASE}/api/config`);
|
|
448
|
-
const data = await res.json();
|
|
449
|
-
setConfig(data.config);
|
|
450
|
-
setConfigExists(data.exists);
|
|
451
|
-
setLocalLlmEnabled(data.config?.localLlm?.enabled || false);
|
|
452
|
-
|
|
453
|
-
fetchEditors(data.config?.defaultEditor);
|
|
454
|
-
|
|
455
|
-
if (data.exists && data.config.devDir) {
|
|
456
|
-
fetchRepos();
|
|
457
|
-
} else {
|
|
458
|
-
setDefaultPaths({
|
|
459
|
-
devDir: data.config.devDir || '',
|
|
460
|
-
workspacesDir: data.config.workspacesDir || '',
|
|
461
|
-
});
|
|
462
|
-
setConfig({
|
|
463
|
-
version: '1.0.0',
|
|
464
|
-
devDir: '',
|
|
465
|
-
workspacesDir: '',
|
|
466
|
-
defaultAssistant: null,
|
|
467
|
-
scanDepth: 2,
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
} catch (e) {
|
|
471
|
-
console.error(e);
|
|
472
|
-
} finally {
|
|
473
|
-
setConfigLoading(false);
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
const saveAppConfig = async (newConfig: NexusFlowConfig) => {
|
|
478
|
-
try {
|
|
479
|
-
const res = await fetch(`${API_BASE}/api/config`, {
|
|
480
|
-
method: 'POST',
|
|
481
|
-
headers: { 'Content-Type': 'application/json' },
|
|
482
|
-
body: JSON.stringify(newConfig),
|
|
483
|
-
});
|
|
484
|
-
if (res.ok) {
|
|
485
|
-
setSaveStatus('success');
|
|
486
|
-
setConfig(newConfig);
|
|
487
|
-
setConfigExists(true);
|
|
488
|
-
setLocalLlmEnabled(newConfig.localLlm?.enabled || false);
|
|
489
|
-
fetchRepos();
|
|
490
|
-
fetchWorkspaces();
|
|
491
|
-
} else {
|
|
492
|
-
setSaveStatus('error');
|
|
493
|
-
}
|
|
494
|
-
} catch {
|
|
495
|
-
setSaveStatus('error');
|
|
496
|
-
}
|
|
497
|
-
setTimeout(() => setSaveStatus(null), 3000);
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const fetchRepos = async () => {
|
|
501
|
-
setReposLoading(true);
|
|
502
|
-
try {
|
|
503
|
-
const res = await fetch(`${API_BASE}/api/repos`);
|
|
504
|
-
const data = await res.json();
|
|
505
|
-
setRepos(Array.isArray(data) ? data : []);
|
|
506
|
-
} catch (e) {
|
|
507
|
-
console.error(e);
|
|
508
|
-
} finally {
|
|
509
|
-
setReposLoading(false);
|
|
510
|
-
}
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
const fetchAIAssistants = async () => {
|
|
514
|
-
try {
|
|
515
|
-
const res = await fetch(`${API_BASE}/api/ai-detect`);
|
|
516
|
-
const data = await res.json();
|
|
517
|
-
setAiAssistants(data);
|
|
518
|
-
const firstHarness = data.find((ai: any) => ai.detected && ai.command);
|
|
519
|
-
if (firstHarness) {
|
|
520
|
-
setSelectedInspectAssistant(firstHarness.name);
|
|
521
|
-
}
|
|
522
|
-
setSelectedAI(data.filter((ai: DetectedAI) => ai.detected).map((ai: DetectedAI) => ai.name));
|
|
523
|
-
} catch (e) {
|
|
524
|
-
console.error(e);
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
|
|
528
|
-
const fetchEditors = async (savedDefaultEditorCode?: string | null) => {
|
|
529
|
-
try {
|
|
530
|
-
const res = await fetch(`${API_BASE}/api/editor-detect`);
|
|
531
|
-
const data = await res.json();
|
|
532
|
-
setEditors(data);
|
|
533
|
-
|
|
534
|
-
let initialEditor = null;
|
|
535
|
-
if (savedDefaultEditorCode) {
|
|
536
|
-
initialEditor = data.find((ed: DetectedEditor) => ed.command === savedDefaultEditorCode);
|
|
537
|
-
}
|
|
538
|
-
if (!initialEditor) {
|
|
539
|
-
initialEditor = data.find((ed: DetectedEditor) => ed.detected);
|
|
540
|
-
}
|
|
541
|
-
if (initialEditor) setSelectedEditor(initialEditor);
|
|
542
|
-
} catch (e) {
|
|
543
|
-
console.error(e);
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
|
|
547
|
-
const fetchWorkspaces = async () => {
|
|
548
|
-
setWorkspacesLoading(true);
|
|
549
|
-
try {
|
|
550
|
-
const res = await fetch(`${API_BASE}/api/workspaces`);
|
|
551
|
-
const data = await res.json();
|
|
552
|
-
setWorkspaces(Array.isArray(data) ? data : []);
|
|
553
|
-
} catch (e) {
|
|
554
|
-
console.error(e);
|
|
555
|
-
} finally {
|
|
556
|
-
setWorkspacesLoading(false);
|
|
557
|
-
}
|
|
558
|
-
};
|
|
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
|
-
|
|
663
|
-
const fetchWorkspaceServices = async (wsId: string, silent = false) => {
|
|
664
|
-
if (!silent) setServicesLoading(true);
|
|
665
|
-
try {
|
|
666
|
-
const encodedId = encodeURIComponent(wsId);
|
|
667
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/services`);
|
|
668
|
-
const data = await res.json();
|
|
669
|
-
setServices(data.services || []);
|
|
670
|
-
setOrchTools(data.orchestrationTools || []);
|
|
671
|
-
setRunningServices(data.runningState || []);
|
|
672
|
-
|
|
673
|
-
if (data.services.length > 0 && !selectedLogService) {
|
|
674
|
-
setSelectedLogService(data.services[0].name);
|
|
675
|
-
}
|
|
676
|
-
} catch (e) {
|
|
677
|
-
console.error(e);
|
|
678
|
-
} finally {
|
|
679
|
-
if (!silent) setServicesLoading(false);
|
|
680
|
-
}
|
|
681
|
-
};
|
|
682
|
-
|
|
683
|
-
const fetchLogs = async (wsId: string, serviceName: string) => {
|
|
684
|
-
try {
|
|
685
|
-
const encodedId = encodeURIComponent(wsId);
|
|
686
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/services/logs/${serviceName}`);
|
|
687
|
-
const data = await res.json();
|
|
688
|
-
setServiceLogs(data.logs || '');
|
|
689
|
-
} catch (e) {
|
|
690
|
-
console.error(e);
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
const fetchGitChanges = async (wsId: string) => {
|
|
695
|
-
setGitChangesLoading(true);
|
|
696
|
-
try {
|
|
697
|
-
const encodedId = encodeURIComponent(wsId);
|
|
698
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/changes`);
|
|
699
|
-
const data = await res.json();
|
|
700
|
-
setGitChanges(data.changes || []);
|
|
701
|
-
} catch (e) {
|
|
702
|
-
console.error(e);
|
|
703
|
-
} finally {
|
|
704
|
-
setGitChangesLoading(false);
|
|
705
|
-
}
|
|
706
|
-
};
|
|
707
|
-
|
|
708
|
-
const fetchWorkspaceSessions = async (wsId: string) => {
|
|
709
|
-
setSessionsLoading(true);
|
|
710
|
-
try {
|
|
711
|
-
const encodedId = encodeURIComponent(wsId);
|
|
712
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/sessions`);
|
|
713
|
-
const data = await res.json();
|
|
714
|
-
setSessions(data.sessions || []);
|
|
715
|
-
} catch (e) {
|
|
716
|
-
console.error(e);
|
|
717
|
-
} finally {
|
|
718
|
-
setSessionsLoading(false);
|
|
719
|
-
}
|
|
720
|
-
};
|
|
721
|
-
|
|
722
|
-
const fetchSessionTranscript = async (assistant: string, sessionId: string) => {
|
|
723
|
-
setTranscriptLoading(true);
|
|
724
|
-
try {
|
|
725
|
-
const res = await fetch(`${API_BASE}/api/session/${assistant}/${sessionId}/transcript`);
|
|
726
|
-
const data = await res.json();
|
|
727
|
-
setTranscript(data.messages || []);
|
|
728
|
-
} catch (e) {
|
|
729
|
-
console.error(e);
|
|
730
|
-
} finally {
|
|
731
|
-
setTranscriptLoading(false);
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const fetchKnowledge = async (wsId: string) => {
|
|
737
|
-
setKnowledgeLoading(true);
|
|
738
|
-
try {
|
|
739
|
-
const encodedId = encodeURIComponent(wsId);
|
|
740
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/knowledge`);
|
|
741
|
-
const data = await res.json();
|
|
742
|
-
setKnowledgeContent(data.content || '');
|
|
743
|
-
setEditedKnowledge(data.content || '');
|
|
744
|
-
} catch (e) {
|
|
745
|
-
console.error(e);
|
|
746
|
-
} finally {
|
|
747
|
-
setKnowledgeLoading(false);
|
|
748
|
-
}
|
|
749
|
-
};
|
|
750
|
-
|
|
751
|
-
const handleSaveKnowledge = async (wsId: string) => {
|
|
752
|
-
setSaveKnowledgeLoading(true);
|
|
753
|
-
try {
|
|
754
|
-
const encodedId = encodeURIComponent(wsId);
|
|
755
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/knowledge`, {
|
|
756
|
-
method: 'PUT',
|
|
757
|
-
headers: { 'Content-Type': 'application/json' },
|
|
758
|
-
body: JSON.stringify({ content: editedKnowledge }),
|
|
759
|
-
});
|
|
760
|
-
if (res.ok) {
|
|
761
|
-
setKnowledgeContent(editedKnowledge);
|
|
762
|
-
setIsEditingKnowledge(false);
|
|
763
|
-
}
|
|
764
|
-
} catch (e) {
|
|
765
|
-
console.error(e);
|
|
766
|
-
} finally {
|
|
767
|
-
setSaveKnowledgeLoading(false);
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
|
-
|
|
771
|
-
const fetchPlan = async (wsId: string) => {
|
|
772
|
-
setPlanLoading(true);
|
|
773
|
-
try {
|
|
774
|
-
const encodedId = encodeURIComponent(wsId);
|
|
775
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/plan`);
|
|
776
|
-
const data = await res.json();
|
|
777
|
-
setPlanContent(data.content || '');
|
|
778
|
-
} catch (e) {
|
|
779
|
-
console.error(e);
|
|
780
|
-
} finally {
|
|
781
|
-
setPlanLoading(false);
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
const handleSyncAll = async (wsId: string) => {
|
|
787
|
-
setSyncLoading(true);
|
|
788
|
-
setSyncResults(null);
|
|
789
|
-
try {
|
|
790
|
-
const encodedId = encodeURIComponent(wsId);
|
|
791
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/sync`, { method: 'POST' });
|
|
792
|
-
const data = await res.json();
|
|
793
|
-
setSyncResults(data.results || []);
|
|
794
|
-
fetchGitChanges(wsId);
|
|
795
|
-
} catch (e) {
|
|
796
|
-
console.error(e);
|
|
797
|
-
} finally {
|
|
798
|
-
setSyncLoading(false);
|
|
799
|
-
}
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
const handleCommitAll = async (wsId: string) => {
|
|
803
|
-
if (!commitMessage.trim()) return;
|
|
804
|
-
setCommitLoading(true);
|
|
805
|
-
setCommitResults(null);
|
|
806
|
-
try {
|
|
807
|
-
const encodedId = encodeURIComponent(wsId);
|
|
808
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/commit`, {
|
|
809
|
-
method: 'POST',
|
|
810
|
-
headers: { 'Content-Type': 'application/json' },
|
|
811
|
-
body: JSON.stringify({ message: commitMessage }),
|
|
812
|
-
});
|
|
813
|
-
const data = await res.json();
|
|
814
|
-
setCommitResults(data.results || []);
|
|
815
|
-
setCommitMessage('');
|
|
816
|
-
setShowCommitModal(false);
|
|
817
|
-
fetchGitChanges(wsId);
|
|
818
|
-
} catch (e) {
|
|
819
|
-
console.error(e);
|
|
820
|
-
} finally {
|
|
821
|
-
setCommitLoading(false);
|
|
822
|
-
}
|
|
823
|
-
};
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
// ─── Actions ────────────────────────────────────────────────────────────
|
|
827
|
-
|
|
828
|
-
const handleToggleRepo = (repo: RepoInfo) => {
|
|
829
|
-
if (selectedRepos.some((r) => r.path === repo.path)) {
|
|
830
|
-
setSelectedRepos(selectedRepos.filter((r) => r.path !== repo.path));
|
|
831
|
-
} else {
|
|
832
|
-
setSelectedRepos([...selectedRepos, repo]);
|
|
833
|
-
}
|
|
834
|
-
};
|
|
835
|
-
|
|
836
|
-
const handleToggleAI = (aiName: string) => {
|
|
837
|
-
if (selectedAI.includes(aiName)) {
|
|
838
|
-
setSelectedAI(selectedAI.filter((x) => x !== aiName));
|
|
839
|
-
} else {
|
|
840
|
-
setSelectedAI([...selectedAI, aiName]);
|
|
841
|
-
}
|
|
842
|
-
};
|
|
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
|
-
|
|
879
|
-
const handleCreateWorkspace = async () => {
|
|
880
|
-
if (!branchName || selectedRepos.length === 0) return;
|
|
881
|
-
setCreating(true);
|
|
882
|
-
setCreationError(null);
|
|
883
|
-
|
|
884
|
-
// Reset steps to pending
|
|
885
|
-
setCreationSteps([
|
|
886
|
-
{ id: 'worktrees', name: 'Create Git Worktrees', status: 'pending', message: 'Waiting...' },
|
|
887
|
-
{ id: 'analysis', name: 'Analyze Repositories', status: 'pending', message: 'Waiting...' },
|
|
888
|
-
{ id: 'context', name: 'Generate AI Context Files', status: 'pending', message: 'Waiting...' },
|
|
889
|
-
]);
|
|
890
|
-
|
|
891
|
-
try {
|
|
892
|
-
const res = await fetch(`${API_BASE}/api/workspace`, {
|
|
893
|
-
method: 'POST',
|
|
894
|
-
headers: { 'Content-Type': 'application/json' },
|
|
895
|
-
body: JSON.stringify({
|
|
896
|
-
branchName,
|
|
897
|
-
description,
|
|
898
|
-
repos: selectedRepos,
|
|
899
|
-
assistants: selectedAI,
|
|
900
|
-
localLlmEnabled,
|
|
901
|
-
teamworkInstructions: customTeamworkInstructions || undefined,
|
|
902
|
-
resumption: {
|
|
903
|
-
testCommand,
|
|
904
|
-
mockCommand: mockCommand || undefined,
|
|
905
|
-
startCommand: startCommand || undefined,
|
|
906
|
-
},
|
|
907
|
-
}),
|
|
908
|
-
});
|
|
909
|
-
const data = await res.json();
|
|
910
|
-
if (res.ok && data.success) {
|
|
911
|
-
const jobId = data.jobId;
|
|
912
|
-
|
|
913
|
-
// Establish EventSource SSE connection
|
|
914
|
-
const eventSource = new EventSource(`${API_BASE}/api/workspace/create-stream/${encodeURIComponent(jobId)}`);
|
|
915
|
-
|
|
916
|
-
eventSource.addEventListener('progress', (e) => {
|
|
917
|
-
try {
|
|
918
|
-
const job = JSON.parse(e.data);
|
|
919
|
-
if (job.steps) {
|
|
920
|
-
setCreationSteps(job.steps);
|
|
921
|
-
}
|
|
922
|
-
if (job.status === 'completed') {
|
|
923
|
-
eventSource.close();
|
|
924
|
-
setCreatedWorkspace({ path: job.workspacePath });
|
|
925
|
-
setActiveStep(4);
|
|
926
|
-
setCreating(false);
|
|
927
|
-
fetchWorkspaces();
|
|
928
|
-
|
|
929
|
-
if (selectedEditor) {
|
|
930
|
-
if (isVsCode) {
|
|
931
|
-
window.parent.postMessage({ type: 'openWorkspaceFolder', workspacePath: job.workspacePath }, '*');
|
|
932
|
-
} else {
|
|
933
|
-
fetch(`${API_BASE}/api/open-editor`, {
|
|
934
|
-
method: 'POST',
|
|
935
|
-
headers: { 'Content-Type': 'application/json' },
|
|
936
|
-
body: JSON.stringify({
|
|
937
|
-
workspacePath: job.workspacePath,
|
|
938
|
-
command: selectedEditor.command,
|
|
939
|
-
}),
|
|
940
|
-
}).catch(console.error);
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
} else if (job.status === 'failed') {
|
|
944
|
-
eventSource.close();
|
|
945
|
-
setCreating(false);
|
|
946
|
-
setCreationError(job.error || 'Failed to create workspace');
|
|
947
|
-
}
|
|
948
|
-
} catch (err) {
|
|
949
|
-
console.error('Failed to parse SSE data:', err);
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
|
|
953
|
-
eventSource.onerror = (err) => {
|
|
954
|
-
console.error('SSE Error:', err);
|
|
955
|
-
eventSource.close();
|
|
956
|
-
setCreating(false);
|
|
957
|
-
setCreationError('Connection lost while building workspace.');
|
|
958
|
-
};
|
|
959
|
-
} else {
|
|
960
|
-
setCreating(false);
|
|
961
|
-
setCreationError(data.error || 'Failed to initialize workspace build');
|
|
962
|
-
}
|
|
963
|
-
} catch (e) {
|
|
964
|
-
console.error(e);
|
|
965
|
-
setCreating(false);
|
|
966
|
-
setCreationError('Network error when creating workspace.');
|
|
967
|
-
}
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
const handleStartServices = async (wsId: string) => {
|
|
971
|
-
try {
|
|
972
|
-
const encodedId = encodeURIComponent(wsId);
|
|
973
|
-
await fetch(`${API_BASE}/api/workspace/${encodedId}/services/start`, {
|
|
974
|
-
method: 'POST',
|
|
975
|
-
headers: { 'Content-Type': 'application/json' },
|
|
976
|
-
body: JSON.stringify({ services }),
|
|
977
|
-
});
|
|
978
|
-
fetchWorkspaceServices(wsId, true);
|
|
979
|
-
} catch (e) {
|
|
980
|
-
console.error(e);
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
|
|
984
|
-
const handleStopServices = async (wsId: string) => {
|
|
985
|
-
try {
|
|
986
|
-
const encodedId = encodeURIComponent(wsId);
|
|
987
|
-
await fetch(`${API_BASE}/api/workspace/${encodedId}/services/stop`, {
|
|
988
|
-
method: 'POST',
|
|
989
|
-
});
|
|
990
|
-
fetchWorkspaceServices(wsId, true);
|
|
991
|
-
setServiceLogs('Services stopped.');
|
|
992
|
-
} catch (e) {
|
|
993
|
-
console.error(e);
|
|
994
|
-
}
|
|
995
|
-
};
|
|
996
|
-
|
|
997
|
-
const handleResumeSession = async (ws: Feature, sessionId?: string, assistant?: string) => {
|
|
998
|
-
setResumingWs(ws.branchName);
|
|
999
|
-
try {
|
|
1000
|
-
const encodedId = encodeURIComponent(ws.branchName);
|
|
1001
|
-
const editor = editors.find((e) => e.detected) || editors[0];
|
|
1002
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/resume`, {
|
|
1003
|
-
method: 'POST',
|
|
1004
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1005
|
-
body: JSON.stringify({
|
|
1006
|
-
command: editor?.command,
|
|
1007
|
-
sessionId,
|
|
1008
|
-
assistant,
|
|
1009
|
-
}),
|
|
1010
|
-
});
|
|
1011
|
-
const data = await res.json();
|
|
1012
|
-
if (res.ok && data.success) {
|
|
1013
|
-
if (isVsCode) {
|
|
1014
|
-
window.parent.postMessage({
|
|
1015
|
-
type: 'executeTerminalCommand',
|
|
1016
|
-
command: data.resumeCommand,
|
|
1017
|
-
cwd: ws.workspacePath
|
|
1018
|
-
}, '*');
|
|
1019
|
-
setActiveWsId(ws.branchName);
|
|
1020
|
-
} else {
|
|
1021
|
-
navigator.clipboard.writeText(data.resumeCommand);
|
|
1022
|
-
showToast(`Session Resumed!\n\n1. Editor launched.\n2. Command "${data.resumeCommand}" copied to clipboard! Paste it into your terminal inside the workspace to continue.`, 'success');
|
|
1023
|
-
setActiveWsId(ws.branchName);
|
|
1024
|
-
}
|
|
1025
|
-
} else {
|
|
1026
|
-
showToast(`Error: ${data.error || 'Failed to resume session'}`, 'error');
|
|
1027
|
-
}
|
|
1028
|
-
} catch (e) {
|
|
1029
|
-
console.error(e);
|
|
1030
|
-
showToast('Network error when resuming session.', 'error');
|
|
1031
|
-
} finally {
|
|
1032
|
-
setResumingWs(null);
|
|
1033
|
-
}
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
const handleOpenInEditor = async (workspacePath: string) => {
|
|
1037
|
-
if (isVsCode) {
|
|
1038
|
-
window.parent.postMessage({ type: 'openWorkspaceFolder', workspacePath }, '*');
|
|
1039
|
-
return;
|
|
1040
|
-
}
|
|
1041
|
-
if (config?.defaultEditor === 'none') {
|
|
1042
|
-
showToast('Your preferred editor is set to "None" (skip opening). You can change this in Settings.', 'info');
|
|
1043
|
-
return;
|
|
1044
|
-
}
|
|
1045
|
-
let editor = null;
|
|
1046
|
-
if (config?.defaultEditor) {
|
|
1047
|
-
editor = editors.find((e) => e.command === config.defaultEditor);
|
|
1048
|
-
}
|
|
1049
|
-
if (!editor) {
|
|
1050
|
-
editor = editors.find((e) => e.detected) || editors[0];
|
|
1051
|
-
}
|
|
1052
|
-
if (!editor) {
|
|
1053
|
-
showToast('No detected editors available.', 'error');
|
|
1054
|
-
return;
|
|
1055
|
-
}
|
|
1056
|
-
try {
|
|
1057
|
-
await fetch(`${API_BASE}/api/open-editor`, {
|
|
1058
|
-
method: 'POST',
|
|
1059
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1060
|
-
body: JSON.stringify({
|
|
1061
|
-
workspacePath,
|
|
1062
|
-
command: editor.command,
|
|
1063
|
-
}),
|
|
1064
|
-
});
|
|
1065
|
-
} catch (e) {
|
|
1066
|
-
console.error(e);
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
|
|
1070
|
-
const executeTerminal = (command: string) => {
|
|
1071
|
-
if (!activeWsId) return;
|
|
1072
|
-
const ws = workspaces.find(w => w.branchName === activeWsId);
|
|
1073
|
-
if (ws) {
|
|
1074
|
-
window.parent.postMessage({
|
|
1075
|
-
type: 'executeTerminalCommand',
|
|
1076
|
-
command,
|
|
1077
|
-
cwd: ws.workspacePath
|
|
1078
|
-
}, '*');
|
|
1079
|
-
}
|
|
1080
|
-
};
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
// Initial loads
|
|
1085
|
-
useEffect(() => {
|
|
1086
|
-
fetchConfig();
|
|
1087
|
-
fetchAIAssistants();
|
|
1088
|
-
fetchWorkspaces();
|
|
1089
|
-
fetchUpdateStatus();
|
|
1090
|
-
fetchWorkflowTemplates();
|
|
1091
|
-
|
|
1092
|
-
// Support auto-loading workspace from VS Code or URL params
|
|
1093
|
-
const queryParams = new URLSearchParams(window.location.search);
|
|
1094
|
-
const queryWsId = queryParams.get('workspaceId');
|
|
1095
|
-
if (queryWsId) {
|
|
1096
|
-
setActiveWsId(queryWsId);
|
|
1097
|
-
setView('workspaces');
|
|
1098
|
-
}
|
|
1099
|
-
}, []);
|
|
1100
|
-
|
|
1101
|
-
// Load tool updates status and LLM recommendations when settings view is open
|
|
1102
|
-
useEffect(() => {
|
|
1103
|
-
if (view === 'settings') {
|
|
1104
|
-
fetchToolsStatus();
|
|
1105
|
-
fetchLlmRecommendation();
|
|
1106
|
-
}
|
|
1107
|
-
}, [view]);
|
|
1108
|
-
|
|
1109
|
-
// Poll logs and services status when active workspace is open
|
|
1110
|
-
useEffect(() => {
|
|
1111
|
-
let interval: any = null;
|
|
1112
|
-
if (activeWsId) {
|
|
1113
|
-
fetchWorkspaceServices(activeWsId);
|
|
1114
|
-
interval = setInterval(() => {
|
|
1115
|
-
fetchWorkspaceServices(activeWsId, true);
|
|
1116
|
-
}, 3000);
|
|
1117
|
-
}
|
|
1118
|
-
return () => {
|
|
1119
|
-
if (interval) clearInterval(interval);
|
|
1120
|
-
};
|
|
1121
|
-
}, [activeWsId]);
|
|
1122
|
-
|
|
1123
|
-
// Load git changes when tab switches to 'changes' or active workspace changes
|
|
1124
|
-
useEffect(() => {
|
|
1125
|
-
if (activeWsId && subTab === 'changes') {
|
|
1126
|
-
fetchGitChanges(activeWsId);
|
|
1127
|
-
}
|
|
1128
|
-
}, [activeWsId, subTab]);
|
|
1129
|
-
|
|
1130
|
-
// Load sessions when active workspace changes or subTab switches to 'sessions'
|
|
1131
|
-
useEffect(() => {
|
|
1132
|
-
if (activeWsId && subTab === 'sessions') {
|
|
1133
|
-
fetchWorkspaceSessions(activeWsId);
|
|
1134
|
-
}
|
|
1135
|
-
}, [activeWsId, subTab]);
|
|
1136
|
-
|
|
1137
|
-
// Load knowledge when subTab switches to 'knowledge' or active workspace changes
|
|
1138
|
-
useEffect(() => {
|
|
1139
|
-
if (activeWsId && subTab === 'knowledge') {
|
|
1140
|
-
fetchKnowledge(activeWsId);
|
|
1141
|
-
}
|
|
1142
|
-
}, [activeWsId, subTab]);
|
|
1143
|
-
|
|
1144
|
-
// Load plan when subTab switches to 'plan' or active workspace changes
|
|
1145
|
-
useEffect(() => {
|
|
1146
|
-
if (activeWsId && subTab === 'plan') {
|
|
1147
|
-
fetchPlan(activeWsId);
|
|
1148
|
-
}
|
|
1149
|
-
}, [activeWsId, subTab]);
|
|
1150
|
-
|
|
1151
|
-
// Poll logs for active service logs
|
|
1152
|
-
useEffect(() => {
|
|
1153
|
-
let interval: any = null;
|
|
1154
|
-
if (activeWsId && selectedLogService) {
|
|
1155
|
-
fetchLogs(activeWsId, selectedLogService);
|
|
1156
|
-
interval = setInterval(() => {
|
|
1157
|
-
fetchLogs(activeWsId, selectedLogService);
|
|
1158
|
-
}, 2000);
|
|
1159
|
-
}
|
|
1160
|
-
return () => {
|
|
1161
|
-
if (interval) clearInterval(interval);
|
|
1162
|
-
};
|
|
1163
|
-
}, [activeWsId, selectedLogService]);
|
|
1164
|
-
|
|
1165
|
-
// Scroll to bottom of logs when log content changes
|
|
1166
|
-
useEffect(() => {
|
|
1167
|
-
if (logsEndRef.current) {
|
|
1168
|
-
logsEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
|
1169
|
-
}
|
|
1170
|
-
}, [serviceLogs]);
|
|
1171
|
-
|
|
1172
|
-
const handleCopyPrompt = (ws: Feature) => {
|
|
1173
|
-
const repoNames = ws.repos.map((r) => r.split(/[\\/]/).pop()).join(', ');
|
|
1174
|
-
const prompt = `You are an AI assistant helping with feature development.
|
|
1175
|
-
We are working in a multi-repository workspace.
|
|
1176
|
-
|
|
1177
|
-
Workspace Metadata:
|
|
1178
|
-
- Feature Branch: ${ws.branchName}
|
|
1179
|
-
- Purpose & Context: ${ws.description}
|
|
1180
|
-
- Mapped Repositories: ${repoNames}
|
|
1181
|
-
|
|
1182
|
-
Core Instructions:
|
|
1183
|
-
1. Always inspect and update "session.md" (session state handover memo) and "plan.md" (tasks checklist) at the workspace root as you progress.
|
|
1184
|
-
2. Read "WORKSPACE.md" at the root for a detailed index of repository relationships, tech stacks, and listening ports.
|
|
1185
|
-
3. Follow all project-specific rules in "CLAUDE.md", ".cursorrules", or "AGENTS.md" in sub-repositories.
|
|
1186
|
-
`;
|
|
1187
|
-
navigator.clipboard.writeText(prompt);
|
|
1188
|
-
showToast('Universal AI briefing prompt copied to clipboard!', 'success');
|
|
1189
|
-
};
|
|
1190
|
-
|
|
1191
|
-
const handleDeleteWorkspace = async (wsName: string) => {
|
|
1192
|
-
if (!window.confirm(`Are you sure you want to delete the workspace "${wsName}"?\nThis will force remove all git worktrees inside it and delete all files in the folder.`)) {
|
|
1193
|
-
return;
|
|
1194
|
-
}
|
|
1195
|
-
setDeleteWsLoading(wsName);
|
|
1196
|
-
try {
|
|
1197
|
-
const encodedId = encodeURIComponent(wsName);
|
|
1198
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}`, {
|
|
1199
|
-
method: 'DELETE',
|
|
1200
|
-
});
|
|
1201
|
-
const data = await res.json();
|
|
1202
|
-
if (res.ok && data.success) {
|
|
1203
|
-
if (activeWsId === wsName) {
|
|
1204
|
-
setActiveWsId(null);
|
|
1205
|
-
}
|
|
1206
|
-
await fetchWorkspaces();
|
|
1207
|
-
showToast(`Workspace ${wsName} successfully deleted.`, 'success');
|
|
1208
|
-
} else {
|
|
1209
|
-
showToast(`Failed to delete workspace: ${data.error || 'Unknown error'}`, 'error');
|
|
1210
|
-
}
|
|
1211
|
-
} catch (e) {
|
|
1212
|
-
console.error(e);
|
|
1213
|
-
showToast('Network error while deleting workspace.', 'error');
|
|
1214
|
-
} finally {
|
|
1215
|
-
setDeleteWsLoading(null);
|
|
1216
|
-
}
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
|
-
const handleAddRepo = async (wsName: string, repoPath: string) => {
|
|
1220
|
-
setAddRepoLoading(true);
|
|
1221
|
-
try {
|
|
1222
|
-
const encodedId = encodeURIComponent(wsName);
|
|
1223
|
-
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/repo`, {
|
|
1224
|
-
method: 'POST',
|
|
1225
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1226
|
-
body: JSON.stringify({ repoPath }),
|
|
1227
|
-
});
|
|
1228
|
-
const data = await res.json();
|
|
1229
|
-
if (res.ok && data.success) {
|
|
1230
|
-
await fetchWorkspaces();
|
|
1231
|
-
if (activeWsId === wsName) {
|
|
1232
|
-
fetchWorkspaceServices(wsName, true);
|
|
1233
|
-
fetchGitChanges(wsName);
|
|
1234
|
-
fetchWorkspaceSessions(wsName);
|
|
1235
|
-
}
|
|
1236
|
-
showToast('Repository successfully added and configurations updated.', 'success');
|
|
1237
|
-
} else {
|
|
1238
|
-
showToast(`Failed to add repository: ${data.error || 'Unknown error'}`, 'error');
|
|
1239
|
-
}
|
|
1240
|
-
} catch (e) {
|
|
1241
|
-
console.error(e);
|
|
1242
|
-
showToast('Network error while adding repository.', 'error');
|
|
1243
|
-
} finally {
|
|
1244
|
-
setAddRepoLoading(false);
|
|
1245
|
-
}
|
|
1246
|
-
};
|
|
1247
|
-
|
|
1248
|
-
const filteredRepos = repos.filter((r) =>
|
|
1249
|
-
r.name.toLowerCase().includes(repoSearch.toLowerCase())
|
|
1250
|
-
);
|
|
1251
|
-
|
|
1252
|
-
if (!configExists && config) {
|
|
1253
|
-
const isFormValid = config.devDir.trim() !== '' && config.workspacesDir.trim() !== '';
|
|
1254
|
-
|
|
1255
|
-
return (
|
|
1256
|
-
<div className="flex min-h-screen bg-[#060813] text-gray-100 font-sans items-center justify-center p-6 bg-gradient-to-br from-[#0b0e24] via-[#060813] to-[#030409]">
|
|
1257
|
-
{/* Onboarding Box */}
|
|
1258
|
-
<div className="max-w-6xl w-full glass-card border border-slate-800/60 rounded-3xl p-10 shadow-2xl glass-card-glow grid grid-cols-1 lg:grid-cols-12 gap-12 animate-slide-in">
|
|
1259
|
-
|
|
1260
|
-
{/* Left Column: Onboarding Guide & Concepts */}
|
|
1261
|
-
<div className="lg:col-span-7 flex flex-col justify-between">
|
|
1262
|
-
<div>
|
|
1263
|
-
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 text-xs font-semibold uppercase tracking-wider mb-5">
|
|
1264
|
-
<Sparkles size={12} className="text-cyan-400 animate-pulse" /> Onboarding Guide
|
|
1265
|
-
</div>
|
|
1266
|
-
<h1 className="text-3xl font-extrabold tracking-tight text-white mb-2 bg-gradient-to-r from-white via-slate-200 to-indigo-300 bg-clip-text text-transparent">
|
|
1267
|
-
Welcome to NexusFlow
|
|
1268
|
-
</h1>
|
|
1269
|
-
<p className="text-sm text-slate-400 mb-8 max-w-xl leading-relaxed">
|
|
1270
|
-
NexusFlow orchestrates multi-repository developer environments. It combines isolated Git worktrees, automatic code analyzer sweeps, and background process running into a single dashboard.
|
|
1271
|
-
</p>
|
|
1272
|
-
|
|
1273
|
-
{/* Onboarding Steps */}
|
|
1274
|
-
<div className="space-y-6">
|
|
1275
|
-
<div className="flex gap-4 group">
|
|
1276
|
-
<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 group-hover:bg-indigo-500/20 group-hover:text-indigo-300 transition-colors">
|
|
1277
|
-
1
|
|
1278
|
-
</div>
|
|
1279
|
-
<div>
|
|
1280
|
-
<h3 className="text-sm font-semibold text-white group-hover:text-indigo-300 transition-colors">Configure Development Folders</h3>
|
|
1281
|
-
<p className="text-xs text-slate-400 mt-1 leading-relaxed">
|
|
1282
|
-
Specify your local code path and target workspaces path. For the first setup, these paths start empty so you can explicitly configure them.
|
|
1283
|
-
</p>
|
|
1284
|
-
</div>
|
|
1285
|
-
</div>
|
|
1286
|
-
|
|
1287
|
-
<div className="flex gap-4 group">
|
|
1288
|
-
<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 group-hover:bg-indigo-500/20 group-hover:text-indigo-300 transition-colors">
|
|
1289
|
-
2
|
|
1290
|
-
</div>
|
|
1291
|
-
<div>
|
|
1292
|
-
<h3 className="text-sm font-semibold text-white group-hover:text-indigo-300 transition-colors">Build Isolated Branch Workspaces</h3>
|
|
1293
|
-
<p className="text-xs text-slate-400 mt-1 leading-relaxed">
|
|
1294
|
-
Choose repositories and input your feature branch. NexusFlow runs <code>git worktree</code> to checkout dependencies under a unified folder structure, leaving your primary repository directories clean.
|
|
1295
|
-
</p>
|
|
1296
|
-
</div>
|
|
1297
|
-
</div>
|
|
1298
|
-
|
|
1299
|
-
<div className="flex gap-4 group">
|
|
1300
|
-
<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 group-hover:bg-indigo-500/20 group-hover:text-indigo-300 transition-colors">
|
|
1301
|
-
3
|
|
1302
|
-
</div>
|
|
1303
|
-
<div>
|
|
1304
|
-
<h3 className="text-sm font-semibold text-white group-hover:text-indigo-300 transition-colors">Align AI Coding Contexts</h3>
|
|
1305
|
-
<p className="text-xs text-slate-400 mt-1 leading-relaxed">
|
|
1306
|
-
NexusFlow automatically generates configuration files (<code>CLAUDE.md</code>, <code>.cursorrules</code>, <code>AGENTS.md</code>) that instruct the AI assistant to analyze project inter-dependencies, document its assumptions in <code>nexusflow-overview.md</code>, and highlight clarifying questions.
|
|
1307
|
-
</p>
|
|
1308
|
-
</div>
|
|
1309
|
-
</div>
|
|
1310
|
-
|
|
1311
|
-
<div className="flex gap-4 group">
|
|
1312
|
-
<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 group-hover:bg-indigo-500/20 group-hover:text-indigo-300 transition-colors">
|
|
1313
|
-
4
|
|
1314
|
-
</div>
|
|
1315
|
-
<div>
|
|
1316
|
-
<h3 className="text-sm font-semibold text-white group-hover:text-indigo-300 transition-colors">Orchestrate Background Services</h3>
|
|
1317
|
-
<p className="text-xs text-slate-400 mt-1 leading-relaxed">
|
|
1318
|
-
Run APIs, database scripts, and frontend watch tasks concurrently from the web portal. Monitor real-time logs inside a unified terminal pane.
|
|
1319
|
-
</p>
|
|
1320
|
-
</div>
|
|
1321
|
-
</div>
|
|
1322
|
-
</div>
|
|
1323
|
-
</div>
|
|
1324
|
-
|
|
1325
|
-
{/* Comparison Dashboard */}
|
|
1326
|
-
<div className="mt-8 pt-6 border-t border-slate-800/80">
|
|
1327
|
-
<h4 className="text-xs font-semibold uppercase tracking-wider text-slate-500 mb-3">Industry Comparison</h4>
|
|
1328
|
-
<div className="overflow-x-auto font-sans">
|
|
1329
|
-
<table className="w-full text-[10px] text-slate-400 text-left border-collapse">
|
|
1330
|
-
<thead>
|
|
1331
|
-
<tr className="border-b border-slate-800 text-slate-500">
|
|
1332
|
-
<th className="py-2 pr-4 font-semibold">Orchestrator</th>
|
|
1333
|
-
<th className="py-2 px-4 font-semibold">Multi-Repo</th>
|
|
1334
|
-
<th className="py-2 px-4 font-semibold">AI Rules Integration</th>
|
|
1335
|
-
<th className="py-2 pl-4 font-semibold">Weight</th>
|
|
1336
|
-
</tr>
|
|
1337
|
-
</thead>
|
|
1338
|
-
<tbody>
|
|
1339
|
-
<tr className="border-b border-slate-850">
|
|
1340
|
-
<td className="py-2 pr-4 font-semibold text-white">Docker Compose</td>
|
|
1341
|
-
<td className="py-2 px-4">Yes (Containers only)</td>
|
|
1342
|
-
<td className="py-2 px-4">No</td>
|
|
1343
|
-
<td className="py-2 pl-4">Medium (VM overhead)</td>
|
|
1344
|
-
</tr>
|
|
1345
|
-
<tr className="border-b border-slate-850">
|
|
1346
|
-
<td className="py-2 pr-4 font-semibold text-white">Lerna / Turborepo</td>
|
|
1347
|
-
<td className="py-2 px-4">Monorepo only</td>
|
|
1348
|
-
<td className="py-2 px-4">No</td>
|
|
1349
|
-
<td className="py-2 pl-4">Light</td>
|
|
1350
|
-
</tr>
|
|
1351
|
-
<tr className="border-b border-slate-850">
|
|
1352
|
-
<td className="py-2 pr-4 font-semibold text-white">DevPod / Gitpod</td>
|
|
1353
|
-
<td className="py-2 px-4">Yes (Complex config)</td>
|
|
1354
|
-
<td className="py-2 px-4">No</td>
|
|
1355
|
-
<td className="py-2 pl-4">Heavy (Full virtual VM)</td>
|
|
1356
|
-
</tr>
|
|
1357
|
-
<tr>
|
|
1358
|
-
<td className="py-2 pr-4 font-semibold text-indigo-400 text-glow-indigo">NexusFlow</td>
|
|
1359
|
-
<td className="py-2 px-4 text-indigo-300 font-medium">Yes (Native Worktrees)</td>
|
|
1360
|
-
<td className="py-2 px-4 text-indigo-300 font-medium">Yes (CLAUDE.md/MDC rules)</td>
|
|
1361
|
-
<td className="py-2 pl-4 text-indigo-300 font-medium">Extremely Light (Native processes)</td>
|
|
1362
|
-
</tr>
|
|
1363
|
-
</tbody>
|
|
1364
|
-
</table>
|
|
1365
|
-
</div>
|
|
1366
|
-
</div>
|
|
1367
|
-
</div>
|
|
1368
|
-
|
|
1369
|
-
{/* Right Column: Configuration Form */}
|
|
1370
|
-
<div className="lg:col-span-5 flex flex-col justify-center">
|
|
1371
|
-
<div className="bg-slate-950/50 border border-slate-850 rounded-2xl p-8 shadow-xl relative overflow-hidden before:absolute before:inset-0 before:bg-gradient-to-b before:from-indigo-500/5 before:to-transparent before:pointer-events-none">
|
|
1372
|
-
<h2 className="text-xl font-bold text-white mb-1 flex items-center gap-2">
|
|
1373
|
-
<SettingsIcon className="text-indigo-400 animate-spin-slow" size={20} /> Initialize Config
|
|
1374
|
-
</h2>
|
|
1375
|
-
<p className="text-xs text-slate-500 mb-6">
|
|
1376
|
-
Define the directories on your machine. The fields are empty so you can provide your paths.
|
|
1377
|
-
</p>
|
|
1378
|
-
|
|
1379
|
-
{/* Form Input fields */}
|
|
1380
|
-
<div className="space-y-5">
|
|
1381
|
-
<div>
|
|
1382
|
-
<label className="block text-[10px] font-semibold text-slate-400 uppercase tracking-wider mb-2">Development Directory</label>
|
|
1383
|
-
<input
|
|
1384
|
-
type="text"
|
|
1385
|
-
className="w-full bg-slate-950/60 border border-slate-800 focus:border-indigo-500/80 focus:ring-1 focus:ring-indigo-500/35 rounded-xl px-4 py-3 text-white placeholder-slate-600 transition-all outline-none text-xs shadow-inner"
|
|
1386
|
-
placeholder="e.g. C:\Users\username\dev"
|
|
1387
|
-
value={config.devDir}
|
|
1388
|
-
onChange={(e) => setConfig({ ...config, devDir: e.target.value })}
|
|
1389
|
-
/>
|
|
1390
|
-
{defaultPaths && (
|
|
1391
|
-
<div className="text-[10px] text-slate-500 mt-1.5 flex justify-between items-center">
|
|
1392
|
-
<span>Suggested: <code className="text-indigo-300">{defaultPaths.devDir}</code></span>
|
|
1393
|
-
</div>
|
|
1394
|
-
)}
|
|
1395
|
-
</div>
|
|
1396
|
-
|
|
1397
|
-
<div>
|
|
1398
|
-
<label className="block text-[10px] font-semibold text-slate-400 uppercase tracking-wider mb-2">Workspaces Directory</label>
|
|
1399
|
-
<input
|
|
1400
|
-
type="text"
|
|
1401
|
-
className="w-full bg-slate-950/60 border border-slate-800 focus:border-indigo-500/80 focus:ring-1 focus:ring-indigo-500/35 rounded-xl px-4 py-3 text-white placeholder-slate-600 transition-all outline-none text-xs shadow-inner"
|
|
1402
|
-
placeholder="e.g. C:\Users\username\dev\workspaces"
|
|
1403
|
-
value={config.workspacesDir}
|
|
1404
|
-
onChange={(e) => setConfig({ ...config, workspacesDir: e.target.value })}
|
|
1405
|
-
/>
|
|
1406
|
-
{defaultPaths && (
|
|
1407
|
-
<div className="text-[10px] text-slate-500 mt-1.5 flex justify-between items-center">
|
|
1408
|
-
<span>Suggested: <code className="text-indigo-300">{defaultPaths.workspacesDir}</code></span>
|
|
1409
|
-
</div>
|
|
1410
|
-
)}
|
|
1411
|
-
</div>
|
|
1412
|
-
|
|
1413
|
-
<div className="pt-2">
|
|
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.'}
|
|
1453
|
-
</span>
|
|
1454
|
-
</div>
|
|
1455
|
-
|
|
1456
|
-
{/* Form Buttons */}
|
|
1457
|
-
<div className="flex gap-2 pt-2">
|
|
1458
|
-
<button
|
|
1459
|
-
type="button"
|
|
1460
|
-
className="flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-2.5 bg-slate-900 border border-slate-800/80 hover:bg-indigo-500/5 hover:border-indigo-500/30 rounded-xl text-xs font-semibold transition-all cursor-pointer text-slate-300 hover:text-white"
|
|
1461
|
-
onClick={() => {
|
|
1462
|
-
if (defaultPaths) {
|
|
1463
|
-
setConfig({
|
|
1464
|
-
...config,
|
|
1465
|
-
devDir: defaultPaths.devDir,
|
|
1466
|
-
workspacesDir: defaultPaths.workspacesDir,
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
}}
|
|
1470
|
-
>
|
|
1471
|
-
Suggest Defaults
|
|
1472
|
-
</button>
|
|
1473
|
-
<button
|
|
1474
|
-
type="button"
|
|
1475
|
-
className="px-3 py-2.5 bg-slate-900 border border-slate-800/80 hover:bg-rose-500/5 hover:border-rose-500/30 hover:text-rose-400 rounded-xl text-xs font-semibold transition-all cursor-pointer text-slate-400"
|
|
1476
|
-
onClick={() => {
|
|
1477
|
-
setConfig({
|
|
1478
|
-
...config,
|
|
1479
|
-
devDir: '',
|
|
1480
|
-
workspacesDir: '',
|
|
1481
|
-
});
|
|
1482
|
-
}}
|
|
1483
|
-
>
|
|
1484
|
-
Clear
|
|
1485
|
-
</button>
|
|
1486
|
-
</div>
|
|
1487
|
-
|
|
1488
|
-
{/* Save Button */}
|
|
1489
|
-
<div className="pt-4">
|
|
1490
|
-
<button
|
|
1491
|
-
type="button"
|
|
1492
|
-
className="w-full inline-flex items-center justify-center gap-2 px-5 py-3.5 rounded-xl text-sm font-bold bg-gradient-to-r from-indigo-500 via-indigo-600 to-purple-650 hover:from-indigo-600 hover:to-purple-700 text-white shadow-lg shadow-indigo-500/20 hover:-translate-y-0.5 active:translate-y-0 disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:translate-y-0 transition-all cursor-pointer"
|
|
1493
|
-
disabled={!isFormValid}
|
|
1494
|
-
onClick={() => saveAppConfig(config)}
|
|
1495
|
-
>
|
|
1496
|
-
Save & Get Started <ArrowRight size={16} />
|
|
1497
|
-
</button>
|
|
1498
|
-
</div>
|
|
1499
|
-
</div>
|
|
1500
|
-
</div>
|
|
1501
|
-
</div>
|
|
1502
|
-
</div>
|
|
1503
|
-
</div>
|
|
1504
|
-
);
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
if (isVsCode) {
|
|
1508
|
-
return (
|
|
1509
|
-
<div className="flex flex-col h-screen w-full bg-[#060813] text-[#d1d5db] font-mono text-[11px] overflow-hidden select-none border-t border-gray-800">
|
|
1510
|
-
{/* Terminal Header */}
|
|
1511
|
-
<div className="flex items-center justify-between px-3 py-2 bg-[#0b0f19] border-b border-gray-800 shrink-0 text-[10px]">
|
|
1512
|
-
<div className="flex items-center gap-2">
|
|
1513
|
-
<span className="font-bold text-indigo-400">NEXUSFLOW_SHELL</span>
|
|
1514
|
-
<span className="text-gray-700">|</span>
|
|
1515
|
-
<span className="text-gray-300">ws: {activeWsId || 'none'}</span>
|
|
1516
|
-
</div>
|
|
1517
|
-
<div className="flex items-center gap-2">
|
|
1518
|
-
{activeWsId && (
|
|
1519
|
-
<button
|
|
1520
|
-
onClick={() => {
|
|
1521
|
-
setActiveWsId(null);
|
|
1522
|
-
setSelectedLogService(null);
|
|
1523
|
-
setServiceLogs('');
|
|
1524
|
-
}}
|
|
1525
|
-
className="text-indigo-400 hover:text-indigo-350 hover:underline transition-colors cursor-pointer bg-transparent border-none p-0 outline-none"
|
|
1526
|
-
>
|
|
1527
|
-
[Change WS]
|
|
1528
|
-
</button>
|
|
1529
|
-
)}
|
|
1530
|
-
<span className="text-[10px] text-gray-500">v{appVersion}</span>
|
|
1531
|
-
</div>
|
|
1532
|
-
</div>
|
|
1533
|
-
|
|
1534
|
-
{!activeWsId ? (
|
|
1535
|
-
/* CLI Workspace Selection Menu */
|
|
1536
|
-
<div className="flex-1 flex flex-col items-center justify-center p-6 text-center overflow-y-auto">
|
|
1537
|
-
<div className="text-indigo-400 font-bold mb-4 text-[12px] uppercase">
|
|
1538
|
-
=== Select Active Workspace ===
|
|
1539
|
-
</div>
|
|
1540
|
-
<div className="flex flex-col gap-2 w-full max-w-xs">
|
|
1541
|
-
{workspaces.map((ws) => (
|
|
1542
|
-
<button
|
|
1543
|
-
key={ws.branchName}
|
|
1544
|
-
onClick={() => {
|
|
1545
|
-
setActiveWsId(ws.branchName);
|
|
1546
|
-
fetchWorkspaceServices(ws.branchName);
|
|
1547
|
-
}}
|
|
1548
|
-
className="w-full text-left p-2.5 bg-gray-950 border border-gray-800 hover:border-indigo-500 rounded hover:bg-indigo-500/5 text-gray-300 hover:text-white transition-all text-[11px] cursor-pointer"
|
|
1549
|
-
>
|
|
1550
|
-
> {ws.branchName}
|
|
1551
|
-
</button>
|
|
1552
|
-
))}
|
|
1553
|
-
{workspaces.length === 0 && (
|
|
1554
|
-
<div className="text-gray-550 italic">No workspaces found. Initialize one via the CLI.</div>
|
|
1555
|
-
)}
|
|
1556
|
-
</div>
|
|
1557
|
-
</div>
|
|
1558
|
-
) : (
|
|
1559
|
-
/* Active Workspace Panel */
|
|
1560
|
-
<div className="flex-1 flex flex-col min-h-0">
|
|
1561
|
-
{/* Top Config / Control Panel */}
|
|
1562
|
-
<div className="p-3 border-b border-gray-800 shrink-0 bg-gray-950/30">
|
|
1563
|
-
{/* Service Control Buttons */}
|
|
1564
|
-
<div className="flex gap-2 mb-3">
|
|
1565
|
-
<button
|
|
1566
|
-
onClick={() => handleStartServices(activeWsId)}
|
|
1567
|
-
className="flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded border border-emerald-500/20 bg-emerald-500/5 hover:bg-emerald-500/10 text-emerald-400 font-bold transition-all cursor-pointer text-[10px]"
|
|
1568
|
-
>
|
|
1569
|
-
<Play size={10} /> [START SERVICES]
|
|
1570
|
-
</button>
|
|
1571
|
-
<button
|
|
1572
|
-
onClick={() => handleStopServices(activeWsId)}
|
|
1573
|
-
className="flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded border border-rose-500/20 bg-rose-500/5 hover:bg-rose-500/10 text-rose-400 font-bold transition-all cursor-pointer text-[10px]"
|
|
1574
|
-
>
|
|
1575
|
-
<X size={10} /> [STOP SERVICES]
|
|
1576
|
-
</button>
|
|
1577
|
-
</div>
|
|
1578
|
-
|
|
1579
|
-
{/* Service List */}
|
|
1580
|
-
<div className="mb-3">
|
|
1581
|
-
<div className="text-[10px] text-gray-400 uppercase font-bold tracking-wider mb-1 px-1">
|
|
1582
|
-
Background Services ({runningServices.filter(rs => rs.pid > 0).length}/{services.length})
|
|
1583
|
-
</div>
|
|
1584
|
-
{services.length === 0 ? (
|
|
1585
|
-
<div className="text-gray-600 italic px-1 text-[10px]">No services detected in workspace.</div>
|
|
1586
|
-
) : (
|
|
1587
|
-
<div className="flex flex-col gap-1 max-h-[120px] overflow-y-auto pr-1">
|
|
1588
|
-
{services.map((service) => {
|
|
1589
|
-
const isRunning = runningServices.some((rs) => rs.name === service.name && rs.pid > 0);
|
|
1590
|
-
const isSelected = selectedLogService === service.name;
|
|
1591
|
-
return (
|
|
1592
|
-
<div
|
|
1593
|
-
key={service.name}
|
|
1594
|
-
onClick={() => setSelectedLogService(service.name)}
|
|
1595
|
-
className={`flex items-center justify-between p-1.5 border rounded cursor-pointer transition-all ${
|
|
1596
|
-
isSelected
|
|
1597
|
-
? 'bg-indigo-950/40 border-indigo-500/50 text-white'
|
|
1598
|
-
: 'bg-gray-950/20 border-gray-800 hover:border-gray-700 text-gray-300'
|
|
1599
|
-
}`}
|
|
1600
|
-
>
|
|
1601
|
-
<div className="flex items-center gap-2 truncate">
|
|
1602
|
-
<span className={`h-1.5 w-1.5 rounded-full ${isRunning ? 'bg-emerald-400 animate-pulse' : 'bg-rose-500'}`}></span>
|
|
1603
|
-
<span className="font-bold truncate">{service.name}</span>
|
|
1604
|
-
</div>
|
|
1605
|
-
<span className="text-[10px] font-mono shrink-0">
|
|
1606
|
-
{isRunning ? (
|
|
1607
|
-
<span className="text-emerald-400 bg-emerald-500/10 px-1 py-0.2 rounded border border-emerald-500/20 text-[8px] font-bold uppercase">on</span>
|
|
1608
|
-
) : (
|
|
1609
|
-
<span className="text-gray-500 bg-gray-900 px-1 py-0.2 rounded border border-gray-800 text-[8px] font-bold uppercase">off</span>
|
|
1610
|
-
)}
|
|
1611
|
-
</span>
|
|
1612
|
-
</div>
|
|
1613
|
-
);
|
|
1614
|
-
})}
|
|
1615
|
-
</div>
|
|
1616
|
-
)}
|
|
1617
|
-
</div>
|
|
1618
|
-
|
|
1619
|
-
{/* MCP Status Check */}
|
|
1620
|
-
<div className="mb-3 border-t border-gray-800 pt-2.5">
|
|
1621
|
-
<div className="text-[10px] text-gray-400 font-bold uppercase mb-1 px-1">
|
|
1622
|
-
Active Local MCP Tools
|
|
1623
|
-
</div>
|
|
1624
|
-
<div className="flex flex-col gap-1 px-1 text-[10px] text-gray-450 font-mono">
|
|
1625
|
-
<div className="flex justify-between">
|
|
1626
|
-
<span>• search_workspace</span>
|
|
1627
|
-
<span className="text-indigo-400 font-bold">READY</span>
|
|
1628
|
-
</div>
|
|
1629
|
-
<div className="flex justify-between">
|
|
1630
|
-
<span>• get_service_logs</span>
|
|
1631
|
-
<span className="text-indigo-400 font-bold">READY</span>
|
|
1632
|
-
</div>
|
|
1633
|
-
<div className="flex justify-between">
|
|
1634
|
-
<span>• delegate_to_local_agent</span>
|
|
1635
|
-
<span className={config?.localLlm?.enabled ? "text-indigo-400 font-bold" : "text-gray-600 font-bold"}>
|
|
1636
|
-
{config?.localLlm?.enabled ? 'READY' : 'DISABLED'}
|
|
1637
|
-
</span>
|
|
1638
|
-
</div>
|
|
1639
|
-
</div>
|
|
1640
|
-
</div>
|
|
1641
|
-
|
|
1642
|
-
{/* Interactive CLI Buttons */}
|
|
1643
|
-
<div className="border-t border-gray-800 pt-2.5">
|
|
1644
|
-
<div className="text-[10px] text-gray-400 font-bold uppercase mb-1.5 px-1">
|
|
1645
|
-
Terminal Commands (Click to Run)
|
|
1646
|
-
</div>
|
|
1647
|
-
<div className="grid grid-cols-2 gap-1.5">
|
|
1648
|
-
<button
|
|
1649
|
-
onClick={() => executeTerminal('nexusflow sync')}
|
|
1650
|
-
className="px-2 py-1.5 text-left border border-gray-850 hover:border-indigo-500 bg-gray-950/40 rounded text-gray-300 hover:text-white hover:bg-indigo-950/20 transition-all font-mono text-[9px] cursor-pointer"
|
|
1651
|
-
>
|
|
1652
|
-
$ nexusflow sync
|
|
1653
|
-
</button>
|
|
1654
|
-
<button
|
|
1655
|
-
onClick={() => executeTerminal('nexusflow diff')}
|
|
1656
|
-
className="px-2 py-1.5 text-left border border-gray-850 hover:border-indigo-500 bg-gray-950/40 rounded text-gray-300 hover:text-white hover:bg-indigo-950/20 transition-all font-mono text-[9px] cursor-pointer"
|
|
1657
|
-
>
|
|
1658
|
-
$ nexusflow diff
|
|
1659
|
-
</button>
|
|
1660
|
-
<button
|
|
1661
|
-
onClick={() => executeTerminal('nexusflow handoff')}
|
|
1662
|
-
className="px-2 py-1.5 text-left border border-gray-850 hover:border-indigo-500 bg-gray-950/40 rounded text-gray-300 hover:text-white hover:bg-indigo-950/20 transition-all font-mono text-[9px] cursor-pointer"
|
|
1663
|
-
>
|
|
1664
|
-
$ nexusflow handoff
|
|
1665
|
-
</button>
|
|
1666
|
-
<button
|
|
1667
|
-
onClick={() => executeTerminal('nexusflow status')}
|
|
1668
|
-
className="px-2 py-1.5 text-left border border-gray-850 hover:border-indigo-500 bg-gray-950/40 rounded text-gray-300 hover:text-white hover:bg-indigo-950/20 transition-all font-mono text-[9px] cursor-pointer"
|
|
1669
|
-
>
|
|
1670
|
-
$ nexusflow status
|
|
1671
|
-
</button>
|
|
1672
|
-
</div>
|
|
1673
|
-
</div>
|
|
1674
|
-
</div>
|
|
1675
|
-
|
|
1676
|
-
{/* Bottom Log Pane */}
|
|
1677
|
-
<div className="flex-1 flex flex-col min-h-0 bg-[#04060d]">
|
|
1678
|
-
<div className="flex items-center justify-between px-3 py-1.5 bg-[#090d1a] border-b border-gray-800/80 shrink-0 text-[9px] text-gray-400 uppercase tracking-wider font-bold">
|
|
1679
|
-
<div className="flex items-center gap-1.5">
|
|
1680
|
-
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500 animate-pulse"></span>
|
|
1681
|
-
<span>log_stream: {selectedLogService || 'none'}</span>
|
|
1682
|
-
</div>
|
|
1683
|
-
<button
|
|
1684
|
-
onClick={() => {
|
|
1685
|
-
if (activeWsId && selectedLogService) {
|
|
1686
|
-
fetchLogs(activeWsId, selectedLogService);
|
|
1687
|
-
}
|
|
1688
|
-
}}
|
|
1689
|
-
className="text-gray-500 hover:text-white font-mono hover:underline cursor-pointer bg-transparent border-none p-0 outline-none"
|
|
1690
|
-
>
|
|
1691
|
-
[refresh]
|
|
1692
|
-
</button>
|
|
1693
|
-
</div>
|
|
1694
|
-
<div className="flex-1 p-3 overflow-y-auto font-mono text-[10px] leading-relaxed whitespace-pre-wrap select-text selection:bg-indigo-500/30 text-gray-300">
|
|
1695
|
-
{serviceLogs.trim() ? (
|
|
1696
|
-
serviceLogs
|
|
1697
|
-
) : (
|
|
1698
|
-
<span className="text-gray-600 italic font-mono">(no logs recorded yet)</span>
|
|
1699
|
-
)}
|
|
1700
|
-
<div ref={logsEndRef} />
|
|
1701
|
-
</div>
|
|
1702
|
-
</div>
|
|
1703
|
-
</div>
|
|
1704
|
-
)}
|
|
1705
|
-
</div>
|
|
1706
|
-
);
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
return (
|
|
1710
|
-
<div className="flex min-h-screen bg-[#05070c] text-slate-300 font-mono crt-screen">
|
|
1711
|
-
{/* Sidebar Navigation */}
|
|
1712
|
-
<aside className="w-64 bg-[#05070c] border-r border-slate-900 flex flex-col p-6 shrink-0 relative z-10 neon-border">
|
|
1713
|
-
<div className="flex items-center gap-3 mb-10 px-2">
|
|
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>
|
|
1722
|
-
</div>
|
|
1723
|
-
</div>
|
|
1724
|
-
<nav className="flex-1">
|
|
1725
|
-
<ul className="flex flex-col gap-2">
|
|
1726
|
-
<li>
|
|
1727
|
-
<button
|
|
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 ${
|
|
1729
|
-
view === 'guide'
|
|
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'
|
|
1732
|
-
}`}
|
|
1733
|
-
onClick={() => {
|
|
1734
|
-
setView('guide');
|
|
1735
|
-
}}
|
|
1736
|
-
>
|
|
1737
|
-
<Sparkles size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
|
|
1738
|
-
Getting Started
|
|
1739
|
-
</button>
|
|
1740
|
-
</li>
|
|
1741
|
-
<li>
|
|
1742
|
-
<button
|
|
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 ${
|
|
1744
|
-
view === 'create'
|
|
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'
|
|
1747
|
-
}`}
|
|
1748
|
-
onClick={() => {
|
|
1749
|
-
setView('create');
|
|
1750
|
-
setActiveStep(0);
|
|
1751
|
-
setCreatedWorkspace(null);
|
|
1752
|
-
setBranchName('');
|
|
1753
|
-
setDescription('');
|
|
1754
|
-
setSelectedRepos([]);
|
|
1755
|
-
setLocalLlmEnabled(config?.localLlm?.enabled || false);
|
|
1756
|
-
}}
|
|
1757
|
-
>
|
|
1758
|
-
<PlusCircle size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
|
|
1759
|
-
New Workspace
|
|
1760
|
-
</button>
|
|
1761
|
-
</li>
|
|
1762
|
-
<li>
|
|
1763
|
-
<button
|
|
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 ${
|
|
1765
|
-
view === 'workspaces'
|
|
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'
|
|
1768
|
-
}`}
|
|
1769
|
-
onClick={() => {
|
|
1770
|
-
setView('workspaces');
|
|
1771
|
-
fetchWorkspaces();
|
|
1772
|
-
}}
|
|
1773
|
-
>
|
|
1774
|
-
<FolderGit2 size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
|
|
1775
|
-
Active Workspaces
|
|
1776
|
-
</button>
|
|
1777
|
-
</li>
|
|
1778
|
-
<li>
|
|
1779
|
-
<button
|
|
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 ${
|
|
1804
|
-
view === 'settings'
|
|
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'
|
|
1807
|
-
}`}
|
|
1808
|
-
onClick={() => {
|
|
1809
|
-
setView('settings');
|
|
1810
|
-
fetchConfig();
|
|
1811
|
-
}}
|
|
1812
|
-
>
|
|
1813
|
-
<SettingsIcon size={16} className="text-cyan-400 group-hover:scale-110 transition-transform" />
|
|
1814
|
-
Settings
|
|
1815
|
-
</button>
|
|
1816
|
-
</li>
|
|
1817
|
-
</ul>
|
|
1818
|
-
</nav>
|
|
1819
|
-
<div className="pt-6 border-t border-slate-900 text-[10px] text-slate-500 text-center tracking-wider font-semibold uppercase">
|
|
1820
|
-
SYSTEM_UI: ONLINE v{appVersion}
|
|
1821
|
-
</div>
|
|
1822
|
-
</aside>
|
|
1823
|
-
|
|
1824
|
-
{/* Main Content Area */}
|
|
1825
|
-
<main className="flex-1 p-8 overflow-y-auto max-w-7xl w-full mx-auto">
|
|
1826
|
-
{configLoading ? (
|
|
1827
|
-
<div className="flex flex-col items-center justify-center py-40 gap-4 text-gray-400">
|
|
1828
|
-
<RefreshCw className="animate-spin text-indigo-400" size={32} />
|
|
1829
|
-
<span className="text-sm font-medium">Loading config settings...</span>
|
|
1830
|
-
</div>
|
|
1831
|
-
) : (
|
|
1832
|
-
<>
|
|
1833
|
-
{/* Update Notification Banner */}
|
|
1834
|
-
{updateStatus && updateStatus.updateAvailable && (
|
|
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">
|
|
1836
|
-
<div className="flex items-center gap-3">
|
|
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">
|
|
1838
|
-
{updatingApp ? (
|
|
1839
|
-
<RefreshCw size={20} className="animate-spin text-amber-400" />
|
|
1840
|
-
) : (
|
|
1841
|
-
<Sparkles size={20} />
|
|
1842
|
-
)}
|
|
1843
|
-
</div>
|
|
1844
|
-
<div>
|
|
1845
|
-
<h4 className="text-sm font-bold text-amber-300">
|
|
1846
|
-
{updatingApp ? (
|
|
1847
|
-
updateStep === 'downloading' ? 'Downloading Update...' : 'Applying Update...'
|
|
1848
|
-
) : (
|
|
1849
|
-
'A new version of NexusFlow is available!'
|
|
1850
|
-
)}
|
|
1851
|
-
</h4>
|
|
1852
|
-
<p className="text-xs text-gray-400 mt-0.5">
|
|
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
|
-
)}
|
|
1858
|
-
</p>
|
|
1859
|
-
</div>
|
|
1860
|
-
</div>
|
|
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
|
-
)}
|
|
1871
|
-
<button
|
|
1872
|
-
onClick={() => {
|
|
1873
|
-
navigator.clipboard.writeText('npm install -g @mrpatronz/nexusflow');
|
|
1874
|
-
showToast('Update command copied to clipboard!', 'success');
|
|
1875
|
-
}}
|
|
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"
|
|
1878
|
-
>
|
|
1879
|
-
Copy Update Command
|
|
1880
|
-
</button>
|
|
1881
|
-
</div>
|
|
1882
|
-
</div>
|
|
1883
|
-
)}
|
|
1884
|
-
|
|
1885
|
-
{/* View 0: Getting Started Guide */}
|
|
1886
|
-
{view === 'guide' && config && (
|
|
1887
|
-
<div className="max-w-4xl mx-auto">
|
|
1888
|
-
<header className="mb-10">
|
|
1889
|
-
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 text-xs font-semibold uppercase tracking-wider mb-4">
|
|
1890
|
-
<Sparkles size={12} className="text-cyan-400" /> Getting Started Guide
|
|
1891
|
-
</div>
|
|
1892
|
-
<h1 className="text-3xl font-extrabold tracking-tight text-white mb-2 bg-gradient-to-r from-white via-gray-200 to-indigo-300 bg-clip-text text-transparent">
|
|
1893
|
-
Welcome to NexusFlow
|
|
1894
|
-
</h1>
|
|
1895
|
-
<p className="text-sm text-gray-400">
|
|
1896
|
-
NexusFlow orchestrates multi-repository developer environments. It combines isolated Git worktrees, automatic code analyzer sweeps, and background process running into a single dashboard.
|
|
1897
|
-
</p>
|
|
1898
|
-
</header>
|
|
1899
|
-
|
|
1900
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-10">
|
|
1901
|
-
{/* Left: Interactive Stepper */}
|
|
1902
|
-
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-6 shadow-xl backdrop-blur-sm space-y-6">
|
|
1903
|
-
<h2 className="text-lg font-bold text-white mb-4">NexusFlow Workflows</h2>
|
|
1904
|
-
|
|
1905
|
-
<div className="flex gap-4">
|
|
1906
|
-
<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">
|
|
1907
|
-
1
|
|
1908
|
-
</div>
|
|
1909
|
-
<div>
|
|
1910
|
-
<h3 className="text-sm font-semibold text-white">Configure Folders</h3>
|
|
1911
|
-
<p className="text-xs text-gray-400 mt-1">
|
|
1912
|
-
Specify your Development folder (where repositories live) and Workspaces folder. These are currently set to:
|
|
1913
|
-
<code className="block mt-1 text-[10px] text-indigo-300 break-all">{config.devDir || 'Not Configured'}</code>
|
|
1914
|
-
</p>
|
|
1915
|
-
</div>
|
|
1916
|
-
</div>
|
|
1917
|
-
|
|
1918
|
-
<div className="flex gap-4">
|
|
1919
|
-
<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">
|
|
1920
|
-
2
|
|
1921
|
-
</div>
|
|
1922
|
-
<div>
|
|
1923
|
-
<h3 className="text-sm font-semibold text-white">Build Workspaces</h3>
|
|
1924
|
-
<p className="text-xs text-gray-400 mt-1">
|
|
1925
|
-
Choose repositories and feature branch. NexusFlow checks out dependencies under a unified workspace directory using Git Worktrees, keeping original projects clean.
|
|
1926
|
-
</p>
|
|
1927
|
-
</div>
|
|
1928
|
-
</div>
|
|
1929
|
-
|
|
1930
|
-
<div className="flex gap-4">
|
|
1931
|
-
<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">
|
|
1932
|
-
3
|
|
1933
|
-
</div>
|
|
1934
|
-
<div>
|
|
1935
|
-
<h3 className="text-sm font-semibold text-white">Align AI Context</h3>
|
|
1936
|
-
<p className="text-xs text-gray-400 mt-1">
|
|
1937
|
-
NexusFlow writes files like <code>CLAUDE.md</code>, <code>.cursorrules</code>, <code>AGENTS.md</code> prompting the LLM to inspect project relations and list key assumptions and questions before coding.
|
|
1938
|
-
</p>
|
|
1939
|
-
</div>
|
|
1940
|
-
</div>
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
</div>
|
|
1944
|
-
|
|
1945
|
-
{/* Right: Quick actions and Telemetry info */}
|
|
1946
|
-
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-6 shadow-xl backdrop-blur-sm flex flex-col justify-between">
|
|
1947
|
-
<div>
|
|
1948
|
-
<h2 className="text-lg font-bold text-white mb-4">Current Configuration</h2>
|
|
1949
|
-
<div className="space-y-3 text-xs">
|
|
1950
|
-
<div className="flex justify-between py-1.5 border-b border-gray-800/60">
|
|
1951
|
-
<span className="text-gray-500">Dev Folder:</span>
|
|
1952
|
-
<span className="text-gray-300 font-mono text-[10px] truncate max-w-[200px]">{config.devDir}</span>
|
|
1953
|
-
</div>
|
|
1954
|
-
<div className="flex justify-between py-1.5 border-b border-gray-800/60">
|
|
1955
|
-
<span className="text-gray-500">Workspaces Folder:</span>
|
|
1956
|
-
<span className="text-gray-300 font-mono text-[10px] truncate max-w-[200px]">{config.workspacesDir}</span>
|
|
1957
|
-
</div>
|
|
1958
|
-
<div className="flex justify-between py-1.5 border-b border-gray-800/60">
|
|
1959
|
-
<span className="text-gray-500">Preferred AI:</span>
|
|
1960
|
-
<span className="text-indigo-400 font-semibold uppercase">{config.defaultAssistant || 'None'}</span>
|
|
1961
|
-
</div>
|
|
1962
|
-
<div className="flex justify-between py-1.5">
|
|
1963
|
-
<span className="text-gray-500">Scan Depth:</span>
|
|
1964
|
-
<span className="text-gray-300 font-semibold">{config.scanDepth} levels</span>
|
|
1965
|
-
</div>
|
|
1966
|
-
</div>
|
|
1967
|
-
</div>
|
|
1968
|
-
|
|
1969
|
-
<div className="pt-6 border-t border-gray-800/60 flex flex-col gap-2">
|
|
1970
|
-
<button
|
|
1971
|
-
className="w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-xs font-semibold bg-gradient-to-r from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white shadow-md shadow-indigo-500/10 hover:-translate-y-0.5 active:translate-y-0 transition-all cursor-pointer"
|
|
1972
|
-
onClick={() => setView('create')}
|
|
1973
|
-
>
|
|
1974
|
-
<PlusCircle size={14} /> Create a Workspace
|
|
1975
|
-
</button>
|
|
1976
|
-
<button
|
|
1977
|
-
className="w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-xs font-semibold bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white transition-all cursor-pointer"
|
|
1978
|
-
onClick={() => setView('settings')}
|
|
1979
|
-
>
|
|
1980
|
-
<SettingsIcon size={14} className="text-gray-500" /> Modify Settings
|
|
1981
|
-
</button>
|
|
1982
|
-
</div>
|
|
1983
|
-
</div>
|
|
1984
|
-
</div>
|
|
1985
|
-
|
|
1986
|
-
{/* Compare dashboard at the bottom */}
|
|
1987
|
-
<div className="bg-[#111827]/20 border border-gray-800/80 rounded-xl p-6 shadow-md">
|
|
1988
|
-
<h4 className="text-xs font-semibold uppercase tracking-wider text-gray-500 mb-3">Industry Comparison</h4>
|
|
1989
|
-
<div className="overflow-x-auto">
|
|
1990
|
-
<table className="w-full text-[10px] text-gray-400 text-left border-collapse">
|
|
1991
|
-
<thead>
|
|
1992
|
-
<tr className="border-b border-gray-800 text-gray-500">
|
|
1993
|
-
<th className="py-2 pr-4 font-semibold">Orchestrator</th>
|
|
1994
|
-
<th className="py-2 px-4 font-semibold">Multi-Repo</th>
|
|
1995
|
-
<th className="py-2 px-4 font-semibold">AI Rules Integration</th>
|
|
1996
|
-
<th className="py-2 pl-4 font-semibold">Weight</th>
|
|
1997
|
-
</tr>
|
|
1998
|
-
</thead>
|
|
1999
|
-
<tbody>
|
|
2000
|
-
<tr className="border-b border-gray-800/40">
|
|
2001
|
-
<td className="py-2 pr-4 font-semibold text-white">Docker Compose</td>
|
|
2002
|
-
<td className="py-2 px-4">Yes (Containers only)</td>
|
|
2003
|
-
<td className="py-2 px-4">No</td>
|
|
2004
|
-
<td className="py-2 pl-4">Medium (VM overhead)</td>
|
|
2005
|
-
</tr>
|
|
2006
|
-
<tr className="border-b border-gray-800/40">
|
|
2007
|
-
<td className="py-2 pr-4 font-semibold text-white">Lerna / Turborepo</td>
|
|
2008
|
-
<td className="py-2 px-4">Monorepo only</td>
|
|
2009
|
-
<td className="py-2 px-4">No</td>
|
|
2010
|
-
<td className="py-2 pl-4">Light</td>
|
|
2011
|
-
</tr>
|
|
2012
|
-
<tr className="border-b border-gray-800/40">
|
|
2013
|
-
<td className="py-2 pr-4 font-semibold text-white">DevPod / Gitpod</td>
|
|
2014
|
-
<td className="py-2 px-4">Yes (Complex config)</td>
|
|
2015
|
-
<td className="py-2 px-4">No</td>
|
|
2016
|
-
<td className="py-2 pl-4">Heavy (Full virtual VM)</td>
|
|
2017
|
-
</tr>
|
|
2018
|
-
<tr>
|
|
2019
|
-
<td className="py-2 pr-4 font-semibold text-indigo-400">NexusFlow</td>
|
|
2020
|
-
<td className="py-2 px-4 text-indigo-300">Yes (Native Worktrees)</td>
|
|
2021
|
-
<td className="py-2 px-4 text-indigo-300">Yes (CLAUDE.md/MDC rules)</td>
|
|
2022
|
-
<td className="py-2 pl-4 text-indigo-300">Extremely Light (Native processes)</td>
|
|
2023
|
-
</tr>
|
|
2024
|
-
</tbody>
|
|
2025
|
-
</table>
|
|
2026
|
-
</div>
|
|
2027
|
-
</div>
|
|
2028
|
-
</div>
|
|
2029
|
-
)}
|
|
2030
|
-
|
|
2031
|
-
{/* View 1: Wizard Workspace Builder */}
|
|
2032
|
-
{view === 'create' && (
|
|
2033
|
-
<div className="max-w-4xl mx-auto">
|
|
2034
|
-
<header className="mb-10">
|
|
2035
|
-
<h1 className="text-3xl font-extrabold tracking-tight text-white mb-2">New Feature Workspace</h1>
|
|
2036
|
-
<p className="text-sm text-gray-400">
|
|
2037
|
-
Set up unified git worktrees, scan tech stacks, and write contextual configurations for your AI assistant.
|
|
2038
|
-
</p>
|
|
2039
|
-
</header>
|
|
2040
|
-
|
|
2041
|
-
{/* Progress Circle bar */}
|
|
2042
|
-
<div className="flex justify-between items-center max-w-2xl mx-auto mb-14 relative px-4">
|
|
2043
|
-
<div className="absolute top-4 left-6 right-6 h-[2px] bg-gray-800 -z-10"></div>
|
|
2044
|
-
<div
|
|
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"
|
|
2046
|
-
style={{ width: `${(activeStep / 4) * 95}%` }}
|
|
2047
|
-
></div>
|
|
2048
|
-
<div className="flex flex-col items-center gap-2">
|
|
2049
|
-
<div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
|
|
2050
|
-
activeStep > 0 ? 'bg-emerald-500 border-emerald-500 text-white' : activeStep === 0 ? 'border-indigo-500 bg-[#0b0f19] text-white shadow-lg shadow-indigo-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
|
|
2051
|
-
}`}>
|
|
2052
|
-
{activeStep > 0 ? <Check size={14} /> : '1'}
|
|
2053
|
-
</div>
|
|
2054
|
-
<span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 0 ? 'text-white' : 'text-gray-500'}`}>Details</span>
|
|
2055
|
-
</div>
|
|
2056
|
-
<div className="flex flex-col items-center gap-2">
|
|
2057
|
-
<div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
|
|
2058
|
-
activeStep > 1 ? 'bg-emerald-500 border-emerald-500 text-white' : activeStep === 1 ? 'border-indigo-500 bg-[#0b0f19] text-white shadow-lg shadow-indigo-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
|
|
2059
|
-
}`}>
|
|
2060
|
-
{activeStep > 1 ? <Check size={14} /> : '2'}
|
|
2061
|
-
</div>
|
|
2062
|
-
<span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 1 ? 'text-white' : 'text-gray-500'}`}>Repos</span>
|
|
2063
|
-
</div>
|
|
2064
|
-
<div className="flex flex-col items-center gap-2">
|
|
2065
|
-
<div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
|
|
2066
|
-
activeStep > 2 ? 'bg-emerald-500 border-emerald-500 text-white' : activeStep === 2 ? 'border-indigo-500 bg-[#0b0f19] text-white shadow-lg shadow-indigo-500/20' : 'border-gray-800 bg-gray-900 text-gray-500'
|
|
2067
|
-
}`}>
|
|
2068
|
-
{activeStep > 2 ? <Check size={14} /> : '3'}
|
|
2069
|
-
</div>
|
|
2070
|
-
<span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 2 ? 'text-white' : 'text-gray-500'}`}>Assistant</span>
|
|
2071
|
-
</div>
|
|
2072
|
-
<div className="flex flex-col items-center gap-2">
|
|
2073
|
-
<div className={`w-8 h-8 rounded-full border-2 flex items-center justify-center font-bold text-xs transition-all ${
|
|
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'
|
|
2083
|
-
}`}>
|
|
2084
|
-
5
|
|
2085
|
-
</div>
|
|
2086
|
-
<span className={`text-[11px] font-semibold tracking-wide uppercase ${activeStep === 4 ? 'text-emerald-400' : 'text-gray-500'}`}>Complete</span>
|
|
2087
|
-
</div>
|
|
2088
|
-
</div>
|
|
2089
|
-
|
|
2090
|
-
{/* Step 0: Name & Description */}
|
|
2091
|
-
{activeStep === 0 && (
|
|
2092
|
-
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm">
|
|
2093
|
-
<div className="mb-6">
|
|
2094
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Feature Branch Name</label>
|
|
2095
|
-
<input
|
|
2096
|
-
type="text"
|
|
2097
|
-
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-600 transition-all outline-none text-sm shadow-inner"
|
|
2098
|
-
placeholder="e.g., feature/oauth-authentication-flow"
|
|
2099
|
-
value={branchName}
|
|
2100
|
-
onChange={(e) => setBranchName(e.target.value)}
|
|
2101
|
-
/>
|
|
2102
|
-
</div>
|
|
2103
|
-
<div className="mb-8">
|
|
2104
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Feature Purpose & Context</label>
|
|
2105
|
-
<textarea
|
|
2106
|
-
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-600 transition-all outline-none text-sm min-h-[140px] resize-y shadow-inner"
|
|
2107
|
-
placeholder="Describe what you want to build. This helps AI assistants analyze context and produce matching plans."
|
|
2108
|
-
value={description}
|
|
2109
|
-
onChange={(e) => setDescription(e.target.value)}
|
|
2110
|
-
/>
|
|
2111
|
-
</div>
|
|
2112
|
-
<div className="flex justify-end">
|
|
2113
|
-
<button
|
|
2114
|
-
className="inline-flex items-center justify-center gap-2 px-5 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-md shadow-indigo-500/10 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed hover:-translate-y-0.5 active:translate-y-0"
|
|
2115
|
-
disabled={!branchName || !description}
|
|
2116
|
-
onClick={() => setActiveStep(1)}
|
|
2117
|
-
>
|
|
2118
|
-
Next Step <ArrowRight size={16} />
|
|
2119
|
-
</button>
|
|
2120
|
-
</div>
|
|
2121
|
-
</div>
|
|
2122
|
-
)}
|
|
2123
|
-
|
|
2124
|
-
{/* Step 1: Repo Selector */}
|
|
2125
|
-
{activeStep === 1 && (
|
|
2126
|
-
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm">
|
|
2127
|
-
<div className="flex justify-between items-center mb-6">
|
|
2128
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider">Select Repositories</label>
|
|
2129
|
-
<div className="relative w-64">
|
|
2130
|
-
<Search size={14} className="absolute left-3 top-3 text-gray-500" />
|
|
2131
|
-
<input
|
|
2132
|
-
type="text"
|
|
2133
|
-
className="w-full bg-[#111827] border border-gray-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 rounded-lg pl-9 pr-4 py-2 text-white placeholder-gray-600 transition-all outline-none text-xs"
|
|
2134
|
-
placeholder="Search repo name..."
|
|
2135
|
-
value={repoSearch}
|
|
2136
|
-
onChange={(e) => setRepoSearch(e.target.value)}
|
|
2137
|
-
/>
|
|
2138
|
-
</div>
|
|
2139
|
-
</div>
|
|
2140
|
-
|
|
2141
|
-
{reposLoading ? (
|
|
2142
|
-
<div className="flex flex-col items-center py-20 gap-3 text-gray-500">
|
|
2143
|
-
<RefreshCw className="animate-spin text-indigo-400" size={24} />
|
|
2144
|
-
<span className="text-xs">Scanning local projects directory...</span>
|
|
2145
|
-
</div>
|
|
2146
|
-
) : (
|
|
2147
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 max-h-[380px] overflow-y-auto p-2 border border-gray-800/60 rounded-lg bg-gray-950/20 mb-8">
|
|
2148
|
-
{filteredRepos.map((repo) => {
|
|
2149
|
-
const isSelected = selectedRepos.some((r) => r.path === repo.path);
|
|
2150
|
-
return (
|
|
2151
|
-
<div
|
|
2152
|
-
key={repo.path}
|
|
2153
|
-
className={`bg-[#111827]/60 border rounded-xl p-4 flex items-start gap-3 cursor-pointer hover:bg-gray-800/20 hover:border-gray-700 transition-all ${
|
|
2154
|
-
isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
|
|
2155
|
-
}`}
|
|
2156
|
-
onClick={() => handleToggleRepo(repo)}
|
|
2157
|
-
>
|
|
2158
|
-
<input
|
|
2159
|
-
type="checkbox"
|
|
2160
|
-
className="mt-1 h-4 w-4 accent-indigo-500 rounded cursor-pointer"
|
|
2161
|
-
checked={isSelected}
|
|
2162
|
-
readOnly
|
|
2163
|
-
/>
|
|
2164
|
-
<div className="flex-1 min-w-0">
|
|
2165
|
-
<div className="text-sm font-semibold text-white truncate">{repo.name}</div>
|
|
2166
|
-
<div className="text-[10px] text-gray-500 truncate mt-1">{repo.path}</div>
|
|
2167
|
-
</div>
|
|
2168
|
-
<span className="text-[9px] px-2 py-0.5 rounded bg-gray-800 text-gray-400 font-semibold uppercase">{repo.defaultBranch}</span>
|
|
2169
|
-
</div>
|
|
2170
|
-
);
|
|
2171
|
-
})}
|
|
2172
|
-
</div>
|
|
2173
|
-
)}
|
|
2174
|
-
|
|
2175
|
-
<div className="flex justify-between">
|
|
2176
|
-
<button
|
|
2177
|
-
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"
|
|
2178
|
-
onClick={() => setActiveStep(0)}
|
|
2179
|
-
>
|
|
2180
|
-
<ArrowLeft size={16} /> Back
|
|
2181
|
-
</button>
|
|
2182
|
-
<button
|
|
2183
|
-
className="inline-flex items-center justify-center gap-2 px-5 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-md shadow-indigo-500/10 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed hover:-translate-y-0.5 active:translate-y-0"
|
|
2184
|
-
disabled={selectedRepos.length === 0}
|
|
2185
|
-
onClick={() => setActiveStep(2)}
|
|
2186
|
-
>
|
|
2187
|
-
Next Step <ArrowRight size={16} />
|
|
2188
|
-
</button>
|
|
2189
|
-
</div>
|
|
2190
|
-
</div>
|
|
2191
|
-
)}
|
|
2192
|
-
|
|
2193
|
-
{/* Step 2: AI & Editor Settings */}
|
|
2194
|
-
{activeStep === 2 && (
|
|
2195
|
-
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm">
|
|
2196
|
-
{creating ? (
|
|
2197
|
-
<div className="flex flex-col items-center py-6">
|
|
2198
|
-
<h3 className="text-lg font-bold text-white mb-2 flex items-center gap-2.5">
|
|
2199
|
-
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
2200
|
-
Building Workspace...
|
|
2201
|
-
</h3>
|
|
2202
|
-
<p className="text-xs text-gray-400 mb-8">
|
|
2203
|
-
Setting up your multi-repo workspace. This will take a moment.
|
|
2204
|
-
</p>
|
|
2205
|
-
|
|
2206
|
-
<div className="w-full max-w-md space-y-4">
|
|
2207
|
-
{creationSteps.map((step) => {
|
|
2208
|
-
const isPending = step.status === 'pending';
|
|
2209
|
-
const isRunning = step.status === 'running';
|
|
2210
|
-
const isCompleted = step.status === 'completed';
|
|
2211
|
-
const isFailed = step.status === 'failed';
|
|
2212
|
-
|
|
2213
|
-
return (
|
|
2214
|
-
<div
|
|
2215
|
-
key={step.id}
|
|
2216
|
-
className={`flex items-start gap-4 p-4 rounded-xl border transition-all duration-300 ${
|
|
2217
|
-
isRunning
|
|
2218
|
-
? 'bg-indigo-500/10 border-indigo-500/50 shadow-md shadow-indigo-500/5'
|
|
2219
|
-
: isCompleted
|
|
2220
|
-
? 'bg-emerald-500/5 border-emerald-500/20 opacity-80'
|
|
2221
|
-
: isFailed
|
|
2222
|
-
? 'bg-rose-500/5 border-rose-500/30'
|
|
2223
|
-
: 'bg-gray-900/20 border-gray-800/40 opacity-40'
|
|
2224
|
-
}`}
|
|
2225
|
-
>
|
|
2226
|
-
<div className="mt-0.5">
|
|
2227
|
-
{isRunning && (
|
|
2228
|
-
<div className="relative flex h-5 w-5 items-center justify-center">
|
|
2229
|
-
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-indigo-400 opacity-75"></span>
|
|
2230
|
-
<RefreshCw className="animate-spin text-indigo-400 relative" size={16} />
|
|
2231
|
-
</div>
|
|
2232
|
-
)}
|
|
2233
|
-
{isCompleted && (
|
|
2234
|
-
<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">
|
|
2235
|
-
<Check size={12} />
|
|
2236
|
-
</div>
|
|
2237
|
-
)}
|
|
2238
|
-
{isFailed && (
|
|
2239
|
-
<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">
|
|
2240
|
-
<AlertTriangle size={12} />
|
|
2241
|
-
</div>
|
|
2242
|
-
)}
|
|
2243
|
-
{isPending && (
|
|
2244
|
-
<div className="h-5 w-5 rounded-full border-2 border-gray-800 flex items-center justify-center text-gray-600">
|
|
2245
|
-
<div className="w-1.5 h-1.5 rounded-full bg-gray-800"></div>
|
|
2246
|
-
</div>
|
|
2247
|
-
)}
|
|
2248
|
-
</div>
|
|
2249
|
-
<div className="flex-1 min-w-0">
|
|
2250
|
-
<h4 className={`text-sm font-bold truncate ${
|
|
2251
|
-
isRunning ? 'text-indigo-400' : isCompleted ? 'text-emerald-400' : isFailed ? 'text-rose-400' : 'text-gray-500'
|
|
2252
|
-
}`}>
|
|
2253
|
-
{step.name}
|
|
2254
|
-
</h4>
|
|
2255
|
-
<p className="text-xs text-gray-400 mt-1 font-mono break-words leading-relaxed">
|
|
2256
|
-
{step.message}
|
|
2257
|
-
</p>
|
|
2258
|
-
</div>
|
|
2259
|
-
</div>
|
|
2260
|
-
);
|
|
2261
|
-
})}
|
|
2262
|
-
</div>
|
|
2263
|
-
|
|
2264
|
-
{creationError && (
|
|
2265
|
-
<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">
|
|
2266
|
-
<span className="font-bold flex items-center gap-1.5">
|
|
2267
|
-
<AlertCircle size={14} className="text-rose-450" /> Build Failed
|
|
2268
|
-
</span>
|
|
2269
|
-
<span className="font-mono">{creationError}</span>
|
|
2270
|
-
<button
|
|
2271
|
-
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"
|
|
2272
|
-
onClick={() => {
|
|
2273
|
-
setCreating(false);
|
|
2274
|
-
setCreationError(null);
|
|
2275
|
-
}}
|
|
2276
|
-
>
|
|
2277
|
-
Dismiss
|
|
2278
|
-
</button>
|
|
2279
|
-
</div>
|
|
2280
|
-
)}
|
|
2281
|
-
</div>
|
|
2282
|
-
) : (
|
|
2283
|
-
<>
|
|
2284
|
-
<div className="mb-8">
|
|
2285
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Target AI Assistant(s)</label>
|
|
2286
|
-
<p className="text-xs text-gray-500 mb-4">
|
|
2287
|
-
We generate context configurations matching the specifications of each checked assistant.
|
|
2288
|
-
</p>
|
|
2289
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
|
2290
|
-
{aiAssistants.map((ai) => {
|
|
2291
|
-
const isSelected = selectedAI.includes(ai.name);
|
|
2292
|
-
return (
|
|
2293
|
-
<div
|
|
2294
|
-
key={ai.name}
|
|
2295
|
-
className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col gap-3 cursor-pointer hover:border-gray-700 transition-all ${
|
|
2296
|
-
isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
|
|
2297
|
-
}`}
|
|
2298
|
-
onClick={() => handleToggleAI(ai.name)}
|
|
2299
|
-
>
|
|
2300
|
-
<div className="flex justify-between items-center">
|
|
2301
|
-
<span className="text-sm font-bold text-white">{ai.displayName}</span>
|
|
2302
|
-
<span className={`text-[9px] px-2 py-0.5 rounded font-semibold uppercase ${
|
|
2303
|
-
ai.detected ? 'bg-emerald-500/10 text-emerald-400' : 'bg-gray-800 text-gray-500'
|
|
2304
|
-
}`}>
|
|
2305
|
-
{ai.detected ? 'Installed' : 'Missing'}
|
|
2306
|
-
</span>
|
|
2307
|
-
</div>
|
|
2308
|
-
<p className="text-[11px] text-gray-500">
|
|
2309
|
-
{ai.name === 'claude' && 'CLAUDE.md guidelines'}
|
|
2310
|
-
{ai.name === 'antigravity' && 'CLAUDE.md guidelines (harness)'}
|
|
2311
|
-
{ai.name === 'codex' && 'AGENTS.md config structure'}
|
|
2312
|
-
{ai.name === 'copilot' && 'GitHub Copilot Workspace configs'}
|
|
2313
|
-
{ai.name === 'cursor' && 'Cursor MDC rules and instructions'}
|
|
2314
|
-
</p>
|
|
2315
|
-
</div>
|
|
2316
|
-
);
|
|
2317
|
-
})}
|
|
2318
|
-
</div>
|
|
2319
|
-
</div>
|
|
2320
|
-
|
|
2321
|
-
<div className="mb-8">
|
|
2322
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Open Workspace In</label>
|
|
2323
|
-
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
|
2324
|
-
{editors.map((ed) => {
|
|
2325
|
-
const isSelected = selectedEditor?.command === ed.command;
|
|
2326
|
-
return (
|
|
2327
|
-
<div
|
|
2328
|
-
key={ed.command}
|
|
2329
|
-
className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col cursor-pointer hover:border-gray-700 transition-all ${
|
|
2330
|
-
isSelected ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
|
|
2331
|
-
}`}
|
|
2332
|
-
onClick={() => {
|
|
2333
|
-
setSelectedEditor(ed);
|
|
2334
|
-
if (config) {
|
|
2335
|
-
const updatedConfig = { ...config, defaultEditor: ed.command };
|
|
2336
|
-
setConfig(updatedConfig);
|
|
2337
|
-
saveAppConfig(updatedConfig);
|
|
2338
|
-
}
|
|
2339
|
-
}}
|
|
2340
|
-
>
|
|
2341
|
-
<span className="text-sm font-bold text-white">{ed.name}</span>
|
|
2342
|
-
<span className={`text-[9px] mt-2 w-max px-2 py-0.5 rounded font-semibold uppercase ${
|
|
2343
|
-
ed.detected ? 'bg-emerald-500/10 text-emerald-400' : 'bg-gray-800 text-gray-500'
|
|
2344
|
-
}`}>
|
|
2345
|
-
{ed.detected ? 'Detected' : 'Missing'}
|
|
2346
|
-
</span>
|
|
2347
|
-
</div>
|
|
2348
|
-
);
|
|
2349
|
-
})}
|
|
2350
|
-
<div
|
|
2351
|
-
className={`bg-[#111827]/60 border rounded-xl p-4 flex flex-col cursor-pointer hover:border-gray-700 transition-all ${
|
|
2352
|
-
selectedEditor === null ? 'border-indigo-500 bg-indigo-500/5' : 'border-gray-800/80'
|
|
2353
|
-
}`}
|
|
2354
|
-
onClick={() => {
|
|
2355
|
-
setSelectedEditor(null);
|
|
2356
|
-
if (config) {
|
|
2357
|
-
const updatedConfig = { ...config, defaultEditor: 'none' };
|
|
2358
|
-
setConfig(updatedConfig);
|
|
2359
|
-
saveAppConfig(updatedConfig);
|
|
2360
|
-
}
|
|
2361
|
-
}}
|
|
2362
|
-
>
|
|
2363
|
-
<span className="text-sm font-bold text-white">None</span>
|
|
2364
|
-
<span className="text-[9px] mt-2 w-max px-2 py-0.5 rounded font-semibold uppercase bg-gray-800 text-gray-500">
|
|
2365
|
-
Skip opening
|
|
2366
|
-
</span>
|
|
2367
|
-
</div>
|
|
2368
|
-
</div>
|
|
2369
|
-
</div>
|
|
2370
|
-
|
|
2371
|
-
<div className={`mb-8 border rounded-xl p-5 ${config?.localLlm?.enabled ? 'border-gray-800/80 bg-gray-950/20' : 'border-gray-800/40 bg-gray-950/10 opacity-60'}`}>
|
|
2372
|
-
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4 flex items-center gap-2">
|
|
2373
|
-
<Cpu size={14} className="text-indigo-400" /> Local AI Co-processor
|
|
2374
|
-
</h4>
|
|
2375
|
-
<p className="text-[11px] text-gray-500 mb-4">
|
|
2376
|
-
Enabling the Local AI Co-processor inserts guidelines in the workspace context files, instructing remote agents to delegate heavy tasks (such as searching, log analysis, and boilerplate generation) to your local Ollama/LM Studio model.
|
|
2377
|
-
</p>
|
|
2378
|
-
{!config?.localLlm?.enabled && (
|
|
2379
|
-
<p className="text-[11px] text-amber-400/80 mb-3 flex items-center gap-1.5">
|
|
2380
|
-
<AlertTriangle size={12} /> Enable a local LLM provider in Settings first.
|
|
2381
|
-
</p>
|
|
2382
|
-
)}
|
|
2383
|
-
<div className="flex items-center gap-3">
|
|
2384
|
-
<input
|
|
2385
|
-
type="checkbox"
|
|
2386
|
-
id="wizardLocalLlmEnabled"
|
|
2387
|
-
className="w-4 h-4 rounded border-gray-800 text-indigo-600 bg-gray-950/20 focus:ring-indigo-500 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
2388
|
-
checked={localLlmEnabled}
|
|
2389
|
-
onChange={(e) => setLocalLlmEnabled(e.target.checked)}
|
|
2390
|
-
disabled={!config?.localLlm?.enabled}
|
|
2391
|
-
/>
|
|
2392
|
-
<label htmlFor="wizardLocalLlmEnabled" className={`text-xs font-semibold cursor-pointer select-none ${config?.localLlm?.enabled ? 'text-white' : 'text-gray-500 cursor-not-allowed'}`}>
|
|
2393
|
-
Include Local AI Co-processor in this workspace context
|
|
2394
|
-
</label>
|
|
2395
|
-
</div>
|
|
2396
|
-
</div>
|
|
2397
|
-
|
|
2398
|
-
<div className="mb-8 border border-gray-800/80 rounded-xl p-5 bg-gray-950/20">
|
|
2399
|
-
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4 flex items-center gap-2">
|
|
2400
|
-
<Terminal size={14} className="text-indigo-400" /> Advanced Session Resumption Settings
|
|
2401
|
-
</h4>
|
|
2402
|
-
<p className="text-[11px] text-gray-500 mb-4">
|
|
2403
|
-
Configure setup and verification test commands. AI assistants and the dashboard use these to resume your sessions green.
|
|
2404
|
-
</p>
|
|
2405
|
-
<div className="space-y-4">
|
|
2406
|
-
<div>
|
|
2407
|
-
<label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Verification / Test Command</label>
|
|
2408
|
-
<input
|
|
2409
|
-
type="text"
|
|
2410
|
-
className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
|
|
2411
|
-
placeholder="e.g., npm run test or vitest run"
|
|
2412
|
-
value={testCommand}
|
|
2413
|
-
onChange={(e) => setTestCommand(e.target.value)}
|
|
2414
|
-
/>
|
|
2415
|
-
</div>
|
|
2416
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
2417
|
-
<div>
|
|
2418
|
-
<label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Mock / Database Command (Optional)</label>
|
|
2419
|
-
<input
|
|
2420
|
-
type="text"
|
|
2421
|
-
className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
|
|
2422
|
-
placeholder="e.g., docker compose up -d redis"
|
|
2423
|
-
value={mockCommand}
|
|
2424
|
-
onChange={(e) => setMockCommand(e.target.value)}
|
|
2425
|
-
/>
|
|
2426
|
-
</div>
|
|
2427
|
-
<div>
|
|
2428
|
-
<label className="block text-[11px] font-semibold text-gray-400 mb-1.5">Start / Run Command (Optional)</label>
|
|
2429
|
-
<input
|
|
2430
|
-
type="text"
|
|
2431
|
-
className="w-full bg-[#030408] border border-gray-800 focus:border-indigo-500 rounded-lg px-3 py-2 text-xs font-mono text-white outline-none transition-all"
|
|
2432
|
-
placeholder="e.g., npm run start:dev"
|
|
2433
|
-
value={startCommand}
|
|
2434
|
-
onChange={(e) => setStartCommand(e.target.value)}
|
|
2435
|
-
/>
|
|
2436
|
-
</div>
|
|
2437
|
-
</div>
|
|
2438
|
-
</div>
|
|
2439
|
-
</div>
|
|
2440
|
-
|
|
2441
|
-
<div className="flex justify-between">
|
|
2442
|
-
<button
|
|
2443
|
-
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"
|
|
2444
|
-
onClick={() => setActiveStep(1)}
|
|
2445
|
-
>
|
|
2446
|
-
<ArrowLeft size={16} /> Back
|
|
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>
|
|
2631
|
-
<button
|
|
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"
|
|
2633
|
-
disabled={creating}
|
|
2634
|
-
onClick={handleCreateWorkspace}
|
|
2635
|
-
>
|
|
2636
|
-
{creating ? (
|
|
2637
|
-
<>
|
|
2638
|
-
<RefreshCw className="animate-spin" size={14} /> Building...
|
|
2639
|
-
</>
|
|
2640
|
-
) : (
|
|
2641
|
-
<>
|
|
2642
|
-
<Sparkles size={14} /> Build Workspace
|
|
2643
|
-
</>
|
|
2644
|
-
)}
|
|
2645
|
-
</button>
|
|
2646
|
-
</div>
|
|
2647
|
-
</>
|
|
2648
|
-
)}
|
|
2649
|
-
</div>
|
|
2650
|
-
)}
|
|
2651
|
-
|
|
2652
|
-
{/* Step 4: Success Screen */}
|
|
2653
|
-
{activeStep === 4 && createdWorkspace && (
|
|
2654
|
-
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-10 text-center shadow-xl backdrop-blur-sm">
|
|
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">
|
|
2656
|
-
<Check size={36} />
|
|
2657
|
-
</div>
|
|
2658
|
-
<h2 className="text-2xl font-bold text-white mb-2">Workspace Generated!</h2>
|
|
2659
|
-
<p className="text-gray-400 text-sm max-w-lg mx-auto mb-8">
|
|
2660
|
-
We spun up git worktrees for your branch <code>{branchName}</code>, analyzed the code architectures, and generated your AI assistant instructions.
|
|
2661
|
-
</p>
|
|
2662
|
-
|
|
2663
|
-
<div className="flex flex-col gap-3 bg-gray-950/40 border border-gray-800/60 rounded-xl p-5 w-fit min-w-[360px] mx-auto text-left text-xs mb-8">
|
|
2664
|
-
<div className="flex justify-between items-center">
|
|
2665
|
-
<span className="text-gray-500 font-medium">Branch name:</span>
|
|
2666
|
-
<code className="text-indigo-400 font-bold">{branchName}</code>
|
|
2667
|
-
</div>
|
|
2668
|
-
<div className="flex justify-between items-center gap-4">
|
|
2669
|
-
<span className="text-gray-500 font-medium">Location:</span>
|
|
2670
|
-
<code className="text-gray-400 font-mono text-[10px] break-all">{createdWorkspace.path}</code>
|
|
2671
|
-
</div>
|
|
2672
|
-
</div>
|
|
2673
|
-
|
|
2674
|
-
<div className="flex justify-center gap-4">
|
|
2675
|
-
<button
|
|
2676
|
-
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"
|
|
2677
|
-
onClick={() => {
|
|
2678
|
-
setView('workspaces');
|
|
2679
|
-
fetchWorkspaces();
|
|
2680
|
-
setActiveWsId(branchName);
|
|
2681
|
-
}}
|
|
2682
|
-
>
|
|
2683
|
-
<Terminal size={14} className="text-cyan-400" /> Manage Services
|
|
2684
|
-
</button>
|
|
2685
|
-
<button
|
|
2686
|
-
className="inline-flex items-center justify-center gap-2 px-5 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-md shadow-indigo-500/10 transition-all cursor-pointer hover:-translate-y-0.5 active:translate-y-0"
|
|
2687
|
-
onClick={() => handleOpenInEditor(createdWorkspace.path)}
|
|
2688
|
-
>
|
|
2689
|
-
<FolderOpen size={14} /> Open Editor
|
|
2690
|
-
</button>
|
|
2691
|
-
</div>
|
|
2692
|
-
</div>
|
|
2693
|
-
)}
|
|
2694
|
-
</div>
|
|
2695
|
-
)}
|
|
2696
|
-
|
|
2697
|
-
{/* View 2: Active Workspaces Panel */}
|
|
2698
|
-
{view === 'workspaces' && (
|
|
2699
|
-
<WorkspaceList
|
|
2700
|
-
workspacesLoading={workspacesLoading}
|
|
2701
|
-
workspaces={workspaces}
|
|
2702
|
-
activeWsId={activeWsId}
|
|
2703
|
-
setActiveWsId={setActiveWsId}
|
|
2704
|
-
resumingWs={resumingWs}
|
|
2705
|
-
handleResumeSession={handleResumeSession}
|
|
2706
|
-
handleCopyPrompt={handleCopyPrompt}
|
|
2707
|
-
handleOpenInEditor={handleOpenInEditor}
|
|
2708
|
-
fetchWorkspaces={fetchWorkspaces}
|
|
2709
|
-
aiAssistants={aiAssistants}
|
|
2710
|
-
repos={repos}
|
|
2711
|
-
deleteWsLoading={deleteWsLoading}
|
|
2712
|
-
addRepoLoading={addRepoLoading}
|
|
2713
|
-
handleDeleteWorkspace={handleDeleteWorkspace}
|
|
2714
|
-
handleAddRepo={handleAddRepo}
|
|
2715
|
-
services={services}
|
|
2716
|
-
runningServices={runningServices}
|
|
2717
|
-
selectedLogService={selectedLogService}
|
|
2718
|
-
serviceLogs={serviceLogs}
|
|
2719
|
-
logsEndRef={logsEndRef}
|
|
2720
|
-
setSelectedLogService={setSelectedLogService}
|
|
2721
|
-
handleStartServices={handleStartServices}
|
|
2722
|
-
handleStopServices={handleStopServices}
|
|
2723
|
-
orchTools={orchTools}
|
|
2724
|
-
servicesLoading={servicesLoading}
|
|
2725
|
-
gitChanges={gitChanges}
|
|
2726
|
-
gitChangesLoading={gitChangesLoading}
|
|
2727
|
-
syncLoading={syncLoading}
|
|
2728
|
-
syncResults={syncResults}
|
|
2729
|
-
commitMessage={commitMessage}
|
|
2730
|
-
showCommitModal={showCommitModal}
|
|
2731
|
-
commitLoading={commitLoading}
|
|
2732
|
-
commitResults={commitResults}
|
|
2733
|
-
setSyncResults={setSyncResults}
|
|
2734
|
-
setCommitResults={setCommitResults}
|
|
2735
|
-
setCommitMessage={setCommitMessage}
|
|
2736
|
-
setShowCommitModal={setShowCommitModal}
|
|
2737
|
-
fetchGitChanges={fetchGitChanges}
|
|
2738
|
-
handleSyncAll={handleSyncAll}
|
|
2739
|
-
handleCommitAll={handleCommitAll}
|
|
2740
|
-
knowledgeContent={knowledgeContent}
|
|
2741
|
-
knowledgeLoading={knowledgeLoading}
|
|
2742
|
-
isEditingKnowledge={isEditingKnowledge}
|
|
2743
|
-
editedKnowledge={editedKnowledge}
|
|
2744
|
-
saveKnowledgeLoading={saveKnowledgeLoading}
|
|
2745
|
-
setEditedKnowledge={setEditedKnowledge}
|
|
2746
|
-
setIsEditingKnowledge={setIsEditingKnowledge}
|
|
2747
|
-
handleSaveKnowledge={handleSaveKnowledge}
|
|
2748
|
-
planContent={planContent}
|
|
2749
|
-
planLoading={planLoading}
|
|
2750
|
-
sessions={sessions}
|
|
2751
|
-
sessionsLoading={sessionsLoading}
|
|
2752
|
-
activeSession={activeSession}
|
|
2753
|
-
transcript={transcript}
|
|
2754
|
-
transcriptLoading={transcriptLoading}
|
|
2755
|
-
setActiveSession={setActiveSession}
|
|
2756
|
-
setTranscript={setTranscript}
|
|
2757
|
-
fetchSessionTranscript={fetchSessionTranscript}
|
|
2758
|
-
subTab={subTab}
|
|
2759
|
-
setSubTab={setSubTab}
|
|
2760
|
-
/>
|
|
2761
|
-
)}
|
|
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
|
-
|
|
3055
|
-
{/* View 3: Settings View */}
|
|
3056
|
-
{view === 'settings' && config && (
|
|
3057
|
-
<div className="max-w-4xl mx-auto">
|
|
3058
|
-
<header className="mb-8">
|
|
3059
|
-
<h1 className="text-3xl font-extrabold tracking-tight text-white mb-2">Global Settings</h1>
|
|
3060
|
-
<p className="text-sm text-gray-400">Configure your local directories, search parameters, and editor defaults.</p>
|
|
3061
|
-
</header>
|
|
3062
|
-
|
|
3063
|
-
{saveStatus === 'success' && (
|
|
3064
|
-
<div className="flex items-center gap-3 bg-emerald-500/10 border border-emerald-500/20 text-emerald-400 rounded-xl p-4 mb-6 text-sm">
|
|
3065
|
-
<Check size={18} /> Settings successfully saved and updated!
|
|
3066
|
-
</div>
|
|
3067
|
-
)}
|
|
3068
|
-
{saveStatus === 'error' && (
|
|
3069
|
-
<div className="flex items-center gap-3 bg-rose-500/10 border border-rose-500/20 text-rose-400 rounded-xl p-4 mb-6 text-sm">
|
|
3070
|
-
<AlertTriangle size={18} /> Error: Could not save configuration details to disk.
|
|
3071
|
-
</div>
|
|
3072
|
-
)}
|
|
3073
|
-
|
|
3074
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm mb-6">
|
|
3075
|
-
<div className="flex flex-col">
|
|
3076
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Development Directory</label>
|
|
3077
|
-
<input
|
|
3078
|
-
type="text"
|
|
3079
|
-
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-600 transition-all outline-none text-sm shadow-inner"
|
|
3080
|
-
value={config.devDir}
|
|
3081
|
-
onChange={(e) => setConfig({ ...config, devDir: e.target.value })}
|
|
3082
|
-
/>
|
|
3083
|
-
<span className="text-[10px] text-gray-500 mt-1">Directory where your git projects are scanned.</span>
|
|
3084
|
-
</div>
|
|
3085
|
-
|
|
3086
|
-
<div className="flex flex-col">
|
|
3087
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Workspaces Directory</label>
|
|
3088
|
-
<input
|
|
3089
|
-
type="text"
|
|
3090
|
-
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-600 transition-all outline-none text-sm shadow-inner"
|
|
3091
|
-
value={config.workspacesDir}
|
|
3092
|
-
onChange={(e) => setConfig({ ...config, workspacesDir: e.target.value })}
|
|
3093
|
-
/>
|
|
3094
|
-
<span className="text-[10px] text-gray-500 mt-1">Directory where unified worktree environments are spun up.</span>
|
|
3095
|
-
</div>
|
|
3096
|
-
|
|
3097
|
-
<div className="flex flex-col">
|
|
3098
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Repo Search Depth</label>
|
|
3099
|
-
<input
|
|
3100
|
-
type="number"
|
|
3101
|
-
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-600 transition-all outline-none text-sm shadow-inner"
|
|
3102
|
-
min={1}
|
|
3103
|
-
max={5}
|
|
3104
|
-
value={config.scanDepth}
|
|
3105
|
-
onChange={(e) => setConfig({ ...config, scanDepth: parseInt(e.target.value, 10) })}
|
|
3106
|
-
/>
|
|
3107
|
-
<span className="text-[10px] text-gray-500 mt-1">Max directory levels deep the system will traverse for git repos.</span>
|
|
3108
|
-
</div>
|
|
3109
|
-
|
|
3110
|
-
<div className="flex flex-col">
|
|
3111
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Default Assistant</label>
|
|
3112
|
-
<select
|
|
3113
|
-
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 transition-all outline-none text-sm shadow-inner cursor-pointer"
|
|
3114
|
-
value={config.defaultAssistant || ''}
|
|
3115
|
-
onChange={(e) => setConfig({ ...config, defaultAssistant: e.target.value || null })}
|
|
3116
|
-
>
|
|
3117
|
-
<option value="">None (Prompt me)</option>
|
|
3118
|
-
<option value="claude">Claude Code</option>
|
|
3119
|
-
<option value="antigravity">Antigravity</option>
|
|
3120
|
-
<option value="codex">Codex</option>
|
|
3121
|
-
<option value="copilot">GitHub Copilot</option>
|
|
3122
|
-
<option value="cursor">Cursor</option>
|
|
3123
|
-
</select>
|
|
3124
|
-
<span className="text-[10px] text-gray-500 mt-1">Your preferred workspace context manager.</span>
|
|
3125
|
-
</div>
|
|
3126
|
-
|
|
3127
|
-
<div className="flex flex-col">
|
|
3128
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Default Editor</label>
|
|
3129
|
-
<select
|
|
3130
|
-
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 transition-all outline-none text-sm shadow-inner cursor-pointer"
|
|
3131
|
-
value={config.defaultEditor || ''}
|
|
3132
|
-
onChange={(e) => setConfig({ ...config, defaultEditor: e.target.value || null })}
|
|
3133
|
-
>
|
|
3134
|
-
<option value="">None (Skip opening)</option>
|
|
3135
|
-
{editors.map((ed) => (
|
|
3136
|
-
<option key={ed.command} value={ed.command}>
|
|
3137
|
-
{ed.name} {ed.detected ? '(Detected)' : '(Not found)'}
|
|
3138
|
-
</option>
|
|
3139
|
-
))}
|
|
3140
|
-
</select>
|
|
3141
|
-
<span className="text-[10px] text-gray-500 mt-1">Your preferred code editor for opening workspaces.</span>
|
|
3142
|
-
</div>
|
|
3143
|
-
|
|
3144
|
-
<div className="flex flex-col md:col-span-2 border-t border-gray-800/60 pt-6 mt-2">
|
|
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
|
|
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>
|
|
3183
|
-
</div>
|
|
3184
|
-
<span className="text-[10px] text-gray-500 mt-1">
|
|
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.'}
|
|
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
|
-
})()}
|
|
3254
|
-
</div>
|
|
3255
|
-
</div>
|
|
3256
|
-
|
|
3257
|
-
{/* Local AI Co-Processor Settings */}
|
|
3258
|
-
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm mb-6 mt-6">
|
|
3259
|
-
<h3 className="text-lg font-bold text-white mb-2">Local AI Co-Processor Settings</h3>
|
|
3260
|
-
<p className="text-xs text-gray-450 mb-6 font-semibold">Enable a local LLM to handle simple tasks like log analysis, git diff summaries, and boilerplate code generation without using remote tokens.</p>
|
|
3261
|
-
|
|
3262
|
-
{recommendation && (
|
|
3263
|
-
<div className="bg-[#0c1020]/80 border border-indigo-500/20 text-xs text-gray-300 rounded-xl p-4 mb-6 flex flex-col gap-1.5">
|
|
3264
|
-
<div className="flex items-center gap-2 text-indigo-400 font-bold">
|
|
3265
|
-
<Cpu size={14} /> Local System Scan Results
|
|
3266
|
-
</div>
|
|
3267
|
-
<div>• Detected Memory: <strong>{recommendation.totalRamGb} GB RAM</strong></div>
|
|
3268
|
-
<div>• Detected GPU: <strong>{recommendation.gpuName}</strong></div>
|
|
3269
|
-
<div>• Recommended Model: <span className="bg-indigo-500/10 text-indigo-300 px-2 py-0.5 rounded font-mono text-[11px] border border-indigo-500/20">{recommendation.recommendedModel}</span></div>
|
|
3270
|
-
</div>
|
|
3271
|
-
)}
|
|
3272
|
-
|
|
3273
|
-
<div className="flex items-center gap-3 mb-6">
|
|
3274
|
-
<input
|
|
3275
|
-
type="checkbox"
|
|
3276
|
-
id="localLlmEnabled"
|
|
3277
|
-
className="w-4 h-4 rounded border-gray-800 bg-[#111827] text-indigo-600 focus:ring-indigo-500 cursor-pointer"
|
|
3278
|
-
checked={config.localLlm?.enabled || false}
|
|
3279
|
-
onChange={(e) => {
|
|
3280
|
-
const defaultLlm = { enabled: e.target.checked, provider: 'ollama' as const, endpoint: 'http://localhost:11434', model: recommendation?.recommendedModel || 'qwen2.5-coder:1.5b' };
|
|
3281
|
-
setConfig({
|
|
3282
|
-
...config,
|
|
3283
|
-
localLlm: config.localLlm ? { ...config.localLlm, enabled: e.target.checked } : defaultLlm
|
|
3284
|
-
});
|
|
3285
|
-
}}
|
|
3286
|
-
/>
|
|
3287
|
-
<label htmlFor="localLlmEnabled" className="text-sm font-semibold text-white cursor-pointer select-none">
|
|
3288
|
-
Enable Local AI Delegation Tool
|
|
3289
|
-
</label>
|
|
3290
|
-
</div>
|
|
3291
|
-
|
|
3292
|
-
{config.localLlm?.enabled && (
|
|
3293
|
-
<div className="space-y-6 border-t border-gray-800/60 pt-6">
|
|
3294
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
3295
|
-
<div className="flex flex-col">
|
|
3296
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Local Provider</label>
|
|
3297
|
-
<select
|
|
3298
|
-
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 transition-all outline-none text-sm shadow-inner cursor-pointer"
|
|
3299
|
-
value={config.localLlm.provider}
|
|
3300
|
-
onChange={(e) => setConfig({
|
|
3301
|
-
...config,
|
|
3302
|
-
localLlm: { ...config.localLlm!, provider: e.target.value as any }
|
|
3303
|
-
})}
|
|
3304
|
-
>
|
|
3305
|
-
<option value="ollama">Ollama</option>
|
|
3306
|
-
<option value="openai-compatible">OpenAI-Compatible (e.g. LM Studio)</option>
|
|
3307
|
-
</select>
|
|
3308
|
-
<span className="text-[10px] text-gray-500 mt-1">Provider protocol to connect to.</span>
|
|
3309
|
-
</div>
|
|
3310
|
-
|
|
3311
|
-
<div className="flex flex-col">
|
|
3312
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Endpoint URL</label>
|
|
3313
|
-
<input
|
|
3314
|
-
type="text"
|
|
3315
|
-
className={`w-full bg-[#111827] border focus:ring-1 rounded-lg px-4 py-3 text-white placeholder-gray-600 transition-all outline-none text-sm shadow-inner ${
|
|
3316
|
-
config.localLlm.endpoint && !config.localLlm.endpoint.trim().startsWith('http://') && !config.localLlm.endpoint.trim().startsWith('https://')
|
|
3317
|
-
? 'border-rose-500/60 focus:border-rose-500 focus:ring-rose-500'
|
|
3318
|
-
: 'border-gray-800 focus:border-indigo-500 focus:ring-indigo-500'
|
|
3319
|
-
}`}
|
|
3320
|
-
value={config.localLlm.endpoint}
|
|
3321
|
-
onChange={(e) => setConfig({
|
|
3322
|
-
...config,
|
|
3323
|
-
localLlm: { ...config.localLlm!, endpoint: e.target.value }
|
|
3324
|
-
})}
|
|
3325
|
-
/>
|
|
3326
|
-
{config.localLlm.endpoint && !config.localLlm.endpoint.trim().startsWith('http://') && !config.localLlm.endpoint.trim().startsWith('https://') && (
|
|
3327
|
-
<span className="text-[10px] text-rose-400 mt-1">Endpoint must start with http:// or https://</span>
|
|
3328
|
-
)}
|
|
3329
|
-
<span className="text-[10px] text-gray-500 mt-1">API base address of the local runner.</span>
|
|
3330
|
-
</div>
|
|
3331
|
-
|
|
3332
|
-
<div className="flex flex-col md:col-span-2">
|
|
3333
|
-
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Model Name</label>
|
|
3334
|
-
<input
|
|
3335
|
-
type="text"
|
|
3336
|
-
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-600 transition-all outline-none text-sm shadow-inner"
|
|
3337
|
-
value={config.localLlm.model}
|
|
3338
|
-
onChange={(e) => setConfig({
|
|
3339
|
-
...config,
|
|
3340
|
-
localLlm: { ...config.localLlm!, model: e.target.value }
|
|
3341
|
-
})}
|
|
3342
|
-
/>
|
|
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>
|
|
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
|
-
)}
|
|
3362
|
-
</div>
|
|
3363
|
-
|
|
3364
|
-
<div className="flex items-center justify-between pt-4 border-t border-gray-800/40">
|
|
3365
|
-
<button
|
|
3366
|
-
type="button"
|
|
3367
|
-
disabled={testingLlm}
|
|
3368
|
-
className="inline-flex items-center justify-center gap-1.5 px-4 py-2 border border-indigo-500/20 rounded-lg text-xs font-semibold bg-indigo-500/5 hover:bg-indigo-500/10 text-indigo-400 transition-all cursor-pointer disabled:opacity-40"
|
|
3369
|
-
onClick={testLlmConnection}
|
|
3370
|
-
>
|
|
3371
|
-
{testingLlm ? 'Testing...' : 'Test Connection'}
|
|
3372
|
-
</button>
|
|
3373
|
-
|
|
3374
|
-
{testStatus && (
|
|
3375
|
-
<div className={`text-xs px-3 py-1.5 rounded-lg border ${
|
|
3376
|
-
testStatus.success
|
|
3377
|
-
? 'bg-emerald-500/10 border-emerald-500/20 text-emerald-400'
|
|
3378
|
-
: 'bg-rose-500/10 border-rose-500/20 text-rose-400'
|
|
3379
|
-
}`}>
|
|
3380
|
-
{testStatus.message}
|
|
3381
|
-
</div>
|
|
3382
|
-
)}
|
|
3383
|
-
</div>
|
|
3384
|
-
</div>
|
|
3385
|
-
)}
|
|
3386
|
-
</div>
|
|
3387
|
-
|
|
3388
|
-
<div className="flex justify-end">
|
|
3389
|
-
<button
|
|
3390
|
-
className="inline-flex items-center justify-center gap-2 px-5 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 disabled:hover:translate-y-0"
|
|
3391
|
-
disabled={!isSettingsFormValid}
|
|
3392
|
-
onClick={() => saveAppConfig(config)}
|
|
3393
|
-
>
|
|
3394
|
-
Save Configuration
|
|
3395
|
-
</button>
|
|
3396
|
-
</div>
|
|
3397
|
-
|
|
3398
|
-
{/* Toolchain Updates Section */}
|
|
3399
|
-
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm mt-6">
|
|
3400
|
-
<div className="flex justify-between items-center mb-6">
|
|
3401
|
-
<div>
|
|
3402
|
-
<h3 className="text-lg font-bold text-white mb-1">AI Toolchain Updates</h3>
|
|
3403
|
-
<p className="text-xs text-gray-555">Monitor and update CLI packages and assistants in your workflow.</p>
|
|
3404
|
-
</div>
|
|
3405
|
-
<button
|
|
3406
|
-
onClick={() => fetchToolsStatus(true)}
|
|
3407
|
-
disabled={toolsLoading}
|
|
3408
|
-
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-gray-800 hover:border-gray-700 bg-gray-950/20 text-xs font-semibold text-white transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
3409
|
-
>
|
|
3410
|
-
<RefreshCw size={13} className={toolsLoading ? 'animate-spin' : ''} />
|
|
3411
|
-
{toolsLoading ? 'Checking...' : 'Check Now'}
|
|
3412
|
-
</button>
|
|
3413
|
-
</div>
|
|
3414
|
-
|
|
3415
|
-
{toolsLoading && toolsStatus.length === 0 ? (
|
|
3416
|
-
<div className="flex flex-col items-center justify-center py-8 gap-2 text-gray-400">
|
|
3417
|
-
<RefreshCw className="animate-spin text-indigo-400" size={24} />
|
|
3418
|
-
<span className="text-xs">Fetching registry version details...</span>
|
|
3419
|
-
</div>
|
|
3420
|
-
) : (
|
|
3421
|
-
<div className="space-y-4">
|
|
3422
|
-
{toolsStatus.map((tool) => (
|
|
3423
|
-
<div key={tool.id} className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 p-4 border border-gray-800/60 rounded-xl bg-gray-950/10">
|
|
3424
|
-
<div className="min-w-0">
|
|
3425
|
-
<h4 className="text-sm font-bold text-white flex items-center gap-2">
|
|
3426
|
-
{tool.name}
|
|
3427
|
-
<span className={`text-[9px] px-2 py-0.5 rounded font-semibold uppercase ${
|
|
3428
|
-
tool.installed ? 'bg-emerald-500/10 text-emerald-400' : 'bg-gray-800 text-gray-500'
|
|
3429
|
-
}`}>
|
|
3430
|
-
{tool.installed ? 'Installed' : 'Not Installed'}
|
|
3431
|
-
</span>
|
|
3432
|
-
</h4>
|
|
3433
|
-
<div className="flex items-center gap-4 mt-1.5 text-xs text-gray-500">
|
|
3434
|
-
<span>Installed version: <code className="text-gray-300 font-mono text-[10px]">{tool.currentVersion}</code></span>
|
|
3435
|
-
{tool.installed && (
|
|
3436
|
-
<span>Latest: <code className="text-gray-300 font-mono text-[10px]">{tool.latestVersion}</code></span>
|
|
3437
|
-
)}
|
|
3438
|
-
</div>
|
|
3439
|
-
<p className="text-[10px] text-gray-600 mt-1 font-mono">{tool.updateCmd}</p>
|
|
3440
|
-
</div>
|
|
3441
|
-
|
|
3442
|
-
<div className="shrink-0 flex items-center gap-3">
|
|
3443
|
-
{tool.updateAvailable ? (
|
|
3444
|
-
<button
|
|
3445
|
-
onClick={() => handleUpdateTool(tool.id)}
|
|
3446
|
-
disabled={updatingToolId !== null}
|
|
3447
|
-
className="px-3.5 py-2 bg-gradient-to-r from-amber-500 to-amber-600 hover:from-amber-600 hover:to-amber-700 text-[#060813] font-bold text-xs rounded-lg transition-all shadow-md shadow-amber-500/10 cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-1.5"
|
|
3448
|
-
>
|
|
3449
|
-
{updatingToolId === tool.id ? (
|
|
3450
|
-
<>
|
|
3451
|
-
<RefreshCw className="animate-spin" size={12} /> Updating...
|
|
3452
|
-
</>
|
|
3453
|
-
) : (
|
|
3454
|
-
<>
|
|
3455
|
-
<Sparkles size={12} /> Update Tool
|
|
3456
|
-
</>
|
|
3457
|
-
)}
|
|
3458
|
-
</button>
|
|
3459
|
-
) : tool.installed ? (
|
|
3460
|
-
<div className="flex items-center gap-2">
|
|
3461
|
-
<span className="text-xs font-semibold text-emerald-400 flex items-center gap-1.5 bg-emerald-500/5 px-2.5 py-1.5 rounded-lg border border-emerald-500/10">
|
|
3462
|
-
<Check size={12} /> Up to Date
|
|
3463
|
-
</span>
|
|
3464
|
-
<button
|
|
3465
|
-
onClick={() => handleUpdateTool(tool.id)}
|
|
3466
|
-
disabled={updatingToolId !== null}
|
|
3467
|
-
className="px-3.5 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-750 text-gray-300 font-bold text-xs rounded-lg transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed flex items-center gap-1.5"
|
|
3468
|
-
>
|
|
3469
|
-
{updatingToolId === tool.id ? (
|
|
3470
|
-
<>
|
|
3471
|
-
<RefreshCw className="animate-spin" size={12} /> Reinstalling...
|
|
3472
|
-
</>
|
|
3473
|
-
) : (
|
|
3474
|
-
'Reinstall'
|
|
3475
|
-
)}
|
|
3476
|
-
</button>
|
|
3477
|
-
</div>
|
|
3478
|
-
) : (
|
|
3479
|
-
<button
|
|
3480
|
-
onClick={() => handleUpdateTool(tool.id)}
|
|
3481
|
-
disabled={updatingToolId !== null}
|
|
3482
|
-
className="px-3.5 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 text-white font-bold text-xs rounded-lg transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
3483
|
-
>
|
|
3484
|
-
{updatingToolId === tool.id ? (
|
|
3485
|
-
<>
|
|
3486
|
-
<RefreshCw className="animate-spin" size={12} /> Installing...
|
|
3487
|
-
</>
|
|
3488
|
-
) : (
|
|
3489
|
-
'Install CLI'
|
|
3490
|
-
)}
|
|
3491
|
-
</button>
|
|
3492
|
-
)}
|
|
3493
|
-
</div>
|
|
3494
|
-
</div>
|
|
3495
|
-
))}
|
|
3496
|
-
</div>
|
|
3497
|
-
)}
|
|
3498
|
-
</div>
|
|
3499
|
-
</div>
|
|
3500
|
-
)}
|
|
3501
|
-
</>
|
|
3502
|
-
)}
|
|
3503
|
-
</main>
|
|
3504
|
-
|
|
3505
|
-
{/* Transcript Modal Overlay */}
|
|
3506
|
-
{activeSession && (
|
|
3507
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/75 backdrop-blur-sm p-4">
|
|
3508
|
-
<div className="bg-[#0b0f19] border border-gray-800 rounded-2xl w-full max-w-4xl h-[80vh] flex flex-col shadow-2xl overflow-hidden animate-fadeIn">
|
|
3509
|
-
{/* Modal Header */}
|
|
3510
|
-
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-800 bg-gray-950/40">
|
|
3511
|
-
<div>
|
|
3512
|
-
<div className="flex items-center gap-2 mb-1">
|
|
3513
|
-
<span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider ${
|
|
3514
|
-
activeSession.assistant === 'antigravity' ? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30' :
|
|
3515
|
-
activeSession.assistant === 'claude' ? 'bg-amber-500/20 text-amber-300 border border-amber-500/30' :
|
|
3516
|
-
activeSession.assistant === 'codex' ? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30' :
|
|
3517
|
-
'bg-sky-500/20 text-sky-300 border border-sky-500/30'
|
|
3518
|
-
}`}>
|
|
3519
|
-
{activeSession.assistant === 'antigravity' ? 'Antigravity' :
|
|
3520
|
-
activeSession.assistant === 'claude' ? 'Claude Code' :
|
|
3521
|
-
activeSession.assistant === 'codex' ? 'OpenAI Codex' : 'GitHub Copilot'}
|
|
3522
|
-
</span>
|
|
3523
|
-
<span className="text-[10px] text-gray-500">Session: {activeSession.id}</span>
|
|
3524
|
-
</div>
|
|
3525
|
-
<h3 className="text-sm font-bold text-white truncate max-w-xl" title={activeSession.title}>
|
|
3526
|
-
{activeSession.title}
|
|
3527
|
-
</h3>
|
|
3528
|
-
</div>
|
|
3529
|
-
<button
|
|
3530
|
-
className="text-gray-400 hover:text-white p-2 hover:bg-gray-800/80 rounded-lg transition-colors cursor-pointer"
|
|
3531
|
-
onClick={() => setActiveSession(null)}
|
|
3532
|
-
>
|
|
3533
|
-
<X size={18} />
|
|
3534
|
-
</button>
|
|
3535
|
-
</div>
|
|
3536
|
-
|
|
3537
|
-
{/* Modal Body / Chat Messages */}
|
|
3538
|
-
<div className="flex-1 overflow-y-auto p-6 space-y-4 bg-gray-950/10">
|
|
3539
|
-
{transcriptLoading ? (
|
|
3540
|
-
<div className="flex flex-col items-center justify-center h-full gap-2">
|
|
3541
|
-
<RefreshCw className="animate-spin text-indigo-400" size={24} />
|
|
3542
|
-
<span className="text-xs text-gray-500 font-medium">Loading conversation history...</span>
|
|
3543
|
-
</div>
|
|
3544
|
-
) : transcript.length === 0 ? (
|
|
3545
|
-
<div className="flex items-center justify-center h-full text-xs text-gray-500">
|
|
3546
|
-
No messages found in this transcript.
|
|
3547
|
-
</div>
|
|
3548
|
-
) : (
|
|
3549
|
-
transcript.map((msg, idx) => (
|
|
3550
|
-
<div key={idx} className={`flex flex-col ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
|
|
3551
|
-
<div className="text-[10px] text-gray-500 mb-1 px-1">
|
|
3552
|
-
{msg.role === 'user' ? 'Developer' : activeSession.assistant === 'antigravity' ? 'Antigravity' : activeSession.assistant === 'claude' ? 'Claude' : activeSession.assistant === 'codex' ? 'Codex' : 'Copilot'}
|
|
3553
|
-
{msg.timestamp && ` • ${new Date(msg.timestamp).toLocaleTimeString()}`}
|
|
3554
|
-
</div>
|
|
3555
|
-
<div className={`max-w-[85%] rounded-2xl px-4 py-3 text-xs leading-relaxed whitespace-pre-wrap ${
|
|
3556
|
-
msg.role === 'user'
|
|
3557
|
-
? 'bg-indigo-650 text-white rounded-tr-none shadow-md border border-indigo-500/10'
|
|
3558
|
-
: 'bg-gray-900 border border-gray-800 text-gray-200 rounded-tl-none shadow-sm font-sans'
|
|
3559
|
-
}`}>
|
|
3560
|
-
{msg.content}
|
|
3561
|
-
</div>
|
|
3562
|
-
</div>
|
|
3563
|
-
))
|
|
3564
|
-
)}
|
|
3565
|
-
</div>
|
|
3566
|
-
|
|
3567
|
-
{/* Modal Footer */}
|
|
3568
|
-
<div className="px-6 py-4 border-t border-gray-800 bg-gray-950/40 flex justify-between items-center">
|
|
3569
|
-
<span className="text-[11px] text-gray-550">
|
|
3570
|
-
Resuming will copy the shell command and open your code editor.
|
|
3571
|
-
</span>
|
|
3572
|
-
<div className="flex gap-2">
|
|
3573
|
-
<button
|
|
3574
|
-
className="inline-flex items-center justify-center gap-1.5 px-4 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 rounded-lg text-xs font-bold text-white transition-all cursor-pointer"
|
|
3575
|
-
onClick={() => {
|
|
3576
|
-
const cmd = activeSession.assistant === 'antigravity' ? `agy --conversation ${activeSession.id}` :
|
|
3577
|
-
activeSession.assistant === 'claude' ? `claude --resume ${activeSession.id}` :
|
|
3578
|
-
activeSession.assistant === 'codex' ? `codex resume ${activeSession.id}` :
|
|
3579
|
-
`copilot --resume ${activeSession.id}`;
|
|
3580
|
-
navigator.clipboard.writeText(cmd);
|
|
3581
|
-
showToast(`Copied run command to clipboard:\n\n${cmd}`, 'success');
|
|
3582
|
-
}}
|
|
3583
|
-
>
|
|
3584
|
-
<Copy size={13} /> Copy Resume Command
|
|
3585
|
-
</button>
|
|
3586
|
-
<button
|
|
3587
|
-
className="inline-flex items-center justify-center gap-1.5 px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-xs font-bold transition-all cursor-pointer"
|
|
3588
|
-
onClick={() => {
|
|
3589
|
-
const ws = workspaces.find(w => w.branchName === activeSession.workspacePath.split(/[\\/]/).pop());
|
|
3590
|
-
handleResumeSession(ws || workspaces[0], activeSession.id, activeSession.assistant);
|
|
3591
|
-
setActiveSession(null);
|
|
3592
|
-
}}
|
|
3593
|
-
>
|
|
3594
|
-
<Play size={12} /> Resume Conversation
|
|
3595
|
-
</button>
|
|
3596
|
-
</div>
|
|
3597
|
-
</div>
|
|
3598
|
-
</div>
|
|
3599
|
-
</div>
|
|
3600
|
-
)}
|
|
3601
|
-
|
|
3602
|
-
{/* Toast Notifications Container */}
|
|
3603
|
-
<div className="fixed bottom-5 right-5 z-[9999] flex flex-col gap-3 pointer-events-none max-w-md w-full">
|
|
3604
|
-
{toasts.map((toast) => (
|
|
3605
|
-
<div
|
|
3606
|
-
key={toast.id}
|
|
3607
|
-
className={`pointer-events-auto flex items-start justify-between gap-3 px-4 py-3 rounded-xl border shadow-2xl transition-all duration-300 animate-slide-in ${
|
|
3608
|
-
toast.type === 'success'
|
|
3609
|
-
? 'bg-[#062c1b]/95 border-emerald-800/80 text-emerald-100'
|
|
3610
|
-
: toast.type === 'error'
|
|
3611
|
-
? 'bg-[#2c0e0e]/95 border-red-900/80 text-red-100'
|
|
3612
|
-
: 'bg-[#131926]/95 border-slate-800/80 text-slate-100'
|
|
3613
|
-
}`}
|
|
3614
|
-
>
|
|
3615
|
-
<div className="flex items-start gap-2.5 text-xs font-semibold flex-1">
|
|
3616
|
-
{toast.type === 'success' && <Check className="text-emerald-400 shrink-0 mt-0.5" size={16} />}
|
|
3617
|
-
{toast.type === 'error' && <AlertCircle className="text-red-400 shrink-0 mt-0.5" size={16} />}
|
|
3618
|
-
{toast.type === 'info' && <Sparkles className="text-indigo-400 shrink-0 mt-0.5" size={16} />}
|
|
3619
|
-
<span className="whitespace-pre-line text-left leading-relaxed">{toast.message}</span>
|
|
3620
|
-
</div>
|
|
3621
|
-
<button
|
|
3622
|
-
onClick={() => setToasts((prev) => prev.filter((t) => t.id !== toast.id))}
|
|
3623
|
-
className="text-slate-400 hover:text-white transition-colors cursor-pointer shrink-0 mt-0.5"
|
|
3624
|
-
>
|
|
3625
|
-
<X size={14} />
|
|
3626
|
-
</button>
|
|
3627
|
-
</div>
|
|
3628
|
-
))}
|
|
3629
|
-
</div>
|
|
3630
|
-
</div>
|
|
3631
|
-
);
|
|
3632
|
-
}
|