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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/template/client/package-lock.json +753 -1
  3. package/template/client/package.json +4 -0
  4. package/template/client/src/App.tsx +20 -0
  5. package/template/client/src/api.ts +455 -3
  6. package/template/client/src/components/AiSettingsModal.tsx +855 -248
  7. package/template/client/src/components/AnnotationDialog.tsx +3 -9
  8. package/template/client/src/components/ChatMessage.tsx +132 -27
  9. package/template/client/src/components/ChatView.tsx +365 -123
  10. package/template/client/src/components/CodeContextPanel.tsx +714 -0
  11. package/template/client/src/components/CodeLineAnnotationPopup.tsx +179 -0
  12. package/template/client/src/components/CodeRunnerModal.tsx +3030 -0
  13. package/template/client/src/components/DocRefModal.tsx +551 -0
  14. package/template/client/src/components/FileAttachments.tsx +128 -12
  15. package/template/client/src/components/FilePickerModal.tsx +181 -0
  16. package/template/client/src/components/FileViewerModal.tsx +406 -28
  17. package/template/client/src/components/InfraLabModal.tsx +1706 -0
  18. package/template/client/src/components/LinkedConvosPicker.tsx +128 -0
  19. package/template/client/src/components/MarkdownRenderer.tsx +219 -2
  20. package/template/client/src/components/NotesModal.tsx +977 -0
  21. package/template/client/src/components/PlotEmbed.tsx +173 -0
  22. package/template/client/src/components/Sidebar.tsx +397 -127
  23. package/template/client/src/components/TextAnnotator.tsx +8 -15
  24. package/template/client/src/components/VizCraftEmbed.tsx +412 -25
  25. package/template/client/src/components/WorkspaceSwitcher.tsx +4 -0
  26. package/template/client/src/infraLab.ts +124 -0
  27. package/template/client/src/reactLab.ts +477 -0
  28. package/template/client/src/store.ts +416 -2
  29. package/template/client/src/types.ts +41 -1
  30. package/template/client/tsconfig.tsbuildinfo +1 -1
  31. package/template/cockpit.json +1 -1
  32. package/template/package.json +1 -1
  33. package/template/server/src/google-drive.ts +144 -2
  34. package/template/server/src/index.ts +1890 -188
  35. package/template/server/src/infra-runner.ts +1104 -0
  36. package/template/server/src/storage.ts +274 -3
@@ -1,8 +1,36 @@
1
1
  import { create } from "zustand";
2
- import type { Topic, Question, CodeSnippet, WorkspaceMeta } from "./types";
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,9 @@ const DEFAULT_AI_SETTINGS: AiSettings = {
11
39
  normal: { maxOutputTokens: 3000, maxSteps: 5 },
12
40
  },
13
41
  vizGuide: "",
42
+ plotGuide: "",
43
+ alwaysSendPrefsDefault: false,
44
+ thinkingBudget: 0,
14
45
  promptGroups: {
15
46
  length: {
16
47
  label: "Response Length",
@@ -50,6 +81,19 @@ const DEFAULT_AI_SETTINGS: AiSettings = {
50
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.",
51
82
  },
52
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
+ },
53
97
  },
54
98
  };
55
99
 
