create-interview-cockpit 0.4.0 → 0.5.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 +19 -0
- package/template/client/package.json +3 -0
- package/template/client/src/App.tsx +17 -0
- package/template/client/src/api.ts +135 -0
- package/template/client/src/components/AiSettingsModal.tsx +218 -4
- package/template/client/src/components/AnnotationDialog.tsx +3 -9
- package/template/client/src/components/ChatMessage.tsx +110 -27
- package/template/client/src/components/ChatView.tsx +69 -4
- package/template/client/src/components/CodeContextPanel.tsx +297 -0
- package/template/client/src/components/CodeLineAnnotationPopup.tsx +179 -0
- package/template/client/src/components/CodeRunnerModal.tsx +1549 -0
- package/template/client/src/components/DocRefModal.tsx +502 -0
- package/template/client/src/components/FileAttachments.tsx +109 -9
- package/template/client/src/components/FilePickerModal.tsx +181 -0
- package/template/client/src/components/FileViewerModal.tsx +406 -28
- package/template/client/src/components/MarkdownRenderer.tsx +205 -2
- package/template/client/src/components/Sidebar.tsx +213 -127
- package/template/client/src/components/TextAnnotator.tsx +8 -15
- package/template/client/src/components/VizCraftEmbed.tsx +162 -19
- package/template/client/src/store.ts +201 -0
- package/template/client/src/types.ts +8 -0
- package/template/cockpit.json +1 -1
- package/template/package.json +1 -1
- package/template/server/src/google-drive.ts +109 -1
- package/template/server/src/index.ts +1107 -46
- package/template/server/src/storage.ts +263 -2
|
@@ -313,6 +313,14 @@ export async function syncWorkspace(
|
|
|
313
313
|
const questions = pending.filter((p) => !p.isContextFile);
|
|
314
314
|
const contextFiles = pending.filter((p) => p.isContextFile);
|
|
315
315
|
|
|
316
|
+
// Collect parent-title links to re-apply after all questions are saved
|
|
317
|
+
// (IDs are regenerated on import, so we must link by title within a topic)
|
|
318
|
+
const parentLinks: Array<{
|
|
319
|
+
questionId: string;
|
|
320
|
+
topicId: string;
|
|
321
|
+
parentTitle: string;
|
|
322
|
+
}> = [];
|
|
323
|
+
|
|
316
324
|
await Promise.all(
|
|
317
325
|
questions.map(async ({ topicId, filename, buffer }) => {
|
|
318
326
|
try {
|
|
@@ -320,6 +328,9 @@ export async function syncWorkspace(
|
|
|
320
328
|
let systemContext = "";
|
|
321
329
|
let messages: storage.Question["messages"] = [];
|
|
322
330
|
let codeContextFiles: string[] = [];
|
|
331
|
+
let codeAnnotations: storage.Question["codeAnnotations"] = {};
|
|
332
|
+
let parentTitle: string | null = null;
|
|
333
|
+
const restoredContextFiles: storage.ContextFile[] = [];
|
|
323
334
|
|
|
324
335
|
if (filename.endsWith(".json")) {
|
|
325
336
|
// Our own exported JSON — restore fields directly, no text extraction
|
|
@@ -329,6 +340,42 @@ export async function syncWorkspace(
|
|
|
329
340
|
systemContext = parsed.systemContext || "";
|
|
330
341
|
messages = parsed.messages || [];
|
|
331
342
|
codeContextFiles = parsed.codeContextFiles || [];
|
|
343
|
+
codeAnnotations = parsed.codeAnnotations || {};
|
|
344
|
+
parentTitle = parsed.parentTitle || null;
|
|
345
|
+
|
|
346
|
+
// Restore code snippet context files (user/ai origin)
|
|
347
|
+
if (
|
|
348
|
+
Array.isArray(parsed.codeSnippets) &&
|
|
349
|
+
parsed.codeSnippets.length > 0
|
|
350
|
+
) {
|
|
351
|
+
const ctxDir =
|
|
352
|
+
storage.getContextFilesDirForWorkspace(workspaceId);
|
|
353
|
+
await Promise.all(
|
|
354
|
+
parsed.codeSnippets.map(
|
|
355
|
+
async (cs: storage.ContextFile & { content?: string }) => {
|
|
356
|
+
if (
|
|
357
|
+
cs.content &&
|
|
358
|
+
(cs.origin === "user" ||
|
|
359
|
+
cs.origin === "ai" ||
|
|
360
|
+
cs.origin === "sandbox")
|
|
361
|
+
) {
|
|
362
|
+
try {
|
|
363
|
+
await fs.mkdir(ctxDir, { recursive: true });
|
|
364
|
+
await fs.writeFile(
|
|
365
|
+
path.join(ctxDir, cs.id),
|
|
366
|
+
Buffer.from(cs.content, "utf-8"),
|
|
367
|
+
);
|
|
368
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
369
|
+
const { content: _content, ...cfMeta } = cs;
|
|
370
|
+
restoredContextFiles.push(cfMeta);
|
|
371
|
+
} catch {
|
|
372
|
+
/* skip bad blobs */
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
),
|
|
377
|
+
);
|
|
378
|
+
}
|
|
332
379
|
} catch {
|
|
333
380
|
// Malformed JSON — fall through to raw text
|
|
334
381
|
systemContext = buffer.toString("utf-8");
|
|
@@ -344,18 +391,47 @@ export async function syncWorkspace(
|
|
|
344
391
|
title,
|
|
345
392
|
systemContext,
|
|
346
393
|
codeContextFiles,
|
|
347
|
-
contextFiles:
|
|
394
|
+
contextFiles: restoredContextFiles,
|
|
348
395
|
messages,
|
|
396
|
+
codeAnnotations,
|
|
349
397
|
createdAt: new Date().toISOString(),
|
|
350
398
|
};
|
|
351
399
|
await storage.saveQuestion(q);
|
|
352
400
|
result.filesImported++;
|
|
401
|
+
if (parentTitle) {
|
|
402
|
+
parentLinks.push({ questionId: q.id, topicId, parentTitle });
|
|
403
|
+
}
|
|
353
404
|
} catch (err: any) {
|
|
354
405
|
result.errors.push(`${filename}: ${err?.message ?? "failed"}`);
|
|
355
406
|
}
|
|
356
407
|
}),
|
|
357
408
|
);
|
|
358
409
|
|
|
410
|
+
// ── Phase 4b: re-link parent–child relationships by title ───────────────────
|
|
411
|
+
// IDs are regenerated on import, so we match parent by title within the same topic.
|
|
412
|
+
if (parentLinks.length > 0) {
|
|
413
|
+
// Group by topicId to avoid redundant getQuestionsByTopic calls
|
|
414
|
+
const byTopic = new Map<string, typeof parentLinks>();
|
|
415
|
+
for (const link of parentLinks) {
|
|
416
|
+
const arr = byTopic.get(link.topicId) ?? [];
|
|
417
|
+
arr.push(link);
|
|
418
|
+
byTopic.set(link.topicId, arr);
|
|
419
|
+
}
|
|
420
|
+
for (const [topicId, links] of byTopic) {
|
|
421
|
+
const allInTopic = await storage.getQuestionsByTopic(topicId);
|
|
422
|
+
for (const { questionId, parentTitle } of links) {
|
|
423
|
+
const parent = allInTopic.find(
|
|
424
|
+
(q) => q.title === parentTitle && q.id !== questionId,
|
|
425
|
+
);
|
|
426
|
+
const child = allInTopic.find((q) => q.id === questionId);
|
|
427
|
+
if (parent && child) {
|
|
428
|
+
child.parentQuestionId = parent.id;
|
|
429
|
+
await storage.saveQuestion(child);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
359
435
|
// ── Phase 5: save context file blobs in parallel, then update topics.json once ─
|
|
360
436
|
if (contextFiles.length > 0) {
|
|
361
437
|
// Write binary files in parallel
|
|
@@ -645,13 +721,45 @@ export async function exportWorkspace(
|
|
|
645
721
|
try {
|
|
646
722
|
const safeName =
|
|
647
723
|
q.title.replace(/[/\\:*?"<>|]/g, "-").trim() || q.id;
|
|
724
|
+
// Look up parent title so the relationship can be restored on import
|
|
725
|
+
const parentTitle = q.parentQuestionId
|
|
726
|
+
? questions.find((p) => p.id === q.parentQuestionId)?.title
|
|
727
|
+
: undefined;
|
|
728
|
+
|
|
729
|
+
// Read code snippet blobs so they survive drive sync
|
|
730
|
+
const ctxDir =
|
|
731
|
+
storage.getContextFilesDirForWorkspace(workspaceId);
|
|
732
|
+
const contextFilesWithContent: Array<
|
|
733
|
+
storage.ContextFile & { content?: string }
|
|
734
|
+
> = [];
|
|
735
|
+
for (const cf of q.contextFiles || []) {
|
|
736
|
+
if (
|
|
737
|
+
cf.origin === "user" ||
|
|
738
|
+
cf.origin === "ai" ||
|
|
739
|
+
cf.origin === "sandbox"
|
|
740
|
+
) {
|
|
741
|
+
try {
|
|
742
|
+
const content = await fs.readFile(
|
|
743
|
+
path.join(ctxDir, cf.id),
|
|
744
|
+
"utf-8",
|
|
745
|
+
);
|
|
746
|
+
contextFilesWithContent.push({ ...cf, content });
|
|
747
|
+
} catch {
|
|
748
|
+
contextFilesWithContent.push(cf);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
648
753
|
// Export full question as JSON so nothing is lost on sync-back
|
|
649
754
|
const payload = JSON.stringify(
|
|
650
755
|
{
|
|
651
756
|
title: q.title,
|
|
757
|
+
parentTitle: parentTitle ?? null,
|
|
652
758
|
systemContext: q.systemContext || "",
|
|
653
759
|
messages: q.messages,
|
|
654
760
|
codeContextFiles: q.codeContextFiles,
|
|
761
|
+
codeAnnotations: q.codeAnnotations ?? {},
|
|
762
|
+
codeSnippets: contextFilesWithContent,
|
|
655
763
|
},
|
|
656
764
|
null,
|
|
657
765
|
2,
|