@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.
Files changed (120) hide show
  1. package/README.md +55 -71
  2. package/dist/bin/codex-remote.mjs +150 -126
  3. package/dist/bin/manage-relay-admin.mjs +20 -0
  4. package/dist/bin/migrate-relay-db.mjs +107 -0
  5. package/dist/bin/self-relay-admin.mjs +8 -0
  6. package/dist/bin/self-relay-demo.mjs +2 -2
  7. package/dist/bin/self-relay-server.mjs +18 -10
  8. package/dist/shared/app-server-events.cjs +42 -15
  9. package/dist/shared/file-mentions.cjs +35 -0
  10. package/dist/shared/server-requests.cjs +86 -58
  11. package/dist/skills/code-relax/SKILL.md +7 -6
  12. package/dist/src/desktop-launcher.mjs +16 -21
  13. package/dist/src/file-upload-session.mjs +124 -0
  14. package/dist/src/lan-socket-server.mjs +52 -9
  15. package/dist/src/managed-app-server.mjs +44 -13
  16. package/dist/src/message-images.mjs +0 -18
  17. package/dist/src/onboarding.mjs +9 -11
  18. package/dist/src/platform/README.md +1 -1
  19. package/dist/src/platform/windows/background-process.mjs +1 -1
  20. package/dist/src/platform/windows/codex-runtime.mjs +251 -0
  21. package/dist/src/platform/windows/desktop-host.mjs +17 -3
  22. package/dist/src/platform/windows/desktop-info.ps1 +2 -4
  23. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -0
  24. package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -14
  25. package/dist/src/platform/windows/launch-worker.mjs +42 -0
  26. package/dist/src/platform/windows/resident-start.js +20 -0
  27. package/dist/src/platform/windows/resident-startup.ps1 +76 -20
  28. package/dist/src/platform/windows/service-host.mjs +4 -16
  29. package/dist/src/platform/windows/start-hidden-console.ps1 +10 -5
  30. package/dist/src/platform/windows/stop-desktop.ps1 +7 -4
  31. package/dist/src/platform/windows/user-environment.mjs +1 -1
  32. package/dist/src/runtime-discovery.mjs +43 -0
  33. package/dist/src/self-relay/admin-cloud.mjs +101 -0
  34. package/dist/src/self-relay/admin-page.mjs +79 -0
  35. package/dist/src/self-relay/admin-server.mjs +66 -0
  36. package/dist/src/self-relay/admin-state.mjs +82 -19
  37. package/dist/src/self-relay/admin-style.mjs +9 -0
  38. package/dist/src/self-relay/admin-usage-page.mjs +28 -0
  39. package/dist/src/self-relay/connector.mjs +243 -30
  40. package/dist/src/self-relay/demo.mjs +3 -4
  41. package/dist/src/self-relay/email-accounts.mjs +141 -0
  42. package/dist/src/self-relay/lifecycle.mjs +99 -142
  43. package/dist/src/self-relay/mysql-state.mjs +104 -0
  44. package/dist/src/self-relay/p2p-probe.mjs +1 -1
  45. package/dist/src/self-relay/server.mjs +335 -59
  46. package/dist/src/self-relay/usage-diagnostics.mjs +46 -0
  47. package/dist/src/self-relay/usage-meter.mjs +125 -0
  48. package/dist/src/server.mjs +731 -1439
  49. package/dist/src/service-control.mjs +4 -4
  50. package/dist/src/service-doctor.mjs +27 -29
  51. package/dist/src/service-health.mjs +2 -2
  52. package/dist/src/service-install.mjs +1 -4
  53. package/dist/src/service-lifecycle.mjs +19 -19
  54. package/dist/src/shared-app-server.mjs +277 -270
  55. package/dist/src/shared-catalog.mjs +1 -11
  56. package/dist/src/shared-recovery.mjs +11 -9
  57. package/dist/src/shared-thread-stream.mjs +1 -1
  58. package/dist/src/skill-install.mjs +0 -13
  59. package/package.json +17 -18
  60. package/tools/postinstall.mjs +69 -15
  61. package/dist/src/app-server-client.mjs +0 -468
  62. package/dist/src/app-server-tasks.mjs +0 -358
  63. package/dist/src/debug-ui.mjs +0 -131
  64. package/dist/src/platform/windows/desktop-monitor.mjs +0 -34
  65. package/dist/src/platform/windows/desktop-shortcut-run.ps1 +0 -5
  66. package/dist/src/platform/windows/desktop-tools.mjs +0 -48
  67. package/dist/src/platform/windows/launch-worker.ps1 +0 -20
  68. package/dist/src/thread-catalog.mjs +0 -47
  69. package/dist/tools/find-desktop-pipe.mjs +0 -10
  70. package/dist/web/activity-view.js +0 -283
  71. package/dist/web/approval-ui.js +0 -188
  72. package/dist/web/capabilities.js +0 -57
  73. package/dist/web/chat-transport.js +0 -202
  74. package/dist/web/chat.css +0 -782
  75. package/dist/web/chat.js +0 -3830
  76. package/dist/web/client-platform.js +0 -11
  77. package/dist/web/community-view.js +0 -20
  78. package/dist/web/community.css +0 -77
  79. package/dist/web/community.html +0 -37
  80. package/dist/web/composer-controller.js +0 -101
  81. package/dist/web/conversation-controller.js +0 -99
  82. package/dist/web/disclosure-state-controller.js +0 -95
  83. package/dist/web/draft-controller.js +0 -103
  84. package/dist/web/harmony-platform.js +0 -52
  85. package/dist/web/highlight-powershell.min.js +0 -39
  86. package/dist/web/highlight.min.js +0 -1262
  87. package/dist/web/history-cache.js +0 -240
  88. package/dist/web/history-controller.js +0 -167
  89. package/dist/web/index.html +0 -325
  90. package/dist/web/lan-session.js +0 -122
  91. package/dist/web/link-action-controller.js +0 -212
  92. package/dist/web/message-send-controller.js +0 -177
  93. package/dist/web/message-view.js +0 -98
  94. package/dist/web/p2p-data-channel.js +0 -50
  95. package/dist/web/p2p-diagnostics.js +0 -28
  96. package/dist/web/p2p-probe.js +0 -123
  97. package/dist/web/page-resume.js +0 -28
  98. package/dist/web/pending-message-store.js +0 -108
  99. package/dist/web/queue-controller.js +0 -82
  100. package/dist/web/resources.json +0 -1
  101. package/dist/web/self-relay-session.js +0 -129
  102. package/dist/web/server-requests.js +0 -67
  103. package/dist/web/station-connection-controller.js +0 -75
  104. package/dist/web/task-list-view.js +0 -296
  105. package/dist/web/thread-attention-controller.js +0 -124
  106. package/dist/web/thread-context-controller.js +0 -30
  107. package/dist/web/thread-list-controller.js +0 -61
  108. package/dist/web/thread-list-sync.js +0 -86
  109. package/dist/web/thread-title-controller.js +0 -57
  110. package/dist/web/timeline-formatters.js +0 -249
  111. package/dist/web/timeline-reducer.js +0 -81
  112. package/dist/web/timeline-renderer.js +0 -161
  113. package/dist/web/timeline-scroll-controller.js +0 -50
  114. package/dist/web/timeline-sync.js +0 -67
  115. package/dist/web/usage-controller.js +0 -258
  116. package/dist/web/vendor/README.md +0 -14
  117. package/dist/web/vendor/highlight-powershell.min.js +0 -39
  118. package/dist/web/vendor/highlight.LICENSE.txt +0 -29
  119. package/dist/web/vendor/highlight.min.js +0 -1262
  120. 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);
@@ -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
- });