create-interview-cockpit 0.11.0 → 0.13.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-interview-cockpit",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Scaffold a personal AI-powered interview prep cockpit",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,12 +3,21 @@ import { useStore } from "./store";
3
3
  import Sidebar from "./components/Sidebar";
4
4
  import ChatView from "./components/ChatView";
5
5
  import CodeContextPanel from "./components/CodeContextPanel";
6
+ import LabsPanel from "./components/LabsPanel";
6
7
  import FileViewerModal from "./components/FileViewerModal";
7
8
  import DocRefModal from "./components/DocRefModal";
8
9
  import AiSettingsModal from "./components/AiSettingsModal";
9
10
  import CodeRunnerModal from "./components/CodeRunnerModal";
10
11
  import InfraLabModal from "./components/InfraLabModal";
11
- import { Code, Plane, PanelLeftClose, PanelLeft, Settings } from "lucide-react";
12
+ import DeploymentLabModal from "./components/DeploymentLabModal";
13
+ import {
14
+ Code,
15
+ FlaskConical,
16
+ Plane,
17
+ PanelLeftClose,
18
+ PanelLeft,
19
+ Settings,
20
+ } from "lucide-react";
12
21
 
13
22
  export default function App() {
14
23
  const {
@@ -21,6 +30,8 @@ export default function App() {
21
30
  currentQuestion,
22
31
  showCodePanel,
23
32
  toggleCodePanel,
33
+ showLabsPanel,
34
+ toggleLabsPanel,
24
35
  showSidebar,
25
36
  toggleSidebar,
26
37
  viewingFile,
@@ -32,6 +43,7 @@ export default function App() {
32
43
  closeSettings,
33
44
  showCodeRunner,
34
45
  showInfraLab,
46
+ showDeploymentLab,
35
47
  closeCodeRunner,
36
48
  } = useStore();
37
49
 
@@ -106,6 +118,17 @@ export default function App() {
106
118
  >
107
119
  <Code className="w-4 h-4" />
108
120
  </button>
121
+ <button
122
+ onClick={toggleLabsPanel}
123
+ className={`p-1.5 rounded transition-colors ${
124
+ showLabsPanel
125
+ ? "bg-violet-500/20 text-violet-400"
126
+ : "text-slate-500 hover:text-slate-300"
127
+ }`}
128
+ title="Toggle labs panel"
129
+ >
130
+ <FlaskConical className="w-4 h-4" />
131
+ </button>
109
132
  </div>
110
133
  </header>
111
134
 
@@ -144,6 +167,11 @@ export default function App() {
144
167
  <CodeContextPanel />
145
168
  </div>
146
169
  )}
170
+ {showLabsPanel && (
171
+ <div className="min-h-0 flex overflow-hidden">
172
+ <LabsPanel />
173
+ </div>
174
+ )}
147
175
  </div>
148
176
  </main>
149
177
 
@@ -161,6 +189,7 @@ export default function App() {
161
189
  {showSettings && <AiSettingsModal />}
162
190
  {showCodeRunner && <CodeRunnerModal />}
163
191
  {showInfraLab && <InfraLabModal />}
192
+ {showDeploymentLab && <DeploymentLabModal />}
164
193
  </div>
165
194
  );
166
195
  }
@@ -274,6 +274,28 @@ export async function deleteQuestionFile(
274
274
  });
275
275
  }
276
276
 
