@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.

Files changed (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/bin/uplink.js +279 -0
  4. package/middleware/error-handler.js +69 -0
  5. package/package.json +93 -0
  6. package/public/css/agents.36b98c0f.css +1469 -0
  7. package/public/css/agents.css +1469 -0
  8. package/public/css/app.a6a7f8f5.css +2731 -0
  9. package/public/css/app.css +2731 -0
  10. package/public/css/artifacts.css +444 -0
  11. package/public/css/commands.css +55 -0
  12. package/public/css/connection.css +131 -0
  13. package/public/css/dashboard.css +233 -0
  14. package/public/css/developer.css +328 -0
  15. package/public/css/files.css +123 -0
  16. package/public/css/markdown.css +156 -0
  17. package/public/css/message-actions.css +278 -0
  18. package/public/css/mobile.css +614 -0
  19. package/public/css/panels-unified.css +483 -0
  20. package/public/css/premium.css +415 -0
  21. package/public/css/realtime.css +189 -0
  22. package/public/css/satellites.css +401 -0
  23. package/public/css/shortcuts.css +185 -0
  24. package/public/css/split-view.4def0262.css +673 -0
  25. package/public/css/split-view.css +673 -0
  26. package/public/css/theme-generator.css +391 -0
  27. package/public/css/themes.css +387 -0
  28. package/public/css/timestamps.css +54 -0
  29. package/public/css/variables.css +78 -0
  30. package/public/dist/bundle.b55050c4.js +15757 -0
  31. package/public/favicon.svg +24 -0
  32. package/public/img/agents/ada.png +0 -0
  33. package/public/img/agents/clarice.png +0 -0
  34. package/public/img/agents/dennis-nedry.png +0 -0
  35. package/public/img/agents/elliot-alderson.png +0 -0
  36. package/public/img/agents/main.png +0 -0
  37. package/public/img/agents/scotty.png +0 -0
  38. package/public/img/agents/top-flight-security.png +0 -0
  39. package/public/index.html +1083 -0
  40. package/public/js/agents-data.js +234 -0
  41. package/public/js/agents-ui.js +72 -0
  42. package/public/js/agents.js +1525 -0
  43. package/public/js/app.js +79 -0
  44. package/public/js/appearance-settings.js +111 -0
  45. package/public/js/artifacts.js +432 -0
  46. package/public/js/audio-queue.js +168 -0
  47. package/public/js/bootstrap.js +54 -0
  48. package/public/js/chat.js +1211 -0
  49. package/public/js/commands.js +581 -0
  50. package/public/js/connection-api.js +121 -0
  51. package/public/js/connection.js +1231 -0
  52. package/public/js/context-tracker.js +271 -0
  53. package/public/js/core.js +172 -0
  54. package/public/js/dashboard.js +452 -0
  55. package/public/js/developer.js +432 -0
  56. package/public/js/encryption.js +124 -0
  57. package/public/js/errors.js +122 -0
  58. package/public/js/event-bus.js +77 -0
  59. package/public/js/fetch-utils.js +171 -0
  60. package/public/js/file-handler.js +229 -0
  61. package/public/js/files.js +352 -0
  62. package/public/js/gateway-chat.js +538 -0
  63. package/public/js/logger.js +112 -0
  64. package/public/js/markdown.js +190 -0
  65. package/public/js/message-actions.js +431 -0
  66. package/public/js/message-renderer.js +288 -0
  67. package/public/js/missed-messages.js +235 -0
  68. package/public/js/mobile-debug.js +95 -0
  69. package/public/js/notifications.js +367 -0
  70. package/public/js/offline-queue.js +178 -0
  71. package/public/js/onboarding.js +543 -0
  72. package/public/js/panels.js +156 -0
  73. package/public/js/premium.js +412 -0
  74. package/public/js/realtime-voice.js +844 -0
  75. package/public/js/satellite-sync.js +256 -0
  76. package/public/js/satellite-ui.js +175 -0
  77. package/public/js/satellites.js +1516 -0
  78. package/public/js/settings.js +1087 -0
  79. package/public/js/shortcuts.js +381 -0
  80. package/public/js/split-chat.js +1234 -0
  81. package/public/js/split-resize.js +211 -0
  82. package/public/js/splitview.js +340 -0
  83. package/public/js/storage.js +408 -0
  84. package/public/js/streaming-handler.js +324 -0
  85. package/public/js/stt-settings.js +316 -0
  86. package/public/js/theme-generator.js +661 -0
  87. package/public/js/themes.js +164 -0
  88. package/public/js/timestamps.js +198 -0
  89. package/public/js/tts-settings.js +575 -0
  90. package/public/js/ui.js +267 -0
  91. package/public/js/update-notifier.js +143 -0
  92. package/public/js/utils/constants.js +165 -0
  93. package/public/js/utils/sanitize.js +93 -0
  94. package/public/js/utils/sse-parser.js +195 -0
  95. package/public/js/voice.js +883 -0
  96. package/public/manifest.json +58 -0
  97. package/public/moon_texture.jpg +0 -0
  98. package/public/sw.js +221 -0
  99. package/public/three.min.js +6 -0
  100. package/server/channel.js +529 -0
  101. package/server/chat.js +270 -0
  102. package/server/config-store.js +362 -0
  103. package/server/config.js +159 -0
  104. package/server/context.js +131 -0
  105. package/server/gateway-commands.js +211 -0
  106. package/server/gateway-proxy.js +318 -0
  107. package/server/index.js +22 -0
  108. package/server/logger.js +89 -0
  109. package/server/middleware/auth.js +188 -0
  110. package/server/middleware.js +218 -0
  111. package/server/openclaw-discover.js +308 -0
  112. package/server/premium/index.js +156 -0
  113. package/server/premium/license.js +140 -0
  114. package/server/realtime/bridge.js +837 -0
  115. package/server/realtime/index.js +349 -0
  116. package/server/realtime/tts-stream.js +446 -0
  117. package/server/routes/agents.js +564 -0
  118. package/server/routes/artifacts.js +174 -0
  119. package/server/routes/chat.js +311 -0
  120. package/server/routes/config-settings.js +345 -0
  121. package/server/routes/config.js +603 -0
  122. package/server/routes/files.js +307 -0
  123. package/server/routes/index.js +18 -0
  124. package/server/routes/media.js +451 -0
  125. package/server/routes/missed-messages.js +107 -0
  126. package/server/routes/premium.js +75 -0
  127. package/server/routes/push.js +156 -0
  128. package/server/routes/satellite.js +406 -0
  129. package/server/routes/status.js +251 -0
  130. package/server/routes/stt.js +35 -0
  131. package/server/routes/voice.js +260 -0
  132. package/server/routes/webhooks.js +203 -0
  133. package/server/routes.js +206 -0
  134. package/server/runtime-config.js +336 -0
  135. package/server/share.js +305 -0
  136. package/server/stt/faster-whisper.js +72 -0
  137. package/server/stt/groq.js +51 -0
  138. package/server/stt/index.js +196 -0
  139. package/server/stt/openai.js +49 -0
  140. package/server/sync.js +244 -0
  141. package/server/tailscale-https.js +175 -0
  142. package/server/tts.js +646 -0
  143. package/server/update-checker.js +172 -0
  144. package/server/utils/filename.js +129 -0
  145. package/server/utils.js +147 -0
  146. package/server/watchdog.js +318 -0
  147. package/server/websocket/broadcast.js +359 -0
  148. package/server/websocket/connections.js +339 -0
  149. package/server/websocket/index.js +215 -0
  150. package/server/websocket/routing.js +277 -0
  151. package/server/websocket/sync.js +102 -0
  152. package/server.js +404 -0
  153. package/utils/detect-tool-usage.js +93 -0
  154. package/utils/errors.js +158 -0
  155. package/utils/html-escape.js +84 -0
  156. package/utils/id-sanitize.js +94 -0
  157. package/utils/response.js +130 -0
  158. package/utils/with-retry.js +105 -0
@@ -0,0 +1,352 @@
1
+ // ============================================
2
+ // FILES MODULE
3
+ // Drag & drop, enhanced file preview
4
+ // ============================================
5
+
6
+ import { UplinkCore } from './core.js';
7
+
8
+ const logger = window.UplinkLogger || console;
9
+
10
+ // State
11
+ let dropZoneVisible = false;
12
+
13
+ // Pending file state (previously on window.*)
14
+ let pendingImage = null;
15
+ let pendingFile = null;
16
+
17
+ // Create drop zone overlay
18
+ const dropZone = document.createElement('div');
19
+ dropZone.className = 'drop-zone';
20
+ dropZone.innerHTML = `
21
+ <div class="drop-zone-content">
22
+ <div class="drop-zone-icon">📎</div>
23
+ <div class="drop-zone-text">Drop files here</div>
24
+ <div class="drop-zone-hint">Images, PDFs, text files, code</div>
25
+ </div>
26
+ `;
27
+
28
+ function init() {
29
+ const app = document.querySelector('.app');
30
+ if (!app) {
31
+ setTimeout(init, 100);
32
+ return;
33
+ }
34
+
35
+ // Add drop zone to DOM
36
+ app.appendChild(dropZone);
37
+
38
+ // Set up drag and drop listeners on the entire app
39
+ app.addEventListener('dragenter', handleDragEnter);
40
+ app.addEventListener('dragover', handleDragOver);
41
+ app.addEventListener('dragleave', handleDragLeave);
42
+ app.addEventListener('drop', handleDrop);
43
+
44
+ // Enhance file preview
45
+ enhanceFilePreview();
46
+
47
+ logger.debug('Files: Initialized');
48
+ }
49
+
50
+ function handleDragEnter(e) {
51
+ e.preventDefault();
52
+ e.stopPropagation();
53
+
54
+ // Only show for files
55
+ if (e.dataTransfer.types.includes('Files')) {
56
+ showDropZone();
57
+ }
58
+ }
59
+
60
+ function handleDragOver(e) {
61
+ e.preventDefault();
62
+ e.stopPropagation();
63
+ e.dataTransfer.dropEffect = 'copy';
64
+ }
65
+
66
+ function handleDragLeave(e) {
67
+ e.preventDefault();
68
+ e.stopPropagation();
69
+
70
+ // Only hide if leaving the app container
71
+ const rect = dropZone.getBoundingClientRect();
72
+ if (
73
+ e.clientX < rect.left ||
74
+ e.clientX > rect.right ||
75
+ e.clientY < rect.top ||
76
+ e.clientY > rect.bottom
77
+ ) {
78
+ hideDropZone();
79
+ }
80
+ }
81
+
82
+ function handleDrop(e) {
83
+ e.preventDefault();
84
+ e.stopPropagation();
85
+ hideDropZone();
86
+
87
+ const files = Array.from(e.dataTransfer.files);
88
+ if (files.length === 0) return;
89
+
90
+ // Process the first file (can extend to handle multiple)
91
+ const file = files[0];
92
+ processFile(file);
93
+ }
94
+
95
+ function showDropZone() {
96
+ if (dropZoneVisible) return;
97
+ dropZoneVisible = true;
98
+ dropZone.classList.add('visible');
99
+ }
100
+
101
+ function hideDropZone() {
102
+ dropZoneVisible = false;
103
+ dropZone.classList.remove('visible');
104
+ }
105
+
106
+ function processFile(file) {
107
+ const reader = new FileReader();
108
+
109
+ reader.onload = (loadEvent) => {
110
+ const fileContent = loadEvent.target.result;
111
+
112
+ // Show preview based on file type
113
+ if (file.type.startsWith('image/')) {
114
+ showImagePreview(fileContent, file.name);
115
+ } else if (isTextFile(file)) {
116
+ showTextPreview(fileContent, file.name, file.type);
117
+ } else {
118
+ // PDF, DOCX, XLSX, and other binary files — store blob for upload
119
+ showGenericPreview(file.name, file.type, file.size);
120
+ pendingFile = {
121
+ blob: file,
122
+ name: file.name,
123
+ type: file.type || 'application/octet-stream',
124
+ size: file.size,
125
+ isText: false,
126
+ };
127
+ }
128
+ };
129
+
130
+ // Read as appropriate format
131
+ if (file.type.startsWith('image/')) {
132
+ reader.readAsDataURL(file);
133
+ } else if (isTextFile(file)) {
134
+ reader.readAsText(file);
135
+ } else {
136
+ // For binary files, still trigger onload but we use the raw File object
137
+ reader.readAsArrayBuffer(file);
138
+ }
139
+ }
140
+
141
+ function isTextFile(file) {
142
+ const textTypes = [
143
+ 'text/',
144
+ 'application/json',
145
+ 'application/javascript',
146
+ 'application/xml',
147
+ 'application/x-yaml'
148
+ ];
149
+ const textExtensions = [
150
+ '.txt', '.md', '.json', '.js', '.ts', '.py', '.html', '.css',
151
+ '.csv', '.xml', '.yaml', '.yml', '.sh', '.bat', '.ps1', '.sql',
152
+ '.env', '.gitignore', '.dockerfile'
153
+ ];
154
+
155
+ if (textTypes.some(t => file.type.startsWith(t))) return true;
156
+ if (textExtensions.some(ext => file.name.toLowerCase().endsWith(ext))) return true;
157
+ return false;
158
+ }
159
+
160
+ function showImagePreview(dataUrl, filename) {
161
+ const preview = document.getElementById('imagePreview');
162
+ const previewImg = document.getElementById('previewImg');
163
+ const fileLabel = preview.querySelector('.file-label');
164
+
165
+ previewImg.src = dataUrl;
166
+ previewImg.style.display = 'block';
167
+ if (fileLabel) fileLabel.style.display = 'none';
168
+ preview.classList.add('visible');
169
+
170
+ // Store for sending
171
+ pendingImage = dataUrl;
172
+ pendingFile = { data: dataUrl, name: filename, type: 'image' };
173
+
174
+ // Scroll messages to bottom so preview doesn't hide last message
175
+ requestAnimationFrame(() => {
176
+ const messagesEl = document.getElementById('messages');
177
+ if (messagesEl) messagesEl.scrollTop = messagesEl.scrollHeight;
178
+ });
179
+ }
180
+
181
+ function showPdfPreview(filename) {
182
+ const preview = document.getElementById('imagePreview');
183
+ const previewImg = document.getElementById('previewImg');
184
+
185
+ previewImg.style.display = 'none';
186
+
187
+ let fileLabel = preview.querySelector('.file-label');
188
+ if (!fileLabel) {
189
+ fileLabel = document.createElement('div');
190
+ fileLabel.className = 'file-label';
191
+ preview.appendChild(fileLabel);
192
+ }
193
+
194
+ fileLabel.textContent = `📄 ${filename}`;
195
+ fileLabel.style.display = 'block';
196
+ preview.classList.add('visible');
197
+ }
198
+
199
+ function showTextPreview(content, filename, mimeType) {
200
+ const preview = document.getElementById('imagePreview');
201
+ const previewImg = document.getElementById('previewImg');
202
+
203
+ previewImg.style.display = 'none';
204
+
205
+ let fileLabel = preview.querySelector('.file-label');
206
+ if (!fileLabel) {
207
+ fileLabel = document.createElement('div');
208
+ fileLabel.className = 'file-label';
209
+ preview.appendChild(fileLabel);
210
+ }
211
+
212
+ // Show filename with icon based on type
213
+ const icon = getFileIcon(filename);
214
+ fileLabel.textContent = `${icon} ${filename}`;
215
+ fileLabel.style.display = 'block';
216
+ preview.classList.add('visible');
217
+
218
+ // Store content for sending
219
+ pendingFile = {
220
+ data: content,
221
+ name: filename,
222
+ type: mimeType || 'text/plain',
223
+ isText: true
224
+ };
225
+ }
226
+
227
+ function showGenericPreview(filename, mimeType, size) {
228
+ const preview = document.getElementById('imagePreview');
229
+ const previewImg = document.getElementById('previewImg');
230
+
231
+ previewImg.style.display = 'none';
232
+
233
+ let fileLabel = preview.querySelector('.file-label');
234
+ if (!fileLabel) {
235
+ fileLabel = document.createElement('div');
236
+ fileLabel.className = 'file-label';
237
+ preview.appendChild(fileLabel);
238
+ }
239
+
240
+ const icon = getFileIcon(filename);
241
+ const sizeStr = formatFileSize(size);
242
+ // Safely build HTML: icon + filename (text) + size span
243
+ fileLabel.innerHTML = '';
244
+ fileLabel.appendChild(document.createTextNode(`${icon} ${filename} `));
245
+ const sizeSpan = document.createElement('span');
246
+ sizeSpan.className = 'file-size';
247
+ sizeSpan.textContent = `(${sizeStr})`;
248
+ fileLabel.appendChild(sizeSpan);
249
+ fileLabel.style.display = 'block';
250
+ preview.classList.add('visible');
251
+ }
252
+
253
+ function getFileIcon(filename) {
254
+ const ext = filename.split('.').pop().toLowerCase();
255
+ const icons = {
256
+ pdf: '📄',
257
+ doc: '📝', docx: '📝',
258
+ xls: '📊', xlsx: '📊', csv: '📊',
259
+ ppt: '📽️', pptx: '📽️',
260
+ zip: '🗜️', rar: '🗜️', '7z': '🗜️',
261
+ mp3: '🎵', wav: '🎵', flac: '🎵',
262
+ mp4: '🎬', mov: '🎬', avi: '🎬',
263
+ js: '📜', ts: '📜', py: '🐍', json: '📋',
264
+ html: '🌐', css: '🎨',
265
+ md: '📑', txt: '📄'
266
+ };
267
+ return icons[ext] || '📁';
268
+ }
269
+
270
+ function formatFileSize(bytes) {
271
+ if (bytes < 1024) return bytes + ' B';
272
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
273
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
274
+ }
275
+
276
+ function enhanceFilePreview() {
277
+ // Get the existing file input
278
+ const fileInput = document.getElementById('fileInput');
279
+ if (!fileInput) return;
280
+
281
+ // Accept all files — validation happens server-side
282
+ // Removed restrictive accept list (caused file dialog freezes on Windows/Chrome)
283
+
284
+ // Override the existing change handler to use our enhanced preview
285
+ const originalHandler = fileInput.onchange;
286
+ fileInput.addEventListener('change', (e) => {
287
+ const file = e.target.files[0];
288
+ if (file) {
289
+ processFile(file);
290
+ fileInput.value = ''; // Reset for re-selection
291
+ }
292
+ });
293
+
294
+ // Wire up the remove/cancel button on the image preview
295
+ const removeBtn = document.getElementById('removePreview');
296
+ if (removeBtn) {
297
+ removeBtn.addEventListener('click', (e) => {
298
+ e.preventDefault();
299
+ e.stopPropagation();
300
+ const preview = document.getElementById('imagePreview');
301
+ const previewImg = document.getElementById('previewImg');
302
+ if (preview) preview.classList.remove('visible');
303
+ if (previewImg) {
304
+ previewImg.src = '';
305
+ previewImg.style.display = 'none';
306
+ }
307
+ const fileLabel = preview?.querySelector('.file-label');
308
+ if (fileLabel) fileLabel.style.display = 'none';
309
+ // Clear pending state
310
+ pendingImage = null;
311
+ pendingFile = null;
312
+ logger.debug('Files: Preview cleared');
313
+ });
314
+ }
315
+ }
316
+
317
+ function destroy() {
318
+ const app = document.getElementById('app');
319
+ if (app) {
320
+ app.removeEventListener('dragenter', handleDragEnter);
321
+ app.removeEventListener('dragover', handleDragOver);
322
+ app.removeEventListener('dragleave', handleDragLeave);
323
+ app.removeEventListener('drop', handleDrop);
324
+ }
325
+
326
+ if (dropZone.parentNode) {
327
+ dropZone.parentNode.removeChild(dropZone);
328
+ }
329
+
330
+ logger.debug('Files: Destroyed');
331
+ }
332
+
333
+ // Expose for external use
334
+ export const UplinkFiles = {
335
+ process: processFile,
336
+ showDropZone,
337
+ hideDropZone,
338
+ destroy,
339
+ getFileIcon,
340
+ // Pending file state accessors (replaces window.pendingImage/pendingFile)
341
+ getPendingImage: () => pendingImage,
342
+ getPendingFile: () => pendingFile,
343
+ clearPending: () => { pendingImage = null; pendingFile = null; },
344
+ setPendingImage: (img) => { pendingImage = img; },
345
+ setPendingFile: (file) => { pendingFile = file; }
346
+ };
347
+
348
+ // Backward compat: assign to window
349
+ window.UplinkFiles = UplinkFiles;
350
+
351
+ // Register with core for coordinated initialization
352
+ UplinkCore.registerModule('files', init);