create-interview-cockpit 0.19.0 → 0.21.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-interview-cockpit",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Scaffold a personal AI-powered interview prep cockpit",
5
5
  "type": "module",
6
6
  "bin": {
@@ -743,6 +743,73 @@ export async function fetchCodeContextTree(): Promise<string[]> {
743
743
  return res.json();
744
744
  }
745
745
 
746
+ // --- Git Diff Context ---
747
+
748
+ export interface GitBranchesResponse {
749
+ enabled: boolean;
750
+ head?: string;
751
+ defaultBranch?: string;
752
+ branches: string[];
753
+ tags?: string[];
754
+ error?: string;
755
+ }
756
+
757
+ export interface GitDiffTreeResponse {
758
+ base: string;
759
+ baseSha?: string;
760
+ head: string;
761
+ headSha?: string | null;
762
+ mode: "two-dot" | "three-dot" | "working-tree";
763
+ truncated: boolean;
764
+ files: Array<{
765
+ path: string;
766
+ oldPath?: string;
767
+ status: "A" | "M" | "D" | "R" | "C" | "T" | "U" | "?";
768
+ additions: number;
769
+ deletions: number;
770
+ binary: boolean;
771
+ }>;
772
+ }
773
+
774
+ export async function fetchGitBranches(): Promise<GitBranchesResponse> {
775
+ const res = await fetch(`${BASE}/code-context/git/branches`);
776
+ return res.json();
777
+ }
778
+
779
+ export async function fetchGitDiffTree(params: {
780
+ base: string;
781
+ head: string;
782
+ mode: "two-dot" | "three-dot" | "working-tree";
783
+ }): Promise<GitDiffTreeResponse> {
784
+ const qs = new URLSearchParams({
785
+ base: params.base,
786
+ head: params.head,
787
+ mode: params.mode,
788
+ });
789
+ const res = await fetch(`${BASE}/code-context/git/diff-tree?${qs}`);
790
+ if (!res.ok) throw new Error((await res.json()).error || "diff-tree failed");
791
+ return res.json();
792
+ }
793
+
794
+ export async function fetchGitDiffFile(params: {
795
+ base: string;
796
+ head: string;
797
+ mode: "two-dot" | "three-dot" | "working-tree";
798
+ path: string;
799
+ view: "patch" | "before" | "after";
800
+ }): Promise<{ path: string; view: string; content: string }> {
801
+ const qs = new URLSearchParams({
802
+ base: params.base,
803
+ head: params.head,
804
+ mode: params.mode,
805
+ path: params.path,
806
+ view: params.view,
807
+ });
808
+ const res = await fetch(`${BASE}/code-context/git/diff-file?${qs}`);
809
+ if (!res.ok) throw new Error((await res.json()).error || "diff-file failed");
810
+ return res.json();
811
+ }
812
+
746
813
  // --- Workspaces ---
747
814
 
748
815
  export async function fetchWorkspaces(): Promise<WorkspacesRegistry> {
@@ -411,6 +411,7 @@ export default function ChatView({ question }: Props) {
411
411
  topicTitle: "",
412
412
  questionTitle: question.title,
413
413
  codeContextFiles: question.codeContextFiles,
414
+ gitDiffContext: question.gitDiffContext,
414
415
  systemContext,
415
416
  responseLength: "normal" as string,
416
417
  groupSelections,
@@ -429,6 +430,7 @@ export default function ChatView({ question }: Props) {
429
430
  topicTitle: currentTopic?.name || "",
430
431
  questionTitle: question.title,
431
432
  codeContextFiles: question.codeContextFiles,
433
+ gitDiffContext: question.gitDiffContext,
432
434
  systemContext,
433
435
  responseLength: groupSelections["length"] ?? "normal",
434
436
  groupSelections,
@@ -2,6 +2,7 @@ import { useEffect, useState, useCallback } from "react";
2
2
  import { useStore } from "../store";
3
3
  import FileViewerModal from "./FileViewerModal";
4
4
  import NotesModal, { notesKey } from "./NotesModal";
5
+ import GitDiffPanel from "./GitDiffPanel";
5
6
  import {
6
7
  File,
7
8
  Search,
@@ -635,6 +636,9 @@ export default function CodeContextPanel() {
635
636
  </div>
636
637
  )}
637
638
 
639
+ {/* ── Git Diff section ───────────────────────────────── */}
640
+ <GitDiffPanel />
641
+
638
642
  {/* ── Notes section ────────────────────────────────────── */}
639
643
  <div className="border-t border-slate-800 px-3 py-2">
640
644
  <button
@@ -320,6 +320,7 @@ export default function FileViewerModal({ filePath, onClose }: Props) {
320
320
  questionId: currentQuestion?.id,
321
321
  topicId: currentQuestion?.topicId ?? selectedTopicId,
322
322
  codeContextFiles: currentQuestion?.codeContextFiles,
323
+ gitDiffContext: currentQuestion?.gitDiffContext,
323
324
  codeSnippets,
324
325
  preferenceSuffix: livePreferenceSuffix,
325
326
  }),
@@ -0,0 +1,403 @@
1
+ import { useEffect, useState, useCallback } from "react";
2
+ import {
3
+ GitBranch,
4
+ RefreshCcw,
5
+ Eye,
6
+ X,
7
+ Check,
8
+ ChevronDown,
9
+ ChevronRight,
10
+ } from "lucide-react";
11
+ import { useStore } from "../store";
12
+ import {
13
+ fetchGitBranches,
14
+ fetchGitDiffTree,
15
+ type GitBranchesResponse,
16
+ type GitDiffTreeResponse,
17
+ } from "../api";
18
+ import type { GitDiffContext, GitDiffMode } from "../types";
19
+ import GitDiffViewerModal from "./GitDiffViewerModal";
20
+
21
+ // Status letter → color/label for the changed-files list.
22
+ const STATUS_META: Record<string, { color: string; label: string }> = {
23
+ A: { color: "text-emerald-400", label: "added" },
24
+ M: { color: "text-amber-400", label: "modified" },
25
+ D: { color: "text-red-400", label: "deleted" },
26
+ R: { color: "text-sky-400", label: "renamed" },
27
+ C: { color: "text-sky-400", label: "copied" },
28
+ T: { color: "text-violet-400", label: "type" },
29
+ U: { color: "text-orange-400", label: "unmerged" },
30
+ "?": { color: "text-slate-400", label: "untracked" },
31
+ };
32
+
33
+ export default function GitDiffPanel() {
34
+ const { currentQuestion, updateGitDiffContext } = useStore();
35
+ const existing = currentQuestion?.gitDiffContext;
36
+
37
+ const [expanded, setExpanded] = useState<boolean>(Boolean(existing));
38
+ const [branches, setBranches] = useState<GitBranchesResponse | null>(null);
39
+ const [baseRef, setBaseRef] = useState<string>(existing?.baseRef || "");
40
+ const [headRef, setHeadRef] = useState<string>(existing?.headRef || "");
41
+ const [mode, setMode] = useState<GitDiffMode>(existing?.mode || "three-dot");
42
+ const [tree, setTree] = useState<GitDiffTreeResponse | null>(null);
43
+ const [loadingTree, setLoadingTree] = useState(false);
44
+ const [error, setError] = useState<string | null>(null);
45
+ const [selected, setSelected] = useState<string[]>(
46
+ existing?.selectedFiles || [],
47
+ );
48
+ const [viewingFile, setViewingFile] = useState<string | null>(null);
49
+
50
+ // Reload selection when switching questions.
51
+ useEffect(() => {
52
+ setBaseRef(existing?.baseRef || "");
53
+ setHeadRef(existing?.headRef || "");
54
+ setMode(existing?.mode || "three-dot");
55
+ setSelected(existing?.selectedFiles || []);
56
+ setTree(null);
57
+ }, [currentQuestion?.id]);
58
+
59
+ const applyBranchDefaults = useCallback(
60
+ (b: GitBranchesResponse) => {
61
+ const currentHead = b.head || "";
62
+ const recommendedBase =
63
+ b.defaultBranch ||
64
+ b.branches.find((branch) => branch !== currentHead) ||
65
+ currentHead ||
66
+ b.branches[0] ||
67
+ "";
68
+ setHeadRef((prev) => prev || currentHead);
69
+ setBaseRef((prev) => {
70
+ // On first open (or after the old bug saved base=head with no files),
71
+ // prefer main/master/origin default as the base and keep current branch as head.
72
+ if (!prev) return recommendedBase;
73
+ if (
74
+ prev === currentHead &&
75
+ recommendedBase &&
76
+ recommendedBase !== currentHead &&
77
+ selected.length === 0
78
+ ) {
79
+ return recommendedBase;
80
+ }
81
+ return prev;
82
+ });
83
+ },
84
+ [selected.length],
85
+ );
86
+
87
+ // Lazy-load branches the first time the section is opened.
88
+ useEffect(() => {
89
+ if (!expanded) return;
90
+ if (branches) {
91
+ applyBranchDefaults(branches);
92
+ return;
93
+ }
94
+ fetchGitBranches()
95
+ .then((b) => {
96
+ setBranches(b);
97
+ applyBranchDefaults(b);
98
+ })
99
+ .catch((e) => setError(e?.message || "Failed to load branches"));
100
+ }, [expanded, branches, applyBranchDefaults]);
101
+
102
+ const loadTree = useCallback(async () => {
103
+ if (!baseRef) return;
104
+ setLoadingTree(true);
105
+ setError(null);
106
+ try {
107
+ const data = await fetchGitDiffTree({
108
+ base: baseRef,
109
+ head: mode === "working-tree" ? "" : headRef,
110
+ mode,
111
+ });
112
+ setTree(data);
113
+ } catch (e: any) {
114
+ setTree(null);
115
+ setError(e?.message || "Failed to load diff");
116
+ } finally {
117
+ setLoadingTree(false);
118
+ }
119
+ }, [baseRef, headRef, mode]);
120
+
121
+ // Auto-load when refs/mode change and the section is open.
122
+ useEffect(() => {
123
+ if (!expanded) return;
124
+ if (!baseRef) return;
125
+ if (mode !== "working-tree" && !headRef) return;
126
+ loadTree();
127
+ }, [expanded, baseRef, headRef, mode]);
128
+
129
+ const persist = useCallback(
130
+ (next: Partial<GitDiffContext>) => {
131
+ if (!currentQuestion) return;
132
+ const ctx: GitDiffContext = {
133
+ baseRef,
134
+ headRef: mode === "working-tree" ? "" : headRef,
135
+ mode,
136
+ selectedFiles: selected,
137
+ ...next,
138
+ };
139
+ // Treat empty selection as "clear" so we don't waste prompt tokens.
140
+ if (!ctx.baseRef || ctx.selectedFiles.length === 0) {
141
+ updateGitDiffContext(currentQuestion.id, null);
142
+ } else {
143
+ updateGitDiffContext(currentQuestion.id, ctx);
144
+ }
145
+ },
146
+ [currentQuestion, baseRef, headRef, mode, selected, updateGitDiffContext],
147
+ );
148
+
149
+ const toggleFile = useCallback(
150
+ (filePath: string) => {
151
+ setSelected((prev) => {
152
+ const next = prev.includes(filePath)
153
+ ? prev.filter((f) => f !== filePath)
154
+ : [...prev, filePath];
155
+ persist({ selectedFiles: next });
156
+ return next;
157
+ });
158
+ },
159
+ [persist],
160
+ );
161
+
162
+ const clearSelection = useCallback(() => {
163
+ setSelected([]);
164
+ if (currentQuestion) updateGitDiffContext(currentQuestion.id, null);
165
+ }, [currentQuestion, updateGitDiffContext]);
166
+
167
+ const branchOptions = branches?.branches ?? [];
168
+ const tagOptions = branches?.tags ?? [];
169
+
170
+ return (
171
+ <div className="border-t border-slate-800">
172
+ <button
173
+ onClick={() => setExpanded((v) => !v)}
174
+ className="w-full flex items-center gap-2 px-3 py-2 group"
175
+ >
176
+ {expanded ? (
177
+ <ChevronDown className="w-3 h-3 text-slate-600" />
178
+ ) : (
179
+ <ChevronRight className="w-3 h-3 text-slate-600" />
180
+ )}
181
+ <GitBranch className="w-3 h-3 text-cyan-400/70 shrink-0" />
182
+ <span className="text-[10px] uppercase tracking-wider text-slate-600 flex-1 text-left">
183
+ Git Diff{selected.length > 0 ? ` (${selected.length})` : ""}
184
+ </span>
185
+ </button>
186
+
187
+ {expanded && (
188
+ <div className="px-3 pb-3 space-y-2">
189
+ {!currentQuestion && (
190
+ <p className="text-[10px] text-slate-600 italic">
191
+ Select a question first
192
+ </p>
193
+ )}
194
+
195
+ {currentQuestion && branches && !branches.enabled && (
196
+ <p className="text-[10px] text-slate-600 italic">
197
+ Set <code>GIT_DIFF_DIR</code> (or <code>CODE_CONTEXT_DIR</code>)
198
+ to a git repo to enable diffs.
199
+ {branches.error ? ` (${branches.error})` : ""}
200
+ </p>
201
+ )}
202
+
203
+ {currentQuestion && branches?.enabled && (
204
+ <>
205
+ <div className="space-y-1">
206
+ <label className="block text-[10px] uppercase tracking-wider text-slate-600">
207
+ Mode
208
+ </label>
209
+ <select
210
+ value={mode}
211
+ onChange={(e) => setMode(e.target.value as GitDiffMode)}
212
+ className="w-full bg-slate-800 border border-slate-700 rounded px-2 py-1 text-xs text-slate-300"
213
+ >
214
+ <option value="three-dot">
215
+ three-dot (base...head, since common ancestor)
216
+ </option>
217
+ <option value="two-dot">two-dot (base..head)</option>
218
+ <option value="working-tree">working tree vs base</option>
219
+ </select>
220
+ </div>
221
+
222
+ <div className="space-y-1">
223
+ <label className="block text-[10px] uppercase tracking-wider text-slate-600">
224
+ Base
225
+ </label>
226
+ <RefInput
227
+ value={baseRef}
228
+ onChange={setBaseRef}
229
+ branches={branchOptions}
230
+ tags={tagOptions}
231
+ />
232
+ </div>
233
+
234
+ {mode !== "working-tree" && (
235
+ <div className="space-y-1">
236
+ <label className="block text-[10px] uppercase tracking-wider text-slate-600">
237
+ Head
238
+ </label>
239
+ <RefInput
240
+ value={headRef}
241
+ onChange={setHeadRef}
242
+ branches={branchOptions}
243
+ tags={tagOptions}
244
+ />
245
+ </div>
246
+ )}
247
+
248
+ <div className="flex items-center gap-2">
249
+ <button
250
+ onClick={loadTree}
251
+ disabled={
252
+ loadingTree ||
253
+ !baseRef ||
254
+ (mode !== "working-tree" && !headRef)
255
+ }
256
+ className="flex items-center gap-1 px-2 py-1 text-xs bg-cyan-500/10 border border-cyan-500/30 text-cyan-300 rounded hover:bg-cyan-500/20 disabled:opacity-40"
257
+ >
258
+ <RefreshCcw className="w-3 h-3" />
259
+ {loadingTree ? "Loading…" : "Load diff"}
260
+ </button>
261
+ {selected.length > 0 && (
262
+ <button
263
+ onClick={clearSelection}
264
+ className="text-[10px] text-red-400/60 hover:text-red-400"
265
+ >
266
+ Clear selection
267
+ </button>
268
+ )}
269
+ </div>
270
+
271
+ {error && <p className="text-[10px] text-red-400">{error}</p>}
272
+
273
+ {tree && (
274
+ <div className="space-y-0.5 max-h-72 overflow-y-auto border-t border-slate-800 pt-2">
275
+ {tree.files.length === 0 && (
276
+ <p className="text-[10px] text-slate-600 italic">
277
+ No changes between these refs.
278
+ </p>
279
+ )}
280
+ {tree.files.map((f) => {
281
+ const meta = STATUS_META[f.status] ?? STATUS_META["M"];
282
+ const isSelected = selected.includes(f.path);
283
+ return (
284
+ <div
285
+ key={f.path}
286
+ className={`flex items-center gap-1 text-xs group rounded px-1 py-0.5 ${
287
+ isSelected ? "bg-cyan-500/10" : ""
288
+ }`}
289
+ >
290
+ <button
291
+ onClick={() => toggleFile(f.path)}
292
+ className="shrink-0"
293
+ title={meta.label}
294
+ >
295
+ {isSelected ? (
296
+ <Check className="w-3 h-3 text-cyan-400" />
297
+ ) : (
298
+ <span
299
+ className={`w-3 h-3 inline-flex items-center justify-center text-[10px] font-mono ${meta.color}`}
300
+ >
301
+ {f.status}
302
+ </span>
303
+ )}
304
+ </button>
305
+ <button
306
+ onClick={() => toggleFile(f.path)}
307
+ className={`flex-1 min-w-0 text-left truncate ${
308
+ isSelected ? "text-cyan-300" : "text-slate-400"
309
+ } hover:text-slate-200`}
310
+ title={
311
+ f.oldPath ? `${f.oldPath} → ${f.path}` : f.path
312
+ }
313
+ >
314
+ {f.path}
315
+ </button>
316
+ <span
317
+ className={`shrink-0 text-[10px] font-mono ${
318
+ f.binary ? "text-slate-600" : "text-slate-500"
319
+ }`}
320
+ >
321
+ {f.binary ? "bin" : `+${f.additions}/-${f.deletions}`}
322
+ </span>
323
+ <button
324
+ onClick={() => setViewingFile(f.path)}
325
+ className="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 text-slate-500 hover:text-cyan-400 transition-all"
326
+ title="View diff"
327
+ >
328
+ <Eye className="w-3 h-3" />
329
+ </button>
330
+ </div>
331
+ );
332
+ })}
333
+ {tree.truncated && (
334
+ <p className="text-[10px] text-amber-400/70 mt-1">
335
+ Truncated — too many changed files.
336
+ </p>
337
+ )}
338
+ </div>
339
+ )}
340
+ </>
341
+ )}
342
+ </div>
343
+ )}
344
+
345
+ {viewingFile && baseRef && (mode === "working-tree" || headRef) && (
346
+ <GitDiffViewerModal
347
+ ctx={{
348
+ baseRef,
349
+ headRef: mode === "working-tree" ? "" : headRef,
350
+ mode,
351
+ selectedFiles: selected,
352
+ }}
353
+ filePath={viewingFile}
354
+ onClose={() => setViewingFile(null)}
355
+ />
356
+ )}
357
+ </div>
358
+ );
359
+ }
360
+
361
+ // Small free-text + datalist combo so the user can either pick a known branch
362
+ // or type any commit-ish (sha, tag, HEAD~3, etc.).
363
+ function RefInput({
364
+ value,
365
+ onChange,
366
+ branches,
367
+ tags,
368
+ }: {
369
+ value: string;
370
+ onChange: (v: string) => void;
371
+ branches: string[];
372
+ tags: string[];
373
+ }) {
374
+ const listId = `git-refs-${Math.random().toString(36).slice(2, 8)}`;
375
+ return (
376
+ <div className="flex gap-1">
377
+ <input
378
+ list={listId}
379
+ value={value}
380
+ onChange={(e) => onChange(e.target.value)}
381
+ placeholder="branch, tag, sha, HEAD~1…"
382
+ className="flex-1 bg-slate-800 border border-slate-700 rounded px-2 py-1 text-xs text-slate-300 placeholder-slate-600 focus:outline-none focus:border-cyan-500 font-mono"
383
+ />
384
+ {value && (
385
+ <button
386
+ onClick={() => onChange("")}
387
+ className="px-1 text-slate-600 hover:text-red-400"
388
+ title="Clear"
389
+ >
390
+ <X className="w-3 h-3" />
391
+ </button>
392
+ )}
393
+ <datalist id={listId}>
394
+ {branches.map((b) => (
395
+ <option key={b} value={b} />
396
+ ))}
397
+ {tags.map((t) => (
398
+ <option key={t} value={t} />
399
+ ))}
400
+ </datalist>
401
+ </div>
402
+ );
403
+ }
@@ -0,0 +1,124 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
3
+ import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
4
+ import { X, GitBranch } from "lucide-react";
5
+ import { fetchGitDiffFile } from "../api";
6
+ import type { GitDiffContext } from "../types";
7
+
8
+ interface Props {
9
+ ctx: GitDiffContext;
10
+ filePath: string;
11
+ onClose: () => void;
12
+ }
13
+
14
+ type View = "patch" | "before" | "after";
15
+
16
+ // Lightweight viewer — fetches the same patch/blob the LLM would lazy-load via readFile.
17
+ export default function GitDiffViewerModal({ ctx, filePath, onClose }: Props) {
18
+ const [view, setView] = useState<View>("patch");
19
+ const [content, setContent] = useState<string>("");
20
+ const [loading, setLoading] = useState(false);
21
+ const [error, setError] = useState<string | null>(null);
22
+
23
+ useEffect(() => {
24
+ let cancelled = false;
25
+ setLoading(true);
26
+ setError(null);
27
+ fetchGitDiffFile({
28
+ base: ctx.baseRef,
29
+ head: ctx.headRef,
30
+ mode: ctx.mode,
31
+ path: filePath,
32
+ view,
33
+ })
34
+ .then((d) => {
35
+ if (cancelled) return;
36
+ setContent(d.content || "");
37
+ })
38
+ .catch((e) => {
39
+ if (cancelled) return;
40
+ setError(e?.message || "Failed to load diff");
41
+ })
42
+ .finally(() => !cancelled && setLoading(false));
43
+ return () => {
44
+ cancelled = true;
45
+ };
46
+ }, [ctx.baseRef, ctx.headRef, ctx.mode, filePath, view]);
47
+
48
+ const language =
49
+ view === "patch"
50
+ ? "diff"
51
+ : (filePath.split(".").pop() || "text").toLowerCase();
52
+
53
+ const headLabel = ctx.mode === "working-tree" ? "working tree" : ctx.headRef;
54
+
55
+ return (
56
+ <div
57
+ className="fixed inset-0 z-50 bg-black/70 flex items-center justify-center p-4"
58
+ onClick={onClose}
59
+ >
60
+ <div
61
+ className="bg-slate-900 border border-slate-700 rounded-lg w-full max-w-5xl max-h-[90vh] flex flex-col overflow-hidden"
62
+ onClick={(e) => e.stopPropagation()}
63
+ >
64
+ <div className="flex items-center gap-2 border-b border-slate-800 px-3 py-2">
65
+ <GitBranch className="w-4 h-4 text-cyan-400/70" />
66
+ <div className="flex-1 min-w-0">
67
+ <div className="text-sm text-slate-200 truncate">{filePath}</div>
68
+ <div className="text-[10px] text-slate-500 truncate">
69
+ {ctx.baseRef}{" "}
70
+ {ctx.mode === "two-dot"
71
+ ? ".."
72
+ : ctx.mode === "working-tree"
73
+ ? "→"
74
+ : "..."}{" "}
75
+ {headLabel}
76
+ </div>
77
+ </div>
78
+ <div className="flex bg-slate-800 rounded overflow-hidden text-xs">
79
+ {(["patch", "before", "after"] as View[]).map((v) => (
80
+ <button
81
+ key={v}
82
+ onClick={() => setView(v)}
83
+ className={`px-2 py-1 ${
84
+ view === v
85
+ ? "bg-cyan-500/20 text-cyan-300"
86
+ : "text-slate-400 hover:text-slate-200"
87
+ }`}
88
+ >
89
+ {v}
90
+ </button>
91
+ ))}
92
+ </div>
93
+ <button
94
+ onClick={onClose}
95
+ className="text-slate-500 hover:text-slate-200 p-1"
96
+ >
97
+ <X className="w-4 h-4" />
98
+ </button>
99
+ </div>
100
+ <div className="flex-1 min-h-0 overflow-auto">
101
+ {loading && (
102
+ <div className="p-4 text-xs text-slate-500">Loading…</div>
103
+ )}
104
+ {error && <div className="p-4 text-xs text-red-400">{error}</div>}
105
+ {!loading && !error && (
106
+ <SyntaxHighlighter
107
+ language={language}
108
+ style={oneDark}
109
+ customStyle={{
110
+ margin: 0,
111
+ background: "transparent",
112
+ fontSize: "12px",
113
+ padding: "12px",
114
+ }}
115
+ showLineNumbers={view !== "patch"}
116
+ >
117
+ {content || "(empty)"}
118
+ </SyntaxHighlighter>
119
+ )}
120
+ </div>
121
+ </div>
122
+ </div>
123
+ );
124
+ }