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.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/template/client/package-lock.json +734 -1
  3. package/template/client/package.json +1 -0
  4. package/template/client/src/App.tsx +3 -0
  5. package/template/client/src/api.ts +321 -4
  6. package/template/client/src/components/AiSettingsModal.tsx +818 -425
  7. package/template/client/src/components/ChatMessage.tsx +34 -12
  8. package/template/client/src/components/ChatView.tsx +298 -121
  9. package/template/client/src/components/CodeContextPanel.tsx +419 -2
  10. package/template/client/src/components/CodeRunnerModal.tsx +1601 -120
  11. package/template/client/src/components/DocRefModal.tsx +55 -6
  12. package/template/client/src/components/FileAttachments.tsx +20 -4
  13. package/template/client/src/components/InfraLabModal.tsx +1706 -0
  14. package/template/client/src/components/LinkedConvosPicker.tsx +128 -0
  15. package/template/client/src/components/MarkdownRenderer.tsx +22 -8
  16. package/template/client/src/components/NotesModal.tsx +977 -0
  17. package/template/client/src/components/PlotEmbed.tsx +173 -0
  18. package/template/client/src/components/Sidebar.tsx +184 -0
  19. package/template/client/src/components/VizCraftEmbed.tsx +257 -13
  20. package/template/client/src/components/WorkspaceSwitcher.tsx +4 -0
  21. package/template/client/src/infraLab.ts +124 -0
  22. package/template/client/src/reactLab.ts +477 -0
  23. package/template/client/src/store.ts +219 -6
  24. package/template/client/src/types.ts +35 -3
  25. package/template/client/tsconfig.tsbuildinfo +1 -1
  26. package/template/server/src/google-drive.ts +37 -3
  27. package/template/server/src/index.ts +693 -52
  28. package/template/server/src/infra-runner.ts +1104 -0
  29. package/template/server/src/storage.ts +13 -3
@@ -1,4 +1,6 @@
1
1
  import { useEffect, useRef, useState, useCallback, useMemo } from "react";
