@opengeni/react 0.3.1 → 0.4.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 (40) hide show
  1. package/dist/index.d.ts +1035 -14
  2. package/dist/index.js +6867 -1884
  3. package/dist/index.js.map +1 -1
  4. package/package.json +65 -2
  5. package/src/client.ts +21 -0
  6. package/src/components/code-editor.tsx +398 -0
  7. package/src/components/desktop-viewer.tsx +647 -0
  8. package/src/components/diff-view.tsx +230 -0
  9. package/src/components/file-browser.tsx +838 -0
  10. package/src/components/message-timeline.tsx +70 -196
  11. package/src/components/pierre-diff.tsx +140 -0
  12. package/src/components/pierre-file.tsx +142 -0
  13. package/src/components/sandbox-files.tsx +509 -0
  14. package/src/components/sandbox-terminal.tsx +425 -0
  15. package/src/components/workspace-dock.tsx +247 -0
  16. package/src/hooks/use-desktop-stream.ts +214 -0
  17. package/src/hooks/use-sandbox-files.ts +670 -0
  18. package/src/hooks/use-sandbox-git.ts +105 -0
  19. package/src/hooks/use-sandbox-terminal.ts +226 -0
  20. package/src/hooks/use-session-capabilities.ts +415 -0
  21. package/src/hooks/use-terminal-stream.ts +207 -0
  22. package/src/index.ts +111 -2
  23. package/src/lib/cn.ts +20 -1
  24. package/src/lib/git-patch.ts +37 -0
  25. package/src/lib/use-theme-type.ts +40 -0
  26. package/src/lib/xterm-theme.ts +34 -0
  27. package/src/timeline/activity-rail.tsx +207 -0
  28. package/src/timeline/disclosure-context.tsx +34 -0
  29. package/src/timeline/index.ts +85 -0
  30. package/src/timeline/parsers.ts +248 -0
  31. package/src/{timeline.ts → timeline/projection.ts} +59 -134
  32. package/src/timeline/registry.ts +96 -0
  33. package/src/timeline/screenshot-lightbox.tsx +152 -0
  34. package/src/timeline/shared.tsx +481 -0
  35. package/src/timeline/tool-diff.tsx +91 -0
  36. package/src/timeline/tool-renderers.tsx +882 -0
  37. package/src/timeline/turn-summary.tsx +125 -0
  38. package/src/timeline/types.ts +131 -0
  39. package/src/types/external.d.ts +7 -0
  40. package/styles/index.css +72 -0
