@mrpatronz/nexusflow 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/dependabot.yml +25 -0
- package/.github/workflows/release.yml +90 -0
- package/.vscode/launch.json +17 -0
- package/.vscode/tasks.json +17 -0
- package/README.md +1 -1
- package/dist/commands/commit.d.ts +18 -0
- package/dist/commands/commit.d.ts.map +1 -0
- package/dist/commands/commit.js +105 -0
- package/dist/commands/commit.js.map +1 -0
- package/dist/commands/diff.d.ts +11 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +101 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/sync.d.ts +11 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +96 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/generators/base.d.ts.map +1 -1
- package/dist/generators/base.js +3 -0
- package/dist/generators/base.js.map +1 -1
- package/dist/generators/index.d.ts +5 -2
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +88 -38
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/plan-generator.d.ts +40 -0
- package/dist/generators/plan-generator.d.ts.map +1 -0
- package/dist/generators/plan-generator.js +312 -0
- package/dist/generators/plan-generator.js.map +1 -0
- package/dist/gui/assets/index-B9Ph2bHH.css +2 -0
- package/dist/gui/assets/index-s4nRkpqY.js +21 -0
- package/dist/gui/index.html +2 -2
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +141 -1
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/multi-git.d.ts +108 -0
- package/dist/utils/multi-git.d.ts.map +1 -0
- package/dist/utils/multi-git.js +206 -0
- package/dist/utils/multi-git.js.map +1 -0
- package/extension/package-lock.json +2811 -0
- package/extension/package.json +61 -0
- package/extension/src/extension.ts +168 -0
- package/extension/tsconfig.json +19 -0
- package/gui/src/App.tsx +377 -24
- package/gui/src/features/changes/ChangesViewer.tsx +212 -0
- package/gui/src/features/knowledge/KnowledgeBase.tsx +84 -0
- package/gui/src/features/onboarding/OnboardingWizard.tsx +230 -0
- package/gui/src/features/plan/ImplementationPlan.tsx +32 -0
- package/gui/src/features/services/ServiceConsole.tsx +159 -0
- package/gui/src/features/sessions/SessionHistory.tsx +195 -0
- package/gui/src/features/workspace/WorkspaceBuilder.tsx +414 -0
- package/gui/src/features/workspace/WorkspaceList.tsx +382 -0
- package/gui/src/types.ts +61 -0
- package/package.json +6 -4
- package/src/commands/commit.ts +131 -0
- package/src/commands/diff.ts +125 -0
- package/src/commands/sync.ts +110 -0
- package/src/generators/base.ts +3 -0
- package/src/generators/index.ts +95 -40
- package/src/generators/plan-generator.ts +390 -0
- package/src/index.ts +59 -0
- package/src/server.ts +152 -1
- package/src/types.ts +17 -0
- package/src/utils/multi-git.ts +306 -0
- package/dist/gui/assets/index-Cq9V485S.js +0 -21
- package/dist/gui/assets/index-Dh1b5gSZ.css +0 -2
package/gui/src/App.tsx
CHANGED
|
@@ -19,9 +19,14 @@ import {
|
|
|
19
19
|
X,
|
|
20
20
|
MessageSquare,
|
|
21
21
|
MessageSquareCode,
|
|
22
|
+
BookOpen,
|
|
23
|
+
ListOrdered,
|
|
24
|
+
Save,
|
|
25
|
+
Edit,
|
|
22
26
|
} from 'lucide-react';
|
|
23
27
|
import './App.css';
|
|
24
28
|
|
|
29
|
+
|
|
25
30
|
// Types matched with src/types.ts
|
|
26
31
|
interface NexusFlowConfig {
|
|
27
32
|
version: string;
|
|
@@ -84,6 +89,7 @@ interface RunningService {
|
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
const API_BASE = import.meta.env.DEV ? 'http://localhost:3000' : '';
|
|
92
|
+
const isVsCode = new URLSearchParams(window.location.search).get('env') === 'vscode';
|
|
87
93
|
|
|
88
94
|
export default function App() {
|
|
89
95
|
const [view, setView] = useState<'guide' | 'create' | 'workspaces' | 'settings'>('guide');
|
|
@@ -143,7 +149,7 @@ export default function App() {
|
|
|
143
149
|
const [orchTools, setOrchTools] = useState<OrchestrationDetection[]>([]);
|
|
144
150
|
const [runningServices, setRunningServices] = useState<RunningService[]>([]);
|
|
145
151
|
const [servicesLoading, setServicesLoading] = useState(false);
|
|
146
|
-
const [subTab, setSubTab] = useState<'services' | 'changes' | 'sessions'>('sessions');
|
|
152
|
+
const [subTab, setSubTab] = useState<'services' | 'changes' | 'sessions' | 'knowledge' | 'plan'>('sessions');
|
|
147
153
|
const [sessions, setSessions] = useState<any[]>([]);
|
|
148
154
|
const [sessionsLoading, setSessionsLoading] = useState(false);
|
|
149
155
|
const [activeSession, setActiveSession] = useState<any | null>(null);
|
|
@@ -151,6 +157,20 @@ export default function App() {
|
|
|
151
157
|
const [transcriptLoading, setTranscriptLoading] = useState(false);
|
|
152
158
|
const [gitChanges, setGitChanges] = useState<any[]>([]);
|
|
153
159
|
const [gitChangesLoading, setGitChangesLoading] = useState(false);
|
|
160
|
+
const [knowledgeContent, setKnowledgeContent] = useState<string>('');
|
|
161
|
+
const [knowledgeLoading, setKnowledgeLoading] = useState<boolean>(false);
|
|
162
|
+
const [isEditingKnowledge, setIsEditingKnowledge] = useState<boolean>(false);
|
|
163
|
+
const [editedKnowledge, setEditedKnowledge] = useState<string>('');
|
|
164
|
+
const [saveKnowledgeLoading, setSaveKnowledgeLoading] = useState<boolean>(false);
|
|
165
|
+
const [planContent, setPlanContent] = useState<string>('');
|
|
166
|
+
const [planLoading, setPlanLoading] = useState<boolean>(false);
|
|
167
|
+
const [syncLoading, setSyncLoading] = useState<boolean>(false);
|
|
168
|
+
const [syncResults, setSyncResults] = useState<any[] | null>(null);
|
|
169
|
+
const [commitMessage, setCommitMessage] = useState<string>('');
|
|
170
|
+
const [showCommitModal, setShowCommitModal] = useState<boolean>(false);
|
|
171
|
+
const [commitLoading, setCommitLoading] = useState<boolean>(false);
|
|
172
|
+
const [commitResults, setCommitResults] = useState<any[] | null>(null);
|
|
173
|
+
|
|
154
174
|
|
|
155
175
|
// Log Viewer
|
|
156
176
|
const [selectedLogService, setSelectedLogService] = useState<string | null>(null);
|
|
@@ -193,6 +213,21 @@ export default function App() {
|
|
|
193
213
|
}
|
|
194
214
|
}, [activeWsId, subTab]);
|
|
195
215
|
|
|
216
|
+
// Load knowledge when subTab switches to 'knowledge' or active workspace changes
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
if (activeWsId && subTab === 'knowledge') {
|
|
219
|
+
fetchKnowledge(activeWsId);
|
|
220
|
+
}
|
|
221
|
+
}, [activeWsId, subTab]);
|
|
222
|
+
|
|
223
|
+
// Load plan when subTab switches to 'plan' or active workspace changes
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
if (activeWsId && subTab === 'plan') {
|
|
226
|
+
fetchPlan(activeWsId);
|
|
227
|
+
}
|
|
228
|
+
}, [activeWsId, subTab]);
|
|
229
|
+
|
|
230
|
+
|
|
196
231
|
|
|
197
232
|
// Poll logs for active service logs
|
|
198
233
|
useEffect(() => {
|
|
@@ -390,6 +425,96 @@ export default function App() {
|
|
|
390
425
|
};
|
|
391
426
|
|
|
392
427
|
|
|
428
|
+
const fetchKnowledge = async (wsId: string) => {
|
|
429
|
+
setKnowledgeLoading(true);
|
|
430
|
+
try {
|
|
431
|
+
const encodedId = encodeURIComponent(wsId);
|
|
432
|
+
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/knowledge`);
|
|
433
|
+
const data = await res.json();
|
|
434
|
+
setKnowledgeContent(data.content || '');
|
|
435
|
+
setEditedKnowledge(data.content || '');
|
|
436
|
+
} catch (e) {
|
|
437
|
+
console.error(e);
|
|
438
|
+
} finally {
|
|
439
|
+
setKnowledgeLoading(false);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const handleSaveKnowledge = async (wsId: string) => {
|
|
444
|
+
setSaveKnowledgeLoading(true);
|
|
445
|
+
try {
|
|
446
|
+
const encodedId = encodeURIComponent(wsId);
|
|
447
|
+
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/knowledge`, {
|
|
448
|
+
method: 'PUT',
|
|
449
|
+
headers: { 'Content-Type': 'application/json' },
|
|
450
|
+
body: JSON.stringify({ content: editedKnowledge }),
|
|
451
|
+
});
|
|
452
|
+
if (res.ok) {
|
|
453
|
+
setKnowledgeContent(editedKnowledge);
|
|
454
|
+
setIsEditingKnowledge(false);
|
|
455
|
+
}
|
|
456
|
+
} catch (e) {
|
|
457
|
+
console.error(e);
|
|
458
|
+
} finally {
|
|
459
|
+
setSaveKnowledgeLoading(false);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const fetchPlan = async (wsId: string) => {
|
|
464
|
+
setPlanLoading(true);
|
|
465
|
+
try {
|
|
466
|
+
const encodedId = encodeURIComponent(wsId);
|
|
467
|
+
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/plan`);
|
|
468
|
+
const data = await res.json();
|
|
469
|
+
setPlanContent(data.content || '');
|
|
470
|
+
} catch (e) {
|
|
471
|
+
console.error(e);
|
|
472
|
+
} finally {
|
|
473
|
+
setPlanLoading(false);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
const handleSyncAll = async (wsId: string) => {
|
|
479
|
+
setSyncLoading(true);
|
|
480
|
+
setSyncResults(null);
|
|
481
|
+
try {
|
|
482
|
+
const encodedId = encodeURIComponent(wsId);
|
|
483
|
+
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/sync`, { method: 'POST' });
|
|
484
|
+
const data = await res.json();
|
|
485
|
+
setSyncResults(data.results || []);
|
|
486
|
+
fetchGitChanges(wsId);
|
|
487
|
+
} catch (e) {
|
|
488
|
+
console.error(e);
|
|
489
|
+
} finally {
|
|
490
|
+
setSyncLoading(false);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const handleCommitAll = async (wsId: string) => {
|
|
495
|
+
if (!commitMessage.trim()) return;
|
|
496
|
+
setCommitLoading(true);
|
|
497
|
+
setCommitResults(null);
|
|
498
|
+
try {
|
|
499
|
+
const encodedId = encodeURIComponent(wsId);
|
|
500
|
+
const res = await fetch(`${API_BASE}/api/workspace/${encodedId}/commit`, {
|
|
501
|
+
method: 'POST',
|
|
502
|
+
headers: { 'Content-Type': 'application/json' },
|
|
503
|
+
body: JSON.stringify({ message: commitMessage }),
|
|
504
|
+
});
|
|
505
|
+
const data = await res.json();
|
|
506
|
+
setCommitResults(data.results || []);
|
|
507
|
+
setCommitMessage('');
|
|
508
|
+
setShowCommitModal(false);
|
|
509
|
+
fetchGitChanges(wsId);
|
|
510
|
+
} catch (e) {
|
|
511
|
+
console.error(e);
|
|
512
|
+
} finally {
|
|
513
|
+
setCommitLoading(false);
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
|
|
393
518
|
// ─── Actions ────────────────────────────────────────────────────────────
|
|
394
519
|
|
|
395
520
|
const handleToggleRepo = (repo: RepoInfo) => {
|
|
@@ -434,14 +559,18 @@ export default function App() {
|
|
|
434
559
|
fetchWorkspaces();
|
|
435
560
|
|
|
436
561
|
if (selectedEditor) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
562
|
+
if (isVsCode) {
|
|
563
|
+
window.parent.postMessage({ type: 'openWorkspaceFolder', workspacePath: data.workspacePath }, '*');
|
|
564
|
+
} else {
|
|
565
|
+
await fetch(`${API_BASE}/api/open-editor`, {
|
|
566
|
+
method: 'POST',
|
|
567
|
+
headers: { 'Content-Type': 'application/json' },
|
|
568
|
+
body: JSON.stringify({
|
|
569
|
+
workspacePath: data.workspacePath,
|
|
570
|
+
command: selectedEditor.command,
|
|
571
|
+
}),
|
|
572
|
+
});
|
|
573
|
+
}
|
|
445
574
|
}
|
|
446
575
|
} else {
|
|
447
576
|
alert(`Error: ${data.error || 'Failed to create workspace'}`);
|
|
@@ -497,9 +626,18 @@ export default function App() {
|
|
|
497
626
|
});
|
|
498
627
|
const data = await res.json();
|
|
499
628
|
if (res.ok && data.success) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
629
|
+
if (isVsCode) {
|
|
630
|
+
window.parent.postMessage({
|
|
631
|
+
type: 'executeTerminalCommand',
|
|
632
|
+
command: data.resumeCommand,
|
|
633
|
+
cwd: ws.workspacePath
|
|
634
|
+
}, '*');
|
|
635
|
+
setActiveWsId(ws.branchName);
|
|
636
|
+
} else {
|
|
637
|
+
navigator.clipboard.writeText(data.resumeCommand);
|
|
638
|
+
alert(`Session Resumed!\n\n1. Editor launched.\n2. Command "${data.resumeCommand}" copied to clipboard! Paste it into your terminal inside the workspace to continue.`);
|
|
639
|
+
setActiveWsId(ws.branchName);
|
|
640
|
+
}
|
|
503
641
|
} else {
|
|
504
642
|
alert(`Error: ${data.error || 'Failed to resume session'}`);
|
|
505
643
|
}
|
|
@@ -512,6 +650,10 @@ export default function App() {
|
|
|
512
650
|
};
|
|
513
651
|
|
|
514
652
|
const handleOpenInEditor = async (workspacePath: string) => {
|
|
653
|
+
if (isVsCode) {
|
|
654
|
+
window.parent.postMessage({ type: 'openWorkspaceFolder', workspacePath }, '*');
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
515
657
|
const editor = editors.find((e) => e.detected) || editors[0];
|
|
516
658
|
if (!editor) {
|
|
517
659
|
alert('No detected editors available.');
|
|
@@ -688,7 +830,7 @@ Core Instructions:
|
|
|
688
830
|
<input
|
|
689
831
|
type="text"
|
|
690
832
|
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-xs shadow-inner"
|
|
691
|
-
placeholder="e.g. C:\Users\
|
|
833
|
+
placeholder="e.g. C:\Users\username\dev"
|
|
692
834
|
value={config.devDir}
|
|
693
835
|
onChange={(e) => setConfig({ ...config, devDir: e.target.value })}
|
|
694
836
|
/>
|
|
@@ -704,7 +846,7 @@ Core Instructions:
|
|
|
704
846
|
<input
|
|
705
847
|
type="text"
|
|
706
848
|
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-xs shadow-inner"
|
|
707
|
-
placeholder="e.g. C:\Users\
|
|
849
|
+
placeholder="e.g. C:\Users\username\dev\workspaces"
|
|
708
850
|
value={config.workspacesDir}
|
|
709
851
|
onChange={(e) => setConfig({ ...config, workspacesDir: e.target.value })}
|
|
710
852
|
/>
|
|
@@ -1528,6 +1670,26 @@ Core Instructions:
|
|
|
1528
1670
|
>
|
|
1529
1671
|
Active Changes (AI Diffs)
|
|
1530
1672
|
</button>
|
|
1673
|
+
<button
|
|
1674
|
+
className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
|
|
1675
|
+
subTab === 'knowledge'
|
|
1676
|
+
? 'border-indigo-500 text-white'
|
|
1677
|
+
: 'border-transparent text-gray-500 hover:text-gray-300'
|
|
1678
|
+
}`}
|
|
1679
|
+
onClick={() => setSubTab('knowledge')}
|
|
1680
|
+
>
|
|
1681
|
+
Knowledge Base
|
|
1682
|
+
</button>
|
|
1683
|
+
<button
|
|
1684
|
+
className={`px-4 py-2 border-b-2 text-xs font-bold transition-all cursor-pointer ${
|
|
1685
|
+
subTab === 'plan'
|
|
1686
|
+
? 'border-indigo-500 text-white'
|
|
1687
|
+
: 'border-transparent text-gray-500 hover:text-gray-300'
|
|
1688
|
+
}`}
|
|
1689
|
+
onClick={() => setSubTab('plan')}
|
|
1690
|
+
>
|
|
1691
|
+
Implementation Plan
|
|
1692
|
+
</button>
|
|
1531
1693
|
</div>
|
|
1532
1694
|
|
|
1533
1695
|
{subTab === 'sessions' && (
|
|
@@ -1695,19 +1857,108 @@ Core Instructions:
|
|
|
1695
1857
|
{/* Active Changes View */}
|
|
1696
1858
|
{subTab === 'changes' && (
|
|
1697
1859
|
<div>
|
|
1698
|
-
<header className="flex justify-between items-center mb-4">
|
|
1860
|
+
<header className="flex justify-between items-center mb-4 flex-wrap gap-2">
|
|
1699
1861
|
<h4 className="text-sm font-bold text-white flex items-center gap-2">
|
|
1700
1862
|
<FolderGit2 size={16} className="text-cyan-400" /> Active Workspace Git Diffs
|
|
1701
1863
|
</h4>
|
|
1702
|
-
<
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1864
|
+
<div className="flex gap-2">
|
|
1865
|
+
<button
|
|
1866
|
+
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"
|
|
1867
|
+
onClick={() => fetchGitChanges(ws.branchName)}
|
|
1868
|
+
disabled={gitChangesLoading}
|
|
1869
|
+
>
|
|
1870
|
+
<RefreshCw size={11} className={gitChangesLoading ? 'animate-spin text-indigo-400' : ''} /> Refresh Changes
|
|
1871
|
+
</button>
|
|
1872
|
+
<button
|
|
1873
|
+
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"
|
|
1874
|
+
onClick={() => handleSyncAll(ws.branchName)}
|
|
1875
|
+
disabled={syncLoading}
|
|
1876
|
+
>
|
|
1877
|
+
{syncLoading ? <RefreshCw size={11} className="animate-spin text-indigo-300" /> : <RefreshCw size={11} />} Sync All
|
|
1878
|
+
</button>
|
|
1879
|
+
<button
|
|
1880
|
+
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"
|
|
1881
|
+
onClick={() => setShowCommitModal(true)}
|
|
1882
|
+
disabled={gitChanges.every(repo => repo.files.length === 0) || commitLoading}
|
|
1883
|
+
>
|
|
1884
|
+
Commit & Push All
|
|
1885
|
+
</button>
|
|
1886
|
+
</div>
|
|
1709
1887
|
</header>
|
|
1710
1888
|
|
|
1889
|
+
{syncResults && (
|
|
1890
|
+
<div className="bg-[#090d1a]/60 border border-gray-800 rounded-xl p-4 mb-4 text-xs">
|
|
1891
|
+
<div className="flex justify-between items-center mb-2 border-b border-gray-800/80 pb-2">
|
|
1892
|
+
<h5 className="font-bold text-white font-mono text-[11px]">Rebase/Sync Results</h5>
|
|
1893
|
+
<button className="text-gray-500 hover:text-gray-300 text-[10px] cursor-pointer" onClick={() => setSyncResults(null)}>Dismiss</button>
|
|
1894
|
+
</div>
|
|
1895
|
+
<div className="space-y-1.5">
|
|
1896
|
+
{syncResults.map((r: any) => (
|
|
1897
|
+
<div key={r.repoName} className="flex justify-between items-center font-mono text-[10px]">
|
|
1898
|
+
<span className="text-gray-300">{r.repoName}</span>
|
|
1899
|
+
<span className={r.success ? "text-emerald-400" : "text-rose-400"}>
|
|
1900
|
+
{r.success ? `✅ Synced (${r.message})` : `⚠️ Conflict: ${r.message}`}
|
|
1901
|
+
</span>
|
|
1902
|
+
</div>
|
|
1903
|
+
))}
|
|
1904
|
+
</div>
|
|
1905
|
+
</div>
|
|
1906
|
+
)}
|
|
1907
|
+
|
|
1908
|
+
{commitResults && (
|
|
1909
|
+
<div className="bg-[#090d1a]/60 border border-gray-800 rounded-xl p-4 mb-4 text-xs">
|
|
1910
|
+
<div className="flex justify-between items-center mb-2 border-b border-gray-800/80 pb-2">
|
|
1911
|
+
<h5 className="font-bold text-white font-mono text-[11px]">Commit/Push Results</h5>
|
|
1912
|
+
<button className="text-gray-500 hover:text-gray-300 text-[10px] cursor-pointer" onClick={() => setCommitResults(null)}>Dismiss</button>
|
|
1913
|
+
</div>
|
|
1914
|
+
<div className="space-y-1.5">
|
|
1915
|
+
{commitResults.map((r: any) => (
|
|
1916
|
+
<div key={r.repoName} className="flex justify-between items-center font-mono text-[10px]">
|
|
1917
|
+
<span className="text-gray-300">{r.repoName}</span>
|
|
1918
|
+
<span className={r.success ? "text-emerald-400" : "text-rose-400"}>
|
|
1919
|
+
{r.success ? `✅ Committed ${r.filesChanged} file(s) (${r.commitHash || 'no hash'})` : `⚠️ Error: ${r.message}`}
|
|
1920
|
+
</span>
|
|
1921
|
+
</div>
|
|
1922
|
+
))}
|
|
1923
|
+
</div>
|
|
1924
|
+
</div>
|
|
1925
|
+
)}
|
|
1926
|
+
|
|
1927
|
+
{showCommitModal && (
|
|
1928
|
+
<div className="bg-[#0b0f19] border border-gray-800 rounded-xl p-4 mb-4">
|
|
1929
|
+
<h5 className="text-xs font-bold text-white mb-2">Enter Commit Message</h5>
|
|
1930
|
+
<input
|
|
1931
|
+
type="text"
|
|
1932
|
+
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"
|
|
1933
|
+
placeholder="feat: implement logic..."
|
|
1934
|
+
value={commitMessage}
|
|
1935
|
+
onChange={(e) => setCommitMessage(e.target.value)}
|
|
1936
|
+
onKeyDown={(e) => {
|
|
1937
|
+
if (e.key === 'Enter') handleCommitAll(ws.branchName);
|
|
1938
|
+
}}
|
|
1939
|
+
/>
|
|
1940
|
+
<div className="flex justify-end gap-2">
|
|
1941
|
+
<button
|
|
1942
|
+
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"
|
|
1943
|
+
onClick={() => {
|
|
1944
|
+
setShowCommitModal(false);
|
|
1945
|
+
setCommitMessage('');
|
|
1946
|
+
}}
|
|
1947
|
+
disabled={commitLoading}
|
|
1948
|
+
>
|
|
1949
|
+
Cancel
|
|
1950
|
+
</button>
|
|
1951
|
+
<button
|
|
1952
|
+
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"
|
|
1953
|
+
onClick={() => handleCommitAll(ws.branchName)}
|
|
1954
|
+
disabled={commitLoading || !commitMessage.trim()}
|
|
1955
|
+
>
|
|
1956
|
+
{commitLoading ? 'Committing...' : 'Commit & Push'}
|
|
1957
|
+
</button>
|
|
1958
|
+
</div>
|
|
1959
|
+
</div>
|
|
1960
|
+
)}
|
|
1961
|
+
|
|
1711
1962
|
{gitChangesLoading ? (
|
|
1712
1963
|
<div className="flex justify-center py-10">
|
|
1713
1964
|
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
@@ -1723,16 +1974,39 @@ Core Instructions:
|
|
|
1723
1974
|
) : (
|
|
1724
1975
|
gitChanges.map((repo) => {
|
|
1725
1976
|
if (repo.files.length === 0) return null;
|
|
1977
|
+
const totalFilesChanged = repo.files.length;
|
|
1978
|
+
const repoAdditions = repo.files.reduce((acc: number, f: any) => acc + (f.additions || 0), 0);
|
|
1979
|
+
const repoDeletions = repo.files.reduce((acc: number, f: any) => acc + (f.deletions || 0), 0);
|
|
1980
|
+
|
|
1726
1981
|
return (
|
|
1727
1982
|
<div key={repo.repoName} className="bg-gray-950/20 border border-gray-800/60 rounded-xl p-4">
|
|
1728
1983
|
<div className="flex justify-between items-center mb-3">
|
|
1729
|
-
<
|
|
1984
|
+
<div className="flex items-center gap-2">
|
|
1985
|
+
<h5 className="text-xs font-bold text-white font-mono">{repo.repoName}</h5>
|
|
1986
|
+
<span className="text-[9px] px-1.5 py-0.5 rounded bg-gray-900 border border-gray-800 text-gray-400 font-mono">
|
|
1987
|
+
{totalFilesChanged} file{totalFilesChanged === 1 ? '' : 's'} changed
|
|
1988
|
+
</span>
|
|
1989
|
+
{(repoAdditions > 0 || repoDeletions > 0) && (
|
|
1990
|
+
<span className="text-[9px] font-mono">
|
|
1991
|
+
<span className="text-emerald-400 font-bold">+{repoAdditions}</span>{' '}
|
|
1992
|
+
<span className="text-rose-400 font-bold">-{repoDeletions}</span>
|
|
1993
|
+
</span>
|
|
1994
|
+
)}
|
|
1995
|
+
</div>
|
|
1730
1996
|
<span className="text-[9px] text-gray-500 font-mono truncate max-w-[280px]">{repo.repoPath}</span>
|
|
1731
1997
|
</div>
|
|
1732
1998
|
<div className="space-y-1.5">
|
|
1733
1999
|
{repo.files.map((fileInfo: any) => (
|
|
1734
2000
|
<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">
|
|
1735
|
-
<
|
|
2001
|
+
<div className="flex flex-col min-w-0">
|
|
2002
|
+
<span className="font-mono text-gray-300 text-[11px] truncate max-w-[320px]">{fileInfo.file}</span>
|
|
2003
|
+
{(fileInfo.additions > 0 || fileInfo.deletions > 0) && (
|
|
2004
|
+
<span className="text-[9px] font-mono mt-0.5">
|
|
2005
|
+
<span className="text-emerald-500">+{fileInfo.additions}</span>{' '}
|
|
2006
|
+
<span className="text-rose-500">-{fileInfo.deletions}</span>
|
|
2007
|
+
</span>
|
|
2008
|
+
)}
|
|
2009
|
+
</div>
|
|
1736
2010
|
<span className={`text-[9px] px-2 py-0.5 rounded font-bold uppercase ${
|
|
1737
2011
|
fileInfo.type === 'added' ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' :
|
|
1738
2012
|
fileInfo.type === 'deleted' ? 'bg-rose-500/10 text-rose-400 border border-rose-500/20' :
|
|
@@ -1751,6 +2025,85 @@ Core Instructions:
|
|
|
1751
2025
|
)}
|
|
1752
2026
|
</div>
|
|
1753
2027
|
)}
|
|
2028
|
+
|
|
2029
|
+
{/* Knowledge Base View */}
|
|
2030
|
+
{subTab === 'knowledge' && (
|
|
2031
|
+
<div className="bg-[#090d1a]/20 border border-gray-800/60 rounded-xl p-4">
|
|
2032
|
+
<header className="flex justify-between items-center mb-4">
|
|
2033
|
+
<h4 className="text-sm font-bold text-white flex items-center gap-2">
|
|
2034
|
+
<BookOpen size={16} className="text-cyan-400" /> Persistent Knowledge Memory (nexusflow-knowledge.md)
|
|
2035
|
+
</h4>
|
|
2036
|
+
<div className="flex gap-2">
|
|
2037
|
+
{isEditingKnowledge ? (
|
|
2038
|
+
<>
|
|
2039
|
+
<button
|
|
2040
|
+
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"
|
|
2041
|
+
onClick={() => {
|
|
2042
|
+
setEditedKnowledge(knowledgeContent);
|
|
2043
|
+
setIsEditingKnowledge(false);
|
|
2044
|
+
}}
|
|
2045
|
+
disabled={saveKnowledgeLoading}
|
|
2046
|
+
>
|
|
2047
|
+
Cancel
|
|
2048
|
+
</button>
|
|
2049
|
+
<button
|
|
2050
|
+
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"
|
|
2051
|
+
onClick={() => handleSaveKnowledge(ws.branchName)}
|
|
2052
|
+
disabled={saveKnowledgeLoading}
|
|
2053
|
+
>
|
|
2054
|
+
{saveKnowledgeLoading ? <RefreshCw className="animate-spin" size={10} /> : <Save size={10} />} Save
|
|
2055
|
+
</button>
|
|
2056
|
+
</>
|
|
2057
|
+
) : (
|
|
2058
|
+
<button
|
|
2059
|
+
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"
|
|
2060
|
+
onClick={() => setIsEditingKnowledge(true)}
|
|
2061
|
+
disabled={knowledgeLoading}
|
|
2062
|
+
>
|
|
2063
|
+
<Edit size={10} /> Edit Knowledge
|
|
2064
|
+
</button>
|
|
2065
|
+
)}
|
|
2066
|
+
</div>
|
|
2067
|
+
</header>
|
|
2068
|
+
|
|
2069
|
+
{knowledgeLoading ? (
|
|
2070
|
+
<div className="flex justify-center py-10">
|
|
2071
|
+
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
2072
|
+
</div>
|
|
2073
|
+
) : isEditingKnowledge ? (
|
|
2074
|
+
<textarea
|
|
2075
|
+
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"
|
|
2076
|
+
value={editedKnowledge}
|
|
2077
|
+
onChange={(e) => setEditedKnowledge(e.target.value)}
|
|
2078
|
+
/>
|
|
2079
|
+
) : (
|
|
2080
|
+
<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">
|
|
2081
|
+
{knowledgeContent || "No knowledge file generated yet."}
|
|
2082
|
+
</div>
|
|
2083
|
+
)}
|
|
2084
|
+
</div>
|
|
2085
|
+
)}
|
|
2086
|
+
|
|
2087
|
+
{/* Implementation Plan View */}
|
|
2088
|
+
{subTab === 'plan' && (
|
|
2089
|
+
<div className="bg-[#090d1a]/20 border border-gray-800/60 rounded-xl p-4">
|
|
2090
|
+
<header className="flex justify-between items-center mb-4">
|
|
2091
|
+
<h4 className="text-sm font-bold text-white flex items-center gap-2">
|
|
2092
|
+
<ListOrdered size={16} className="text-cyan-400" /> Inter-Repo Implementation Plan (nexusflow-plan.md)
|
|
2093
|
+
</h4>
|
|
2094
|
+
</header>
|
|
2095
|
+
|
|
2096
|
+
{planLoading ? (
|
|
2097
|
+
<div className="flex justify-center py-10">
|
|
2098
|
+
<RefreshCw className="animate-spin text-indigo-400" size={20} />
|
|
2099
|
+
</div>
|
|
2100
|
+
) : (
|
|
2101
|
+
<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">
|
|
2102
|
+
{planContent || "No implementation plan generated yet."}
|
|
2103
|
+
</div>
|
|
2104
|
+
)}
|
|
2105
|
+
</div>
|
|
2106
|
+
)}
|
|
1754
2107
|
</div>
|
|
1755
2108
|
)}
|
|
1756
2109
|
</div>
|