@@ -64,6 +108,7 @@ interface Store {
64
108
  showCodePanel: boolean;
65
109
  showSidebar: boolean;
66
110
  viewingFile: string | null;
111
+ viewingDoc: { fileId: string; quote: string; fileName: string } | null;
67
112
 
68
113
  // ── Workspaces ───────────────────────────────────────────────
69
114
  workspaces: WorkspaceMeta[];
@@ -105,19 +150,30 @@ interface Store {
105
150
  createDriveSubfolder: (
106
151
  id: string,
107
152
  name: string,
108
- ) => Promise<import("./api").DriveFolder>;
153
+ ) => Promise<
154
+ import("./api").DriveFolder | { needsAuth: true; authUrl: string }
155
+ >;
109
156
 
110
157
  fetchTopics: () => Promise<void>;
111
158
  fetchQuestions: (topicId: string) => Promise<void>;
112
159
  addTopic: (name: string) => Promise<void>;
113
160
  removeTopic: (id: string) => Promise<void>;
114
161
  renameTopic: (id: string, name: string) => Promise<void>;
162
+ updateTopicSystemContext: (
163
+ id: string,
164
+ systemContext: string,
165
+ ) => Promise<void>;
115
166
  addQuestion: (topicId: string, title: string) => Promise<void>;
116
167
  addChildQuestion: (
117
168
  topicId: string,
118
169
  parentQuestionId: string,
119
170
  title: string,
120
171
  ) => Promise<void>;
172
+ moveQuestion: (
173
+ questionId: string,
174
+ topicId: string,
175
+ parentQuestionId: string | null,
176
+ ) => Promise<void>;
121
177
  removeQuestion: (questionId: string, topicId: string) => Promise<void>;
122
178
  renameQuestion: (
123
179
  questionId: string,
@@ -136,18 +192,57 @@ interface Store {
136
192
  files: FileList | File[],
137
193
  ) => Promise<void>;
138
194
  removeTopicFile: (topicId: string, fileId: string) => Promise<void>;
195
+ linkFileToTopic: (
196
+ topicId: string,
197
+ fileId: string,
198
+ originalName: string,
199
+ ) => Promise<void>;
139
200
  uploadQuestionFiles: (
140
201
  questionId: string,
141
202
  files: FileList | File[],
142
203
  ) => Promise<void>;
143
204
  removeQuestionFile: (questionId: string, fileId: string) => Promise<void>;
205
+ linkFileToQuestion: (
206
+ questionId: string,
207
+ fileId: string,
208
+ originalName: string,
209
+ ) => Promise<void>;
210
+ saveCodeSnippetToQuestion: (
211
+ questionId: string,
212
+ code: string,
213
+ language: string,
214
+ label: string,
215
+ origin: "user" | "ai" | "sandbox" | "infra" | "react" | "nextjs",
216
+ ) => Promise<import("./types").ContextFile>;
144
217
  clearMessages: (questionId: string) => Promise<void>;
218
+
219
+ // ── Linked Conversations ─────────────────────────────────────
220
+ linkConversation: (questionId: string, linkedId: string) => Promise<void>;
221
+ unlinkConversation: (questionId: string, linkedId: string) => Promise<void>;
222
+
223
+ // ── Workspace Context Files ──────────────────────────────────
224
+ workspaceFiles: import("./types").ContextFile[];
225
+ fetchWorkspaceFiles: () => Promise<void>;
226
+ uploadWorkspaceFiles: (files: FileList | File[]) => Promise<void>;
227
+ removeWorkspaceFile: (fileId: string) => Promise<void>;
228
+
145
229
  updateQuestionSystemContext: (
146
230
  questionId: string,
147
231
  systemContext: string,
148
232
  ) => Promise<void>;
149
233
  openFileViewer: (path: string) => void;
150
234
  closeFileViewer: () => void;
235
+ openDocViewer: (fileId: string, quote: string, fileName: string) => void;
236
+ closeDocViewer: () => void;
237
+ /** Inline code blocks written by the AI, keyed by `inline:<id>` */
238
+ inlineCodeSnippets: Record<
239
+ string,
240
+ { content: string; language: string; label: string }
241
+ >;
242
+ registerInlineCode: (
243
+ id: string,
244
+ entry: { content: string; language: string; label: string },
245
+ ) => void;
151
246
  codeSnippets: CodeSnippet[];
152
247
  addSnippet: (snippet: CodeSnippet) => void;
153
248
  removeSnippet: (id: string) => void;
@@ -157,13 +252,83 @@ interface Store {
157
252
  aiSettings: AiSettings;
158
253
  fetchAiSettings: () => Promise<void>;
159
254
  saveAiSettings: (patch: Partial<AiSettings>) => Promise<void>;
255
+ /** The currently active preference suffix (LENGTH/STYLE/AUDIENCE/etc) built by ChatView. */
256
+ livePreferenceSuffix: string;
257
+ setLivePreferenceSuffix: (suffix: string) => void;
160
258
  showSettings: boolean;
161
259
  openSettings: () => void;
162
260
  closeSettings: () => void;
261
+
262
+ // ── Code Runner ──────────────────────────────────────────────
263
+ showCodeRunner: boolean;
264
+ /** Code pre-filled into the runner when opened */
265
+ runnerInitialCode: string;
266
+ /** Language hint — 'typescript' or 'javascript' */
267
+ runnerInitialLanguage: string;
268
+ /** When set, opens the runner in sandbox mode with these values pre-filled */
269
+ runnerInitialSandbox: {
270
+ serverCode: string;
271
+ serverLang: string;
272
+ clientCode: string;
273
+ clientLang: string;
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;
279
+ } | null;
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;
283
+ openSandbox: (
284
+ serverCode: string,
285
+ serverLang: string,
286
+ clientCode: string,
287
+ clientLang: string,
288
+ fileId?: string,
289
+ opts?: {
290
+ clientType?: "script" | "react" | "nextjs";
291
+ reactFiles?: Record<string, string>;
292
+ reactActiveFile?: string;
293
+ },
294
+ ) => void;
295
+ overwriteContextFileContent: (
296
+ questionId: string,
297
+ fileId: string,
298
+ content: string,
299
+ ) => Promise<void>;
300
+ renameContextFile: (
301
+ questionId: string,
302
+ fileId: string,
303
+ label: string,
304
+ ) => Promise<void>;
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;
163
327
  }
164
328
 
165
329
  export const useStore = create<Store>((set, get) => ({
166
330
  topics: [],
331
+ workspaceFiles: [],
167
332
  questionsByTopic: {},
168
333
  selectedTopicId: null,
169
334
  selectedQuestionId: null,
@@ -173,9 +338,20 @@ export const useStore = create<Store>((set, get) => ({
173
338
  showCodePanel: false,
174
339
  showSidebar: true,
175
340
  viewingFile: null,
341
+ viewingDoc: null,
342
+ inlineCodeSnippets: {},
176
343
  codeSnippets: [],
177
344
  aiSettings: DEFAULT_AI_SETTINGS,
345
+ livePreferenceSuffix: "",
178
346
  showSettings: false,
347
+ showCodeRunner: false,
348
+ runnerInitialCode: "",
349
+ runnerInitialLanguage: "typescript",
350
+ runnerInitialSandbox: null,
351
+ runnerInitialFileId: null,
352
+ showInfraLab: false,
353
+ runnerInitialInfra: null,
354
+ runnerInitialInfraFileId: null,
179
355
 
180
356
  // ── Workspaces ───────────────────────────────────────────────
181
357
  workspaces: [],
@@ -215,6 +391,8 @@ export const useStore = create<Store>((set, get) => ({
215
391
  });
216
392
  const topics = await api.fetchTopics();
217
393
  set({ topics });
394
+ const workspaceFiles = await api.fetchWorkspaceFiles();
395
+ set({ workspaceFiles });
218
396
  },
219
397
 
220
398
  deleteWorkspace: async (id) => {
@@ -363,6 +541,13 @@ export const useStore = create<Store>((set, get) => ({
363
541
  }));
364
542
  },
365
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
+
366
551
  addQuestion: async (topicId, title) => {
367
552
  const question = await api.createQuestion(topicId, title);
368
553
  set((s) => ({
@@ -383,6 +568,29 @@ export const useStore = create<Store>((set, get) => ({
383
568
  }));
384
569
  },
385
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
+
386
594
  removeQuestion: async (questionId, topicId) => {
387
595
  await api.deleteQuestion(questionId);
388
596
  set((s) => ({
@@ -506,6 +714,17 @@ export const useStore = create<Store>((set, get) => ({
506
714
  }));
507
715
  },
508
716
 
717
+ linkFileToTopic: async (topicId, fileId, originalName) => {
718
+ const cf = await api.linkFileToTopic(topicId, fileId, originalName);
719
+ set((s) => ({
720
+ topics: s.topics.map((t) =>
721
+ t.id === topicId
722
+ ? { ...t, contextFiles: [...(t.contextFiles || []), cf] }
723
+ : t,
724
+ ),
725
+ }));
726
+ },
727
+
509
728
  uploadQuestionFiles: async (questionId, files) => {
510
729
  const uploaded = await api.uploadQuestionFiles(questionId, files);
511
730
  set((s) => ({
@@ -537,6 +756,45 @@ export const useStore = create<Store>((set, get) => ({
537
756
  }));
538
757
  },
539
758
 
759
+ linkFileToQuestion: async (questionId, fileId, originalName) => {
760
+ const cf = await api.linkFileToQuestion(questionId, fileId, originalName);
761
+ set((s) => ({
762
+ currentQuestion:
763
+ s.currentQuestion?.id === questionId
764
+ ? {
765
+ ...s.currentQuestion,
766
+ contextFiles: [...(s.currentQuestion.contextFiles || []), cf],
767
+ }
768
+ : s.currentQuestion,
769
+ }));
770
+ },
771
+
772
+ saveCodeSnippetToQuestion: async (
773
+ questionId,
774
+ code,
775
+ language,
776
+ label,
777
+ origin,
778
+ ) => {
779
+ const cf = await api.saveCodeSnippet(
780
+ questionId,
781
+ code,
782
+ language,
783
+ label,
784
+ origin,
785
+ );
786
+ set((s) => ({
787
+ currentQuestion:
788
+ s.currentQuestion?.id === questionId
789
+ ? {
790
+ ...s.currentQuestion,
791
+ contextFiles: [...(s.currentQuestion.contextFiles || []), cf],
792
+ }
793
+ : s.currentQuestion,
794
+ }));
795
+ return cf;
796
+ },
797
+
540
798
  clearMessages: async (questionId) => {
541
799
  await api.updateQuestion(questionId, { messages: [] });
542
800
  set((s) => ({
@@ -547,6 +805,55 @@ export const useStore = create<Store>((set, get) => ({
547
805
  }));
548
806
  },
549
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
+
840
+ fetchWorkspaceFiles: async () => {
841
+ const files = await api.fetchWorkspaceFiles();
842
+ set({ workspaceFiles: files });
843
+ },
844
+
845
+ uploadWorkspaceFiles: async (files) => {
846
+ const uploaded = await api.uploadWorkspaceFiles(files);
847
+ set((s) => ({ workspaceFiles: [...s.workspaceFiles, ...uploaded] }));
848
+ },
849
+
850
+ removeWorkspaceFile: async (fileId) => {
851
+ await api.deleteWorkspaceFile(fileId);
852
+ set((s) => ({
853
+ workspaceFiles: s.workspaceFiles.filter((f) => f.id !== fileId),
854
+ }));
855
+ },
856
+
550
857
  updateQuestionSystemContext: async (questionId, systemContext) => {
551
858
  const updated = await api.updateQuestion(questionId, { systemContext });
552
859
  set((s) => ({
@@ -568,6 +875,111 @@ export const useStore = create<Store>((set, get) => ({
568
875
 
569
876
  openFileViewer: (path) => set({ viewingFile: path }),
570
877
  closeFileViewer: () => set({ viewingFile: null }),
878
+ registerInlineCode: (id, entry) =>
879
+ set((s) => ({
880
+ inlineCodeSnippets: { ...s.inlineCodeSnippets, [id]: entry },
881
+ })),
882
+ openDocViewer: (fileId, quote, fileName) =>
883
+ set({ viewingDoc: { fileId, quote, fileName } }),
884
+ closeDocViewer: () => set({ viewingDoc: null }),
885
+ openCodeRunner: (code = "", language = "typescript", fileId?) =>
886
+ set({
887
+ showCodeRunner: true,
888
+ showInfraLab: false,
889
+ runnerInitialCode: code,
890
+ runnerInitialLanguage: language,
891
+ runnerInitialSandbox: null,
892
+ runnerInitialFileId: fileId ?? null,
893
+ }),
894
+ openSandbox: (
895
+ serverCode,
896
+ serverLang,
897
+ clientCode,
898
+ clientLang,
899
+ fileId?,
900
+ opts?,
901
+ ) =>
902
+ set({
903
+ showCodeRunner: true,
904
+ showInfraLab: false,
905
+ runnerInitialCode: "",
906
+ runnerInitialLanguage: "typescript",
907
+ runnerInitialSandbox: {
908
+ serverCode,
909
+ serverLang,
910
+ clientCode,
911
+ clientLang,
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,
959
+ },
960
+ runnerInitialFileId: null,
961
+ }),
962
+
963
+ overwriteContextFileContent: async (questionId, fileId, content) => {
964
+ await api.overwriteContextFileContent(questionId, fileId, content);
965
+ },
966
+
967
+ renameContextFile: async (questionId, fileId, label) => {
968
+ const cf = await api.renameContextFile(questionId, fileId, label);
969
+ set((s) => ({
970
+ currentQuestion:
971
+ s.currentQuestion?.id === questionId
972
+ ? {
973
+ ...s.currentQuestion,
974
+ contextFiles: (s.currentQuestion.contextFiles || []).map((f) =>
975
+ f.id === fileId ? { ...f, label: cf.label, name: cf.name } : f,
976
+ ),
977
+ }
978
+ : s.currentQuestion,
979
+ }));
980
+ },
981
+ closeCodeRunner: () => set({ showCodeRunner: false }),
982
+ closeInfraLab: () => set({ showInfraLab: false }),
571
983
 
572
984
  fetchAiSettings: async () => {
573
985
  const settings = await api.fetchAiSettings();
@@ -579,6 +991,8 @@ export const useStore = create<Store>((set, get) => ({
579
991
  set({ aiSettings: updated });
580
992
  },
581
993
 
994
+ setLivePreferenceSuffix: (suffix) => set({ livePreferenceSuffix: suffix }),
995
+
582
996
  openSettings: () => set({ showSettings: true }),
583
997
  closeSettings: () => set({ showSettings: false }),
584
998
  }));
@@ -1,9 +1,45 @@
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;
4
13
  originalName: string;
5
14
  driveFileId?: string;
6
15
  createdAt: string;
16
+ /** Distinguishes how this file was added. 'upload' = user-uploaded doc,
17
+ * 'user' = code saved from Code Runner, 'ai' = AI-generated code block,
18
+ * 'sandbox' = paired server+client sandbox saved as JSON,
19
+ * 'infra' = Terraform-style infra lab workspace saved as JSON. */
20
+ origin?: ContextFileOrigin;
21
+ /** Language hint for code snippets (e.g. 'typescript', 'javascript'). */
22
+ language?: string;
23
+ /** Short display label for code snippets. */
24
+ label?: string;
25
+ }
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>;
7
43
  }
8
44
 
9
45
  export interface WorkspaceMeta {
@@ -29,6 +65,8 @@ export interface Topic {
29
65
  id: string;
30
66
  name: string;
31
67
  contextFiles: ContextFile[];
68
+ /** Topic-wide system prompt prepended to every question's context in this topic. */
69
+ systemContext?: string;
32
70
  createdAt: string;
33
71
  }
34
72
 
@@ -74,7 +112,7 @@ export interface ReadingBookmark {
74
112
  export interface Question {
75
113
  id: string;
76
114
  topicId: string;
77
- parentQuestionId?: string;
115
+ parentQuestionId?: string | null;
78
116
  title: string;
79
117
  systemContext: string;
80
118
  codeContextFiles: string[];
@@ -82,5 +120,7 @@ export interface Question {
82
120
  messages: Message[];
83
121
  annotations?: Annotation[];
84
122
  readingBookmark?: ReadingBookmark;
123
+ /** IDs of other questions in the same topic whose conversation history is injected as context. */
124
+ linkedConversationIds?: string[];
85
125
  createdAt: string;
86
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"}
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.3.0"
2
+ "version": "0.4.0"
3
3
  }
@@ -6,7 +6,7 @@
6
6
  "dev": "concurrently -n server,client -c blue,green \"npm run dev --prefix server\" \"npm run dev --prefix client\"",
7
7
  "build": "npm run build --prefix client",
8
8
  "start": "npm run start --prefix server",
9
- "sync:template": "npx create-interview-cockpit upgrade"
9
+ "sync:template": "node scripts/sync-template.js"
10
10
  },
11
11
  "devDependencies": {
12
12
  "concurrently": "^9.1.0"