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
|
@@ -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,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<
|
|
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"}
|
package/template/cockpit.json
CHANGED
package/template/package.json
CHANGED
|
@@ -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": "
|
|
9
|
+
"sync:template": "node scripts/sync-template.js"
|
|
10
10
|
},
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"concurrently": "^9.1.0"
|