277
+ export async function detachQuestionLabFile(
278
+ questionId: string,
279
+ fileId: string,
280
+ ): Promise<ContextFile> {
281
+ const res = await fetch(
282
+ `${BASE}/questions/${questionId}/context-files/${fileId}/detach`,
283
+ { method: "POST" },
284
+ );
285
+ return res.json();
286
+ }
287
+
288
+ export async function attachQuestionLabFile(
289
+ questionId: string,
290
+ fileId: string,
291
+ ): Promise<ContextFile> {
292
+ const res = await fetch(
293
+ `${BASE}/questions/${questionId}/context-files/${fileId}/attach`,
294
+ { method: "POST" },
295
+ );
296
+ return res.json();
297
+ }
298
+
277
299
  export interface PickableFile {
278
300
  fileId: string;
279
301
  originalName: string;
@@ -968,3 +990,100 @@ export async function fetchModuleFederationGeneratedFile(
968
990
  export async function stopModuleFederationSandbox(id: string): Promise<void> {
969
991
  await fetch(`${BASE}/module-federation/${id}`, { method: "DELETE" });
970
992
  }
993
+
994
+ // ── React Lab (Vite) ─────────────────────────────────────────────────────────
995
+
996
+ export interface ReactLabSandboxInfo {
997
+ id: string;
998
+ port: number;
999
+ url: string;
1000
+ }
1001
+
1002
+ export async function startReactLabSandbox(
1003
+ files: Record<string, string>,
1004
+ ): Promise<ReactLabSandboxInfo> {
1005
+ const res = await fetch(`${BASE}/react-lab/start`, {
1006
+ method: "POST",
1007
+ headers: { "Content-Type": "application/json" },
1008
+ body: JSON.stringify({ files }),
1009
+ });
1010
+ if (!res.ok) {
1011
+ const body = await res.json().catch(() => ({}));
1012
+ throw new Error(
1013
+ (body as any).error || `Failed to start React lab (${res.status})`,
1014
+ );
1015
+ }
1016
+ return res.json();
1017
+ }
1018
+
1019
+ export async function updateReactLabFiles(
1020
+ id: string,
1021
+ files: Record<string, string>,
1022
+ ): Promise<void> {
1023
+ await fetch(`${BASE}/react-lab/${id}/update-files`, {
1024
+ method: "POST",
1025
+ headers: { "Content-Type": "application/json" },
1026
+ body: JSON.stringify({ files }),
1027
+ });
1028
+ }
1029
+
1030
+ export async function streamReactLabCommand(
1031
+ input: { id: string; command: string },
1032
+ onMessage: (message: ModuleFederationCommandStreamMessage) => void,
1033
+ ): Promise<void> {
1034
+ const res = await fetch(`${BASE}/react-lab/${input.id}/command-stream`, {
1035
+ method: "POST",
1036
+ headers: { "Content-Type": "application/json" },
1037
+ body: JSON.stringify({ command: input.command }),
1038
+ });
1039
+
1040
+ if (!res.ok || !res.body) {
1041
+ const error = await res.text().catch(() => "React lab command failed");
1042
+ throw new Error(error || "React lab command failed");
1043
+ }
1044
+
1045
+ const reader = res.body.getReader();
1046
+ const decoder = new TextDecoder();
1047
+ let buffer = "";
1048
+
1049
+ const flushBuffer = () => {
1050
+ const parts = buffer.split("\n\n");
1051
+ buffer = parts.pop() ?? "";
1052
+ for (const part of parts) {
1053
+ const dataLine = part.split("\n").find((l) => l.startsWith("data:"));
1054
+ if (!dataLine) continue;
1055
+ try {
1056
+ const payload = JSON.parse(dataLine.slice(5).trim());
1057
+ onMessage(payload as ModuleFederationCommandStreamMessage);
1058
+ } catch {}
1059
+ }
1060
+ };
1061
+
1062
+ while (true) {
1063
+ const { value, done } = await reader.read();
1064
+ buffer += decoder.decode(value ?? new Uint8Array(), { stream: !done });
1065
+ flushBuffer();
1066
+ if (done) break;
1067
+ }
1068
+
1069
+ if (buffer.trim()) {
1070
+ flushBuffer();
1071
+ }
1072
+ }
1073
+
1074
+ export async function readReactLabFile(
1075
+ id: string,
1076
+ filePath: string,
1077
+ ): Promise<string | null> {
1078
+ const res = await fetch(
1079
+ `${BASE}/react-lab/${id}/read-file?path=${encodeURIComponent(filePath)}`,
1080
+ { cache: "no-store" },
1081
+ );
1082
+ if (!res.ok) return null;
1083
+ const body = (await res.json()) as { content?: string };
1084
+ return body.content ?? null;
1085
+ }
1086
+
1087
+ export async function stopReactLabSandbox(id: string): Promise<void> {
1088
+ await fetch(`${BASE}/react-lab/${id}`, { method: "DELETE" });
1089
+ }