@mooncompany/uplink-chat 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @mooncompany/uplink-chat might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/bin/uplink.js +279 -0
- package/middleware/error-handler.js +69 -0
- package/package.json +93 -0
- package/public/css/agents.36b98c0f.css +1469 -0
- package/public/css/agents.css +1469 -0
- package/public/css/app.a6a7f8f5.css +2731 -0
- package/public/css/app.css +2731 -0
- package/public/css/artifacts.css +444 -0
- package/public/css/commands.css +55 -0
- package/public/css/connection.css +131 -0
- package/public/css/dashboard.css +233 -0
- package/public/css/developer.css +328 -0
- package/public/css/files.css +123 -0
- package/public/css/markdown.css +156 -0
- package/public/css/message-actions.css +278 -0
- package/public/css/mobile.css +614 -0
- package/public/css/panels-unified.css +483 -0
- package/public/css/premium.css +415 -0
- package/public/css/realtime.css +189 -0
- package/public/css/satellites.css +401 -0
- package/public/css/shortcuts.css +185 -0
- package/public/css/split-view.4def0262.css +673 -0
- package/public/css/split-view.css +673 -0
- package/public/css/theme-generator.css +391 -0
- package/public/css/themes.css +387 -0
- package/public/css/timestamps.css +54 -0
- package/public/css/variables.css +78 -0
- package/public/dist/bundle.b55050c4.js +15757 -0
- package/public/favicon.svg +24 -0
- package/public/img/agents/ada.png +0 -0
- package/public/img/agents/clarice.png +0 -0
- package/public/img/agents/dennis-nedry.png +0 -0
- package/public/img/agents/elliot-alderson.png +0 -0
- package/public/img/agents/main.png +0 -0
- package/public/img/agents/scotty.png +0 -0
- package/public/img/agents/top-flight-security.png +0 -0
- package/public/index.html +1083 -0
- package/public/js/agents-data.js +234 -0
- package/public/js/agents-ui.js +72 -0
- package/public/js/agents.js +1525 -0
- package/public/js/app.js +79 -0
- package/public/js/appearance-settings.js +111 -0
- package/public/js/artifacts.js +432 -0
- package/public/js/audio-queue.js +168 -0
- package/public/js/bootstrap.js +54 -0
- package/public/js/chat.js +1211 -0
- package/public/js/commands.js +581 -0
- package/public/js/connection-api.js +121 -0
- package/public/js/connection.js +1231 -0
- package/public/js/context-tracker.js +271 -0
- package/public/js/core.js +172 -0
- package/public/js/dashboard.js +452 -0
- package/public/js/developer.js +432 -0
- package/public/js/encryption.js +124 -0
- package/public/js/errors.js +122 -0
- package/public/js/event-bus.js +77 -0
- package/public/js/fetch-utils.js +171 -0
- package/public/js/file-handler.js +229 -0
- package/public/js/files.js +352 -0
- package/public/js/gateway-chat.js +538 -0
- package/public/js/logger.js +112 -0
- package/public/js/markdown.js +190 -0
- package/public/js/message-actions.js +431 -0
- package/public/js/message-renderer.js +288 -0
- package/public/js/missed-messages.js +235 -0
- package/public/js/mobile-debug.js +95 -0
- package/public/js/notifications.js +367 -0
- package/public/js/offline-queue.js +178 -0
- package/public/js/onboarding.js +543 -0
- package/public/js/panels.js +156 -0
- package/public/js/premium.js +412 -0
- package/public/js/realtime-voice.js +844 -0
- package/public/js/satellite-sync.js +256 -0
- package/public/js/satellite-ui.js +175 -0
- package/public/js/satellites.js +1516 -0
- package/public/js/settings.js +1087 -0
- package/public/js/shortcuts.js +381 -0
- package/public/js/split-chat.js +1234 -0
- package/public/js/split-resize.js +211 -0
- package/public/js/splitview.js +340 -0
- package/public/js/storage.js +408 -0
- package/public/js/streaming-handler.js +324 -0
- package/public/js/stt-settings.js +316 -0
- package/public/js/theme-generator.js +661 -0
- package/public/js/themes.js +164 -0
- package/public/js/timestamps.js +198 -0
- package/public/js/tts-settings.js +575 -0
- package/public/js/ui.js +267 -0
- package/public/js/update-notifier.js +143 -0
- package/public/js/utils/constants.js +165 -0
- package/public/js/utils/sanitize.js +93 -0
- package/public/js/utils/sse-parser.js +195 -0
- package/public/js/voice.js +883 -0
- package/public/manifest.json +58 -0
- package/public/moon_texture.jpg +0 -0
- package/public/sw.js +221 -0
- package/public/three.min.js +6 -0
- package/server/channel.js +529 -0
- package/server/chat.js +270 -0
- package/server/config-store.js +362 -0
- package/server/config.js +159 -0
- package/server/context.js +131 -0
- package/server/gateway-commands.js +211 -0
- package/server/gateway-proxy.js +318 -0
- package/server/index.js +22 -0
- package/server/logger.js +89 -0
- package/server/middleware/auth.js +188 -0
- package/server/middleware.js +218 -0
- package/server/openclaw-discover.js +308 -0
- package/server/premium/index.js +156 -0
- package/server/premium/license.js +140 -0
- package/server/realtime/bridge.js +837 -0
- package/server/realtime/index.js +349 -0
- package/server/realtime/tts-stream.js +446 -0
- package/server/routes/agents.js +564 -0
- package/server/routes/artifacts.js +174 -0
- package/server/routes/chat.js +311 -0
- package/server/routes/config-settings.js +345 -0
- package/server/routes/config.js +603 -0
- package/server/routes/files.js +307 -0
- package/server/routes/index.js +18 -0
- package/server/routes/media.js +451 -0
- package/server/routes/missed-messages.js +107 -0
- package/server/routes/premium.js +75 -0
- package/server/routes/push.js +156 -0
- package/server/routes/satellite.js +406 -0
- package/server/routes/status.js +251 -0
- package/server/routes/stt.js +35 -0
- package/server/routes/voice.js +260 -0
- package/server/routes/webhooks.js +203 -0
- package/server/routes.js +206 -0
- package/server/runtime-config.js +336 -0
- package/server/share.js +305 -0
- package/server/stt/faster-whisper.js +72 -0
- package/server/stt/groq.js +51 -0
- package/server/stt/index.js +196 -0
- package/server/stt/openai.js +49 -0
- package/server/sync.js +244 -0
- package/server/tailscale-https.js +175 -0
- package/server/tts.js +646 -0
- package/server/update-checker.js +172 -0
- package/server/utils/filename.js +129 -0
- package/server/utils.js +147 -0
- package/server/watchdog.js +318 -0
- package/server/websocket/broadcast.js +359 -0
- package/server/websocket/connections.js +339 -0
- package/server/websocket/index.js +215 -0
- package/server/websocket/routing.js +277 -0
- package/server/websocket/sync.js +102 -0
- package/server.js +404 -0
- package/utils/detect-tool-usage.js +93 -0
- package/utils/errors.js +158 -0
- package/utils/html-escape.js +84 -0
- package/utils/id-sanitize.js +94 -0
- package/utils/response.js +130 -0
- package/utils/with-retry.js +105 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// EVENT BUS MODULE
|
|
3
|
+
// Observer pattern for cross-module communication
|
|
4
|
+
// Replaces window.* patching pattern
|
|
5
|
+
// ============================================
|
|
6
|
+
|
|
7
|
+
const listeners = {};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Subscribe to an event
|
|
11
|
+
* @param {string} event - Event name
|
|
12
|
+
* @param {Function} callback - Event handler
|
|
13
|
+
* @returns {Function} Unsubscribe function
|
|
14
|
+
*/
|
|
15
|
+
export function on(event, callback) {
|
|
16
|
+
if (!listeners[event]) listeners[event] = [];
|
|
17
|
+
listeners[event].push(callback);
|
|
18
|
+
|
|
19
|
+
// Return unsubscribe function
|
|
20
|
+
return () => off(event, callback);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Unsubscribe from an event
|
|
25
|
+
* @param {string} event - Event name
|
|
26
|
+
* @param {Function} callback - Event handler to remove
|
|
27
|
+
*/
|
|
28
|
+
export function off(event, callback) {
|
|
29
|
+
if (!listeners[event]) return;
|
|
30
|
+
listeners[event] = listeners[event].filter(cb => cb !== callback);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Emit an event to all subscribers
|
|
35
|
+
* @param {string} event - Event name
|
|
36
|
+
* @param {*} data - Event data
|
|
37
|
+
*/
|
|
38
|
+
export function emit(event, data) {
|
|
39
|
+
if (!listeners[event]) return;
|
|
40
|
+
listeners[event].forEach(cb => {
|
|
41
|
+
try {
|
|
42
|
+
cb(data);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(`[EventBus] Error in listener for "${event}":`, err);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get count of listeners for an event (for debugging)
|
|
51
|
+
* @param {string} event - Event name
|
|
52
|
+
* @returns {number} Number of listeners
|
|
53
|
+
*/
|
|
54
|
+
export function listenerCount(event) {
|
|
55
|
+
return listeners[event] ? listeners[event].length : 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Clear all listeners for an event
|
|
60
|
+
* @param {string} event - Event name (optional, clears all if omitted)
|
|
61
|
+
*/
|
|
62
|
+
export function clear(event) {
|
|
63
|
+
if (event) {
|
|
64
|
+
delete listeners[event];
|
|
65
|
+
} else {
|
|
66
|
+
Object.keys(listeners).forEach(key => delete listeners[key]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Export singleton for backwards compatibility
|
|
71
|
+
const EventBus = { on, off, emit, listenerCount, clear };
|
|
72
|
+
export default EventBus;
|
|
73
|
+
|
|
74
|
+
// Also expose on window for debugging
|
|
75
|
+
if (typeof window !== 'undefined') {
|
|
76
|
+
window.UplinkEventBus = EventBus;
|
|
77
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// FETCH UTILS MODULE
|
|
3
|
+
// Unified fetch wrapper with hooks for logging, timeouts, and metrics
|
|
4
|
+
// ============================================
|
|
5
|
+
|
|
6
|
+
import { UplinkLogger } from './logger.js';
|
|
7
|
+
|
|
8
|
+
// Store original fetch before any interception
|
|
9
|
+
const originalFetch = window.fetch;
|
|
10
|
+
|
|
11
|
+
// Hook registry
|
|
12
|
+
const hooks = {
|
|
13
|
+
beforeRequest: [], // (url, options) => modified options or void
|
|
14
|
+
afterResponse: [], // (url, options, response, duration) => void
|
|
15
|
+
onError: [] // (url, options, error, duration) => void
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Default timeout (30 seconds)
|
|
19
|
+
let defaultTimeout = 30000;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Register a hook
|
|
23
|
+
* @param {string} type - 'beforeRequest', 'afterResponse', or 'onError'
|
|
24
|
+
* @param {Function} fn - Hook function
|
|
25
|
+
* @returns {Function} Unregister function
|
|
26
|
+
*/
|
|
27
|
+
function registerHook(type, fn) {
|
|
28
|
+
if (!hooks[type]) {
|
|
29
|
+
UplinkLogger.warn('FetchUtils: Unknown hook type', type);
|
|
30
|
+
return () => {};
|
|
31
|
+
}
|
|
32
|
+
hooks[type].push(fn);
|
|
33
|
+
return () => {
|
|
34
|
+
const idx = hooks[type].indexOf(fn);
|
|
35
|
+
if (idx > -1) hooks[type].splice(idx, 1);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set default timeout for all requests
|
|
41
|
+
* @param {number} ms - Timeout in milliseconds
|
|
42
|
+
*/
|
|
43
|
+
function setFetchTimeout(ms) {
|
|
44
|
+
defaultTimeout = ms;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Enhanced fetch with timeout and hooks
|
|
49
|
+
* @param {string|Request} input - URL or Request object
|
|
50
|
+
* @param {Object} options - Fetch options (plus optional timeout)
|
|
51
|
+
* @returns {Promise<Response>}
|
|
52
|
+
*/
|
|
53
|
+
async function enhancedFetch(input, options = {}) {
|
|
54
|
+
const url = typeof input === 'string' ? input : input.url;
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
|
|
57
|
+
// Extract timeout from options or use default
|
|
58
|
+
const timeout = options.timeout ?? defaultTimeout;
|
|
59
|
+
const fetchOptions = { ...options };
|
|
60
|
+
delete fetchOptions.timeout;
|
|
61
|
+
|
|
62
|
+
// Run beforeRequest hooks
|
|
63
|
+
for (const hook of hooks.beforeRequest) {
|
|
64
|
+
try {
|
|
65
|
+
const modified = await hook(url, fetchOptions);
|
|
66
|
+
if (modified) Object.assign(fetchOptions, modified);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
UplinkLogger.warn('FetchUtils: beforeRequest hook error', e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Create abort controller for timeout
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const existingSignal = fetchOptions.signal;
|
|
75
|
+
fetchOptions.signal = controller.signal;
|
|
76
|
+
|
|
77
|
+
// Set up timeout
|
|
78
|
+
let timeoutId = null;
|
|
79
|
+
if (timeout > 0) {
|
|
80
|
+
timeoutId = window.setTimeout(() => controller.abort(), timeout);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Handle existing abort signal
|
|
84
|
+
if (existingSignal) {
|
|
85
|
+
existingSignal.addEventListener('abort', () => controller.abort());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const response = await originalFetch(input, fetchOptions);
|
|
90
|
+
const duration = Date.now() - startTime;
|
|
91
|
+
|
|
92
|
+
// Clear timeout
|
|
93
|
+
if (timeoutId) window.clearTimeout(timeoutId);
|
|
94
|
+
|
|
95
|
+
// Run afterResponse hooks
|
|
96
|
+
for (const hook of hooks.afterResponse) {
|
|
97
|
+
try {
|
|
98
|
+
await hook(url, fetchOptions, response.clone(), duration);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
UplinkLogger.warn('FetchUtils: afterResponse hook error', e);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return response;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const duration = Date.now() - startTime;
|
|
107
|
+
|
|
108
|
+
// Clear timeout
|
|
109
|
+
if (timeoutId) window.clearTimeout(timeoutId);
|
|
110
|
+
|
|
111
|
+
// Enhance timeout errors
|
|
112
|
+
if (error.name === 'AbortError' && duration >= timeout - 100) {
|
|
113
|
+
error.isTimeout = true;
|
|
114
|
+
error.message = `Request timeout after ${timeout}ms`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Run onError hooks
|
|
118
|
+
for (const hook of hooks.onError) {
|
|
119
|
+
try {
|
|
120
|
+
await hook(url, fetchOptions, error, duration);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
UplinkLogger.warn('FetchUtils: onError hook error', e);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Install the enhanced fetch globally
|
|
132
|
+
* Call this once after all hooks are registered
|
|
133
|
+
*/
|
|
134
|
+
function install() {
|
|
135
|
+
window.fetch = enhancedFetch;
|
|
136
|
+
UplinkLogger.debug('FetchUtils: Installed enhanced fetch');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Restore original fetch
|
|
141
|
+
*/
|
|
142
|
+
function uninstall() {
|
|
143
|
+
window.fetch = originalFetch;
|
|
144
|
+
UplinkLogger.debug('FetchUtils: Restored original fetch');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the original unmodified fetch
|
|
149
|
+
*/
|
|
150
|
+
function getOriginalFetch() {
|
|
151
|
+
return originalFetch;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Export API
|
|
155
|
+
export const UplinkFetch = {
|
|
156
|
+
registerHook,
|
|
157
|
+
setTimeout: setFetchTimeout,
|
|
158
|
+
install,
|
|
159
|
+
uninstall,
|
|
160
|
+
getOriginalFetch,
|
|
161
|
+
fetch: enhancedFetch
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export { registerHook, setFetchTimeout, install, uninstall, getOriginalFetch, enhancedFetch };
|
|
165
|
+
|
|
166
|
+
// Backward compat: assign to window
|
|
167
|
+
window.UplinkFetch = UplinkFetch;
|
|
168
|
+
|
|
169
|
+
// Auto-install enhanced fetch
|
|
170
|
+
// Hooks can be registered before or after install - they're called dynamically
|
|
171
|
+
install();
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// FILE HANDLER MODULE
|
|
3
|
+
// Image and file upload logic with progress tracking
|
|
4
|
+
// ============================================
|
|
5
|
+
|
|
6
|
+
import { UplinkLogger } from './logger.js';
|
|
7
|
+
import { UplinkSSEParser } from './utils/sse-parser.js';
|
|
8
|
+
import { UplinkErrors } from './errors.js';
|
|
9
|
+
|
|
10
|
+
// ============================================
|
|
11
|
+
// UPLOAD WITH PROGRESS (XHR)
|
|
12
|
+
// ============================================
|
|
13
|
+
|
|
14
|
+
export function uploadWithProgress(url, formData, onProgress) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const xhr = new XMLHttpRequest();
|
|
17
|
+
|
|
18
|
+
xhr.upload.addEventListener('progress', (e) => {
|
|
19
|
+
if (e.lengthComputable && onProgress) {
|
|
20
|
+
const percentComplete = Math.round((e.loaded / e.total) * 100);
|
|
21
|
+
onProgress(percentComplete);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
xhr.addEventListener('load', () => {
|
|
26
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
27
|
+
resolve({ ok: true, status: xhr.status, blob: xhr.response });
|
|
28
|
+
} else {
|
|
29
|
+
reject(new Error(`Upload failed: ${xhr.status}`));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
xhr.addEventListener('error', () => reject(new Error('Network error during upload')));
|
|
34
|
+
xhr.addEventListener('abort', () => reject(new Error('Upload aborted')));
|
|
35
|
+
|
|
36
|
+
xhr.open('POST', url);
|
|
37
|
+
xhr.responseType = 'blob';
|
|
38
|
+
xhr.send(formData);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function blobToStreamResponse(blob) {
|
|
43
|
+
const text = await blob.text();
|
|
44
|
+
return {
|
|
45
|
+
body: new ReadableStream({
|
|
46
|
+
start(controller) {
|
|
47
|
+
controller.enqueue(new TextEncoder().encode(text));
|
|
48
|
+
controller.close();
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================
|
|
55
|
+
// IMAGE UPLOAD
|
|
56
|
+
// ============================================
|
|
57
|
+
|
|
58
|
+
export async function sendImageMessage(imageUrl, caption, chat, skipAddMessage, imageTypingTimeoutMs) {
|
|
59
|
+
caption = caption || '';
|
|
60
|
+
imageTypingTimeoutMs = imageTypingTimeoutMs || 300000;
|
|
61
|
+
|
|
62
|
+
if (!skipAddMessage) chat.addMessage(caption, 'user', imageUrl);
|
|
63
|
+
|
|
64
|
+
const progressMsg = chat.addMessage('Uploading image...', 'system', null, false);
|
|
65
|
+
chat.showTyping(imageTypingTimeoutMs);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const blobResponse = await fetch(imageUrl);
|
|
69
|
+
const blob = await blobResponse.blob();
|
|
70
|
+
|
|
71
|
+
const form = new FormData();
|
|
72
|
+
form.append('image', blob, 'image.jpg');
|
|
73
|
+
if (caption) form.append('caption', caption);
|
|
74
|
+
|
|
75
|
+
const uploadBlob = await uploadWithProgress('/api/image', form, (pct) => {
|
|
76
|
+
if (progressMsg && progressMsg.parentNode) {
|
|
77
|
+
const progressText = progressMsg.querySelector('.message-content');
|
|
78
|
+
if (progressText) {
|
|
79
|
+
progressText.textContent = `Uploading image... ${pct}%`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (progressMsg && progressMsg.parentNode) {
|
|
85
|
+
progressMsg.remove();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const streamResponse = await blobToStreamResponse(uploadBlob.blob);
|
|
89
|
+
|
|
90
|
+
let uploadResult = null;
|
|
91
|
+
await UplinkSSEParser.processStream(streamResponse, {
|
|
92
|
+
onData: (payload) => { uploadResult = payload; },
|
|
93
|
+
onError: (err) => {
|
|
94
|
+
UplinkLogger.warn('[Image Upload] SSE parse error:', err);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
chat.hideTyping();
|
|
99
|
+
|
|
100
|
+
if (uploadResult?.response) {
|
|
101
|
+
if (uploadResult.imageUrl) {
|
|
102
|
+
chat.updateLastUserImageUrl(uploadResult.imageUrl);
|
|
103
|
+
}
|
|
104
|
+
chat.addMessage(uploadResult.response, 'assistant');
|
|
105
|
+
if (window.UplinkConnection?.markMessageSeen) {
|
|
106
|
+
window.UplinkConnection.markMessageSeen(null, 'assistant', uploadResult.response, Date.now());
|
|
107
|
+
}
|
|
108
|
+
const core = window.UplinkCore;
|
|
109
|
+
if (core?.audioResponses) {
|
|
110
|
+
const urls = uploadResult.audioUrls?.length
|
|
111
|
+
? uploadResult.audioUrls
|
|
112
|
+
: (uploadResult.audioUrl ? [uploadResult.audioUrl] : []);
|
|
113
|
+
urls.forEach(url => chat.playAudio(url));
|
|
114
|
+
}
|
|
115
|
+
} else if (uploadResult?.error) {
|
|
116
|
+
const friendlyMsg = UplinkErrors.getFriendlyMessage(uploadResult.error) || uploadResult.error;
|
|
117
|
+
chat.addMessage(friendlyMsg, 'system', null, false);
|
|
118
|
+
} else {
|
|
119
|
+
chat.addMessage('No response received from image analysis', 'system', null, false);
|
|
120
|
+
}
|
|
121
|
+
} catch (uploadError) {
|
|
122
|
+
chat.hideTyping();
|
|
123
|
+
const friendlyMsg = UplinkErrors.getFriendlyMessage(uploadError) || 'Upload failed';
|
|
124
|
+
chat.addMessage(friendlyMsg, 'system', null, false);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================
|
|
129
|
+
// FILE UPLOAD (non-image)
|
|
130
|
+
// ============================================
|
|
131
|
+
|
|
132
|
+
export async function sendFileMessage(fileInfo, caption, chat, imageTypingTimeoutMs) {
|
|
133
|
+
caption = caption || '';
|
|
134
|
+
imageTypingTimeoutMs = imageTypingTimeoutMs || 300000;
|
|
135
|
+
|
|
136
|
+
const icon = window.UplinkFiles?.getFileIcon?.(fileInfo.name) || '📎';
|
|
137
|
+
chat.addMessage(`${icon} ${fileInfo.name}${caption ? ': ' + caption : ''}`, 'user');
|
|
138
|
+
|
|
139
|
+
const progressMsg = chat.addMessage('Uploading file...', 'system', null, false);
|
|
140
|
+
chat.showTyping(imageTypingTimeoutMs);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const form = new FormData();
|
|
144
|
+
form.append('file', fileInfo.blob, fileInfo.name);
|
|
145
|
+
if (caption) form.append('caption', caption);
|
|
146
|
+
|
|
147
|
+
const satelliteId = window.UplinkSatellites?.getCurrentSatellite() || 'main';
|
|
148
|
+
form.append('satelliteId', satelliteId);
|
|
149
|
+
|
|
150
|
+
const uploadBlob = await uploadWithProgress('/api/file', form, (pct) => {
|
|
151
|
+
if (progressMsg?.parentNode) {
|
|
152
|
+
const progressText = progressMsg.querySelector('.message-content');
|
|
153
|
+
if (progressText) progressText.textContent = `Uploading file... ${pct}%`;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (progressMsg?.parentNode) progressMsg.remove();
|
|
158
|
+
|
|
159
|
+
const streamResponse = await blobToStreamResponse(uploadBlob.blob);
|
|
160
|
+
|
|
161
|
+
let result = null;
|
|
162
|
+
await UplinkSSEParser.processStream(streamResponse, {
|
|
163
|
+
onData: (payload) => { result = payload; },
|
|
164
|
+
onError: (err) => {
|
|
165
|
+
UplinkLogger.warn('[File Upload] SSE parse error:', err);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
chat.hideTyping();
|
|
170
|
+
|
|
171
|
+
if (result?.response) {
|
|
172
|
+
chat.addMessage(result.response, 'assistant');
|
|
173
|
+
if (window.UplinkConnection?.markMessageSeen) {
|
|
174
|
+
window.UplinkConnection.markMessageSeen(null, 'assistant', result.response, Date.now());
|
|
175
|
+
}
|
|
176
|
+
} else if (result?.error) {
|
|
177
|
+
const friendlyMsg = UplinkErrors.getFriendlyMessage(result.error) || result.error;
|
|
178
|
+
chat.addMessage(friendlyMsg, 'system', null, false);
|
|
179
|
+
} else {
|
|
180
|
+
chat.addMessage('No response received for file upload', 'system', null, false);
|
|
181
|
+
}
|
|
182
|
+
} catch (err) {
|
|
183
|
+
chat.hideTyping();
|
|
184
|
+
if (progressMsg?.parentNode) progressMsg.remove();
|
|
185
|
+
const friendlyMsg = UplinkErrors.getFriendlyMessage(err) || 'File upload failed';
|
|
186
|
+
chat.addMessage(friendlyMsg, 'system', null, false);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ============================================
|
|
191
|
+
// IMAGE URL UPDATE
|
|
192
|
+
// ============================================
|
|
193
|
+
|
|
194
|
+
export function updateLastUserImageUrl(container, serverUrl) {
|
|
195
|
+
if (!container) return;
|
|
196
|
+
|
|
197
|
+
const userMsgs = container.querySelectorAll('.message.user');
|
|
198
|
+
for (let i = userMsgs.length - 1; i >= 0; i--) {
|
|
199
|
+
const img = userMsgs[i].querySelector('img');
|
|
200
|
+
if (img && (img.src.startsWith('data:') || img.src.includes('__pending_upload__'))) {
|
|
201
|
+
img.src = serverUrl;
|
|
202
|
+
|
|
203
|
+
if (window.UplinkStorage) {
|
|
204
|
+
window.UplinkStorage.updateLastImageUrl(serverUrl);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (window.UplinkSatellites?.updateLastImageUrl) {
|
|
208
|
+
window.UplinkSatellites.updateLastImageUrl(serverUrl);
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ============================================
|
|
216
|
+
// PUBLIC API
|
|
217
|
+
// ============================================
|
|
218
|
+
|
|
219
|
+
export const UplinkFileHandler = {
|
|
220
|
+
sendImageMessage,
|
|
221
|
+
sendFileMessage,
|
|
222
|
+
updateLastUserImageUrl,
|
|
223
|
+
uploadWithProgress
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// Backward compat: assign to window
|
|
227
|
+
window.UplinkFileHandler = UplinkFileHandler;
|
|
228
|
+
|
|
229
|
+
UplinkLogger.debug('FileHandler: Module loaded');
|