create-interview-cockpit 0.17.1 → 0.17.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-interview-cockpit",
3
- "version": "0.17.1",
3
+ "version": "0.17.3",
4
4
  "description": "Scaffold a personal AI-powered interview prep cockpit",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,6 +2,7 @@ import type {
2
2
  Topic,
3
3
  Question,
4
4
  ContextFile,
5
+ ContextFileOrigin,
5
6
  WorkspacesRegistry,
6
7
  InfraLabWorkspace,
7
8
  } from "./types";
@@ -342,15 +343,7 @@ export async function saveCodeSnippet(
342
343
  code: string,
343
344
  language: string,
344
345
  label: string,
345
- origin:
346
- | "user"
347
- | "ai"
348
- | "sandbox"
349
- | "browser-security"
350
- | "infra"
351
- | "react"
352
- | "nextjs"
353
- | "module-federation",
346
+ origin: Exclude<ContextFileOrigin, "upload">,
354
347
  ): Promise<ContextFile> {
355
348
  const res = await fetch(`${BASE}/questions/${questionId}/save-code-snippet`, {
356
349
  method: "POST",
@@ -27,6 +27,7 @@ interface Props {
27
27
  newSpec: string,
28
28
  ) => void;
29
29
  onDeleteMessage?: (messageId: string) => void;
30
+ isStreaming?: boolean;
30
31
  }
31
32
 
32
33
  function getTextContent(message: UIMessage): string {
@@ -90,10 +91,11 @@ const ChatMessage = memo(function ChatMessage({
90
91
  preferenceSuffix,
91
92
  onSpecRefined,
92
93
  onDeleteMessage,
94
+ isStreaming = false,
93
95
  }: Props) {
94
96
  const isUser = message.role === "user";
95
97
  const content = getTextContent(message);
96
- const reasoning = !isUser ? getReasoningContent(message) : "";
98
+ const reasoning = !isUser && !isStreaming ? getReasoningContent(message) : "";
97
99
  const [copied, setCopied] = useState(false);
98
100
 
99
101
  // Stable wrappers so MarkdownRenderer's `components` useMemo doesn't invalidate
@@ -196,19 +198,26 @@ const ChatMessage = memo(function ChatMessage({
196
198
  ) : (
197
199
  <>
198
200
  {reasoning && <ThinkingBlock reasoning={reasoning} />}
199
- <TextAnnotator
200
- content={content}
201
- messageId={message.id}
202
- annotations={annotations}
203
- onAnnotationCreate={onAnnotationCreate ?? (() => {})}
204
- onAnnotationUpdate={onAnnotationUpdate ?? (() => {})}
205
- bookmarkedBlockIndex={bookmarkedBlockIndex}
206
- onBookmarkBlock={
207
- onSetBookmark ? stableBookmarkBlock : undefined
208
- }
209
- preferenceSuffix={preferenceSuffix}
210
- onSpecRefined={onSpecRefined ? stableSpecRefined : undefined}
211
- />
201
+ {isStreaming ? (
202
+ <div className="whitespace-pre-wrap break-words text-slate-200">
203
+ {content}
204
+ <span className="ml-1 inline-block h-3 w-1 animate-pulse rounded-sm bg-cyan-400/70 align-middle" />
205
+ </div>
206
+ ) : (
207
+ <TextAnnotator
208
+ content={content}
209
+ messageId={message.id}
210
+ annotations={annotations}
211
+ onAnnotationCreate={onAnnotationCreate ?? (() => {})}
212
+ onAnnotationUpdate={onAnnotationUpdate ?? (() => {})}
213
+ bookmarkedBlockIndex={bookmarkedBlockIndex}
214
+ onBookmarkBlock={
215
+ onSetBookmark ? stableBookmarkBlock : undefined
216
+ }
217
+ preferenceSuffix={preferenceSuffix}
218
+ onSpecRefined={onSpecRefined ? stableSpecRefined : undefined}
219
+ />
220
+ )}
212
221
  </>
213
222
  )}
214
223
  </div>
@@ -227,6 +227,11 @@ const ChatTranscript = memo(function ChatTranscript({
227
227
  preferenceSuffix={preferenceSuffix}
228
228
  onSpecRefined={onSpecRefined}
229
229
  onDeleteMessage={!isLoading ? onDeleteMessage : undefined}
230
+ isStreaming={
231
+ status === "streaming" &&
232
+ message.role === "assistant" &&
233
+ message.id === messages[messages.length - 1]?.id
234
+ }
230
235
  />
231
236
  </div>
232
237
  ))}
@@ -504,6 +509,7 @@ export default function ChatView({ question }: Props) {
504
509
  id: question.id,
505
510
  transport,
506
511
  messages: initialMessages,
512
+ experimental_throttle: 80,
507
513
  onError: handleChatError,
508
514
  onFinish: handleChatFinish,
509
515
  });
@@ -542,6 +548,7 @@ export default function ChatView({ question }: Props) {
542
548
  // Only show the Continue button if the last assistant message looks truncated
543
549
  // (doesn't end with sentence-terminating punctuation or a closing code fence).
544
550
  const lastResponseLooksTruncated = useMemo(() => {
551
+ if (status !== "ready") return false;
545
552
  if (messages.length === 0) return false;
546
553
  const last = messages[messages.length - 1];
547
554
  if (last.role !== "assistant") return false;
@@ -562,7 +569,7 @@ export default function ChatView({ question }: Props) {
562
569
  const lastLine = (lines.filter(Boolean).pop() ?? "").trimEnd();
563
570
  // Complete if ends with sentence punctuation or structural close chars
564
571
  return !/[.!?`\])>]$/.test(lastLine);
565
- }, [messages]);
572
+ }, [messages, status]);
566
573
 
567
574
  // Smooth scroll to bottom whenever a NEW message is added (length changes).
568
575
  // Using messages.length instead of the full messages array avoids firing on
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  useCallback,
3
3
  useEffect,
4
+ useMemo,
4
5
  useRef,
5
6
  useState,
6
7
  useLayoutEffect,
@@ -36,7 +37,12 @@ import {
36
37
  import { useStore } from "../store";
37
38
  import Editor from "react-simple-code-editor";
38
39
  import MonacoEditorLib from "@monaco-editor/react";
39
- import type { OnMount, BeforeMount, Monaco } from "@monaco-editor/react";
40
+ import type {
41
+ OnMount,
42
+ BeforeMount,
43
+ Monaco,
44
+ OnChange,
45
+ } from "@monaco-editor/react";
40
46
  import { setupTypeAcquisition } from "@typescript/ata";
41
47
  import ts from "typescript";
42
48
  import Prism from "prismjs";
@@ -430,8 +436,17 @@ function MonacoEditorWrapper({
430
436
  }) {
431
437
  const fontSizePx = parseInt(fontSize) || 12;
432
438
  const monacoRef = useRef<Monaco | null>(null);
439
+ const onChangeRef = useRef(onChange);
433
440
  const { initATA, acquireTypes } = useATA(monacoRef);
434
441
 
442
+ useEffect(() => {
443
+ onChangeRef.current = onChange;
444
+ }, [onChange]);
445
+
446
+ const handleEditorChange = useCallback<OnChange>((val) => {
447
+ onChangeRef.current(val ?? "");
448
+ }, []);
449
+
435
450
  const handleBeforeMount: BeforeMount = (monaco) => {
436
451
  monacoRef.current = monaco;
437
452
 
@@ -659,7 +674,8 @@ declare module 'next/server' {
659
674
 
660
675
  // Re-scan for new imports whenever code changes
661
676
  useEffect(() => {
662
- acquireTypes(value);
677
+ const id = window.setTimeout(() => acquireTypes(value), 400);
678
+ return () => window.clearTimeout(id);
663
679
  }, [value, acquireTypes]);
664
680
 
665
681
  const modelPath =
@@ -669,6 +685,30 @@ declare module 'next/server' {
669
685
  : `file:///${path.replace(/^\/+/, "")}`
670
686
  : undefined;
671
687
 
688
+ const editorOptions = useMemo(
689
+ () => ({
690
+ fontSize: fontSizePx,
691
+ fontFamily: EDITOR_FONT,
692
+ minimap: { enabled: false },
693
+ scrollBeyondLastLine: false,
694
+ lineNumbers: "on" as const,
695
+ glyphMargin: false,
696
+ folding: false,
697
+ lineDecorationsWidth: 4,
698
+ lineNumbersMinChars: 3,
699
+ renderLineHighlight: "line" as const,
700
+ overviewRulerLanes: 0,
701
+ hideCursorInOverviewRuler: true,
702
+ overviewRulerBorder: false,
703
+ scrollbar: { vertical: "auto" as const, horizontal: "auto" as const },
704
+ padding: { top: 8, bottom: 8 },
705
+ wordWrap: "off" as const,
706
+ automaticLayout: true,
707
+ placeholder,
708
+ }),
709
+ [fontSizePx, placeholder],
710
+ );
711
+
672
712
  return (
673
713
  <div className="absolute inset-0">
674
714
  <MonacoEditorLib
@@ -678,30 +718,11 @@ declare module 'next/server' {
678
718
  language={language.includes("typescript") ? "typescript" : "javascript"}
679
719
  path={modelPath}
680
720
  theme="vs-dark"
681
- value={value}
682
- onChange={(val) => onChange(val ?? "")}
721
+ defaultValue={value}
722
+ onChange={handleEditorChange}
683
723
  beforeMount={handleBeforeMount}
684
724
  onMount={handleMount}
685
- options={{
686
- fontSize: fontSizePx,
687
- fontFamily: EDITOR_FONT,
688
- minimap: { enabled: false },
689
- scrollBeyondLastLine: false,
690
- lineNumbers: "on",
691
- glyphMargin: false,
692
- folding: false,
693
- lineDecorationsWidth: 4,
694
- lineNumbersMinChars: 3,
695
- renderLineHighlight: "line",
696
- overviewRulerLanes: 0,
697
- hideCursorInOverviewRuler: true,
698
- overviewRulerBorder: false,
699
- scrollbar: { vertical: "auto", horizontal: "auto" },
700
- padding: { top: 8, bottom: 8 },
701
- wordWrap: "off",
702
- automaticLayout: true,
703
- placeholder,
704
- }}
725
+ options={editorOptions}
705
726
  />
706
727
  </div>
707
728
  );
@@ -2574,6 +2595,18 @@ export default function CodeRunnerModal() {
2574
2595
  : clientType === "module-federation"
2575
2596
  ? "module-federation"
2576
2597
  : "react";
2598
+ let pendingDelta = "";
2599
+ let flushTimer: number | null = null;
2600
+ const flushPendingDelta = () => {
2601
+ if (!pendingDelta || abort.aborted) return;
2602
+ const chunk = pendingDelta;
2603
+ pendingDelta = "";
2604
+ setSbxChatMessages((prev) =>
2605
+ prev.map((m) =>
2606
+ m.id === aId ? { ...m, content: m.content + chunk } : m,
2607
+ ),
2608
+ );
2609
+ };
2577
2610
  try {
2578
2611
  const history = [...sbxChatMessages, userMsg].map((m) => ({
2579
2612
  role: m.role,
@@ -2589,14 +2622,17 @@ export default function CodeRunnerModal() {
2589
2622
  },
2590
2623
  (delta) => {
2591
2624
  if (abort.aborted) return;
2592
- setSbxChatMessages((prev) =>
2593
- prev.map((m) =>
2594
- m.id === aId ? { ...m, content: m.content + delta } : m,
2595
- ),
2596
- );
2625
+ pendingDelta += delta;
2626
+ if (flushTimer !== null) return;
2627
+ flushTimer = window.setTimeout(() => {
2628
+ flushTimer = null;
2629
+ flushPendingDelta();
2630
+ }, 80);
2597
2631
  },
2598
2632
  );
2633
+ flushPendingDelta();
2599
2634
  } catch (err: unknown) {
2635
+ flushPendingDelta();
2600
2636
  if (!abort.aborted)
2601
2637
  setSbxChatMessages((prev) =>
2602
2638
  prev.map((m) =>
@@ -2606,6 +2642,7 @@ export default function CodeRunnerModal() {
2606
2642
  ),
2607
2643
  );
2608
2644
  } finally {
2645
+ if (flushTimer !== null) window.clearTimeout(flushTimer);
2609
2646
  if (!abort.aborted) setSbxChatLoading(false);
2610
2647
  }
2611
2648
  }, [
@@ -7016,7 +7053,7 @@ export default function CodeRunnerModal() {
7016
7053
  )}
7017
7054
  </p>
7018
7055
  )}
7019
- {sbxChatMessages.map((msg) => (
7056
+ {sbxChatMessages.map((msg, index) => (
7020
7057
  <div
7021
7058
  key={msg.id}
7022
7059
  className={`flex flex-col gap-0.5 ${msg.role === "user" ? "items-end" : "items-start"}`}
@@ -7031,67 +7068,76 @@ export default function CodeRunnerModal() {
7031
7068
  {msg.role === "user" ? (
7032
7069
  msg.content
7033
7070
  ) : msg.content ? (
7034
- <ReactMarkdown
7035
- remarkPlugins={[remarkGfm]}
7036
- components={{
7037
- code({ className, children, ...props }) {
7038
- const isBlock =
7039
- className?.startsWith("language-");
7040
- return isBlock ? (
7041
- <pre className="bg-slate-900/80 rounded p-2 overflow-x-auto my-1">
7071
+ sbxChatLoading &&
7072
+ msg.role === "assistant" &&
7073
+ index === sbxChatMessages.length - 1 ? (
7074
+ <div className="whitespace-pre-wrap break-words">
7075
+ {msg.content}
7076
+ <span className="ml-1 inline-block h-2.5 w-1 animate-pulse rounded-sm bg-violet-400/70 align-middle" />
7077
+ </div>
7078
+ ) : (
7079
+ <ReactMarkdown
7080
+ remarkPlugins={[remarkGfm]}
7081
+ components={{
7082
+ code({ className, children, ...props }) {
7083
+ const isBlock =
7084
+ className?.startsWith("language-");
7085
+ return isBlock ? (
7086
+ <pre className="bg-slate-900/80 rounded p-2 overflow-x-auto my-1">
7087
+ <code
7088
+ className={`${className ?? ""} text-[11px]`}
7089
+ {...props}
7090
+ >
7091
+ {children}
7092
+ </code>
7093
+ </pre>
7094
+ ) : (
7042
7095
  <code
7043
- className={`${className ?? ""} text-[11px]`}
7096
+ className="bg-slate-900/60 px-1 rounded text-violet-300 text-[11px]"
7044
7097
  {...props}
7045
7098
  >
7046
7099
  {children}
7047
7100
  </code>
7048
- </pre>
7049
- ) : (
7050
- <code
7051
- className="bg-slate-900/60 px-1 rounded text-violet-300 text-[11px]"
7052
- {...props}
7053
- >
7054
- {children}
7055
- </code>
7056
- );
7057
- },
7058
- p({ children }) {
7059
- return (
7060
- <p className="mb-1 last:mb-0">{children}</p>
7061
- );
7062
- },
7063
- ul({ children }) {
7064
- return (
7065
- <ul className="list-disc list-inside mb-1 space-y-0.5">
7066
- {children}
7067
- </ul>
7068
- );
7069
- },
7070
- ol({ children }) {
7071
- return (
7072
- <ol className="list-decimal list-inside mb-1 space-y-0.5">
7073
- {children}
7074
- </ol>
7075
- );
7076
- },
7077
- h2({ children }) {
7078
- return (
7079
- <h2 className="text-xs font-semibold text-slate-200 mt-2 mb-0.5">
7080
- {children}
7081
- </h2>
7082
- );
7083
- },
7084
- h3({ children }) {
7085
- return (
7086
- <h3 className="text-xs font-semibold text-slate-300 mt-1.5 mb-0.5">
7087
- {children}
7088
- </h3>
7089
- );
7090
- },
7091
- }}
7092
- >
7093
- {msg.content}
7094
- </ReactMarkdown>
7101
+ );
7102
+ },
7103
+ p({ children }) {
7104
+ return (
7105
+ <p className="mb-1 last:mb-0">{children}</p>
7106
+ );
7107
+ },
7108
+ ul({ children }) {
7109
+ return (
7110
+ <ul className="list-disc list-inside mb-1 space-y-0.5">
7111
+ {children}
7112
+ </ul>
7113
+ );
7114
+ },
7115
+ ol({ children }) {
7116
+ return (
7117
+ <ol className="list-decimal list-inside mb-1 space-y-0.5">
7118
+ {children}
7119
+ </ol>
7120
+ );
7121
+ },
7122
+ h2({ children }) {
7123
+ return (
7124
+ <h2 className="text-xs font-semibold text-slate-200 mt-2 mb-0.5">
7125
+ {children}
7126
+ </h2>
7127
+ );
7128
+ },
7129
+ h3({ children }) {
7130
+ return (
7131
+ <h3 className="text-xs font-semibold text-slate-300 mt-1.5 mb-0.5">
7132
+ {children}
7133
+ </h3>
7134
+ );
7135
+ },
7136
+ }}
7137
+ >
7138
+ {msg.content}
7139
+ </ReactMarkdown>
7140
+ )
7095
7141
  ) : (
7096
7142
  <span className="flex items-center gap-1.5 text-slate-500">
7097
7143
  <Loader2 className="w-3 h-3 animate-spin" />{" "}
@@ -365,7 +365,7 @@ export default function InfraLabModal() {
365
365
  };
366
366
  }, [activeInfraId]);
367
367
 
368
- const runInfra = async (action: InfraRunAction) => {
368
+ const runInfra = async (action: Exclude<InfraRunAction, "command">) => {
369
369
  if (!currentQuestion) return;
370
370
 
371
371
  setRunError(null);
@@ -15,6 +15,7 @@ import {
15
15
  Loader2,
16
16
  } from "lucide-react";
17
17
  import { useStore } from "../store";
18
+ import type { DriveFolder } from "../api";
18
19
  import type { WorkspaceMeta } from "../types";
19
20
 
20
21
  // ── Workspace Switcher ─────────────────────────────────────────────────────────
@@ -78,7 +79,7 @@ export default function WorkspaceSwitcher() {
78
79
  // Subfolder picker state (export destination)
79
80
  const [folderPicker, setFolderPicker] = useState<{
80
81
  ws: WorkspaceMeta;
81
- folders: { id: string; name: string }[];
82
+ folders: DriveFolder[];
82
83
  loading: boolean;
83
84
  } | null>(null);
84
85
  const [newFolderName, setNewFolderName] = useState("");
@@ -370,7 +371,7 @@ export default function WorkspaceSwitcher() {
370
371
  folderPicker.ws.id,
371
372
  name,
372
373
  );
373
- if ("needsAuth" in created && created.needsAuth) {
374
+ if ("needsAuth" in created) {
374
375
  window.location.href = created.authUrl;
375
376
  return;
376
377
  }
@@ -6,6 +6,7 @@ import type {
6
6
  WorkspaceMeta,
7
7
  InfraLabWorkspace,
8
8
  FrontendLabWorkspace,
9
+ ContextFileOrigin,
9
10
  } from "./types";
10
11
  import type { AiSettings } from "./api";
11
12
  import * as api from "./api";
@@ -216,16 +217,7 @@ interface Store {
216
217
  code: string,
217
218
  language: string,
218
219
  label: string,
219
- origin:
220
- | "user"
221
- | "ai"
222
- | "sandbox"
223
- | "browser-security"
224
- | "infra"
225
- | "react"
226
- | "nextjs"
227
- | "module-federation"
228
- | "canvas",
220
+ origin: Exclude<ContextFileOrigin, "upload">,
229
221
  ) => Promise<import("./types").ContextFile>;
230
222
  clearMessages: (questionId: string) => Promise<void>;
231
223
 
@@ -1 +1 @@
1
- {"root":["./src/app.tsx","./src/api.ts","./src/browsersecuritytemplates.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/browsersecuritylabmodal.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/deploymentlabmodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/infralabmodal.tsx","./src/components/labspanel.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.tsx","./src/components/plotembed.tsx","./src/components/sidebar.tsx","./src/components/textannotator.tsx","./src/components/vizcraftembed.tsx","./src/components/workspaceswitcher.tsx"],"errors":true,"version":"5.9.3"}
1
+ {"root":["./src/app.tsx","./src/api.ts","./src/browsersecuritytemplates.ts","./src/infralab.ts","./src/main.tsx","./src/reactlab.ts","./src/store.ts","./src/types.ts","./src/vite-env.d.ts","./src/components/aisettingsmodal.tsx","./src/components/annotationdialog.tsx","./src/components/browsersecuritylabmodal.tsx","./src/components/canvaslabmodal.tsx","./src/components/chatmessage.tsx","./src/components/chatview.tsx","./src/components/codecontextpanel.tsx","./src/components/codelineannotationpopup.tsx","./src/components/coderunnermodal.tsx","./src/components/deploymentlabmodal.tsx","./src/components/docrefmodal.tsx","./src/components/fileattachments.tsx","./src/components/filepickermodal.tsx","./src/components/fileviewermodal.tsx","./src/components/infralabmodal.tsx","./src/components/labspanel.tsx","./src/components/linkedconvospicker.tsx","./src/components/markdownrenderer.tsx","./src/components/mermaiddiagram.tsx","./src/components/notesmodal.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.16.0"
2
+ "version": "0.17.1"
3
3
  }