neural-loom 0.2.56 → 0.2.58
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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +5 -5
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/diagnostics/route-bundle-stats.json +5 -5
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_global-error/page/build-manifest.json +2 -2
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page/build-manifest.json +2 -2
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page/build-manifest.json +2 -2
- package/.next/server/app/page/react-loadable-manifest.json +2 -2
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +5 -5
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/08o_~ynkkb9ph.js +31 -0
- package/.next/static/chunks/{0eidfx-s54367.js → 0gy2rolwxzjl8.js} +1 -1
- package/.next/static/chunks/0obzqbxd9niy0.js +1 -0
- package/.next/static/chunks/12m0vh2h637z4.js +1 -0
- package/.next/static/chunks/15b-kgkhsdb0s.js +11 -0
- package/.next/static/chunks/{turbopack-0.g.4cakdth0h.js → turbopack-06zh3hgrjcphg.js} +1 -1
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/package.json +2 -1
- package/public/manifest.json +6 -2
- package/src/app/api/git/route.ts +79 -0
- package/src/app/components/IdeLayout.tsx +426 -12
- package/.next/static/chunks/0ljspdd3cv1ft.js +0 -31
- package/.next/static/chunks/0st7_hghhyuv4.js +0 -1
- package/.next/static/chunks/162be8draqhn..js +0 -11
- /package/.next/static/{E5W0YoY_yEbo_xJeWaMEB → HoQA8gsbI6xeWQpFAPJZ9}/_buildManifest.js +0 -0
- /package/.next/static/{E5W0YoY_yEbo_xJeWaMEB → HoQA8gsbI6xeWQpFAPJZ9}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{E5W0YoY_yEbo_xJeWaMEB → HoQA8gsbI6xeWQpFAPJZ9}/_ssgManifest.js +0 -0
package/src/app/api/git/route.ts
CHANGED
|
@@ -96,6 +96,57 @@ export async function GET(request: Request) {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
if (action === "branches") {
|
|
100
|
+
const { stdout } = await execPromise("git branch --format=\"%(refname:short)|%(HEAD)\"", { cwd: targetRoot });
|
|
101
|
+
const branches = stdout.split(/\r?\n/).filter(Boolean).map(line => {
|
|
102
|
+
const [name, active] = line.split("|");
|
|
103
|
+
return {
|
|
104
|
+
name: name.trim(),
|
|
105
|
+
isActive: active === "*"
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
return NextResponse.json({ success: true, branches });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (action === "log") {
|
|
112
|
+
try {
|
|
113
|
+
const { stdout } = await execPromise("git log --graph --abbrev-commit --decorate --format=format:\"%h||%p||%s||%an||%ar\" --all -n 100", { cwd: targetRoot });
|
|
114
|
+
const lines = stdout.split(/\r?\n/);
|
|
115
|
+
const logItems = lines.map(line => {
|
|
116
|
+
const parts = line.split("||");
|
|
117
|
+
if (parts.length >= 5) {
|
|
118
|
+
const firstPart = parts[0];
|
|
119
|
+
const hashMatch = firstPart.match(/([0-9a-f]{7,40})$/);
|
|
120
|
+
const hash = hashMatch ? hashMatch[1] : "";
|
|
121
|
+
const graph = firstPart.substring(0, firstPart.length - hash.length);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
isCommit: true,
|
|
125
|
+
graph,
|
|
126
|
+
hash,
|
|
127
|
+
parents: parts[1].trim().split(/\s+/).filter(Boolean),
|
|
128
|
+
subject: parts[2],
|
|
129
|
+
author: parts[3],
|
|
130
|
+
date: parts[4]
|
|
131
|
+
};
|
|
132
|
+
} else {
|
|
133
|
+
return {
|
|
134
|
+
isCommit: false,
|
|
135
|
+
graph: line,
|
|
136
|
+
hash: "",
|
|
137
|
+
parents: [],
|
|
138
|
+
subject: "",
|
|
139
|
+
author: "",
|
|
140
|
+
date: ""
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return NextResponse.json({ success: true, log: logItems });
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return NextResponse.json({ success: true, log: [] });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
99
150
|
return NextResponse.json({ error: "Invalid action" }, { status: 400 });
|
|
100
151
|
} catch (error) {
|
|
101
152
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -189,6 +240,34 @@ export async function POST(request: Request) {
|
|
|
189
240
|
return NextResponse.json({ success: true, output: stdout + stderr });
|
|
190
241
|
}
|
|
191
242
|
|
|
243
|
+
if (action === "checkout") {
|
|
244
|
+
const { branchName } = body;
|
|
245
|
+
if (!branchName) {
|
|
246
|
+
return NextResponse.json({ error: "Missing branchName parameter" }, { status: 400 });
|
|
247
|
+
}
|
|
248
|
+
const { stdout, stderr } = await execPromise(`git checkout ${JSON.stringify(branchName)}`, { cwd: targetRoot });
|
|
249
|
+
return NextResponse.json({ success: true, output: stdout + stderr });
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (action === "create_branch") {
|
|
253
|
+
const { branchName } = body;
|
|
254
|
+
if (!branchName) {
|
|
255
|
+
return NextResponse.json({ error: "Missing branchName parameter" }, { status: 400 });
|
|
256
|
+
}
|
|
257
|
+
const { stdout, stderr } = await execPromise(`git checkout -b ${JSON.stringify(branchName)}`, { cwd: targetRoot });
|
|
258
|
+
return NextResponse.json({ success: true, output: stdout + stderr });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (action === "delete_branch") {
|
|
262
|
+
const { branchName, force } = body;
|
|
263
|
+
if (!branchName) {
|
|
264
|
+
return NextResponse.json({ error: "Missing branchName parameter" }, { status: 400 });
|
|
265
|
+
}
|
|
266
|
+
const flag = force ? "-D" : "-d";
|
|
267
|
+
const { stdout, stderr } = await execPromise(`git branch ${flag} ${JSON.stringify(branchName)}`, { cwd: targetRoot });
|
|
268
|
+
return NextResponse.json({ success: true, output: stdout + stderr });
|
|
269
|
+
}
|
|
270
|
+
|
|
192
271
|
return NextResponse.json({ error: "Invalid action" }, { status: 400 });
|
|
193
272
|
} catch (error) {
|
|
194
273
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1,11 +1,85 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState, useCallback } from "react";
|
|
4
|
+
import dynamic from "next/dynamic";
|
|
4
5
|
import TerminalConsole from "./TerminalConsole";
|
|
5
6
|
import DirectoryBrowserModal from "./DirectoryBrowserModal";
|
|
6
7
|
import MarkdownPreview from "./MarkdownPreview";
|
|
7
8
|
import DbExplorer from "./DbExplorer";
|
|
8
9
|
|
|
10
|
+
const MonacoEditor = dynamic(() => import("@monaco-editor/react"), { ssr: false });
|
|
11
|
+
|
|
12
|
+
const getEditorLanguage = (filePath: string): string => {
|
|
13
|
+
if (!filePath) return "plaintext";
|
|
14
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
15
|
+
switch (ext) {
|
|
16
|
+
case "ts":
|
|
17
|
+
case "tsx":
|
|
18
|
+
return "typescript";
|
|
19
|
+
case "js":
|
|
20
|
+
case "jsx":
|
|
21
|
+
return "javascript";
|
|
22
|
+
case "json":
|
|
23
|
+
return "json";
|
|
24
|
+
case "css":
|
|
25
|
+
return "css";
|
|
26
|
+
case "md":
|
|
27
|
+
return "markdown";
|
|
28
|
+
case "html":
|
|
29
|
+
return "html";
|
|
30
|
+
case "sql":
|
|
31
|
+
return "sql";
|
|
32
|
+
case "py":
|
|
33
|
+
return "python";
|
|
34
|
+
case "sh":
|
|
35
|
+
case "bash":
|
|
36
|
+
return "shell";
|
|
37
|
+
case "yaml":
|
|
38
|
+
case "yml":
|
|
39
|
+
return "yaml";
|
|
40
|
+
default:
|
|
41
|
+
return "plaintext";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleEditorWillMount = (monaco: any) => {
|
|
46
|
+
monaco.editor.defineTheme("neural-loom-dark", {
|
|
47
|
+
base: "vs-dark",
|
|
48
|
+
inherit: true,
|
|
49
|
+
rules: [
|
|
50
|
+
{ token: "", foreground: "d4d4d4" },
|
|
51
|
+
{ token: "comment", foreground: "6a9955" },
|
|
52
|
+
{ token: "keyword", foreground: "c586c0" },
|
|
53
|
+
{ token: "string", foreground: "ce9178" },
|
|
54
|
+
{ token: "number", foreground: "b5cea8" },
|
|
55
|
+
{ token: "regexp", foreground: "d16969" },
|
|
56
|
+
{ token: "type", foreground: "4ec9b0" },
|
|
57
|
+
{ token: "class", foreground: "4ec9b0" },
|
|
58
|
+
{ token: "function", foreground: "dcdcaa" },
|
|
59
|
+
],
|
|
60
|
+
colors: {
|
|
61
|
+
"editor.background": "#05070a",
|
|
62
|
+
"editor.foreground": "#d4d4d4",
|
|
63
|
+
"editorLineNumber.foreground": "#5a5a5a",
|
|
64
|
+
"editorLineNumber.activeForeground": "#c084fc",
|
|
65
|
+
"editor.lineHighlightBackground": "#0f131a",
|
|
66
|
+
"editor.selectionBackground": "#264f78",
|
|
67
|
+
"editorCursor.foreground": "#c084fc",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const editorOptions = {
|
|
73
|
+
minimap: { enabled: true },
|
|
74
|
+
fontSize: 13,
|
|
75
|
+
lineNumbers: "on" as const,
|
|
76
|
+
wordWrap: "on" as const,
|
|
77
|
+
automaticLayout: true,
|
|
78
|
+
scrollBeyondLastLine: false,
|
|
79
|
+
tabSize: 2,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
|
|
9
83
|
const getModeIcon = (type: string) => {
|
|
10
84
|
if (type === 'mock') return '🧪';
|
|
11
85
|
if (type.includes('-docker')) return '🐳';
|
|
@@ -41,6 +115,16 @@ interface GitFile {
|
|
|
41
115
|
worktreeStatus?: string;
|
|
42
116
|
}
|
|
43
117
|
|
|
118
|
+
interface GitLogItem {
|
|
119
|
+
isCommit: boolean;
|
|
120
|
+
graph: string;
|
|
121
|
+
hash: string;
|
|
122
|
+
parents: string[];
|
|
123
|
+
subject: string;
|
|
124
|
+
author: string;
|
|
125
|
+
date: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
44
128
|
interface SessionStatus {
|
|
45
129
|
type?: string;
|
|
46
130
|
status?: string;
|
|
@@ -236,6 +320,12 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
236
320
|
const [gitBranch, setGitBranch] = useState("");
|
|
237
321
|
const [gitSync, setGitSync] = useState({ ahead: 0, behind: 0 });
|
|
238
322
|
const [commitMessage, setCommitMessage] = useState("");
|
|
323
|
+
const [gitBranches, setGitBranches] = useState<{ name: string; isActive: boolean }[]>([]);
|
|
324
|
+
const [gitLog, setGitLog] = useState<GitLogItem[]>([]);
|
|
325
|
+
const [isBranchesExpanded, setIsBranchesExpanded] = useState(false);
|
|
326
|
+
const [isHistoryExpanded, setIsHistoryExpanded] = useState(false);
|
|
327
|
+
const [newBranchName, setNewBranchName] = useState("");
|
|
328
|
+
const [branchActionLoading, setBranchActionLoading] = useState(false);
|
|
239
329
|
|
|
240
330
|
// Status & Telemetry
|
|
241
331
|
const [sessionStatus, setSessionStatus] = useState<SessionStatus | null>(null);
|
|
@@ -266,6 +356,34 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
266
356
|
setGitFiles(data.files || []);
|
|
267
357
|
setGitBranch(data.branch || "");
|
|
268
358
|
setGitSync({ ahead: data.ahead || 0, behind: data.behind || 0 });
|
|
359
|
+
|
|
360
|
+
if (data.isGit) {
|
|
361
|
+
// Fetch branches
|
|
362
|
+
try {
|
|
363
|
+
const branchesRes = await fetch(`/api/git?action=branches&root=${encodeURIComponent(workspaceRoot)}`);
|
|
364
|
+
if (branchesRes.ok) {
|
|
365
|
+
const branchesData = await branchesRes.json();
|
|
366
|
+
if (branchesData.success) {
|
|
367
|
+
setGitBranches(branchesData.branches || []);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
} catch (err) {
|
|
371
|
+
console.error("Failed to load git branches:", err);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Fetch log
|
|
375
|
+
try {
|
|
376
|
+
const logRes = await fetch(`/api/git?action=log&root=${encodeURIComponent(workspaceRoot)}`);
|
|
377
|
+
if (logRes.ok) {
|
|
378
|
+
const logData = await logRes.json();
|
|
379
|
+
if (logData.success) {
|
|
380
|
+
setGitLog(logData.log || []);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
} catch (err) {
|
|
384
|
+
console.error("Failed to load git log:", err);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
269
387
|
}
|
|
270
388
|
}
|
|
271
389
|
} catch (e) {
|
|
@@ -436,6 +554,102 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
436
554
|
}
|
|
437
555
|
};
|
|
438
556
|
|
|
557
|
+
const handleGitCheckout = async (branchName: string) => {
|
|
558
|
+
setBranchActionLoading(true);
|
|
559
|
+
try {
|
|
560
|
+
const res = await fetch("/api/git", {
|
|
561
|
+
method: "POST",
|
|
562
|
+
headers: { "Content-Type": "application/json" },
|
|
563
|
+
body: JSON.stringify({
|
|
564
|
+
action: "checkout",
|
|
565
|
+
branchName,
|
|
566
|
+
rootPath: workspaceRoot,
|
|
567
|
+
}),
|
|
568
|
+
});
|
|
569
|
+
const data = await res.json();
|
|
570
|
+
if (res.ok && data.success) {
|
|
571
|
+
await loadGitStatus();
|
|
572
|
+
} else {
|
|
573
|
+
alert(data.error || `Failed to checkout branch ${branchName}`);
|
|
574
|
+
}
|
|
575
|
+
} catch (e) {
|
|
576
|
+
console.error(e);
|
|
577
|
+
alert("Failed to checkout branch due to a network error.");
|
|
578
|
+
} finally {
|
|
579
|
+
setBranchActionLoading(false);
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const handleGitCreateBranch = async (e?: React.FormEvent) => {
|
|
584
|
+
if (e) e.preventDefault();
|
|
585
|
+
if (!newBranchName.trim()) return;
|
|
586
|
+
setBranchActionLoading(true);
|
|
587
|
+
try {
|
|
588
|
+
const res = await fetch("/api/git", {
|
|
589
|
+
method: "POST",
|
|
590
|
+
headers: { "Content-Type": "application/json" },
|
|
591
|
+
body: JSON.stringify({
|
|
592
|
+
action: "create_branch",
|
|
593
|
+
branchName: newBranchName.trim(),
|
|
594
|
+
rootPath: workspaceRoot,
|
|
595
|
+
}),
|
|
596
|
+
});
|
|
597
|
+
const data = await res.json();
|
|
598
|
+
if (res.ok && data.success) {
|
|
599
|
+
setNewBranchName("");
|
|
600
|
+
await loadGitStatus();
|
|
601
|
+
} else {
|
|
602
|
+
alert(data.error || `Failed to create branch ${newBranchName}`);
|
|
603
|
+
}
|
|
604
|
+
} catch (e) {
|
|
605
|
+
console.error(e);
|
|
606
|
+
alert("Failed to create branch due to a network error.");
|
|
607
|
+
} finally {
|
|
608
|
+
setBranchActionLoading(false);
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const handleGitDeleteBranch = async (branchName: string, force = false) => {
|
|
613
|
+
if (branchName === gitBranch) {
|
|
614
|
+
alert("Cannot delete the currently active branch.");
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
const message = force
|
|
618
|
+
? `Are you sure you want to FORCE delete the branch "${branchName}"? All unmerged changes will be lost.`
|
|
619
|
+
: `Are you sure you want to delete the branch "${branchName}"?`;
|
|
620
|
+
if (!confirm(message)) return;
|
|
621
|
+
|
|
622
|
+
setBranchActionLoading(true);
|
|
623
|
+
try {
|
|
624
|
+
const res = await fetch("/api/git", {
|
|
625
|
+
method: "POST",
|
|
626
|
+
headers: { "Content-Type": "application/json" },
|
|
627
|
+
body: JSON.stringify({
|
|
628
|
+
action: "delete_branch",
|
|
629
|
+
branchName,
|
|
630
|
+
force,
|
|
631
|
+
rootPath: workspaceRoot,
|
|
632
|
+
}),
|
|
633
|
+
});
|
|
634
|
+
const data = await res.json();
|
|
635
|
+
if (res.ok && data.success) {
|
|
636
|
+
await loadGitStatus();
|
|
637
|
+
} else {
|
|
638
|
+
// If it failed because it's not merged, offer force delete
|
|
639
|
+
if (!force && data.error && (data.error.includes("not fully merged") || data.error.includes("not merged"))) {
|
|
640
|
+
handleGitDeleteBranch(branchName, true);
|
|
641
|
+
} else {
|
|
642
|
+
alert(data.error || `Failed to delete branch ${branchName}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
} catch (e) {
|
|
646
|
+
console.error(e);
|
|
647
|
+
alert("Failed to delete branch due to a network error.");
|
|
648
|
+
} finally {
|
|
649
|
+
setBranchActionLoading(false);
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
|
|
439
653
|
const handleSelectGitFile = async (file: GitFile) => {
|
|
440
654
|
try {
|
|
441
655
|
const res = await fetch(`/api/git?action=diff&file=${encodeURIComponent(file.relativePath)}&root=${encodeURIComponent(workspaceRoot)}`);
|
|
@@ -1316,6 +1530,194 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1316
1530
|
</div>
|
|
1317
1531
|
)}
|
|
1318
1532
|
</div>
|
|
1533
|
+
|
|
1534
|
+
{/* Branches Section */}
|
|
1535
|
+
<div style={{ borderTop: "1px solid var(--border)", paddingTop: "0.75rem" }}>
|
|
1536
|
+
<div
|
|
1537
|
+
onClick={() => setIsBranchesExpanded(!isBranchesExpanded)}
|
|
1538
|
+
style={{
|
|
1539
|
+
display: "flex",
|
|
1540
|
+
justifyContent: "space-between",
|
|
1541
|
+
alignItems: "center",
|
|
1542
|
+
cursor: "pointer",
|
|
1543
|
+
marginBottom: "0.5rem",
|
|
1544
|
+
userSelect: "none"
|
|
1545
|
+
}}
|
|
1546
|
+
>
|
|
1547
|
+
<span style={{ fontSize: "0.7rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", display: "flex", alignItems: "center", gap: "0.25rem" }}>
|
|
1548
|
+
{isBranchesExpanded ? "▼" : "▶"} Branches ({gitBranches.length})
|
|
1549
|
+
</span>
|
|
1550
|
+
</div>
|
|
1551
|
+
|
|
1552
|
+
{isBranchesExpanded && (
|
|
1553
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
|
|
1554
|
+
{/* Create Branch Input */}
|
|
1555
|
+
<form onSubmit={handleGitCreateBranch} style={{ display: "flex", gap: "0.25rem" }}>
|
|
1556
|
+
<input
|
|
1557
|
+
type="text"
|
|
1558
|
+
placeholder="New branch name..."
|
|
1559
|
+
value={newBranchName}
|
|
1560
|
+
onChange={(e) => setNewBranchName(e.target.value)}
|
|
1561
|
+
disabled={branchActionLoading}
|
|
1562
|
+
style={{
|
|
1563
|
+
flex: 1,
|
|
1564
|
+
backgroundColor: "#05070a",
|
|
1565
|
+
border: "1px solid var(--border)",
|
|
1566
|
+
borderRadius: "4px",
|
|
1567
|
+
padding: "0.25rem 0.5rem",
|
|
1568
|
+
color: "#ffffff",
|
|
1569
|
+
fontSize: "0.7rem",
|
|
1570
|
+
outline: "none"
|
|
1571
|
+
}}
|
|
1572
|
+
/>
|
|
1573
|
+
<button
|
|
1574
|
+
type="submit"
|
|
1575
|
+
disabled={branchActionLoading || !newBranchName.trim()}
|
|
1576
|
+
style={{
|
|
1577
|
+
backgroundColor: "rgba(59, 130, 246, 0.15)",
|
|
1578
|
+
border: "1px solid #3b82f6",
|
|
1579
|
+
color: "#3b82f6",
|
|
1580
|
+
padding: "0.25rem 0.5rem",
|
|
1581
|
+
borderRadius: "4px",
|
|
1582
|
+
fontSize: "0.7rem",
|
|
1583
|
+
fontWeight: 600,
|
|
1584
|
+
cursor: "pointer",
|
|
1585
|
+
opacity: (!newBranchName.trim() || branchActionLoading) ? 0.5 : 1
|
|
1586
|
+
}}
|
|
1587
|
+
>
|
|
1588
|
+
+ Create
|
|
1589
|
+
</button>
|
|
1590
|
+
</form>
|
|
1591
|
+
|
|
1592
|
+
{/* Branch List */}
|
|
1593
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "0.25rem", maxHeight: "150px", overflowY: "auto" }}>
|
|
1594
|
+
{gitBranches.map((br) => (
|
|
1595
|
+
<div
|
|
1596
|
+
key={br.name}
|
|
1597
|
+
style={{
|
|
1598
|
+
display: "flex",
|
|
1599
|
+
alignItems: "center",
|
|
1600
|
+
justifyContent: "space-between",
|
|
1601
|
+
padding: "0.35rem 0.5rem",
|
|
1602
|
+
borderRadius: "4px",
|
|
1603
|
+
backgroundColor: br.isActive ? "rgba(59, 130, 246, 0.08)" : "transparent",
|
|
1604
|
+
border: br.isActive ? "1px solid rgba(59, 130, 246, 0.2)" : "1px solid transparent",
|
|
1605
|
+
fontSize: "0.75rem",
|
|
1606
|
+
transition: "all 0.15s ease",
|
|
1607
|
+
}}
|
|
1608
|
+
>
|
|
1609
|
+
<div style={{ display: "flex", alignItems: "center", gap: "0.35rem", fontWeight: br.isActive ? 700 : 500, color: br.isActive ? "var(--primary)" : "#cbd5e1", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: 1 }}>
|
|
1610
|
+
<span style={{ fontSize: "0.7rem" }}>{br.isActive ? "●" : "○"}</span>
|
|
1611
|
+
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }} title={br.name}>{br.name}</span>
|
|
1612
|
+
</div>
|
|
1613
|
+
<div style={{ display: "flex", gap: "0.25rem" }}>
|
|
1614
|
+
{!br.isActive && (
|
|
1615
|
+
<>
|
|
1616
|
+
<button
|
|
1617
|
+
type="button"
|
|
1618
|
+
onClick={() => handleGitCheckout(br.name)}
|
|
1619
|
+
disabled={branchActionLoading}
|
|
1620
|
+
title={`Switch to ${br.name}`}
|
|
1621
|
+
style={{
|
|
1622
|
+
background: "none",
|
|
1623
|
+
border: "none",
|
|
1624
|
+
cursor: "pointer",
|
|
1625
|
+
fontSize: "0.7rem",
|
|
1626
|
+
padding: "2px 4px",
|
|
1627
|
+
color: "var(--muted)"
|
|
1628
|
+
}}
|
|
1629
|
+
>
|
|
1630
|
+
➔
|
|
1631
|
+
</button>
|
|
1632
|
+
<button
|
|
1633
|
+
type="button"
|
|
1634
|
+
onClick={() => handleGitDeleteBranch(br.name)}
|
|
1635
|
+
disabled={branchActionLoading}
|
|
1636
|
+
title={`Delete ${br.name}`}
|
|
1637
|
+
style={{
|
|
1638
|
+
background: "none",
|
|
1639
|
+
border: "none",
|
|
1640
|
+
cursor: "pointer",
|
|
1641
|
+
fontSize: "0.7rem",
|
|
1642
|
+
padding: "2px 4px",
|
|
1643
|
+
color: "var(--muted)"
|
|
1644
|
+
}}
|
|
1645
|
+
>
|
|
1646
|
+
✖
|
|
1647
|
+
</button>
|
|
1648
|
+
</>
|
|
1649
|
+
)}
|
|
1650
|
+
</div>
|
|
1651
|
+
</div>
|
|
1652
|
+
))}
|
|
1653
|
+
</div>
|
|
1654
|
+
</div>
|
|
1655
|
+
)}
|
|
1656
|
+
</div>
|
|
1657
|
+
|
|
1658
|
+
{/* Commit History Section */}
|
|
1659
|
+
<div style={{ borderTop: "1px solid var(--border)", paddingTop: "0.75rem" }}>
|
|
1660
|
+
<div
|
|
1661
|
+
onClick={() => setIsHistoryExpanded(!isHistoryExpanded)}
|
|
1662
|
+
style={{
|
|
1663
|
+
display: "flex",
|
|
1664
|
+
justifyContent: "space-between",
|
|
1665
|
+
alignItems: "center",
|
|
1666
|
+
cursor: "pointer",
|
|
1667
|
+
marginBottom: "0.5rem",
|
|
1668
|
+
userSelect: "none"
|
|
1669
|
+
}}
|
|
1670
|
+
>
|
|
1671
|
+
<span style={{ fontSize: "0.7rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", display: "flex", alignItems: "center", gap: "0.25rem" }}>
|
|
1672
|
+
{isHistoryExpanded ? "▼" : "▶"} Commit History
|
|
1673
|
+
</span>
|
|
1674
|
+
</div>
|
|
1675
|
+
|
|
1676
|
+
{isHistoryExpanded && (
|
|
1677
|
+
<div style={{
|
|
1678
|
+
backgroundColor: "#05070a",
|
|
1679
|
+
border: "1px solid var(--border)",
|
|
1680
|
+
borderRadius: "6px",
|
|
1681
|
+
padding: "0.5rem",
|
|
1682
|
+
maxHeight: "220px",
|
|
1683
|
+
overflowY: "auto",
|
|
1684
|
+
overflowX: "auto"
|
|
1685
|
+
}}>
|
|
1686
|
+
{gitLog.length === 0 ? (
|
|
1687
|
+
<div style={{ color: "var(--muted)", fontStyle: "italic", fontSize: "0.7rem", textAlign: "center", padding: "0.5rem" }}>
|
|
1688
|
+
No commit history found.
|
|
1689
|
+
</div>
|
|
1690
|
+
) : (
|
|
1691
|
+
<div style={{ fontFamily: "var(--font-mono)", fontSize: "0.65rem", lineHeight: "1.4", whiteSpace: "pre" }}>
|
|
1692
|
+
{gitLog.map((item, idx) => (
|
|
1693
|
+
<div
|
|
1694
|
+
key={idx}
|
|
1695
|
+
style={{
|
|
1696
|
+
display: "flex",
|
|
1697
|
+
padding: "2px 0",
|
|
1698
|
+
borderBottom: "1px solid rgba(255,255,255,0.01)",
|
|
1699
|
+
alignItems: "center"
|
|
1700
|
+
}}
|
|
1701
|
+
>
|
|
1702
|
+
<span style={{ color: "#3b82f6", flexShrink: 0, fontWeight: 700 }}>{item.graph}</span>
|
|
1703
|
+
{item.isCommit && (
|
|
1704
|
+
<>
|
|
1705
|
+
<span style={{ color: "#eab308", marginRight: "6px", flexShrink: 0 }} title="Commit Hash">{item.hash}</span>
|
|
1706
|
+
<span style={{ flex: 1, textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap", color: "#cbd5e1" }} title={item.subject}>
|
|
1707
|
+
{item.subject}
|
|
1708
|
+
</span>
|
|
1709
|
+
<span style={{ color: "var(--muted)", fontSize: "0.55rem", marginLeft: "6px", flexShrink: 0 }} title={`${item.author} (${item.date})`}>
|
|
1710
|
+
{item.date}
|
|
1711
|
+
</span>
|
|
1712
|
+
</>
|
|
1713
|
+
)}
|
|
1714
|
+
</div>
|
|
1715
|
+
))}
|
|
1716
|
+
</div>
|
|
1717
|
+
)}
|
|
1718
|
+
</div>
|
|
1719
|
+
)}
|
|
1720
|
+
</div>
|
|
1319
1721
|
</div>
|
|
1320
1722
|
) : (
|
|
1321
1723
|
<div style={{ padding: "1rem", color: "var(--muted)", fontSize: "0.75rem", fontStyle: "italic" }}>
|
|
@@ -1826,11 +2228,15 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1826
2228
|
) : currentMode === 'split' ? (
|
|
1827
2229
|
<div style={{ display: "flex", width: "100%", height: "100%", overflow: "hidden" }}>
|
|
1828
2230
|
<div style={{ flex: 1, height: "100%" }}>
|
|
1829
|
-
<
|
|
2231
|
+
<MonacoEditor
|
|
2232
|
+
height="100%"
|
|
2233
|
+
language="markdown"
|
|
2234
|
+
theme="neural-loom-dark"
|
|
1830
2235
|
value={activeFile.content}
|
|
1831
|
-
onChange={(
|
|
1832
|
-
|
|
1833
|
-
|
|
2236
|
+
onChange={(value) => handleContentChange(value || "")}
|
|
2237
|
+
beforeMount={handleEditorWillMount}
|
|
2238
|
+
options={editorOptions}
|
|
2239
|
+
loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
|
|
1834
2240
|
/>
|
|
1835
2241
|
</div>
|
|
1836
2242
|
<div style={{ flex: 1, height: "100%", overflow: "hidden" }}>
|
|
@@ -1838,19 +2244,27 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1838
2244
|
</div>
|
|
1839
2245
|
</div>
|
|
1840
2246
|
) : (
|
|
1841
|
-
<
|
|
2247
|
+
<MonacoEditor
|
|
2248
|
+
height="100%"
|
|
2249
|
+
language="markdown"
|
|
2250
|
+
theme="neural-loom-dark"
|
|
1842
2251
|
value={activeFile.content}
|
|
1843
|
-
onChange={(
|
|
1844
|
-
|
|
1845
|
-
|
|
2252
|
+
onChange={(value) => handleContentChange(value || "")}
|
|
2253
|
+
beforeMount={handleEditorWillMount}
|
|
2254
|
+
options={editorOptions}
|
|
2255
|
+
loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
|
|
1846
2256
|
/>
|
|
1847
2257
|
)
|
|
1848
2258
|
) : (
|
|
1849
|
-
<
|
|
2259
|
+
<MonacoEditor
|
|
2260
|
+
height="100%"
|
|
2261
|
+
language={getEditorLanguage(activeFile.path)}
|
|
2262
|
+
theme="neural-loom-dark"
|
|
1850
2263
|
value={activeFile.content}
|
|
1851
|
-
onChange={(
|
|
1852
|
-
|
|
1853
|
-
|
|
2264
|
+
onChange={(value) => handleContentChange(value || "")}
|
|
2265
|
+
beforeMount={handleEditorWillMount}
|
|
2266
|
+
options={editorOptions}
|
|
2267
|
+
loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
|
|
1854
2268
|
/>
|
|
1855
2269
|
)
|
|
1856
2270
|
) : (
|