@walkhi/code-relax 0.1.0-beta.2 → 0.1.0-beta.21
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/README.md +55 -71
- package/dist/bin/codex-remote.mjs +150 -126
- package/dist/bin/manage-relay-admin.mjs +20 -0
- package/dist/bin/migrate-relay-db.mjs +107 -0
- package/dist/bin/self-relay-admin.mjs +8 -0
- package/dist/bin/self-relay-demo.mjs +2 -2
- package/dist/bin/self-relay-server.mjs +18 -10
- package/dist/shared/app-server-events.cjs +42 -15
- package/dist/shared/file-mentions.cjs +35 -0
- package/dist/shared/server-requests.cjs +86 -58
- package/dist/skills/code-relax/SKILL.md +7 -6
- package/dist/src/desktop-launcher.mjs +16 -21
- package/dist/src/file-upload-session.mjs +124 -0
- package/dist/src/lan-socket-server.mjs +52 -9
- package/dist/src/managed-app-server.mjs +44 -13
- package/dist/src/message-images.mjs +0 -18
- package/dist/src/onboarding.mjs +9 -11
- package/dist/src/platform/README.md +1 -1
- package/dist/src/platform/windows/background-process.mjs +1 -1
- package/dist/src/platform/windows/codex-runtime.mjs +251 -0
- package/dist/src/platform/windows/desktop-host.mjs +17 -3
- package/dist/src/platform/windows/desktop-info.ps1 +2 -4
- package/dist/src/platform/windows/desktop-runtime.ps1 +10 -0
- package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -14
- package/dist/src/platform/windows/launch-worker.mjs +42 -0
- package/dist/src/platform/windows/resident-start.js +20 -0
- package/dist/src/platform/windows/resident-startup.ps1 +76 -20
- package/dist/src/platform/windows/service-host.mjs +4 -16
- package/dist/src/platform/windows/start-hidden-console.ps1 +10 -5
- package/dist/src/platform/windows/stop-desktop.ps1 +7 -4
- package/dist/src/platform/windows/user-environment.mjs +1 -1
- package/dist/src/runtime-discovery.mjs +43 -0
- package/dist/src/self-relay/admin-cloud.mjs +101 -0
- package/dist/src/self-relay/admin-page.mjs +79 -0
- package/dist/src/self-relay/admin-server.mjs +66 -0
- package/dist/src/self-relay/admin-state.mjs +82 -19
- package/dist/src/self-relay/admin-style.mjs +9 -0
- package/dist/src/self-relay/admin-usage-page.mjs +28 -0
- package/dist/src/self-relay/connector.mjs +243 -30
- package/dist/src/self-relay/demo.mjs +3 -4
- package/dist/src/self-relay/email-accounts.mjs +141 -0
- package/dist/src/self-relay/lifecycle.mjs +99 -142
- package/dist/src/self-relay/mysql-state.mjs +104 -0
- package/dist/src/self-relay/p2p-probe.mjs +1 -1
- package/dist/src/self-relay/server.mjs +335 -59
- package/dist/src/self-relay/usage-diagnostics.mjs +46 -0
- package/dist/src/self-relay/usage-meter.mjs +125 -0
- package/dist/src/server.mjs +731 -1439
- package/dist/src/service-control.mjs +4 -4
- package/dist/src/service-doctor.mjs +27 -29
- package/dist/src/service-health.mjs +2 -2
- package/dist/src/service-install.mjs +1 -4
- package/dist/src/service-lifecycle.mjs +19 -19
- package/dist/src/shared-app-server.mjs +277 -270
- package/dist/src/shared-catalog.mjs +1 -11
- package/dist/src/shared-recovery.mjs +11 -9
- package/dist/src/shared-thread-stream.mjs +1 -1
- package/dist/src/skill-install.mjs +0 -13
- package/package.json +17 -18
- package/tools/postinstall.mjs +69 -15
- package/dist/src/app-server-client.mjs +0 -468
- package/dist/src/app-server-tasks.mjs +0 -358
- package/dist/src/debug-ui.mjs +0 -131
- package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
- package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
- package/dist/src/platform/windows/desktop-tools.mjs +0 -48
- package/dist/src/platform/windows/launch-worker.ps1 +0 -20
- package/dist/src/thread-catalog.mjs +0 -47
- package/dist/tools/find-desktop-pipe.mjs +0 -10
- package/dist/web/activity-view.js +0 -283
- package/dist/web/approval-ui.js +0 -188
- package/dist/web/capabilities.js +0 -57
- package/dist/web/chat-transport.js +0 -202
- package/dist/web/chat.css +0 -782
- package/dist/web/chat.js +0 -3830
- package/dist/web/client-platform.js +0 -11
- package/dist/web/community-view.js +0 -20
- package/dist/web/community.css +0 -77
- package/dist/web/community.html +0 -37
- package/dist/web/composer-controller.js +0 -101
- package/dist/web/conversation-controller.js +0 -99
- package/dist/web/disclosure-state-controller.js +0 -95
- package/dist/web/draft-controller.js +0 -103
- package/dist/web/harmony-platform.js +0 -52
- package/dist/web/highlight-powershell.min.js +0 -39
- package/dist/web/highlight.min.js +0 -1262
- package/dist/web/history-cache.js +0 -240
- package/dist/web/history-controller.js +0 -167
- package/dist/web/index.html +0 -325
- package/dist/web/lan-session.js +0 -122
- package/dist/web/link-action-controller.js +0 -212
- package/dist/web/message-send-controller.js +0 -177
- package/dist/web/message-view.js +0 -98
- package/dist/web/p2p-data-channel.js +0 -50
- package/dist/web/p2p-diagnostics.js +0 -28
- package/dist/web/p2p-probe.js +0 -123
- package/dist/web/page-resume.js +0 -28
- package/dist/web/pending-message-store.js +0 -108
- package/dist/web/queue-controller.js +0 -82
- package/dist/web/resources.json +0 -1
- package/dist/web/self-relay-session.js +0 -129
- package/dist/web/server-requests.js +0 -67
- package/dist/web/station-connection-controller.js +0 -75
- package/dist/web/task-list-view.js +0 -296
- package/dist/web/thread-attention-controller.js +0 -124
- package/dist/web/thread-context-controller.js +0 -30
- package/dist/web/thread-list-controller.js +0 -61
- package/dist/web/thread-list-sync.js +0 -86
- package/dist/web/thread-title-controller.js +0 -57
- package/dist/web/timeline-formatters.js +0 -249
- package/dist/web/timeline-reducer.js +0 -81
- package/dist/web/timeline-renderer.js +0 -161
- package/dist/web/timeline-scroll-controller.js +0 -50
- package/dist/web/timeline-sync.js +0 -67
- package/dist/web/usage-controller.js +0 -258
- package/dist/web/vendor/README.md +0 -14
- package/dist/web/vendor/highlight-powershell.min.js +0 -39
- package/dist/web/vendor/highlight.LICENSE.txt +0 -29
- package/dist/web/vendor/highlight.min.js +0 -1262
- package/dist/web/vendor/lucide.LICENSE.txt +0 -17
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
(function (root) {
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
function createLinkActionController(options) {
|
|
5
|
-
const { document, transport, getSelectedThreadId, showToast } = options;
|
|
6
|
-
let menu;
|
|
7
|
-
let outsideHandler;
|
|
8
|
-
let updateInfoButton;
|
|
9
|
-
|
|
10
|
-
function close() {
|
|
11
|
-
if (outsideHandler) document.removeEventListener('pointerdown', outsideHandler, true);
|
|
12
|
-
outsideHandler = null;
|
|
13
|
-
menu?.remove();
|
|
14
|
-
menu = null;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function installCodeRelaxUpdate() {
|
|
18
|
-
if (!root.codexNative?.installCodeRelaxUpdate) {
|
|
19
|
-
showToast('当前客户端不支持安装更新', 3000);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
root.codexNative.installCodeRelaxUpdate();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function downloadReferencedFile(anchor) {
|
|
26
|
-
if (anchor.dataset.downloading === 'true') return;
|
|
27
|
-
anchor.dataset.downloading = 'true';
|
|
28
|
-
const label = anchor.textContent;
|
|
29
|
-
anchor.textContent = '正在下载…';
|
|
30
|
-
showToast('正在下载文件…');
|
|
31
|
-
transport.readFile(getSelectedThreadId(), anchor.dataset.downloadPath).then(file => {
|
|
32
|
-
if (typeof file?.dataBase64 !== 'string') throw new Error('工作站未返回文件内容');
|
|
33
|
-
if (!root.codexNative?.saveDownloadedFile) throw new Error('当前客户端不支持保存文件');
|
|
34
|
-
root.codexNative.saveDownloadedFile(file.name || anchor.dataset.downloadName || '下载文件', file.dataBase64);
|
|
35
|
-
}).catch(error => showToast('下载失败:' + error.message, 3000)).finally(() => {
|
|
36
|
-
anchor.dataset.downloading = 'false';
|
|
37
|
-
anchor.textContent = label;
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function downloadReferencedImage(anchor) {
|
|
42
|
-
if (anchor.dataset.downloading === 'true') return;
|
|
43
|
-
anchor.dataset.downloading = 'true';
|
|
44
|
-
const label = anchor.textContent;
|
|
45
|
-
anchor.textContent = '正在下载…';
|
|
46
|
-
showToast('正在下载原图…');
|
|
47
|
-
try {
|
|
48
|
-
const url = await transport.readImage(getSelectedThreadId(), anchor.dataset.downloadImage, 'original', anchor.dataset.downloadRecovery || '');
|
|
49
|
-
const response = await fetch(url);
|
|
50
|
-
if (!response.ok) throw new Error(`图片读取失败(${response.status})`);
|
|
51
|
-
const blob = await response.blob();
|
|
52
|
-
const bytes = new Uint8Array(await blob.arrayBuffer()); let binary = '';
|
|
53
|
-
for (let offset = 0; offset < bytes.length; offset += 16384) binary += String.fromCharCode(...bytes.subarray(offset, offset + 16384));
|
|
54
|
-
if (!root.codexNative?.saveDownloadedFile) throw new Error('当前客户端不支持保存图片');
|
|
55
|
-
root.codexNative.saveDownloadedFile(anchor.dataset.downloadName || '图片', btoa(binary));
|
|
56
|
-
} catch (error) {
|
|
57
|
-
showToast('下载失败:' + error.message, 3000);
|
|
58
|
-
} finally {
|
|
59
|
-
anchor.dataset.downloading = 'false';
|
|
60
|
-
anchor.textContent = label;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function isCodeRelaxInstallLink(value) {
|
|
65
|
-
try {
|
|
66
|
-
const url = new URL(value);
|
|
67
|
-
const path = url.pathname.replace(/\/$/, '');
|
|
68
|
-
return url.protocol === 'https:' && url.host === 'api.uulife.site'
|
|
69
|
-
&& (path === '/code-relax/install' || path === '/code-relax/releases/latest/manifest.json5');
|
|
70
|
-
} catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function splitFileName(name) {
|
|
76
|
-
const dot = name.lastIndexOf('.');
|
|
77
|
-
return dot > 0 ? [name.slice(0, dot), name.slice(dot)] : [name, ''];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function positionMenu(anchor, nextMenu) {
|
|
81
|
-
document.body.append(nextMenu);
|
|
82
|
-
menu = nextMenu;
|
|
83
|
-
const rect = anchor.getBoundingClientRect();
|
|
84
|
-
const width = nextMenu.offsetWidth;
|
|
85
|
-
const height = nextMenu.offsetHeight;
|
|
86
|
-
nextMenu.style.left = `${Math.max(8, Math.min(rect.left, innerWidth - width - 8))}px`;
|
|
87
|
-
nextMenu.style.top = `${Math.max(8, Math.min(rect.bottom + 6, innerHeight - height - 8))}px`;
|
|
88
|
-
requestAnimationFrame(() => {
|
|
89
|
-
outsideHandler = event => {
|
|
90
|
-
if (nextMenu.contains(event.target) || anchor.contains(event.target)) return;
|
|
91
|
-
close();
|
|
92
|
-
};
|
|
93
|
-
document.addEventListener('pointerdown', outsideHandler, true);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function showFile(anchor) {
|
|
98
|
-
close();
|
|
99
|
-
const nextMenu = document.createElement('div');
|
|
100
|
-
nextMenu.className = 'file-action-menu';
|
|
101
|
-
nextMenu.setAttribute('role', 'menu');
|
|
102
|
-
const fullName = anchor.dataset.downloadName || '下载文件';
|
|
103
|
-
const fileName = document.createElement('div');
|
|
104
|
-
fileName.className = 'file-action-name';
|
|
105
|
-
const [bodyText, extensionText] = splitFileName(fullName);
|
|
106
|
-
const body = document.createElement('span');
|
|
107
|
-
body.className = 'file-action-name-body';
|
|
108
|
-
body.textContent = bodyText;
|
|
109
|
-
const extension = document.createElement('span');
|
|
110
|
-
extension.className = 'file-action-name-extension';
|
|
111
|
-
extension.textContent = extensionText;
|
|
112
|
-
fileName.append(body, extension);
|
|
113
|
-
fileName.title = fullName;
|
|
114
|
-
nextMenu.append(fileName);
|
|
115
|
-
const addAction = (label, install) => {
|
|
116
|
-
const button = document.createElement('button');
|
|
117
|
-
button.type = 'button';
|
|
118
|
-
button.textContent = label;
|
|
119
|
-
button.setAttribute('role', 'menuitem');
|
|
120
|
-
button.onclick = () => {
|
|
121
|
-
close();
|
|
122
|
-
if (install) installCodeRelaxUpdate();
|
|
123
|
-
else downloadReferencedFile(anchor);
|
|
124
|
-
};
|
|
125
|
-
nextMenu.append(button);
|
|
126
|
-
};
|
|
127
|
-
addAction('下载', false);
|
|
128
|
-
if (/^Code[- ]Relax.*\.hap$/i.test(fullName)) addAction('下载并安装', true);
|
|
129
|
-
positionMenu(anchor, nextMenu);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function showWeb(anchor) {
|
|
133
|
-
close();
|
|
134
|
-
const target = anchor.dataset.externalLink;
|
|
135
|
-
const nextMenu = document.createElement('div');
|
|
136
|
-
nextMenu.className = 'file-action-menu';
|
|
137
|
-
nextMenu.setAttribute('role', 'menu');
|
|
138
|
-
const heading = document.createElement('div');
|
|
139
|
-
heading.className = 'file-action-name link-action-url';
|
|
140
|
-
heading.textContent = target;
|
|
141
|
-
heading.title = target;
|
|
142
|
-
nextMenu.append(heading);
|
|
143
|
-
const addAction = (label, action) => {
|
|
144
|
-
const button = document.createElement('button');
|
|
145
|
-
button.type = 'button';
|
|
146
|
-
button.textContent = label;
|
|
147
|
-
button.setAttribute('role', 'menuitem');
|
|
148
|
-
button.onclick = () => {
|
|
149
|
-
close();
|
|
150
|
-
if (action === 'copy' && root.codexNative?.copyText) root.codexNative.copyText(target);
|
|
151
|
-
else if (action === 'open' && root.codexNative?.openExternalLink) root.codexNative.openExternalLink(target);
|
|
152
|
-
else if (action === 'install') installCodeRelaxUpdate();
|
|
153
|
-
else showToast('请先更新客户端后再使用此操作', 3000);
|
|
154
|
-
};
|
|
155
|
-
nextMenu.append(button);
|
|
156
|
-
return button;
|
|
157
|
-
};
|
|
158
|
-
let shouldRequestUpdateInfo = false;
|
|
159
|
-
if (isCodeRelaxInstallLink(target)) {
|
|
160
|
-
updateInfoButton = addAction('安装更新 · 正在获取版本…', 'install');
|
|
161
|
-
shouldRequestUpdateInfo = true;
|
|
162
|
-
}
|
|
163
|
-
addAction('复制链接', 'copy');
|
|
164
|
-
addAction('在系统浏览器中打开', 'open');
|
|
165
|
-
positionMenu(anchor, nextMenu);
|
|
166
|
-
if (shouldRequestUpdateInfo) {
|
|
167
|
-
if (root.codexNative?.requestCodeRelaxUpdateInfo) root.codexNative.requestCodeRelaxUpdateInfo();
|
|
168
|
-
else updateInfoButton.textContent = '安装更新';
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function handleContentClick(event, embedded) {
|
|
173
|
-
const codeReference = event.target.closest('a[data-code-reference]');
|
|
174
|
-
if (codeReference) {
|
|
175
|
-
event.preventDefault();
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
const imageDownload = event.target.closest('a[data-download-image]');
|
|
179
|
-
if (imageDownload) {
|
|
180
|
-
event.preventDefault();
|
|
181
|
-
void downloadReferencedImage(imageDownload);
|
|
182
|
-
return true;
|
|
183
|
-
}
|
|
184
|
-
const download = event.target.closest('a[data-download-path]');
|
|
185
|
-
if (download) {
|
|
186
|
-
event.preventDefault();
|
|
187
|
-
showFile(download);
|
|
188
|
-
return true;
|
|
189
|
-
}
|
|
190
|
-
const webLink = event.target.closest('a[data-external-link]');
|
|
191
|
-
if (embedded && webLink) {
|
|
192
|
-
event.preventDefault();
|
|
193
|
-
showWeb(webLink);
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
if (embedded && event.target.closest('a')) event.preventDefault();
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
root.harmonyCodeRelaxUpdateInfo = (versionName, versionCode, error) => {
|
|
201
|
-
if (!updateInfoButton?.isConnected) return;
|
|
202
|
-
updateInfoButton.textContent = versionName && versionCode
|
|
203
|
-
? `安装更新 · ${versionName}(${versionCode})`
|
|
204
|
-
: '安装更新';
|
|
205
|
-
if (error) updateInfoButton.title = `版本读取失败:${error}`;
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
return { close, handleContentClick, showFile, showWeb };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
root.createLinkActionController = createLinkActionController;
|
|
212
|
-
})(globalThis);
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
(function (root) {
|
|
2
|
-
function createMessageSendController(options) {
|
|
3
|
-
const { state, transport, platform, crypto, el, pendingMessages, drafts, composer, queue, threadContext, conversation } = options;
|
|
4
|
-
|
|
5
|
-
function createBody(prompt) {
|
|
6
|
-
const body = {
|
|
7
|
-
prompt,
|
|
8
|
-
clientUserMessageId: Array.from(crypto.getRandomValues(new Uint8Array(16)),
|
|
9
|
-
byte => byte.toString(16).padStart(2, '0')).join(''),
|
|
10
|
-
images: state.draftImages.map(image => image.id),
|
|
11
|
-
hostId: state.threads.find(thread => thread.id === state.selectedId)?.hostId,
|
|
12
|
-
};
|
|
13
|
-
const pending = state.pendingSettingsByThread.get(state.selectedId);
|
|
14
|
-
const selectedModel = pending?.model || state.currentModel;
|
|
15
|
-
const selectedThinking = pending?.thinking || state.currentThinking;
|
|
16
|
-
if (!transport.has('immediateSettings') || options.isNewThread()) {
|
|
17
|
-
if (selectedModel) body.model = selectedModel;
|
|
18
|
-
if (selectedThinking) body.thinking = selectedThinking;
|
|
19
|
-
}
|
|
20
|
-
if (options.isNewThread()) {
|
|
21
|
-
body.hostId = state.newThreadProject?.hostId;
|
|
22
|
-
if (transport.has('immediateSettings')) body.serviceTier = state.currentServiceTier;
|
|
23
|
-
}
|
|
24
|
-
return body;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function send(mode = 'direct') {
|
|
28
|
-
if (options.isConnectionBlocked()) return;
|
|
29
|
-
if (options.isThreadReadOnly()) {
|
|
30
|
-
options.setStatus(state.timeline.thread.readOnlyReason || '该对话当前只能查看');
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (state.historyLoading || state.timeline?.cached) {
|
|
34
|
-
options.setStatus('请等待对话同步完成后再发送');
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (options.isApplyingSettings()) {
|
|
38
|
-
options.setStatus('请等待工作站确认模型设置后再发送');
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (platform.voice.active) {
|
|
42
|
-
platform.callbacks.sendVoice(mode);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (options.isNewThread()) return options.createNewThread();
|
|
46
|
-
const prompt = el('prompt').value.trim();
|
|
47
|
-
if (!state.selectedId || (!prompt && !state.draftImages.length) || state.uploadingImages || state.sending) return;
|
|
48
|
-
state.sending = true;
|
|
49
|
-
composer.updateMode();
|
|
50
|
-
composer.setControlsDisabled(true);
|
|
51
|
-
const threadId = state.selectedId;
|
|
52
|
-
const pendingAtSend = state.pendingSettingsByThread.get(threadId);
|
|
53
|
-
const body = createBody(prompt);
|
|
54
|
-
const optimistic = mode === 'direct' ? {
|
|
55
|
-
clientUserMessageId: body.clientUserMessageId,
|
|
56
|
-
request: { ...body },
|
|
57
|
-
text: prompt,
|
|
58
|
-
attachments: state.draftImages.map(image => ({
|
|
59
|
-
...image,
|
|
60
|
-
type: 'image',
|
|
61
|
-
available: true,
|
|
62
|
-
url: image.url || image.thumbnailUrl,
|
|
63
|
-
})),
|
|
64
|
-
baseline: pendingMessages.baseline(state.timeline),
|
|
65
|
-
turnId: composer.isActive() ? state.timeline?.turns?.at(-1)?.id : null,
|
|
66
|
-
settled: false,
|
|
67
|
-
status: '',
|
|
68
|
-
} : null;
|
|
69
|
-
if (optimistic) {
|
|
70
|
-
pendingMessages.set(threadId, [...pendingMessages.get(threadId), optimistic]);
|
|
71
|
-
el('prompt').value = '';
|
|
72
|
-
state.draftImages = [];
|
|
73
|
-
options.renderDraftImages();
|
|
74
|
-
drafts.clearConversation(threadId);
|
|
75
|
-
options.renderPendingMessages();
|
|
76
|
-
options.scrollTimelineToBottom(true);
|
|
77
|
-
composer.updateActions();
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
const endpoint = mode === 'queue' ? 'queue' : 'messages';
|
|
81
|
-
const result = await options.api(`/api/threads/${encodeURIComponent(threadId)}/${endpoint}`, {
|
|
82
|
-
method: 'POST',
|
|
83
|
-
body: JSON.stringify(body),
|
|
84
|
-
});
|
|
85
|
-
if (state.pendingSettingsByThread.get(threadId) === pendingAtSend) {
|
|
86
|
-
state.pendingSettingsByThread.delete(threadId);
|
|
87
|
-
if (state.selectedId === threadId) options.renderModelControl();
|
|
88
|
-
}
|
|
89
|
-
if (optimistic) {
|
|
90
|
-
const submission = result.data || result;
|
|
91
|
-
optimistic.turnId = submission.turnId || submission.turn?.id || optimistic.turnId;
|
|
92
|
-
optimistic.status = '';
|
|
93
|
-
} else if (state.selectedId === threadId) {
|
|
94
|
-
el('prompt').value = '';
|
|
95
|
-
state.draftImages = [];
|
|
96
|
-
options.renderDraftImages();
|
|
97
|
-
drafts.clearConversation(threadId);
|
|
98
|
-
}
|
|
99
|
-
if (transport.has('immediateSettings') && state.selectedId === threadId) void threadContext.update(threadId);
|
|
100
|
-
if (mode === 'queue') queue.render(result.items || []);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
if (optimistic) optimistic.status = '发送未确认,收到正式消息后会自动合并';
|
|
103
|
-
options.setStatus(`发送失败:${error.message}`);
|
|
104
|
-
} finally {
|
|
105
|
-
if (optimistic) optimistic.settled = true;
|
|
106
|
-
state.sending = false;
|
|
107
|
-
composer.setControlsDisabled(false);
|
|
108
|
-
composer.updateActions();
|
|
109
|
-
if (state.selectedId === threadId) options.renderPendingMessages();
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function resend(message) {
|
|
114
|
-
if (message.retrying || state.sending || options.isConnectionBlocked()
|
|
115
|
-
|| options.isThreadReadOnly() || state.historyLoading) return;
|
|
116
|
-
const threadId = state.selectedId;
|
|
117
|
-
if (!pendingMessages.has(threadId, message)) return;
|
|
118
|
-
message.retrying = true;
|
|
119
|
-
state.sending = true;
|
|
120
|
-
composer.updateActions();
|
|
121
|
-
options.renderPendingMessages();
|
|
122
|
-
const uploaded = [];
|
|
123
|
-
let selection = conversation.generation();
|
|
124
|
-
try {
|
|
125
|
-
const recovery = options.restoreConversation();
|
|
126
|
-
selection = conversation.generation();
|
|
127
|
-
await recovery;
|
|
128
|
-
if (!conversation.isCurrent(selection, threadId) || !pendingMessages.has(threadId, message)) return;
|
|
129
|
-
if (options.isConnectionBlocked()) throw new Error('连接尚未恢复,未重新发送');
|
|
130
|
-
let result;
|
|
131
|
-
if (message.queueItemId) {
|
|
132
|
-
result = await options.api(`/api/threads/${encodeURIComponent(threadId)}/queue/${encodeURIComponent(message.queueItemId)}/steer`, { method: 'POST' });
|
|
133
|
-
if (state.selectedId === threadId) queue.render(result.items || []);
|
|
134
|
-
} else {
|
|
135
|
-
const images = [];
|
|
136
|
-
for (const attachment of message.attachments) {
|
|
137
|
-
const url = attachment.url || attachment.thumbnailUrl;
|
|
138
|
-
if (!/^data:image\/(?:png|jpeg|webp);base64,/i.test(url || '')) {
|
|
139
|
-
throw new Error('原图片已不可用,未重新发送;请重新选择图片');
|
|
140
|
-
}
|
|
141
|
-
const image = await transport.addImage(await (await fetch(url)).blob());
|
|
142
|
-
images.push(image.id);
|
|
143
|
-
uploaded.push(image.id);
|
|
144
|
-
}
|
|
145
|
-
if (!conversation.isCurrent(selection, threadId) || !pendingMessages.has(threadId, message)) return;
|
|
146
|
-
result = await options.api(`/api/threads/${encodeURIComponent(threadId)}/messages`, {
|
|
147
|
-
method: 'POST',
|
|
148
|
-
body: JSON.stringify({
|
|
149
|
-
...message.request,
|
|
150
|
-
prompt: message.text,
|
|
151
|
-
images,
|
|
152
|
-
clientUserMessageId: message.clientUserMessageId,
|
|
153
|
-
}),
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
const submission = result.data || result;
|
|
157
|
-
message.turnId = submission.turnId || submission.turn?.id || message.turnId;
|
|
158
|
-
message.status = '';
|
|
159
|
-
} catch (error) {
|
|
160
|
-
message.status = '发送未确认,收到正式消息后会自动合并';
|
|
161
|
-
conversation.failSync(selection);
|
|
162
|
-
options.showToast(`重新发送未完成:${error.message}`);
|
|
163
|
-
} finally {
|
|
164
|
-
for (const id of uploaded) transport.removeImage(id);
|
|
165
|
-
message.retrying = false;
|
|
166
|
-
message.settled = true;
|
|
167
|
-
state.sending = false;
|
|
168
|
-
composer.updateActions();
|
|
169
|
-
if (state.selectedId === threadId) options.renderPendingMessages();
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return Object.freeze({ createBody, resend, send });
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
root.createMessageSendController = createMessageSendController;
|
|
177
|
-
})(globalThis);
|
package/dist/web/message-view.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
(function (root) {
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
function parseQuestionReply(text) {
|
|
5
|
-
const match = /^\s*<send_user_message_question_reply>\s*([\s\S]*?)\s*<\/send_user_message_question_reply>\s*$/.exec(text);
|
|
6
|
-
if (!match) return null;
|
|
7
|
-
let replies;
|
|
8
|
-
try {
|
|
9
|
-
replies = JSON.parse(match[1]);
|
|
10
|
-
} catch {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
if (!Array.isArray(replies) || !replies.length || !replies.every(reply =>
|
|
14
|
-
reply && typeof reply.question === 'string' && typeof reply.answer === 'string')) return null;
|
|
15
|
-
return replies;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function stripInternalMessageMetadata(text) {
|
|
19
|
-
if (typeof text !== 'string') return '';
|
|
20
|
-
return text.replace(/(?:^|\r?\n)[ \t]*<oai-mem-citation\b[\s\S]*?(?:<\/oai-mem-citation>[ \t]*(?=\r?\n|$)|$)/gi, '').trimEnd();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function visibleMessageText(kind, text) {
|
|
24
|
-
return kind === 'agent' ? stripInternalMessageMetadata(text) : text;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function createMessageView(options) {
|
|
28
|
-
const {
|
|
29
|
-
document,
|
|
30
|
-
markdown,
|
|
31
|
-
enhanceCodeBlocks,
|
|
32
|
-
remoteImageElement,
|
|
33
|
-
openImageViewer,
|
|
34
|
-
linkActions,
|
|
35
|
-
isEmbedded,
|
|
36
|
-
scrollTimelineToBottom,
|
|
37
|
-
} = options;
|
|
38
|
-
|
|
39
|
-
function render(kind, text, role, imageAssets = {}, fileAssets = {}) {
|
|
40
|
-
const element = document.createElement('div');
|
|
41
|
-
element.className = `message ${kind}`;
|
|
42
|
-
const roleElement = document.createElement('span');
|
|
43
|
-
roleElement.className = 'role';
|
|
44
|
-
roleElement.textContent = role;
|
|
45
|
-
const content = document.createElement('div');
|
|
46
|
-
content.className = 'message-body';
|
|
47
|
-
const visibleText = visibleMessageText(kind, text);
|
|
48
|
-
const replies = kind === 'user' ? parseQuestionReply(visibleText) : null;
|
|
49
|
-
if (replies) {
|
|
50
|
-
for (const reply of replies) {
|
|
51
|
-
const item = document.createElement('div');
|
|
52
|
-
item.className = 'question-reply';
|
|
53
|
-
const question = document.createElement('div');
|
|
54
|
-
question.className = 'question-reply-question';
|
|
55
|
-
question.textContent = reply.question;
|
|
56
|
-
const answer = document.createElement('div');
|
|
57
|
-
answer.className = 'question-reply-answer';
|
|
58
|
-
answer.textContent = reply.answer;
|
|
59
|
-
item.append(question, answer);
|
|
60
|
-
content.append(item);
|
|
61
|
-
}
|
|
62
|
-
} else {
|
|
63
|
-
content.innerHTML = visibleText
|
|
64
|
-
? markdown.render(visibleText, { imageAssets, fileAssets, allowRemoteFiles: kind === 'agent' })
|
|
65
|
-
: '';
|
|
66
|
-
enhanceCodeBlocks(content, visibleText);
|
|
67
|
-
}
|
|
68
|
-
for (const placeholder of content.querySelectorAll('[data-relay-image]')) {
|
|
69
|
-
placeholder.replaceWith(remoteImageElement(
|
|
70
|
-
imageAssets[placeholder.dataset.relayImage],
|
|
71
|
-
true,
|
|
72
|
-
placeholder.dataset.imageAlt,
|
|
73
|
-
));
|
|
74
|
-
}
|
|
75
|
-
const showImage = event => {
|
|
76
|
-
const target = event.target.closest('[data-full-image]');
|
|
77
|
-
if (!target) return;
|
|
78
|
-
event.preventDefault();
|
|
79
|
-
const preview = target.currentSrc || target.src || target.dataset.fullImage;
|
|
80
|
-
openImageViewer(preview, target.alt || target.textContent || '图片', () => target.dataset.fullImage);
|
|
81
|
-
};
|
|
82
|
-
content.addEventListener('click', showImage);
|
|
83
|
-
content.addEventListener('click', event => linkActions.handleContentClick(event, isEmbedded()));
|
|
84
|
-
content.addEventListener('keydown', event => {
|
|
85
|
-
if (event.target.matches('img[data-full-image]') && ['Enter', ' '].includes(event.key)) showImage(event);
|
|
86
|
-
});
|
|
87
|
-
for (const image of content.querySelectorAll('img[data-full-image]')) {
|
|
88
|
-
image.addEventListener('load', () => scrollTimelineToBottom(), { once: true });
|
|
89
|
-
}
|
|
90
|
-
element.append(roleElement, content);
|
|
91
|
-
return element;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return { render };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
root.CodexMessageView = Object.freeze({ createMessageView, parseQuestionReply, visibleMessageText });
|
|
98
|
-
})(globalThis);
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
(function (root, factory) {
|
|
2
|
-
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
-
else root.CodeRelaxP2pDataFramer = factory();
|
|
4
|
-
})(globalThis, () => {
|
|
5
|
-
const CHUNK_CHARS = 12000;
|
|
6
|
-
const MAX_MESSAGE_BYTES = 8 * 1024 * 1024;
|
|
7
|
-
const MAX_PARTS = 1024;
|
|
8
|
-
const byteLength = value => new TextEncoder().encode(value).length;
|
|
9
|
-
|
|
10
|
-
return class {
|
|
11
|
-
constructor() { this.inbound = undefined; }
|
|
12
|
-
create(payload, id) {
|
|
13
|
-
if (typeof payload !== 'string' || !/^[a-f0-9]{32}-[0-9]{1,10}$/.test(id)) throw new Error('Invalid P2P payload');
|
|
14
|
-
const bytes = byteLength(payload);
|
|
15
|
-
if (bytes > MAX_MESSAGE_BYTES) throw new Error('Code Relax 直连消息超过 8 MiB');
|
|
16
|
-
const total = Math.max(1, Math.ceil(payload.length / CHUNK_CHARS));
|
|
17
|
-
if (total > MAX_PARTS) throw new Error('Code Relax 直连消息分片过多');
|
|
18
|
-
return { id, payload, bytes, index: 0, total };
|
|
19
|
-
}
|
|
20
|
-
frame(item) {
|
|
21
|
-
if (!item || item.index >= item.total) return undefined;
|
|
22
|
-
const start = item.index * CHUNK_CHARS;
|
|
23
|
-
return JSON.stringify({ type: 'data', id: item.id, index: item.index, total: item.total,
|
|
24
|
-
payload: item.payload.slice(start, start + CHUNK_CHARS) });
|
|
25
|
-
}
|
|
26
|
-
commit(item) { item.index++; return item.index >= item.total; }
|
|
27
|
-
accept(message) {
|
|
28
|
-
if (!message || message.type !== 'data') return { handled: false };
|
|
29
|
-
if (!/^[a-f0-9]{32}-[0-9]{1,10}$/.test(message.id || '') || !Number.isSafeInteger(message.index) ||
|
|
30
|
-
!Number.isSafeInteger(message.total) || message.total < 1 || message.total > MAX_PARTS ||
|
|
31
|
-
message.index < 0 || message.index >= message.total || typeof message.payload !== 'string' ||
|
|
32
|
-
message.payload.length > CHUNK_CHARS) throw new Error('Invalid P2P data frame');
|
|
33
|
-
if (!this.inbound) {
|
|
34
|
-
if (message.index !== 0) throw new Error('Invalid P2P data order');
|
|
35
|
-
this.inbound = { id: message.id, total: message.total, next: 0, bytes: 0, parts: [] };
|
|
36
|
-
}
|
|
37
|
-
const current = this.inbound;
|
|
38
|
-
if (current.id !== message.id || current.total !== message.total || current.next !== message.index) {
|
|
39
|
-
throw new Error('Invalid P2P data order');
|
|
40
|
-
}
|
|
41
|
-
current.bytes += byteLength(message.payload);
|
|
42
|
-
if (current.bytes > MAX_MESSAGE_BYTES) throw new Error('P2P data message too large');
|
|
43
|
-
current.parts.push(message.payload); current.next++;
|
|
44
|
-
if (current.next !== current.total) return { handled: true, complete: false };
|
|
45
|
-
this.inbound = undefined;
|
|
46
|
-
return { handled: true, complete: true, payload: current.parts.join('') };
|
|
47
|
-
}
|
|
48
|
-
reset() { this.inbound = undefined; }
|
|
49
|
-
};
|
|
50
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
(function (root, factory) {
|
|
2
|
-
if (typeof module === 'object' && module.exports) module.exports = factory();
|
|
3
|
-
else root.CodeRelaxP2pDiagnostics = factory();
|
|
4
|
-
})(globalThis, () => class {
|
|
5
|
-
constructor() {
|
|
6
|
-
this.started = Date.now(); this.local = [0, 0, 0, 0, 0]; this.remote = [0, 0, 0, 0, 0];
|
|
7
|
-
this.offer = false; this.answer = false; this.open = false; this.ping = false; this.pong = false;
|
|
8
|
-
this.ice = 'new'; this.gathering = 'new'; this.error = 'none';
|
|
9
|
-
}
|
|
10
|
-
candidate(side, candidate) {
|
|
11
|
-
const type = /\btyp (host|srflx|prflx|relay)\b/.exec(candidate || '')?.[1];
|
|
12
|
-
const index = ['host', 'srflx', 'prflx', 'relay'].indexOf(type);
|
|
13
|
-
const counts = side === 'local' ? this.local : this.remote;
|
|
14
|
-
counts[index < 0 ? 4 : index]++;
|
|
15
|
-
}
|
|
16
|
-
snapshot(stage) {
|
|
17
|
-
const known = (value, choices) => choices.includes(value) ? value : 'unknown';
|
|
18
|
-
const counts = values => values.map(value => Math.min(value, 999)).join(',');
|
|
19
|
-
return `stage=${known(stage, ['verified', 'timeout', 'failed', 'closed', 'complete', 'replaced', 'unsupported'])}` +
|
|
20
|
-
`;ms=${Math.min(Date.now() - this.started, 999999)}` +
|
|
21
|
-
`;offer=${Number(this.offer)};answer=${Number(this.answer)}` +
|
|
22
|
-
`;ice=${known(this.ice, ['new', 'checking', 'connected', 'completed', 'failed', 'disconnected', 'closed'])}` +
|
|
23
|
-
`;gathering=${known(this.gathering, ['new', 'in-progress', 'gathering', 'complete'])}` +
|
|
24
|
-
`;local=${counts(this.local)};remote=${counts(this.remote)}` +
|
|
25
|
-
`;open=${Number(this.open)};ping=${Number(this.ping)};pong=${Number(this.pong)}` +
|
|
26
|
-
`;error=${known(this.error, ['none', 'stun', 'description', 'candidate', 'channel', 'frame', 'peer', 'initialization', 'signal'])}`;
|
|
27
|
-
}
|
|
28
|
-
});
|