create-interview-cockpit 0.5.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 +734 -1
- package/template/client/package.json +1 -0
- package/template/client/src/App.tsx +3 -0
- package/template/client/src/api.ts +321 -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 +419 -2
- package/template/client/src/components/CodeRunnerModal.tsx +1601 -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 +477 -0
- package/template/client/src/store.ts +219 -6
- package/template/client/src/types.ts +35 -3
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/server/src/google-drive.ts +37 -3
- package/template/server/src/index.ts +693 -52
- package/template/server/src/infra-runner.ts +1104 -0
- package/template/server/src/storage.ts +13 -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,14 @@ interface Store {
|
|
|
159
212
|
code: string,
|
|
160
213
|
language: string,
|
|
161
214
|
label: string,
|
|
162
|
-
origin: "user" | "ai" | "sandbox",
|
|
215
|
+
origin: "user" | "ai" | "sandbox" | "infra" | "react" | "nextjs",
|
|
163
216
|
) => Promise<import("./types").ContextFile>;
|
|
164
217
|
clearMessages: (questionId: string) => Promise<void>;
|
|
165
218
|
|
|
219
|
+
// ── Linked Conversations ─────────────────────────────────────
|
|
220
|
+
linkConversation: (questionId: string, linkedId: string) => Promise<void>;
|
|
221
|
+
unlinkConversation: (questionId: string, linkedId: string) => Promise<void>;
|
|
222
|
+
|
|
166
223
|
// ── Workspace Context Files ──────────────────────────────────
|
|
167
224
|
workspaceFiles: import("./types").ContextFile[];
|
|
168
225
|
fetchWorkspaceFiles: () => Promise<void>;
|
|
@@ -215,14 +272,25 @@ interface Store {
|
|
|
215
272
|
clientCode: string;
|
|
216
273
|
clientLang: string;
|
|
217
274
|
fileId?: string;
|
|
275
|
+
/** If set, the client panel opens in React or Next.js preview mode instead of script mode */
|
|
276
|
+
clientType?: "script" | "react" | "nextjs";
|
|
277
|
+
reactFiles?: Record<string, string> | null;
|
|
278
|
+
reactActiveFile?: string | null;
|
|
218
279
|
} | null;
|
|
219
|
-
|
|
280
|
+
/** When set, the script runner opens with this file pre-loaded — Save will overwrite it */
|
|
281
|
+
runnerInitialFileId: string | null;
|
|
282
|
+
openCodeRunner: (code?: string, language?: string, fileId?: string) => void;
|
|
220
283
|
openSandbox: (
|
|
221
284
|
serverCode: string,
|
|
222
285
|
serverLang: string,
|
|
223
286
|
clientCode: string,
|
|
224
287
|
clientLang: string,
|
|
225
288
|
fileId?: string,
|
|
289
|
+
opts?: {
|
|
290
|
+
clientType?: "script" | "react" | "nextjs";
|
|
291
|
+
reactFiles?: Record<string, string>;
|
|
292
|
+
reactActiveFile?: string;
|
|
293
|
+
},
|
|
226
294
|
) => void;
|
|
227
295
|
overwriteContextFileContent: (
|
|
228
296
|
questionId: string,
|
|
@@ -235,6 +303,27 @@ interface Store {
|
|
|
235
303
|
label: string,
|
|
236
304
|
) => Promise<void>;
|
|
237
305
|
closeCodeRunner: () => void;
|
|
306
|
+
|
|
307
|
+
// ── Infra Lab ────────────────────────────────────────────────
|
|
308
|
+
showInfraLab: boolean;
|
|
309
|
+
runnerInitialInfra: InfraLabWorkspace | null;
|
|
310
|
+
runnerInitialInfraFileId: string | null;
|
|
311
|
+
openInfraLab: (workspace?: InfraLabWorkspace, fileId?: string) => void;
|
|
312
|
+
closeInfraLab: () => void;
|
|
313
|
+
|
|
314
|
+
// ── Frontend Labs (React / Next.js) — open inside the sandbox ──
|
|
315
|
+
openReactLab: (
|
|
316
|
+
workspace?: FrontendLabWorkspace,
|
|
317
|
+
fileId?: string,
|
|
318
|
+
serverCode?: string,
|
|
319
|
+
serverLang?: string,
|
|
320
|
+
) => void;
|
|
321
|
+
openNextLab: (
|
|
322
|
+
workspace?: FrontendLabWorkspace,
|
|
323
|
+
fileId?: string,
|
|
324
|
+
serverCode?: string,
|
|
325
|
+
serverLang?: string,
|
|
326
|
+
) => void;
|
|
238
327
|
}
|
|
239
328
|
|
|
240
329
|
export const useStore = create<Store>((set, get) => ({
|
|
@@ -259,6 +348,10 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
259
348
|
runnerInitialCode: "",
|
|
260
349
|
runnerInitialLanguage: "typescript",
|
|
261
350
|
runnerInitialSandbox: null,
|
|
351
|
+
runnerInitialFileId: null,
|
|
352
|
+
showInfraLab: false,
|
|
353
|
+
runnerInitialInfra: null,
|
|
354
|
+
runnerInitialInfraFileId: null,
|
|
262
355
|
|
|
263
356
|
// ── Workspaces ───────────────────────────────────────────────
|
|
264
357
|
workspaces: [],
|
|
@@ -448,6 +541,13 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
448
541
|
}));
|
|
449
542
|
},
|
|
450
543
|
|
|
544
|
+
updateTopicSystemContext: async (id, systemContext) => {
|
|
545
|
+
await api.updateTopic(id, { systemContext });
|
|
546
|
+
set((s) => ({
|
|
547
|
+
topics: s.topics.map((t) => (t.id === id ? { ...t, systemContext } : t)),
|
|
548
|
+
}));
|
|
549
|
+
},
|
|
550
|
+
|
|
451
551
|
addQuestion: async (topicId, title) => {
|
|
452
552
|
const question = await api.createQuestion(topicId, title);
|
|
453
553
|
set((s) => ({
|
|
@@ -468,6 +568,29 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
468
568
|
}));
|
|
469
569
|
},
|
|
470
570
|
|
|
571
|
+
moveQuestion: async (questionId, topicId, parentQuestionId) => {
|
|
572
|
+
await api.updateQuestion(questionId, {
|
|
573
|
+
parentQuestionId,
|
|
574
|
+
});
|
|
575
|
+
set((s) => ({
|
|
576
|
+
questionsByTopic: {
|
|
577
|
+
...s.questionsByTopic,
|
|
578
|
+
[topicId]: (s.questionsByTopic[topicId] || []).map((q) =>
|
|
579
|
+
q.id === questionId
|
|
580
|
+
? { ...q, parentQuestionId: parentQuestionId ?? undefined }
|
|
581
|
+
: q,
|
|
582
|
+
),
|
|
583
|
+
},
|
|
584
|
+
currentQuestion:
|
|
585
|
+
s.currentQuestion?.id === questionId
|
|
586
|
+
? {
|
|
587
|
+
...s.currentQuestion,
|
|
588
|
+
parentQuestionId: parentQuestionId ?? undefined,
|
|
589
|
+
}
|
|
590
|
+
: s.currentQuestion,
|
|
591
|
+
}));
|
|
592
|
+
},
|
|
593
|
+
|
|
471
594
|
removeQuestion: async (questionId, topicId) => {
|
|
472
595
|
await api.deleteQuestion(questionId);
|
|
473
596
|
set((s) => ({
|
|
@@ -682,6 +805,38 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
682
805
|
}));
|
|
683
806
|
},
|
|
684
807
|
|
|
808
|
+
linkConversation: async (questionId, linkedId) => {
|
|
809
|
+
const q = get().currentQuestion;
|
|
810
|
+
const current =
|
|
811
|
+
q?.id === questionId ? q : await api.fetchQuestion(questionId);
|
|
812
|
+
const existing = current.linkedConversationIds ?? [];
|
|
813
|
+
if (existing.includes(linkedId)) return;
|
|
814
|
+
const updated = [...existing, linkedId];
|
|
815
|
+
await api.updateQuestion(questionId, { linkedConversationIds: updated });
|
|
816
|
+
set((s) => ({
|
|
817
|
+
currentQuestion:
|
|
818
|
+
s.currentQuestion?.id === questionId
|
|
819
|
+
? { ...s.currentQuestion, linkedConversationIds: updated }
|
|
820
|
+
: s.currentQuestion,
|
|
821
|
+
}));
|
|
822
|
+
},
|
|
823
|
+
|
|
824
|
+
unlinkConversation: async (questionId, linkedId) => {
|
|
825
|
+
const q = get().currentQuestion;
|
|
826
|
+
const current =
|
|
827
|
+
q?.id === questionId ? q : await api.fetchQuestion(questionId);
|
|
828
|
+
const updated = (current.linkedConversationIds ?? []).filter(
|
|
829
|
+
(id) => id !== linkedId,
|
|
830
|
+
);
|
|
831
|
+
await api.updateQuestion(questionId, { linkedConversationIds: updated });
|
|
832
|
+
set((s) => ({
|
|
833
|
+
currentQuestion:
|
|
834
|
+
s.currentQuestion?.id === questionId
|
|
835
|
+
? { ...s.currentQuestion, linkedConversationIds: updated }
|
|
836
|
+
: s.currentQuestion,
|
|
837
|
+
}));
|
|
838
|
+
},
|
|
839
|
+
|
|
685
840
|
fetchWorkspaceFiles: async () => {
|
|
686
841
|
const files = await api.fetchWorkspaceFiles();
|
|
687
842
|
set({ workspaceFiles: files });
|
|
@@ -727,16 +882,26 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
727
882
|
openDocViewer: (fileId, quote, fileName) =>
|
|
728
883
|
set({ viewingDoc: { fileId, quote, fileName } }),
|
|
729
884
|
closeDocViewer: () => set({ viewingDoc: null }),
|
|
730
|
-
openCodeRunner: (code = "", language = "typescript") =>
|
|
885
|
+
openCodeRunner: (code = "", language = "typescript", fileId?) =>
|
|
731
886
|
set({
|
|
732
887
|
showCodeRunner: true,
|
|
888
|
+
showInfraLab: false,
|
|
733
889
|
runnerInitialCode: code,
|
|
734
890
|
runnerInitialLanguage: language,
|
|
735
891
|
runnerInitialSandbox: null,
|
|
892
|
+
runnerInitialFileId: fileId ?? null,
|
|
736
893
|
}),
|
|
737
|
-
openSandbox: (
|
|
894
|
+
openSandbox: (
|
|
895
|
+
serverCode,
|
|
896
|
+
serverLang,
|
|
897
|
+
clientCode,
|
|
898
|
+
clientLang,
|
|
899
|
+
fileId?,
|
|
900
|
+
opts?,
|
|
901
|
+
) =>
|
|
738
902
|
set({
|
|
739
903
|
showCodeRunner: true,
|
|
904
|
+
showInfraLab: false,
|
|
740
905
|
runnerInitialCode: "",
|
|
741
906
|
runnerInitialLanguage: "typescript",
|
|
742
907
|
runnerInitialSandbox: {
|
|
@@ -745,7 +910,54 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
745
910
|
clientCode,
|
|
746
911
|
clientLang,
|
|
747
912
|
fileId,
|
|
913
|
+
clientType: opts?.clientType,
|
|
914
|
+
reactFiles: opts?.reactFiles,
|
|
915
|
+
reactActiveFile: opts?.reactActiveFile,
|
|
916
|
+
},
|
|
917
|
+
runnerInitialFileId: null,
|
|
918
|
+
}),
|
|
919
|
+
openInfraLab: (workspace, fileId?) =>
|
|
920
|
+
set({
|
|
921
|
+
showInfraLab: true,
|
|
922
|
+
showCodeRunner: false,
|
|
923
|
+
runnerInitialInfra: workspace ?? null,
|
|
924
|
+
runnerInitialInfraFileId: fileId ?? null,
|
|
925
|
+
}),
|
|
926
|
+
openReactLab: (workspace?, fileId?, serverCode?, serverLang?) =>
|
|
927
|
+
set({
|
|
928
|
+
showCodeRunner: true,
|
|
929
|
+
showInfraLab: false,
|
|
930
|
+
runnerInitialCode: "",
|
|
931
|
+
runnerInitialLanguage: "typescript",
|
|
932
|
+
runnerInitialSandbox: {
|
|
933
|
+
serverCode: serverCode ?? DEFAULT_SERVER_CODE,
|
|
934
|
+
serverLang: serverLang ?? "typescript",
|
|
935
|
+
clientCode: "",
|
|
936
|
+
clientLang: "javascript",
|
|
937
|
+
fileId,
|
|
938
|
+
clientType: "react",
|
|
939
|
+
reactFiles: workspace?.files ?? null,
|
|
940
|
+
reactActiveFile: workspace?.activeFile ?? null,
|
|
941
|
+
},
|
|
942
|
+
runnerInitialFileId: null,
|
|
943
|
+
}),
|
|
944
|
+
openNextLab: (workspace?, fileId?, serverCode?, serverLang?) =>
|
|
945
|
+
set({
|
|
946
|
+
showCodeRunner: true,
|
|
947
|
+
showInfraLab: false,
|
|
948
|
+
runnerInitialCode: "",
|
|
949
|
+
runnerInitialLanguage: "typescript",
|
|
950
|
+
runnerInitialSandbox: {
|
|
951
|
+
serverCode: serverCode ?? DEFAULT_SERVER_CODE,
|
|
952
|
+
serverLang: serverLang ?? "typescript",
|
|
953
|
+
clientCode: "",
|
|
954
|
+
clientLang: "javascript",
|
|
955
|
+
fileId,
|
|
956
|
+
clientType: "nextjs",
|
|
957
|
+
reactFiles: workspace?.files ?? null,
|
|
958
|
+
reactActiveFile: workspace?.activeFile ?? null,
|
|
748
959
|
},
|
|
960
|
+
runnerInitialFileId: null,
|
|
749
961
|
}),
|
|
750
962
|
|
|
751
963
|
overwriteContextFileContent: async (questionId, fileId, content) => {
|
|
@@ -767,6 +979,7 @@ export const useStore = create<Store>((set, get) => ({
|
|
|
767
979
|
}));
|
|
768
980
|
},
|
|
769
981
|
closeCodeRunner: () => set({ showCodeRunner: false }),
|
|
982
|
+
closeInfraLab: () => set({ showInfraLab: false }),
|
|
770
983
|
|
|
771
984
|
fetchAiSettings: async () => {
|
|
772
985
|
const settings = await api.fetchAiSettings();
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export type ContextFileOrigin =
|
|
2
|
+
| "user"
|
|
3
|
+
| "ai"
|
|
4
|
+
| "upload"
|
|
5
|
+
| "sandbox"
|
|
6
|
+
| "infra"
|
|
7
|
+
| "react"
|
|
8
|
+
| "nextjs";
|
|
9
|
+
|
|
1
10
|
export interface ContextFile {
|
|
2
11
|
id: string;
|
|
3
12
|
name: string;
|
|
@@ -6,14 +15,33 @@ export interface ContextFile {
|
|
|
6
15
|
createdAt: string;
|
|
7
16
|
/** Distinguishes how this file was added. 'upload' = user-uploaded doc,
|
|
8
17
|
* 'user' = code saved from Code Runner, 'ai' = AI-generated code block,
|
|
9
|
-
* 'sandbox' = paired server+client sandbox saved as JSON
|
|
10
|
-
|
|
18
|
+
* 'sandbox' = paired server+client sandbox saved as JSON,
|
|
19
|
+
* 'infra' = Terraform-style infra lab workspace saved as JSON. */
|
|
20
|
+
origin?: ContextFileOrigin;
|
|
11
21
|
/** Language hint for code snippets (e.g. 'typescript', 'javascript'). */
|
|
12
22
|
language?: string;
|
|
13
23
|
/** Short display label for code snippets. */
|
|
14
24
|
label?: string;
|
|
15
25
|
}
|
|
16
26
|
|
|
27
|
+
export interface FrontendLabWorkspace {
|
|
28
|
+
version: 1;
|
|
29
|
+
label: string;
|
|
30
|
+
/** Determines defaults, entry file, and file-tree conventions. */
|
|
31
|
+
type: "react" | "nextjs";
|
|
32
|
+
activeFile: string;
|
|
33
|
+
files: Record<string, string>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface InfraLabWorkspace {
|
|
37
|
+
version: 1;
|
|
38
|
+
label: string;
|
|
39
|
+
provider: "aws";
|
|
40
|
+
executionMode: "plan-only" | "localstack";
|
|
41
|
+
activeFile: string;
|
|
42
|
+
files: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
|
|
17
45
|
export interface WorkspaceMeta {
|
|
18
46
|
id: string;
|
|
19
47
|
name: string;
|
|
@@ -37,6 +65,8 @@ export interface Topic {
|
|
|
37
65
|
id: string;
|
|
38
66
|
name: string;
|
|
39
67
|
contextFiles: ContextFile[];
|
|
68
|
+
/** Topic-wide system prompt prepended to every question's context in this topic. */
|
|
69
|
+
systemContext?: string;
|
|
40
70
|
createdAt: string;
|
|
41
71
|
}
|
|
42
72
|
|
|
@@ -82,7 +112,7 @@ export interface ReadingBookmark {
|
|
|
82
112
|
export interface Question {
|
|
83
113
|
id: string;
|
|
84
114
|
topicId: string;
|
|
85
|
-
parentQuestionId?: string;
|
|
115
|
+
parentQuestionId?: string | null;
|
|
86
116
|
title: string;
|
|
87
117
|
systemContext: string;
|
|
88
118
|
codeContextFiles: string[];
|
|
@@ -90,5 +120,7 @@ export interface Question {
|
|
|
90
120
|
messages: Message[];
|
|
91
121
|
annotations?: Annotation[];
|
|
92
122
|
readingBookmark?: ReadingBookmark;
|
|
123
|
+
/** IDs of other questions in the same topic whose conversation history is injected as context. */
|
|
124
|
+
linkedConversationIds?: string[];
|
|
93
125
|
createdAt: string;
|
|
94
126
|
}
|
|
@@ -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"}
|
|
@@ -357,7 +357,10 @@ 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 === "infra")
|
|
361
364
|
) {
|
|
362
365
|
try {
|
|
363
366
|
await fs.mkdir(ctxDir, { recursive: true });
|
|
@@ -562,7 +565,35 @@ async function getExportDriveClient(): Promise<drive_v3.Drive> {
|
|
|
562
565
|
/* ok */
|
|
563
566
|
}
|
|
564
567
|
});
|
|
565
|
-
|
|
568
|
+
|
|
569
|
+
// Wrap the drive client so any invalid_grant errors clear the stored token
|
|
570
|
+
// and surface a friendlier error that the caller can detect.
|
|
571
|
+
const drive = google.drive({ version: "v3", auth: client });
|
|
572
|
+
const originalFiles = drive.files as any;
|
|
573
|
+
const patchMethod = (obj: any, method: string) => {
|
|
574
|
+
const orig = obj[method].bind(obj);
|
|
575
|
+
obj[method] = async (...args: any[]) => {
|
|
576
|
+
try {
|
|
577
|
+
return await orig(...args);
|
|
578
|
+
} catch (err: any) {
|
|
579
|
+
if (err?.message?.includes("invalid_grant") || err?.code === 400) {
|
|
580
|
+
try {
|
|
581
|
+
await fs.unlink(EXPORT_TOKENS_FILE);
|
|
582
|
+
} catch {
|
|
583
|
+
/* already gone */
|
|
584
|
+
}
|
|
585
|
+
const e = new Error("NEEDS_REAUTH");
|
|
586
|
+
(e as any).needsReauth = true;
|
|
587
|
+
throw e;
|
|
588
|
+
}
|
|
589
|
+
throw err;
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
};
|
|
593
|
+
["create", "update", "delete", "list", "get"].forEach((m) =>
|
|
594
|
+
patchMethod(originalFiles, m),
|
|
595
|
+
);
|
|
596
|
+
return drive;
|
|
566
597
|
}
|
|
567
598
|
|
|
568
599
|
export interface ExportResult {
|
|
@@ -736,7 +767,10 @@ export async function exportWorkspace(
|
|
|
736
767
|
if (
|
|
737
768
|
cf.origin === "user" ||
|
|
738
769
|
cf.origin === "ai" ||
|
|
739
|
-
cf.origin === "sandbox"
|
|
770
|
+
cf.origin === "sandbox" ||
|
|
771
|
+
cf.origin === "react" ||
|
|
772
|
+
cf.origin === "nextjs" ||
|
|
773
|
+
cf.origin === "infra"
|
|
740
774
|
) {
|
|
741
775
|
try {
|
|
742
776
|
const content = await fs.readFile(
|