@@ -0,0 +1,230 @@
1
+ import type { GitDiffLine, GitFileDiff } from "@opengeni/sdk";
2
+ import { type ReactNode } from "react";
3
+ import { cn } from "../lib/cn";
4
+
5
+ /** Theme tokens for the diff line backgrounds. */
6
+ export type DiffTheme = {
7
+ addBackground?: string;
8
+ delBackground?: string;
9
+ contextBackground?: string;
10
+ metaForeground?: string;
11
+ };
12
+
13
+ export type DiffViewProps = {
14
+ /** From `useSandboxGit().diff` — the structured per-file hunks. */
15
+ diff: GitFileDiff[];
16
+ /** Rendered instead of the built-in diff (the Pierre-swap escape hatch). */
17
+ fallback?: ReactNode | undefined;
18
+ /** unified | split. Default "unified". */
19
+ layout?: "unified" | "split" | undefined;
20
+ theme?: DiffTheme | undefined;
21
+ onSelectFile?: ((path: string) => void) | undefined;
22
+ /** Distinguishes "no changes" from "no repository mounted". */
23
+ isRepo?: boolean | undefined;
24
+ emptyState?: ReactNode | undefined;
25
+ className?: string | undefined;
26
+ };
27
+
28
+ const STATUS_LABEL: Record<GitFileDiff["status"], string> = {
29
+ added: "added",
30
+ modified: "modified",
31
+ deleted: "deleted",
32
+ renamed: "renamed",
33
+ copied: "copied",
34
+ untracked: "untracked",
35
+ ignored: "ignored",
36
+ conflicted: "conflicted",
37
+ typechange: "typechange",
38
+ };
39
+
40
+ /**
41
+ * The diff view, fed by the Git service via `useSandboxGit().diff`. This is our
42
+ * `PierreDiff` boundary: the built-in renderer delivers the same UX (per-file
43
+ * hunks, unified or side-by-side, old/new line numbers, add/del counts), and a
44
+ * consumer with Pierre's `@pierre/diffs` installed can swap it via `fallback`
45
+ * while keeping the same `GitFileDiff[]` data contract from the hook.
46
+ */
47
+ export function DiffView({
48
+ diff,
49
+ fallback,
50
+ layout = "unified",
51
+ theme,
52
+ onSelectFile,
53
+ isRepo = true,
54
+ emptyState,
55
+ className,
56
+ }: DiffViewProps) {
57
+ if (fallback !== undefined) {
58
+ return <div className={className}>{fallback}</div>;
59
+ }
60
+
61
+ if (diff.length === 0) {
62
+ return (
63
+ <div className={cn("p-3 text-xs text-[color:var(--color-fg-subtle,#888)]", className)}>
64
+ {emptyState ?? (isRepo ? "No changes." : "No repository mounted.")}
65
+ </div>
66
+ );
67
+ }
68
+
69
+ return (
70
+ <div className={cn("min-w-0 overflow-auto", className)} data-opengeni-diff>
71
+ {diff.map((file) => (
72
+ <FileDiffBlock
73
+ key={file.path}
74
+ file={file}
75
+ layout={layout}
76
+ theme={theme}
77
+ {...(onSelectFile ? { onSelect: () => onSelectFile(file.path) } : {})}
78
+ />
79
+ ))}
80
+ </div>
81
+ );
82
+ }
83
+
84
+ function FileDiffBlock({
85
+ file,
86
+ layout,
87
+ theme,
88
+ onSelect,
89
+ }: {
90
+ file: GitFileDiff;
91
+ layout: "unified" | "split";
92
+ theme: DiffTheme | undefined;
93
+ onSelect?: (() => void) | undefined;
94
+ }) {
95
+ return (
96
+ <section className="mb-2 overflow-hidden rounded border border-[color:var(--color-border,#2a2a2a)]">
97
+ <header
98
+ className={cn(
99
+ "flex items-center justify-between gap-2 border-b border-[color:var(--color-border,#2a2a2a)] bg-[color:var(--color-bg-subtle,#161616)] px-2 py-1 text-xs",
100
+ onSelect && "cursor-pointer",
101
+ )}
102
+ onClick={onSelect}
103
+ >
104
+ <span className="truncate font-mono">
105
+ {file.oldPath && file.oldPath !== file.path ? `${file.oldPath} → ${file.path}` : file.path}
106
+ </span>
107
+ <span className="flex shrink-0 items-center gap-2">
108
+ <span className="rounded bg-[color:var(--color-bg,#0d0d0d)] px-1 py-0.5 text-[10px] uppercase tracking-wide text-[color:var(--color-fg-subtle,#888)]">
109
+ {STATUS_LABEL[file.status]}
110
+ </span>
111
+ <span className="text-[color:var(--color-success,#3fb950)]">+{file.additions}</span>
112
+ <span className="text-[color:var(--color-danger,#f85149)]">−{file.deletions}</span>
113
+ </span>
114
+ </header>
115
+ {file.isBinary ? (
116
+ <div className="px-2 py-3 text-xs text-[color:var(--color-fg-subtle,#888)]">Binary file not shown.</div>
117
+ ) : file.truncated ? (
118
+ <div className="px-2 py-3 text-xs text-[color:var(--color-fg-subtle,#888)]">Diff too large — truncated.</div>
119
+ ) : layout === "split" ? (
120
+ <SplitHunks file={file} theme={theme} />
121
+ ) : (
122
+ <UnifiedHunks file={file} theme={theme} />
123
+ )}
124
+ </section>
125
+ );
126
+ }
127
+
128
+ function lineBg(type: GitDiffLine["type"], theme: DiffTheme | undefined): string | undefined {
129
+ if (type === "add") return theme?.addBackground ?? "var(--color-diff-add, rgba(63,185,80,0.15))";
130
+ if (type === "del") return theme?.delBackground ?? "var(--color-diff-del, rgba(248,81,73,0.15))";
131
+ if (type === "meta") return undefined;
132
+ return theme?.contextBackground;
133
+ }
134
+
135
+ function marker(type: GitDiffLine["type"]): string {
136
+ if (type === "add") return "+";
137
+ if (type === "del") return "−";
138
+ return " ";
139
+ }
140
+
141
+ function UnifiedHunks({ file, theme }: { file: GitFileDiff; theme: DiffTheme | undefined }) {
142
+ return (
143
+ <div className="font-mono text-[11px] leading-tight">
144
+ {file.hunks.map((hunk, hi) => (
145
+ <div key={`${file.path}-h${hi}`}>
146
+ <div
147
+ className="px-2 py-0.5 text-[color:var(--color-info,#58a6ff)]"
148
+ style={{ color: theme?.metaForeground }}
149
+ >
150
+ {hunk.header}
151
+ </div>
152
+ {hunk.lines.map((line, li) => (
153
+ <div
154
+ key={`${file.path}-h${hi}-l${li}`}
155
+ className="flex"
156
+ style={{ backgroundColor: lineBg(line.type, theme) }}
157
+ >
158
+ <span className="w-10 shrink-0 select-none px-1 text-right text-[color:var(--color-fg-subtle,#666)]">
159
+ {line.oldNo ?? ""}
160
+ </span>
161
+ <span className="w-10 shrink-0 select-none px-1 text-right text-[color:var(--color-fg-subtle,#666)]">
162
+ {line.newNo ?? ""}
163
+ </span>
164
+ <span className="w-4 shrink-0 select-none text-center text-[color:var(--color-fg-subtle,#888)]">
165
+ {marker(line.type)}
166
+ </span>
167
+ <span className="whitespace-pre-wrap break-all px-1">{line.text}</span>
168
+ </div>
169
+ ))}
170
+ </div>
171
+ ))}
172
+ </div>
173
+ );
174
+ }
175
+
176
+ function SplitHunks({ file, theme }: { file: GitFileDiff; theme: DiffTheme | undefined }) {
177
+ return (
178
+ <div className="font-mono text-[11px] leading-tight">
179
+ {file.hunks.map((hunk, hi) => {
180
+ // Pair del/add lines side-by-side; context spans both columns.
181
+ const left: (GitDiffLine | null)[] = [];
182
+ const right: (GitDiffLine | null)[] = [];
183
+ for (const line of hunk.lines) {
184
+ if (line.type === "context" || line.type === "meta") {
185
+ left.push(line);
186
+ right.push(line);
187
+ } else if (line.type === "del") {
188
+ left.push(line);
189
+ } else {
190
+ right.push(line);
191
+ }
192
+ }
193
+ const rows = Math.max(left.length, right.length);
194
+ return (
195
+ <div key={`${file.path}-h${hi}`}>
196
+ <div className="px-2 py-0.5 text-[color:var(--color-info,#58a6ff)]" style={{ color: theme?.metaForeground }}>
197
+ {hunk.header}
198
+ </div>
199
+ {Array.from({ length: rows }).map((_, ri) => {
200
+ const l = left[ri] ?? null;
201
+ const r = right[ri] ?? null;
202
+ return (
203
+ <div key={`${file.path}-h${hi}-r${ri}`} className="flex">
204
+ <span
205
+ className="flex w-1/2 min-w-0"
206
+ style={{ backgroundColor: l ? lineBg(l.type, theme) : undefined }}
207
+ >
208
+ <span className="w-10 shrink-0 select-none px-1 text-right text-[color:var(--color-fg-subtle,#666)]">
209
+ {l?.oldNo ?? ""}
210
+ </span>
211
+ <span className="whitespace-pre-wrap break-all px-1">{l?.text ?? ""}</span>
212
+ </span>
213
+ <span
214
+ className="flex w-1/2 min-w-0 border-l border-[color:var(--color-border,#2a2a2a)]"
215
+ style={{ backgroundColor: r ? lineBg(r.type, theme) : undefined }}
216
+ >
217
+ <span className="w-10 shrink-0 select-none px-1 text-right text-[color:var(--color-fg-subtle,#666)]">
218
+ {r?.newNo ?? ""}
219
+ </span>
220
+ <span className="whitespace-pre-wrap break-all px-1">{r?.text ?? ""}</span>
221
+ </span>
222
+ </div>
223
+ );
224
+ })}
225
+ </div>
226
+ );
227
+ })}
228
+ </div>
229
+ );
230
+ }