@mrpatronz/nexusflow 0.2.7 → 0.2.8
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/.github/workflows/release.yml +7 -0
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +6 -1
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +58 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +73 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +11 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/config.test.d.ts +2 -0
- package/dist/core/config.test.d.ts.map +1 -0
- package/dist/core/config.test.js +75 -0
- package/dist/core/config.test.js.map +1 -0
- package/dist/core/scanner.test.d.ts +2 -0
- package/dist/core/scanner.test.d.ts.map +1 -0
- package/dist/core/scanner.test.js +53 -0
- package/dist/core/scanner.test.js.map +1 -0
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +8 -2
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/skills-generator.js +1 -1
- package/dist/generators/skills-generator.js.map +1 -1
- package/dist/gui/assets/index-Ca2VRMrQ.js +22 -0
- package/dist/gui/assets/index-fj0RJiOb.css +2 -0
- package/dist/gui/index.html +2 -2
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +132 -93
- package/dist/mcp/server.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +132 -11
- package/dist/server.js.map +1 -1
- package/dist/server.test.d.ts +2 -0
- package/dist/server.test.d.ts.map +1 -0
- package/dist/server.test.js +318 -0
- package/dist/server.test.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/detect-ai.test.d.ts +2 -0
- package/dist/utils/detect-ai.test.d.ts.map +1 -0
- package/dist/utils/detect-ai.test.js +37 -0
- package/dist/utils/detect-ai.test.js.map +1 -0
- package/dist/utils/detect-editors.test.d.ts +2 -0
- package/dist/utils/detect-editors.test.d.ts.map +1 -0
- package/dist/utils/detect-editors.test.js +39 -0
- package/dist/utils/detect-editors.test.js.map +1 -0
- package/dist/utils/git.test.d.ts +2 -0
- package/dist/utils/git.test.d.ts.map +1 -0
- package/dist/utils/git.test.js +55 -0
- package/dist/utils/git.test.js.map +1 -0
- package/dist/utils/local-ai.d.ts +27 -0
- package/dist/utils/local-ai.d.ts.map +1 -0
- package/dist/utils/local-ai.js +96 -0
- package/dist/utils/local-ai.js.map +1 -0
- package/dist/utils/local-ai.test.d.ts +2 -0
- package/dist/utils/local-ai.test.d.ts.map +1 -0
- package/dist/utils/local-ai.test.js +108 -0
- package/dist/utils/local-ai.test.js.map +1 -0
- package/dist/utils/system-scanner.d.ts +16 -0
- package/dist/utils/system-scanner.d.ts.map +1 -0
- package/dist/utils/system-scanner.js +85 -0
- package/dist/utils/system-scanner.js.map +1 -0
- package/dist/utils/system-scanner.test.d.ts +2 -0
- package/dist/utils/system-scanner.test.d.ts.map +1 -0
- package/dist/utils/system-scanner.test.js +73 -0
- package/dist/utils/system-scanner.test.js.map +1 -0
- package/gui/e2e/wizard.spec.ts +327 -0
- package/gui/package-lock.json +64 -0
- package/gui/package.json +4 -1
- package/gui/playwright.config.ts +41 -0
- package/gui/src/App.tsx +376 -786
- package/gui/src/index.css +16 -0
- package/package.json +3 -1
- package/scripts/simulate-workspaces.ts +55 -0
- package/src/commands/create.ts +7 -1
- package/src/commands/doctor.ts +56 -0
- package/src/commands/init.ts +77 -1
- package/src/core/config.test.ts +96 -0
- package/src/core/config.ts +11 -1
- package/src/core/scanner.test.ts +61 -0
- package/src/generators/base.ts +13 -2
- package/src/generators/skills-generator.ts +1 -1
- package/src/mcp/server.ts +144 -95
- package/src/server.test.ts +367 -0
- package/src/server.ts +139 -10
- package/src/types.ts +16 -0
- package/src/utils/detect-ai.test.ts +44 -0
- package/src/utils/detect-editors.test.ts +47 -0
- package/src/utils/git.test.ts +74 -0
- package/src/utils/local-ai.test.ts +130 -0
- package/src/utils/local-ai.ts +111 -0
- package/src/utils/system-scanner.test.ts +89 -0
- package/src/utils/system-scanner.ts +96 -0
- package/vitest.config.ts +2 -1
- package/dist/gui/assets/index-BgEo5w2Q.js +0 -22
- package/dist/gui/assets/index-D-VigurY.css +0 -2
package/gui/src/App.tsx
CHANGED
|
@@ -5,8 +5,6 @@ import {
|
|
|
5
5
|
Settings as SettingsIcon,
|
|
6
6
|
Terminal,
|
|
7
7
|
Play,
|
|
8
|
-
Square,
|
|
9
|
-
ExternalLink,
|
|
10
8
|
Check,
|
|
11
9
|
AlertTriangle,
|
|
12
10
|
AlertCircle,
|
|
@@ -18,19 +16,20 @@ import {
|
|
|
18
16
|
Sparkles,
|
|
19
17
|
Copy,
|
|
20
18
|
X,
|
|
21
|
-
|
|
22
|
-
MessageSquareCode,
|
|
23
|
-
BookOpen,
|
|
24
|
-
ListOrdered,
|
|
25
|
-
Save,
|
|
26
|
-
Edit,
|
|
27
|
-
Download,
|
|
28
|
-
Trash2,
|
|
19
|
+
Cpu,
|
|
29
20
|
} from 'lucide-react';
|
|
30
21
|
import './App.css';
|
|
22
|
+
import { WorkspaceList } from './features/workspace/WorkspaceList.js';
|
|
31
23
|
|
|
32
24
|
|
|
33
25
|
// Types matched with src/types.ts
|
|
26
|
+
interface LocalLlmConfig {
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
provider: 'ollama' | 'openai-compatible';
|
|
29
|
+
endpoint: string;
|
|
30
|
+
model: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
34
33
|
interface NexusFlowConfig {
|
|
35
34
|
version: string;
|
|
36
35
|
devDir: string;
|
|
@@ -38,6 +37,8 @@ interface NexusFlowConfig {
|
|
|
38
37
|
defaultAssistant: string | null;
|
|
39
38
|
defaultEditor?: string | null;
|
|
40
39
|
scanDepth: number;
|
|
40
|
+
localLlm?: LocalLlmConfig;
|
|
41
|
+
packContextXml?: boolean;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
interface DetectedAI {
|
|
@@ -95,90 +96,24 @@ interface RunningService {
|
|
|
95
96
|
const API_BASE = import.meta.env.DEV ? 'http://localhost:3000' : '';
|
|
96
97
|
const isVsCode = new URLSearchParams(window.location.search).get('env') === 'vscode';
|
|
97
98
|
|
|
98
|
-
const parseInlineStyles = (text: string): React.ReactNode => {
|
|
99
|
-
const tokenRegex = /(\*\*.*?\*\*|`.*?`|\*.*?\*)/g;
|
|
100
|
-
const parts = text.split(tokenRegex);
|
|
101
|
-
|
|
102
|
-
return parts.map((part, index) => {
|
|
103
|
-
if (part.startsWith('**') && part.endsWith('**')) {
|
|
104
|
-
return <strong key={index} className="font-bold text-gray-200">{part.slice(2, -2)}</strong>;
|
|
105
|
-
}
|
|
106
|
-
if (part.startsWith('`') && part.endsWith('`')) {
|
|
107
|
-
return <code key={index} className="px-1.5 py-0.5 bg-gray-900 border border-gray-800 text-indigo-300 rounded font-mono text-[10px]">{part.slice(1, -1)}</code>;
|
|
108
|
-
}
|
|
109
|
-
if (part.startsWith('*') && part.endsWith('*')) {
|
|
110
|
-
return <em key={index} className="italic text-gray-300">{part.slice(1, -1)}</em>;
|
|
111
|
-
}
|
|
112
|
-
return part;
|
|
113
|
-
});
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const renderFormattedDescription = (text: string) => {
|
|
117
|
-
if (!text) return null;
|
|
118
|
-
|
|
119
|
-
const lines = text.split('\n');
|
|
120
|
-
const elements: React.ReactNode[] = [];
|
|
121
|
-
let currentList: { type: 'ul' | 'ol'; items: string[] } | null = null;
|
|
122
|
-
|
|
123
|
-
const flushList = (key: number) => {
|
|
124
|
-
if (currentList) {
|
|
125
|
-
const ListTag = currentList.type;
|
|
126
|
-
elements.push(
|
|
127
|
-
<ListTag key={`list-${key}`} className={currentList.type === 'ul' ? 'list-disc pl-5 my-1.5 space-y-1' : 'list-decimal pl-5 my-1.5 space-y-1'}>
|
|
128
|
-
{currentList.items.map((item, idx) => (
|
|
129
|
-
<li key={`item-${idx}`} className="leading-relaxed">{parseInlineStyles(item)}</li>
|
|
130
|
-
))}
|
|
131
|
-
</ListTag>
|
|
132
|
-
);
|
|
133
|
-
currentList = null;
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
lines.forEach((line, idx) => {
|
|
138
|
-
const trimmed = line.trim();
|
|
139
|
-
|
|
140
|
-
// Check for bullet list item
|
|
141
|
-
const bulletMatch = line.match(/^(\s*)[-*+]\s+(.*)/);
|
|
142
|
-
if (bulletMatch) {
|
|
143
|
-
if (!currentList || currentList.type !== 'ul') {
|
|
144
|
-
flushList(idx);
|
|
145
|
-
currentList = { type: 'ul', items: [] };
|
|
146
|
-
}
|
|
147
|
-
currentList.items.push(bulletMatch[2]);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Check for numbered list item
|
|
152
|
-
const numberMatch = line.match(/^(\s*)\d+\.\s+(.*)/);
|
|
153
|
-
if (numberMatch) {
|
|
154
|
-
if (!currentList || currentList.type !== 'ol') {
|
|
155
|
-
flushList(idx);
|
|
156
|
-
currentList = { type: 'ol', items: [] };
|
|
157
|
-
}
|
|
158
|
-
currentList.items.push(numberMatch[2]);
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// If not a list item, flush any active list first
|
|
163
|
-
flushList(idx);
|
|
164
|
-
|
|
165
|
-
if (trimmed === '') {
|
|
166
|
-
elements.push(<div key={`empty-${idx}`} className="h-2" />);
|
|
167
|
-
} else {
|
|
168
|
-
elements.push(
|
|
169
|
-
<div key={`line-${idx}`} className="leading-relaxed">
|
|
170
|
-
{parseInlineStyles(line)}
|
|
171
|
-
</div>
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
flushList(lines.length);
|
|
177
|
-
return <div className="space-y-1">{elements}</div>;
|
|
178
|
-
};
|
|
179
|
-
|
|
180
99
|
export default function App() {
|
|
181
100
|
const [view, setView] = useState<'guide' | 'create' | 'workspaces' | 'settings'>('guide');
|
|
101
|
+
|
|
102
|
+
// Toast State
|
|
103
|
+
interface Toast {
|
|
104
|
+
id: string;
|
|
105
|
+
message: string;
|
|
106
|
+
type: 'success' | 'error' | 'info';
|
|
107
|
+
duration?: number;
|
|
108
|
+
}
|
|
109
|
+
const [toasts, setToasts] = useState<Toast[]>([]);
|
|
110
|
+
const showToast = (message: string, type: 'success' | 'error' | 'info' = 'info', duration = 5000) => {
|
|
111
|
+
const id = Math.random().toString(36).substring(2, 9);
|
|
112
|
+
setToasts((prev) => [...prev, { id, message, type, duration }]);
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
115
|
+
}, duration);
|
|
116
|
+
};
|
|
182
117
|
|
|
183
118
|
// App Config
|
|
184
119
|
const [config, setConfig] = useState<NexusFlowConfig | null>(null);
|
|
@@ -209,6 +144,7 @@ export default function App() {
|
|
|
209
144
|
const [selectedRepos, setSelectedRepos] = useState<RepoInfo[]>([]);
|
|
210
145
|
const [selectedAI, setSelectedAI] = useState<string[]>([]);
|
|
211
146
|
const [selectedEditor, setSelectedEditor] = useState<DetectedEditor | null>(null);
|
|
147
|
+
const [localLlmEnabled, setLocalLlmEnabled] = useState(false);
|
|
212
148
|
const [creating, setCreating] = useState(false);
|
|
213
149
|
const [createdWorkspace, setCreatedWorkspace] = useState<{ path: string } | null>(null);
|
|
214
150
|
const [creationError, setCreationError] = useState<string | null>(null);
|
|
@@ -284,6 +220,23 @@ export default function App() {
|
|
|
284
220
|
const [serviceLogs, setServiceLogs] = useState<string>('');
|
|
285
221
|
const logsEndRef = useRef<HTMLDivElement | null>(null);
|
|
286
222
|
|
|
223
|
+
// Local LLM states
|
|
224
|
+
const [recommendation, setRecommendation] = useState<{ totalRamGb: number; gpuName: string; recommendedModel: string } | null>(null);
|
|
225
|
+
const [testStatus, setTestStatus] = useState<{ success: boolean; message: string } | null>(null);
|
|
226
|
+
const [testingLlm, setTestingLlm] = useState(false);
|
|
227
|
+
|
|
228
|
+
const isSettingsFormValid = (() => {
|
|
229
|
+
if (!config) return false;
|
|
230
|
+
if (!config.devDir || config.devDir.trim() === '' || !config.workspacesDir || config.workspacesDir.trim() === '') return false;
|
|
231
|
+
if (config.localLlm?.enabled) {
|
|
232
|
+
const endpoint = config.localLlm.endpoint?.trim() || '';
|
|
233
|
+
if (!endpoint.startsWith('http://') && !endpoint.startsWith('https://')) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return true;
|
|
238
|
+
})();
|
|
239
|
+
|
|
287
240
|
// Initial loads
|
|
288
241
|
useEffect(() => {
|
|
289
242
|
fetchConfig();
|
|
@@ -292,10 +245,11 @@ export default function App() {
|
|
|
292
245
|
fetchUpdateStatus();
|
|
293
246
|
}, []);
|
|
294
247
|
|
|
295
|
-
// Load tool updates status when settings view is open
|
|
248
|
+
// Load tool updates status and LLM recommendations when settings view is open
|
|
296
249
|
useEffect(() => {
|
|
297
250
|
if (view === 'settings') {
|
|
298
251
|
fetchToolsStatus();
|
|
252
|
+
fetchLlmRecommendation();
|
|
299
253
|
}
|
|
300
254
|
}, [view]);
|
|
301
255
|
|
|
@@ -406,26 +360,62 @@ export default function App() {
|
|
|
406
360
|
});
|
|
407
361
|
const data = await res.json();
|
|
408
362
|
if (res.ok && data.success) {
|
|
409
|
-
|
|
363
|
+
showToast(`${toolId} updated successfully!\n\nOutput:\n${data.output}`, 'success');
|
|
410
364
|
fetchToolsStatus(true);
|
|
411
365
|
fetchUpdateStatus();
|
|
412
366
|
} else {
|
|
413
|
-
|
|
367
|
+
showToast(`Error: ${data.error || 'Failed to update tool'}`, 'error');
|
|
414
368
|
}
|
|
415
369
|
} catch (e) {
|
|
416
370
|
console.error(e);
|
|
417
|
-
|
|
371
|
+
showToast('Network error when updating tool.', 'error');
|
|
418
372
|
} finally {
|
|
419
373
|
setUpdatingToolId(null);
|
|
420
374
|
}
|
|
421
375
|
};
|
|
422
376
|
|
|
377
|
+
const fetchLlmRecommendation = async () => {
|
|
378
|
+
try {
|
|
379
|
+
const res = await fetch(`${API_BASE}/api/local-llm/recommend`);
|
|
380
|
+
if (res.ok) {
|
|
381
|
+
const data = await res.json();
|
|
382
|
+
setRecommendation(data);
|
|
383
|
+
}
|
|
384
|
+
} catch (e) {
|
|
385
|
+
console.error('Error fetching LLM recommendation:', e);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const testLlmConnection = async () => {
|
|
390
|
+
if (!config?.localLlm) return;
|
|
391
|
+
setTestingLlm(true);
|
|
392
|
+
setTestStatus(null);
|
|
393
|
+
try {
|
|
394
|
+
const res = await fetch(`${API_BASE}/api/local-llm/test`, {
|
|
395
|
+
method: 'POST',
|
|
396
|
+
headers: { 'Content-Type': 'application/json' },
|
|
397
|
+
body: JSON.stringify(config.localLlm),
|
|
398
|
+
});
|
|
399
|
+
const data = await res.json();
|
|
400
|
+
if (data.success) {
|
|
401
|
+
setTestStatus({ success: true, message: data.message });
|
|
402
|
+
} else {
|
|
403
|
+
setTestStatus({ success: false, message: data.error || 'Connection failed.' });
|
|
404
|
+
}
|
|
405
|
+
} catch (e: any) {
|
|
406
|
+
setTestStatus({ success: false, message: e.message || 'Network error.' });
|
|
407
|
+
} finally {
|
|
408
|
+
setTestingLlm(false);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
423
412
|
const fetchConfig = async () => {
|
|
424
413
|
try {
|
|
425
414
|
const res = await fetch(`${API_BASE}/api/config`);
|
|
426
415
|
const data = await res.json();
|
|
427
416
|
setConfig(data.config);
|
|
428
417
|
setConfigExists(data.exists);
|
|
418
|
+
setLocalLlmEnabled(data.config?.localLlm?.enabled || false);
|
|
429
419
|
|
|
430
420
|
fetchEditors(data.config?.defaultEditor);
|
|
431
421
|
|
|
@@ -462,6 +452,7 @@ export default function App() {
|
|
|
462
452
|
setSaveStatus('success');
|
|
463
453
|
setConfig(newConfig);
|
|
464
454
|
setConfigExists(true);
|
|
455
|
+
setLocalLlmEnabled(newConfig.localLlm?.enabled || false);
|
|
465
456
|
fetchRepos();
|
|
466
457
|
fetchWorkspaces();
|
|
467
458
|
} else {
|
|
@@ -732,6 +723,7 @@ export default function App() {
|
|
|
732
723
|
description,
|
|
733
724
|
repos: selectedRepos,
|
|
734
725
|
assistants: selectedAI,
|
|
726
|
+
localLlmEnabled,
|
|
735
727
|
resumption: {
|
|
736
728
|
testCommand,
|
|
737
729
|
mockCommand: mockCommand || undefined,
|
|
@@ -852,15 +844,15 @@ export default function App() {
|
|
|
852
844
|
setActiveWsId(ws.branchName);
|
|
853
845
|
} else {
|
|
854
846
|
navigator.clipboard.writeText(data.resumeCommand);
|
|
855
|
-
|
|
847
|
+
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');
|
|
856
848
|
setActiveWsId(ws.branchName);
|
|
857
849
|
}
|
|
858
850
|
} else {
|
|
859
|
-
|
|
851
|
+
showToast(`Error: ${data.error || 'Failed to resume session'}`, 'error');
|
|
860
852
|
}
|
|
861
853
|
} catch (e) {
|
|
862
854
|
console.error(e);
|
|
863
|
-
|
|
855
|
+
showToast('Network error when resuming session.', 'error');
|
|
864
856
|
} finally {
|
|
865
857
|
setResumingWs(null);
|
|
866
858
|
}
|
|
@@ -872,7 +864,7 @@ export default function App() {
|
|
|
872
864
|
return;
|
|
873
865
|
}
|
|
874
866
|
if (config?.defaultEditor === 'none') {
|
|
875
|
-
|
|
867
|
+
showToast('Your preferred editor is set to "None" (skip opening). You can change this in Settings.', 'info');
|
|
876
868
|
return;
|
|
877
869
|
}
|
|
878
870
|
let editor = null;
|
|
@@ -883,7 +875,7 @@ export default function App() {
|
|
|
883
875
|
editor = editors.find((e) => e.detected) || editors[0];
|
|
884
876
|
}
|
|
885
877
|
if (!editor) {
|
|
886
|
-
|
|
878
|
+
showToast('No detected editors available.', 'error');
|
|
887
879
|
return;
|
|
888
880
|
}
|
|
889
881
|
try {
|
|
@@ -900,18 +892,7 @@ export default function App() {
|
|
|
900
892
|
}
|
|
901
893
|
};
|
|
902
894
|
|
|
903
|
-
|
|
904
|
-
const encodedId = encodeURIComponent(wsBranchName);
|
|
905
|
-
const downloadUrl = `${API_BASE}/api/workspace/${encodedId}/pack`;
|
|
906
|
-
|
|
907
|
-
// Create temporary link and click it
|
|
908
|
-
const link = document.createElement('a');
|
|
909
|
-
link.href = downloadUrl;
|
|
910
|
-
link.setAttribute('download', '');
|
|
911
|
-
document.body.appendChild(link);
|
|
912
|
-
link.click();
|
|
913
|
-
document.body.removeChild(link);
|
|
914
|
-
};
|
|
895
|
+
|
|
915
896
|
|
|
916
897
|
const handleCopyPrompt = (ws: Feature) => {
|
|
917
898
|
const repoNames = ws.repos.map((r) => r.split(/[\\/]/).pop()).join(', ');
|
|
@@ -929,7 +910,7 @@ Core Instructions:
|
|
|
929
910
|
3. Follow all project-specific rules in "CLAUDE.md", ".cursorrules", or "AGENTS.md" in sub-repositories.
|
|
930
911
|
`;
|
|
931
912
|
navigator.clipboard.writeText(prompt);
|
|
932
|
-
|
|
913
|
+
showToast('Universal AI briefing prompt copied to clipboard!', 'success');
|
|
933
914
|
};
|
|
934
915
|
|
|
935
916
|
const handleDeleteWorkspace = async (wsName: string) => {
|
|
@@ -948,13 +929,13 @@ Core Instructions:
|
|
|
948
929
|
setActiveWsId(null);
|
|
949
930
|
}
|
|
950
931
|
await fetchWorkspaces();
|
|
951
|
-
|
|
932
|
+
showToast(`Workspace ${wsName} successfully deleted.`, 'success');
|
|
952
933
|
} else {
|
|
953
|
-
|
|
934
|
+
showToast(`Failed to delete workspace: ${data.error || 'Unknown error'}`, 'error');
|
|
954
935
|
}
|
|
955
936
|
} catch (e) {
|
|
956
937
|
console.error(e);
|
|
957
|
-
|
|
938
|
+
showToast('Network error while deleting workspace.', 'error');
|
|
958
939
|
} finally {
|
|
959
940
|
setDeleteWsLoading(null);
|
|
960
941
|
}
|
|
@@ -977,13 +958,13 @@ Core Instructions:
|
|
|
977
958
|
fetchGitChanges(wsName);
|
|
978
959
|
fetchWorkspaceSessions(wsName);
|
|
979
960
|
}
|
|
980
|
-
|
|
961
|
+
showToast('Repository successfully added. Configurations and Repomix packing updated.', 'success');
|
|
981
962
|
} else {
|
|
982
|
-
|
|
963
|
+
showToast(`Failed to add repository: ${data.error || 'Unknown error'}`, 'error');
|
|
983
964
|
}
|
|
984
965
|
} catch (e) {
|
|
985
966
|
console.error(e);
|
|
986
|
-
|
|
967
|
+
showToast('Network error while adding repository.', 'error');
|
|
987
968
|
} finally {
|
|
988
969
|
setAddRepoLoading(false);
|
|
989
970
|
}
|
|
@@ -1154,6 +1135,24 @@ Core Instructions:
|
|
|
1154
1135
|
)}
|
|
1155
1136
|
</div>
|
|
1156
1137
|
|
|
1138
|
+
<div className="pt-2">
|
|
1139
|
+
<div className="flex items-center gap-3">
|
|
1140
|
+
<input
|
|
1141
|
+
type="checkbox"
|
|
1142
|
+
id="onboardingPackContextXml"
|
|
1143
|
+
className="w-4 h-4 rounded border-gray-800 bg-[#111827] text-indigo-600 focus:ring-indigo-500 cursor-pointer"
|
|
1144
|
+
checked={config.packContextXml || false}
|
|
1145
|
+
onChange={(e) => setConfig({ ...config, packContextXml: e.target.checked })}
|
|
1146
|
+
/>
|
|
1147
|
+
<label htmlFor="onboardingPackContextXml" className="text-xs font-semibold text-white cursor-pointer select-none">
|
|
1148
|
+
Pack Codebase Context with Repomix (XML)
|
|
1149
|
+
</label>
|
|
1150
|
+
</div>
|
|
1151
|
+
<span className="text-[10px] text-gray-500 mt-1 block">
|
|
1152
|
+
Aggregates all repository files into a single token-efficient XML file (<code>nexusflow-context.xml</code>) at the workspace root, giving AI assistants immediate access to the full codebase state.
|
|
1153
|
+
</span>
|
|
1154
|
+
</div>
|
|
1155
|
+
|
|
1157
1156
|
{/* Form Buttons */}
|
|
1158
1157
|
<div className="flex gap-2 pt-2">
|
|
1159
1158
|
<button
|
|
@@ -1248,6 +1247,7 @@ Core Instructions:
|
|
|
1248
1247
|
setBranchName('');
|
|
1249
1248
|
setDescription('');
|
|
1250
1249
|
setSelectedRepos([]);
|
|
1250
|
+
setLocalLlmEnabled(config?.localLlm?.enabled || false);
|
|
1251
1251
|
}}
|
|
1252
1252
|
>
|
|
1253
1253
|
<PlusCircle size={18} className="text-indigo-400" />
|
|
@@ -1322,7 +1322,7 @@ Core Instructions:
|
|
|
1322
1322
|
<button
|
|
1323
1323
|
onClick={() => {
|
|
1324
1324
|
navigator.clipboard.writeText('npm install -g @mrpatronz/nexusflow');
|
|
1325
|
-
|
|
1325
|
+
showToast('Update command copied to clipboard!', 'success');
|
|
1326
1326
|
}}
|
|
1327
1327
|
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 cursor-pointer flex items-center gap-1.5"
|
|
1328
1328
|
>
|
|
@@ -1394,9 +1394,9 @@ Core Instructions:
|
|
|
1394
1394
|
4
|
|
1395
1395
|
</div>
|
|
1396
1396
|
<div>
|
|
1397
|
-
<h3 className="text-sm font-semibold text-white">Repomix Packing</h3>
|
|
1397
|
+
<h3 className="text-sm font-semibold text-white">Repomix Packing (Optional)</h3>
|
|
1398
1398
|
<p className="text-xs text-gray-400 mt-1">
|
|
1399
|
-
NexusFlow aggregates the entire multi-repo codebase using <code>Repomix</code> into a token-efficient XML file <code>nexusflow-context.xml</code>, giving AI immediate access to the full codebase state.
|
|
1399
|
+
If enabled, NexusFlow aggregates the entire multi-repo codebase using <code>Repomix</code> into a token-efficient XML file <code>nexusflow-context.xml</code>, giving AI immediate access to the full codebase state. You can toggle this setting in Settings.
|
|
1400
1400
|
</p>
|
|
1401
1401
|
</div>
|
|
1402
1402
|
</div>
|
|
@@ -1820,6 +1820,33 @@ Core Instructions:
|
|
|
1820
1820
|
</div>
|
|
1821
1821
|
</div>
|
|
1822
1822
|
|
|
1823
|
+
<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'}`}>
|
|
1824
|
+
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4 flex items-center gap-2">
|
|
1825
|
+
<Cpu size={14} className="text-indigo-400" /> Local AI Co-processor
|
|
1826
|
+
</h4>
|
|
1827
|
+
<p className="text-[11px] text-gray-500 mb-4">
|
|
1828
|
+
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.
|
|
1829
|
+
</p>
|
|
1830
|
+
{!config?.localLlm?.enabled && (
|
|
1831
|
+
<p className="text-[11px] text-amber-400/80 mb-3 flex items-center gap-1.5">
|
|
1832
|
+
<AlertTriangle size={12} /> Enable a local LLM provider in Settings first.
|
|
1833
|
+
</p>
|
|
1834
|
+
)}
|
|
1835
|
+
<div className="flex items-center gap-3">
|
|
1836
|
+
<input
|
|
1837
|
+
type="checkbox"
|
|
1838
|
+
id="wizardLocalLlmEnabled"
|
|
1839
|
+
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"
|
|
1840
|
+
checked={localLlmEnabled}
|
|
1841
|
+
onChange={(e) => setLocalLlmEnabled(e.target.checked)}
|
|
1842
|
+
disabled={!config?.localLlm?.enabled}
|
|
1843
|
+
/>
|
|
1844
|
+
<label htmlFor="wizardLocalLlmEnabled" className={`text-xs font-semibold cursor-pointer select-none ${config?.localLlm?.enabled ? 'text-white' : 'text-gray-500 cursor-not-allowed'}`}>
|
|
1845
|
+
Include Local AI Co-processor in this workspace context
|
|
1846
|
+
</label>
|
|
1847
|
+
</div>
|
|
1848
|
+
</div>
|
|
1849
|
+
|
|
1823
1850
|
<div className="mb-8 border border-gray-800/80 rounded-xl p-5 bg-gray-950/20">
|
|
1824
1851
|
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-4 flex items-center gap-2">
|
|
1825
1852
|
<Terminal size={14} className="text-indigo-400" /> Advanced Session Resumption Settings
|
|
@@ -1938,667 +1965,67 @@ Core Instructions:
|
|
|
1938
1965
|
|
|
1939
1966
|
{/* View 2: Active Workspaces Panel */}
|
|
1940
1967
|
{view === 'workspaces' && (
|
|
1941
|
-
<
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
{
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
>
|
|
2003
|
-
<Download size={12} className="text-emerald-400" /> Export Context
|
|
2004
|
-
</button>
|
|
2005
|
-
<button
|
|
2006
|
-
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-xs font-semibold transition-all cursor-pointer"
|
|
2007
|
-
onClick={() => handleOpenInEditor(ws.workspacePath)}
|
|
2008
|
-
>
|
|
2009
|
-
<ExternalLink size={12} /> Open
|
|
2010
|
-
</button>
|
|
2011
|
-
<button
|
|
2012
|
-
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-indigo-500 hover:bg-indigo-600 text-white rounded-lg text-xs font-semibold transition-all cursor-pointer shadow-md shadow-indigo-500/10"
|
|
2013
|
-
onClick={() => {
|
|
2014
|
-
if (isExpanded) {
|
|
2015
|
-
setActiveWsId(null);
|
|
2016
|
-
} else {
|
|
2017
|
-
setActiveWsId(ws.branchName);
|
|
2018
|
-
}
|
|
2019
|
-
}}
|
|
2020
|
-
>
|
|
2021
|
-
{isExpanded ? 'Hide Runner' : 'Orchestrate'}
|
|
2022
|
-
</button>
|
|
2023
|
-
<button
|
|
2024
|
-
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 bg-red-950/40 border border-red-900/60 hover:bg-red-900/60 hover:border-red-800 rounded-lg text-xs font-semibold text-red-200 transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed animate-fade-in"
|
|
2025
|
-
onClick={() => handleDeleteWorkspace(ws.branchName)}
|
|
2026
|
-
disabled={deleteWsLoading === ws.branchName}
|
|
2027
|
-
>
|
|
2028
|
-
<Trash2 size={12} className={deleteWsLoading === ws.branchName ? 'animate-spin' : ''} />
|
|
2029
|
-
{deleteWsLoading === ws.branchName ? 'Deleting...' : 'Delete'}
|
|
2030
|
-
</button>
|
|
2031
|
-
</div>
|
|
2032
|
-
</div>
|
|
2033
|
-
|
|
2034
|
-
<div className="text-xs text-gray-400 bg-gray-950/40 border-l-2 border-indigo-500 rounded-r-lg px-4 py-3 mb-4 font-medium italic">
|
|
2035
|
-
{renderFormattedDescription(ws.description)}
|
|
2036
|
-
</div>
|
|
2037
|
-
|
|
2038
|
-
<div className="flex items-center gap-3 mb-4">
|
|
2039
|
-
<div className="flex flex-wrap gap-2">
|
|
2040
|
-
{ws.repos.map((repoPath) => (
|
|
2041
|
-
<span key={repoPath} className="text-[10px] px-2.5 py-1 bg-gray-900 border border-gray-800 text-gray-300 rounded-md font-semibold">
|
|
2042
|
-
{repoPath.split(/[\\/]/).pop()}
|
|
2043
|
-
</span>
|
|
2044
|
-
))}
|
|
2045
|
-
</div>
|
|
2046
|
-
|
|
2047
|
-
{repos.filter((r) => !ws.repos.includes(r.path)).length > 0 && (
|
|
2048
|
-
<div className="flex items-center gap-1.5 ml-auto">
|
|
2049
|
-
<select
|
|
2050
|
-
id={`add-repo-select-${ws.branchName}`}
|
|
2051
|
-
className="bg-gray-900 border border-gray-800 text-gray-300 rounded-md text-[10px] px-2 py-1 font-semibold outline-none focus:border-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer"
|
|
2052
|
-
defaultValue=""
|
|
2053
|
-
onChange={async (e) => {
|
|
2054
|
-
const val = e.target.value;
|
|
2055
|
-
if (val) {
|
|
2056
|
-
if (window.confirm(`Are you sure you want to add repository "${val.split(/[\\/]/).pop()}" to this workspace?\nThis will create a new git worktree and re-run analysis.`)) {
|
|
2057
|
-
await handleAddRepo(ws.branchName, val);
|
|
2058
|
-
}
|
|
2059
|
-
e.target.value = "";
|
|
2060
|
-
}
|
|
2061
|
-
}}
|
|
2062
|
-
disabled={addRepoLoading}
|
|
2063
|
-
>
|
|
2064
|
-
<option value="" disabled>+ Add Repository</option>
|
|
2065
|
-
{repos
|
|
2066
|
-
.filter((r) => !ws.repos.includes(r.path))
|
|
2067
|
-
.map((r) => (
|
|
2068
|
-
<option key={r.path} value={r.path}>{r.name}</option>
|
|
2069
|
-
))
|
|
2070
|
-
}
|
|
2071
|
-
</select>
|
|
2072
|
-
</div>
|
|
2073
|
-
)}
|
|
2074
|
-
</div>
|
|
2075
|
-
|
|
2076
|
-
{/* Expansion: Process management console */}
|
|
2077
|
-
{isExpanded && (
|
|
2078
|
-
<div className="mt-6 pt-6 border-t border-gray-800/80">
|
|
2079
|
-
|
|
2080
|
-
{/* Telemetry Dashboard Grid */}
|
|
2081
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
|
2082
|
-
{/* Telemetry 1: CPU */}
|
|
2083
|
-
<div className="bg-gray-950/40 border border-gray-800/60 rounded-xl p-4 flex flex-col justify-between">
|
|
2084
|
-
<div className="flex justify-between items-center text-xs font-semibold text-gray-400 mb-2">
|
|
2085
|
-
<span>Simulated CPU Load</span>
|
|
2086
|
-
<span className={runningServices.length > 0 ? "text-emerald-400" : "text-gray-500"}>{runningServices.length > 0 ? '4.8%' : '0%'}</span>
|
|
2087
|
-
</div>
|
|
2088
|
-
<div className="w-full bg-gray-800 rounded-full h-1.5 mb-2 overflow-hidden">
|
|
2089
|
-
<div
|
|
2090
|
-
className={`h-1.5 rounded-full transition-all duration-500 ${runningServices.length > 0 ? "bg-gradient-to-r from-emerald-500 to-teal-400 animate-pulse" : "bg-gray-700"}`}
|
|
2091
|
-
style={{ width: runningServices.length > 0 ? '48%' : '0%' }}
|
|
2092
|
-
></div>
|
|
2093
|
-
</div>
|
|
2094
|
-
<span className="text-[10px] text-gray-500">Fluctuating active worker processes</span>
|
|
2095
|
-
</div>
|
|
2096
|
-
|
|
2097
|
-
{/* Telemetry 2: Memory */}
|
|
2098
|
-
<div className="bg-gray-950/40 border border-gray-800/60 rounded-xl p-4 flex flex-col justify-between">
|
|
2099
|
-
<div className="flex justify-between items-center text-xs font-semibold text-gray-400 mb-2">
|
|
2100
|
-
<span>Simulated Memory Allocation</span>
|
|
2101
|
-
<span className={runningServices.length > 0 ? "text-cyan-400" : "text-gray-500"}>{runningServices.length > 0 ? '192 MB' : '0 MB'}</span>
|
|
2102
|
-
</div>
|
|
2103
|
-
<div className="w-full bg-gray-800 rounded-full h-1.5 mb-2 overflow-hidden">
|
|
2104
|
-
<div
|
|
2105
|
-
className={`h-1.5 rounded-full transition-all duration-500 ${runningServices.length > 0 ? "bg-gradient-to-r from-cyan-500 to-indigo-400" : "bg-gray-700"}`}
|
|
2106
|
-
style={{ width: runningServices.length > 0 ? '65%' : '0%' }}
|
|
2107
|
-
></div>
|
|
2108
|
-
</div>
|
|
2109
|
-
<span className="text-[10px] text-gray-500">Working set size for spawned servers</span>
|
|
2110
|
-
</div>
|
|
2111
|
-
|
|
2112
|
-
{/* Telemetry 3: Environment Health */}
|
|
2113
|
-
<div className="bg-gray-950/40 border border-gray-800/60 rounded-xl p-4 flex flex-col justify-between">
|
|
2114
|
-
<div className="flex justify-between items-center text-xs font-semibold text-gray-400 mb-2">
|
|
2115
|
-
<span>Active Workspace Status</span>
|
|
2116
|
-
<span className={`inline-flex items-center gap-1 text-[10px] uppercase font-bold tracking-wider ${runningServices.length > 0 ? "text-emerald-400" : "text-gray-500"}`}>
|
|
2117
|
-
<span className={`w-1.5 h-1.5 rounded-full ${runningServices.length > 0 ? "bg-emerald-500 animate-ping" : "bg-gray-600"}`}></span>
|
|
2118
|
-
{runningServices.length > 0 ? "Online" : "Idle"}
|
|
2119
|
-
</span>
|
|
2120
|
-
</div>
|
|
2121
|
-
<div className="text-xs text-white font-medium truncate">
|
|
2122
|
-
{runningServices.length > 0 ? `${runningServices.length} processes active` : "All processes offline"}
|
|
2123
|
-
</div>
|
|
2124
|
-
<span className="text-[10px] text-gray-500">Service state mapping</span>
|
|
2125
|
-
</div>
|
|
2126
|
-
</div>
|
|
2127
|
-
|
|
2128
|
-
{/* Sub-tab Navigation */}
|
|
2129
|
-
<div className="flex border-b border-gray-800/80 mb-6 gap-2">
|
|
2130
|
-
<button
|
|
2131
|
-
className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
|
|
2132
|
-
subTab === 'sessions'
|
|
2133
|
-
? 'border-indigo-500 text-white'
|
|
2134
|
-
: 'border-transparent text-gray-500 hover:text-gray-300'
|
|
2135
|
-
}`}
|
|
2136
|
-
onClick={() => setSubTab('sessions')}
|
|
2137
|
-
>
|
|
2138
|
-
AI Session History
|
|
2139
|
-
</button>
|
|
2140
|
-
<button
|
|
2141
|
-
className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
|
|
2142
|
-
subTab === 'services'
|
|
2143
|
-
? 'border-indigo-500 text-white'
|
|
2144
|
-
: 'border-transparent text-gray-500 hover:text-gray-300'
|
|
2145
|
-
}`}
|
|
2146
|
-
onClick={() => setSubTab('services')}
|
|
2147
|
-
>
|
|
2148
|
-
Orchestrated Services
|
|
2149
|
-
</button>
|
|
2150
|
-
<button
|
|
2151
|
-
className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
|
|
2152
|
-
subTab === 'changes'
|
|
2153
|
-
? 'border-indigo-500 text-white'
|
|
2154
|
-
: 'border-transparent text-gray-500 hover:text-gray-300'
|
|
2155
|
-
}`}
|
|
2156
|
-
onClick={() => setSubTab('changes')}
|
|
2157
|
-
>
|
|
2158
|
-
Active Changes (AI Diffs)
|
|
2159
|
-
</button>
|
|
2160
|
-
<button
|
|
2161
|
-
className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
|
|
2162
|
-
subTab === 'knowledge'
|
|
2163
|
-
? 'border-indigo-500 text-white'
|
|
2164
|
-
: 'border-transparent text-gray-500 hover:text-gray-300'
|
|
2165
|
-
}`}
|
|
2166
|
-
onClick={() => setSubTab('knowledge')}
|
|
2167
|
-
>
|
|
2168
|
-
Knowledge Base
|
|
2169
|
-
</button>
|
|
2170
|
-
<button
|
|
2171
|
-
className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
|
|
2172
|
-
subTab === 'plan'
|
|
2173
|
-
? 'border-indigo-500 text-white'
|
|
2174
|
-
: 'border-transparent text-gray-500 hover:text-gray-300'
|
|
2175
|
-
}`}
|
|
2176
|
-
onClick={() => setSubTab('plan')}
|
|
2177
|
-
>
|
|
2178
|
-
Implementation Plan
|
|
2179
|
-
</button>
|
|
2180
|
-
</div>
|
|
2181
|
-
|
|
2182
|
-
{subTab === 'sessions' && (
|
|
2183
|
-
sessionsLoading ? (
|
|
2184
|
-
<div className="flex justify-center py-10">
|
|
2185
|
-
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
2186
|
-
</div>
|
|
2187
|
-
) : sessions.length === 0 ? (
|
|
2188
|
-
<div className="text-center py-10 bg-gray-950/20 border border-gray-800/60 rounded-xl p-6">
|
|
2189
|
-
<MessageSquareCode size={36} className="text-gray-650 mx-auto mb-3" />
|
|
2190
|
-
<h4 className="text-sm font-bold text-gray-400 mb-1">No Past AI Sessions Found</h4>
|
|
2191
|
-
<p className="text-xs text-gray-505 max-w-md mx-auto leading-relaxed">
|
|
2192
|
-
Start a conversation with your AI assistant (e.g. running <code>claude</code>, <code>agy</code>, <code>codex</code>, or <code>copilot</code> inside this directory) to track session history here.
|
|
2193
|
-
</p>
|
|
2194
|
-
</div>
|
|
2195
|
-
) : (
|
|
2196
|
-
<div className="space-y-3 mb-6">
|
|
2197
|
-
{sessions.map((sess) => (
|
|
2198
|
-
<div key={sess.id} className="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-gray-950/20 border border-gray-800/60 rounded-xl p-4 hover:border-gray-700/80 transition-all">
|
|
2199
|
-
<div className="flex-1 min-w-0">
|
|
2200
|
-
<div className="flex items-center gap-2.5 mb-1.5">
|
|
2201
|
-
<span className={`px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-wider ${
|
|
2202
|
-
sess.assistant === 'antigravity' ? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30' :
|
|
2203
|
-
sess.assistant === 'claude' ? 'bg-amber-500/20 text-amber-300 border border-amber-500/30' :
|
|
2204
|
-
sess.assistant === 'codex' ? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30' :
|
|
2205
|
-
'bg-sky-500/20 text-sky-300 border border-sky-500/30'
|
|
2206
|
-
}`}>
|
|
2207
|
-
{sess.assistant === 'antigravity' ? 'Antigravity' :
|
|
2208
|
-
sess.assistant === 'claude' ? 'Claude Code' :
|
|
2209
|
-
sess.assistant === 'codex' ? 'OpenAI Codex' : 'GitHub Copilot'}
|
|
2210
|
-
</span>
|
|
2211
|
-
<span className="text-[10px] text-gray-500">
|
|
2212
|
-
Updated: {new Date(sess.updatedAt).toLocaleString()}
|
|
2213
|
-
</span>
|
|
2214
|
-
<span className="text-[10px] text-gray-500">•</span>
|
|
2215
|
-
<span className="text-[10px] text-gray-550 font-medium">
|
|
2216
|
-
{sess.messageCount} messages
|
|
2217
|
-
</span>
|
|
2218
|
-
</div>
|
|
2219
|
-
<h4 className="text-xs font-semibold text-white truncate pr-4" title={sess.title}>
|
|
2220
|
-
{sess.title}
|
|
2221
|
-
</h4>
|
|
2222
|
-
</div>
|
|
2223
|
-
<div className="flex items-center gap-2 shrink-0">
|
|
2224
|
-
<button
|
|
2225
|
-
className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-indigo-500/10 border border-indigo-500/20 hover:bg-indigo-500/20 text-indigo-300 rounded-lg text-xs font-bold transition-all cursor-pointer"
|
|
2226
|
-
onClick={() => {
|
|
2227
|
-
setActiveSession(sess);
|
|
2228
|
-
setTranscript([]);
|
|
2229
|
-
fetchSessionTranscript(sess.assistant, sess.id);
|
|
2230
|
-
}}
|
|
2231
|
-
>
|
|
2232
|
-
<MessageSquare size={13} /> View Chat Log
|
|
2233
|
-
</button>
|
|
2234
|
-
<button
|
|
2235
|
-
className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-xs font-bold transition-all cursor-pointer"
|
|
2236
|
-
onClick={() => handleResumeSession(ws, sess.id, sess.assistant)}
|
|
2237
|
-
>
|
|
2238
|
-
<Play size={11} /> Resume
|
|
2239
|
-
</button>
|
|
2240
|
-
</div>
|
|
2241
|
-
</div>
|
|
2242
|
-
))}
|
|
2243
|
-
</div>
|
|
2244
|
-
)
|
|
2245
|
-
)}
|
|
2246
|
-
|
|
2247
|
-
{subTab === 'services' && (
|
|
2248
|
-
servicesLoading ? (
|
|
2249
|
-
<div className="flex justify-center py-10">
|
|
2250
|
-
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
2251
|
-
</div>
|
|
2252
|
-
) : (
|
|
2253
|
-
<div>
|
|
2254
|
-
{orchTools.length > 0 && (
|
|
2255
|
-
<div className="flex items-center gap-3 bg-indigo-500/5 border border-indigo-500/20 rounded-xl p-4 mb-4 text-xs text-indigo-300">
|
|
2256
|
-
<AlertTriangle size={16} className="text-indigo-400 shrink-0" />
|
|
2257
|
-
<div>
|
|
2258
|
-
<strong>Orchestration manifest detected:</strong> {orchTools.map((t) => t.tool).join(', ')}. You can start them inside sub-repos.
|
|
2259
|
-
</div>
|
|
2260
|
-
</div>
|
|
2261
|
-
)}
|
|
2262
|
-
|
|
2263
|
-
<div className="flex flex-col gap-2 mb-4">
|
|
2264
|
-
{services.map((svc) => {
|
|
2265
|
-
const isRunning = runningServices.some((rs) => rs.name === svc.name);
|
|
2266
|
-
return (
|
|
2267
|
-
<div key={svc.name} className="flex justify-between items-center bg-gray-950/20 border border-gray-800/60 rounded-xl px-4 py-3 text-xs">
|
|
2268
|
-
<div className="flex items-center gap-3">
|
|
2269
|
-
<span className={`w-2.5 h-2.5 rounded-full ${isRunning ? 'bg-emerald-500 shadow-[0_0_8px_#10b981]' : 'bg-gray-700'}`}></span>
|
|
2270
|
-
<div>
|
|
2271
|
-
<span className="font-bold text-white">{svc.name}</span>
|
|
2272
|
-
<span className="text-[10px] text-gray-500 ml-3">
|
|
2273
|
-
{svc.command} {svc.args.join(' ')} {svc.port ? `• port ${svc.port}` : ''}
|
|
2274
|
-
</span>
|
|
2275
|
-
</div>
|
|
2276
|
-
</div>
|
|
2277
|
-
<span className="text-[9px] px-2 py-0.5 rounded bg-gray-800 text-gray-500 font-bold uppercase">{svc.source}</span>
|
|
2278
|
-
</div>
|
|
2279
|
-
);
|
|
2280
|
-
})}
|
|
2281
|
-
</div>
|
|
2282
|
-
|
|
2283
|
-
<div className="flex gap-2 mb-6">
|
|
2284
|
-
<button
|
|
2285
|
-
className="inline-flex items-center justify-center gap-1.5 px-4 py-2.5 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg text-xs font-semibold transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed shadow-md shadow-emerald-500/10 hover:-translate-y-0.5 active:translate-y-0"
|
|
2286
|
-
onClick={() => handleStartServices(ws.branchName)}
|
|
2287
|
-
disabled={runningServices.length > 0}
|
|
2288
|
-
>
|
|
2289
|
-
<Play size={14} /> Start All Services
|
|
2290
|
-
</button>
|
|
2291
|
-
<button
|
|
2292
|
-
className="inline-flex items-center justify-center gap-1.5 px-4 py-2.5 bg-rose-600 hover:bg-rose-700 text-white rounded-lg text-xs font-semibold transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed shadow-md shadow-rose-600/10 hover:-translate-y-0.5 active:translate-y-0"
|
|
2293
|
-
onClick={() => handleStopServices(ws.branchName)}
|
|
2294
|
-
disabled={runningServices.length === 0}
|
|
2295
|
-
>
|
|
2296
|
-
<Square size={14} /> Stop All
|
|
2297
|
-
</button>
|
|
2298
|
-
</div>
|
|
2299
|
-
|
|
2300
|
-
{/* Interactive Console Screen */}
|
|
2301
|
-
{services.length > 0 && (
|
|
2302
|
-
<div className="border border-gray-800/80 rounded-xl overflow-hidden shadow-2xl bg-gray-950/60">
|
|
2303
|
-
{/* Terminal Window Header */}
|
|
2304
|
-
<div className="flex items-center justify-between bg-gray-900/80 px-4 py-3 border-b border-gray-800/60">
|
|
2305
|
-
<div className="flex items-center gap-2">
|
|
2306
|
-
<div className="flex gap-1.5">
|
|
2307
|
-
<span className="w-3 h-3 rounded-full bg-rose-500/20 border border-rose-500/30"></span>
|
|
2308
|
-
<span className="w-3 h-3 rounded-full bg-amber-500/20 border border-amber-500/30"></span>
|
|
2309
|
-
<span className="w-3 h-3 rounded-full bg-emerald-500/20 border border-emerald-500/30"></span>
|
|
2310
|
-
</div>
|
|
2311
|
-
<span className="text-[11px] font-mono text-gray-400 font-semibold ml-2">nexusflow-terminal: {selectedLogService}</span>
|
|
2312
|
-
</div>
|
|
2313
|
-
<div className="flex gap-1 bg-gray-950/40 p-1 rounded-lg border border-gray-800/60">
|
|
2314
|
-
{services.map((svc) => (
|
|
2315
|
-
<button
|
|
2316
|
-
key={svc.name}
|
|
2317
|
-
className={`px-3 py-1 rounded-md text-[10px] font-mono font-bold transition-all cursor-pointer ${
|
|
2318
|
-
selectedLogService === svc.name
|
|
2319
|
-
? 'bg-slate-800 text-white shadow-sm'
|
|
2320
|
-
: 'text-gray-500 hover:text-gray-300'
|
|
2321
|
-
}`}
|
|
2322
|
-
onClick={() => setSelectedLogService(svc.name)}
|
|
2323
|
-
>
|
|
2324
|
-
{svc.name}
|
|
2325
|
-
</button>
|
|
2326
|
-
))}
|
|
2327
|
-
</div>
|
|
2328
|
-
</div>
|
|
2329
|
-
|
|
2330
|
-
{/* Terminal Screen Body */}
|
|
2331
|
-
<div className="bg-[#030408] p-5 font-mono text-[11px] text-cyan-400/90 h-80 overflow-y-auto whitespace-pre-wrap shadow-inner relative scrollbar-thin scrollbar-thumb-gray-800">
|
|
2332
|
-
<div className="absolute top-3 right-3 text-[9px] bg-slate-900/60 border border-gray-800 text-cyan-500/60 px-2 py-0.5 rounded font-mono select-none uppercase tracking-wider">
|
|
2333
|
-
LIVE LOGS
|
|
2334
|
-
</div>
|
|
2335
|
-
{serviceLogs}
|
|
2336
|
-
<div ref={logsEndRef}></div>
|
|
2337
|
-
</div>
|
|
2338
|
-
</div>
|
|
2339
|
-
)}
|
|
2340
|
-
</div>
|
|
2341
|
-
)
|
|
2342
|
-
)}
|
|
2343
|
-
|
|
2344
|
-
{/* Active Changes View */}
|
|
2345
|
-
{subTab === 'changes' && (
|
|
2346
|
-
<div>
|
|
2347
|
-
<header className="flex justify-between items-center mb-4 flex-wrap gap-2">
|
|
2348
|
-
<h4 className="text-sm font-bold text-white flex items-center gap-2">
|
|
2349
|
-
<FolderGit2 size={16} className="text-cyan-400" /> Active Workspace Git Diffs
|
|
2350
|
-
</h4>
|
|
2351
|
-
<div className="flex gap-2">
|
|
2352
|
-
<button
|
|
2353
|
-
className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-300"
|
|
2354
|
-
onClick={() => fetchGitChanges(ws.branchName)}
|
|
2355
|
-
disabled={gitChangesLoading}
|
|
2356
|
-
>
|
|
2357
|
-
<RefreshCw size={11} className={gitChangesLoading ? 'animate-spin text-indigo-400' : ''} /> Refresh Changes
|
|
2358
|
-
</button>
|
|
2359
|
-
<button
|
|
2360
|
-
className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-[10px] font-semibold transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
2361
|
-
onClick={() => handleSyncAll(ws.branchName)}
|
|
2362
|
-
disabled={syncLoading}
|
|
2363
|
-
>
|
|
2364
|
-
{syncLoading ? <RefreshCw size={11} className="animate-spin text-indigo-300" /> : <RefreshCw size={11} />} Sync All
|
|
2365
|
-
</button>
|
|
2366
|
-
<button
|
|
2367
|
-
className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg text-[10px] font-semibold transition-all cursor-pointer disabled:opacity-40 disabled:cursor-not-allowed"
|
|
2368
|
-
onClick={() => setShowCommitModal(true)}
|
|
2369
|
-
disabled={gitChanges.every(repo => repo.files.length === 0) || commitLoading}
|
|
2370
|
-
>
|
|
2371
|
-
Commit & Push All
|
|
2372
|
-
</button>
|
|
2373
|
-
</div>
|
|
2374
|
-
</header>
|
|
2375
|
-
|
|
2376
|
-
{syncResults && (
|
|
2377
|
-
<div className="bg-[#090d1a]/60 border border-gray-800 rounded-xl p-4 mb-4 text-xs">
|
|
2378
|
-
<div className="flex justify-between items-center mb-2 border-b border-gray-800/80 pb-2">
|
|
2379
|
-
<h5 className="font-bold text-white font-mono text-[11px]">Rebase/Sync Results</h5>
|
|
2380
|
-
<button className="text-gray-500 hover:text-gray-300 text-[10px] cursor-pointer" onClick={() => setSyncResults(null)}>Dismiss</button>
|
|
2381
|
-
</div>
|
|
2382
|
-
<div className="space-y-1.5">
|
|
2383
|
-
{syncResults.map((r: any) => (
|
|
2384
|
-
<div key={r.repoName} className="flex justify-between items-center font-mono text-[10px]">
|
|
2385
|
-
<span className="text-gray-300">{r.repoName}</span>
|
|
2386
|
-
<span className={r.success ? "text-emerald-400" : "text-rose-400"}>
|
|
2387
|
-
{r.success ? `✅ Synced (${r.message})` : `⚠️ Conflict: ${r.message}`}
|
|
2388
|
-
</span>
|
|
2389
|
-
</div>
|
|
2390
|
-
))}
|
|
2391
|
-
</div>
|
|
2392
|
-
</div>
|
|
2393
|
-
)}
|
|
2394
|
-
|
|
2395
|
-
{commitResults && (
|
|
2396
|
-
<div className="bg-[#090d1a]/60 border border-gray-800 rounded-xl p-4 mb-4 text-xs">
|
|
2397
|
-
<div className="flex justify-between items-center mb-2 border-b border-gray-800/80 pb-2">
|
|
2398
|
-
<h5 className="font-bold text-white font-mono text-[11px]">Commit/Push Results</h5>
|
|
2399
|
-
<button className="text-gray-500 hover:text-gray-300 text-[10px] cursor-pointer" onClick={() => setCommitResults(null)}>Dismiss</button>
|
|
2400
|
-
</div>
|
|
2401
|
-
<div className="space-y-1.5">
|
|
2402
|
-
{commitResults.map((r: any) => (
|
|
2403
|
-
<div key={r.repoName} className="flex justify-between items-center font-mono text-[10px]">
|
|
2404
|
-
<span className="text-gray-300">{r.repoName}</span>
|
|
2405
|
-
<span className={r.success ? "text-emerald-400" : "text-rose-400"}>
|
|
2406
|
-
{r.success ? `✅ Committed ${r.filesChanged} file(s) (${r.commitHash || 'no hash'})` : `⚠️ Error: ${r.message}`}
|
|
2407
|
-
</span>
|
|
2408
|
-
</div>
|
|
2409
|
-
))}
|
|
2410
|
-
</div>
|
|
2411
|
-
</div>
|
|
2412
|
-
)}
|
|
2413
|
-
|
|
2414
|
-
{showCommitModal && (
|
|
2415
|
-
<div className="bg-[#0b0f19] border border-gray-800 rounded-xl p-4 mb-4">
|
|
2416
|
-
<h5 className="text-xs font-bold text-white mb-2">Enter Commit Message</h5>
|
|
2417
|
-
<input
|
|
2418
|
-
type="text"
|
|
2419
|
-
className="w-full bg-gray-950 border border-gray-800 rounded-lg px-3 py-2 text-xs font-mono text-gray-350 focus:outline-none focus:border-indigo-500 mb-3"
|
|
2420
|
-
placeholder="feat: implement logic..."
|
|
2421
|
-
value={commitMessage}
|
|
2422
|
-
onChange={(e) => setCommitMessage(e.target.value)}
|
|
2423
|
-
onKeyDown={(e) => {
|
|
2424
|
-
if (e.key === 'Enter') handleCommitAll(ws.branchName);
|
|
2425
|
-
}}
|
|
2426
|
-
/>
|
|
2427
|
-
<div className="flex justify-end gap-2">
|
|
2428
|
-
<button
|
|
2429
|
-
className="px-3 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-850 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-400"
|
|
2430
|
-
onClick={() => {
|
|
2431
|
-
setShowCommitModal(false);
|
|
2432
|
-
setCommitMessage('');
|
|
2433
|
-
}}
|
|
2434
|
-
disabled={commitLoading}
|
|
2435
|
-
>
|
|
2436
|
-
Cancel
|
|
2437
|
-
</button>
|
|
2438
|
-
<button
|
|
2439
|
-
className="px-3 py-1.5 bg-emerald-600 hover:bg-emerald-500 rounded-lg text-[10px] font-semibold text-white transition-all cursor-pointer"
|
|
2440
|
-
onClick={() => handleCommitAll(ws.branchName)}
|
|
2441
|
-
disabled={commitLoading || !commitMessage.trim()}
|
|
2442
|
-
>
|
|
2443
|
-
{commitLoading ? 'Committing...' : 'Commit & Push'}
|
|
2444
|
-
</button>
|
|
2445
|
-
</div>
|
|
2446
|
-
</div>
|
|
2447
|
-
)}
|
|
2448
|
-
|
|
2449
|
-
{gitChangesLoading ? (
|
|
2450
|
-
<div className="flex justify-center py-10">
|
|
2451
|
-
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
2452
|
-
</div>
|
|
2453
|
-
) : (
|
|
2454
|
-
<div className="space-y-4">
|
|
2455
|
-
{gitChanges.every(repo => repo.files.length === 0) ? (
|
|
2456
|
-
<div className="flex flex-col items-center justify-center py-12 bg-gray-950/20 border border-gray-800/40 rounded-xl text-center">
|
|
2457
|
-
<Check size={28} className="text-emerald-500 mb-2" />
|
|
2458
|
-
<h5 className="text-xs font-bold text-white">No Uncommitted Changes</h5>
|
|
2459
|
-
<p className="text-[10px] text-gray-500 mt-0.5">Workspace is completely synced with Git feature branches.</p>
|
|
2460
|
-
</div>
|
|
2461
|
-
) : (
|
|
2462
|
-
gitChanges.map((repo) => {
|
|
2463
|
-
if (repo.files.length === 0) return null;
|
|
2464
|
-
const totalFilesChanged = repo.files.length;
|
|
2465
|
-
const repoAdditions = repo.files.reduce((acc: number, f: any) => acc + (f.additions || 0), 0);
|
|
2466
|
-
const repoDeletions = repo.files.reduce((acc: number, f: any) => acc + (f.deletions || 0), 0);
|
|
2467
|
-
|
|
2468
|
-
return (
|
|
2469
|
-
<div key={repo.repoName} className="bg-gray-950/20 border border-gray-800/60 rounded-xl p-4">
|
|
2470
|
-
<div className="flex justify-between items-center mb-3">
|
|
2471
|
-
<div className="flex items-center gap-2">
|
|
2472
|
-
<h5 className="text-xs font-bold text-white font-mono">{repo.repoName}</h5>
|
|
2473
|
-
<span className="text-[9px] px-1.5 py-0.5 rounded bg-gray-900 border border-gray-800 text-gray-400 font-mono">
|
|
2474
|
-
{totalFilesChanged} file{totalFilesChanged === 1 ? '' : 's'} changed
|
|
2475
|
-
</span>
|
|
2476
|
-
{(repoAdditions > 0 || repoDeletions > 0) && (
|
|
2477
|
-
<span className="text-[9px] font-mono">
|
|
2478
|
-
<span className="text-emerald-400 font-bold">+{repoAdditions}</span>{' '}
|
|
2479
|
-
<span className="text-rose-400 font-bold">-{repoDeletions}</span>
|
|
2480
|
-
</span>
|
|
2481
|
-
)}
|
|
2482
|
-
</div>
|
|
2483
|
-
<span className="text-[9px] text-gray-500 font-mono truncate max-w-[280px]">{repo.repoPath}</span>
|
|
2484
|
-
</div>
|
|
2485
|
-
<div className="space-y-1.5">
|
|
2486
|
-
{repo.files.map((fileInfo: any) => (
|
|
2487
|
-
<div key={fileInfo.file} className="flex justify-between items-center bg-[#090d1a]/40 px-3 py-2 rounded-lg border border-gray-800/30 text-xs">
|
|
2488
|
-
<div className="flex flex-col min-w-0">
|
|
2489
|
-
<span className="font-mono text-gray-300 text-[11px] truncate max-w-[320px]">{fileInfo.file}</span>
|
|
2490
|
-
{(fileInfo.additions > 0 || fileInfo.deletions > 0) && (
|
|
2491
|
-
<span className="text-[9px] font-mono mt-0.5">
|
|
2492
|
-
<span className="text-emerald-500">+{fileInfo.additions}</span>{' '}
|
|
2493
|
-
<span className="text-rose-500">-{fileInfo.deletions}</span>
|
|
2494
|
-
</span>
|
|
2495
|
-
)}
|
|
2496
|
-
</div>
|
|
2497
|
-
<span className={`text-[9px] px-2 py-0.5 rounded font-bold uppercase ${
|
|
2498
|
-
fileInfo.type === 'added' ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' :
|
|
2499
|
-
fileInfo.type === 'deleted' ? 'bg-rose-500/10 text-rose-400 border border-rose-500/20' :
|
|
2500
|
-
'bg-amber-500/10 text-amber-400 border border-amber-500/20'
|
|
2501
|
-
}`}>
|
|
2502
|
-
{fileInfo.type}
|
|
2503
|
-
</span>
|
|
2504
|
-
</div>
|
|
2505
|
-
))}
|
|
2506
|
-
</div>
|
|
2507
|
-
</div>
|
|
2508
|
-
);
|
|
2509
|
-
})
|
|
2510
|
-
)}
|
|
2511
|
-
</div>
|
|
2512
|
-
)}
|
|
2513
|
-
</div>
|
|
2514
|
-
)}
|
|
2515
|
-
|
|
2516
|
-
{/* Knowledge Base View */}
|
|
2517
|
-
{subTab === 'knowledge' && (
|
|
2518
|
-
<div className="bg-[#090d1a]/20 border border-gray-800/60 rounded-xl p-4">
|
|
2519
|
-
<header className="flex justify-between items-center mb-4">
|
|
2520
|
-
<h4 className="text-sm font-bold text-white flex items-center gap-2">
|
|
2521
|
-
<BookOpen size={16} className="text-cyan-400" /> Persistent Knowledge Memory (nexusflow-knowledge.md)
|
|
2522
|
-
</h4>
|
|
2523
|
-
<div className="flex gap-2">
|
|
2524
|
-
{isEditingKnowledge ? (
|
|
2525
|
-
<>
|
|
2526
|
-
<button
|
|
2527
|
-
className="px-2.5 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-850 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-400"
|
|
2528
|
-
onClick={() => {
|
|
2529
|
-
setEditedKnowledge(knowledgeContent);
|
|
2530
|
-
setIsEditingKnowledge(false);
|
|
2531
|
-
}}
|
|
2532
|
-
disabled={saveKnowledgeLoading}
|
|
2533
|
-
>
|
|
2534
|
-
Cancel
|
|
2535
|
-
</button>
|
|
2536
|
-
<button
|
|
2537
|
-
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-[10px] font-semibold transition-all cursor-pointer"
|
|
2538
|
-
onClick={() => handleSaveKnowledge(ws.branchName)}
|
|
2539
|
-
disabled={saveKnowledgeLoading}
|
|
2540
|
-
>
|
|
2541
|
-
{saveKnowledgeLoading ? <RefreshCw className="animate-spin" size={10} /> : <Save size={10} />} Save
|
|
2542
|
-
</button>
|
|
2543
|
-
</>
|
|
2544
|
-
) : (
|
|
2545
|
-
<button
|
|
2546
|
-
className="inline-flex items-center gap-1.5 px-2.5 py-1.5 bg-gray-900 border border-gray-800 hover:bg-gray-800 hover:border-gray-700 rounded-lg text-[10px] font-semibold transition-all cursor-pointer text-gray-350"
|
|
2547
|
-
onClick={() => setIsEditingKnowledge(true)}
|
|
2548
|
-
disabled={knowledgeLoading}
|
|
2549
|
-
>
|
|
2550
|
-
<Edit size={10} /> Edit Knowledge
|
|
2551
|
-
</button>
|
|
2552
|
-
)}
|
|
2553
|
-
</div>
|
|
2554
|
-
</header>
|
|
2555
|
-
|
|
2556
|
-
{knowledgeLoading ? (
|
|
2557
|
-
<div className="flex justify-center py-10">
|
|
2558
|
-
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
2559
|
-
</div>
|
|
2560
|
-
) : isEditingKnowledge ? (
|
|
2561
|
-
<textarea
|
|
2562
|
-
className="w-full h-96 bg-gray-950/60 border border-gray-800/80 rounded-xl p-4 text-xs font-mono text-gray-305 focus:outline-none focus:border-indigo-500/80 resize-y"
|
|
2563
|
-
value={editedKnowledge}
|
|
2564
|
-
onChange={(e) => setEditedKnowledge(e.target.value)}
|
|
2565
|
-
/>
|
|
2566
|
-
) : (
|
|
2567
|
-
<div className="bg-gray-950/40 border border-gray-800/30 rounded-xl p-4 text-gray-350 text-xs leading-relaxed overflow-auto font-mono whitespace-pre-wrap max-h-96">
|
|
2568
|
-
{knowledgeContent || "No knowledge file generated yet."}
|
|
2569
|
-
</div>
|
|
2570
|
-
)}
|
|
2571
|
-
</div>
|
|
2572
|
-
)}
|
|
2573
|
-
|
|
2574
|
-
{/* Implementation Plan View */}
|
|
2575
|
-
{subTab === 'plan' && (
|
|
2576
|
-
<div className="bg-[#090d1a]/20 border border-gray-800/60 rounded-xl p-4">
|
|
2577
|
-
<header className="flex justify-between items-center mb-4">
|
|
2578
|
-
<h4 className="text-sm font-bold text-white flex items-center gap-2">
|
|
2579
|
-
<ListOrdered size={16} className="text-cyan-400" /> Inter-Repo Implementation Plan (nexusflow-plan.md)
|
|
2580
|
-
</h4>
|
|
2581
|
-
</header>
|
|
2582
|
-
|
|
2583
|
-
{planLoading ? (
|
|
2584
|
-
<div className="flex justify-center py-10">
|
|
2585
|
-
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
2586
|
-
</div>
|
|
2587
|
-
) : (
|
|
2588
|
-
<div className="bg-gray-950/40 border border-gray-800/30 rounded-xl p-4 text-gray-350 text-xs leading-relaxed overflow-auto font-mono whitespace-pre-wrap max-h-96">
|
|
2589
|
-
{planContent || "No implementation plan generated yet."}
|
|
2590
|
-
</div>
|
|
2591
|
-
)}
|
|
2592
|
-
</div>
|
|
2593
|
-
)}
|
|
2594
|
-
</div>
|
|
2595
|
-
)}
|
|
2596
|
-
</div>
|
|
2597
|
-
);
|
|
2598
|
-
})}
|
|
2599
|
-
</div>
|
|
2600
|
-
)}
|
|
2601
|
-
</div>
|
|
1968
|
+
<WorkspaceList
|
|
1969
|
+
workspacesLoading={workspacesLoading}
|
|
1970
|
+
workspaces={workspaces}
|
|
1971
|
+
activeWsId={activeWsId}
|
|
1972
|
+
setActiveWsId={setActiveWsId}
|
|
1973
|
+
resumingWs={resumingWs}
|
|
1974
|
+
handleResumeSession={handleResumeSession}
|
|
1975
|
+
handleCopyPrompt={handleCopyPrompt}
|
|
1976
|
+
handleOpenInEditor={handleOpenInEditor}
|
|
1977
|
+
fetchWorkspaces={fetchWorkspaces}
|
|
1978
|
+
repos={repos}
|
|
1979
|
+
deleteWsLoading={deleteWsLoading}
|
|
1980
|
+
addRepoLoading={addRepoLoading}
|
|
1981
|
+
handleDeleteWorkspace={handleDeleteWorkspace}
|
|
1982
|
+
handleAddRepo={handleAddRepo}
|
|
1983
|
+
services={services}
|
|
1984
|
+
runningServices={runningServices}
|
|
1985
|
+
selectedLogService={selectedLogService}
|
|
1986
|
+
serviceLogs={serviceLogs}
|
|
1987
|
+
logsEndRef={logsEndRef}
|
|
1988
|
+
setSelectedLogService={setSelectedLogService}
|
|
1989
|
+
handleStartServices={handleStartServices}
|
|
1990
|
+
handleStopServices={handleStopServices}
|
|
1991
|
+
orchTools={orchTools}
|
|
1992
|
+
servicesLoading={servicesLoading}
|
|
1993
|
+
gitChanges={gitChanges}
|
|
1994
|
+
gitChangesLoading={gitChangesLoading}
|
|
1995
|
+
syncLoading={syncLoading}
|
|
1996
|
+
syncResults={syncResults}
|
|
1997
|
+
commitMessage={commitMessage}
|
|
1998
|
+
showCommitModal={showCommitModal}
|
|
1999
|
+
commitLoading={commitLoading}
|
|
2000
|
+
commitResults={commitResults}
|
|
2001
|
+
setSyncResults={setSyncResults}
|
|
2002
|
+
setCommitResults={setCommitResults}
|
|
2003
|
+
setCommitMessage={setCommitMessage}
|
|
2004
|
+
setShowCommitModal={setShowCommitModal}
|
|
2005
|
+
fetchGitChanges={fetchGitChanges}
|
|
2006
|
+
handleSyncAll={handleSyncAll}
|
|
2007
|
+
handleCommitAll={handleCommitAll}
|
|
2008
|
+
knowledgeContent={knowledgeContent}
|
|
2009
|
+
knowledgeLoading={knowledgeLoading}
|
|
2010
|
+
isEditingKnowledge={isEditingKnowledge}
|
|
2011
|
+
editedKnowledge={editedKnowledge}
|
|
2012
|
+
saveKnowledgeLoading={saveKnowledgeLoading}
|
|
2013
|
+
setEditedKnowledge={setEditedKnowledge}
|
|
2014
|
+
setIsEditingKnowledge={setIsEditingKnowledge}
|
|
2015
|
+
handleSaveKnowledge={handleSaveKnowledge}
|
|
2016
|
+
planContent={planContent}
|
|
2017
|
+
planLoading={planLoading}
|
|
2018
|
+
sessions={sessions}
|
|
2019
|
+
sessionsLoading={sessionsLoading}
|
|
2020
|
+
activeSession={activeSession}
|
|
2021
|
+
transcript={transcript}
|
|
2022
|
+
transcriptLoading={transcriptLoading}
|
|
2023
|
+
setActiveSession={setActiveSession}
|
|
2024
|
+
setTranscript={setTranscript}
|
|
2025
|
+
fetchSessionTranscript={fetchSessionTranscript}
|
|
2026
|
+
subTab={subTab}
|
|
2027
|
+
setSubTab={setSubTab}
|
|
2028
|
+
/>
|
|
2602
2029
|
)}
|
|
2603
2030
|
|
|
2604
2031
|
{/* View 3: Settings View */}
|
|
@@ -2689,11 +2116,145 @@ Core Instructions:
|
|
|
2689
2116
|
</select>
|
|
2690
2117
|
<span className="text-[10px] text-gray-500 mt-1">Your preferred code editor for opening workspaces.</span>
|
|
2691
2118
|
</div>
|
|
2119
|
+
|
|
2120
|
+
<div className="flex flex-col md:col-span-2 border-t border-gray-800/60 pt-6 mt-2">
|
|
2121
|
+
<h3 className="text-sm font-bold text-white mb-2">Codebase Context Settings</h3>
|
|
2122
|
+
<div className="flex items-center gap-3">
|
|
2123
|
+
<input
|
|
2124
|
+
type="checkbox"
|
|
2125
|
+
id="packContextXml"
|
|
2126
|
+
className="w-4 h-4 rounded border-gray-800 bg-[#111827] text-indigo-600 focus:ring-indigo-500 cursor-pointer"
|
|
2127
|
+
checked={config.packContextXml || false}
|
|
2128
|
+
onChange={(e) => setConfig({ ...config, packContextXml: e.target.checked })}
|
|
2129
|
+
/>
|
|
2130
|
+
<label htmlFor="packContextXml" className="text-xs font-semibold text-white cursor-pointer select-none">
|
|
2131
|
+
Pack Codebase Context with Repomix (XML)
|
|
2132
|
+
</label>
|
|
2133
|
+
</div>
|
|
2134
|
+
<span className="text-[10px] text-gray-500 mt-1">
|
|
2135
|
+
Aggregates files of all repositories in a workspace into a single token-efficient XML file (<code>nexusflow-context.xml</code>) at the workspace root, giving AI assistants immediate access to the full codebase state.
|
|
2136
|
+
</span>
|
|
2137
|
+
</div>
|
|
2138
|
+
</div>
|
|
2139
|
+
|
|
2140
|
+
{/* Local AI Co-Processor Settings */}
|
|
2141
|
+
<div className="bg-[#111827]/40 border border-gray-800/80 rounded-xl p-8 shadow-xl backdrop-blur-sm mb-6 mt-6">
|
|
2142
|
+
<h3 className="text-lg font-bold text-white mb-2">Local AI Co-Processor Settings</h3>
|
|
2143
|
+
<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>
|
|
2144
|
+
|
|
2145
|
+
{recommendation && (
|
|
2146
|
+
<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">
|
|
2147
|
+
<div className="flex items-center gap-2 text-indigo-400 font-bold">
|
|
2148
|
+
<Cpu size={14} /> Local System Scan Results
|
|
2149
|
+
</div>
|
|
2150
|
+
<div>• Detected Memory: <strong>{recommendation.totalRamGb} GB RAM</strong></div>
|
|
2151
|
+
<div>• Detected GPU: <strong>{recommendation.gpuName}</strong></div>
|
|
2152
|
+
<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>
|
|
2153
|
+
</div>
|
|
2154
|
+
)}
|
|
2155
|
+
|
|
2156
|
+
<div className="flex items-center gap-3 mb-6">
|
|
2157
|
+
<input
|
|
2158
|
+
type="checkbox"
|
|
2159
|
+
id="localLlmEnabled"
|
|
2160
|
+
className="w-4 h-4 rounded border-gray-800 bg-[#111827] text-indigo-600 focus:ring-indigo-500 cursor-pointer"
|
|
2161
|
+
checked={config.localLlm?.enabled || false}
|
|
2162
|
+
onChange={(e) => {
|
|
2163
|
+
const defaultLlm = { enabled: e.target.checked, provider: 'ollama' as const, endpoint: 'http://localhost:11434', model: recommendation?.recommendedModel || 'qwen2.5-coder:1.5b' };
|
|
2164
|
+
setConfig({
|
|
2165
|
+
...config,
|
|
2166
|
+
localLlm: config.localLlm ? { ...config.localLlm, enabled: e.target.checked } : defaultLlm
|
|
2167
|
+
});
|
|
2168
|
+
}}
|
|
2169
|
+
/>
|
|
2170
|
+
<label htmlFor="localLlmEnabled" className="text-sm font-semibold text-white cursor-pointer select-none">
|
|
2171
|
+
Enable Local AI Delegation Tool
|
|
2172
|
+
</label>
|
|
2173
|
+
</div>
|
|
2174
|
+
|
|
2175
|
+
{config.localLlm?.enabled && (
|
|
2176
|
+
<div className="space-y-6 border-t border-gray-800/60 pt-6">
|
|
2177
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
2178
|
+
<div className="flex flex-col">
|
|
2179
|
+
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Local Provider</label>
|
|
2180
|
+
<select
|
|
2181
|
+
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"
|
|
2182
|
+
value={config.localLlm.provider}
|
|
2183
|
+
onChange={(e) => setConfig({
|
|
2184
|
+
...config,
|
|
2185
|
+
localLlm: { ...config.localLlm!, provider: e.target.value as any }
|
|
2186
|
+
})}
|
|
2187
|
+
>
|
|
2188
|
+
<option value="ollama">Ollama</option>
|
|
2189
|
+
<option value="openai-compatible">OpenAI-Compatible (e.g. LM Studio)</option>
|
|
2190
|
+
</select>
|
|
2191
|
+
<span className="text-[10px] text-gray-500 mt-1">Provider protocol to connect to.</span>
|
|
2192
|
+
</div>
|
|
2193
|
+
|
|
2194
|
+
<div className="flex flex-col">
|
|
2195
|
+
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Endpoint URL</label>
|
|
2196
|
+
<input
|
|
2197
|
+
type="text"
|
|
2198
|
+
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 ${
|
|
2199
|
+
config.localLlm.endpoint && !config.localLlm.endpoint.trim().startsWith('http://') && !config.localLlm.endpoint.trim().startsWith('https://')
|
|
2200
|
+
? 'border-rose-500/60 focus:border-rose-500 focus:ring-rose-500'
|
|
2201
|
+
: 'border-gray-800 focus:border-indigo-500 focus:ring-indigo-500'
|
|
2202
|
+
}`}
|
|
2203
|
+
value={config.localLlm.endpoint}
|
|
2204
|
+
onChange={(e) => setConfig({
|
|
2205
|
+
...config,
|
|
2206
|
+
localLlm: { ...config.localLlm!, endpoint: e.target.value }
|
|
2207
|
+
})}
|
|
2208
|
+
/>
|
|
2209
|
+
{config.localLlm.endpoint && !config.localLlm.endpoint.trim().startsWith('http://') && !config.localLlm.endpoint.trim().startsWith('https://') && (
|
|
2210
|
+
<span className="text-[10px] text-rose-400 mt-1">Endpoint must start with http:// or https://</span>
|
|
2211
|
+
)}
|
|
2212
|
+
<span className="text-[10px] text-gray-500 mt-1">API base address of the local runner.</span>
|
|
2213
|
+
</div>
|
|
2214
|
+
|
|
2215
|
+
<div className="flex flex-col md:col-span-2">
|
|
2216
|
+
<label className="block text-xs font-semibold text-gray-400 uppercase tracking-wider mb-2">Model Name</label>
|
|
2217
|
+
<input
|
|
2218
|
+
type="text"
|
|
2219
|
+
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"
|
|
2220
|
+
value={config.localLlm.model}
|
|
2221
|
+
onChange={(e) => setConfig({
|
|
2222
|
+
...config,
|
|
2223
|
+
localLlm: { ...config.localLlm!, model: e.target.value }
|
|
2224
|
+
})}
|
|
2225
|
+
/>
|
|
2226
|
+
<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>
|
|
2227
|
+
</div>
|
|
2228
|
+
</div>
|
|
2229
|
+
|
|
2230
|
+
<div className="flex items-center justify-between pt-4 border-t border-gray-800/40">
|
|
2231
|
+
<button
|
|
2232
|
+
type="button"
|
|
2233
|
+
disabled={testingLlm}
|
|
2234
|
+
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"
|
|
2235
|
+
onClick={testLlmConnection}
|
|
2236
|
+
>
|
|
2237
|
+
{testingLlm ? 'Testing...' : 'Test Connection'}
|
|
2238
|
+
</button>
|
|
2239
|
+
|
|
2240
|
+
{testStatus && (
|
|
2241
|
+
<div className={`text-xs px-3 py-1.5 rounded-lg border ${
|
|
2242
|
+
testStatus.success
|
|
2243
|
+
? 'bg-emerald-500/10 border-emerald-500/20 text-emerald-400'
|
|
2244
|
+
: 'bg-rose-500/10 border-rose-500/20 text-rose-400'
|
|
2245
|
+
}`}>
|
|
2246
|
+
{testStatus.message}
|
|
2247
|
+
</div>
|
|
2248
|
+
)}
|
|
2249
|
+
</div>
|
|
2250
|
+
</div>
|
|
2251
|
+
)}
|
|
2692
2252
|
</div>
|
|
2693
2253
|
|
|
2694
2254
|
<div className="flex justify-end">
|
|
2695
2255
|
<button
|
|
2696
|
-
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"
|
|
2256
|
+
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"
|
|
2257
|
+
disabled={!isSettingsFormValid}
|
|
2697
2258
|
onClick={() => saveAppConfig(config)}
|
|
2698
2259
|
>
|
|
2699
2260
|
Save Configuration
|
|
@@ -2883,7 +2444,7 @@ Core Instructions:
|
|
|
2883
2444
|
activeSession.assistant === 'codex' ? `codex resume ${activeSession.id}` :
|
|
2884
2445
|
`copilot --resume ${activeSession.id}`;
|
|
2885
2446
|
navigator.clipboard.writeText(cmd);
|
|
2886
|
-
|
|
2447
|
+
showToast(`Copied run command to clipboard:\n\n${cmd}`, 'success');
|
|
2887
2448
|
}}
|
|
2888
2449
|
>
|
|
2889
2450
|
<Copy size={13} /> Copy Resume Command
|
|
@@ -2903,6 +2464,35 @@ Core Instructions:
|
|
|
2903
2464
|
</div>
|
|
2904
2465
|
</div>
|
|
2905
2466
|
)}
|
|
2467
|
+
|
|
2468
|
+
{/* Toast Notifications Container */}
|
|
2469
|
+
<div className="fixed bottom-5 right-5 z-[9999] flex flex-col gap-3 pointer-events-none max-w-md w-full">
|
|
2470
|
+
{toasts.map((toast) => (
|
|
2471
|
+
<div
|
|
2472
|
+
key={toast.id}
|
|
2473
|
+
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 ${
|
|
2474
|
+
toast.type === 'success'
|
|
2475
|
+
? 'bg-[#062c1b]/95 border-emerald-800/80 text-emerald-100'
|
|
2476
|
+
: toast.type === 'error'
|
|
2477
|
+
? 'bg-[#2c0e0e]/95 border-red-900/80 text-red-100'
|
|
2478
|
+
: 'bg-[#131926]/95 border-slate-800/80 text-slate-100'
|
|
2479
|
+
}`}
|
|
2480
|
+
>
|
|
2481
|
+
<div className="flex items-start gap-2.5 text-xs font-semibold flex-1">
|
|
2482
|
+
{toast.type === 'success' && <Check className="text-emerald-400 shrink-0 mt-0.5" size={16} />}
|
|
2483
|
+
{toast.type === 'error' && <AlertCircle className="text-red-400 shrink-0 mt-0.5" size={16} />}
|
|
2484
|
+
{toast.type === 'info' && <Sparkles className="text-indigo-400 shrink-0 mt-0.5" size={16} />}
|
|
2485
|
+
<span className="whitespace-pre-line text-left leading-relaxed">{toast.message}</span>
|
|
2486
|
+
</div>
|
|
2487
|
+
<button
|
|
2488
|
+
onClick={() => setToasts((prev) => prev.filter((t) => t.id !== toast.id))}
|
|
2489
|
+
className="text-slate-400 hover:text-white transition-colors cursor-pointer shrink-0 mt-0.5"
|
|
2490
|
+
>
|
|
2491
|
+
<X size={14} />
|
|
2492
|
+
</button>
|
|
2493
|
+
</div>
|
|
2494
|
+
))}
|
|
2495
|
+
</div>
|
|
2906
2496
|
</div>
|
|
2907
2497
|
);
|
|
2908
2498
|
}
|