create-interview-cockpit 0.5.0 → 0.7.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 +384 -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 +530 -2
- package/template/client/src/components/CodeRunnerModal.tsx +1895 -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 +960 -0
- package/template/client/src/store.ts +250 -6
- package/template/client/src/types.ts +36 -3
- package/template/client/tsconfig.tsbuildinfo +1 -1
- package/template/cockpit.json +1 -1
- package/template/server/src/google-drive.ts +39 -3
- package/template/server/src/index.ts +954 -52
- package/template/server/src/infra-runner.ts +1104 -0
- package/template/server/src/storage.ts +22 -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(
|
|
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" ||
|
|
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
|
|
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
|
-
|
|
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="
|
|
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="
|
|
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
|
);
|