@elench/shell 0.1.26 → 0.1.27
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/dist/codex/thread-inspector.d.ts +11 -0
- package/dist/codex/thread-inspector.d.ts.map +1 -0
- package/dist/codex/thread-inspector.js +27 -0
- package/dist/codex/thread-inspector.js.map +1 -0
- package/dist/codex/thread-scroll-follow.d.ts +12 -0
- package/dist/codex/thread-scroll-follow.d.ts.map +1 -0
- package/dist/codex/thread-scroll-follow.js +86 -0
- package/dist/codex/thread-scroll-follow.js.map +1 -0
- package/dist/codex/thread-sidebar-preferences.d.ts +9 -0
- package/dist/codex/thread-sidebar-preferences.d.ts.map +1 -0
- package/dist/codex/thread-sidebar-preferences.js +65 -0
- package/dist/codex/thread-sidebar-preferences.js.map +1 -0
- package/dist/codex/thread-sidebar.d.ts +13 -0
- package/dist/codex/thread-sidebar.d.ts.map +1 -0
- package/dist/codex/thread-sidebar.js +145 -0
- package/dist/codex/thread-sidebar.js.map +1 -0
- package/dist/codex/thread-workspace.d.ts +21 -0
- package/dist/codex/thread-workspace.d.ts.map +1 -0
- package/dist/codex/thread-workspace.js +49 -0
- package/dist/codex/thread-workspace.js.map +1 -0
- package/dist/codex/transcript-react.d.ts +8 -113
- package/dist/codex/transcript-react.d.ts.map +1 -1
- package/dist/codex/transcript-react.js +5 -667
- package/dist/codex/transcript-react.js.map +1 -1
- package/dist/codex/transcript-renderers.d.ts +68 -0
- package/dist/codex/transcript-renderers.d.ts.map +1 -0
- package/dist/codex/transcript-renderers.js +190 -0
- package/dist/codex/transcript-renderers.js.map +1 -0
- package/dist/codex/turn-controls.d.ts +11 -0
- package/dist/codex/turn-controls.d.ts.map +1 -0
- package/dist/codex/turn-controls.js +144 -0
- package/dist/codex/turn-controls.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,669 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import remarkGfm from "remark-gfm";
|
|
8
|
-
import { EmptyState } from "../components/system/async-state.js";
|
|
9
|
-
import { StatusDot } from "../components/system/status.js";
|
|
10
|
-
import { Button } from "../components/ui/button.js";
|
|
11
|
-
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger, } from "../components/ui/context-menu.js";
|
|
12
|
-
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "../components/ui/dialog.js";
|
|
13
|
-
import { Input } from "../components/ui/input.js";
|
|
14
|
-
import { SimpleSelect } from "../components/ui/select.js";
|
|
15
|
-
import { Textarea } from "../components/ui/textarea.js";
|
|
16
|
-
import { SidebarActionRow } from "../components/shell/primitives/sidebar-action-row.js";
|
|
17
|
-
import { SidebarSection } from "../components/shell/primitives/sidebar-section.js";
|
|
18
|
-
import { InspectorItem, InspectorItemHeader, InspectorItemValue, } from "../components/shell/primitives/inspector-panel.js";
|
|
19
|
-
import { cn } from "../lib/utils.js";
|
|
20
|
-
import { useCodexAppServer, useCodexThreadSession } from "./react.js";
|
|
21
|
-
import { codexInputText, codexThreadDescription, codexThreadSourceKind, codexThreadTitle, countFileUpdateChanges, DEFAULT_CODEX_TRANSCRIPT_RENDER_SETTINGS, formatNumber, formatTokenUsageBreakdown, languageForPath, mergeCodexTranscriptSettings, parseCodexUnifiedDiff, summarizeCodexThread, toCodexTranscriptEntries, tokenUsagePercent, } from "./transcript.js";
|
|
22
|
-
const codexTurnControlStores = new Map();
|
|
23
|
-
const CODEX_THREAD_SCROLL_BOTTOM_THRESHOLD = 48;
|
|
24
|
-
const CODEX_THREAD_SIDEBAR_STORAGE_KEY = "elench-shell:codex-thread-sidebar:v1";
|
|
25
|
-
const DEFAULT_CODEX_THREAD_SIDEBAR_PREFERENCES = {
|
|
26
|
-
pinnedThreadIds: [],
|
|
27
|
-
};
|
|
28
|
-
function readCodexThreadSidebarPreferences() {
|
|
29
|
-
if (typeof window === "undefined") {
|
|
30
|
-
return DEFAULT_CODEX_THREAD_SIDEBAR_PREFERENCES;
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
const raw = window.localStorage.getItem(CODEX_THREAD_SIDEBAR_STORAGE_KEY);
|
|
34
|
-
if (!raw)
|
|
35
|
-
return DEFAULT_CODEX_THREAD_SIDEBAR_PREFERENCES;
|
|
36
|
-
const parsed = JSON.parse(raw);
|
|
37
|
-
return {
|
|
38
|
-
pinnedThreadIds: Array.isArray(parsed.pinnedThreadIds)
|
|
39
|
-
? parsed.pinnedThreadIds.filter((threadId) => typeof threadId === "string")
|
|
40
|
-
: [],
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return DEFAULT_CODEX_THREAD_SIDEBAR_PREFERENCES;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function writeCodexThreadSidebarPreferences(preferences) {
|
|
48
|
-
if (typeof window === "undefined")
|
|
49
|
-
return;
|
|
50
|
-
try {
|
|
51
|
-
window.localStorage.setItem(CODEX_THREAD_SIDEBAR_STORAGE_KEY, JSON.stringify(preferences));
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
// Ignore storage failures; pinning is a local UI preference.
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function useCodexThreadSidebarPreferences() {
|
|
58
|
-
const [preferences, setPreferences] = useState(() => readCodexThreadSidebarPreferences());
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
if (typeof window === "undefined")
|
|
61
|
-
return;
|
|
62
|
-
const handleStorage = (event) => {
|
|
63
|
-
if (event.key === CODEX_THREAD_SIDEBAR_STORAGE_KEY) {
|
|
64
|
-
setPreferences(readCodexThreadSidebarPreferences());
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
window.addEventListener("storage", handleStorage);
|
|
68
|
-
return () => window.removeEventListener("storage", handleStorage);
|
|
69
|
-
}, []);
|
|
70
|
-
const setPinned = useCallback((threadId, pinned) => {
|
|
71
|
-
setPreferences((current) => {
|
|
72
|
-
const pinnedThreadIds = new Set(current.pinnedThreadIds);
|
|
73
|
-
if (pinned) {
|
|
74
|
-
pinnedThreadIds.add(threadId);
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
pinnedThreadIds.delete(threadId);
|
|
78
|
-
}
|
|
79
|
-
const next = { pinnedThreadIds: [...pinnedThreadIds] };
|
|
80
|
-
writeCodexThreadSidebarPreferences(next);
|
|
81
|
-
return next;
|
|
82
|
-
});
|
|
83
|
-
}, []);
|
|
84
|
-
return { preferences, setPinned };
|
|
85
|
-
}
|
|
86
|
-
function distanceFromScrollBottom(element) {
|
|
87
|
-
return Math.max(0, element.scrollHeight - element.scrollTop - element.clientHeight);
|
|
88
|
-
}
|
|
89
|
-
function scrollElementToBottom(element) {
|
|
90
|
-
element.scrollTop = Math.max(0, element.scrollHeight - element.clientHeight);
|
|
91
|
-
}
|
|
92
|
-
function codexTurnControlKey(session) {
|
|
93
|
-
if (session?.threadId)
|
|
94
|
-
return `thread:${session.threadId}`;
|
|
95
|
-
const cwd = session?.turn?.cwd ?? session?.startThread?.cwd ?? "";
|
|
96
|
-
return `new:${cwd}`;
|
|
97
|
-
}
|
|
98
|
-
function codexTurnControlBaseSignature(session) {
|
|
99
|
-
return JSON.stringify(session?.turn ?? {});
|
|
100
|
-
}
|
|
101
|
-
function mergeTurnControlBase(base, value) {
|
|
102
|
-
const next = {
|
|
103
|
-
...base,
|
|
104
|
-
...value,
|
|
105
|
-
};
|
|
106
|
-
if ("cwd" in base) {
|
|
107
|
-
next.cwd = base.cwd;
|
|
108
|
-
}
|
|
109
|
-
return next;
|
|
110
|
-
}
|
|
111
|
-
function ensureCodexTurnControlStore(key, base) {
|
|
112
|
-
let store = codexTurnControlStores.get(key);
|
|
113
|
-
if (!store) {
|
|
114
|
-
store = {
|
|
115
|
-
listeners: new Set(),
|
|
116
|
-
value: { ...base },
|
|
117
|
-
};
|
|
118
|
-
codexTurnControlStores.set(key, store);
|
|
119
|
-
return store;
|
|
120
|
-
}
|
|
121
|
-
store.value = mergeTurnControlBase(base, store.value);
|
|
122
|
-
return store;
|
|
123
|
-
}
|
|
124
|
-
function setCodexTurnControlStoreValue(key, value) {
|
|
125
|
-
const store = ensureCodexTurnControlStore(key, {});
|
|
126
|
-
store.value = value;
|
|
127
|
-
for (const listener of store.listeners) {
|
|
128
|
-
listener(store.value);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function useCodexTurnControlState(session) {
|
|
132
|
-
const key = codexTurnControlKey(session);
|
|
133
|
-
const baseSignature = codexTurnControlBaseSignature(session);
|
|
134
|
-
const base = useMemo(() => JSON.parse(baseSignature), [baseSignature]);
|
|
135
|
-
const [value, setValue] = useState(() => ensureCodexTurnControlStore(key, base).value);
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
const store = ensureCodexTurnControlStore(key, base);
|
|
138
|
-
setValue(store.value);
|
|
139
|
-
store.listeners.add(setValue);
|
|
140
|
-
return () => {
|
|
141
|
-
store.listeners.delete(setValue);
|
|
142
|
-
};
|
|
143
|
-
}, [baseSignature, key]);
|
|
144
|
-
const update = useCallback((nextValue) => {
|
|
145
|
-
setCodexTurnControlStoreValue(key, mergeTurnControlBase(base, nextValue));
|
|
146
|
-
}, [base, key]);
|
|
147
|
-
return [value, update];
|
|
148
|
-
}
|
|
149
|
-
function useCodexThreadScrollFollow({ scrollRef, contentRef, threadKey, }) {
|
|
150
|
-
const followingRef = useRef(true);
|
|
151
|
-
const lastScrollHeightRef = useRef(0);
|
|
152
|
-
const lastThreadKeyRef = useRef(null);
|
|
153
|
-
const [hasNewOutput, setHasNewOutput] = useState(false);
|
|
154
|
-
const followLatest = useCallback(() => {
|
|
155
|
-
const element = scrollRef.current;
|
|
156
|
-
if (!element)
|
|
157
|
-
return;
|
|
158
|
-
scrollElementToBottom(element);
|
|
159
|
-
followingRef.current = true;
|
|
160
|
-
lastScrollHeightRef.current = element.scrollHeight;
|
|
161
|
-
setHasNewOutput(false);
|
|
162
|
-
}, [scrollRef]);
|
|
163
|
-
const handleScroll = useCallback(() => {
|
|
164
|
-
const element = scrollRef.current;
|
|
165
|
-
if (!element)
|
|
166
|
-
return;
|
|
167
|
-
const isAtBottom = distanceFromScrollBottom(element) <= CODEX_THREAD_SCROLL_BOTTOM_THRESHOLD;
|
|
168
|
-
followingRef.current = isAtBottom;
|
|
169
|
-
if (isAtBottom)
|
|
170
|
-
setHasNewOutput(false);
|
|
171
|
-
lastScrollHeightRef.current = element.scrollHeight;
|
|
172
|
-
}, [scrollRef]);
|
|
173
|
-
useLayoutEffect(() => {
|
|
174
|
-
const element = scrollRef.current;
|
|
175
|
-
if (!element)
|
|
176
|
-
return;
|
|
177
|
-
const threadChanged = threadKey !== lastThreadKeyRef.current;
|
|
178
|
-
if (threadChanged) {
|
|
179
|
-
lastThreadKeyRef.current = threadKey;
|
|
180
|
-
followingRef.current = true;
|
|
181
|
-
scrollElementToBottom(element);
|
|
182
|
-
lastScrollHeightRef.current = element.scrollHeight;
|
|
183
|
-
setHasNewOutput(false);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const contentGrew = element.scrollHeight > lastScrollHeightRef.current;
|
|
187
|
-
if (followingRef.current) {
|
|
188
|
-
scrollElementToBottom(element);
|
|
189
|
-
setHasNewOutput(false);
|
|
190
|
-
}
|
|
191
|
-
else if (contentGrew) {
|
|
192
|
-
setHasNewOutput(true);
|
|
193
|
-
}
|
|
194
|
-
lastScrollHeightRef.current = element.scrollHeight;
|
|
195
|
-
});
|
|
196
|
-
useEffect(() => {
|
|
197
|
-
const contentElement = contentRef.current;
|
|
198
|
-
const scrollElement = scrollRef.current;
|
|
199
|
-
if (!contentElement ||
|
|
200
|
-
!scrollElement ||
|
|
201
|
-
typeof ResizeObserver === "undefined") {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
const observer = new ResizeObserver(() => {
|
|
205
|
-
if (followingRef.current) {
|
|
206
|
-
scrollElementToBottom(scrollElement);
|
|
207
|
-
lastScrollHeightRef.current = scrollElement.scrollHeight;
|
|
208
|
-
setHasNewOutput(false);
|
|
209
|
-
}
|
|
210
|
-
else if (scrollElement.scrollHeight > lastScrollHeightRef.current) {
|
|
211
|
-
lastScrollHeightRef.current = scrollElement.scrollHeight;
|
|
212
|
-
setHasNewOutput(true);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
observer.observe(contentElement);
|
|
216
|
-
return () => observer.disconnect();
|
|
217
|
-
}, [contentRef, scrollRef, threadKey]);
|
|
218
|
-
return {
|
|
219
|
-
followLatest,
|
|
220
|
-
handleScroll,
|
|
221
|
-
hasNewOutput,
|
|
222
|
-
isFollowing: followingRef.current,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
export function CodexThreadWorkspace({ className, defaultMessage = "", emptyDescription = "Send a message to start the Codex thread.", emptyTitle = "No Codex output yet", onThreadChange, placeholder = "Message Codex", settings, session, }) {
|
|
226
|
-
const [turnControls] = useCodexTurnControlState(session);
|
|
227
|
-
const resolvedSession = useMemo(() => ({
|
|
228
|
-
...session,
|
|
229
|
-
onThreadChange,
|
|
230
|
-
turn: {
|
|
231
|
-
...session?.turn,
|
|
232
|
-
...turnControls,
|
|
233
|
-
},
|
|
234
|
-
}), [onThreadChange, session, turnControls]);
|
|
235
|
-
const { error, isPending, sendText, snapshot } = useCodexThreadSession(resolvedSession);
|
|
236
|
-
const scrollRef = useRef(null);
|
|
237
|
-
const transcriptContentRef = useRef(null);
|
|
238
|
-
const threadKey = snapshot?.thread?.id ?? session?.threadId ?? null;
|
|
239
|
-
const threadScroll = useCodexThreadScrollFollow({
|
|
240
|
-
contentRef: transcriptContentRef,
|
|
241
|
-
scrollRef,
|
|
242
|
-
threadKey,
|
|
243
|
-
});
|
|
244
|
-
return (_jsxs("div", { className: cn("flex h-full min-h-0 flex-col overflow-hidden", className), "data-codex-thread-workspace": true, children: [_jsxs("div", { className: "relative min-h-0 min-w-0 flex-1", children: [_jsx("div", { className: "h-full min-h-0 min-w-0 overflow-x-hidden overflow-y-auto px-6 py-5", "data-codex-thread-following": threadScroll.isFollowing ? "true" : "false", "data-codex-thread-new-output": threadScroll.hasNewOutput ? "true" : undefined, "data-codex-thread-scroll": true, onScroll: threadScroll.handleScroll, ref: scrollRef, children: _jsx("div", { ref: transcriptContentRef, children: _jsx(CodexThreadTranscript, { emptyDescription: emptyDescription, emptyTitle: emptyTitle, settings: settings, snapshot: snapshot }) }) }), threadScroll.hasNewOutput ? (_jsxs(Button, { "aria-label": "Jump to latest Codex output", className: "absolute right-5 bottom-4 shadow-sm", onClick: threadScroll.followLatest, size: "sm", type: "button", variant: "outline", children: [_jsx(ArrowDownIcon, {}), "Latest"] })) : null] }), _jsxs("div", { className: "shrink-0 border-t border-border bg-bg-surface px-4 py-3", "data-codex-thread-composer": true, children: [error ? (_jsx("div", { className: "mb-3 rounded-md border border-destructive/30 bg-destructive/8 px-3 py-2 text-[12px] text-fg", children: error.message })) : null, _jsx(CodexThreadComposer, { defaultMessage: defaultMessage, disabled: isPending, onSubmit: sendText, placeholder: placeholder })] })] }));
|
|
245
|
-
}
|
|
246
|
-
const CODEX_REASONING_EFFORT_OPTIONS = [
|
|
247
|
-
{ label: "Configured default", value: "__default" },
|
|
248
|
-
{ label: "None", value: "none" },
|
|
249
|
-
{ label: "Minimal", value: "minimal" },
|
|
250
|
-
{ label: "Low", value: "low" },
|
|
251
|
-
{ label: "Medium", value: "medium" },
|
|
252
|
-
{ label: "High", value: "high" },
|
|
253
|
-
{ label: "XHigh", value: "xhigh" },
|
|
254
|
-
];
|
|
255
|
-
const CODEX_APPROVAL_OPTIONS = [
|
|
256
|
-
{ label: "Configured default", value: "__default" },
|
|
257
|
-
{ label: "On request", value: "on-request" },
|
|
258
|
-
{ label: "On failure", value: "on-failure" },
|
|
259
|
-
{ label: "Untrusted", value: "untrusted" },
|
|
260
|
-
{ label: "Never", value: "never" },
|
|
261
|
-
];
|
|
262
|
-
const CODEX_REASONING_SUMMARY_OPTIONS = [
|
|
263
|
-
{ label: "Configured default", value: "__default" },
|
|
264
|
-
{ label: "Auto", value: "auto" },
|
|
265
|
-
{ label: "Concise", value: "concise" },
|
|
266
|
-
{ label: "Detailed", value: "detailed" },
|
|
267
|
-
{ label: "None", value: "none" },
|
|
268
|
-
];
|
|
269
|
-
function useCodexModelOptions() {
|
|
270
|
-
const { client, initialized } = useCodexAppServer();
|
|
271
|
-
const [models, setModels] = useState([]);
|
|
272
|
-
useEffect(() => {
|
|
273
|
-
if (!client || !initialized)
|
|
274
|
-
return;
|
|
275
|
-
let active = true;
|
|
276
|
-
client
|
|
277
|
-
.listModels({ limit: 80 })
|
|
278
|
-
.then((response) => {
|
|
279
|
-
if (!active)
|
|
280
|
-
return;
|
|
281
|
-
setModels(response.data.map((model) => ({
|
|
282
|
-
label: model.displayName,
|
|
283
|
-
value: model.id,
|
|
284
|
-
})));
|
|
285
|
-
})
|
|
286
|
-
.catch(() => {
|
|
287
|
-
if (active)
|
|
288
|
-
setModels([]);
|
|
289
|
-
});
|
|
290
|
-
return () => {
|
|
291
|
-
active = false;
|
|
292
|
-
};
|
|
293
|
-
}, [client, initialized]);
|
|
294
|
-
return [{ label: "Configured default", value: "__default" }, ...models];
|
|
295
|
-
}
|
|
296
|
-
function useCodexTurnControlSetter(onChange, value) {
|
|
297
|
-
const set = (key, nextValue) => {
|
|
298
|
-
onChange({
|
|
299
|
-
...value,
|
|
300
|
-
[key]: nextValue,
|
|
301
|
-
});
|
|
302
|
-
};
|
|
303
|
-
return set;
|
|
304
|
-
}
|
|
305
|
-
export function CodexTurnControls({ onChange, value }) {
|
|
306
|
-
const modelOptions = useCodexModelOptions();
|
|
307
|
-
const set = useCodexTurnControlSetter(onChange, value);
|
|
308
|
-
return (_jsxs("div", { className: "grid gap-2", children: [_jsx(ControlLabel, { label: "Model", children: _jsx(SimpleSelect, { onValueChange: (model) => set("model", model === "__default" ? null : model), options: modelOptions, value: value.model ?? "__default" }) }), _jsx(ControlLabel, { label: "Effort", children: _jsx(SimpleSelect, { onValueChange: (effort) => set("effort", effort === "__default" ? null : effort), options: CODEX_REASONING_EFFORT_OPTIONS, value: value.effort ?? "__default" }) }), _jsx(ControlLabel, { label: "Approval", children: _jsx(SimpleSelect, { onValueChange: (approvalPolicy) => set("approvalPolicy", approvalPolicy === "__default" ? null : approvalPolicy), options: CODEX_APPROVAL_OPTIONS, value: typeof value.approvalPolicy === "string"
|
|
309
|
-
? value.approvalPolicy
|
|
310
|
-
: "__default" }) }), _jsx(ControlLabel, { label: "Reasoning summary", children: _jsx(SimpleSelect, { onValueChange: (summary) => set("summary", summary === "__default" ? null : summary), options: CODEX_REASONING_SUMMARY_OPTIONS, value: value.summary ?? "__default" }) })] }));
|
|
311
|
-
}
|
|
312
|
-
function ControlLabel({ children, label, }) {
|
|
313
|
-
return (_jsxs("label", { className: "grid gap-1 text-[11px] font-medium text-fg-tertiary", children: [label, children] }));
|
|
314
|
-
}
|
|
315
|
-
export function CodexThreadComposer({ defaultMessage = "", disabled = false, onSubmit, placeholder = "Message Codex", }) {
|
|
316
|
-
const [message, setMessage] = useState(defaultMessage);
|
|
317
|
-
const submit = () => {
|
|
318
|
-
const next = message.trim();
|
|
319
|
-
if (!next)
|
|
320
|
-
return;
|
|
321
|
-
setMessage("");
|
|
322
|
-
void onSubmit(next);
|
|
323
|
-
};
|
|
324
|
-
return (_jsxs("div", { className: "flex items-end gap-2", children: [_jsx(Textarea, { "aria-label": "Codex message", className: "min-h-20 resize-none", onChange: (event) => setMessage(event.target.value), onKeyDown: (event) => {
|
|
325
|
-
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
326
|
-
event.preventDefault();
|
|
327
|
-
submit();
|
|
328
|
-
}
|
|
329
|
-
}, placeholder: placeholder, value: message }), _jsxs(Button, { "aria-label": "Send Codex message", disabled: disabled || message.trim().length === 0, onClick: submit, children: [_jsx(SendIcon, {}), "Send"] })] }));
|
|
330
|
-
}
|
|
331
|
-
export function CodexThreadTranscript({ emptyDescription = "Send a message to start the Codex thread.", emptyTitle = "No Codex output yet", settings, snapshot, }) {
|
|
332
|
-
const resolvedSettings = useMemo(() => mergeCodexTranscriptSettings(settings), [settings]);
|
|
333
|
-
const entries = useMemo(() => toCodexTranscriptEntries(snapshot), [snapshot]);
|
|
334
|
-
if (entries.length === 0) {
|
|
335
|
-
return (_jsx(EmptyState, { className: "min-h-[24rem]", description: emptyDescription, title: emptyTitle }));
|
|
336
|
-
}
|
|
337
|
-
return (_jsx("div", { className: cn("mx-auto w-full max-w-[980px] min-w-0", resolvedSettings.density === "compact" ? "space-y-3" : "space-y-5"), children: entries.map((entry) => {
|
|
338
|
-
switch (entry.type) {
|
|
339
|
-
case "userMessage":
|
|
340
|
-
return (_jsx(CodexUserMessageBubble, { item: entry.item, settings: resolvedSettings }, entry.id));
|
|
341
|
-
case "assistantText":
|
|
342
|
-
return (_jsx(CodexAssistantText, { settings: resolvedSettings, text: entry.item.text }, entry.id));
|
|
343
|
-
case "reasoning":
|
|
344
|
-
return (_jsx(CodexReasoningBlock, { item: entry.item, settings: resolvedSettings }, entry.id));
|
|
345
|
-
case "turnPlan":
|
|
346
|
-
return _jsx(CodexPlanText, { plan: entry.plan }, entry.id);
|
|
347
|
-
case "planItem":
|
|
348
|
-
return (_jsx(CodexAssistantText, { settings: resolvedSettings, text: entry.item.text }, entry.id));
|
|
349
|
-
case "command":
|
|
350
|
-
return (_jsx(CodexCommandBlock, { item: entry.item, settings: resolvedSettings }, entry.id));
|
|
351
|
-
case "fileChange":
|
|
352
|
-
return (_jsx(CodexFileChangeBlock, { item: entry.item, settings: resolvedSettings }, entry.id));
|
|
353
|
-
case "toolCall":
|
|
354
|
-
return (_jsx(CodexToolCallText, { item: entry.item, settings: resolvedSettings }, entry.id));
|
|
355
|
-
case "webSearch":
|
|
356
|
-
return (_jsx(CodexTranscriptLine, { icon: _jsx(GitBranchIcon, { className: "size-3.5" }), label: "web search", value: entry.item.query }, entry.id));
|
|
357
|
-
case "pendingRequest":
|
|
358
|
-
return (_jsx(CodexPendingRequestLine, { request: entry.request }, entry.id));
|
|
359
|
-
default:
|
|
360
|
-
return (_jsx(CodexAssistantText, { settings: resolvedSettings, text: JSON.stringify(entry.item, null, 2) }, entry.id));
|
|
361
|
-
}
|
|
362
|
-
}) }));
|
|
363
|
-
}
|
|
364
|
-
export function CodexUserMessageBubble({ item, settings = DEFAULT_CODEX_TRANSCRIPT_RENDER_SETTINGS, }) {
|
|
365
|
-
return (_jsx("div", { className: "flex justify-end", children: _jsx("div", { className: "max-w-[760px] rounded-[10px] border border-primary/25 bg-primary/10 px-3.5 py-2.5 text-[13px] leading-6 text-fg", children: _jsx(CodexMarkdownText, { settings: settings, text: codexInputText(item) }) }) }));
|
|
366
|
-
}
|
|
367
|
-
export function CodexAssistantText({ settings = DEFAULT_CODEX_TRANSCRIPT_RENDER_SETTINGS, text, }) {
|
|
368
|
-
return (_jsx("div", { className: "max-w-[900px] text-[13px] leading-6 text-fg", children: _jsx(CodexMarkdownText, { settings: settings, text: text }) }));
|
|
369
|
-
}
|
|
370
|
-
export function CodexMarkdownText({ settings = DEFAULT_CODEX_TRANSCRIPT_RENDER_SETTINGS, text, }) {
|
|
371
|
-
if (settings.markdown === "raw") {
|
|
372
|
-
return _jsx("pre", { className: "whitespace-pre-wrap font-sans", children: text });
|
|
373
|
-
}
|
|
374
|
-
return (_jsx(ReactMarkdown, { components: {
|
|
375
|
-
code: MarkdownCode,
|
|
376
|
-
p: ({ children }) => _jsx("p", { className: "mb-2 last:mb-0", children: children }),
|
|
377
|
-
pre: ({ children }) => _jsx(_Fragment, { children: children }),
|
|
378
|
-
}, remarkPlugins: [remarkGfm], children: text }));
|
|
379
|
-
}
|
|
380
|
-
function MarkdownCode(props) {
|
|
381
|
-
const text = String(props.children ?? "").replace(/\n$/, "");
|
|
382
|
-
const language = /language-([\w-]+)/.exec(props.className ?? "")?.[1] ?? "text";
|
|
383
|
-
const inline = props.inline ?? (!props.className && !text.includes("\n"));
|
|
384
|
-
if (inline) {
|
|
385
|
-
return (_jsx("code", { className: "rounded bg-bg-elevated px-1 py-0.5 font-mono text-[0.92em]", children: text }));
|
|
386
|
-
}
|
|
387
|
-
return _jsx(CodexHighlightedCode, { code: text, language: language });
|
|
388
|
-
}
|
|
389
|
-
export function CodexHighlightedCode({ code, language = "text", }) {
|
|
390
|
-
const [html, setHtml] = useState(null);
|
|
391
|
-
useEffect(() => {
|
|
392
|
-
let active = true;
|
|
393
|
-
setHtml(null);
|
|
394
|
-
import("shiki")
|
|
395
|
-
.then(({ codeToHtml }) => codeToHtml(code || " ", {
|
|
396
|
-
lang: language,
|
|
397
|
-
theme: "github-dark-default",
|
|
398
|
-
}))
|
|
399
|
-
.then((nextHtml) => {
|
|
400
|
-
if (active)
|
|
401
|
-
setHtml(nextHtml);
|
|
402
|
-
})
|
|
403
|
-
.catch(() => {
|
|
404
|
-
if (active)
|
|
405
|
-
setHtml(null);
|
|
406
|
-
});
|
|
407
|
-
return () => {
|
|
408
|
-
active = false;
|
|
409
|
-
};
|
|
410
|
-
}, [code, language]);
|
|
411
|
-
if (!html) {
|
|
412
|
-
return (_jsx("pre", { className: "my-2 overflow-x-auto rounded-md border border-border bg-bg-card p-3 font-mono text-[12px] leading-5 text-fg", children: code }));
|
|
413
|
-
}
|
|
414
|
-
return (_jsx("div", { className: "codex-shiki my-2 overflow-x-auto rounded-md border border-border bg-bg-card text-[12px] leading-5 [&_pre]:!m-0 [&_pre]:!bg-transparent [&_pre]:!p-3", dangerouslySetInnerHTML: { __html: html } }));
|
|
415
|
-
}
|
|
416
|
-
export function CodexReasoningBlock({ item, settings, }) {
|
|
417
|
-
if (settings.reasoning === "hidden")
|
|
418
|
-
return null;
|
|
419
|
-
const content = settings.reasoning === "expanded"
|
|
420
|
-
? [...item.summary, ...item.content]
|
|
421
|
-
: item.summary;
|
|
422
|
-
const text = content.filter(Boolean).join("\n\n");
|
|
423
|
-
if (!text)
|
|
424
|
-
return null;
|
|
425
|
-
return (_jsx(CodexTranscriptLine, { icon: _jsx(BotIcon, { className: "size-3.5" }), label: "reasoning", muted: true, value: text }));
|
|
426
|
-
}
|
|
427
|
-
export function CodexPlanText({ plan }) {
|
|
428
|
-
return (_jsxs("div", { className: "text-[13px] leading-6 text-fg-secondary", children: [plan.explanation ? (_jsx("p", { className: "mb-1.5 whitespace-pre-wrap", children: plan.explanation })) : null, _jsx("div", { className: "space-y-1", children: plan.steps.map((step, index) => (_jsxs("div", { className: "flex items-start gap-2", children: [_jsx(StatusDot, { className: "mt-[7px]", status: step.status === "completed" ? "healthy" : "unknown" }), _jsx("span", { className: "whitespace-pre-wrap", children: step.step })] }, `${step.step}-${index}`))) })] }));
|
|
429
|
-
}
|
|
430
|
-
export function CodexCommandBlock({ item, settings, }) {
|
|
431
|
-
const output = item.aggregatedOutput ?? "";
|
|
432
|
-
const collapsed = settings.commandOutput === "collapsed" ||
|
|
433
|
-
(settings.commandOutput === "auto" && output.split("\n").length > 80);
|
|
434
|
-
return (_jsxs("div", { className: "min-w-0 space-y-1.5 text-[13px] leading-6", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 text-[12px] text-fg-tertiary", children: [_jsx(TerminalIcon, { className: "size-3.5" }), _jsx("span", { className: "font-semibold text-fg-secondary", children: "command" }), _jsx("span", { children: item.status }), item.exitCode != null ? _jsxs("span", { children: ["exit ", item.exitCode] }) : null, item.durationMs != null ? _jsxs("span", { children: [item.durationMs, "ms"] }) : null] }), _jsx("pre", { className: "max-w-full whitespace-pre-wrap break-words font-mono text-[12px] leading-5 text-fg", children: item.command }), item.cwd ? (_jsx("div", { className: "font-mono text-[11px] text-fg-tertiary", children: item.cwd })) : null, output ? (collapsed ? (_jsxs("div", { className: "font-mono text-[12px] text-fg-tertiary", children: ["output collapsed (", formatNumber(output.split("\n").length), " lines)"] })) : (_jsx("pre", { className: "max-h-[32rem] max-w-full overflow-auto rounded-md border border-border bg-bg-card p-3 font-mono text-[12px] leading-5 text-fg", children: _jsx(CodexAnsiText, { enabled: settings.ansi, text: output }) }))) : null] }));
|
|
435
|
-
}
|
|
436
|
-
export function CodexAnsiText({ enabled = true, text, }) {
|
|
437
|
-
if (!enabled)
|
|
438
|
-
return _jsx(_Fragment, { children: Anser.ansiToText(text) });
|
|
439
|
-
const html = Anser.ansiToHtml(Anser.escapeForHtml(text));
|
|
440
|
-
return _jsx("span", { dangerouslySetInnerHTML: { __html: html } });
|
|
441
|
-
}
|
|
442
|
-
export function CodexFileChangeBlock({ item, settings, }) {
|
|
443
|
-
const totals = countFileUpdateChanges(item.changes);
|
|
444
|
-
return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-[12px] text-fg-tertiary", children: [_jsx(GitBranchIcon, { className: "size-3.5" }), _jsx("span", { className: "font-semibold text-fg-secondary", children: "edited files" }), _jsxs("span", { className: "text-status-ok", children: ["+", totals.additions] }), _jsxs("span", { className: "text-status-err", children: ["-", totals.deletions] }), _jsx("span", { children: item.status })] }), item.changes.map((change, index) => (_jsx(CodexUnifiedDiff, { change: change, settings: settings }, `${change.path}-${index}`)))] }));
|
|
445
|
-
}
|
|
446
|
-
export function CodexUnifiedDiff({ change, settings = DEFAULT_CODEX_TRANSCRIPT_RENDER_SETTINGS, }) {
|
|
447
|
-
const files = parseCodexUnifiedDiff(change);
|
|
448
|
-
const collapsed = settings.diffs === "collapsed" ||
|
|
449
|
-
(settings.diffs === "auto" &&
|
|
450
|
-
files.some((file) => file.hunks.some((hunk) => hunk.changes.length > 160)));
|
|
451
|
-
return (_jsx("div", { className: "space-y-2", children: files.map((file) => (_jsx(CodexDiffFile, { collapsed: collapsed, file: file, settings: settings }, `${file.oldPath}:${file.newPath}`))) }));
|
|
452
|
-
}
|
|
453
|
-
function CodexDiffFile({ collapsed, file, settings, }) {
|
|
454
|
-
const language = languageForPath(file.displayPath);
|
|
455
|
-
return (_jsxs("div", { className: "space-y-1.5", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 font-mono text-[12px] text-fg-secondary", children: [_jsx("span", { children: file.displayPath }), _jsxs("span", { className: "text-status-ok", children: ["+", file.totals.additions] }), _jsxs("span", { className: "text-status-err", children: ["-", file.totals.deletions] })] }), collapsed ? (_jsxs("div", { className: "font-mono text-[12px] text-fg-tertiary", children: ["diff collapsed (", formatNumber(totalDiffLines(file)), " lines)"] })) : (_jsx("div", { className: "overflow-hidden rounded-md border border-border bg-bg-card font-mono text-[12px] leading-5", children: file.hunks.map((hunk, hunkIndex) => (_jsxs("div", { children: [_jsx("div", { className: "border-b border-border/70 bg-bg-elevated px-3 py-1 text-fg-tertiary", children: hunk.content }), hunk.changes.map((change, index) => (_jsxs("div", { className: cn("grid grid-cols-[4.5rem_4.5rem_1.5rem_minmax(0,1fr)] gap-2 px-3 py-0.5", change.type === "insert" && "bg-status-ok/10", change.type === "delete" && "bg-status-err/10"), children: [_jsx("span", { className: "text-right text-fg-tertiary", children: "oldLineNumber" in change ? change.oldLineNumber : "" }), _jsx("span", { className: "text-right text-fg-tertiary", children: "newLineNumber" in change ? change.newLineNumber : "" }), _jsx("span", { className: cn("text-fg-tertiary", change.type === "insert" && "text-status-ok", change.type === "delete" && "text-status-err"), children: change.type === "insert"
|
|
456
|
-
? "+"
|
|
457
|
-
: change.type === "delete"
|
|
458
|
-
? "-"
|
|
459
|
-
: " " }), _jsx("span", { className: "min-w-0 whitespace-pre-wrap break-words text-fg", children: _jsx(CodexHighlightedFragment, { code: change.content.replace(/^[+\- ]/, ""), enabled: settings.syntaxHighlighting, language: language }) })] }, `${change.content}-${index}`)))] }, `${hunk.content}-${hunkIndex}`))) }))] }));
|
|
460
|
-
}
|
|
461
|
-
function CodexHighlightedFragment({ code, enabled, language, }) {
|
|
462
|
-
const [html, setHtml] = useState(null);
|
|
463
|
-
useEffect(() => {
|
|
464
|
-
if (!enabled || language === "text" || code.trim().length === 0) {
|
|
465
|
-
setHtml(null);
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
let active = true;
|
|
469
|
-
import("shiki")
|
|
470
|
-
.then(({ codeToHtml }) => codeToHtml(code, { lang: language, theme: "github-dark-default" }))
|
|
471
|
-
.then((nextHtml) => {
|
|
472
|
-
if (!active)
|
|
473
|
-
return;
|
|
474
|
-
const match = /<code[^>]*>([\s\S]*?)<\/code>/.exec(nextHtml);
|
|
475
|
-
setHtml(match?.[1] ?? null);
|
|
476
|
-
})
|
|
477
|
-
.catch(() => {
|
|
478
|
-
if (active)
|
|
479
|
-
setHtml(null);
|
|
480
|
-
});
|
|
481
|
-
return () => {
|
|
482
|
-
active = false;
|
|
483
|
-
};
|
|
484
|
-
}, [code, enabled, language]);
|
|
485
|
-
if (!html)
|
|
486
|
-
return _jsx(_Fragment, { children: code });
|
|
487
|
-
return _jsx("span", { dangerouslySetInnerHTML: { __html: html } });
|
|
488
|
-
}
|
|
489
|
-
export function CodexToolCallText({ item, settings, }) {
|
|
490
|
-
const title = item.type === "mcpToolCall"
|
|
491
|
-
? `${item.server}.${item.tool}`
|
|
492
|
-
: [item.namespace, item.tool].filter(Boolean).join(".");
|
|
493
|
-
const detail = settings.toolDetails === "full"
|
|
494
|
-
? JSON.stringify(item, null, 2)
|
|
495
|
-
: item.type === "mcpToolCall" && item.error
|
|
496
|
-
? item.error.message
|
|
497
|
-
: "";
|
|
498
|
-
return (_jsx(CodexTranscriptLine, { icon: _jsx(CircleIcon, { className: "size-3.5" }), label: item.type === "mcpToolCall" ? "mcp" : "tool", muted: true, value: [title, detail].filter(Boolean).join("\n") }));
|
|
499
|
-
}
|
|
500
|
-
export function CodexPendingRequestLine({ request, }) {
|
|
501
|
-
return (_jsx(CodexTranscriptLine, { icon: _jsx(ClockIcon, { className: "size-3.5" }), label: "pending request", value: `${request.method}\nrequest ${String(request.id)}` }));
|
|
502
|
-
}
|
|
503
|
-
function CodexTranscriptLine({ icon, label, muted = false, value, }) {
|
|
504
|
-
return (_jsxs("div", { className: cn("space-y-1 text-[13px] leading-6", muted && "text-fg-secondary"), children: [_jsxs("div", { className: "flex items-center gap-2 text-[12px] font-semibold text-fg-tertiary", children: [icon, label] }), _jsx("pre", { className: "whitespace-pre-wrap font-sans", children: value })] }));
|
|
505
|
-
}
|
|
506
|
-
export function CodexThreadInspector({ session, showControls = false, snapshot, }) {
|
|
507
|
-
const summary = useMemo(() => summarizeCodexThread(snapshot), [snapshot]);
|
|
508
|
-
const thread = summary.thread;
|
|
509
|
-
const usagePercent = tokenUsagePercent(summary.tokenUsage);
|
|
510
|
-
const [turnControls, setTurnControls] = useCodexTurnControlState(session);
|
|
511
|
-
const lastTurn = summary.lastTurnDurationMs == null
|
|
512
|
-
? "none"
|
|
513
|
-
: `${summary.lastTurnDurationMs}ms`;
|
|
514
|
-
return (_jsxs("div", { className: "space-y-2", children: [_jsx(CodexInspectorValueItem, { label: "Thread", mono: true, value: thread?.id ?? "none" }), _jsx(CodexInspectorValueItem, { label: "Status", value: thread?.status.type ?? "none" }), _jsx(CodexInspectorValueItem, { label: "Source", value: thread ? codexThreadSourceKind(thread) : "none" }), _jsx(CodexInspectorValueItem, { label: "Cwd", mono: true, value: thread?.cwd ?? "none" }), showControls ? (_jsx(CodexTurnControlInspectorItems, { onChange: setTurnControls, value: turnControls })) : null, _jsx(CodexInspectorValueItem, { label: "Turns", mono: true, value: formatNumber(summary.turnCount) }), _jsx(CodexInspectorValueItem, { label: "Items", mono: true, value: formatNumber(summary.itemCount) }), _jsx(CodexInspectorValueItem, { label: "Commands", mono: true, value: formatNumber(summary.commandCount) }), _jsx(CodexInspectorValueItem, { label: "File changes", mono: true, value: formatNumber(summary.fileChangeCount) }), _jsx(CodexInspectorValueItem, { label: "Last turn", mono: true, value: lastTurn }), _jsx(CodexInspectorValueItem, { label: "Tokens", mono: true, value: formatTokenUsageBreakdown(summary.tokenUsage?.tokenUsage.total) }), _jsx(CodexInspectorValueItem, { label: "Context", mono: true, value: usagePercent == null ? "unknown" : `${usagePercent}%` }), _jsx(CodexInspectorValueItem, { label: "Pending", mono: true, value: String(summary.pendingRequestCount) })] }));
|
|
515
|
-
}
|
|
516
|
-
function CodexTurnControlInspectorItems({ onChange, value, }) {
|
|
517
|
-
const modelOptions = useCodexModelOptions();
|
|
518
|
-
const set = useCodexTurnControlSetter(onChange, value);
|
|
519
|
-
return (_jsxs(_Fragment, { children: [_jsx(CodexTurnControlInspectorItem, { label: "Model", children: _jsx(SimpleSelect, { className: "text-[12px]", onValueChange: (model) => set("model", model === "__default" ? null : model), options: modelOptions, value: value.model ?? "__default" }) }), _jsx(CodexTurnControlInspectorItem, { label: "Effort", children: _jsx(SimpleSelect, { className: "text-[12px]", onValueChange: (effort) => set("effort", effort === "__default" ? null : effort), options: CODEX_REASONING_EFFORT_OPTIONS, value: value.effort ?? "__default" }) }), _jsx(CodexTurnControlInspectorItem, { label: "Approval", children: _jsx(SimpleSelect, { className: "text-[12px]", onValueChange: (approvalPolicy) => set("approvalPolicy", approvalPolicy === "__default" ? null : approvalPolicy), options: CODEX_APPROVAL_OPTIONS, value: typeof value.approvalPolicy === "string"
|
|
520
|
-
? value.approvalPolicy
|
|
521
|
-
: "__default" }) }), _jsx(CodexTurnControlInspectorItem, { label: "Reasoning summary", children: _jsx(SimpleSelect, { className: "text-[12px]", onValueChange: (summary) => set("summary", summary === "__default" ? null : summary), options: CODEX_REASONING_SUMMARY_OPTIONS, value: value.summary ?? "__default" }) })] }));
|
|
522
|
-
}
|
|
523
|
-
function CodexTurnControlInspectorItem({ children, label, }) {
|
|
524
|
-
return (_jsx(InspectorItem, { children: _jsxs("label", { className: "block", children: [_jsx(InspectorItemHeader, { className: "mb-2", title: label }), children] }) }));
|
|
525
|
-
}
|
|
526
|
-
function CodexInspectorValueItem({ label, mono = false, value, }) {
|
|
527
|
-
return (_jsxs(InspectorItem, { children: [_jsx(InspectorItemHeader, { title: label }), _jsx(InspectorItemValue, { className: "block whitespace-normal text-left break-words", mono: mono, children: value })] }));
|
|
528
|
-
}
|
|
529
|
-
export function CodexThreadSidebar({ activeThreadId, archived = false, cwd, limit = 40, onOpenThread, searchTerm, sourceKinds, }) {
|
|
530
|
-
const { client, error: serverError, initialized, status, } = useCodexAppServer();
|
|
531
|
-
const { preferences, setPinned } = useCodexThreadSidebarPreferences();
|
|
532
|
-
const [threads, setThreads] = useState([]);
|
|
533
|
-
const [error, setError] = useState(null);
|
|
534
|
-
const [loading, setLoading] = useState(false);
|
|
535
|
-
const [renamingThread, setRenamingThread] = useState(null);
|
|
536
|
-
const pinnedThreadIds = preferences.pinnedThreadIds;
|
|
537
|
-
const pinnedThreadIdSet = useMemo(() => new Set(pinnedThreadIds), [pinnedThreadIds]);
|
|
538
|
-
const requestLimit = pinnedThreadIds.length > 0
|
|
539
|
-
? Math.max(limit, 100, limit + pinnedThreadIds.length)
|
|
540
|
-
: limit;
|
|
541
|
-
const sortedThreads = useMemo(() => threads
|
|
542
|
-
.map((thread, index) => ({ index, thread }))
|
|
543
|
-
.sort((left, right) => {
|
|
544
|
-
const leftPinned = pinnedThreadIdSet.has(left.thread.id);
|
|
545
|
-
const rightPinned = pinnedThreadIdSet.has(right.thread.id);
|
|
546
|
-
if (leftPinned !== rightPinned)
|
|
547
|
-
return leftPinned ? -1 : 1;
|
|
548
|
-
return left.index - right.index;
|
|
549
|
-
})
|
|
550
|
-
.map(({ thread }) => thread)
|
|
551
|
-
.slice(0, Math.max(limit, pinnedThreadIds.length)), [limit, pinnedThreadIdSet, pinnedThreadIds.length, threads]);
|
|
552
|
-
const refresh = useCallback(async () => {
|
|
553
|
-
if (!client || !initialized)
|
|
554
|
-
return;
|
|
555
|
-
setLoading(true);
|
|
556
|
-
setError(null);
|
|
557
|
-
try {
|
|
558
|
-
const response = await client.listThreads({
|
|
559
|
-
archived,
|
|
560
|
-
cwd,
|
|
561
|
-
limit: requestLimit,
|
|
562
|
-
searchTerm,
|
|
563
|
-
sortDirection: "desc",
|
|
564
|
-
sortKey: "updated_at",
|
|
565
|
-
sourceKinds,
|
|
566
|
-
});
|
|
567
|
-
setThreads(response.data);
|
|
568
|
-
}
|
|
569
|
-
catch (nextError) {
|
|
570
|
-
setError(nextError instanceof Error ? nextError : new Error(String(nextError)));
|
|
571
|
-
}
|
|
572
|
-
finally {
|
|
573
|
-
setLoading(false);
|
|
574
|
-
}
|
|
575
|
-
}, [
|
|
576
|
-
archived,
|
|
577
|
-
client,
|
|
578
|
-
cwd,
|
|
579
|
-
initialized,
|
|
580
|
-
requestLimit,
|
|
581
|
-
searchTerm,
|
|
582
|
-
sourceKinds,
|
|
583
|
-
]);
|
|
584
|
-
useEffect(() => {
|
|
585
|
-
void refresh();
|
|
586
|
-
}, [refresh]);
|
|
587
|
-
useEffect(() => {
|
|
588
|
-
if (!client)
|
|
589
|
-
return;
|
|
590
|
-
return client.onNotification((notification) => {
|
|
591
|
-
if (notification.method === "thread/started" ||
|
|
592
|
-
notification.method === "thread/status/changed" ||
|
|
593
|
-
notification.method === "thread/name/updated" ||
|
|
594
|
-
notification.method === "thread/archived" ||
|
|
595
|
-
notification.method === "thread/unarchived" ||
|
|
596
|
-
notification.method === "thread/closed" ||
|
|
597
|
-
notification.method === "turn/completed") {
|
|
598
|
-
void refresh();
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
}, [client, refresh]);
|
|
602
|
-
return (_jsxs(_Fragment, { children: [_jsxs(SidebarSection, { children: [_jsx("div", { className: "mb-1 flex justify-end px-2", children: _jsx("button", { "aria-label": "Refresh Codex threads", className: "rounded-md p-1 text-fg-tertiary hover:bg-bg-hover hover:text-fg", onClick: () => void refresh(), type: "button", children: _jsx(RefreshCcwIcon, { className: cn("size-3.5", loading && "animate-spin") }) }) }), serverError || error ? (_jsx("div", { className: "px-2 py-2 text-[11px] text-status-err", children: (serverError ?? error)?.message })) : null, !serverError && !error && (!client || !initialized) ? (_jsx("div", { className: "px-2 py-2 text-[11px] text-fg-tertiary", children: status === "connecting"
|
|
603
|
-
? "Connecting to Codex..."
|
|
604
|
-
: "Codex is not connected." })) : null, client && initialized && threads.length === 0 && !loading ? (_jsx("div", { className: "px-2 py-2 text-[11px] text-fg-tertiary", children: "No Codex threads found." })) : null, sortedThreads.map((thread) => {
|
|
605
|
-
const pinned = pinnedThreadIdSet.has(thread.id);
|
|
606
|
-
const active = activeThreadId === thread.id;
|
|
607
|
-
return (_jsxs(ContextMenu, { children: [_jsx(ContextMenuTrigger, { asChild: true, children: _jsx(SidebarActionRow, { active: active, "aria-selected": active, "data-codex-thread-id": thread.id, "data-codex-thread-pinned": pinned ? "true" : undefined, "data-codex-thread-row": true, description: codexThreadDescription(thread), icon: pinned ? (_jsx(PinIcon, { className: "size-4" })) : thread.status.type === "active" ? (_jsx(ClockIcon, { className: "size-4" })) : (_jsx(MessageSquareIcon, { className: "size-4" })), onClick: () => onOpenThread(thread), role: "tab", title: codexThreadTitle(thread) }) }), _jsxs(ContextMenuContent, { className: "min-w-44", children: [_jsxs(ContextMenuItem, { onSelect: () => setRenamingThread(thread), children: [_jsx(PencilIcon, { className: "size-4" }), "Rename thread..."] }), _jsx(ContextMenuSeparator, {}), _jsxs(ContextMenuItem, { onSelect: () => setPinned(thread.id, !pinned), children: [pinned ? (_jsx(PinOffIcon, { className: "size-4" })) : (_jsx(PinIcon, { className: "size-4" })), pinned ? "Unpin thread" : "Pin thread"] })] })] }, thread.id));
|
|
608
|
-
})] }), _jsx(CodexThreadRenameDialog, { client: client, onOpenChange: (open) => {
|
|
609
|
-
if (!open)
|
|
610
|
-
setRenamingThread(null);
|
|
611
|
-
}, onRenamed: (threadId, name) => {
|
|
612
|
-
setThreads((current) => current.map((thread) => thread.id === threadId ? { ...thread, name } : thread));
|
|
613
|
-
void refresh();
|
|
614
|
-
}, open: renamingThread !== null, thread: renamingThread })] }));
|
|
615
|
-
}
|
|
616
|
-
function CodexThreadRenameDialog({ client, onOpenChange, onRenamed, open, thread, }) {
|
|
617
|
-
const [error, setError] = useState(null);
|
|
618
|
-
const [name, setName] = useState("");
|
|
619
|
-
const [saving, setSaving] = useState(false);
|
|
620
|
-
useEffect(() => {
|
|
621
|
-
if (!thread)
|
|
622
|
-
return;
|
|
623
|
-
setName(codexThreadTitle(thread));
|
|
624
|
-
setError(null);
|
|
625
|
-
setSaving(false);
|
|
626
|
-
}, [thread]);
|
|
627
|
-
const submit = async (event) => {
|
|
628
|
-
event.preventDefault();
|
|
629
|
-
if (!client || !thread)
|
|
630
|
-
return;
|
|
631
|
-
const trimmedName = name.trim();
|
|
632
|
-
if (!trimmedName) {
|
|
633
|
-
setError(new Error("Enter a thread name."));
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
if (trimmedName === codexThreadTitle(thread)) {
|
|
637
|
-
onOpenChange(false);
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
setSaving(true);
|
|
641
|
-
setError(null);
|
|
642
|
-
try {
|
|
643
|
-
await client.setThreadName({
|
|
644
|
-
name: trimmedName,
|
|
645
|
-
threadId: thread.id,
|
|
646
|
-
});
|
|
647
|
-
onRenamed(thread.id, trimmedName);
|
|
648
|
-
onOpenChange(false);
|
|
649
|
-
}
|
|
650
|
-
catch (nextError) {
|
|
651
|
-
setError(nextError instanceof Error ? nextError : new Error(String(nextError)));
|
|
652
|
-
}
|
|
653
|
-
finally {
|
|
654
|
-
setSaving(false);
|
|
655
|
-
}
|
|
656
|
-
};
|
|
657
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsx(DialogContent, { children: _jsxs("form", { className: "grid gap-4", onSubmit: submit, children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Rename thread" }), _jsx(DialogDescription, { children: "Set a user-facing title for this Codex thread." })] }), _jsxs("label", { className: "grid gap-1.5 text-[12px] font-medium text-fg-tertiary", children: ["Thread name", _jsx(Input, { "aria-label": "Thread name", autoFocus: true, disabled: saving, onChange: (event) => setName(event.target.value), value: name })] }), error ? (_jsx("div", { className: "rounded-md border border-destructive/30 bg-destructive/8 px-3 py-2 text-[12px] text-fg", children: error.message })) : null, _jsxs(DialogFooter, { children: [_jsx(Button, { disabled: saving, onClick: () => onOpenChange(false), type: "button", variant: "outline", children: "Cancel" }), _jsx(Button, { disabled: saving || name.trim().length === 0, type: "submit", children: "Save" })] })] }) }) }));
|
|
658
|
-
}
|
|
659
|
-
export function CodexThreadStats({ snapshot, }) {
|
|
660
|
-
const summary = summarizeCodexThread(snapshot);
|
|
661
|
-
return (_jsxs("div", { className: "grid gap-3 md:grid-cols-4", children: [_jsx(CodexStat, { label: "Turns", value: summary.turnCount }), _jsx(CodexStat, { label: "Commands", value: summary.commandCount }), _jsx(CodexStat, { label: "Changes", value: summary.fileChangeCount }), _jsx(CodexStat, { label: "Pending", value: summary.pendingRequestCount })] }));
|
|
662
|
-
}
|
|
663
|
-
function CodexStat({ label, value }) {
|
|
664
|
-
return (_jsxs("div", { className: "rounded-[10px] border border-border bg-bg-card px-4 py-[14px]", children: [_jsx("p", { className: "mb-1.5 text-[11px] font-medium tracking-[0.06em] text-fg-tertiary uppercase", children: label }), _jsx("p", { className: "font-mono text-[22px] leading-[1.2] font-semibold text-fg", children: value })] }));
|
|
665
|
-
}
|
|
666
|
-
function totalDiffLines(file) {
|
|
667
|
-
return file.hunks.reduce((count, hunk) => count + hunk.changes.length, 0);
|
|
668
|
-
}
|
|
2
|
+
export { CodexThreadComposer, CodexThreadWorkspace } from "./thread-workspace.js";
|
|
3
|
+
export { CodexThreadSidebar } from "./thread-sidebar.js";
|
|
4
|
+
export { CodexThreadInspector, CodexThreadStats } from "./thread-inspector.js";
|
|
5
|
+
export { CodexTurnControls } from "./turn-controls.js";
|
|
6
|
+
export { CodexAnsiText, CodexAssistantText, CodexCommandBlock, CodexFileChangeBlock, CodexHighlightedCode, CodexMarkdownText, CodexPendingRequestLine, CodexPlanText, CodexReasoningBlock, CodexThreadTranscript, CodexToolCallText, CodexUnifiedDiff, CodexUserMessageBubble, } from "./transcript-renderers.js";
|
|
669
7
|
//# sourceMappingURL=transcript-react.js.map
|