create-interview-cockpit 0.4.0 → 0.6.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/package.json +1 -1
- package/template/client/package-lock.json +753 -1
- package/template/client/package.json +4 -0
- package/template/client/src/App.tsx +20 -0
- package/template/client/src/api.ts +455 -3
- package/template/client/src/components/AiSettingsModal.tsx +855 -248
- package/template/client/src/components/AnnotationDialog.tsx +3 -9
- package/template/client/src/components/ChatMessage.tsx +132 -27
- package/template/client/src/components/ChatView.tsx +365 -123
- package/template/client/src/components/CodeContextPanel.tsx +714 -0
- package/template/client/src/components/CodeLineAnnotationPopup.tsx +179 -0
- package/template/client/src/components/CodeRunnerModal.tsx +3030 -0
- package/template/client/src/components/DocRefModal.tsx +551 -0
- package/template/client/src/components/FileAttachments.tsx +128 -12
- package/template/client/src/components/FilePickerModal.tsx +181 -0
- package/template/client/src/components/FileViewerModal.tsx +406 -28
- package/template/client/src/components/InfraLabModal.tsx +1706 -0
- package/template/client/src/components/LinkedConvosPicker.tsx +128 -0
- package/template/client/src/components/MarkdownRenderer.tsx +219 -2
- package/template/client/src/components/NotesModal.tsx +977 -0
- package/template/client/src/components/PlotEmbed.tsx +173 -0
- package/template/client/src/components/Sidebar.tsx +397 -127
- package/template/client/src/components/TextAnnotator.tsx +8 -15
- package/template/client/src/components/VizCraftEmbed.tsx +412 -25
- package/template/client/src/components/WorkspaceSwitcher.tsx +4 -0
- package/template/client/src/infraLab.ts +124 -0
- package/template/client/src/reactLab.ts +477 -0
- package/template/client/src/store.ts +416 -2
- package/template/client/src/types.ts +41 -1
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
- package/template/package.json +1 -1
- package/template/server/src/google-drive.ts +144 -2
- package/template/server/src/index.ts +1890 -188
- package/template/server/src/infra-runner.ts +1104 -0
- package/template/server/src/storage.ts +274 -3
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { memo, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { Loader2, RefreshCw } from "lucide-react";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
spec: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function sanitizePlotSpec(raw: string): string {
|
|
10
|
+
return raw
|
|
11
|
+
.trim()
|
|
12
|
+
.replace(/^```(?:plot|vega|vega-lite|json|yaml)?\s*/i, "")
|
|
13
|
+
.replace(/```\s*$/i, "")
|
|
14
|
+
.replace(/[\u201C\u201D]/g, '"')
|
|
15
|
+
.replace(/[\u2018\u2019\u02BC]/g, "'")
|
|
16
|
+
.replace(/\u2013|\u2014/g, "-");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parsePlotSpec(raw: string): unknown {
|
|
20
|
+
const trimmed = sanitizePlotSpec(raw);
|
|
21
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
22
|
+
return JSON.parse(trimmed);
|
|
23
|
+
}
|
|
24
|
+
return parseYaml(trimmed);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const EMBED_OPTIONS = {
|
|
28
|
+
actions: false,
|
|
29
|
+
renderer: "svg" as const,
|
|
30
|
+
theme: "dark" as const,
|
|
31
|
+
config: {
|
|
32
|
+
background: "transparent",
|
|
33
|
+
title: {
|
|
34
|
+
color: "#e2e8f0",
|
|
35
|
+
subtitleColor: "#94a3b8",
|
|
36
|
+
},
|
|
37
|
+
axis: {
|
|
38
|
+
domainColor: "#475569",
|
|
39
|
+
gridColor: "rgba(71, 85, 105, 0.35)",
|
|
40
|
+
tickColor: "#64748b",
|
|
41
|
+
labelColor: "#cbd5e1",
|
|
42
|
+
titleColor: "#e2e8f0",
|
|
43
|
+
},
|
|
44
|
+
legend: {
|
|
45
|
+
labelColor: "#cbd5e1",
|
|
46
|
+
titleColor: "#e2e8f0",
|
|
47
|
+
},
|
|
48
|
+
view: {
|
|
49
|
+
stroke: "transparent",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default memo(function PlotEmbed({ spec }: Props) {
|
|
55
|
+
const hostRef = useRef<HTMLDivElement>(null);
|
|
56
|
+
const finalizeRef = useRef<null | (() => void)>(null);
|
|
57
|
+
const [activeSpec, setActiveSpec] = useState(spec);
|
|
58
|
+
const [error, setError] = useState<string | null>(null);
|
|
59
|
+
const [fixing, setFixing] = useState(false);
|
|
60
|
+
const [rendering, setRendering] = useState(true);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
setActiveSpec(spec);
|
|
64
|
+
}, [spec]);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
let cancelled = false;
|
|
68
|
+
|
|
69
|
+
const renderPlot = async () => {
|
|
70
|
+
setRendering(true);
|
|
71
|
+
setError(null);
|
|
72
|
+
finalizeRef.current?.();
|
|
73
|
+
finalizeRef.current = null;
|
|
74
|
+
if (hostRef.current) {
|
|
75
|
+
hostRef.current.innerHTML = "";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const parsed = parsePlotSpec(activeSpec);
|
|
80
|
+
const { default: vegaEmbed } = await import("vega-embed");
|
|
81
|
+
if (cancelled || !hostRef.current) return;
|
|
82
|
+
const result = await vegaEmbed(
|
|
83
|
+
hostRef.current,
|
|
84
|
+
parsed as any,
|
|
85
|
+
EMBED_OPTIONS,
|
|
86
|
+
);
|
|
87
|
+
finalizeRef.current = () => result.finalize();
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (!cancelled) {
|
|
90
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
if (!cancelled) {
|
|
94
|
+
setRendering(false);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
void renderPlot();
|
|
100
|
+
|
|
101
|
+
return () => {
|
|
102
|
+
cancelled = true;
|
|
103
|
+
finalizeRef.current?.();
|
|
104
|
+
finalizeRef.current = null;
|
|
105
|
+
};
|
|
106
|
+
}, [activeSpec]);
|
|
107
|
+
|
|
108
|
+
const handleFix = async () => {
|
|
109
|
+
setFixing(true);
|
|
110
|
+
try {
|
|
111
|
+
const res = await fetch("/api/fix-plot", {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: { "Content-Type": "application/json" },
|
|
114
|
+
body: JSON.stringify({ spec: activeSpec, error }),
|
|
115
|
+
});
|
|
116
|
+
if (!res.ok) throw new Error("Fix request failed");
|
|
117
|
+
const { spec: fixed } = (await res.json()) as { spec: string };
|
|
118
|
+
setActiveSpec(fixed);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error("Fix plot error:", err);
|
|
121
|
+
} finally {
|
|
122
|
+
setFixing(false);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
if (error) {
|
|
127
|
+
return (
|
|
128
|
+
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3 my-2">
|
|
129
|
+
<div className="flex items-center justify-between mb-1 gap-3">
|
|
130
|
+
<div>
|
|
131
|
+
<p className="text-xs text-red-400">Plot error:</p>
|
|
132
|
+
<p className="text-[11px] text-slate-500 mt-1 whitespace-pre-wrap">
|
|
133
|
+
{error}
|
|
134
|
+
</p>
|
|
135
|
+
</div>
|
|
136
|
+
<button
|
|
137
|
+
onClick={handleFix}
|
|
138
|
+
disabled={fixing}
|
|
139
|
+
className="flex items-center gap-1 px-2 py-0.5 text-[11px] rounded bg-slate-700 text-slate-300 hover:bg-slate-600 disabled:opacity-50 transition-colors shrink-0"
|
|
140
|
+
>
|
|
141
|
+
{fixing ? (
|
|
142
|
+
<>
|
|
143
|
+
<Loader2 className="w-3 h-3 animate-spin" />
|
|
144
|
+
Fixing…
|
|
145
|
+
</>
|
|
146
|
+
) : (
|
|
147
|
+
<>
|
|
148
|
+
<RefreshCw className="w-3 h-3" />
|
|
149
|
+
Fix plot
|
|
150
|
+
</>
|
|
151
|
+
)}
|
|
152
|
+
</button>
|
|
153
|
+
</div>
|
|
154
|
+
<pre className="text-xs text-slate-400 whitespace-pre-wrap overflow-x-auto">
|
|
155
|
+
{activeSpec}
|
|
156
|
+
</pre>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div className="my-3 bg-slate-800/30 rounded-lg p-4 overflow-x-auto">
|
|
163
|
+
{rendering && (
|
|
164
|
+
<div className="flex justify-center pb-4">
|
|
165
|
+
<span className="text-xs text-slate-500 animate-pulse">
|
|
166
|
+
Rendering plot…
|
|
167
|
+
</span>
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
<div ref={hostRef} className="plot-embed min-w-[280px]" />
|
|
171
|
+
</div>
|
|
172
|
+
);
|
|
173
|
+
});
|