neural-loom 0.2.59 → 0.2.61
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/app-path-routes-manifest.json +3 -0
- 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/route-bundle-stats.json +2 -2
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +18 -0
- 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.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/api/configs/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/configs/route/build-manifest.json +9 -0
- package/.next/server/app/api/configs/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/configs/route.js +6 -0
- package/.next/server/app/api/configs/route.js.map +5 -0
- package/.next/server/app/api/configs/route.js.nft.json +1 -0
- package/.next/server/app/api/configs/route_client-reference-manifest.js +3 -0
- 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/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 +2 -2
- package/.next/server/app/api/sessions/inject/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/input/route.js +2 -2
- package/.next/server/app/api/sessions/input/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/launch/route.js +2 -2
- package/.next/server/app/api/sessions/launch/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route.js +2 -2
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/scope/route.js +2 -2
- package/.next/server/app/api/sessions/scope/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/start/route.js +2 -2
- package/.next/server/app/api/sessions/start/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/stats/route.js +2 -2
- package/.next/server/app/api/sessions/stats/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/stop/route.js +2 -2
- package/.next/server/app/api/sessions/stop/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/upload-image/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/sessions/upload-image/route/build-manifest.json +9 -0
- package/.next/server/app/api/sessions/upload-image/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/sessions/upload-image/route.js +7 -0
- package/.next/server/app/api/sessions/upload-image/route.js.map +5 -0
- package/.next/server/app/api/sessions/upload-image/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/upload-image/route_client-reference-manifest.js +3 -0
- package/.next/server/app/api/sql/connections/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/query/route.js.nft.json +1 -1
- package/.next/server/app/api/sql/schema/route.js.nft.json +1 -1
- package/.next/server/app/api/telemetry/route/app-paths-manifest.json +3 -0
- package/.next/server/app/api/telemetry/route/build-manifest.json +9 -0
- package/.next/server/app/api/telemetry/route/server-reference-manifest.json +4 -0
- package/.next/server/app/api/telemetry/route.js +6 -0
- package/.next/server/app/api/telemetry/route.js.map +5 -0
- package/.next/server/app/api/telemetry/route.js.nft.json +1 -0
- package/.next/server/app/api/telemetry/route_client-reference-manifest.js +3 -0
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- 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 +2 -2
- package/.next/server/app/page/react-loadable-manifest.json +5 -5
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +3 -0
- package/.next/server/chunks/[root-of-the-server]__0.ptwor._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0.ptwor._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__00-sqou._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0thsozo._.js.map → [root-of-the-server]__00-sqou._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__00l5btc._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__00l5btc._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__01621nz._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__01621nz._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__06men8g._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__06men8g._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__08r0s-s._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__08r0s-s._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__097vtn1._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0em6a~~._.js.map → [root-of-the-server]__097vtn1._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0_v2.a8._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0_v2.a8._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0co.g5k._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0co.g5k._.js.map +1 -1
- package/.next/server/chunks/[root-of-the-server]__0e03ite._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__10wwca8._.js.map → [root-of-the-server]__0e03ite._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0fxtx7z._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0fxtx7z._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0h.~hv6._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0s0is_9._.js.map → [root-of-the-server]__0h.~hv6._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js +33 -0
- package/.next/server/chunks/[root-of-the-server]__0li1h1u._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0s1axek._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__0i~01cv._.js.map → [root-of-the-server]__0s1axek._.js.map} +1 -1
- package/.next/server/chunks/[root-of-the-server]__0tq0maq._.js +15 -0
- package/.next/server/chunks/[root-of-the-server]__0tq0maq._.js.map +1 -0
- package/.next/server/chunks/[root-of-the-server]__0viil6b._.js +15 -0
- package/.next/server/chunks/{[root-of-the-server]__07zx04e._.js.map → [root-of-the-server]__0viil6b._.js.map} +1 -1
- package/.next/server/chunks/_next-internal_server_app_api_configs_route_actions_087k_4j.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_configs_route_actions_087k_4j.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_upload-image_route_actions_09tfn3a.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_sessions_upload-image_route_actions_09tfn3a.js.map +1 -0
- package/.next/server/chunks/_next-internal_server_app_api_telemetry_route_actions_0.yo5qr.js +3 -0
- package/.next/server/chunks/_next-internal_server_app_api_telemetry_route_actions_0.yo5qr.js.map +1 -0
- 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/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/07lj0adi0kt5p.css +1 -0
- package/.next/static/chunks/0xhaghhxllgzs.js +1 -0
- package/.next/static/chunks/128c-kly7grxg.js +4 -0
- package/.next/static/chunks/{0-vb7c~yrtpon.js → 15_baizhaiq8c.js} +16 -3
- package/.next/static/chunks/15sigqif_v.mu.js +1 -0
- package/.next/static/chunks/17ud2wmsyqknc.js +11 -0
- package/.next/trace +1 -1
- package/.next/trace-build +1 -1
- package/.next/types/routes.d.ts +4 -1
- package/.next/types/validator.ts +27 -0
- package/bin/cli.js +4 -4
- package/bin/fix-pwa-ubuntu.sh +50 -0
- package/package.json +2 -1
- package/src/app/api/configs/route.ts +40 -0
- package/src/app/api/sessions/launch/route.ts +3 -3
- package/src/app/api/sessions/route.ts +2 -2
- package/src/app/api/sessions/start/route.ts +2 -2
- package/src/app/api/sessions/upload-image/route.ts +57 -0
- package/src/app/api/sql/connections/route.ts +9 -6
- package/src/app/api/sql/mcp/route.ts +3 -2
- package/src/app/api/sql/query/route.ts +3 -2
- package/src/app/api/sql/schema/route.ts +3 -2
- package/src/app/api/telemetry/route.ts +18 -0
- package/src/app/components/AiderWizard.tsx +46 -2
- package/src/app/components/ClaudeWizard.tsx +30 -0
- package/src/app/components/IdeLayout.tsx +566 -70
- package/src/app/components/TerminalConsole.tsx +368 -11
- package/src/app/page.module.css +8 -2
- package/src/app/page.tsx +534 -401
- package/src/lib/agents/AgentRunner.ts +88 -5
- package/src/lib/agents/AiderRunner.ts +4 -18
- package/src/lib/agents/ClaudeRunner.ts +16 -26
- package/src/lib/agents/DockerRunner.ts +16 -26
- package/src/lib/agents/MockRunner.ts +4 -16
- package/src/lib/agents/SSHRunner.ts +16 -26
- package/src/lib/agents/SessionConfig.ts +79 -0
- package/src/lib/agents/SessionManager.ts +16 -16
- package/src/lib/agents/SessionPersister.ts +13 -9
- package/src/lib/agents/ShellRunner.ts +14 -21
- package/src/lib/agents/WebSocketServer.ts +2 -2
- package/.next/server/chunks/[root-of-the-server]__01.zh3b._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__01.zh3b._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__07zx04e._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0_o7_jj._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js +0 -33
- package/.next/server/chunks/[root-of-the-server]__0d0pykb._.js.map +0 -1
- package/.next/server/chunks/[root-of-the-server]__0em6a~~._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0i~01cv._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0s0is_9._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__0thsozo._.js +0 -15
- package/.next/server/chunks/[root-of-the-server]__10wwca8._.js +0 -15
- package/.next/static/chunks/076_b43_kienv.js +0 -1
- package/.next/static/chunks/0hoyc~vbuqe0t.css +0 -1
- package/.next/static/chunks/0k2co3uj4w81_.js +0 -1
- package/.next/static/chunks/0sqeixl35qoac.js +0 -1
- package/.next/static/chunks/10pqt4p7e3rk1.js +0 -11
- /package/.next/static/{cwv8IVp6ik4mwOjuM24B1 → b48zoI5QUg8m7v1asTiCM}/_buildManifest.js +0 -0
- /package/.next/static/{cwv8IVp6ik4mwOjuM24B1 → b48zoI5QUg8m7v1asTiCM}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{cwv8IVp6ik4mwOjuM24B1 → b48zoI5QUg8m7v1asTiCM}/_ssgManifest.js +0 -0
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState, useCallback } from "react";
|
|
3
|
+
import { useEffect, useState, useCallback, useMemo } from "react";
|
|
4
4
|
import dynamic from "next/dynamic";
|
|
5
5
|
import TerminalConsole from "./TerminalConsole";
|
|
6
6
|
import DirectoryBrowserModal from "./DirectoryBrowserModal";
|
|
7
7
|
import MarkdownPreview from "./MarkdownPreview";
|
|
8
8
|
import DbExplorer from "./DbExplorer";
|
|
9
9
|
|
|
10
|
+
interface TokenTransaction {
|
|
11
|
+
timestamp: string;
|
|
12
|
+
sessionId: string;
|
|
13
|
+
sessionName: string;
|
|
14
|
+
type: string;
|
|
15
|
+
inputTokens: number;
|
|
16
|
+
outputTokens: number;
|
|
17
|
+
cost: number;
|
|
18
|
+
totalInput: number;
|
|
19
|
+
totalOutput: number;
|
|
20
|
+
totalCost: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
const MonacoEditor = dynamic(() => import("@monaco-editor/react"), { ssr: false });
|
|
11
24
|
|
|
12
25
|
const getEditorLanguage = (filePath: string): string => {
|
|
@@ -42,6 +55,35 @@ const getEditorLanguage = (filePath: string): string => {
|
|
|
42
55
|
}
|
|
43
56
|
};
|
|
44
57
|
|
|
58
|
+
interface MergeConflictBlock {
|
|
59
|
+
index: number;
|
|
60
|
+
startIndex: number;
|
|
61
|
+
endIndex: number;
|
|
62
|
+
current: string;
|
|
63
|
+
incoming: string;
|
|
64
|
+
rawText: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const parseMergeConflicts = (text: string): MergeConflictBlock[] => {
|
|
68
|
+
if (!text) return [];
|
|
69
|
+
const regex = /<<<<<<<[^]*?\r?\n([^]*?)\r?\n=======\r?\n([^]*?)\r?\n>>>>>>>.*/g;
|
|
70
|
+
const conflicts: MergeConflictBlock[] = [];
|
|
71
|
+
let match;
|
|
72
|
+
let index = 0;
|
|
73
|
+
while ((match = regex.exec(text)) !== null) {
|
|
74
|
+
conflicts.push({
|
|
75
|
+
index: index++,
|
|
76
|
+
startIndex: match.index,
|
|
77
|
+
endIndex: match.index + match[0].length,
|
|
78
|
+
current: match[1],
|
|
79
|
+
incoming: match[2],
|
|
80
|
+
rawText: match[0]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return conflicts;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
87
|
const handleEditorWillMount = (monaco: any) => {
|
|
46
88
|
monaco.editor.defineTheme("neural-loom-dark", {
|
|
47
89
|
base: "vs-dark",
|
|
@@ -152,7 +194,7 @@ interface FlatFile {
|
|
|
152
194
|
interface RunnerStatus {
|
|
153
195
|
id: string;
|
|
154
196
|
name: string;
|
|
155
|
-
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker';
|
|
197
|
+
type: 'mock' | 'claude' | 'claude-docker' | 'claude-ssh' | 'aider' | 'aider-docker' | 'shell';
|
|
156
198
|
status: 'idle' | 'running' | 'stopped';
|
|
157
199
|
createdAt: string;
|
|
158
200
|
logsCount: number;
|
|
@@ -250,15 +292,104 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
250
292
|
const [editorMode, setEditorMode] = useState<'edit' | 'split' | 'preview'>('edit');
|
|
251
293
|
const [explorerVisible, setExplorerVisible] = useState(true);
|
|
252
294
|
const [activeTab, setActiveTab] = useState<"explorer" | "git" | "analytics" | "auditor" | "browser" | "database">("explorer");
|
|
253
|
-
const [browserUrlInput, setBrowserUrlInput] = useState("http://localhost:
|
|
295
|
+
const [browserUrlInput, setBrowserUrlInput] = useState(typeof window !== "undefined" ? window.location.origin : "http://localhost:9600");
|
|
254
296
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
255
297
|
|
|
298
|
+
// Terminal tab states & handlers
|
|
299
|
+
const [consoleSessionId, setConsoleSessionId] = useState<string>(sessionId);
|
|
300
|
+
|
|
301
|
+
useEffect(() => {
|
|
302
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
303
|
+
setConsoleSessionId(sessionId);
|
|
304
|
+
}, [sessionId]);
|
|
305
|
+
|
|
306
|
+
const workspaceShells = sessions.filter(s => s.type === 'shell' && s.id.startsWith(`shell-${sessionId}-`));
|
|
307
|
+
|
|
308
|
+
const handleCreateShell = async () => {
|
|
309
|
+
try {
|
|
310
|
+
const res = await fetch("/api/sessions/launch", {
|
|
311
|
+
method: "POST",
|
|
312
|
+
headers: { "Content-Type": "application/json" },
|
|
313
|
+
body: JSON.stringify({
|
|
314
|
+
type: "shell",
|
|
315
|
+
name: `Shell ${workspaceShells.length + 1}`,
|
|
316
|
+
workspaceRoot: workspaceRoot,
|
|
317
|
+
parentSessionId: sessionId
|
|
318
|
+
})
|
|
319
|
+
});
|
|
320
|
+
if (res.ok) {
|
|
321
|
+
const data = await res.json();
|
|
322
|
+
if (data.success && data.session) {
|
|
323
|
+
if (onRefreshSessions) {
|
|
324
|
+
onRefreshSessions();
|
|
325
|
+
}
|
|
326
|
+
setConsoleSessionId(data.session.id);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} catch (err) {
|
|
330
|
+
console.error("Failed to launch shell terminal:", err);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const handleCloseShell = async (shellId: string, e: React.MouseEvent) => {
|
|
335
|
+
e.stopPropagation();
|
|
336
|
+
if (consoleSessionId === shellId) {
|
|
337
|
+
setConsoleSessionId(sessionId);
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
await fetch("/api/sessions/stop", {
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers: { "Content-Type": "application/json" },
|
|
343
|
+
body: JSON.stringify({ id: shellId, delete: true })
|
|
344
|
+
});
|
|
345
|
+
if (onRefreshSessions) {
|
|
346
|
+
onRefreshSessions();
|
|
347
|
+
}
|
|
348
|
+
} catch (err) {
|
|
349
|
+
console.error("Failed to delete shell:", err);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
256
353
|
// Layout sizing states
|
|
257
354
|
const [sidebarWidth, setSidebarWidth] = useState(300);
|
|
258
355
|
const [isResizingSidebar, setIsResizingSidebar] = useState(false);
|
|
259
356
|
const [terminalHeight, setTerminalHeight] = useState(380);
|
|
260
357
|
const [isResizingTerminal, setIsResizingTerminal] = useState(false);
|
|
261
358
|
|
|
359
|
+
// Load layout sizes from localStorage on mount
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
if (typeof window !== "undefined") {
|
|
362
|
+
const savedWidth = localStorage.getItem("neural-loom:sidebar-width");
|
|
363
|
+
if (savedWidth) {
|
|
364
|
+
const w = parseInt(savedWidth, 10);
|
|
365
|
+
if (!isNaN(w) && w > 150 && w < 600) {
|
|
366
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
367
|
+
setSidebarWidth(w);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
const savedHeight = localStorage.getItem("neural-loom:terminal-height");
|
|
371
|
+
if (savedHeight) {
|
|
372
|
+
const h = parseInt(savedHeight, 10);
|
|
373
|
+
if (!isNaN(h) && h > 100 && h < window.innerHeight - 150) {
|
|
374
|
+
setTerminalHeight(h);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}, []);
|
|
379
|
+
|
|
380
|
+
// Save layout sizes when changed
|
|
381
|
+
useEffect(() => {
|
|
382
|
+
if (typeof window !== "undefined" && sidebarWidth !== 300) {
|
|
383
|
+
localStorage.setItem("neural-loom:sidebar-width", sidebarWidth.toString());
|
|
384
|
+
}
|
|
385
|
+
}, [sidebarWidth]);
|
|
386
|
+
|
|
387
|
+
useEffect(() => {
|
|
388
|
+
if (typeof window !== "undefined" && terminalHeight !== 380) {
|
|
389
|
+
localStorage.setItem("neural-loom:terminal-height", terminalHeight.toString());
|
|
390
|
+
}
|
|
391
|
+
}, [terminalHeight]);
|
|
392
|
+
|
|
262
393
|
const startResizingSidebar = useCallback((e: React.MouseEvent) => {
|
|
263
394
|
e.preventDefault();
|
|
264
395
|
setIsResizingSidebar(true);
|
|
@@ -334,6 +465,30 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
334
465
|
// Log Search & Auditor
|
|
335
466
|
const [logSearchQuery, setLogSearchQuery] = useState("");
|
|
336
467
|
|
|
468
|
+
// Telemetry history
|
|
469
|
+
const [telemetryHistory, setTelemetryHistory] = useState<TokenTransaction[]>([]);
|
|
470
|
+
|
|
471
|
+
const fetchTelemetryHistory = async () => {
|
|
472
|
+
try {
|
|
473
|
+
const res = await fetch("/api/telemetry");
|
|
474
|
+
if (res.ok) {
|
|
475
|
+
const data = await res.json();
|
|
476
|
+
if (data.success && data.history) {
|
|
477
|
+
setTelemetryHistory(data.history);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
} catch (err) {
|
|
481
|
+
console.error("Failed to fetch telemetry history:", err);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
useEffect(() => {
|
|
486
|
+
if (activeTab === "analytics") {
|
|
487
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
488
|
+
fetchTelemetryHistory();
|
|
489
|
+
}
|
|
490
|
+
}, [activeTab]);
|
|
491
|
+
|
|
337
492
|
// Custom Commands
|
|
338
493
|
const [customCommands, setCustomCommands] = useState<{ name: string; command: string }[]>([]);
|
|
339
494
|
const [newCmdName, setNewCmdName] = useState("");
|
|
@@ -807,6 +962,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
807
962
|
}, [workspaceRoot, scopedDirsStr]);
|
|
808
963
|
|
|
809
964
|
useEffect(() => {
|
|
965
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
810
966
|
loadFiles();
|
|
811
967
|
const interval = setInterval(() => {
|
|
812
968
|
loadFiles();
|
|
@@ -1738,7 +1894,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1738
1894
|
type="text"
|
|
1739
1895
|
value={browserUrlInput}
|
|
1740
1896
|
onChange={(e) => setBrowserUrlInput(e.target.value)}
|
|
1741
|
-
placeholder="http://localhost:
|
|
1897
|
+
placeholder={typeof window !== "undefined" ? window.location.origin : "http://localhost:9600"}
|
|
1742
1898
|
style={{
|
|
1743
1899
|
flex: 1,
|
|
1744
1900
|
backgroundColor: "#05070a",
|
|
@@ -1779,8 +1935,8 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1779
1935
|
</label>
|
|
1780
1936
|
<div style={{ display: "flex", flexDirection: "column", gap: "0.35rem" }}>
|
|
1781
1937
|
{[
|
|
1782
|
-
{ label:
|
|
1783
|
-
{ label:
|
|
1938
|
+
{ label: `Next.js App (${typeof window !== "undefined" ? window.location.port : "9600"})`, url: typeof window !== "undefined" ? window.location.origin : "http://localhost:9600" },
|
|
1939
|
+
{ label: `WebSocket Gateway (${wsPort})`, url: `http://localhost:${wsPort}` },
|
|
1784
1940
|
{ label: "Vite Server (5173)", url: "http://localhost:5173" }
|
|
1785
1941
|
].map((item, idx) => (
|
|
1786
1942
|
<div
|
|
@@ -1827,10 +1983,11 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1827
1983
|
)}
|
|
1828
1984
|
|
|
1829
1985
|
{activeTab === "analytics" && (
|
|
1830
|
-
<div style={{ padding: "0.5rem 1rem", display: "flex", flexDirection: "column", gap: "1.25rem" }}>
|
|
1986
|
+
<div style={{ padding: "0.5rem 1rem", display: "flex", flexDirection: "column", gap: "1.25rem", maxHeight: "100%", overflowY: "auto" }}>
|
|
1987
|
+
{/* Session Token Usage */}
|
|
1831
1988
|
<div>
|
|
1832
1989
|
<div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
|
|
1833
|
-
Token Usage
|
|
1990
|
+
Active Session Token Usage
|
|
1834
1991
|
</div>
|
|
1835
1992
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.5rem" }}>
|
|
1836
1993
|
<div style={analyticsCardStyle}>
|
|
@@ -1850,6 +2007,137 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
1850
2007
|
</div>
|
|
1851
2008
|
</div>
|
|
1852
2009
|
|
|
2010
|
+
{/* All-time Accumulations */}
|
|
2011
|
+
<div>
|
|
2012
|
+
<div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
|
|
2013
|
+
All-time Cumulative Totals
|
|
2014
|
+
</div>
|
|
2015
|
+
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.5rem" }}>
|
|
2016
|
+
<div style={analyticsCardStyle}>
|
|
2017
|
+
<div style={analyticsCardLabelStyle}>Total Input</div>
|
|
2018
|
+
<div style={analyticsCardValueStyle}>
|
|
2019
|
+
{telemetryHistory.reduce((sum, item) => sum + (item.inputTokens || 0), 0).toLocaleString()}
|
|
2020
|
+
</div>
|
|
2021
|
+
</div>
|
|
2022
|
+
<div style={analyticsCardStyle}>
|
|
2023
|
+
<div style={analyticsCardLabelStyle}>Total Output</div>
|
|
2024
|
+
<div style={analyticsCardValueStyle}>
|
|
2025
|
+
{telemetryHistory.reduce((sum, item) => sum + (item.outputTokens || 0), 0).toLocaleString()}
|
|
2026
|
+
</div>
|
|
2027
|
+
</div>
|
|
2028
|
+
<div style={{ ...analyticsCardStyle, gridColumn: "span 2" }}>
|
|
2029
|
+
<div style={analyticsCardLabelStyle}>Total Cost</div>
|
|
2030
|
+
<div style={{ ...analyticsCardValueStyle, color: "#10b981", fontSize: "1.25rem" }}>
|
|
2031
|
+
${telemetryHistory.reduce((sum, item) => sum + (item.cost || 0), 0).toFixed(4)}
|
|
2032
|
+
</div>
|
|
2033
|
+
</div>
|
|
2034
|
+
</div>
|
|
2035
|
+
</div>
|
|
2036
|
+
|
|
2037
|
+
{/* Historical Token Analytics Chart */}
|
|
2038
|
+
{telemetryHistory.length > 0 && (
|
|
2039
|
+
<div>
|
|
2040
|
+
<div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
|
|
2041
|
+
Cost & Token History Trend (Last 10 Actions)
|
|
2042
|
+
</div>
|
|
2043
|
+
<div className="card" style={{ padding: "1rem", backgroundColor: "#05070a", border: "1px solid var(--border)", display: "flex", flexDirection: "column", alignItems: "center" }}>
|
|
2044
|
+
<svg width="240" height="110" style={{ overflow: "visible" }}>
|
|
2045
|
+
{(() => {
|
|
2046
|
+
const chartData = telemetryHistory.slice(-10);
|
|
2047
|
+
const maxVal = Math.max(...chartData.map(d => (d.inputTokens || 0) + (d.outputTokens || 0)), 100);
|
|
2048
|
+
const barWidth = 14;
|
|
2049
|
+
const gap = 7;
|
|
2050
|
+
const svgHeight = 90;
|
|
2051
|
+
|
|
2052
|
+
return chartData.map((d, i) => {
|
|
2053
|
+
const inp = d.inputTokens || 0;
|
|
2054
|
+
const out = d.outputTokens || 0;
|
|
2055
|
+
|
|
2056
|
+
const inpHeight = (inp / maxVal) * (svgHeight - 10);
|
|
2057
|
+
const outHeight = (out / maxVal) * (svgHeight - 10);
|
|
2058
|
+
|
|
2059
|
+
const x = i * (barWidth + gap) + 15;
|
|
2060
|
+
const inpY = svgHeight - inpHeight;
|
|
2061
|
+
const outY = inpY - outHeight;
|
|
2062
|
+
|
|
2063
|
+
return (
|
|
2064
|
+
<g key={i}>
|
|
2065
|
+
{/* Input portion (purple) */}
|
|
2066
|
+
<rect
|
|
2067
|
+
x={x}
|
|
2068
|
+
y={inpY}
|
|
2069
|
+
width={barWidth}
|
|
2070
|
+
height={inpHeight}
|
|
2071
|
+
fill="#a855f7"
|
|
2072
|
+
opacity="0.85"
|
|
2073
|
+
rx="1.5"
|
|
2074
|
+
/>
|
|
2075
|
+
{/* Output portion (yellow) */}
|
|
2076
|
+
<rect
|
|
2077
|
+
x={x}
|
|
2078
|
+
y={outY}
|
|
2079
|
+
width={barWidth}
|
|
2080
|
+
height={outHeight}
|
|
2081
|
+
fill="#eab308"
|
|
2082
|
+
opacity="0.9"
|
|
2083
|
+
rx="1.5"
|
|
2084
|
+
/>
|
|
2085
|
+
<title>{`${d.sessionName || d.type}\nCost: $${(d.cost || 0).toFixed(4)}\nInput: ${inp.toLocaleString()}\nOutput: ${out.toLocaleString()}`}</title>
|
|
2086
|
+
</g>
|
|
2087
|
+
);
|
|
2088
|
+
});
|
|
2089
|
+
})()}
|
|
2090
|
+
<line x1="10" y1="90" x2="230" y2="90" stroke="var(--border)" strokeWidth="1" />
|
|
2091
|
+
</svg>
|
|
2092
|
+
{/* Legend */}
|
|
2093
|
+
<div style={{ display: "flex", gap: "1rem", marginTop: "0.5rem", fontSize: "0.65rem" }}>
|
|
2094
|
+
<span style={{ display: "flex", alignItems: "center", gap: "0.25rem" }}>
|
|
2095
|
+
<span style={{ width: "8px", height: "8px", backgroundColor: "#a855f7", borderRadius: "2px" }}></span> Input
|
|
2096
|
+
</span>
|
|
2097
|
+
<span style={{ display: "flex", alignItems: "center", gap: "0.25rem" }}>
|
|
2098
|
+
<span style={{ width: "8px", height: "8px", backgroundColor: "#eab308", borderRadius: "2px" }}></span> Output
|
|
2099
|
+
</span>
|
|
2100
|
+
</div>
|
|
2101
|
+
</div>
|
|
2102
|
+
</div>
|
|
2103
|
+
)}
|
|
2104
|
+
|
|
2105
|
+
{/* Tabular Invocations Log */}
|
|
2106
|
+
{telemetryHistory.length > 0 && (
|
|
2107
|
+
<div>
|
|
2108
|
+
<div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
|
|
2109
|
+
Cumulative Log
|
|
2110
|
+
</div>
|
|
2111
|
+
<div style={{ overflowX: "auto", border: "1px solid var(--border)", borderRadius: "6px", backgroundColor: "#05070a" }}>
|
|
2112
|
+
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: "0.7rem", textAlign: "left" }}>
|
|
2113
|
+
<thead>
|
|
2114
|
+
<tr style={{ borderBottom: "1px solid var(--border)", backgroundColor: "rgba(255,255,255,0.02)" }}>
|
|
2115
|
+
<th style={{ padding: "0.4rem 0.5rem", color: "var(--muted)" }}>Agent</th>
|
|
2116
|
+
<th style={{ padding: "0.4rem 0.5rem", color: "var(--muted)" }}>Tokens</th>
|
|
2117
|
+
<th style={{ padding: "0.4rem 0.5rem", color: "var(--muted)" }}>Cost</th>
|
|
2118
|
+
<th style={{ padding: "0.4rem 0.5rem", color: "var(--muted)" }}>Time</th>
|
|
2119
|
+
</tr>
|
|
2120
|
+
</thead>
|
|
2121
|
+
<tbody>
|
|
2122
|
+
{telemetryHistory.slice(-8).reverse().map((item, index) => (
|
|
2123
|
+
<tr key={index} style={{ borderBottom: "1px solid rgba(255,255,255,0.03)" }}>
|
|
2124
|
+
<td style={{ padding: "0.4rem 0.5rem", fontWeight: 600 }}>{item.sessionName?.substring(0, 12) || item.type}</td>
|
|
2125
|
+
<td style={{ padding: "0.4rem 0.5rem" }}>
|
|
2126
|
+
{((item.inputTokens || 0) + (item.outputTokens || 0)).toLocaleString()}
|
|
2127
|
+
</td>
|
|
2128
|
+
<td style={{ padding: "0.4rem 0.5rem", color: "#10b981" }}>${(item.cost || 0).toFixed(4)}</td>
|
|
2129
|
+
<td style={{ padding: "0.4rem 0.5rem", color: "var(--muted)" }}>
|
|
2130
|
+
{new Date(item.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
|
2131
|
+
</td>
|
|
2132
|
+
</tr>
|
|
2133
|
+
))}
|
|
2134
|
+
</tbody>
|
|
2135
|
+
</table>
|
|
2136
|
+
</div>
|
|
2137
|
+
</div>
|
|
2138
|
+
)}
|
|
2139
|
+
|
|
2140
|
+
{/* Resource Telemetry */}
|
|
1853
2141
|
<div>
|
|
1854
2142
|
<div style={{ fontSize: "0.75rem", fontWeight: 700, textTransform: "uppercase", color: "var(--muted)", marginBottom: "0.5rem" }}>
|
|
1855
2143
|
Resource Telemetry
|
|
@@ -2090,7 +2378,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
2090
2378
|
<span style={{ fontSize: "0.6rem", color: "var(--muted)", backgroundColor: "rgba(255,255,255,0.05)", padding: "1px 4px", borderRadius: "3px" }}>
|
|
2091
2379
|
{s.type.replace("claude-", "").replace("aider-", "")}
|
|
2092
2380
|
</span>
|
|
2093
|
-
{s.status === 'running' && s.agentState && (
|
|
2381
|
+
{s.status === 'running' && s.agentState && s.type !== 'shell' && (
|
|
2094
2382
|
<span
|
|
2095
2383
|
style={{
|
|
2096
2384
|
fontSize: "0.6rem",
|
|
@@ -2215,19 +2503,136 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
2215
2503
|
|
|
2216
2504
|
{/* Editor contents */}
|
|
2217
2505
|
<div style={editorWorkspaceStyle}>
|
|
2218
|
-
{
|
|
2219
|
-
activeFile.
|
|
2220
|
-
<
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2506
|
+
<div style={{ display: "flex", flexDirection: "column", height: "100%", width: "100%" }}>
|
|
2507
|
+
{activeFile && parseMergeConflicts(activeFile.content).length > 0 && (
|
|
2508
|
+
<div style={{
|
|
2509
|
+
padding: "0.5rem 1rem",
|
|
2510
|
+
backgroundColor: "rgba(220, 38, 38, 0.15)",
|
|
2511
|
+
borderBottom: "1px solid var(--danger)",
|
|
2512
|
+
color: "var(--foreground)",
|
|
2513
|
+
fontSize: "0.8rem",
|
|
2514
|
+
display: "flex",
|
|
2515
|
+
flexDirection: "column",
|
|
2516
|
+
gap: "0.5rem",
|
|
2517
|
+
flexShrink: 0,
|
|
2518
|
+
zIndex: 20
|
|
2519
|
+
}}>
|
|
2520
|
+
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
|
2521
|
+
<span style={{ fontWeight: 600, color: "#f87171" }}>
|
|
2522
|
+
⚠️ {parseMergeConflicts(activeFile.content).length} Git Merge Conflict(s) Detected in this file!
|
|
2523
|
+
</span>
|
|
2524
|
+
</div>
|
|
2525
|
+
<div style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap" }}>
|
|
2526
|
+
{parseMergeConflicts(activeFile.content).slice(0, 3).map((conflict, idx) => (
|
|
2527
|
+
<div
|
|
2528
|
+
key={idx}
|
|
2529
|
+
style={{
|
|
2530
|
+
backgroundColor: "#05070a",
|
|
2531
|
+
border: "1px solid var(--border)",
|
|
2532
|
+
borderRadius: "4px",
|
|
2533
|
+
padding: "0.4rem 0.6rem",
|
|
2534
|
+
display: "flex",
|
|
2535
|
+
flexDirection: "column",
|
|
2536
|
+
gap: "0.35rem",
|
|
2537
|
+
flex: "1 1 200px",
|
|
2538
|
+
maxWidth: "350px"
|
|
2539
|
+
}}
|
|
2540
|
+
>
|
|
2541
|
+
<div style={{ fontSize: "0.7rem", color: "var(--muted)", fontWeight: 600 }}>Conflict Block #{idx + 1}</div>
|
|
2542
|
+
<div style={{ display: "flex", gap: "0.25rem" }}>
|
|
2543
|
+
<button
|
|
2544
|
+
onClick={() => {
|
|
2545
|
+
const newContent = activeFile.content.replace(conflict.rawText, conflict.current);
|
|
2546
|
+
handleContentChange(newContent);
|
|
2547
|
+
}}
|
|
2548
|
+
style={{
|
|
2549
|
+
padding: "0.2rem 0.4rem",
|
|
2550
|
+
borderRadius: "3px",
|
|
2551
|
+
backgroundColor: "var(--primary)",
|
|
2552
|
+
color: "#fff",
|
|
2553
|
+
border: "none",
|
|
2554
|
+
fontSize: "0.7rem",
|
|
2555
|
+
cursor: "pointer"
|
|
2556
|
+
}}
|
|
2557
|
+
>
|
|
2558
|
+
Accept Current
|
|
2559
|
+
</button>
|
|
2560
|
+
<button
|
|
2561
|
+
onClick={() => {
|
|
2562
|
+
const newContent = activeFile.content.replace(conflict.rawText, conflict.incoming);
|
|
2563
|
+
handleContentChange(newContent);
|
|
2564
|
+
}}
|
|
2565
|
+
style={{
|
|
2566
|
+
padding: "0.2rem 0.4rem",
|
|
2567
|
+
borderRadius: "3px",
|
|
2568
|
+
backgroundColor: "#10b981",
|
|
2569
|
+
color: "#fff",
|
|
2570
|
+
border: "none",
|
|
2571
|
+
fontSize: "0.7rem",
|
|
2572
|
+
cursor: "pointer"
|
|
2573
|
+
}}
|
|
2574
|
+
>
|
|
2575
|
+
Accept Incoming
|
|
2576
|
+
</button>
|
|
2577
|
+
<button
|
|
2578
|
+
onClick={() => {
|
|
2579
|
+
const newContent = activeFile.content.replace(conflict.rawText, conflict.current + "\n" + conflict.incoming);
|
|
2580
|
+
handleContentChange(newContent);
|
|
2581
|
+
}}
|
|
2582
|
+
style={{
|
|
2583
|
+
padding: "0.2rem 0.4rem",
|
|
2584
|
+
borderRadius: "3px",
|
|
2585
|
+
backgroundColor: "transparent",
|
|
2586
|
+
color: "var(--foreground)",
|
|
2587
|
+
border: "1px solid var(--border)",
|
|
2588
|
+
fontSize: "0.7rem",
|
|
2589
|
+
cursor: "pointer"
|
|
2590
|
+
}}
|
|
2591
|
+
>
|
|
2592
|
+
Accept Both
|
|
2593
|
+
</button>
|
|
2594
|
+
</div>
|
|
2595
|
+
</div>
|
|
2596
|
+
))}
|
|
2597
|
+
{parseMergeConflicts(activeFile.content).length > 3 && (
|
|
2598
|
+
<div style={{ display: "flex", alignItems: "center", fontSize: "0.75rem", color: "var(--muted)" }}>
|
|
2599
|
+
+ {parseMergeConflicts(activeFile.content).length - 3} more conflict blocks...
|
|
2600
|
+
</div>
|
|
2601
|
+
)}
|
|
2227
2602
|
</div>
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2603
|
+
</div>
|
|
2604
|
+
)}
|
|
2605
|
+
|
|
2606
|
+
<div style={{ flex: 1, minHeight: 0, position: "relative", width: "100%", height: "100%" }}>
|
|
2607
|
+
{activeFile ? (
|
|
2608
|
+
activeFile.path.startsWith("browser:") ? (
|
|
2609
|
+
<BrowserView url={activeFile.content} />
|
|
2610
|
+
) : activeFile.path.startsWith("diff:") ? (
|
|
2611
|
+
<DiffView content={activeFile.content} filePath={activeFile.path.substring(5)} />
|
|
2612
|
+
) : isMarkdown ? (
|
|
2613
|
+
currentMode === 'preview' ? (
|
|
2614
|
+
<div style={{ height: "100%", overflow: "hidden" }}>
|
|
2615
|
+
<MarkdownPreview content={activeFile.content} />
|
|
2616
|
+
</div>
|
|
2617
|
+
) : currentMode === 'split' ? (
|
|
2618
|
+
<div style={{ display: "flex", width: "100%", height: "100%", overflow: "hidden" }}>
|
|
2619
|
+
<div style={{ flex: 1, height: "100%" }}>
|
|
2620
|
+
<MonacoEditor
|
|
2621
|
+
height="100%"
|
|
2622
|
+
language="markdown"
|
|
2623
|
+
theme="neural-loom-dark"
|
|
2624
|
+
value={activeFile.content}
|
|
2625
|
+
onChange={(value) => handleContentChange(value || "")}
|
|
2626
|
+
beforeMount={handleEditorWillMount}
|
|
2627
|
+
options={editorOptions}
|
|
2628
|
+
loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
|
|
2629
|
+
/>
|
|
2630
|
+
</div>
|
|
2631
|
+
<div style={{ flex: 1, height: "100%", overflow: "hidden" }}>
|
|
2632
|
+
<MarkdownPreview content={activeFile.content} />
|
|
2633
|
+
</div>
|
|
2634
|
+
</div>
|
|
2635
|
+
) : (
|
|
2231
2636
|
<MonacoEditor
|
|
2232
2637
|
height="100%"
|
|
2233
2638
|
language="markdown"
|
|
@@ -2238,47 +2643,33 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
2238
2643
|
options={editorOptions}
|
|
2239
2644
|
loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
|
|
2240
2645
|
/>
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2646
|
+
)
|
|
2647
|
+
) : (
|
|
2648
|
+
<MonacoEditor
|
|
2649
|
+
height="100%"
|
|
2650
|
+
language={getEditorLanguage(activeFile.path)}
|
|
2651
|
+
theme="neural-loom-dark"
|
|
2652
|
+
value={activeFile.content}
|
|
2653
|
+
onChange={(value) => handleContentChange(value || "")}
|
|
2654
|
+
beforeMount={handleEditorWillMount}
|
|
2655
|
+
options={editorOptions}
|
|
2656
|
+
loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
|
|
2657
|
+
/>
|
|
2658
|
+
)
|
|
2246
2659
|
) : (
|
|
2247
|
-
<
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
)
|
|
2258
|
-
) : (
|
|
2259
|
-
<MonacoEditor
|
|
2260
|
-
height="100%"
|
|
2261
|
-
language={getEditorLanguage(activeFile.path)}
|
|
2262
|
-
theme="neural-loom-dark"
|
|
2263
|
-
value={activeFile.content}
|
|
2264
|
-
onChange={(value) => handleContentChange(value || "")}
|
|
2265
|
-
beforeMount={handleEditorWillMount}
|
|
2266
|
-
options={editorOptions}
|
|
2267
|
-
loading={<div style={{ padding: "1rem", color: "var(--muted)" }}>Loading Editor...</div>}
|
|
2268
|
-
/>
|
|
2269
|
-
)
|
|
2270
|
-
) : (
|
|
2271
|
-
<div style={emptyEditorStateStyle}>
|
|
2272
|
-
<div style={{ fontSize: "2rem", marginBottom: "1rem" }}>💻</div>
|
|
2273
|
-
<h4 style={{ margin: 0, fontWeight: 600 }}>NeuralLoom Workspace Editor</h4>
|
|
2274
|
-
<p style={{ margin: "0.5rem 0 0 0", color: "var(--muted)", fontSize: "0.8rem" }}>
|
|
2275
|
-
Select files from the left explorer panel to view and edit. Use Ctrl+S to save changes.
|
|
2276
|
-
</p>
|
|
2277
|
-
<p style={{ margin: "0.5rem 0 0 0", color: "var(--muted)", fontSize: "0.75rem", fontStyle: "italic" }}>
|
|
2278
|
-
Press <kbd style={{ background: "rgba(255,255,255,0.1)", padding: "2px 4px", borderRadius: "3px" }}>Ctrl+P</kbd> to search files.
|
|
2279
|
-
</p>
|
|
2660
|
+
<div style={emptyEditorStateStyle}>
|
|
2661
|
+
<div style={{ fontSize: "2rem", marginBottom: "1rem" }}>💻</div>
|
|
2662
|
+
<h4 style={{ margin: 0, fontWeight: 600 }}>NeuralLoom Workspace Editor</h4>
|
|
2663
|
+
<p style={{ margin: "0.5rem 0 0 0", color: "var(--muted)", fontSize: "0.8rem" }}>
|
|
2664
|
+
Select files from the left explorer panel to view and edit. Use Ctrl+S to save changes.
|
|
2665
|
+
</p>
|
|
2666
|
+
<p style={{ margin: "0.5rem 0 0 0", color: "var(--muted)", fontSize: "0.75rem", fontStyle: "italic" }}>
|
|
2667
|
+
Press <kbd style={{ background: "rgba(255,255,255,0.1)", padding: "2px 4px", borderRadius: "3px" }}>Ctrl+P</kbd> to search files.
|
|
2668
|
+
</p>
|
|
2669
|
+
</div>
|
|
2670
|
+
)}
|
|
2280
2671
|
</div>
|
|
2281
|
-
|
|
2672
|
+
</div>
|
|
2282
2673
|
</div>
|
|
2283
2674
|
</div>
|
|
2284
2675
|
|
|
@@ -2306,9 +2697,94 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
2306
2697
|
<div style={{ ...terminalPanelStyle, flex: "none", height: `${terminalHeight}px` }}>
|
|
2307
2698
|
{/* Dashboard control headers injected above xterm */}
|
|
2308
2699
|
<div style={terminalHeaderStyle}>
|
|
2309
|
-
<
|
|
2310
|
-
|
|
2311
|
-
|
|
2700
|
+
<div style={{ display: "flex", alignItems: "flex-end", gap: "0.25rem", height: "100%", overflowX: "auto" }}>
|
|
2701
|
+
<div
|
|
2702
|
+
style={{
|
|
2703
|
+
padding: "0.25rem 0.75rem",
|
|
2704
|
+
backgroundColor: consoleSessionId === sessionId ? "#0f131a" : "transparent",
|
|
2705
|
+
border: "1px solid " + (consoleSessionId === sessionId ? "var(--border-focus)" : "transparent"),
|
|
2706
|
+
borderBottom: consoleSessionId === sessionId ? "1px solid #0f131a" : "none",
|
|
2707
|
+
borderRadius: "4px 4px 0 0",
|
|
2708
|
+
color: consoleSessionId === sessionId ? "var(--foreground)" : "var(--muted)",
|
|
2709
|
+
fontSize: "0.75rem",
|
|
2710
|
+
fontWeight: consoleSessionId === sessionId ? 600 : 500,
|
|
2711
|
+
cursor: "pointer",
|
|
2712
|
+
display: "flex",
|
|
2713
|
+
alignItems: "center",
|
|
2714
|
+
gap: "0.5rem",
|
|
2715
|
+
height: "100%",
|
|
2716
|
+
position: "relative",
|
|
2717
|
+
top: "1px"
|
|
2718
|
+
}}
|
|
2719
|
+
onClick={() => setConsoleSessionId(sessionId)}
|
|
2720
|
+
>
|
|
2721
|
+
🤖 {activeSession?.name || "Agent PTY"}
|
|
2722
|
+
</div>
|
|
2723
|
+
{workspaceShells.map((shell) => (
|
|
2724
|
+
<div
|
|
2725
|
+
key={shell.id}
|
|
2726
|
+
style={{
|
|
2727
|
+
padding: "0.25rem 0.75rem",
|
|
2728
|
+
backgroundColor: consoleSessionId === shell.id ? "#0f131a" : "transparent",
|
|
2729
|
+
border: "1px solid " + (consoleSessionId === shell.id ? "var(--border-focus)" : "transparent"),
|
|
2730
|
+
borderBottom: consoleSessionId === shell.id ? "1px solid #0f131a" : "none",
|
|
2731
|
+
borderRadius: "4px 4px 0 0",
|
|
2732
|
+
color: consoleSessionId === shell.id ? "var(--foreground)" : "var(--muted)",
|
|
2733
|
+
fontSize: "0.75rem",
|
|
2734
|
+
fontWeight: consoleSessionId === shell.id ? 600 : 500,
|
|
2735
|
+
cursor: "pointer",
|
|
2736
|
+
display: "flex",
|
|
2737
|
+
alignItems: "center",
|
|
2738
|
+
gap: "0.5rem",
|
|
2739
|
+
height: "100%",
|
|
2740
|
+
position: "relative",
|
|
2741
|
+
top: "1px"
|
|
2742
|
+
}}
|
|
2743
|
+
onClick={() => setConsoleSessionId(shell.id)}
|
|
2744
|
+
>
|
|
2745
|
+
🐚 {shell.name}
|
|
2746
|
+
<button
|
|
2747
|
+
onClick={(e) => handleCloseShell(shell.id, e)}
|
|
2748
|
+
style={{
|
|
2749
|
+
background: "none",
|
|
2750
|
+
border: "none",
|
|
2751
|
+
color: "var(--danger)",
|
|
2752
|
+
fontSize: "0.7rem",
|
|
2753
|
+
cursor: "pointer",
|
|
2754
|
+
padding: "0 0.15rem",
|
|
2755
|
+
display: "flex",
|
|
2756
|
+
alignItems: "center",
|
|
2757
|
+
justifyContent: "center",
|
|
2758
|
+
borderRadius: "3px"
|
|
2759
|
+
}}
|
|
2760
|
+
title="Close terminal tab"
|
|
2761
|
+
>
|
|
2762
|
+
✖
|
|
2763
|
+
</button>
|
|
2764
|
+
</div>
|
|
2765
|
+
))}
|
|
2766
|
+
<button
|
|
2767
|
+
onClick={handleCreateShell}
|
|
2768
|
+
style={{
|
|
2769
|
+
background: "rgba(255,255,255,0.03)",
|
|
2770
|
+
border: "1px solid var(--border)",
|
|
2771
|
+
borderRadius: "4px",
|
|
2772
|
+
color: "var(--muted)",
|
|
2773
|
+
fontSize: "0.8rem",
|
|
2774
|
+
padding: "0.1rem 0.4rem",
|
|
2775
|
+
cursor: "pointer",
|
|
2776
|
+
display: "flex",
|
|
2777
|
+
alignItems: "center",
|
|
2778
|
+
justifyContent: "center",
|
|
2779
|
+
marginLeft: "0.5rem",
|
|
2780
|
+
height: "20px",
|
|
2781
|
+
alignSelf: "center"
|
|
2782
|
+
}}
|
|
2783
|
+
title="Open new terminal tab"
|
|
2784
|
+
>
|
|
2785
|
+
+
|
|
2786
|
+
</button>
|
|
2787
|
+
</div>
|
|
2312
2788
|
|
|
2313
2789
|
<div style={{ display: "flex", gap: "0.5rem", alignItems: "center" }}>
|
|
2314
2790
|
{activeSession?.status === "stopped" && (
|
|
@@ -2442,7 +2918,7 @@ export default function IdeLayout({ sessionId, workspaceRoot, scopedDirs, onClos
|
|
|
2442
2918
|
|
|
2443
2919
|
<div style={{ ...terminalConsoleWrapperStyle, display: "flex", width: "100%" }}>
|
|
2444
2920
|
<div style={{ flex: 1, height: "100%", borderRight: splitSessionId ? "1px solid var(--border)" : "none", overflow: "hidden" }}>
|
|
2445
|
-
<TerminalConsole sessionId={
|
|
2921
|
+
<TerminalConsole sessionId={consoleSessionId} height="100%" wsPort={wsPort} />
|
|
2446
2922
|
</div>
|
|
2447
2923
|
{splitSessionId && (
|
|
2448
2924
|
<div style={{ flex: 1, height: "100%", overflow: "hidden", display: "flex", flexDirection: "column" }}>
|
|
@@ -3393,6 +3869,7 @@ function BrowserView({ url }: BrowserViewProps) {
|
|
|
3393
3869
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
3394
3870
|
|
|
3395
3871
|
useEffect(() => {
|
|
3872
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
3396
3873
|
setCurrentUrl(url);
|
|
3397
3874
|
setIframeUrl(url);
|
|
3398
3875
|
}, [url]);
|
|
@@ -3527,17 +4004,21 @@ function BrowserView({ url }: BrowserViewProps) {
|
|
|
3527
4004
|
);
|
|
3528
4005
|
}
|
|
3529
4006
|
|
|
3530
|
-
interface
|
|
4007
|
+
interface ParsedDiffLine {
|
|
4008
|
+
type: 'meta' | 'header' | 'hunk' | 'addition' | 'deletion' | 'normal';
|
|
3531
4009
|
content: string;
|
|
3532
|
-
|
|
4010
|
+
oldNum: number | null;
|
|
4011
|
+
newNum: number | null;
|
|
4012
|
+
raw: string;
|
|
3533
4013
|
}
|
|
3534
4014
|
|
|
3535
|
-
function
|
|
3536
|
-
|
|
3537
|
-
|
|
4015
|
+
function parseDiffLines(lines: string[]): {
|
|
4016
|
+
parsedLines: ParsedDiffLine[];
|
|
4017
|
+
additions: number;
|
|
4018
|
+
deletions: number;
|
|
4019
|
+
} {
|
|
3538
4020
|
let oldLineNum = 0;
|
|
3539
4021
|
let newLineNum = 0;
|
|
3540
|
-
|
|
3541
4022
|
let additions = 0;
|
|
3542
4023
|
let deletions = 0;
|
|
3543
4024
|
|
|
@@ -3587,6 +4068,21 @@ function DiffView({ content, filePath }: DiffViewProps) {
|
|
|
3587
4068
|
raw: line
|
|
3588
4069
|
};
|
|
3589
4070
|
});
|
|
4071
|
+
|
|
4072
|
+
return { parsedLines, additions, deletions };
|
|
4073
|
+
}
|
|
4074
|
+
|
|
4075
|
+
interface DiffViewProps {
|
|
4076
|
+
content: string;
|
|
4077
|
+
filePath: string;
|
|
4078
|
+
}
|
|
4079
|
+
|
|
4080
|
+
function DiffView({ content, filePath }: DiffViewProps) {
|
|
4081
|
+
const lines = content.split(/\r?\n/);
|
|
4082
|
+
|
|
4083
|
+
const { parsedLines, additions, deletions } = useMemo(() => {
|
|
4084
|
+
return parseDiffLines(lines);
|
|
4085
|
+
}, [lines]);
|
|
3590
4086
|
|
|
3591
4087
|
return (
|
|
3592
4088
|
<div style={{ display: "flex", flexDirection: "column", height: "100%", backgroundColor: "#0b101b" }}>
|