neural-loom 0.2.40 → 0.2.41
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-manifest.json +3 -3
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/diagnostics/build-diagnostics.json +1 -1
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/server/app/api/context/aider/route.js.nft.json +1 -1
- package/.next/server/app/api/context/claude/route.js.nft.json +1 -1
- package/.next/server/app/api/context/ssh/route.js.nft.json +1 -1
- package/.next/server/app/api/recents/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/inject/route.js +1 -1
- package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/input/route.js +1 -1
- package/.next/server/app/api/sessions/input/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/launch/route.js +1 -1
- package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/scope/route.js +1 -1
- package/.next/server/app/api/sessions/scope/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/start/route.js +1 -1
- package/.next/server/app/api/sessions/start/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/stats/route.js +1 -1
- package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/stop/route.js +1 -1
- package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -1
- package/.next/server/app/page/react-loadable-manifest.json +3 -3
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__005xmeg._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0d9_-7g._.js.map → [root-of-the-server]__005xmeg._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0_~zjnf._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0fhv7.1._.js.map → [root-of-the-server]__0_~zjnf._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js +8 -8
- package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0hl8jxx._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0~zauvq._.js.map → [root-of-the-server]__0hl8jxx._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0ho7s6e._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0-vx6-s._.js.map → [root-of-the-server]__0ho7s6e._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0lh_jn-._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0xr~y1f._.js.map → [root-of-the-server]__0lh_jn-._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0mq3k0u._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0zkcq9q._.js.map → [root-of-the-server]__0mq3k0u._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0rr36fm._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0qqnqov._.js.map → [root-of-the-server]__0rr36fm._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0xcrc-1._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0t_~on0._.js.map → [root-of-the-server]__0xcrc-1._.js.map} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0v73tbn._.js.map +1 -1
- package/.next/server/chunks/ssr/_0t7oqy6._.js +1 -1
- package/.next/server/chunks/ssr/_0t7oqy6._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages-manifest.json +1 -4
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/ZtSBLhTiGqKpNetcymWiC/_ssgManifest.js +1 -0
- package/.next/static/chunks/{0x__nd4avi0fg.js → 070hz362~_h8n.js} +1 -1
- package/.next/static/chunks/0bclk7hf35ac4.js +1 -0
- package/.next/static/chunks/17jifiroq0_q..js +1 -0
- package/package.json +1 -1
- package/src/app/components/IdeLayout.tsx +500 -25
- package/src/app/components/TerminalConsole.tsx +2 -2
- package/src/app/layout.tsx +0 -1
- package/src/app/page.tsx +12 -4
- package/src/lib/agents/AiderRunner.ts +8 -8
- package/src/lib/agents/ClaudeRunner.ts +43 -18
- package/src/lib/agents/DockerRunner.ts +1 -3
- package/src/lib/agents/SSHRunner.ts +1 -3
- package/src/lib/agents/ShellRunner.ts +1 -3
- package/src/lib/agents/WebSocketServer.ts +20 -13
- package/.next/BUILD_ID +0 -1
- package/.next/app-path-routes-manifest.json +0 -20
- package/.next/diagnostics/route-bundle-stats.json +0 -25
- package/.next/export-marker.json +0 -6
- package/.next/images-manifest.json +0 -68
- package/.next/prerender-manifest.json +0 -85
- package/.next/required-server-files.js +0 -335
- package/.next/required-server-files.json +0 -335
- package/.next/routes-manifest.json +0 -153
- package/.next/server/app/_global-error.html +0 -1
- package/.next/server/app/_global-error.meta +0 -15
- package/.next/server/app/_global-error.rsc +0 -14
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
- package/.next/server/app/_global-error.segments/_full.segment.rsc +0 -14
- package/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
- package/.next/server/app/_global-error.segments/_index.segment.rsc +0 -5
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
- package/.next/server/app/_not-found.html +0 -1
- package/.next/server/app/_not-found.meta +0 -16
- package/.next/server/app/_not-found.rsc +0 -16
- package/.next/server/app/_not-found.segments/_full.segment.rsc +0 -16
- package/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
- package/.next/server/app/_not-found.segments/_index.segment.rsc +0 -5
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -5
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
- package/.next/server/app/index.html +0 -1
- package/.next/server/app/index.meta +0 -14
- package/.next/server/app/index.rsc +0 -21
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -10
- package/.next/server/app/index.segments/_full.segment.rsc +0 -21
- package/.next/server/app/index.segments/_head.segment.rsc +0 -6
- package/.next/server/app/index.segments/_index.segment.rsc +0 -5
- package/.next/server/app/index.segments/_tree.segment.rsc +0 -5
- package/.next/server/chunks/[root-of-the-server]__0-vx6-s._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0d9_-7g._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0fhv7.1._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0qqnqov._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0t_~on0._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0xr~y1f._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0zkcq9q._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0~zauvq._.js +0 -15
- package/.next/server/functions-config-manifest.json +0 -4
- package/.next/server/pages/404.html +0 -1
- package/.next/server/pages/500.html +0 -1
- package/.next/server/prefetch-hints.json +0 -1
- package/.next/static/5uGIuH8oXybqivkeHIrqb/_ssgManifest.js +0 -1
- package/.next/static/chunks/07-tub5vqy.fk.js +0 -1
- package/.next/static/chunks/0wq3_34inbzj6.js +0 -1
- package/.next/trace +0 -1
- package/.next/trace-build +0 -1
- /package/.next/static/{5uGIuH8oXybqivkeHIrqb → ZtSBLhTiGqKpNetcymWiC}/_buildManifest.js +0 -0
- /package/.next/static/{5uGIuH8oXybqivkeHIrqb → ZtSBLhTiGqKpNetcymWiC}/_clientMiddlewareManifest.js +0 -0
|
@@ -156,7 +156,8 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
156
156
|
|
|
157
157
|
const [editorMode, setEditorMode] = useState<'edit' | 'split' | 'preview'>('edit');
|
|
158
158
|
const [explorerVisible, setExplorerVisible] = useState(true);
|
|
159
|
-
const [activeTab, setActiveTab] = useState<"explorer" | "git" | "analytics" | "auditor">("explorer");
|
|
159
|
+
const [activeTab, setActiveTab] = useState<"explorer" | "git" | "analytics" | "auditor" | "browser">("explorer");
|
|
160
|
+
const [browserUrlInput, setBrowserUrlInput] = useState("http://localhost:3000");
|
|
160
161
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
161
162
|
|
|
162
163
|
// Layout sizing states
|
|
@@ -806,6 +807,25 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
806
807
|
}
|
|
807
808
|
};
|
|
808
809
|
|
|
810
|
+
const handleOpenBrowserTab = (url: string) => {
|
|
811
|
+
const formattedUrl = url.trim();
|
|
812
|
+
if (!formattedUrl) return;
|
|
813
|
+
const browserPath = `browser:${formattedUrl}`;
|
|
814
|
+
const alreadyOpen = openFiles.find(f => f.path === browserPath);
|
|
815
|
+
if (!alreadyOpen) {
|
|
816
|
+
setOpenFiles(prev => [
|
|
817
|
+
...prev,
|
|
818
|
+
{
|
|
819
|
+
name: `Browser: ${formattedUrl.replace(/^https?:\/\//, "")}`,
|
|
820
|
+
path: browserPath,
|
|
821
|
+
content: formattedUrl,
|
|
822
|
+
isModified: false,
|
|
823
|
+
}
|
|
824
|
+
]);
|
|
825
|
+
}
|
|
826
|
+
setActiveFilePath(browserPath);
|
|
827
|
+
};
|
|
828
|
+
|
|
809
829
|
const activeFile = openFiles.find((f) => f.path === activeFilePath);
|
|
810
830
|
const isMarkdown = activeFile ? activeFile.path.toLowerCase().endsWith(".md") : false;
|
|
811
831
|
const currentMode = isMarkdown ? editorMode : "edit";
|
|
@@ -821,7 +841,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
821
841
|
};
|
|
822
842
|
|
|
823
843
|
const handleSaveFile = useCallback(async () => {
|
|
824
|
-
if (!activeFile) return;
|
|
844
|
+
if (!activeFile || activeFile.path.startsWith("diff:") || activeFile.path.startsWith("browser:")) return;
|
|
825
845
|
try {
|
|
826
846
|
const res = await fetch("/api/files", {
|
|
827
847
|
method: "POST",
|
|
@@ -968,6 +988,26 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
968
988
|
>
|
|
969
989
|
🔍
|
|
970
990
|
</div>
|
|
991
|
+
|
|
992
|
+
{/* Web Browser Tab */}
|
|
993
|
+
<div
|
|
994
|
+
onClick={() => {
|
|
995
|
+
if (activeTab === "browser") {
|
|
996
|
+
setExplorerVisible(!explorerVisible);
|
|
997
|
+
} else {
|
|
998
|
+
setActiveTab("browser");
|
|
999
|
+
setExplorerVisible(true);
|
|
1000
|
+
}
|
|
1001
|
+
}}
|
|
1002
|
+
style={{
|
|
1003
|
+
...sidebarIconStyle,
|
|
1004
|
+
backgroundColor: activeTab === "browser" && explorerVisible ? "rgba(255,255,255,0.08)" : "transparent",
|
|
1005
|
+
borderLeft: activeTab === "browser" && explorerVisible ? "2px solid var(--primary)" : "2px solid transparent",
|
|
1006
|
+
}}
|
|
1007
|
+
title="Local Web Browser Test Preview"
|
|
1008
|
+
>
|
|
1009
|
+
🌐
|
|
1010
|
+
</div>
|
|
971
1011
|
</div>
|
|
972
1012
|
|
|
973
1013
|
<div
|
|
@@ -989,6 +1029,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
989
1029
|
{activeTab === "git" && "Source Control"}
|
|
990
1030
|
{activeTab === "analytics" && "Telemetry & Cost"}
|
|
991
1031
|
{activeTab === "auditor" && "Session Auditor"}
|
|
1032
|
+
{activeTab === "browser" && "Browser Preview"}
|
|
992
1033
|
</span>
|
|
993
1034
|
{activeTab === "explorer" && (
|
|
994
1035
|
<div style={{ display: "flex", gap: "0.25rem" }}>
|
|
@@ -1254,6 +1295,105 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1254
1295
|
</div>
|
|
1255
1296
|
)}
|
|
1256
1297
|
|
|
1298
|
+
{activeTab === "browser" && (
|
|
1299
|
+
<div style={{ padding: "1rem", display: "flex", flexDirection: "column", gap: "1rem" }}>
|
|
1300
|
+
<div>
|
|
1301
|
+
<label style={{ fontSize: "0.7rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", display: "block", marginBottom: "0.4rem" }}>
|
|
1302
|
+
Url Address
|
|
1303
|
+
</label>
|
|
1304
|
+
<div style={{ display: "flex", gap: "0.25rem" }}>
|
|
1305
|
+
<input
|
|
1306
|
+
type="text"
|
|
1307
|
+
value={browserUrlInput}
|
|
1308
|
+
onChange={(e) => setBrowserUrlInput(e.target.value)}
|
|
1309
|
+
placeholder="http://localhost:3000"
|
|
1310
|
+
style={{
|
|
1311
|
+
flex: 1,
|
|
1312
|
+
backgroundColor: "#05070a",
|
|
1313
|
+
border: "1px solid var(--border)",
|
|
1314
|
+
borderRadius: "4px",
|
|
1315
|
+
padding: "0.35rem 0.5rem",
|
|
1316
|
+
color: "#ffffff",
|
|
1317
|
+
fontSize: "0.75rem",
|
|
1318
|
+
outline: "none"
|
|
1319
|
+
}}
|
|
1320
|
+
onKeyDown={(e) => {
|
|
1321
|
+
if (e.key === "Enter") {
|
|
1322
|
+
handleOpenBrowserTab(browserUrlInput);
|
|
1323
|
+
}
|
|
1324
|
+
}}
|
|
1325
|
+
/>
|
|
1326
|
+
<button
|
|
1327
|
+
onClick={() => handleOpenBrowserTab(browserUrlInput)}
|
|
1328
|
+
style={{
|
|
1329
|
+
backgroundColor: "var(--primary)",
|
|
1330
|
+
border: "none",
|
|
1331
|
+
color: "#ffffff",
|
|
1332
|
+
padding: "0.35rem 0.75rem",
|
|
1333
|
+
borderRadius: "4px",
|
|
1334
|
+
fontSize: "0.75rem",
|
|
1335
|
+
fontWeight: 600,
|
|
1336
|
+
cursor: "pointer"
|
|
1337
|
+
}}
|
|
1338
|
+
>
|
|
1339
|
+
Go
|
|
1340
|
+
</button>
|
|
1341
|
+
</div>
|
|
1342
|
+
</div>
|
|
1343
|
+
|
|
1344
|
+
<div>
|
|
1345
|
+
<label style={{ fontSize: "0.7rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", display: "block", marginBottom: "0.5rem" }}>
|
|
1346
|
+
Quick Launch Ports
|
|
1347
|
+
</label>
|
|
1348
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "0.35rem" }}>
|
|
1349
|
+
{[
|
|
1350
|
+
{ label: "Next.js App (3000)", url: "http://localhost:3000" },
|
|
1351
|
+
{ label: "WebSocket Gateway (3001)", url: "http://localhost:3001" },
|
|
1352
|
+
{ label: "Vite Server (5173)", url: "http://localhost:5173" }
|
|
1353
|
+
].map((item, idx) => (
|
|
1354
|
+
<div
|
|
1355
|
+
key={idx}
|
|
1356
|
+
onClick={() => {
|
|
1357
|
+
setBrowserUrlInput(item.url);
|
|
1358
|
+
handleOpenBrowserTab(item.url);
|
|
1359
|
+
}}
|
|
1360
|
+
style={{
|
|
1361
|
+
display: "flex",
|
|
1362
|
+
alignItems: "center",
|
|
1363
|
+
justifyContent: "space-between",
|
|
1364
|
+
padding: "0.5rem 0.75rem",
|
|
1365
|
+
borderRadius: "4px",
|
|
1366
|
+
backgroundColor: "rgba(255,255,255,0.02)",
|
|
1367
|
+
border: "1px solid var(--border)",
|
|
1368
|
+
cursor: "pointer",
|
|
1369
|
+
fontSize: "0.75rem",
|
|
1370
|
+
transition: "all 0.15s ease"
|
|
1371
|
+
}}
|
|
1372
|
+
onMouseEnter={(e) => e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.06)"}
|
|
1373
|
+
onMouseLeave={(e) => e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.02)"}
|
|
1374
|
+
>
|
|
1375
|
+
<span style={{ color: "var(--foreground)" }}>{item.label}</span>
|
|
1376
|
+
<span style={{ color: "var(--primary)", fontSize: "0.8rem" }}>🌐➔</span>
|
|
1377
|
+
</div>
|
|
1378
|
+
))}
|
|
1379
|
+
</div>
|
|
1380
|
+
</div>
|
|
1381
|
+
|
|
1382
|
+
<div style={{
|
|
1383
|
+
marginTop: "auto",
|
|
1384
|
+
padding: "0.5rem",
|
|
1385
|
+
borderRadius: "6px",
|
|
1386
|
+
backgroundColor: "rgba(234, 179, 8, 0.05)",
|
|
1387
|
+
border: "1px solid rgba(234, 179, 8, 0.15)",
|
|
1388
|
+
fontSize: "0.65rem",
|
|
1389
|
+
color: "var(--muted)",
|
|
1390
|
+
lineHeight: "1.4"
|
|
1391
|
+
}}>
|
|
1392
|
+
<span style={{ color: "#eab308", fontWeight: 700 }}>⚠️ Iframe CSP Note</span>: Some public websites (e.g. Google, GitHub) block loading inside frames via security headers. Use this tab primarily to preview and debug local development webservers.
|
|
1393
|
+
</div>
|
|
1394
|
+
</div>
|
|
1395
|
+
)}
|
|
1396
|
+
|
|
1257
1397
|
{activeTab === "analytics" && (
|
|
1258
1398
|
<div style={{ padding: "0.5rem 1rem", display: "flex", flexDirection: "column", gap: "1.25rem" }}>
|
|
1259
1399
|
<div>
|
|
@@ -1552,7 +1692,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1552
1692
|
))}
|
|
1553
1693
|
</div>
|
|
1554
1694
|
|
|
1555
|
-
{activeFile && !activeFile.path.startsWith("diff:") && (
|
|
1695
|
+
{activeFile && !activeFile.path.startsWith("diff:") && !activeFile.path.startsWith("browser:") && (
|
|
1556
1696
|
<div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
|
|
1557
1697
|
{isMarkdown && (
|
|
1558
1698
|
<div style={modeToggleContainerStyle}>
|
|
@@ -1612,28 +1752,10 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1612
1752
|
{/* Editor contents */}
|
|
1613
1753
|
<div style={editorWorkspaceStyle}>
|
|
1614
1754
|
{activeFile ? (
|
|
1615
|
-
activeFile.path.startsWith("
|
|
1616
|
-
<
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
let bg = "transparent";
|
|
1620
|
-
if (line.startsWith("+")) {
|
|
1621
|
-
color = "#4ade80";
|
|
1622
|
-
bg = "rgba(74, 222, 128, 0.08)";
|
|
1623
|
-
} else if (line.startsWith("-")) {
|
|
1624
|
-
color = "#f87171";
|
|
1625
|
-
bg = "rgba(248, 113, 113, 0.08)";
|
|
1626
|
-
} else if (line.startsWith("@@")) {
|
|
1627
|
-
color = "#60a5fa";
|
|
1628
|
-
bg = "rgba(96, 165, 250, 0.05)";
|
|
1629
|
-
}
|
|
1630
|
-
return (
|
|
1631
|
-
<div key={i} style={{ color, backgroundColor: bg, whiteSpace: "pre-wrap", padding: "0.1rem 0.5rem" }}>
|
|
1632
|
-
{line}
|
|
1633
|
-
</div>
|
|
1634
|
-
);
|
|
1635
|
-
})}
|
|
1636
|
-
</div>
|
|
1755
|
+
activeFile.path.startsWith("browser:") ? (
|
|
1756
|
+
<BrowserView url={activeFile.content} />
|
|
1757
|
+
) : activeFile.path.startsWith("diff:") ? (
|
|
1758
|
+
<DiffView content={activeFile.content} filePath={activeFile.path.substring(5)} />
|
|
1637
1759
|
) : isMarkdown ? (
|
|
1638
1760
|
currentMode === 'preview' ? (
|
|
1639
1761
|
<div style={{ height: "100%", overflow: "hidden" }}>
|
|
@@ -2785,6 +2907,359 @@ function GitFileRow({ file, isStaged, onAction, onSelect, onIgnore, onDiscard }:
|
|
|
2785
2907
|
);
|
|
2786
2908
|
}
|
|
2787
2909
|
|
|
2910
|
+
interface BrowserViewProps {
|
|
2911
|
+
url: string;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
function BrowserView({ url }: BrowserViewProps) {
|
|
2915
|
+
const [currentUrl, setCurrentUrl] = useState(url);
|
|
2916
|
+
const [iframeUrl, setIframeUrl] = useState(url);
|
|
2917
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
2918
|
+
|
|
2919
|
+
useEffect(() => {
|
|
2920
|
+
setCurrentUrl(url);
|
|
2921
|
+
setIframeUrl(url);
|
|
2922
|
+
}, [url]);
|
|
2923
|
+
|
|
2924
|
+
const handleGo = () => {
|
|
2925
|
+
let targetUrl = currentUrl.trim();
|
|
2926
|
+
if (targetUrl && !/^https?:\/\//i.test(targetUrl)) {
|
|
2927
|
+
targetUrl = "http://" + targetUrl;
|
|
2928
|
+
}
|
|
2929
|
+
setCurrentUrl(targetUrl);
|
|
2930
|
+
setIframeUrl(targetUrl);
|
|
2931
|
+
};
|
|
2932
|
+
|
|
2933
|
+
const handleReload = () => {
|
|
2934
|
+
setRefreshKey(prev => prev + 1);
|
|
2935
|
+
};
|
|
2936
|
+
|
|
2937
|
+
const handleOpenExternal = () => {
|
|
2938
|
+
window.open(iframeUrl, "_blank");
|
|
2939
|
+
};
|
|
2940
|
+
|
|
2941
|
+
return (
|
|
2942
|
+
<div style={{ display: "flex", flexDirection: "column", width: "100%", height: "100%", backgroundColor: "#0b101b" }}>
|
|
2943
|
+
{/* Navigation Toolbar */}
|
|
2944
|
+
<div style={{
|
|
2945
|
+
display: "flex",
|
|
2946
|
+
alignItems: "center",
|
|
2947
|
+
gap: "0.5rem",
|
|
2948
|
+
padding: "0.5rem",
|
|
2949
|
+
backgroundColor: "#05070a",
|
|
2950
|
+
borderBottom: "1px solid var(--border)",
|
|
2951
|
+
}}>
|
|
2952
|
+
<button
|
|
2953
|
+
onClick={handleReload}
|
|
2954
|
+
title="Hard Reload Frame"
|
|
2955
|
+
style={{
|
|
2956
|
+
background: "none",
|
|
2957
|
+
border: "1px solid var(--border)",
|
|
2958
|
+
borderRadius: "4px",
|
|
2959
|
+
color: "var(--muted)",
|
|
2960
|
+
cursor: "pointer",
|
|
2961
|
+
fontSize: "0.85rem",
|
|
2962
|
+
padding: "0.25rem 0.5rem",
|
|
2963
|
+
display: "flex",
|
|
2964
|
+
alignItems: "center",
|
|
2965
|
+
backgroundColor: "rgba(255,255,255,0.02)"
|
|
2966
|
+
}}
|
|
2967
|
+
onMouseEnter={(e) => e.currentTarget.style.color = "var(--foreground)"}
|
|
2968
|
+
onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
|
|
2969
|
+
>
|
|
2970
|
+
🔄
|
|
2971
|
+
</button>
|
|
2972
|
+
|
|
2973
|
+
<input
|
|
2974
|
+
type="text"
|
|
2975
|
+
value={currentUrl}
|
|
2976
|
+
onChange={(e) => setCurrentUrl(e.target.value)}
|
|
2977
|
+
onKeyDown={(e) => {
|
|
2978
|
+
if (e.key === "Enter") {
|
|
2979
|
+
handleGo();
|
|
2980
|
+
}
|
|
2981
|
+
}}
|
|
2982
|
+
style={{
|
|
2983
|
+
flex: 1,
|
|
2984
|
+
backgroundColor: "#080c14",
|
|
2985
|
+
border: "1px solid var(--border)",
|
|
2986
|
+
borderRadius: "4px",
|
|
2987
|
+
padding: "0.25rem 0.75rem",
|
|
2988
|
+
color: "#ffffff",
|
|
2989
|
+
fontSize: "0.8rem",
|
|
2990
|
+
outline: "none",
|
|
2991
|
+
fontFamily: "var(--font-mono), monospace"
|
|
2992
|
+
}}
|
|
2993
|
+
/>
|
|
2994
|
+
|
|
2995
|
+
<button
|
|
2996
|
+
onClick={handleGo}
|
|
2997
|
+
title="Go to URL"
|
|
2998
|
+
style={{
|
|
2999
|
+
background: "none",
|
|
3000
|
+
border: "1px solid var(--border)",
|
|
3001
|
+
borderRadius: "4px",
|
|
3002
|
+
color: "var(--primary)",
|
|
3003
|
+
cursor: "pointer",
|
|
3004
|
+
fontSize: "0.8rem",
|
|
3005
|
+
padding: "0.25rem 0.5rem",
|
|
3006
|
+
display: "flex",
|
|
3007
|
+
alignItems: "center",
|
|
3008
|
+
backgroundColor: "rgba(255,255,255,0.02)"
|
|
3009
|
+
}}
|
|
3010
|
+
>
|
|
3011
|
+
➔
|
|
3012
|
+
</button>
|
|
3013
|
+
|
|
3014
|
+
<button
|
|
3015
|
+
onClick={handleOpenExternal}
|
|
3016
|
+
title="Open in External Browser"
|
|
3017
|
+
style={{
|
|
3018
|
+
background: "none",
|
|
3019
|
+
border: "1px solid var(--border)",
|
|
3020
|
+
borderRadius: "4px",
|
|
3021
|
+
color: "var(--muted)",
|
|
3022
|
+
cursor: "pointer",
|
|
3023
|
+
fontSize: "0.85rem",
|
|
3024
|
+
padding: "0.25rem 0.5rem",
|
|
3025
|
+
display: "flex",
|
|
3026
|
+
alignItems: "center",
|
|
3027
|
+
backgroundColor: "rgba(255,255,255,0.02)"
|
|
3028
|
+
}}
|
|
3029
|
+
onMouseEnter={(e) => e.currentTarget.style.color = "var(--foreground)"}
|
|
3030
|
+
onMouseLeave={(e) => e.currentTarget.style.color = "var(--muted)"}
|
|
3031
|
+
>
|
|
3032
|
+
🌐
|
|
3033
|
+
</button>
|
|
3034
|
+
</div>
|
|
3035
|
+
|
|
3036
|
+
{/* Frame Wrapper */}
|
|
3037
|
+
<div style={{ flex: 1, position: "relative", width: "100%", height: "100%", backgroundColor: "#ffffff" }}>
|
|
3038
|
+
<iframe
|
|
3039
|
+
key={refreshKey}
|
|
3040
|
+
src={iframeUrl}
|
|
3041
|
+
style={{
|
|
3042
|
+
width: "100%",
|
|
3043
|
+
height: "100%",
|
|
3044
|
+
border: "none",
|
|
3045
|
+
backgroundColor: "#ffffff",
|
|
3046
|
+
}}
|
|
3047
|
+
sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
|
|
3048
|
+
/>
|
|
3049
|
+
</div>
|
|
3050
|
+
</div>
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
interface DiffViewProps {
|
|
3055
|
+
content: string;
|
|
3056
|
+
filePath: string;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
function DiffView({ content, filePath }: DiffViewProps) {
|
|
3060
|
+
const lines = content.split(/\r?\n/);
|
|
3061
|
+
|
|
3062
|
+
let oldLineNum = 0;
|
|
3063
|
+
let newLineNum = 0;
|
|
3064
|
+
|
|
3065
|
+
let additions = 0;
|
|
3066
|
+
let deletions = 0;
|
|
3067
|
+
|
|
3068
|
+
const parsedLines = lines.map((line) => {
|
|
3069
|
+
let type: 'meta' | 'header' | 'hunk' | 'addition' | 'deletion' | 'normal' = 'normal';
|
|
3070
|
+
let lineContent = line;
|
|
3071
|
+
let oldNum: number | null = null;
|
|
3072
|
+
let newNum: number | null = null;
|
|
3073
|
+
|
|
3074
|
+
if (line.startsWith("diff --git") || line.startsWith("index ") || line.startsWith("--- ") || line.startsWith("+++ ")) {
|
|
3075
|
+
type = 'header';
|
|
3076
|
+
} else if (line.startsWith("@@")) {
|
|
3077
|
+
type = 'hunk';
|
|
3078
|
+
const match = line.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
|
|
3079
|
+
if (match) {
|
|
3080
|
+
oldLineNum = parseInt(match[1], 10);
|
|
3081
|
+
newLineNum = parseInt(match[2], 10);
|
|
3082
|
+
}
|
|
3083
|
+
} else if (line.startsWith("+")) {
|
|
3084
|
+
type = 'addition';
|
|
3085
|
+
lineContent = line.substring(1);
|
|
3086
|
+
newNum = newLineNum++;
|
|
3087
|
+
additions++;
|
|
3088
|
+
} else if (line.startsWith("-")) {
|
|
3089
|
+
type = 'deletion';
|
|
3090
|
+
lineContent = line.substring(1);
|
|
3091
|
+
oldNum = oldLineNum++;
|
|
3092
|
+
deletions++;
|
|
3093
|
+
} else {
|
|
3094
|
+
type = 'normal';
|
|
3095
|
+
if (line.startsWith(" ")) {
|
|
3096
|
+
lineContent = line.substring(1);
|
|
3097
|
+
}
|
|
3098
|
+
if (oldLineNum > 0) {
|
|
3099
|
+
oldNum = oldLineNum++;
|
|
3100
|
+
}
|
|
3101
|
+
if (newLineNum > 0) {
|
|
3102
|
+
newNum = newLineNum++;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
return {
|
|
3107
|
+
type,
|
|
3108
|
+
content: lineContent,
|
|
3109
|
+
oldNum,
|
|
3110
|
+
newNum,
|
|
3111
|
+
raw: line
|
|
3112
|
+
};
|
|
3113
|
+
});
|
|
3114
|
+
|
|
3115
|
+
return (
|
|
3116
|
+
<div style={{ display: "flex", flexDirection: "column", height: "100%", backgroundColor: "#0b101b" }}>
|
|
3117
|
+
<div style={{
|
|
3118
|
+
display: "flex",
|
|
3119
|
+
alignItems: "center",
|
|
3120
|
+
justifyContent: "space-between",
|
|
3121
|
+
padding: "0.5rem 1rem",
|
|
3122
|
+
backgroundColor: "#05070a",
|
|
3123
|
+
borderBottom: "1px solid var(--border)",
|
|
3124
|
+
}}>
|
|
3125
|
+
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
|
|
3126
|
+
<span style={{ fontSize: "1rem" }}>📄</span>
|
|
3127
|
+
<span style={{ fontWeight: 600, fontSize: "0.85rem", color: "var(--foreground)", fontFamily: "var(--font-mono)" }}>
|
|
3128
|
+
{filePath}
|
|
3129
|
+
</span>
|
|
3130
|
+
</div>
|
|
3131
|
+
<div style={{ display: "flex", alignItems: "center", gap: "0.75rem", fontSize: "0.75rem" }}>
|
|
3132
|
+
{additions > 0 && (
|
|
3133
|
+
<span style={{ color: "#4ade80", backgroundColor: "rgba(74, 222, 128, 0.1)", padding: "0.15rem 0.4rem", borderRadius: "4px", fontWeight: 600 }}>
|
|
3134
|
+
+{additions} lines
|
|
3135
|
+
</span>
|
|
3136
|
+
)}
|
|
3137
|
+
{deletions > 0 && (
|
|
3138
|
+
<span style={{ color: "#f87171", backgroundColor: "rgba(248, 113, 113, 0.1)", padding: "0.15rem 0.4rem", borderRadius: "4px", fontWeight: 600 }}>
|
|
3139
|
+
-{deletions} lines
|
|
3140
|
+
</span>
|
|
3141
|
+
)}
|
|
3142
|
+
<span style={{ color: "var(--muted)" }}>
|
|
3143
|
+
{parsedLines.length} lines in diff
|
|
3144
|
+
</span>
|
|
3145
|
+
</div>
|
|
3146
|
+
</div>
|
|
3147
|
+
|
|
3148
|
+
<div style={{ flex: 1, overflow: "auto", padding: "0.5rem 0" }}>
|
|
3149
|
+
<table style={{ width: "100%", borderCollapse: "collapse", fontFamily: "var(--font-mono), monospace", fontSize: "0.8rem", lineHeight: 1.5 }}>
|
|
3150
|
+
<tbody>
|
|
3151
|
+
{parsedLines.map((lineObj, idx) => {
|
|
3152
|
+
if (lineObj.type === 'header') {
|
|
3153
|
+
return (
|
|
3154
|
+
<tr key={idx} style={{ backgroundColor: "rgba(255,255,255,0.02)" }}>
|
|
3155
|
+
<td colSpan={4} style={{ color: "var(--muted)", padding: "0.25rem 1rem", fontSize: "0.75rem", borderBottom: "1px solid rgba(255,255,255,0.03)" }}>
|
|
3156
|
+
{lineObj.raw}
|
|
3157
|
+
</td>
|
|
3158
|
+
</tr>
|
|
3159
|
+
);
|
|
3160
|
+
}
|
|
3161
|
+
if (lineObj.type === 'hunk') {
|
|
3162
|
+
return (
|
|
3163
|
+
<tr key={idx} style={{ backgroundColor: "rgba(96, 165, 250, 0.05)" }}>
|
|
3164
|
+
<td colSpan={2} style={{
|
|
3165
|
+
width: "80px",
|
|
3166
|
+
textAlign: "center",
|
|
3167
|
+
color: "rgba(96, 165, 250, 0.6)",
|
|
3168
|
+
borderRight: "1px solid rgba(96, 165, 250, 0.15)",
|
|
3169
|
+
userSelect: "none",
|
|
3170
|
+
padding: "0.25rem 0",
|
|
3171
|
+
fontSize: "0.7rem",
|
|
3172
|
+
}}>
|
|
3173
|
+
@@
|
|
3174
|
+
</td>
|
|
3175
|
+
<td style={{
|
|
3176
|
+
width: "25px",
|
|
3177
|
+
borderRight: "1px solid rgba(255, 255, 255, 0.05)",
|
|
3178
|
+
}}></td>
|
|
3179
|
+
<td style={{ color: "#60a5fa", padding: "0.25rem 1rem", fontStyle: "italic", fontSize: "0.75rem" }}>
|
|
3180
|
+
{lineObj.raw}
|
|
3181
|
+
</td>
|
|
3182
|
+
</tr>
|
|
3183
|
+
);
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
let rowBg = "transparent";
|
|
3187
|
+
let codeColor = "var(--foreground)";
|
|
3188
|
+
let sign = " ";
|
|
3189
|
+
let signColor = "transparent";
|
|
3190
|
+
|
|
3191
|
+
if (lineObj.type === 'addition') {
|
|
3192
|
+
rowBg = "rgba(74, 222, 128, 0.08)";
|
|
3193
|
+
codeColor = "#4ade80";
|
|
3194
|
+
sign = "+";
|
|
3195
|
+
signColor = "rgba(74, 222, 128, 0.4)";
|
|
3196
|
+
} else if (lineObj.type === 'deletion') {
|
|
3197
|
+
rowBg = "rgba(248, 113, 113, 0.08)";
|
|
3198
|
+
codeColor = "#f87171";
|
|
3199
|
+
sign = "-";
|
|
3200
|
+
signColor = "rgba(248, 113, 113, 0.4)";
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
return (
|
|
3204
|
+
<tr key={idx} style={{ backgroundColor: rowBg }}>
|
|
3205
|
+
{/* Old line number */}
|
|
3206
|
+
<td style={{
|
|
3207
|
+
width: "40px",
|
|
3208
|
+
textAlign: "right",
|
|
3209
|
+
paddingRight: "0.75rem",
|
|
3210
|
+
color: "var(--muted)",
|
|
3211
|
+
opacity: lineObj.oldNum ? 0.6 : 0.2,
|
|
3212
|
+
userSelect: "none",
|
|
3213
|
+
borderRight: "1px solid rgba(255, 255, 255, 0.05)",
|
|
3214
|
+
fontSize: "0.75rem"
|
|
3215
|
+
}}>
|
|
3216
|
+
{lineObj.oldNum || ""}
|
|
3217
|
+
</td>
|
|
3218
|
+
{/* New line number */}
|
|
3219
|
+
<td style={{
|
|
3220
|
+
width: "40px",
|
|
3221
|
+
textAlign: "right",
|
|
3222
|
+
paddingRight: "0.75rem",
|
|
3223
|
+
color: "var(--muted)",
|
|
3224
|
+
opacity: lineObj.newNum ? 0.6 : 0.2,
|
|
3225
|
+
userSelect: "none",
|
|
3226
|
+
borderRight: "1px solid rgba(255, 255, 255, 0.05)",
|
|
3227
|
+
fontSize: "0.75rem"
|
|
3228
|
+
}}>
|
|
3229
|
+
{lineObj.newNum || ""}
|
|
3230
|
+
</td>
|
|
3231
|
+
{/* Sign (+ / -) */}
|
|
3232
|
+
<td style={{
|
|
3233
|
+
width: "25px",
|
|
3234
|
+
textAlign: "center",
|
|
3235
|
+
color: signColor,
|
|
3236
|
+
userSelect: "none",
|
|
3237
|
+
fontWeight: "bold",
|
|
3238
|
+
fontSize: "0.85rem",
|
|
3239
|
+
borderRight: "1px solid rgba(255, 255, 255, 0.05)",
|
|
3240
|
+
}}>
|
|
3241
|
+
{sign}
|
|
3242
|
+
</td>
|
|
3243
|
+
{/* Code line content */}
|
|
3244
|
+
<td style={{
|
|
3245
|
+
paddingLeft: "0.75rem",
|
|
3246
|
+
color: codeColor,
|
|
3247
|
+
whiteSpace: "pre-wrap",
|
|
3248
|
+
wordBreak: "break-all"
|
|
3249
|
+
}}>
|
|
3250
|
+
{lineObj.content}
|
|
3251
|
+
</td>
|
|
3252
|
+
</tr>
|
|
3253
|
+
);
|
|
3254
|
+
})}
|
|
3255
|
+
</tbody>
|
|
3256
|
+
</table>
|
|
3257
|
+
</div>
|
|
3258
|
+
</div>
|
|
3259
|
+
);
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
|
|
2788
3263
|
const commitTextareaStyle: React.CSSProperties = {
|
|
2789
3264
|
width: "100%",
|
|
2790
3265
|
minHeight: "60px",
|
|
@@ -25,9 +25,9 @@ export default function TerminalConsole({ sessionId, height = "400px", wsPort =
|
|
|
25
25
|
// Initialize xterm
|
|
26
26
|
const term = new Terminal({
|
|
27
27
|
cursorBlink: true,
|
|
28
|
-
fontFamily:
|
|
28
|
+
fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
29
29
|
fontSize: 13,
|
|
30
|
-
lineHeight: 1.
|
|
30
|
+
lineHeight: 1.2,
|
|
31
31
|
scrollback: 5000,
|
|
32
32
|
theme: {
|
|
33
33
|
background: "#05070a",
|
package/src/app/layout.tsx
CHANGED
package/src/app/page.tsx
CHANGED
|
@@ -145,11 +145,19 @@ export default function Home() {
|
|
|
145
145
|
};
|
|
146
146
|
initFetch();
|
|
147
147
|
|
|
148
|
-
//
|
|
148
|
+
// Unregister any active Service Workers to disable PWA interceptions
|
|
149
149
|
if ('serviceWorker' in navigator) {
|
|
150
|
-
navigator.serviceWorker.
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
navigator.serviceWorker.getRegistrations().then((registrations) => {
|
|
151
|
+
for (const registration of registrations) {
|
|
152
|
+
registration.unregister()
|
|
153
|
+
.then((success) => {
|
|
154
|
+
if (success) {
|
|
155
|
+
console.log('Service Worker unregistered successfully.');
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
.catch((err) => console.error('Failed to unregister Service Worker:', err));
|
|
159
|
+
}
|
|
160
|
+
});
|
|
153
161
|
}
|
|
154
162
|
|
|
155
163
|
const interval = setInterval(fetchSessions, 4000);
|
|
@@ -6,9 +6,7 @@ import { AgentRunner, RunnerStatus } from "./AgentRunner";
|
|
|
6
6
|
import { SessionPersister } from "./SessionPersister";
|
|
7
7
|
|
|
8
8
|
function isWindows(): boolean {
|
|
9
|
-
|
|
10
|
-
const key = Object.keys(process).find(k => k.startsWith("plat") && k.endsWith("form"));
|
|
11
|
-
return !!key && (process as unknown as Record<string, unknown>)[key] === "win32";
|
|
9
|
+
return typeof process !== "undefined" && process.platform === "win32";
|
|
12
10
|
}
|
|
13
11
|
|
|
14
12
|
function findGitBash(): string | null {
|
|
@@ -106,12 +104,14 @@ export class AiderRunner extends AgentRunner {
|
|
|
106
104
|
this.appendLog(`\x1b[1;36m NEURALLOOM AIDER AGENT ROUTE (${this.type.toUpperCase()}) \x1b[0m\r\n`);
|
|
107
105
|
this.appendLog("\x1b[1;35m==================================================\x1b[0m\r\n");
|
|
108
106
|
|
|
109
|
-
const
|
|
110
|
-
|
|
107
|
+
const useCmd = isWindows();
|
|
108
|
+
const shellPath = useCmd ? (process.env.COMSPEC || "cmd.exe") : findGitBash();
|
|
109
|
+
if (!shellPath) {
|
|
111
110
|
this.status = 'stopped';
|
|
112
|
-
this.appendLog("\x1b[1;31m[System Error]
|
|
111
|
+
this.appendLog("\x1b[1;31m[System Error] Shell not found. Cannot spawn terminal.\x1b[0m\r\n");
|
|
113
112
|
return;
|
|
114
113
|
}
|
|
114
|
+
const shellArgs = useCmd ? ["/c"] : ["-c"];
|
|
115
115
|
|
|
116
116
|
try {
|
|
117
117
|
if (!this.ptyModule) {
|
|
@@ -214,7 +214,7 @@ export class AiderRunner extends AgentRunner {
|
|
|
214
214
|
|
|
215
215
|
const dockerCmd = `docker run -it --name neural-loom-session-${this.id} --rm ${mountsStr} -w /workspace ${envFlagsStr} paulgauthier/aider ${modelFlag} ${extraFlags} --no-fancy-input ${loadAllFilesFlag}`.trim();
|
|
216
216
|
|
|
217
|
-
this.ptyProcess = this.ptyModule!.spawn(
|
|
217
|
+
this.ptyProcess = this.ptyModule!.spawn(shellPath, [...shellArgs, dockerCmd], {
|
|
218
218
|
name: "xterm-256color",
|
|
219
219
|
cols: 120,
|
|
220
220
|
rows: 30,
|
|
@@ -245,7 +245,7 @@ export class AiderRunner extends AgentRunner {
|
|
|
245
245
|
this.appendLog("[System] Spawning local Aider CLI...\r\n");
|
|
246
246
|
const localCmd = `aider ${modelFlag} ${extraFlags} --no-fancy-input ${loadAllFilesFlag}`.trim();
|
|
247
247
|
|
|
248
|
-
this.ptyProcess = this.ptyModule!.spawn(
|
|
248
|
+
this.ptyProcess = this.ptyModule!.spawn(shellPath, [...shellArgs, localCmd], {
|
|
249
249
|
name: "xterm-256color",
|
|
250
250
|
cols: 120,
|
|
251
251
|
rows: 30,
|