remcodex 0.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/server/src/app.js +186 -0
- package/dist/server/src/cli.js +270 -0
- package/dist/server/src/controllers/codex-options.controller.js +199 -0
- package/dist/server/src/controllers/message.controller.js +21 -0
- package/dist/server/src/controllers/project.controller.js +44 -0
- package/dist/server/src/controllers/session.controller.js +175 -0
- package/dist/server/src/db/client.js +10 -0
- package/dist/server/src/db/migrations.js +32 -0
- package/dist/server/src/gateways/ws.gateway.js +60 -0
- package/dist/server/src/services/codex-app-server-runner.js +363 -0
- package/dist/server/src/services/codex-exec-runner.js +147 -0
- package/dist/server/src/services/codex-rollout-sync.js +977 -0
- package/dist/server/src/services/codex-runner.js +11 -0
- package/dist/server/src/services/codex-stream-events.js +478 -0
- package/dist/server/src/services/event-store.js +328 -0
- package/dist/server/src/services/project-manager.js +130 -0
- package/dist/server/src/services/pty-runner.js +72 -0
- package/dist/server/src/services/session-manager.js +1586 -0
- package/dist/server/src/services/session-timeline-service.js +181 -0
- package/dist/server/src/types/codex-launch.js +2 -0
- package/dist/server/src/types/models.js +37 -0
- package/dist/server/src/utils/ansi.js +143 -0
- package/dist/server/src/utils/codex-launch.js +102 -0
- package/dist/server/src/utils/codex-quota.js +179 -0
- package/dist/server/src/utils/codex-status.js +163 -0
- package/dist/server/src/utils/codex-ui-options.js +114 -0
- package/dist/server/src/utils/command.js +46 -0
- package/dist/server/src/utils/errors.js +16 -0
- package/dist/server/src/utils/ids.js +7 -0
- package/dist/server/src/utils/node-pty.js +29 -0
- package/package.json +36 -0
- package/scripts/fix-node-pty-helper.js +36 -0
- package/web/api.js +175 -0
- package/web/app.js +8082 -0
- package/web/components/composer.js +627 -0
- package/web/components/session-workbench.js +173 -0
- package/web/i18n/index.js +171 -0
- package/web/i18n/locales/de.js +50 -0
- package/web/i18n/locales/en.js +320 -0
- package/web/i18n/locales/es.js +50 -0
- package/web/i18n/locales/fr.js +50 -0
- package/web/i18n/locales/ja.js +50 -0
- package/web/i18n/locales/ko.js +50 -0
- package/web/i18n/locales/pt-BR.js +50 -0
- package/web/i18n/locales/ru.js +50 -0
- package/web/i18n/locales/zh-CN.js +320 -0
- package/web/i18n/locales/zh-Hant.js +53 -0
- package/web/index.html +23 -0
- package/web/message-rich-text.js +218 -0
- package/web/session-command-activity.js +980 -0
- package/web/session-event-adapter.js +826 -0
- package/web/session-timeline-reducer.js +728 -0
- package/web/session-timeline-renderer.js +656 -0
- package/web/session-ws.js +31 -0
- package/web/styles.css +5665 -0
- package/web/vendor/markdown-it.js +6969 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { getCodexHosts, getCodexQuota } from "../api.js";
|
|
2
|
+
import { t } from "../i18n/index.js";
|
|
3
|
+
|
|
4
|
+
const CODEX_LAUNCH_STORAGE_KEY = "remote-agent-console.codexLaunch.v1";
|
|
5
|
+
|
|
6
|
+
export const CLIENT_FALLBACK_CODEX_UI_OPTIONS = {
|
|
7
|
+
models: [
|
|
8
|
+
{ id: "gpt-5.4", label: "gpt-5.4" },
|
|
9
|
+
{ id: "gpt-5.4-mini", label: "GPT-5.4-Mini" },
|
|
10
|
+
{ id: "gpt-5.3-codex", label: "gpt-5.3-codex" },
|
|
11
|
+
{ id: "gpt-5.2-codex", label: "gpt-5.2-codex" },
|
|
12
|
+
{ id: "gpt-5.2", label: "gpt-5.2" },
|
|
13
|
+
{ id: "gpt-5.1-codex-max", label: "gpt-5.1-codex-max" },
|
|
14
|
+
{ id: "gpt-5.1-codex-mini", label: "gpt-5.1-codex-mini" },
|
|
15
|
+
],
|
|
16
|
+
reasoningLevels: [
|
|
17
|
+
{ id: "low", label: "low", launch: { reasoningEffort: "low" } },
|
|
18
|
+
{ id: "medium", label: "medium", launch: { reasoningEffort: "medium" } },
|
|
19
|
+
{ id: "high", label: "high", launch: { reasoningEffort: "high" } },
|
|
20
|
+
{ id: "xhigh", label: "xhigh", launch: { reasoningEffort: "xhigh" } },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function getReasoningLabel(id, fallback = "") {
|
|
25
|
+
if (id === "low") {
|
|
26
|
+
return t("composer.reasoning.low");
|
|
27
|
+
}
|
|
28
|
+
if (id === "medium") {
|
|
29
|
+
return t("composer.reasoning.medium");
|
|
30
|
+
}
|
|
31
|
+
if (id === "high") {
|
|
32
|
+
return t("composer.reasoning.high");
|
|
33
|
+
}
|
|
34
|
+
if (id === "xhigh") {
|
|
35
|
+
return t("composer.reasoning.xhigh");
|
|
36
|
+
}
|
|
37
|
+
return fallback || id || "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let activeComposerContext = null;
|
|
41
|
+
let selectMeasureEl = null;
|
|
42
|
+
|
|
43
|
+
function positionComposerEnvironmentPopover() {
|
|
44
|
+
const popover = document.querySelector(".toolbar-host-wrap .input-popover");
|
|
45
|
+
const toggle = document.querySelector("#composer-env-toggle");
|
|
46
|
+
if (!(popover instanceof HTMLElement) || !(toggle instanceof HTMLElement)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;
|
|
51
|
+
const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
|
|
52
|
+
const margin = 8;
|
|
53
|
+
const gap = 8;
|
|
54
|
+
const maxWidth = Math.min(352, Math.max(220, viewportWidth - margin * 2));
|
|
55
|
+
|
|
56
|
+
popover.style.width = `${maxWidth}px`;
|
|
57
|
+
popover.style.maxWidth = `${maxWidth}px`;
|
|
58
|
+
popover.style.position = "fixed";
|
|
59
|
+
popover.style.left = "0";
|
|
60
|
+
popover.style.right = "auto";
|
|
61
|
+
popover.style.bottom = "auto";
|
|
62
|
+
popover.style.transform = "none";
|
|
63
|
+
|
|
64
|
+
const toggleRect = toggle.getBoundingClientRect();
|
|
65
|
+
const popoverRect = popover.getBoundingClientRect();
|
|
66
|
+
const width = Math.min(maxWidth, Math.ceil(popoverRect.width || maxWidth));
|
|
67
|
+
const height = Math.ceil(popoverRect.height || 0);
|
|
68
|
+
|
|
69
|
+
let left = toggleRect.right - width;
|
|
70
|
+
left = Math.max(margin, Math.min(left, viewportWidth - width - margin));
|
|
71
|
+
|
|
72
|
+
let top = toggleRect.top - height - gap;
|
|
73
|
+
if (top < margin) {
|
|
74
|
+
top = Math.min(viewportHeight - height - margin, toggleRect.bottom + gap);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
popover.style.left = `${Math.max(margin, left)}px`;
|
|
78
|
+
popover.style.top = `${Math.max(margin, top)}px`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function escapeHtml(value) {
|
|
82
|
+
return String(value ?? "")
|
|
83
|
+
.replaceAll("&", "&")
|
|
84
|
+
.replaceAll("<", "<")
|
|
85
|
+
.replaceAll(">", ">")
|
|
86
|
+
.replaceAll('"', """)
|
|
87
|
+
.replaceAll("'", "'");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function renderComposerSlashMenuItem(item, active) {
|
|
91
|
+
const hint = item.hint ? `<span class="composer-slash-item-hint">${escapeHtml(item.hint)}</span>` : "";
|
|
92
|
+
return `
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
class="composer-slash-item ${active ? "composer-slash-item--active" : ""}"
|
|
96
|
+
data-slash-command-id="${escapeHtml(item.id)}"
|
|
97
|
+
${item.enabled ? "" : 'disabled aria-disabled="true"'}
|
|
98
|
+
>
|
|
99
|
+
<span class="composer-slash-item-main">
|
|
100
|
+
<span class="composer-slash-item-name">${escapeHtml(item.slash)}</span>
|
|
101
|
+
<span class="composer-slash-item-title">${escapeHtml(item.title)}</span>
|
|
102
|
+
</span>
|
|
103
|
+
<span class="composer-slash-item-desc">${escapeHtml(item.description)}</span>
|
|
104
|
+
${hint}
|
|
105
|
+
</button>
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function renderComposerSlashMenu(detailState, items = []) {
|
|
110
|
+
if (!detailState?.slashMenuOpen) {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (detailState.slashCommandsLoading) {
|
|
115
|
+
return `
|
|
116
|
+
<div class="composer-slash-menu" role="listbox" aria-label="${escapeHtml(t("composer.slashMenu"))}">
|
|
117
|
+
<div class="composer-slash-empty">${escapeHtml(t("composer.slashLoading"))}</div>
|
|
118
|
+
</div>
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!items.length) {
|
|
123
|
+
return `
|
|
124
|
+
<div class="composer-slash-menu" role="listbox" aria-label="${escapeHtml(t("composer.slashMenu"))}">
|
|
125
|
+
<div class="composer-slash-empty">${escapeHtml(t("composer.slashEmpty"))}</div>
|
|
126
|
+
</div>
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return `
|
|
131
|
+
<div class="composer-slash-menu" role="listbox" aria-label="${escapeHtml(t("composer.slashMenu"))}">
|
|
132
|
+
${items.map((item, index) => renderComposerSlashMenuItem(item, index === detailState.slashActiveIndex)).join("")}
|
|
133
|
+
</div>
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function ensureSelectMeasureEl() {
|
|
138
|
+
if (selectMeasureEl) {
|
|
139
|
+
return selectMeasureEl;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
selectMeasureEl = document.createElement("span");
|
|
143
|
+
selectMeasureEl.style.position = "absolute";
|
|
144
|
+
selectMeasureEl.style.visibility = "hidden";
|
|
145
|
+
selectMeasureEl.style.pointerEvents = "none";
|
|
146
|
+
selectMeasureEl.style.whiteSpace = "nowrap";
|
|
147
|
+
selectMeasureEl.style.left = "-9999px";
|
|
148
|
+
selectMeasureEl.style.top = "-9999px";
|
|
149
|
+
document.body.appendChild(selectMeasureEl);
|
|
150
|
+
return selectMeasureEl;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function syncSelectWidth(el) {
|
|
154
|
+
if (!(el instanceof HTMLSelectElement)) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const measureEl = ensureSelectMeasureEl();
|
|
159
|
+
const computed = window.getComputedStyle(el);
|
|
160
|
+
const selectedText = el.options[el.selectedIndex]?.text?.trim() || "";
|
|
161
|
+
|
|
162
|
+
measureEl.style.fontFamily = computed.fontFamily;
|
|
163
|
+
measureEl.style.fontSize = computed.fontSize;
|
|
164
|
+
measureEl.style.fontWeight = computed.fontWeight;
|
|
165
|
+
measureEl.style.fontStyle = computed.fontStyle;
|
|
166
|
+
measureEl.style.fontVariant = computed.fontVariant;
|
|
167
|
+
measureEl.style.letterSpacing = computed.letterSpacing;
|
|
168
|
+
measureEl.style.textTransform = computed.textTransform;
|
|
169
|
+
measureEl.textContent = selectedText;
|
|
170
|
+
|
|
171
|
+
const textWidth = Math.ceil(measureEl.getBoundingClientRect().width);
|
|
172
|
+
const paddingLeft = Number.parseFloat(computed.paddingLeft) || 0;
|
|
173
|
+
const paddingRight = Number.parseFloat(computed.paddingRight) || 0;
|
|
174
|
+
const borderLeft = Number.parseFloat(computed.borderLeftWidth) || 0;
|
|
175
|
+
const borderRight = Number.parseFloat(computed.borderRightWidth) || 0;
|
|
176
|
+
const arrowAllowance = 18;
|
|
177
|
+
const nextWidth = Math.ceil(
|
|
178
|
+
textWidth + paddingLeft + paddingRight + borderLeft + borderRight + arrowAllowance,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
el.style.width = `${nextWidth}px`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function renderComposerQuotaPopover(detailState) {
|
|
185
|
+
const quota = detailState.codexQuota?.quota;
|
|
186
|
+
const hourPercent =
|
|
187
|
+
typeof quota?.hour?.percent === "number" && Number.isFinite(quota.hour.percent)
|
|
188
|
+
? `${quota.hour.percent}%`
|
|
189
|
+
: "--";
|
|
190
|
+
const hourRemain =
|
|
191
|
+
typeof quota?.hour?.remainTime === "string" && quota.hour.remainTime.trim()
|
|
192
|
+
? quota.hour.remainTime.trim()
|
|
193
|
+
: "--";
|
|
194
|
+
const weekPercent =
|
|
195
|
+
typeof quota?.week?.percent === "number" && Number.isFinite(quota.week.percent)
|
|
196
|
+
? `${quota.week.percent}%`
|
|
197
|
+
: "--";
|
|
198
|
+
const weekReset =
|
|
199
|
+
typeof quota?.week?.resetDate === "string" && quota.week.resetDate.trim()
|
|
200
|
+
? quota.week.resetDate.trim()
|
|
201
|
+
: "--";
|
|
202
|
+
const summary = `${hourPercent} / ${weekPercent}`;
|
|
203
|
+
|
|
204
|
+
return `
|
|
205
|
+
<details class="input-popover-quota">
|
|
206
|
+
<summary class="input-popover-quota-summary">
|
|
207
|
+
<span>${escapeHtml(t("composer.quota.remaining"))}</span>
|
|
208
|
+
<span class="input-popover-quota-summary-value">${escapeHtml(summary)}</span>
|
|
209
|
+
</summary>
|
|
210
|
+
<div class="input-popover-quota-body">
|
|
211
|
+
<p class="input-popover-quota-line">${escapeHtml(t("composer.quota.hours", { percent: hourPercent, remain: hourRemain }))}</p>
|
|
212
|
+
<p class="input-popover-quota-line">${escapeHtml(t("composer.quota.week", { percent: weekPercent, reset: weekReset }))}</p>
|
|
213
|
+
</div>
|
|
214
|
+
</details>
|
|
215
|
+
`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function ensureDetailCodexLaunch(detailState, uiOptions) {
|
|
219
|
+
const current = detailState.codexLaunch ?? loadCodexLaunchPrefs();
|
|
220
|
+
const normalized = normalizeCodexLaunchAgainstUi(current, uiOptions);
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
!detailState.codexLaunch ||
|
|
224
|
+
detailState.codexLaunch.modelId !== normalized.modelId ||
|
|
225
|
+
detailState.codexLaunch.reasoningId !== normalized.reasoningId ||
|
|
226
|
+
detailState.codexLaunch.profile !== normalized.profile
|
|
227
|
+
) {
|
|
228
|
+
detailState.codexLaunch = normalized;
|
|
229
|
+
persistCodexLaunchPrefs(normalized);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return detailState.codexLaunch;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function renderComposerEnvironmentPopover(detailState) {
|
|
236
|
+
const hosts = Array.isArray(detailState.remoteHosts) ? detailState.remoteHosts : [];
|
|
237
|
+
const activeHost = detailState.activeRemoteHost || "";
|
|
238
|
+
|
|
239
|
+
return `
|
|
240
|
+
<div class="input-popover" role="menu" aria-label="${escapeHtml(t("composer.environment"))}">
|
|
241
|
+
<div class="input-popover-group">
|
|
242
|
+
${
|
|
243
|
+
hosts.length > 0
|
|
244
|
+
? hosts
|
|
245
|
+
.map(
|
|
246
|
+
(host) => `
|
|
247
|
+
<button
|
|
248
|
+
type="button"
|
|
249
|
+
class="input-popover-item ${activeHost === host ? "input-popover-item--active" : ""}"
|
|
250
|
+
data-remote-host="${escapeHtml(host)}"
|
|
251
|
+
>
|
|
252
|
+
<span class="input-popover-item-check">${activeHost === host ? "✓" : ""}</span>
|
|
253
|
+
<span class="input-popover-item-label">${escapeHtml(host)}</span>
|
|
254
|
+
</button>
|
|
255
|
+
`,
|
|
256
|
+
)
|
|
257
|
+
.join("")
|
|
258
|
+
: `<div class="input-popover-item-label">${escapeHtml(t("composer.unsynced"))}</div>`
|
|
259
|
+
}
|
|
260
|
+
</div>
|
|
261
|
+
<div class="input-popover-divider"></div>
|
|
262
|
+
${renderComposerQuotaPopover(detailState)}
|
|
263
|
+
</div>
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function setActiveRemoteHost(detailState, host) {
|
|
268
|
+
detailState.activeRemoteHost = String(host || "").trim();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function closeComposerEnvironmentMenu(detailState, onRender) {
|
|
272
|
+
if (!detailState.composerEnvironmentMenuOpen) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
detailState.composerEnvironmentMenuOpen = false;
|
|
277
|
+
onRender();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function ensureComposerEnvironmentGlobalListeners() {
|
|
281
|
+
if (window.__composerEnvironmentListenersBound) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
window.__composerEnvironmentListenersBound = true;
|
|
286
|
+
|
|
287
|
+
document.addEventListener("click", (event) => {
|
|
288
|
+
const context = activeComposerContext;
|
|
289
|
+
if (!context?.detailState?.composerEnvironmentMenuOpen) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
294
|
+
if (!target) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (target.closest(".input-container")) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
closeComposerEnvironmentMenu(context.detailState, context.onRender);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
document.addEventListener("keydown", (event) => {
|
|
306
|
+
const context = activeComposerContext;
|
|
307
|
+
if (event.key === "Escape" && context?.detailState?.composerEnvironmentMenuOpen) {
|
|
308
|
+
closeComposerEnvironmentMenu(context.detailState, context.onRender);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function bindComposerMetaControls(detailState) {
|
|
314
|
+
ensureDetailCodexLaunch(detailState, detailState.codexUiOptions);
|
|
315
|
+
|
|
316
|
+
document.querySelectorAll("[data-codex-pref]").forEach((el) => {
|
|
317
|
+
const key = el.getAttribute("data-codex-pref");
|
|
318
|
+
if (!key || (key !== "modelId" && key !== "reasoningId" && key !== "profile")) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const onChange = () => {
|
|
323
|
+
detailState.codexLaunch[key] = el.value;
|
|
324
|
+
persistCodexLaunchPrefs(detailState.codexLaunch);
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
if (el.tagName === "SELECT") {
|
|
328
|
+
syncSelectWidth(el);
|
|
329
|
+
window.requestAnimationFrame(() => syncSelectWidth(el));
|
|
330
|
+
if (document.fonts?.ready) {
|
|
331
|
+
document.fonts.ready.then(() => syncSelectWidth(el)).catch(() => {});
|
|
332
|
+
}
|
|
333
|
+
el.onchange = () => {
|
|
334
|
+
onChange();
|
|
335
|
+
syncSelectWidth(el);
|
|
336
|
+
};
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
el.oninput = onChange;
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function bindComposerEnvironmentControls(detailState, onRender) {
|
|
345
|
+
ensureComposerEnvironmentGlobalListeners();
|
|
346
|
+
|
|
347
|
+
const toggle = document.querySelector("#composer-env-toggle");
|
|
348
|
+
if (toggle) {
|
|
349
|
+
toggle.onclick = async () => {
|
|
350
|
+
const nextOpen = !detailState.composerEnvironmentMenuOpen;
|
|
351
|
+
detailState.composerEnvironmentMenuOpen = nextOpen;
|
|
352
|
+
|
|
353
|
+
if (nextOpen) {
|
|
354
|
+
if (!detailState.codexQuota && detailState.session?.sessionId) {
|
|
355
|
+
try {
|
|
356
|
+
detailState.codexQuota = await getCodexQuota(detailState.session.sessionId);
|
|
357
|
+
} catch {
|
|
358
|
+
detailState.codexQuota = null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const hostResult = await getCodexHosts();
|
|
364
|
+
detailState.remoteHosts = Array.isArray(hostResult?.hosts)
|
|
365
|
+
? hostResult.hosts.filter((item) => typeof item === "string" && item.trim())
|
|
366
|
+
: [];
|
|
367
|
+
detailState.activeRemoteHost =
|
|
368
|
+
typeof hostResult?.activeHost === "string" && hostResult.activeHost.trim()
|
|
369
|
+
? hostResult.activeHost.trim()
|
|
370
|
+
: (detailState.remoteHosts[0] || "");
|
|
371
|
+
} catch {
|
|
372
|
+
detailState.remoteHosts = [];
|
|
373
|
+
detailState.activeRemoteHost = "";
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
onRender();
|
|
378
|
+
window.requestAnimationFrame(() => positionComposerEnvironmentPopover());
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
document.querySelectorAll("[data-remote-host]").forEach((el) => {
|
|
383
|
+
el.onclick = () => {
|
|
384
|
+
const next = el.getAttribute("data-remote-host");
|
|
385
|
+
if (!next) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
setActiveRemoteHost(detailState, next);
|
|
390
|
+
detailState.composerEnvironmentMenuOpen = false;
|
|
391
|
+
onRender();
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function isBusyStatus(status) {
|
|
397
|
+
return ["starting", "running", "stopping"].includes(status);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function isSessionComposerBusy(session) {
|
|
401
|
+
if (!session) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (session.sourceKind === "imported_rollout") {
|
|
406
|
+
return Boolean(session.liveBusy);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return isBusyStatus(session.status);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function defaultCodexLaunch() {
|
|
413
|
+
return {
|
|
414
|
+
modelId: "",
|
|
415
|
+
reasoningId: "medium",
|
|
416
|
+
profile: "",
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function loadCodexLaunchPrefs() {
|
|
421
|
+
try {
|
|
422
|
+
const raw = window.localStorage?.getItem(CODEX_LAUNCH_STORAGE_KEY);
|
|
423
|
+
if (!raw) {
|
|
424
|
+
return defaultCodexLaunch();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const parsed = JSON.parse(raw);
|
|
428
|
+
const base = defaultCodexLaunch();
|
|
429
|
+
|
|
430
|
+
let modelId = typeof parsed.modelId === "string" ? parsed.modelId : "";
|
|
431
|
+
let reasoningId = typeof parsed.reasoningId === "string" ? parsed.reasoningId : "";
|
|
432
|
+
|
|
433
|
+
if (!modelId && typeof parsed.model === "string" && parsed.model.trim()) {
|
|
434
|
+
modelId = parsed.model.trim();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!reasoningId && typeof parsed.speed === "string") {
|
|
438
|
+
if (parsed.speed === "fast") {
|
|
439
|
+
reasoningId = "low";
|
|
440
|
+
} else if (parsed.speed === "deep") {
|
|
441
|
+
reasoningId = "high";
|
|
442
|
+
} else {
|
|
443
|
+
reasoningId = "medium";
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
...base,
|
|
449
|
+
modelId,
|
|
450
|
+
reasoningId: reasoningId || "medium",
|
|
451
|
+
profile: typeof parsed.profile === "string" ? parsed.profile : "",
|
|
452
|
+
};
|
|
453
|
+
} catch {
|
|
454
|
+
return defaultCodexLaunch();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function normalizeCodexLaunchAgainstUi(prefs, uiOptions) {
|
|
459
|
+
const opts =
|
|
460
|
+
uiOptions && Array.isArray(uiOptions.models) && uiOptions.models.length > 0
|
|
461
|
+
? uiOptions
|
|
462
|
+
: CLIENT_FALLBACK_CODEX_UI_OPTIONS;
|
|
463
|
+
const modelIds = new Set(opts.models.map((m) => m.id));
|
|
464
|
+
const reasoningIds = new Set(opts.reasoningLevels.map((r) => r.id));
|
|
465
|
+
let { modelId, reasoningId } = prefs;
|
|
466
|
+
if (!modelId || !modelIds.has(modelId)) {
|
|
467
|
+
modelId = opts.models[0]?.id || "";
|
|
468
|
+
}
|
|
469
|
+
if (!reasoningId || !reasoningIds.has(reasoningId)) {
|
|
470
|
+
reasoningId = "medium";
|
|
471
|
+
}
|
|
472
|
+
return { ...prefs, modelId, reasoningId };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function persistCodexLaunchPrefs(prefs) {
|
|
476
|
+
try {
|
|
477
|
+
window.localStorage?.setItem(CODEX_LAUNCH_STORAGE_KEY, JSON.stringify(prefs));
|
|
478
|
+
} catch {
|
|
479
|
+
/* ignore */
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function buildCodexLaunchPayload(launch, uiOptions) {
|
|
484
|
+
if (!launch) {
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const opts =
|
|
489
|
+
uiOptions && Array.isArray(uiOptions.models) && uiOptions.models.length > 0
|
|
490
|
+
? uiOptions
|
|
491
|
+
: CLIENT_FALLBACK_CODEX_UI_OPTIONS;
|
|
492
|
+
|
|
493
|
+
const codex = {};
|
|
494
|
+
const modelId = String(launch.modelId || "").trim();
|
|
495
|
+
if (modelId) {
|
|
496
|
+
codex.model = modelId;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const profile = String(launch.profile || "").trim();
|
|
500
|
+
if (profile) {
|
|
501
|
+
codex.profile = profile;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const level = opts.reasoningLevels.find((r) => r.id === launch.reasoningId);
|
|
505
|
+
const reasoningEffort = level?.launch?.reasoningEffort;
|
|
506
|
+
if (
|
|
507
|
+
reasoningEffort === "low" ||
|
|
508
|
+
reasoningEffort === "medium" ||
|
|
509
|
+
reasoningEffort === "high" ||
|
|
510
|
+
reasoningEffort === "xhigh"
|
|
511
|
+
) {
|
|
512
|
+
codex.reasoningEffort = reasoningEffort;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return Object.keys(codex).length ? codex : undefined;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function renderComposerInput({ session, detailState, uiOptions }) {
|
|
519
|
+
const opts =
|
|
520
|
+
uiOptions && Array.isArray(uiOptions.models) && uiOptions.models.length > 0
|
|
521
|
+
? uiOptions
|
|
522
|
+
: CLIENT_FALLBACK_CODEX_UI_OPTIONS;
|
|
523
|
+
const launch = ensureDetailCodexLaunch(detailState, opts);
|
|
524
|
+
const modelOptionsHtml = opts.models
|
|
525
|
+
.map(
|
|
526
|
+
(model) =>
|
|
527
|
+
`<option value="${escapeHtml(model.id)}" ${launch.modelId === model.id ? "selected" : ""}>${escapeHtml(model.label)}</option>`,
|
|
528
|
+
)
|
|
529
|
+
.join("");
|
|
530
|
+
const reasoningOptionsHtml = opts.reasoningLevels
|
|
531
|
+
.map(
|
|
532
|
+
(level) =>
|
|
533
|
+
`<option value="${escapeHtml(level.id)}" ${launch.reasoningId === level.id ? "selected" : ""}>${escapeHtml(getReasoningLabel(level.id, level.label))}</option>`,
|
|
534
|
+
)
|
|
535
|
+
.join("");
|
|
536
|
+
const activeHost = detailState.activeRemoteHost || "--";
|
|
537
|
+
const isBusy = isSessionComposerBusy(session);
|
|
538
|
+
|
|
539
|
+
return `
|
|
540
|
+
<div class="input-container ${isBusy ? "input-container--busy" : ""}">
|
|
541
|
+
<textarea
|
|
542
|
+
name="content"
|
|
543
|
+
class="input-area"
|
|
544
|
+
rows="1"
|
|
545
|
+
placeholder="${escapeHtml(t("composer.placeholder"))}"
|
|
546
|
+
required
|
|
547
|
+
autocomplete="off"
|
|
548
|
+
autocorrect="on"
|
|
549
|
+
aria-label="${escapeHtml(t("composer.aria.message"))}"
|
|
550
|
+
>${escapeHtml(detailState.draft)}</textarea>
|
|
551
|
+
<div id="composer-slash-slot"></div>
|
|
552
|
+
<div class="toolbar">
|
|
553
|
+
<div class="toolbar-center">
|
|
554
|
+
<select class="toolbar-select" data-codex-pref="modelId" aria-label="${escapeHtml(t("composer.aria.model"))}">
|
|
555
|
+
${modelOptionsHtml}
|
|
556
|
+
</select>
|
|
557
|
+
<select class="toolbar-select" data-codex-pref="reasoningId" aria-label="${escapeHtml(t("composer.aria.reasoning"))}">
|
|
558
|
+
${reasoningOptionsHtml}
|
|
559
|
+
</select>
|
|
560
|
+
<div class="toolbar-host-wrap">
|
|
561
|
+
<button
|
|
562
|
+
type="button"
|
|
563
|
+
id="composer-env-toggle"
|
|
564
|
+
class="toolbar-env"
|
|
565
|
+
aria-haspopup="menu"
|
|
566
|
+
aria-expanded="${detailState.composerEnvironmentMenuOpen ? "true" : "false"}"
|
|
567
|
+
>
|
|
568
|
+
<span class="toolbar-env-label">${escapeHtml(activeHost)}</span>
|
|
569
|
+
</button>
|
|
570
|
+
${
|
|
571
|
+
detailState.composerEnvironmentMenuOpen
|
|
572
|
+
? renderComposerEnvironmentPopover(detailState)
|
|
573
|
+
: ""
|
|
574
|
+
}
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
<div class="toolbar-right">
|
|
578
|
+
<button
|
|
579
|
+
type="button"
|
|
580
|
+
id="composer-action"
|
|
581
|
+
class="composer-action-fab ${isBusy ? "composer-action-fab--stop" : "composer-action-fab--send"}"
|
|
582
|
+
aria-label="${escapeHtml(isBusy ? t("composer.aria.stop") : t("composer.aria.send"))}"
|
|
583
|
+
>
|
|
584
|
+
${
|
|
585
|
+
isBusy
|
|
586
|
+
? `<svg class="composer-action-icon" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true">
|
|
587
|
+
<rect x="7" y="7" width="10" height="10" rx="1.5" fill="currentColor" />
|
|
588
|
+
</svg>`
|
|
589
|
+
: `<svg class="composer-action-icon" width="20" height="20" viewBox="0 0 24 24" aria-hidden="true">
|
|
590
|
+
<path
|
|
591
|
+
fill="currentColor"
|
|
592
|
+
d="M3.4 20.4 21 12 3.4 3.6l-.8 6.2 12.2 2.2-12.2 2.2.8 6.2Z"
|
|
593
|
+
/>
|
|
594
|
+
</svg>`
|
|
595
|
+
}
|
|
596
|
+
</button>
|
|
597
|
+
</div>
|
|
598
|
+
</div>
|
|
599
|
+
</div>
|
|
600
|
+
`;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export function bindComposerInputControls({ detailState, onRender }) {
|
|
604
|
+
activeComposerContext = { detailState, onRender };
|
|
605
|
+
bindComposerMetaControls(detailState);
|
|
606
|
+
bindComposerEnvironmentControls(detailState, onRender);
|
|
607
|
+
if (detailState.composerEnvironmentMenuOpen) {
|
|
608
|
+
window.requestAnimationFrame(() => positionComposerEnvironmentPopover());
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
export function adjustComposerHeight(el) {
|
|
613
|
+
if (!el) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
const computed = window.getComputedStyle(el);
|
|
618
|
+
const lineHeight = Number.parseFloat(computed.lineHeight) || 24;
|
|
619
|
+
const minPx = Math.ceil(lineHeight);
|
|
620
|
+
const maxPx = 176;
|
|
621
|
+
|
|
622
|
+
el.style.overflowY = "hidden";
|
|
623
|
+
el.style.height = "auto";
|
|
624
|
+
const targetHeight = Math.max(minPx, Math.min(el.scrollHeight, maxPx));
|
|
625
|
+
el.style.height = `${targetHeight}px`;
|
|
626
|
+
el.style.overflowY = el.scrollHeight > maxPx ? "auto" : "hidden";
|
|
627
|
+
}
|