2
+ import ReactMarkdown from "react-markdown";
3
+ import remarkGfm from "remark-gfm";
2
4
  import {
3
5
  X,
4
6
  GripVertical,
@@ -25,11 +27,13 @@ type ResizeDir = "e" | "s" | "se" | "sw" | "w" | "ne" | "nw" | "n" | null;
25
27
 
26
28
  const PDF_EXTS = new Set(["pdf"]);
27
29
  const IMAGE_EXTS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg"]);
30
+ const MD_EXTS = new Set(["md", "mdx"]);
28
31
 
29
- function getFileType(name: string): "pdf" | "image" | "text" {
32
+ function getFileType(name: string): "pdf" | "image" | "md" | "text" {
30
33
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
31
34
  if (PDF_EXTS.has(ext)) return "pdf";
32
35
  if (IMAGE_EXTS.has(ext)) return "image";
36
+ if (MD_EXTS.has(ext)) return "md";
33
37
  return "text";
34
38
  }
35
39
 
@@ -60,7 +64,9 @@ export default function DocRefModal({
60
64
 
61
65
  // Text mode state
62
66
  const [content, setContent] = useState<string | null>(null);
63
- const [loading, setLoading] = useState(fileType === "text");
67
+ const [loading, setLoading] = useState(
68
+ fileType === "text" || fileType === "md",
69
+ );
64
70
  const [fetchError, setFetchError] = useState<string | null>(null);
65
71
  const highlightRef = useRef<HTMLElement | null>(null);
66
72
 
@@ -101,10 +107,12 @@ export default function DocRefModal({
101
107
  const savedPos = useRef(pos);
102
108
  const savedSize = useRef(size);
103
109
 
104
- // Fetch extracted text — for text files on mount, or for PDFs when text view is activated
110
+ // Fetch extracted text — for text/md files on mount, or for PDFs when text view is activated
105
111
  useEffect(() => {
106
112
  const needsText =
107
- fileType === "text" || (fileType === "pdf" && pdfViewMode === "text");
113
+ fileType === "text" ||
114
+ fileType === "md" ||
115
+ (fileType === "pdf" && pdfViewMode === "text");
108
116
  if (!needsText) return;
109
117
  if (content !== null) return; // already loaded, skip re-fetch
110
118
  setLoading(true);
@@ -328,8 +336,8 @@ export default function DocRefModal({
328
336
  </button>
329
337
  </div>
330
338
 
331
- {/* ── Search bar (PDF + text; hidden for plain images) ── */}
332
- {fileType !== "image" && (
339
+ {/* ── Search bar (PDF + text; hidden for images and markdown) ── */}
340
+ {fileType !== "image" && fileType !== "md" && (
333
341
  <form
334
342
  onSubmit={handleSearchSubmit}
335
343
  className="flex items-center gap-2 px-3 py-1.5 bg-slate-800/60 border-b border-slate-700/60 shrink-0"
@@ -433,6 +441,47 @@ export default function DocRefModal({
433
441
  </div>
434
442
  )}
435
443
 
444
+ {/* Markdown — rendered with react-markdown */}
445
+ {fileType === "md" && (
446
+ <>
447
+ {loading && (
448
+ <div className="flex items-center justify-center h-full">
449
+ <Loader2 className="w-5 h-5 text-emerald-400 animate-spin" />
450
+ </div>
451
+ )}
452
+ {fetchError && (
453
+ <div className="flex items-center justify-center h-full p-4">
454
+ <p className="text-sm text-red-400 text-center">
455
+ {fetchError}
456
+ </p>
457
+ </div>
458
+ )}
459
+ {!loading && !fetchError && content !== null && (
460
+ <div className="w-full h-full overflow-auto p-5">
461
+ <div
462
+ className="prose prose-invert prose-sm max-w-none
463
+ prose-headings:text-slate-100 prose-headings:font-semibold
464
+ prose-p:text-slate-300 prose-p:leading-relaxed
465
+ prose-a:text-cyan-400 prose-a:no-underline hover:prose-a:underline
466
+ prose-code:text-emerald-300 prose-code:bg-slate-800 prose-code:px-1 prose-code:rounded
467
+ prose-pre:bg-slate-800/80 prose-pre:border prose-pre:border-slate-700
468
+ prose-blockquote:border-l-violet-500 prose-blockquote:text-slate-400
469
+ prose-strong:text-slate-100
470
+ prose-li:text-slate-300
471
+ prose-hr:border-slate-700
472
+ prose-table:text-slate-300
473
+ prose-th:text-slate-200 prose-th:border prose-th:border-slate-700 prose-th:bg-slate-800/60
474
+ prose-td:border prose-td:border-slate-700/60"
475
+ >
476
+ <ReactMarkdown remarkPlugins={[remarkGfm]}>
477
+ {content}
478
+ </ReactMarkdown>
479
+ </div>
480
+ </div>
481
+ )}
482
+ </>
483
+ )}
484
+
436
485
  {/* Text / DOCX / other — extracted-text view with highlight */}
437
486
  {fileType === "text" && (
438
487
  <>
@@ -33,11 +33,17 @@ export default function FileAttachments({
33
33
  }: Props) {
34
34
  const inputRef = useRef<HTMLInputElement>(null);
35
35
  const [showPicker, setShowPicker] = useState(false);
36
+ const [uploadError, setUploadError] = useState<string | null>(null);
36
37
  const openDocViewer = useStore((s) => s.openDocViewer);
37
38
 
38
39
  const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
39
40
  if (e.target.files?.length) {
40
- await onUpload(e.target.files);
41
+ setUploadError(null);
42
+ try {
43
+ await onUpload(e.target.files);
44
+ } catch (err: any) {
45
+ setUploadError(err?.message ?? "Upload failed");
46
+ }
41
47
  e.target.value = "";
42
48
  }
43
49
  };
@@ -61,7 +67,7 @@ export default function FileAttachments({
61
67
  multiple
62
68
  onChange={handleChange}
63
69
  className="hidden"
64
- accept=".txt,.md,.ts,.tsx,.js,.jsx,.json,.css,.scss,.html,.xml,.yaml,.yml,.csv,.py,.java,.cs,.go,.rs,.sql,.sh,.toml,.ini,.log,.cfg,.conf,.env,.pdf,.docx"
70
+ accept="*"
65
71
  />
66
72
  {files.map((f) => (
67
73
  <span
@@ -117,6 +123,11 @@ export default function FileAttachments({
117
123
  Link
118
124
  </button>
119
125
  )}
126
+ {uploadError && (
127
+ <span className="text-[10px] text-red-400 ml-1" title={uploadError}>
128
+ ⚠ {uploadError}
129
+ </span>
130
+ )}
120
131
  </div>
121
132
  );
122
133
  }
@@ -139,7 +150,7 @@ export default function FileAttachments({
139
150
  multiple
140
151
  onChange={handleChange}
141
152
  className="hidden"
142
- accept=".txt,.md,.ts,.tsx,.js,.jsx,.json,.css,.scss,.html,.xml,.yaml,.yml,.csv,.py,.java,.cs,.go,.rs,.sql,.sh,.toml,.ini,.log,.cfg,.conf,.env,.pdf,.docx"
153
+ accept="*"
143
154
  />
144
155
 
145
156
  {files.length > 0 && (
@@ -183,7 +194,7 @@ export default function FileAttachments({
183
194
  </div>
184
195
  )}
185
196
 
186
- <div className="flex items-center gap-3">
197
+ <div className="flex items-center gap-3 flex-wrap">
187
198
  <button
188
199
  onClick={() => inputRef.current?.click()}
189
200
  className="flex items-center gap-1 text-xs text-slate-600 hover:text-violet-400 transition-colors"
@@ -201,6 +212,11 @@ export default function FileAttachments({
201
212
  Link existing
202
213
  </button>
203
214
  )}
215
+ {uploadError && (
216
+ <span className="text-xs text-red-400" title={uploadError}>
217
+ ⚠ {uploadError}
218
+ </span>
219
+ )}
204
220
  </div>
205
221
  </div>
206
222
  );