@abhinav2203/codeflow-canvas 0.1.0
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/abhinav2203-codeflow-canvas-0.1.0.tgz +0 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +84 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/components/blueprint-workbench.d.ts +2 -0
- package/dist/components/blueprint-workbench.d.ts.map +1 -0
- package/dist/components/blueprint-workbench.js +144 -0
- package/dist/components/blueprint-workbench.js.map +1 -0
- package/dist/components/code-diff-editor.d.ts +12 -0
- package/dist/components/code-diff-editor.d.ts.map +1 -0
- package/dist/components/code-diff-editor.js +39 -0
- package/dist/components/code-diff-editor.js.map +1 -0
- package/dist/components/code-editor.d.ts +25 -0
- package/dist/components/code-editor.d.ts.map +1 -0
- package/dist/components/code-editor.js +264 -0
- package/dist/components/code-editor.js.map +1 -0
- package/dist/components/file-tabs.d.ts +5 -0
- package/dist/components/file-tabs.d.ts.map +1 -0
- package/dist/components/file-tabs.js +164 -0
- package/dist/components/file-tabs.js.map +1 -0
- package/dist/components/file-tree.d.ts +7 -0
- package/dist/components/file-tree.d.ts.map +1 -0
- package/dist/components/file-tree.js +176 -0
- package/dist/components/file-tree.js.map +1 -0
- package/dist/components/graph-canvas.d.ts +25 -0
- package/dist/components/graph-canvas.d.ts.map +1 -0
- package/dist/components/graph-canvas.js +224 -0
- package/dist/components/graph-canvas.js.map +1 -0
- package/dist/components/ide-layout.d.ts +10 -0
- package/dist/components/ide-layout.d.ts.map +1 -0
- package/dist/components/ide-layout.js +40 -0
- package/dist/components/ide-layout.js.map +1 -0
- package/dist/components/ide-workbench.d.ts +4 -0
- package/dist/components/ide-workbench.d.ts.map +1 -0
- package/dist/components/ide-workbench.js +6 -0
- package/dist/components/ide-workbench.js.map +1 -0
- package/dist/components/index.d.ts +13 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +13 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/monaco-setup.d.ts +4 -0
- package/dist/components/monaco-setup.d.ts.map +1 -0
- package/dist/components/monaco-setup.js +34 -0
- package/dist/components/monaco-setup.js.map +1 -0
- package/dist/components/opencode-settings.d.ts +8 -0
- package/dist/components/opencode-settings.d.ts.map +1 -0
- package/dist/components/opencode-settings.js +33 -0
- package/dist/components/opencode-settings.js.map +1 -0
- package/dist/components/policy-workbench.d.ts +2 -0
- package/dist/components/policy-workbench.d.ts.map +1 -0
- package/dist/components/policy-workbench.js +102 -0
- package/dist/components/policy-workbench.js.map +1 -0
- package/dist/components/ts-language-service.d.ts +14 -0
- package/dist/components/ts-language-service.d.ts.map +1 -0
- package/dist/components/ts-language-service.js +123 -0
- package/dist/components/ts-language-service.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/browser/storage.d.ts +16 -0
- package/dist/lib/browser/storage.d.ts.map +1 -0
- package/dist/lib/browser/storage.js +18 -0
- package/dist/lib/browser/storage.js.map +1 -0
- package/dist/lib/edit.d.ts +14 -0
- package/dist/lib/edit.d.ts.map +1 -0
- package/dist/lib/edit.js +57 -0
- package/dist/lib/edit.js.map +1 -0
- package/dist/lib/flow-view.d.ts +80 -0
- package/dist/lib/flow-view.d.ts.map +1 -0
- package/dist/lib/flow-view.js +850 -0
- package/dist/lib/flow-view.js.map +1 -0
- package/dist/lib/heatmap.d.ts +28 -0
- package/dist/lib/heatmap.d.ts.map +1 -0
- package/dist/lib/heatmap.js +61 -0
- package/dist/lib/heatmap.js.map +1 -0
- package/dist/lib/index.d.ts +9 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/node-navigation.d.ts +36 -0
- package/dist/lib/node-navigation.d.ts.map +1 -0
- package/dist/lib/node-navigation.js +52 -0
- package/dist/lib/node-navigation.js.map +1 -0
- package/dist/lib/traces.d.ts +3 -0
- package/dist/lib/traces.d.ts.map +1 -0
- package/dist/lib/traces.js +64 -0
- package/dist/lib/traces.js.map +1 -0
- package/dist/lib/types.d.ts +57 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +7 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/store/blueprint-store.d.ts +35 -0
- package/dist/store/blueprint-store.d.ts.map +1 -0
- package/dist/store/blueprint-store.js +79 -0
- package/dist/store/blueprint-store.js.map +1 -0
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/package.json +52 -0
- package/scripts/wrap-cli.mjs +15 -0
- package/src/bin/cli.ts +128 -0
- package/src/components/blueprint-workbench.tsx +305 -0
- package/src/components/code-diff-editor.tsx +80 -0
- package/src/components/code-editor.tsx +389 -0
- package/src/components/file-tabs.tsx +288 -0
- package/src/components/file-tree.tsx +301 -0
- package/src/components/graph-canvas.tsx +404 -0
- package/src/components/ide-layout.tsx +104 -0
- package/src/components/ide-workbench.tsx +5 -0
- package/src/components/index.ts +12 -0
- package/src/components/monaco-setup.ts +67 -0
- package/src/components/opencode-settings.tsx +82 -0
- package/src/components/policy-workbench.tsx +233 -0
- package/src/components/ts-language-service.ts +170 -0
- package/src/index.ts +54 -0
- package/src/lib/browser/storage.ts +19 -0
- package/src/lib/edit.ts +74 -0
- package/src/lib/flow-view.ts +1176 -0
- package/src/lib/heatmap.ts +103 -0
- package/src/lib/index.ts +41 -0
- package/src/lib/node-navigation.ts +76 -0
- package/src/lib/traces.ts +79 -0
- package/src/lib/types.ts +79 -0
- package/src/store/blueprint-store.ts +136 -0
- package/src/store/index.ts +2 -0
- package/test-fixtures/minimal-blueprint.json +34 -0
- package/test-fixtures/sample-blueprint.json +184 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +22 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo } from "react";
|
|
4
|
+
import { Rnd, type RndDragCallback, type RndResizeCallback } from "react-rnd";
|
|
5
|
+
|
|
6
|
+
import { useBlueprintStore } from "../store/blueprint-store.js";
|
|
7
|
+
|
|
8
|
+
function getDefaultFloatingGraphBounds() {
|
|
9
|
+
if (typeof window === "undefined") {
|
|
10
|
+
return { x: 48, y: 48, width: 420, height: 320 };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const width = Math.max(360, Math.round(window.innerWidth * 0.3));
|
|
14
|
+
const height = Math.max(260, Math.round(window.innerHeight * 0.35));
|
|
15
|
+
const x = Math.max(24, window.innerWidth - width - 40);
|
|
16
|
+
const y = Math.max(24, window.innerHeight - height - 120);
|
|
17
|
+
|
|
18
|
+
return { x, y, width, height };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type IdeLayoutProps = {
|
|
22
|
+
explorer: React.ReactNode;
|
|
23
|
+
mainContent: React.ReactNode;
|
|
24
|
+
bottomPanel?: React.ReactNode;
|
|
25
|
+
floatingGraphContent?: React.ReactNode;
|
|
26
|
+
rightSidebar?: React.ReactNode;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function IdeLayout({
|
|
30
|
+
explorer,
|
|
31
|
+
mainContent,
|
|
32
|
+
bottomPanel,
|
|
33
|
+
floatingGraphContent,
|
|
34
|
+
rightSidebar
|
|
35
|
+
}: IdeLayoutProps) {
|
|
36
|
+
const { activeFile, floatingGraph, setFloatingGraph } = useBlueprintStore();
|
|
37
|
+
|
|
38
|
+
const resolvedFloatingGraph = useMemo(() => {
|
|
39
|
+
const defaults = getDefaultFloatingGraphBounds();
|
|
40
|
+
return {
|
|
41
|
+
x: floatingGraph.x || defaults.x,
|
|
42
|
+
y: floatingGraph.y || defaults.y,
|
|
43
|
+
width: floatingGraph.width || defaults.width,
|
|
44
|
+
height: floatingGraph.height || defaults.height
|
|
45
|
+
};
|
|
46
|
+
}, [floatingGraph.height, floatingGraph.width, floatingGraph.x, floatingGraph.y]);
|
|
47
|
+
|
|
48
|
+
const handleDragStop: RndDragCallback = useCallback(
|
|
49
|
+
(_event, data) => {
|
|
50
|
+
setFloatingGraph({ x: data.x, y: data.y });
|
|
51
|
+
},
|
|
52
|
+
[setFloatingGraph]
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const handleResizeStop: RndResizeCallback = useCallback(
|
|
56
|
+
(_event, _direction, ref, _delta, position) => {
|
|
57
|
+
setFloatingGraph({
|
|
58
|
+
x: position.x,
|
|
59
|
+
y: position.y,
|
|
60
|
+
width: parseInt(ref.style.width, 10),
|
|
61
|
+
height: parseInt(ref.style.height, 10)
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
[setFloatingGraph]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="ide-layout-shell">
|
|
69
|
+
<aside className="ide-left-sidebar">
|
|
70
|
+
<div className="ide-pane-header">Explorer</div>
|
|
71
|
+
<div className="ide-pane-body">{explorer}</div>
|
|
72
|
+
</aside>
|
|
73
|
+
<div className="ide-main-stack">
|
|
74
|
+
<main className="ide-main-area">
|
|
75
|
+
{mainContent}
|
|
76
|
+
{activeFile && floatingGraph.visible && floatingGraphContent ? (
|
|
77
|
+
<Rnd
|
|
78
|
+
bounds="parent"
|
|
79
|
+
className="ide-floating-graph"
|
|
80
|
+
dragHandleClassName="ide-floating-graph-header"
|
|
81
|
+
minHeight={220}
|
|
82
|
+
minWidth={320}
|
|
83
|
+
onDragStop={handleDragStop}
|
|
84
|
+
onResizeStop={handleResizeStop}
|
|
85
|
+
position={{ x: resolvedFloatingGraph.x, y: resolvedFloatingGraph.y }}
|
|
86
|
+
size={{ width: resolvedFloatingGraph.width, height: resolvedFloatingGraph.height }}
|
|
87
|
+
>
|
|
88
|
+
<div className="ide-floating-graph-header">
|
|
89
|
+
<span>Live Graph</span>
|
|
90
|
+
<span>Drag to reposition</span>
|
|
91
|
+
</div>
|
|
92
|
+
<div className="ide-floating-graph-body">{floatingGraphContent}</div>
|
|
93
|
+
</Rnd>
|
|
94
|
+
) : null}
|
|
95
|
+
</main>
|
|
96
|
+
<section className="ide-bottom-panel">{bottomPanel}</section>
|
|
97
|
+
</div>
|
|
98
|
+
<aside className="ide-right-sidebar">
|
|
99
|
+
<div className="ide-pane-header">Agent</div>
|
|
100
|
+
<div className="ide-pane-body">{rightSidebar ?? <div className="ide-agent-slot" />}</div>
|
|
101
|
+
</aside>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { IdeLayout } from "./ide-layout.js";
|
|
2
|
+
export { FileTree } from "./file-tree.js";
|
|
3
|
+
export { FileTabs } from "./file-tabs.js";
|
|
4
|
+
export { GraphCanvas } from "./graph-canvas.js";
|
|
5
|
+
export { CodeEditor } from "./code-editor.js";
|
|
6
|
+
export { CodeDiffEditor } from "./code-diff-editor.js";
|
|
7
|
+
export { BlueprintWorkbench } from "./blueprint-workbench.js";
|
|
8
|
+
export { PolicyWorkbench } from "./policy-workbench.js";
|
|
9
|
+
export { IdeWorkbench } from "./ide-workbench.js";
|
|
10
|
+
export { OpencodeSettings } from "./opencode-settings.js";
|
|
11
|
+
export { prepareMonaco, toMonacoPath } from "./monaco-setup.js";
|
|
12
|
+
export { TypeScriptLanguageService, getTypeScriptLanguageService } from "./ts-language-service.js";
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type * as Monaco from "monaco-editor";
|
|
2
|
+
|
|
3
|
+
import { getTypeScriptLanguageService } from "./ts-language-service.js";
|
|
4
|
+
|
|
5
|
+
type MonacoEnvironmentShape = {
|
|
6
|
+
getWorker: (_workerId: string, label: string) => Worker;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type MonacoGlobal = typeof globalThis & {
|
|
10
|
+
MonacoEnvironment?: MonacoEnvironmentShape;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let workersConfigured = false;
|
|
14
|
+
|
|
15
|
+
export function prepareMonaco(monaco: typeof Monaco): void {
|
|
16
|
+
if (!workersConfigured) {
|
|
17
|
+
const monacoGlobal = globalThis as MonacoGlobal;
|
|
18
|
+
monacoGlobal.MonacoEnvironment = {
|
|
19
|
+
getWorker(_workerId, label) {
|
|
20
|
+
if (label === "typescript" || label === "javascript") {
|
|
21
|
+
return new Worker(
|
|
22
|
+
new URL("monaco-editor/esm/vs/language/typescript/ts.worker.js", import.meta.url),
|
|
23
|
+
{ type: "module" }
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (label === "json") {
|
|
28
|
+
return new Worker(
|
|
29
|
+
new URL("monaco-editor/esm/vs/language/json/json.worker.js", import.meta.url),
|
|
30
|
+
{ type: "module" }
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (label === "css" || label === "scss" || label === "less") {
|
|
35
|
+
return new Worker(
|
|
36
|
+
new URL("monaco-editor/esm/vs/language/css/css.worker.js", import.meta.url),
|
|
37
|
+
{ type: "module" }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (label === "html" || label === "handlebars" || label === "razor") {
|
|
42
|
+
return new Worker(
|
|
43
|
+
new URL("monaco-editor/esm/vs/language/html/html.worker.js", import.meta.url),
|
|
44
|
+
{ type: "module" }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return new Worker(
|
|
49
|
+
new URL("monaco-editor/esm/vs/editor/editor.worker.js", import.meta.url),
|
|
50
|
+
{ type: "module" }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
workersConfigured = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getTypeScriptLanguageService(monaco).configureDefaults();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function toMonacoPath(filePath: string): string {
|
|
61
|
+
if (filePath.startsWith("file://")) {
|
|
62
|
+
return filePath;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const normalized = filePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
66
|
+
return `file:///${normalized}`;
|
|
67
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import type { OpencodeProvider, OpencodeServerInfo } from "../lib/types.js";
|
|
5
|
+
|
|
6
|
+
const PROVIDERS: { id: OpencodeProvider; label: string }[] = [
|
|
7
|
+
{ id: "anthropic", label: "Anthropic (Claude)" },
|
|
8
|
+
{ id: "openai", label: "OpenAI (GPT)" },
|
|
9
|
+
{ id: "google", label: "Google (Gemini)" },
|
|
10
|
+
{ id: "azure", label: "Azure OpenAI" },
|
|
11
|
+
{ id: "groq", label: "Groq" },
|
|
12
|
+
{ id: "mistral", label: "Mistral" },
|
|
13
|
+
{ id: "cohere", label: "Cohere" },
|
|
14
|
+
{ id: "perplexity", label: "Perplexity" },
|
|
15
|
+
{ id: "openrouter", label: "OpenRouter" },
|
|
16
|
+
{ id: "bedrock", label: "AWS Bedrock" },
|
|
17
|
+
{ id: "local", label: "Local Model" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
type Props = {
|
|
21
|
+
onClose?: () => void;
|
|
22
|
+
onStatusChange?: (status: OpencodeServerInfo) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function OpencodeSettings({ onClose, onStatusChange }: Props) {
|
|
26
|
+
const [provider, setProvider] = useState<OpencodeProvider>("anthropic");
|
|
27
|
+
const [apiKey, setApiKey] = useState("");
|
|
28
|
+
const [model, setModel] = useState("");
|
|
29
|
+
const [baseUrl, setBaseUrl] = useState("");
|
|
30
|
+
const [serverStatus, setServerStatus] = useState<OpencodeServerInfo>({ status: "stopped" });
|
|
31
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
32
|
+
const [error, setError] = useState<string | null>(null);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="opencode-settings">
|
|
36
|
+
<div className="opencode-settings-header">
|
|
37
|
+
<h3>OpenCode Agent Settings</h3>
|
|
38
|
+
{onClose && (
|
|
39
|
+
<button onClick={onClose} type="button" className="close-btn">
|
|
40
|
+
×
|
|
41
|
+
</button>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div className="server-status-bar">
|
|
46
|
+
<span className={`status-indicator status-${serverStatus.status}`} />
|
|
47
|
+
<span className="status-text">
|
|
48
|
+
{serverStatus.status === "running"
|
|
49
|
+
? `Connected (${serverStatus.url})`
|
|
50
|
+
: serverStatus.status === "starting"
|
|
51
|
+
? "Starting..."
|
|
52
|
+
: serverStatus.status === "error"
|
|
53
|
+
? `Error: ${serverStatus.error}`
|
|
54
|
+
: "Not connected"}
|
|
55
|
+
</span>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
{error && <div className="error-message">{error}</div>}
|
|
59
|
+
|
|
60
|
+
<label className="field">
|
|
61
|
+
<span>AI Provider</span>
|
|
62
|
+
<select value={provider} onChange={(e) => setProvider(e.target.value as OpencodeProvider)}>
|
|
63
|
+
{PROVIDERS.map((p) => (
|
|
64
|
+
<option key={p.id} value={p.id}>
|
|
65
|
+
{p.label}
|
|
66
|
+
</option>
|
|
67
|
+
))}
|
|
68
|
+
</select>
|
|
69
|
+
</label>
|
|
70
|
+
|
|
71
|
+
<label className="field">
|
|
72
|
+
<span>API Key</span>
|
|
73
|
+
<input
|
|
74
|
+
type="password"
|
|
75
|
+
value={apiKey}
|
|
76
|
+
onChange={(e) => setApiKey(e.target.value)}
|
|
77
|
+
placeholder={`Enter your ${PROVIDERS.find((p) => p.id === provider)?.label || provider} API key`}
|
|
78
|
+
/>
|
|
79
|
+
</label>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { QueryResult } from "@abhinav2203/coderag";
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from "react";
|
|
6
|
+
|
|
7
|
+
import { CodeEditor } from "./code-editor.js";
|
|
8
|
+
import { GraphCanvas } from "./graph-canvas.js";
|
|
9
|
+
import { buildDetailFlow } from "../lib/flow-view.js";
|
|
10
|
+
import { computeHeatmap } from "../lib/heatmap.js";
|
|
11
|
+
import type { HeatmapData } from "../lib/heatmap.js";
|
|
12
|
+
import type { CycleReport, SmellReport, GraphMetrics } from "../lib/types.js";
|
|
13
|
+
import type {
|
|
14
|
+
ApprovalRecord,
|
|
15
|
+
BlueprintGraph,
|
|
16
|
+
BlueprintNode,
|
|
17
|
+
ConflictReport,
|
|
18
|
+
ExecutionMode,
|
|
19
|
+
ExportResult,
|
|
20
|
+
ObservabilityLog,
|
|
21
|
+
PersistedSession,
|
|
22
|
+
RiskReport,
|
|
23
|
+
RunPlan,
|
|
24
|
+
RuntimeExecutionResult
|
|
25
|
+
} from "@abhinav2203/codeflow-core/schema";
|
|
26
|
+
import { emptyContract, traceSpanSchema } from "@abhinav2203/codeflow-core/schema";
|
|
27
|
+
import {
|
|
28
|
+
AUTO_IMPLEMENT_STORAGE_KEY,
|
|
29
|
+
LIVE_COMPLETIONS_STORAGE_KEY,
|
|
30
|
+
THEME_STORAGE_KEY,
|
|
31
|
+
loadSessionApiKey,
|
|
32
|
+
readLocalBooleanPreference,
|
|
33
|
+
readLocalPreference,
|
|
34
|
+
storeSessionApiKey,
|
|
35
|
+
writeLocalBooleanPreference,
|
|
36
|
+
writeLocalPreference
|
|
37
|
+
} from "../lib/browser/storage.js";
|
|
38
|
+
|
|
39
|
+
type BuildResponse = {
|
|
40
|
+
graph?: BlueprintGraph;
|
|
41
|
+
runPlan?: RunPlan;
|
|
42
|
+
session?: PersistedSession;
|
|
43
|
+
error?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type ExportResponse = {
|
|
47
|
+
result?: ExportResult;
|
|
48
|
+
runPlan?: RunPlan;
|
|
49
|
+
riskReport?: RiskReport;
|
|
50
|
+
session?: PersistedSession;
|
|
51
|
+
approval?: ApprovalRecord;
|
|
52
|
+
requiresApproval?: boolean;
|
|
53
|
+
error?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type ExecutionResponse = {
|
|
57
|
+
result?: RuntimeExecutionResult;
|
|
58
|
+
executedNodeId?: string;
|
|
59
|
+
graph?: BlueprintGraph;
|
|
60
|
+
runPlan?: RunPlan;
|
|
61
|
+
session?: PersistedSession;
|
|
62
|
+
error?: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type ObservabilityLatestResponse = {
|
|
66
|
+
graph?: BlueprintGraph | null;
|
|
67
|
+
latestSpans?: Array<{ spanId: string; name: string; status: string; runtime: string }>;
|
|
68
|
+
latestLogs?: ObservabilityLog[];
|
|
69
|
+
error?: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type ConflictResponse = {
|
|
73
|
+
report?: ConflictReport;
|
|
74
|
+
error?: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type CyclesResponse = {
|
|
78
|
+
report?: CycleReport;
|
|
79
|
+
error?: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type SmellsResponse = {
|
|
83
|
+
report?: SmellReport;
|
|
84
|
+
error?: string;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
type MetricsResponse = {
|
|
88
|
+
metrics?: GraphMetrics;
|
|
89
|
+
error?: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
type StatusTone = "info" | "success" | "danger";
|
|
93
|
+
type ThemePreference = "system" | "light" | "dark";
|
|
94
|
+
type ResolvedTheme = "light" | "dark";
|
|
95
|
+
|
|
96
|
+
const POLICY_CANVAS_PROMPT = `Act as an Enterprise Mobility Architect. Using the Google STITCH MCP server, design a secure Engineering Department device profile.
|
|
97
|
+
|
|
98
|
+
Create nodes for a managed Chrome browser policy enabling developer tools while disabling insecure extensions.
|
|
99
|
+
Add a node for a corporate VPN configuration.
|
|
100
|
+
Wire these to a Fleet: Engineering-Laptops group node.
|
|
101
|
+
Verify the STITCH contract before sync: every policy needs a valid version ID and the VPN policy needs its certificate reference.
|
|
102
|
+
If a policy node drifts from schema, mark it Invalid, highlight it in red, and suggest a Heal fix based on the latest Google management API spec.`;
|
|
103
|
+
|
|
104
|
+
const maskApiKey = (value: string) => {
|
|
105
|
+
const trimmed = value.trim();
|
|
106
|
+
if (trimmed.length <= 8) {
|
|
107
|
+
return trimmed;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return `${trimmed.slice(0, 4)}...${trimmed.slice(-4)}`;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export function PolicyWorkbench() {
|
|
114
|
+
const MIN_OBSERVABILITY_INTERVAL_SECS = 2;
|
|
115
|
+
const [projectName, setProjectName] = useState("CodeFlow Workspace");
|
|
116
|
+
const [repoPath, setRepoPath] = useState("");
|
|
117
|
+
const [prdText, setPrdText] = useState("");
|
|
118
|
+
const [aiPrompt, setAiPrompt] = useState("");
|
|
119
|
+
const [nvidiaApiKey, setNvidiaApiKey] = useState("");
|
|
120
|
+
const [mode, setMode] = useState<ExecutionMode>("essential");
|
|
121
|
+
const [outputDir, setOutputDir] = useState("");
|
|
122
|
+
const [traceInput, setTraceInput] = useState("");
|
|
123
|
+
const [runInput, setRunInput] = useState("{}");
|
|
124
|
+
const [graph, setGraph] = useState<BlueprintGraph | null>(null);
|
|
125
|
+
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
|
|
126
|
+
const [error, setError] = useState<string | null>(null);
|
|
127
|
+
const [busyLabel, setBusyLabel] = useState<string | null>(null);
|
|
128
|
+
const [exportResult, setExportResult] = useState<ExportResult | null>(null);
|
|
129
|
+
const [runPlan, setRunPlan] = useState<RunPlan | null>(null);
|
|
130
|
+
const [riskReport, setRiskReport] = useState<RiskReport | null>(null);
|
|
131
|
+
const [session, setSession] = useState<PersistedSession | null>(null);
|
|
132
|
+
const [pendingApproval, setPendingApproval] = useState<ApprovalRecord | null>(null);
|
|
133
|
+
const [executionResult, setExecutionResult] = useState<RuntimeExecutionResult | null>(null);
|
|
134
|
+
const [latestLogs, setLatestLogs] = useState<ObservabilityLog[]>([]);
|
|
135
|
+
const [latestSpans, setLatestSpans] = useState<ObservabilityLatestResponse["latestSpans"]>([]);
|
|
136
|
+
const [conflictReport, setConflictReport] = useState<ConflictReport | null>(null);
|
|
137
|
+
const [newNodeName, setNewNodeName] = useState("");
|
|
138
|
+
const [newNodeKind, setNewNodeKind] = useState<BlueprintNode["kind"]>("function");
|
|
139
|
+
const [edgeFrom, setEdgeFrom] = useState("");
|
|
140
|
+
const [edgeTo, setEdgeTo] = useState("");
|
|
141
|
+
const [edgeKind, setEdgeKind] = useState<"calls" | "imports" | "inherits">("calls");
|
|
142
|
+
const [useAI, setUseAI] = useState(true);
|
|
143
|
+
const [drilldownStack, setDrilldownStack] = useState<string[]>([]);
|
|
144
|
+
const [selectedDetailNodeId, setSelectedDetailNodeId] = useState<string | null>(null);
|
|
145
|
+
const [codeDrafts, setCodeDrafts] = useState<Record<string, string>>({});
|
|
146
|
+
const [suggestionInstruction, setSuggestionInstruction] = useState("");
|
|
147
|
+
const [codeSuggestion, setCodeSuggestion] = useState<{ summary: string; code: string; notes: string[] } | null>(null);
|
|
148
|
+
const [liveCompletionsEnabled, setLiveCompletionsEnabled] = useState(true);
|
|
149
|
+
const [serverApiKeyConfigured, setServerApiKeyConfigured] = useState(false);
|
|
150
|
+
const [apiKeyStatusLoaded, setApiKeyStatusLoaded] = useState(false);
|
|
151
|
+
const [statusTitle, setStatusTitle] = useState("Ready to build");
|
|
152
|
+
const [statusDetail, setStatusDetail] = useState(
|
|
153
|
+
"Enter a project description or repo input, then build a blueprint."
|
|
154
|
+
);
|
|
155
|
+
const [statusTone, setStatusTone] = useState<StatusTone>("info");
|
|
156
|
+
const [showSettings, setShowSettings] = useState(false);
|
|
157
|
+
const [showPromptPanel, setShowPromptPanel] = useState(true);
|
|
158
|
+
const [showEditPanel, setShowEditPanel] = useState(false);
|
|
159
|
+
const [showInspector, setShowInspector] = useState(false);
|
|
160
|
+
const [showAnalysisPanel, setShowAnalysisPanel] = useState(false);
|
|
161
|
+
const [showObservabilityPanel, setShowObservabilityPanel] = useState(false);
|
|
162
|
+
const [showPolicyLayerPanel, setShowPolicyLayerPanel] = useState(false);
|
|
163
|
+
const [themePreference, setThemePreference] = useState<ThemePreference>("system");
|
|
164
|
+
const [systemTheme, setSystemTheme] = useState<ResolvedTheme>("light");
|
|
165
|
+
const [autoObservability, setAutoObservability] = useState(false);
|
|
166
|
+
const [observabilityIntervalSecs, setObservabilityIntervalSecs] = useState(5);
|
|
167
|
+
const autoObsRef = useRef(autoObservability);
|
|
168
|
+
autoObsRef.current = autoObservability;
|
|
169
|
+
const [autoImplementNodes, setAutoImplementNodes] = useState(false);
|
|
170
|
+
const [cycleReport, setCycleReport] = useState<CycleReport | null>(null);
|
|
171
|
+
const [smellReport, setSmellReport] = useState<SmellReport | null>(null);
|
|
172
|
+
const [graphMetrics, setGraphMetrics] = useState<GraphMetrics | null>(null);
|
|
173
|
+
const [mermaidDiagram, setMermaidDiagram] = useState<string | null>(null);
|
|
174
|
+
const resolvedTheme = themePreference === "system" ? systemTheme : themePreference;
|
|
175
|
+
const topbarRef = useRef<HTMLElement | null>(null);
|
|
176
|
+
const [floatingPanelTop, setFloatingPanelTop] = useState(112);
|
|
177
|
+
|
|
178
|
+
const selectedNode = graph?.nodes.find((node) => node.id === selectedNodeId) ?? null;
|
|
179
|
+
const drilldownNodeId = drilldownStack.at(-1) ?? null;
|
|
180
|
+
const drilldownRootNode = graph?.nodes.find((node) => node.id === drilldownNodeId) ?? null;
|
|
181
|
+
const detailFlow =
|
|
182
|
+
graph && drilldownNodeId
|
|
183
|
+
? buildDetailFlow(graph, drilldownNodeId, selectedDetailNodeId ?? undefined)
|
|
184
|
+
: null;
|
|
185
|
+
const heatmapData: HeatmapData | undefined = useMemo(
|
|
186
|
+
() =>
|
|
187
|
+
graph &&
|
|
188
|
+
graph.nodes.some(
|
|
189
|
+
(node) => node.traceState && node.traceState.count > 0
|
|
190
|
+
)
|
|
191
|
+
? computeHeatmap(graph)
|
|
192
|
+
: undefined,
|
|
193
|
+
[graph]
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const canStartImplementation = false;
|
|
197
|
+
const canStartIntegration = false;
|
|
198
|
+
const canImplementActiveNode = false;
|
|
199
|
+
const canRunActiveNode = false;
|
|
200
|
+
const canRunIntegration = false;
|
|
201
|
+
const isBusy = Boolean(busyLabel);
|
|
202
|
+
const isBuilding = busyLabel === "Building blueprint";
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<div className="workbench-shell" data-theme={resolvedTheme}>
|
|
206
|
+
<header className={`workbench-topbar ${graph ? "workbench-topbar-compact" : ""}`} ref={topbarRef}>
|
|
207
|
+
<div className="topbar-start">
|
|
208
|
+
<div className="brand-lockup">
|
|
209
|
+
<p className="brand-eyebrow">CodeFlow</p>
|
|
210
|
+
<h1>Policy Canvas</h1>
|
|
211
|
+
<p className="brand-caption">Graph-native architecture, compliance, and deployment control.</p>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</header>
|
|
215
|
+
|
|
216
|
+
<main className="policy-layout focus-layout">
|
|
217
|
+
<section className="workbench-main focus-main">
|
|
218
|
+
<section className="graph-panel full-graph focus-graph">
|
|
219
|
+
<GraphCanvas
|
|
220
|
+
graph={graph}
|
|
221
|
+
selectedNodeId={drilldownRootNode ? selectedDetailNodeId : selectedNodeId}
|
|
222
|
+
nodes={detailFlow?.nodes}
|
|
223
|
+
edges={detailFlow?.edges}
|
|
224
|
+
onSelect={(nodeId) => setSelectedNodeId(nodeId)}
|
|
225
|
+
heatmapData={drilldownRootNode ? undefined : heatmapData}
|
|
226
|
+
theme={resolvedTheme}
|
|
227
|
+
/>
|
|
228
|
+
</section>
|
|
229
|
+
</section>
|
|
230
|
+
</main>
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type * as Monaco from "monaco-editor";
|
|
2
|
+
|
|
3
|
+
type LanguageDefaultsApi = {
|
|
4
|
+
addExtraLib: (content: string, filePath?: string) => Monaco.IDisposable;
|
|
5
|
+
setCompilerOptions: (options: Record<string, unknown>) => void;
|
|
6
|
+
setDiagnosticsOptions: (options: Record<string, unknown>) => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type MonacoTypeScriptApi = {
|
|
10
|
+
javascriptDefaults: LanguageDefaultsApi;
|
|
11
|
+
typescriptDefaults: LanguageDefaultsApi;
|
|
12
|
+
JsxEmit: {
|
|
13
|
+
ReactJSX?: number;
|
|
14
|
+
React?: number;
|
|
15
|
+
};
|
|
16
|
+
ModuleKind: {
|
|
17
|
+
ESNext: number;
|
|
18
|
+
};
|
|
19
|
+
ModuleResolutionKind: {
|
|
20
|
+
NodeJs: number;
|
|
21
|
+
};
|
|
22
|
+
ScriptTarget: {
|
|
23
|
+
ESNext: number;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function getTypeScriptApi(monaco: typeof Monaco): MonacoTypeScriptApi | null {
|
|
28
|
+
return (monaco.languages as unknown as { typescript?: MonacoTypeScriptApi }).typescript ?? null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function toExtraLibPath(filePath: string): string {
|
|
32
|
+
const normalized = filePath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
33
|
+
return `file:///${normalized}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class TypeScriptLanguageService {
|
|
37
|
+
private monaco: typeof Monaco;
|
|
38
|
+
private defaultsConfigured = false;
|
|
39
|
+
private workspaceLibs = new Map<string, Monaco.IDisposable>();
|
|
40
|
+
private globalLibDisposables: Monaco.IDisposable[] = [];
|
|
41
|
+
|
|
42
|
+
constructor(monaco: typeof Monaco) {
|
|
43
|
+
this.monaco = monaco;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
configureDefaults(): void {
|
|
47
|
+
if (this.defaultsConfigured) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const api = getTypeScriptApi(this.monaco);
|
|
52
|
+
if (!api) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const jsxMode = api.JsxEmit.ReactJSX ?? api.JsxEmit.React ?? 2;
|
|
57
|
+
const compilerOptions = {
|
|
58
|
+
allowJs: true,
|
|
59
|
+
allowNonTsExtensions: true,
|
|
60
|
+
baseUrl: ".",
|
|
61
|
+
checkJs: true,
|
|
62
|
+
esModuleInterop: true,
|
|
63
|
+
jsx: jsxMode,
|
|
64
|
+
module: api.ModuleKind.ESNext,
|
|
65
|
+
moduleResolution: api.ModuleResolutionKind.NodeJs,
|
|
66
|
+
noEmit: true,
|
|
67
|
+
paths: {
|
|
68
|
+
"@/*": ["./src/*"],
|
|
69
|
+
"@/components/*": ["./src/components/*"],
|
|
70
|
+
"@/lib/*": ["./src/lib/*"],
|
|
71
|
+
"@/store/*": ["./src/store/*"]
|
|
72
|
+
},
|
|
73
|
+
strict: true,
|
|
74
|
+
target: api.ScriptTarget.ESNext
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
api.typescriptDefaults.setCompilerOptions(compilerOptions);
|
|
78
|
+
api.javascriptDefaults.setCompilerOptions(compilerOptions);
|
|
79
|
+
|
|
80
|
+
api.typescriptDefaults.setDiagnosticsOptions({
|
|
81
|
+
noSemanticValidation: false,
|
|
82
|
+
noSyntaxValidation: false
|
|
83
|
+
});
|
|
84
|
+
api.javascriptDefaults.setDiagnosticsOptions({
|
|
85
|
+
noSemanticValidation: false,
|
|
86
|
+
noSyntaxValidation: false
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
this.addGlobalTypes(api);
|
|
90
|
+
this.defaultsConfigured = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private addGlobalTypes(api: MonacoTypeScriptApi): void {
|
|
94
|
+
const reactTypes = `
|
|
95
|
+
declare namespace React {
|
|
96
|
+
type ReactNode = import('react').ReactNode;
|
|
97
|
+
type FC<P = {}> = import('react').FunctionComponent<P>;
|
|
98
|
+
type CSSProperties = import('react').CSSProperties;
|
|
99
|
+
}
|
|
100
|
+
declare module 'react' {
|
|
101
|
+
function useState<T>(initial: T | (() => T)): [T, (value: T) => void];
|
|
102
|
+
function useState<T>(): [T | undefined, (value: T) => void];
|
|
103
|
+
function useEffect(effect: () => void | (() => void)): void;
|
|
104
|
+
function useEffect(effect: () => void, deps: any[]): void;
|
|
105
|
+
function useCallback<T extends (...args: any[]) => any>(callback: T, deps: any[]): T;
|
|
106
|
+
function useMemo<T>(factory: () => T, deps: any[]): T;
|
|
107
|
+
function useRef<T>(initial: T): { current: T };
|
|
108
|
+
function useRef<T>(initial?: T): { current: T | undefined };
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const nextTypes = `
|
|
113
|
+
declare namespace Next {
|
|
114
|
+
function dynamic<T>(importFn: () => Promise<T>): T;
|
|
115
|
+
function dynamic<T>(importFn: () => Promise<T>, options: { ssr?: boolean }): T;
|
|
116
|
+
}
|
|
117
|
+
declare module 'next' {
|
|
118
|
+
export function GetServerSideProps(context: any): any;
|
|
119
|
+
export function GetStaticProps(context: any): any;
|
|
120
|
+
export function GetServerSideProps(context: any): any;
|
|
121
|
+
}
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
const nodeTypes = `
|
|
125
|
+
declare module 'node:fs' {
|
|
126
|
+
export function readFile(path: string, encoding: string): Promise<string>;
|
|
127
|
+
export function writeFile(path: string, data: string): Promise<void>;
|
|
128
|
+
}
|
|
129
|
+
declare module 'node:path' {
|
|
130
|
+
export function resolve(...paths: string[]): string;
|
|
131
|
+
export function join(...paths: string[]): string;
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
this.globalLibDisposables.push(
|
|
136
|
+
api.typescriptDefaults.addExtraLib(reactTypes, "file:///node_modules/@types/react-global.d.ts"),
|
|
137
|
+
api.typescriptDefaults.addExtraLib(nextTypes, "file:///node_modules/@types/next-global.d.ts"),
|
|
138
|
+
api.typescriptDefaults.addExtraLib(nodeTypes, "file:///node_modules/@types/node-global.d.ts")
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
upsertWorkspaceFile(filePath: string, content: string): void {
|
|
143
|
+
this.configureDefaults();
|
|
144
|
+
const api = getTypeScriptApi(this.monaco);
|
|
145
|
+
if (!api) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const extraLibPath = toExtraLibPath(filePath);
|
|
150
|
+
this.workspaceLibs.get(extraLibPath)?.dispose();
|
|
151
|
+
this.workspaceLibs.set(extraLibPath, api.typescriptDefaults.addExtraLib(content, extraLibPath));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
dispose(): void {
|
|
155
|
+
this.globalLibDisposables.forEach((disposable) => disposable.dispose());
|
|
156
|
+
this.globalLibDisposables = [];
|
|
157
|
+
this.workspaceLibs.forEach((disposable) => disposable.dispose());
|
|
158
|
+
this.workspaceLibs.clear();
|
|
159
|
+
this.defaultsConfigured = false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let languageService: TypeScriptLanguageService | null = null;
|
|
164
|
+
|
|
165
|
+
export function getTypeScriptLanguageService(monaco: typeof Monaco): TypeScriptLanguageService {
|
|
166
|
+
if (!languageService) {
|
|
167
|
+
languageService = new TypeScriptLanguageService(monaco);
|
|
168
|
+
}
|
|
169
|
+
return languageService;
|
|
170
|
+
}
|