create-interview-cockpit 0.5.0 → 0.7.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 +734 -1
- package/template/client/package.json +1 -0
- package/template/client/src/App.tsx +3 -0
- package/template/client/src/api.ts +384 -4
- package/template/client/src/components/AiSettingsModal.tsx +818 -425
- package/template/client/src/components/ChatMessage.tsx +34 -12
- package/template/client/src/components/ChatView.tsx +298 -121
- package/template/client/src/components/CodeContextPanel.tsx +530 -2
- package/template/client/src/components/CodeRunnerModal.tsx +1895 -120
- package/template/client/src/components/DocRefModal.tsx +55 -6
- package/template/client/src/components/FileAttachments.tsx +20 -4
- 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 +22 -8
- 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 +184 -0
- package/template/client/src/components/VizCraftEmbed.tsx +257 -13
- package/template/client/src/components/WorkspaceSwitcher.tsx +4 -0
- package/template/client/src/infraLab.ts +124 -0
- package/template/client/src/reactLab.ts +960 -0
- package/template/client/src/store.ts +250 -6
- package/template/client/src/types.ts +36 -3
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
- package/template/server/src/google-drive.ts +39 -3
- package/template/server/src/index.ts +954 -52
- package/template/server/src/infra-runner.ts +1104 -0
- package/template/server/src/storage.ts +22 -3
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
import { create } from "zustand";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
Topic,
|
|
4
|
+
Question,
|
|
5
|
+
CodeSnippet,
|
|
6
|
+
WorkspaceMeta,
|
|
7
|
+
InfraLabWorkspace,
|
|
8
|
+
FrontendLabWorkspace,
|
|
9
|
+
} from "./types";
|
|
3
10
|
import type { AiSettings } from "./api";
|
|
4
11
|
import * as api from "./api";
|
|
5
12
|
|
|
13
|
+
// Default Express server used when opening a React/Next.js sandbox with no prior server code
|
|
14
|
+
const DEFAULT_SERVER_CODE = `import express from 'express';
|
|
15
|
+
const app = express();
|
|
16
|
+
app.use(express.json());
|
|
17
|
+
// Allow requests from the React preview iframe
|
|
18
|
+
app.use((_req, res, next) => {
|
|
19
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
20
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
|
|
21
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
22
|
+
if (_req.method === 'OPTIONS') { res.sendStatus(204); return; }
|
|
23
|
+
next();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
app.get('/api/hello', (_req, res) => {
|
|
27
|
+
res.json({ message: 'Hello from Express!', time: Date.now() });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const port = Number(process.env.PORT);
|
|
31
|
+
app.listen(port, () => console.log('Server on :' + port));
|
|
32
|
+
`;
|
|
33
|
+
|
|
6
34
|
const DEFAULT_AI_SETTINGS: AiSettings = {
|
|
7
35
|
systemPrompt: "",
|
|
8
36
|
responseProfiles: {
|
|
@@ -11,6 +39,7 @@ const DEFAULT_AI_SETTINGS: AiSettings = {
|
|
|
11
39
|
normal: { maxOutputTokens: 3000, maxSteps: 5 },
|
|
12
40
|
},
|
|
13
41
|
vizGuide: "",
|
|
42
|
+
plotGuide: "",
|
|
14
43
|
alwaysSendPrefsDefault: false,
|
|
15
44
|
thinkingBudget: 0,
|
|
16
45
|
promptGroups: {
|
|
@@ -52,6 +81,19 @@ const DEFAULT_AI_SETTINGS: AiSettings = {
|
|
|
52
81
|
"When using technical terms or abbreviations, immediately expand their meaning in square brackets right after the term — e.g. 'TCP [Transmission Control Protocol — a connection-oriented transport protocol]' or 'idempotent [an operation that produces the same result no matter how many times it is applied]'. Do this throughout your response so I never need to look anything up.",
|
|
53
82
|
},
|
|
54
83
|
},
|
|
84
|
+
"diagram-use": {
|
|
85
|
+
label: "Diagram Usage",
|
|
86
|
+
description: "Which visual formats to use and how often.",
|
|
87
|
+
default: "none",
|
|
88
|
+
options: {
|
|
89
|
+
none: "",
|
|
90
|
+
vizcraft:
|
|
91
|
+
"Prioritize using vizcraft (viz) diagrams as much as possible. *NB:* Character limits do not apply to these diagrams",
|
|
92
|
+
mermaid:
|
|
93
|
+
"Prioritize using mermaid diagrams as much as possible. *NB:* Character limits do not apply to these diagrams",
|
|
94
|
+
plot: "Prioritize using plot blocks for graphs, curves, distributions, and data charts when they would make the explanation clearer. *NB:* Character limits do not apply to these plots",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
55
97
|
},
|
|
56
98
|
};
|
|
57
99
|
|
|
@@ -108,19 +150,30 @@ interface Store {
|
|
|
108
150
|
createDriveSubfolder: (
|
|
109
151
|
id: string,
|
|
110
152
|
name: string,
|
|
111
|
-
) => Promise<
|
|
153
|
+
) => Promise<
|
|
154
|
+
import("./api").DriveFolder | { needsAuth: true; authUrl: string }
|
|
155
|
+
>;
|
|
112
156
|
|
|
113
157
|
fetchTopics: () => Promise<void>;
|
|
114
158
|
fetchQuestions: (topicId: string) => Promise<void>;
|
|
115
159
|
addTopic: (name: string) => Promise<void>;
|
|
116
160
|
removeTopic: (id: string) => Promise<void>;
|
|
117
161
|
renameTopic: (id: string, name: string) => Promise<void>;
|
|
162
|
+
updateTopicSystemContext: (
|
|
163
|
+
id: string,
|
|
164
|
+
systemContext: string,
|
|
165
|
+
) => Promise<void>;
|
|
118
166
|
addQuestion: (topicId: string, title: string) => Promise<void>;
|
|
119
167
|
addChildQuestion: (
|
|
120
168
|
topicId: string,
|
|
121
169
|
parentQuestionId: string,
|
|
122
170
|
title: string,
|
|
123
171
|
) => Promise<void>;
|
|
172
|
+
moveQuestion: (
|
|
173
|
+
questionId: string,
|
|
174
|
+
topicId: string,
|
|
175
|
+
parentQuestionId: string | null,
|
|
176
|
+
) => Promise<void>;
|
|
124
177
|
removeQuestion: (questionId: string, topicId: string) => Promise<void>;
|
|
125
178
|
renameQuestion: (
|
|
126
179
|
questionId: string,
|
|
@@ -159,10 +212,21 @@ interface Store {
|
|
|
159
212
|
code: string,
|
|
160
213
|
language: string,
|
|
161
214
|
label: string,
|
|
162
|
-
origin:
|
|
215
|
+
origin:
|
|
216
|
+
| "user"
|
|
217
|
+
| "ai"
|
|
218
|
+
| "sandbox"
|
|
219
|
+
| "infra"
|
|
220
|
+
| "react"
|
|
221
|
+
| "nextjs"
|
|
222
|
+
| "module-federation",
|
|
163
223
|
) => Promise<import("./types").ContextFile>;
|
|
164
224
|
clearMessages: (questionId: string) => Promise<void>;
|
|
165
225
|
|
|
226
|
+
// ── Linked Conversations ─────────────────────────────────────
|
|
227
|
+
linkConversation: (questionId: string, linkedId: string) => Promise<void>;
|
|
228
|
+
unlinkConversation: (questionId: string, linkedId: string) => Promise<void>;
|
|
229
|
+
|
|
166
230
|
// ── Workspace Context Files ──────────────────────────────────
|
|
167
231
|
workspaceFiles: import("./types").ContextFile[];
|
|
168
232
|
fetchWorkspaceFiles: () => Promise<void>;
|
|
@@ -215,14 +279,25 @@ interface Store {
|
|
|
215
279
|
clientCode: string;
|
|
216
280
|
clientLang: string;
|
|
217
281
|
fileId?: string;
|
|
282
|
+
/** If set, the client panel opens in React or Next.js preview mode instead of script mode */
|
|
283
|
+
clientType?: "script" | "react" | "nextjs" | "module-federation";
|
|
284
|
+
reactFiles?: Record<string, string> | null;
|
|
285
|
+
reactActiveFile?: string | null;
|
|
218
286
|
} | null;
|
|
219
|
-
|
|
287
|
+
/** When set, the script runner opens with this file pre-loaded — Save will overwrite it */
|
|
288
|
+
runnerInitialFileId: string | null;
|
|
289
|
+
openCodeRunner: (code?: string, language?: string, fileId?: string) => void;
|
|
220
290
|
openSandbox: (
|
|
221
291
|
serverCode: string,
|
|
222
292
|
serverLang: string,
|
|
223
293
|
clientCode: string,
|
|
224
294
|
clientLang: string,
|
|
225
295
|
fileId?: string,
|
|
296
|
+
opts?: {
|
|
297
|
+
clientType?: "script" | "react" | "nextjs" | "module-federation";
|
|
298
|
+
reactFiles?: Record<string, string>;
|
|
299
|
+
reactActiveFile?: string;
|
|
300
|
+
},
|
|
226
301
|
) => void;
|
|
227
302
|
overwriteContextFileContent: (
|
|
228
303
|
questionId: string,
|
|
@@ -235,6 +310,33 @@ interface Store {
|
|
|
235
310
|
label: string,
|
|
236
311
|
) => Promise<void>;
|
|
237
312
|
closeCodeRunner: () => void;
|
|
313
|
+
|
|
314
|
+
// ── Infra Lab ────────────────────────────────────────────────
|
|
315
|
+
showInfraLab: boolean;
|
|
316
|
+
runnerInitialInfra: InfraLabWorkspace | null;
|
|
317
|
+
runnerInitialInfraFileId: string | null;
|
|
318
|
+
openInfraLab: (workspace?: InfraLabWorkspace, fileId?: string) => void;
|
|
319
|
+
closeInfraLab: () => void;
|
|
320
|
+
|
|
321
|
+
// ── Frontend Labs (React / Next.js / Module Federation) — open inside the sandbox ──
|
|
322
|
+
openReactLab: (
|
|
323
|
+
workspace?: FrontendLabWorkspace,
|
|
324
|
+
fileId?: string,
|
|
325
|
+
serverCode?: string,
|
|
326
|
+
serverLang?: string,
|
|
327
|
+
) => void;
|
|
328
|
+
openNextLab: (
|
|
329
|
+
workspace?: FrontendLabWorkspace,
|
|
330
|
+
fileId?: string,
|
|
331
|
+
serverCode?: string,
|
|
332
|
+
serverLang?: string,
|
|
333
|
+
) => void;
|
|
334
|
+
openModuleFederationLab: (
|
|
335
|
+
workspace?: FrontendLabWorkspace,
|
|
336
|
+
fileId?: string,
|
|
337
|
+
serverCode?: string,
|
|
338
|
+
serverLang?: string,
|
|
339
|
+
) => void;
|
|
238
340
|
}
|
|
239
341
|
|
|
240
342
|
export const useStore = create<Store>((set, get) => ({
|
|
@@ -259,6 +361,10 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
259
361
|
runnerInitialCode: "",
|
|
260
362
|
runnerInitialLanguage: "typescript",
|
|
261
363
|
runnerInitialSandbox: null,
|
|
364
|
+
runnerInitialFileId: null,
|
|
365
|
+
showInfraLab: false,
|
|
366
|
+
runnerInitialInfra: null,
|
|
367
|
+
runnerInitialInfraFileId: null,
|
|
262
368
|
|
|
263
369
|
// ── Workspaces ───────────────────────────────────────────────
|
|
264
370
|
workspaces: [],
|
|
@@ -448,6 +554,13 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
448
554
|
}));
|
|
449
555
|
},
|
|
450
556
|
|
|
557
|
+
updateTopicSystemContext: async (id, systemContext) => {
|
|
558
|
+
await api.updateTopic(id, { systemContext });
|
|
559
|
+
set((s) => ({
|
|
560
|
+
topics: s.topics.map((t) => (t.id === id ? { ...t, systemContext } : t)),
|
|
561
|
+
}));
|
|
562
|
+
},
|
|
563
|
+
|
|
451
564
|
addQuestion: async (topicId, title) => {
|
|
452
565
|
const question = await api.createQuestion(topicId, title);
|
|
453
566
|
set((s) => ({
|
|
@@ -468,6 +581,29 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
468
581
|
}));
|
|
469
582
|
},
|
|
470
583
|
|
|
584
|
+
moveQuestion: async (questionId, topicId, parentQuestionId) => {
|
|
585
|
+
await api.updateQuestion(questionId, {
|
|
586
|
+
parentQuestionId,
|
|
587
|
+
});
|
|
588
|
+
set((s) => ({
|
|
589
|
+
questionsByTopic: {
|
|
590
|
+
...s.questionsByTopic,
|
|
591
|
+
[topicId]: (s.questionsByTopic[topicId] || []).map((q) =>
|
|
592
|
+
q.id === questionId
|
|
593
|
+
? { ...q, parentQuestionId: parentQuestionId ?? undefined }
|
|
594
|
+
: q,
|
|
595
|
+
),
|
|
596
|
+
},
|
|
597
|
+
currentQuestion:
|
|
598
|
+
s.currentQuestion?.id === questionId
|
|
599
|
+
? {
|
|
600
|
+
...s.currentQuestion,
|
|
601
|
+
parentQuestionId: parentQuestionId ?? undefined,
|
|
602
|
+
}
|
|
603
|
+
: s.currentQuestion,
|
|
604
|
+
}));
|
|
605
|
+
},
|
|
606
|
+
|
|
471
607
|
removeQuestion: async (questionId, topicId) => {
|
|
472
608
|
await api.deleteQuestion(questionId);
|
|
473
609
|
set((s) => ({
|
|
@@ -682,6 +818,38 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
682
818
|
}));
|
|
683
819
|
},
|
|
684
820
|
|
|
821
|
+
linkConversation: async (questionId, linkedId) => {
|
|
822
|
+
const q = get().currentQuestion;
|
|
823
|
+
const current =
|
|
824
|
+
q?.id === questionId ? q : await api.fetchQuestion(questionId);
|
|
825
|
+
const existing = current.linkedConversationIds ?? [];
|
|
826
|
+
if (existing.includes(linkedId)) return;
|
|
827
|
+
const updated = [...existing, linkedId];
|
|
828
|
+
await api.updateQuestion(questionId, { linkedConversationIds: updated });
|
|
829
|
+
set((s) => ({
|
|
830
|
+
currentQuestion:
|
|
831
|
+
s.currentQuestion?.id === questionId
|
|
832
|
+
? { ...s.currentQuestion, linkedConversationIds: updated }
|
|
833
|
+
: s.currentQuestion,
|
|
834
|
+
}));
|
|
835
|
+
},
|
|
836
|
+
|
|
837
|
+
unlinkConversation: async (questionId, linkedId) => {
|
|
838
|
+
const q = get().currentQuestion;
|
|
839
|
+
const current =
|
|
840
|
+
q?.id === questionId ? q : await api.fetchQuestion(questionId);
|
|
841
|
+
const updated = (current.linkedConversationIds ?? []).filter(
|
|
842
|
+
(id) => id !== linkedId,
|
|
843
|
+
);
|
|
844
|
+
await api.updateQuestion(questionId, { linkedConversationIds: updated });
|
|
845
|
+
set((s) => ({
|
|
846
|
+
currentQuestion:
|
|
847
|
+
s.currentQuestion?.id === questionId
|
|
848
|
+
? { ...s.currentQuestion, linkedConversationIds: updated }
|
|
849
|
+
: s.currentQuestion,
|
|
850
|
+
}));
|
|
851
|
+
},
|
|
852
|
+
|
|
685
853
|
fetchWorkspaceFiles: async () => {
|
|
686
854
|
const files = await api.fetchWorkspaceFiles();
|
|
687
855
|
set({ workspaceFiles: files });
|
|
@@ -727,16 +895,26 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
727
895
|
openDocViewer: (fileId, quote, fileName) =>
|
|
728
896
|
set({ viewingDoc: { fileId, quote, fileName } }),
|
|
729
897
|
closeDocViewer: () => set({ viewingDoc: null }),
|
|
730
|
-
openCodeRunner: (code = "", language = "typescript") =>
|
|
898
|
+
openCodeRunner: (code = "", language = "typescript", fileId?) =>
|
|
731
899
|
set({
|
|
732
900
|
showCodeRunner: true,
|
|
901
|
+
showInfraLab: false,
|
|
733
902
|
runnerInitialCode: code,
|
|
734
903
|
runnerInitialLanguage: language,
|
|
735
904
|
runnerInitialSandbox: null,
|
|
905
|
+
runnerInitialFileId: fileId ?? null,
|
|
736
906
|
}),
|
|
737
|
-
openSandbox: (
|
|
907
|
+
openSandbox: (
|
|
908
|
+
serverCode,
|
|
909
|
+
serverLang,
|
|
910
|
+
clientCode,
|
|
911
|
+
clientLang,
|
|
912
|
+
fileId?,
|
|
913
|
+
opts?,
|
|
914
|
+
) =>
|
|
738
915
|
set({
|
|
739
916
|
showCodeRunner: true,
|
|
917
|
+
showInfraLab: false,
|
|
740
918
|
runnerInitialCode: "",
|
|
741
919
|
runnerInitialLanguage: "typescript",
|
|
742
920
|
runnerInitialSandbox: {
|
|
@@ -745,7 +923,72 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
745
923
|
clientCode,
|
|
746
924
|
clientLang,
|
|
747
925
|
fileId,
|
|
926
|
+
clientType: opts?.clientType,
|
|
927
|
+
reactFiles: opts?.reactFiles,
|
|
928
|
+
reactActiveFile: opts?.reactActiveFile,
|
|
929
|
+
},
|
|
930
|
+
runnerInitialFileId: null,
|
|
931
|
+
}),
|
|
932
|
+
openInfraLab: (workspace, fileId?) =>
|
|
933
|
+
set({
|
|
934
|
+
showInfraLab: true,
|
|
935
|
+
showCodeRunner: false,
|
|
936
|
+
runnerInitialInfra: workspace ?? null,
|
|
937
|
+
runnerInitialInfraFileId: fileId ?? null,
|
|
938
|
+
}),
|
|
939
|
+
openReactLab: (workspace?, fileId?, serverCode?, serverLang?) =>
|
|
940
|
+
set({
|
|
941
|
+
showCodeRunner: true,
|
|
942
|
+
showInfraLab: false,
|
|
943
|
+
runnerInitialCode: "",
|
|
944
|
+
runnerInitialLanguage: "typescript",
|
|
945
|
+
runnerInitialSandbox: {
|
|
946
|
+
serverCode: serverCode ?? DEFAULT_SERVER_CODE,
|
|
947
|
+
serverLang: serverLang ?? "typescript",
|
|
948
|
+
clientCode: "",
|
|
949
|
+
clientLang: "javascript",
|
|
950
|
+
fileId,
|
|
951
|
+
clientType: "react",
|
|
952
|
+
reactFiles: workspace?.files ?? null,
|
|
953
|
+
reactActiveFile: workspace?.activeFile ?? null,
|
|
954
|
+
},
|
|
955
|
+
runnerInitialFileId: null,
|
|
956
|
+
}),
|
|
957
|
+
openNextLab: (workspace?, fileId?, serverCode?, serverLang?) =>
|
|
958
|
+
set({
|
|
959
|
+
showCodeRunner: true,
|
|
960
|
+
showInfraLab: false,
|
|
961
|
+
runnerInitialCode: "",
|
|
962
|
+
runnerInitialLanguage: "typescript",
|
|
963
|
+
runnerInitialSandbox: {
|
|
964
|
+
serverCode: serverCode ?? DEFAULT_SERVER_CODE,
|
|
965
|
+
serverLang: serverLang ?? "typescript",
|
|
966
|
+
clientCode: "",
|
|
967
|
+
clientLang: "javascript",
|
|
968
|
+
fileId,
|
|
969
|
+
clientType: "nextjs",
|
|
970
|
+
reactFiles: workspace?.files ?? null,
|
|
971
|
+
reactActiveFile: workspace?.activeFile ?? null,
|
|
972
|
+
},
|
|
973
|
+
runnerInitialFileId: null,
|
|
974
|
+
}),
|
|
975
|
+
openModuleFederationLab: (workspace?, fileId?, serverCode?, serverLang?) =>
|
|
976
|
+
set({
|
|
977
|
+
showCodeRunner: true,
|
|
978
|
+
showInfraLab: false,
|
|
979
|
+
runnerInitialCode: "",
|
|
980
|
+
runnerInitialLanguage: "typescript",
|
|
981
|
+
runnerInitialSandbox: {
|
|
982
|
+
serverCode: serverCode ?? DEFAULT_SERVER_CODE,
|
|
983
|
+
serverLang: serverLang ?? "typescript",
|
|
984
|
+
clientCode: "",
|
|
985
|
+
clientLang: "javascript",
|
|
986
|
+
fileId,
|
|
987
|
+
clientType: "module-federation",
|
|
988
|
+
reactFiles: workspace?.files ?? null,
|
|
989
|
+
reactActiveFile: workspace?.activeFile ?? null,
|
|
748
990
|
},
|
|
991
|
+
runnerInitialFileId: null,
|
|
749
992
|
}),
|
|
750
993
|
|
|
751
994
|
overwriteContextFileContent: async (questionId, fileId, content) => {
|
|
@@ -767,6 +1010,7 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
767
1010
|
}));
|
|
768
1011
|
},
|
|
769
1012
|
closeCodeRunner: () => set({ showCodeRunner: false }),
|
|
1013
|
+
closeInfraLab: () => set({ showInfraLab: false }),
|
|
770
1014
|
|
|
771
1015
|
fetchAiSettings: async () => {
|
|
772
1016
|
const settings = await api.fetchAiSettings();
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export type ContextFileOrigin =
|
|
2
|
+
| "user"
|
|
3
|
+
| "ai"
|
|
4
|
+
| "upload"
|
|
5
|
+
| "sandbox"
|
|
6
|
+
| "infra"
|
|
7
|
+
| "react"
|
|
8
|
+
| "nextjs"
|
|
9
|
+
| "module-federation";
|
|
10
|
+
|
|
1
11
|
export interface ContextFile {
|
|
2
12
|
id: string;
|
|
3
13
|
name: string;
|
|
@@ -6,14 +16,33 @@ export interface ContextFile {
|
|
|
6
16
|
createdAt: string;
|
|
7
17
|
/** Distinguishes how this file was added. 'upload' = user-uploaded doc,
|
|
8
18
|
* 'user' = code saved from Code Runner, 'ai' = AI-generated code block,
|
|
9
|
-
* 'sandbox' = paired server+client sandbox saved as JSON
|
|
10
|
-
|
|
19
|
+
* 'sandbox' = paired server+client sandbox saved as JSON,
|
|
20
|
+
* 'infra' = Terraform-style infra lab workspace saved as JSON. */
|
|
21
|
+
origin?: ContextFileOrigin;
|
|
11
22
|
/** Language hint for code snippets (e.g. 'typescript', 'javascript'). */
|
|
12
23
|
language?: string;
|
|
13
24
|
/** Short display label for code snippets. */
|
|
14
25
|
label?: string;
|
|
15
26
|
}
|
|
16
27
|
|
|
28
|
+
export interface FrontendLabWorkspace {
|
|
29
|
+
version: 1;
|
|
30
|
+
label: string;
|
|
31
|
+
/** Determines defaults, entry file, and file-tree conventions. */
|
|
32
|
+
type: "react" | "nextjs" | "module-federation";
|
|
33
|
+
activeFile: string;
|
|
34
|
+
files: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface InfraLabWorkspace {
|
|
38
|
+
version: 1;
|
|
39
|
+
label: string;
|
|
40
|
+
provider: "aws";
|
|
41
|
+
executionMode: "plan-only" | "localstack";
|
|
42
|
+
activeFile: string;
|
|
43
|
+
files: Record<string, string>;
|
|
44
|
+
}
|
|
45
|
+
|
|
17
46
|
export interface WorkspaceMeta {
|
|
18
47
|
id: string;
|
|
19
48
|
name: string;
|
|
@@ -37,6 +66,8 @@ export interface Topic {
|
|
|
37
66
|
id: string;
|
|
38
67
|
name: string;
|
|
39
68
|
contextFiles: ContextFile[];
|
|
69
|
+
/** Topic-wide system prompt prepended to every question's context in this topic. */
|
|
70
|
+
systemContext?: string;
|
|
40
71
|
createdAt: string;
|
|
41
72
|
}
|
|
42
73
|
|
|
@@ -82,7 +113,7 @@ export interface ReadingBookmark {
|
|
|
82
113
|
export interface Question {
|
|
83
114
|
id: string;
|
|
84
115
|
topicId: string;
|
|
85
|
-
parentQuestionId?: string;
|
|
116
|
+
parentQuestionId?: string | null;
|
|
86
117
|
title: string;
|
|
87
118
|
systemContext: string;
|
|
88
119
|
codeContextFiles: string[];
|
|
@@ -90,5 +121,7 @@ export interface Question {
|
|
|
90
121
|
messages: Message[];
|
|
91
122
|
annotations?: Annotation[];
|
|
92
123
|
readingBookmark?: ReadingBookmark;
|
|
124
|
+
/** IDs of other questions in the same topic whose conversation history is injected as context. */
|
|
125
|
+
linkedConversationIds?: string[];
|
|
93
126
|
createdAt: string;
|
|
94
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/fileattachments.tsx","./src/components/fileviewermodal.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/app.tsx","./src/api.ts","./src/main.tsx","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/plotembed.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx","./src/components/vizcraftembed.tsx","./src/components/workspaceswitcher.tsx"],"version":"5.9.3"}
|
package/template/cockpit.json
CHANGED
|
@@ -357,7 +357,11 @@ export async function syncWorkspace(
|
|
|
357
357
|
cs.content &&
|
|
358
358
|
(cs.origin === "user" ||
|
|
359
359
|
cs.origin === "ai" ||
|
|
360
|
-
cs.origin === "sandbox"
|
|
360
|
+
cs.origin === "sandbox" ||
|
|
361
|
+
cs.origin === "react" ||
|
|
362
|
+
cs.origin === "nextjs" ||
|
|
363
|
+
cs.origin === "module-federation" ||
|
|
364
|
+
cs.origin === "infra")
|
|
361
365
|
) {
|
|
362
366
|
try {
|
|
363
367
|
await fs.mkdir(ctxDir, { recursive: true });
|
|
@@ -562,7 +566,35 @@ async function getExportDriveClient(): Promise<drive_v3.Drive> {
|
|
|
562
566
|
/* ok */
|
|
563
567
|
}
|
|
564
568
|
});
|
|
565
|
-
|
|
569
|
+
|
|
570
|
+
// Wrap the drive client so any invalid_grant errors clear the stored token
|
|
571
|
+
// and surface a friendlier error that the caller can detect.
|
|
572
|
+
const drive = google.drive({ version: "v3", auth: client });
|
|
573
|
+
const originalFiles = drive.files as any;
|
|
574
|
+
const patchMethod = (obj: any, method: string) => {
|
|
575
|
+
const orig = obj[method].bind(obj);
|
|
576
|
+
obj[method] = async (...args: any[]) => {
|
|
577
|
+
try {
|
|
578
|
+
return await orig(...args);
|
|
579
|
+
} catch (err: any) {
|
|
580
|
+
if (err?.message?.includes("invalid_grant") || err?.code === 400) {
|
|
581
|
+
try {
|
|
582
|
+
await fs.unlink(EXPORT_TOKENS_FILE);
|
|
583
|
+
} catch {
|
|
584
|
+
/* already gone */
|
|
585
|
+
}
|
|
586
|
+
const e = new Error("NEEDS_REAUTH");
|
|
587
|
+
(e as any).needsReauth = true;
|
|
588
|
+
throw e;
|
|
589
|
+
}
|
|
590
|
+
throw err;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
};
|
|
594
|
+
["create", "update", "delete", "list", "get"].forEach((m) =>
|
|
595
|
+
patchMethod(originalFiles, m),
|
|
596
|
+
);
|
|
597
|
+
return drive;
|
|
566
598
|
}
|
|
567
599
|
|
|
568
600
|
export interface ExportResult {
|
|
@@ -736,7 +768,11 @@ export async function exportWorkspace(
|
|
|
736
768
|
if (
|
|
737
769
|
cf.origin === "user" ||
|
|
738
770
|
cf.origin === "ai" ||
|
|
739
|
-
cf.origin === "sandbox"
|
|
771
|
+
cf.origin === "sandbox" ||
|
|
772
|
+
cf.origin === "react" ||
|
|
773
|
+
cf.origin === "nextjs" ||
|
|
774
|
+
cf.origin === "module-federation" ||
|
|
775
|
+
cf.origin === "infra"
|
|
740
776
|
) {
|
|
741
777
|
try {
|
|
742
778
|
const content = await fs.readFile(
|