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
|
@@ -16,9 +16,7 @@ interface Props {
|
|
|
16
16
|
onUpdate: (updated: Annotation) => void;
|
|
17
17
|
messageContent: string;
|
|
18
18
|
initialPos?: { x: number; y: number };
|
|
19
|
-
|
|
20
|
-
responseStyle?: string;
|
|
21
|
-
responseAudience?: string;
|
|
19
|
+
preferenceSuffix?: string;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
export default function AnnotationDialog({
|
|
@@ -27,9 +25,7 @@ export default function AnnotationDialog({
|
|
|
27
25
|
onUpdate,
|
|
28
26
|
messageContent,
|
|
29
27
|
initialPos,
|
|
30
|
-
|
|
31
|
-
responseStyle,
|
|
32
|
-
responseAudience,
|
|
28
|
+
preferenceSuffix,
|
|
33
29
|
}: Props) {
|
|
34
30
|
const [pos, setPos] = useState(() => ({
|
|
35
31
|
x: initialPos?.x ?? Math.max(0, (window.innerWidth - DEFAULT_W) / 2),
|
|
@@ -169,9 +165,7 @@ export default function AnnotationDialog({
|
|
|
169
165
|
messageContent,
|
|
170
166
|
priorResponse: ann.response,
|
|
171
167
|
followUps: ann.followUps ?? [],
|
|
172
|
-
|
|
173
|
-
responseStyle,
|
|
174
|
-
responseAudience,
|
|
168
|
+
preferenceSuffix,
|
|
175
169
|
}),
|
|
176
170
|
});
|
|
177
171
|
const data = await res.json();
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { memo } from "react";
|
|
1
|
+
import { memo, useState, useCallback } from "react";
|
|
2
2
|
import type { UIMessage } from "ai";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
User,
|
|
5
|
+
Bot,
|
|
6
|
+
Copy,
|
|
7
|
+
Check,
|
|
8
|
+
ChevronDown,
|
|
9
|
+
ChevronRight,
|
|
10
|
+
Brain,
|
|
11
|
+
Trash2,
|
|
12
|
+
} from "lucide-react";
|
|
4
13
|
import TextAnnotator from "./TextAnnotator";
|
|
5
14
|
import type { Annotation } from "../types";
|
|
6
15
|
|
|
@@ -11,9 +20,13 @@ interface Props {
|
|
|
11
20
|
onAnnotationUpdate?: (annotation: Annotation) => void;
|
|
12
21
|
bookmarkedBlockIndex?: number;
|
|
13
22
|
onSetBookmark?: (messageId: string, blockIndex: number) => void;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
preferenceSuffix?: string;
|
|
24
|
+
onSpecRefined?: (
|
|
25
|
+
messageId: string,
|
|
26
|
+
originalSpec: string,
|
|
27
|
+
newSpec: string,
|
|
28
|
+
) => void;
|
|
29
|
+
onDeleteMessage?: (messageId: string) => void;
|
|
17
30
|
}
|
|
18
31
|
|
|
19
32
|
function getTextContent(message: UIMessage): string {
|
|
@@ -26,6 +39,47 @@ function getTextContent(message: UIMessage): string {
|
|
|
26
39
|
return "";
|
|
27
40
|
}
|
|
28
41
|
|
|
42
|
+
function getReasoningContent(message: UIMessage): string {
|
|
43
|
+
if (!message.parts) return "";
|
|
44
|
+
return message.parts
|
|
45
|
+
.filter(
|
|
46
|
+
(p): p is Extract<UIMessage["parts"][number], { type: "reasoning" }> =>
|
|
47
|
+
p.type === "reasoning" && typeof (p as any).text === "string",
|
|
48
|
+
)
|
|
49
|
+
.map((p) => p.text)
|
|
50
|
+
.join("\n\n");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ThinkingBlock({ reasoning }: { reasoning: string }) {
|
|
54
|
+
const [open, setOpen] = useState(false);
|
|
55
|
+
const wordCount = reasoning.trim().split(/\s+/).length;
|
|
56
|
+
return (
|
|
57
|
+
<div className="mb-2 rounded-lg border border-slate-700/60 bg-slate-800/40 text-xs overflow-hidden">
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
onClick={() => setOpen((v) => !v)}
|
|
61
|
+
className="w-full flex items-center gap-1.5 px-3 py-2 text-slate-500 hover:text-slate-300 transition-colors text-left"
|
|
62
|
+
>
|
|
63
|
+
<Brain className="w-3 h-3 shrink-0 text-violet-400" />
|
|
64
|
+
<span className="text-violet-400/80 font-medium">Thinking</span>
|
|
65
|
+
<span className="text-slate-600 ml-auto mr-1">
|
|
66
|
+
{wordCount.toLocaleString()} words
|
|
67
|
+
</span>
|
|
68
|
+
{open ? (
|
|
69
|
+
<ChevronDown className="w-3 h-3 shrink-0" />
|
|
70
|
+
) : (
|
|
71
|
+
<ChevronRight className="w-3 h-3 shrink-0" />
|
|
72
|
+
)}
|
|
73
|
+
</button>
|
|
74
|
+
{open && (
|
|
75
|
+
<div className="px-3 pb-3 pt-0 text-slate-500 whitespace-pre-wrap leading-relaxed border-t border-slate-700/40 max-h-80 overflow-y-auto">
|
|
76
|
+
{reasoning}
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
29
83
|
const ChatMessage = memo(function ChatMessage({
|
|
30
84
|
message,
|
|
31
85
|
annotations = [],
|
|
@@ -33,15 +87,42 @@ const ChatMessage = memo(function ChatMessage({
|
|
|
33
87
|
onAnnotationUpdate,
|
|
34
88
|
bookmarkedBlockIndex,
|
|
35
89
|
onSetBookmark,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
90
|
+
preferenceSuffix,
|
|
91
|
+
onSpecRefined,
|
|
92
|
+
onDeleteMessage,
|
|
39
93
|
}: Props) {
|
|
40
94
|
const isUser = message.role === "user";
|
|
41
95
|
const content = getTextContent(message);
|
|
96
|
+
const reasoning = !isUser ? getReasoningContent(message) : "";
|
|
97
|
+
const [copied, setCopied] = useState(false);
|
|
98
|
+
|
|
99
|
+
// Stable wrappers so MarkdownRenderer's `components` useMemo doesn't invalidate
|
|
100
|
+
// on every ChatMessage re-render (which would remount InlineCodeBlock and cause
|
|
101
|
+
// a Zustand update loop).
|
|
102
|
+
const stableBookmarkBlock = useCallback(
|
|
103
|
+
(idx: number) => onSetBookmark?.(message.id, idx),
|
|
104
|
+
[onSetBookmark, message.id],
|
|
105
|
+
);
|
|
106
|
+
const stableSpecRefined = useCallback(
|
|
107
|
+
(orig: string, refined: string) =>
|
|
108
|
+
onSpecRefined?.(message.id, orig, refined),
|
|
109
|
+
[onSpecRefined, message.id],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const handleCopy = useCallback(() => {
|
|
113
|
+
if (!content) return;
|
|
114
|
+
navigator.clipboard.writeText(content).then(() => {
|
|
115
|
+
setCopied(true);
|
|
116
|
+
setTimeout(() => setCopied(false), 1500);
|
|
117
|
+
});
|
|
118
|
+
}, [content]);
|
|
119
|
+
|
|
120
|
+
const handleDelete = useCallback(() => {
|
|
121
|
+
onDeleteMessage?.(message.id);
|
|
122
|
+
}, [onDeleteMessage, message.id]);
|
|
42
123
|
|
|
43
124
|
return (
|
|
44
|
-
<div className="flex gap-3 animate-fadeIn">
|
|
125
|
+
<div className="group flex gap-3 animate-fadeIn">
|
|
45
126
|
<div
|
|
46
127
|
className={`w-7 h-7 rounded-full flex items-center justify-center shrink-0 mt-0.5 ${
|
|
47
128
|
isUser
|
|
@@ -56,8 +137,32 @@ const ChatMessage = memo(function ChatMessage({
|
|
|
56
137
|
)}
|
|
57
138
|
</div>
|
|
58
139
|
<div className="min-w-0 flex-1">
|
|
59
|
-
<div className="
|
|
60
|
-
|
|
140
|
+
<div className="flex items-center gap-2 mb-1">
|
|
141
|
+
<div className="text-[10px] font-medium text-slate-600">
|
|
142
|
+
{isUser ? "You" : "Coach"}
|
|
143
|
+
</div>
|
|
144
|
+
{content && (
|
|
145
|
+
<button
|
|
146
|
+
onClick={handleCopy}
|
|
147
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 rounded text-slate-500 hover:text-slate-300 hover:bg-slate-700"
|
|
148
|
+
title="Copy message"
|
|
149
|
+
>
|
|
150
|
+
{copied ? (
|
|
151
|
+
<Check className="w-3 h-3 text-cyan-400" />
|
|
152
|
+
) : (
|
|
153
|
+
<Copy className="w-3 h-3" />
|
|
154
|
+
)}
|
|
155
|
+
</button>
|
|
156
|
+
)}
|
|
157
|
+
{onDeleteMessage && (
|
|
158
|
+
<button
|
|
159
|
+
onClick={handleDelete}
|
|
160
|
+
className="opacity-0 group-hover:opacity-100 transition-opacity p-0.5 rounded text-slate-500 hover:text-red-400 hover:bg-slate-700"
|
|
161
|
+
title="Delete message"
|
|
162
|
+
>
|
|
163
|
+
<Trash2 className="w-3 h-3" />
|
|
164
|
+
</button>
|
|
165
|
+
)}
|
|
61
166
|
</div>
|
|
62
167
|
<div className="text-sm leading-relaxed text-slate-200">
|
|
63
168
|
{isUser ? (
|
|
@@ -89,22 +194,22 @@ const ChatMessage = memo(function ChatMessage({
|
|
|
89
194
|
)}
|
|
90
195
|
</div>
|
|
91
196
|
) : (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
: undefined
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
197
|
+
<>
|
|
198
|
+
{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
|
+
/>
|
|
212
|
+
</>
|
|
108
213
|
)}
|
|
109
214
|
</div>
|
|
110
215
|
</div>
|