vibesurf 0.1.9a6__py3-none-any.whl → 0.1.11__py3-none-any.whl

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 vibesurf might be problematic. Click here for more details.

Files changed (69) hide show
  1. vibe_surf/_version.py +2 -2
  2. vibe_surf/agents/browser_use_agent.py +68 -45
  3. vibe_surf/agents/prompts/report_writer_prompt.py +73 -0
  4. vibe_surf/agents/prompts/vibe_surf_prompt.py +85 -172
  5. vibe_surf/agents/report_writer_agent.py +380 -226
  6. vibe_surf/agents/vibe_surf_agent.py +878 -814
  7. vibe_surf/agents/views.py +130 -0
  8. vibe_surf/backend/api/activity.py +3 -1
  9. vibe_surf/backend/api/browser.py +70 -0
  10. vibe_surf/backend/api/config.py +8 -5
  11. vibe_surf/backend/api/files.py +59 -50
  12. vibe_surf/backend/api/models.py +2 -2
  13. vibe_surf/backend/api/task.py +47 -13
  14. vibe_surf/backend/database/manager.py +24 -18
  15. vibe_surf/backend/database/queries.py +199 -192
  16. vibe_surf/backend/database/schemas.py +1 -1
  17. vibe_surf/backend/main.py +80 -3
  18. vibe_surf/backend/shared_state.py +30 -35
  19. vibe_surf/backend/utils/encryption.py +3 -1
  20. vibe_surf/backend/utils/llm_factory.py +41 -36
  21. vibe_surf/browser/agent_browser_session.py +308 -62
  22. vibe_surf/browser/browser_manager.py +71 -100
  23. vibe_surf/browser/utils.py +5 -3
  24. vibe_surf/browser/watchdogs/dom_watchdog.py +0 -45
  25. vibe_surf/chrome_extension/background.js +88 -0
  26. vibe_surf/chrome_extension/manifest.json +3 -1
  27. vibe_surf/chrome_extension/scripts/api-client.js +13 -0
  28. vibe_surf/chrome_extension/scripts/file-manager.js +482 -0
  29. vibe_surf/chrome_extension/scripts/history-manager.js +658 -0
  30. vibe_surf/chrome_extension/scripts/modal-manager.js +487 -0
  31. vibe_surf/chrome_extension/scripts/session-manager.js +52 -11
  32. vibe_surf/chrome_extension/scripts/settings-manager.js +1214 -0
  33. vibe_surf/chrome_extension/scripts/ui-manager.js +1530 -3163
  34. vibe_surf/chrome_extension/sidepanel.html +47 -7
  35. vibe_surf/chrome_extension/styles/activity.css +934 -0
  36. vibe_surf/chrome_extension/styles/base.css +76 -0
  37. vibe_surf/chrome_extension/styles/history-modal.css +791 -0
  38. vibe_surf/chrome_extension/styles/input.css +568 -0
  39. vibe_surf/chrome_extension/styles/layout.css +186 -0
  40. vibe_surf/chrome_extension/styles/responsive.css +454 -0
  41. vibe_surf/chrome_extension/styles/settings-environment.css +165 -0
  42. vibe_surf/chrome_extension/styles/settings-forms.css +389 -0
  43. vibe_surf/chrome_extension/styles/settings-modal.css +141 -0
  44. vibe_surf/chrome_extension/styles/settings-profiles.css +244 -0
  45. vibe_surf/chrome_extension/styles/settings-responsive.css +144 -0
  46. vibe_surf/chrome_extension/styles/settings-utilities.css +25 -0
  47. vibe_surf/chrome_extension/styles/variables.css +54 -0
  48. vibe_surf/cli.py +5 -22
  49. vibe_surf/common.py +35 -0
  50. vibe_surf/llm/openai_compatible.py +148 -93
  51. vibe_surf/logger.py +99 -0
  52. vibe_surf/{controller/vibesurf_tools.py → tools/browser_use_tools.py} +233 -221
  53. vibe_surf/tools/file_system.py +415 -0
  54. vibe_surf/{controller → tools}/mcp_client.py +4 -3
  55. vibe_surf/tools/report_writer_tools.py +21 -0
  56. vibe_surf/tools/vibesurf_tools.py +657 -0
  57. vibe_surf/tools/views.py +120 -0
  58. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.dist-info}/METADATA +23 -3
  59. vibesurf-0.1.11.dist-info/RECORD +93 -0
  60. vibe_surf/chrome_extension/styles/main.css +0 -2338
  61. vibe_surf/chrome_extension/styles/settings.css +0 -1100
  62. vibe_surf/controller/file_system.py +0 -53
  63. vibe_surf/controller/views.py +0 -37
  64. vibesurf-0.1.9a6.dist-info/RECORD +0 -71
  65. /vibe_surf/{controller → tools}/__init__.py +0 -0
  66. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.dist-info}/WHEEL +0 -0
  67. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.dist-info}/entry_points.txt +0 -0
  68. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.dist-info}/licenses/LICENSE +0 -0
  69. {vibesurf-0.1.9a6.dist-info → vibesurf-0.1.11.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,482 @@
1
+ // File Manager - Handles file uploads, file links, and file operations
2
+ // Manages file selection, upload, display, and file:// protocol handling
3
+
4
+ class VibeSurfFileManager {
5
+ constructor(sessionManager) {
6
+ this.sessionManager = sessionManager;
7
+ this.state = {
8
+ uploadedFiles: [],
9
+ isHandlingFileLink: false
10
+ };
11
+ this.elements = {};
12
+ this.eventListeners = new Map();
13
+
14
+ this.bindElements();
15
+ this.bindEvents();
16
+ }
17
+
18
+ bindElements() {
19
+ this.elements = {
20
+ // File input and buttons
21
+ attachFileBtn: document.getElementById('attach-file-btn'),
22
+ fileInput: document.getElementById('file-input'),
23
+
24
+ // File list container (created dynamically)
25
+ uploadedFilesList: null
26
+ };
27
+ }
28
+
29
+ bindEvents() {
30
+ // File attachment
31
+ this.elements.attachFileBtn?.addEventListener('click', this.handleAttachFiles.bind(this));
32
+ this.elements.fileInput?.addEventListener('change', this.handleFileSelection.bind(this));
33
+
34
+ // File link handling with delegation
35
+ document.addEventListener('click', this.handleFileLinkClick.bind(this));
36
+
37
+ // Listen for files uploaded event from session manager
38
+ if (this.sessionManager) {
39
+ this.sessionManager.on('filesUploaded', this.handleFilesUploaded.bind(this));
40
+ }
41
+ }
42
+
43
+ // Event system for communicating with main UI manager
44
+ on(event, callback) {
45
+ if (!this.eventListeners.has(event)) {
46
+ this.eventListeners.set(event, []);
47
+ }
48
+ this.eventListeners.get(event).push(callback);
49
+ }
50
+
51
+ emit(event, data) {
52
+ if (this.eventListeners.has(event)) {
53
+ this.eventListeners.get(event).forEach(callback => {
54
+ try {
55
+ callback(data);
56
+ } catch (error) {
57
+ console.error(`[FileManager] Event callback error for ${event}:`, error);
58
+ }
59
+ });
60
+ }
61
+ }
62
+
63
+ // File Upload Handling
64
+ handleAttachFiles() {
65
+ this.elements.fileInput?.click();
66
+ }
67
+
68
+ async handleFileSelection(event) {
69
+ const files = Array.from(event.target.files);
70
+
71
+ if (files.length === 0) return;
72
+
73
+ try {
74
+ this.emit('loading', { message: `Uploading ${files.length} file(s)...` });
75
+
76
+ const response = await this.sessionManager.uploadFiles(files);
77
+
78
+ console.log('[FileManager] File upload response:', response);
79
+
80
+ // SessionManager will emit 'filesUploaded' event, no need to handle manually
81
+ // Remove duplicate handling that was causing files to appear twice
82
+
83
+ this.emit('loading', { hide: true });
84
+ this.emit('notification', {
85
+ message: `${files.length} file(s) uploaded successfully`,
86
+ type: 'success'
87
+ });
88
+
89
+ // Clear file input
90
+ event.target.value = '';
91
+ } catch (error) {
92
+ this.emit('loading', { hide: true });
93
+ this.emit('notification', {
94
+ message: `File upload failed: ${error.message}`,
95
+ type: 'error'
96
+ });
97
+ }
98
+ }
99
+
100
+ handleFilesUploaded(data) {
101
+ console.log('[FileManager] Files uploaded event received:', data);
102
+
103
+ // Ensure data.files is always an array - handle both single file and array cases
104
+ let filesArray = [];
105
+ if (data.files) {
106
+ if (Array.isArray(data.files)) {
107
+ filesArray = data.files;
108
+ } else {
109
+ // If single file object, wrap in array
110
+ filesArray = [data.files];
111
+ console.log('[FileManager] Single file detected, wrapping in array');
112
+ }
113
+ }
114
+
115
+ console.log('[FileManager] Processing files array:', filesArray);
116
+
117
+ if (filesArray.length > 0) {
118
+ // Append new files to existing uploaded files (for multiple uploads)
119
+ const newFiles = filesArray.map(file => ({
120
+ id: file.file_id,
121
+ name: file.original_filename,
122
+ path: file.file_path, // Updated to use file_path field
123
+ size: file.file_size,
124
+ type: file.mime_type,
125
+ stored_filename: file.stored_filename,
126
+ file_path: file.file_path // Add file_path for backward compatibility
127
+ }));
128
+
129
+ console.log('[FileManager] Mapped new files:', newFiles);
130
+
131
+ // Add to existing files instead of replacing
132
+ this.state.uploadedFiles = [...this.state.uploadedFiles, ...newFiles];
133
+
134
+ console.log('[FileManager] Updated uploaded files state:', this.state.uploadedFiles);
135
+
136
+ // Update the visual file list
137
+ this.updateFilesList();
138
+ } else {
139
+ console.warn('[FileManager] No files to process in uploaded data');
140
+ }
141
+ }
142
+
143
+ // File List Management
144
+ updateFilesList() {
145
+ const container = this.getOrCreateFilesListContainer();
146
+
147
+ // Debug logging to identify the issue
148
+ console.log('[FileManager] updateFilesList called');
149
+ console.log('[FileManager] uploadedFiles type:', typeof this.state.uploadedFiles);
150
+ console.log('[FileManager] uploadedFiles isArray:', Array.isArray(this.state.uploadedFiles));
151
+ console.log('[FileManager] uploadedFiles value:', this.state.uploadedFiles);
152
+
153
+ // Ensure uploadedFiles is always an array
154
+ if (!Array.isArray(this.state.uploadedFiles)) {
155
+ console.error('[FileManager] uploadedFiles is not an array, resetting to empty array');
156
+ this.state.uploadedFiles = [];
157
+ }
158
+
159
+ if (this.state.uploadedFiles.length === 0) {
160
+ container.style.display = 'none';
161
+ return;
162
+ }
163
+
164
+ container.style.display = 'block';
165
+
166
+ // Build HTML safely with proper validation
167
+ let filesHTML = '';
168
+ try {
169
+ filesHTML = this.state.uploadedFiles.map((file, index) => {
170
+ console.log(`[FileManager] Processing file ${index}:`, file);
171
+
172
+ // Validate file object structure
173
+ if (!file || typeof file !== 'object') {
174
+ console.error(`[FileManager] Invalid file object at index ${index}:`, file);
175
+ return '';
176
+ }
177
+
178
+ // Extract properties safely with fallbacks
179
+ const fileId = file.id || file.file_id || `file_${index}`;
180
+ const fileName = file.name || file.original_filename || 'Unknown file';
181
+ const filePath = file.path || file.file_path || file.stored_filename || 'Unknown path';
182
+
183
+ console.log(`[FileManager] File display data: id=${fileId}, name=${fileName}, path=${filePath}`);
184
+
185
+ return `
186
+ <div class="file-item" data-file-id="${fileId}">
187
+ <span class="file-name" title="${filePath}">${fileName}</span>
188
+ <button class="file-remove-btn" title="Remove file" data-file-id="${fileId}">×</button>
189
+ </div>
190
+ `;
191
+ }).join('');
192
+ } catch (error) {
193
+ console.error('[FileManager] Error generating files HTML:', error);
194
+ filesHTML = '<div class="error-message">Error displaying files</div>';
195
+ }
196
+
197
+ container.innerHTML = `
198
+ <div class="files-items">
199
+ ${filesHTML}
200
+ </div>
201
+ `;
202
+
203
+ // Add event listeners for remove buttons
204
+ container.querySelectorAll('.file-remove-btn').forEach(btn => {
205
+ btn.addEventListener('click', (e) => {
206
+ e.preventDefault();
207
+ const fileId = btn.dataset.fileId;
208
+ this.removeUploadedFile(fileId);
209
+ });
210
+ });
211
+ }
212
+
213
+ getOrCreateFilesListContainer() {
214
+ let container = document.getElementById('uploaded-files-list');
215
+
216
+ if (!container) {
217
+ container = document.createElement('div');
218
+ container.id = 'uploaded-files-list';
219
+ container.className = 'uploaded-files-container';
220
+
221
+ // Insert after the textarea-container to avoid affecting button layout
222
+ const taskInput = document.getElementById('task-input');
223
+ if (taskInput) {
224
+ const textareaContainer = taskInput.closest('.textarea-container');
225
+ if (textareaContainer && textareaContainer.parentElement) {
226
+ // Insert after the textarea-container but before the input-footer
227
+ const inputFooter = textareaContainer.parentElement.querySelector('.input-footer');
228
+ if (inputFooter) {
229
+ textareaContainer.parentElement.insertBefore(container, inputFooter);
230
+ } else {
231
+ textareaContainer.parentElement.insertBefore(container, textareaContainer.nextSibling);
232
+ }
233
+ }
234
+ }
235
+ }
236
+
237
+ return container;
238
+ }
239
+
240
+ removeUploadedFile(fileId) {
241
+ console.log('[FileManager] Removing uploaded file:', fileId);
242
+
243
+ // Remove from state
244
+ this.state.uploadedFiles = this.state.uploadedFiles.filter(file => file.id !== fileId);
245
+
246
+ // Update visual list
247
+ this.updateFilesList();
248
+
249
+ this.emit('notification', {
250
+ message: 'File removed from upload list',
251
+ type: 'info'
252
+ });
253
+ }
254
+
255
+ clearUploadedFiles() {
256
+ this.state.uploadedFiles = [];
257
+ this.updateFilesList();
258
+ }
259
+
260
+ // File Link Handling
261
+ handleFileLinkClick(event) {
262
+ const target = event.target;
263
+
264
+ // Check if clicked element is a file link
265
+ if (target.matches('a.file-link') || target.closest('a.file-link')) {
266
+ event.preventDefault();
267
+
268
+ const fileLink = target.matches('a.file-link') ? target : target.closest('a.file-link');
269
+ const filePath = fileLink.getAttribute('data-file-path');
270
+
271
+ this.handleFileLink(filePath);
272
+ }
273
+ }
274
+
275
+ async handleFileLink(filePath) {
276
+ // Prevent multiple simultaneous calls
277
+ if (this.state.isHandlingFileLink) {
278
+ return;
279
+ }
280
+
281
+ this.state.isHandlingFileLink = true;
282
+
283
+ try {
284
+ // First decode the URL-encoded path
285
+ let decodedPath = decodeURIComponent(filePath);
286
+
287
+ // Remove file:// protocol prefix and normalize
288
+ let cleanPath = decodedPath.replace(/^file:\/\/\//, '').replace(/^file:\/\//, '');
289
+
290
+ // Ensure path starts with / for Unix paths if not Windows drive
291
+ if (!cleanPath.startsWith('/') && !cleanPath.match(/^[A-Za-z]:/)) {
292
+ cleanPath = '/' + cleanPath;
293
+ }
294
+
295
+ // Convert all backslashes to forward slashes
296
+ cleanPath = cleanPath.replace(/\\/g, '/');
297
+
298
+ // Create proper file URL - always use triple slash for proper format
299
+ const fileUrl = cleanPath.match(/^[A-Za-z]:/) ?
300
+ `file:///${cleanPath}` :
301
+ `file:///${cleanPath.replace(/^\//, '')}`; // Remove leading slash and add triple slash
302
+
303
+ // Show user notification about the action
304
+ this.emit('notification', {
305
+ message: `Opening file: ${cleanPath}`,
306
+ type: 'info'
307
+ });
308
+
309
+ // Use setTimeout to prevent UI blocking
310
+ setTimeout(async () => {
311
+ try {
312
+ // For user-clicked file links, use OPEN_FILE_URL to keep tab open
313
+ // This prevents the auto-close behavior in OPEN_FILE_SYSTEM
314
+ const fileOpenResponse = await chrome.runtime.sendMessage({
315
+ type: 'OPEN_FILE_URL',
316
+ data: { fileUrl: fileUrl }
317
+ });
318
+
319
+ if (fileOpenResponse && fileOpenResponse.success) {
320
+ this.emit('notification', {
321
+ message: 'File opened in browser tab',
322
+ type: 'success'
323
+ });
324
+ return;
325
+ }
326
+
327
+ // If OPEN_FILE_URL fails, try direct browser open
328
+ try {
329
+ const opened = window.open(fileUrl, '_blank', 'noopener,noreferrer');
330
+ if (opened) {
331
+ this.emit('notification', {
332
+ message: 'File opened in browser',
333
+ type: 'success'
334
+ });
335
+ return;
336
+ }
337
+ } catch (browserError) {
338
+ console.error('[FileManager] Browser open failed:', browserError);
339
+ }
340
+
341
+ // Last resort: Copy path to clipboard
342
+ this.copyToClipboardFallback(fileUrl);
343
+
344
+ } catch (error) {
345
+ console.error('[FileManager] Error in async file handling:', error);
346
+ this.emit('notification', {
347
+ message: `Unable to open file: ${error.message}`,
348
+ type: 'error'
349
+ });
350
+ } finally {
351
+ this.state.isHandlingFileLink = false;
352
+ }
353
+ }, 50); // Small delay to prevent UI blocking
354
+
355
+ } catch (error) {
356
+ console.error('[FileManager] Error handling file link:', error);
357
+ this.emit('notification', {
358
+ message: `Unable to open file: ${error.message}`,
359
+ type: 'error'
360
+ });
361
+ this.state.isHandlingFileLink = false;
362
+ }
363
+ }
364
+
365
+
366
+ async copyToClipboardFallback(fileUrl) {
367
+ try {
368
+ await navigator.clipboard.writeText(fileUrl);
369
+ this.emit('notification', {
370
+ message: 'File URL copied to clipboard - paste in browser address bar',
371
+ type: 'info'
372
+ });
373
+ } catch (clipboardError) {
374
+ console.error('[FileManager] Clipboard failed:', clipboardError);
375
+ this.emit('notification', {
376
+ message: 'Unable to open file. URL: ' + fileUrl,
377
+ type: 'warning'
378
+ });
379
+ }
380
+ }
381
+
382
+ // Utility Methods
383
+ formatFileSize(bytes) {
384
+ if (!bytes) return '0 B';
385
+
386
+ const k = 1024;
387
+ const sizes = ['B', 'KB', 'MB', 'GB'];
388
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
389
+
390
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
391
+ }
392
+
393
+ escapeHtml(text) {
394
+ if (typeof text !== 'string') return '';
395
+ const div = document.createElement('div');
396
+ div.textContent = text;
397
+ return div.innerHTML;
398
+ }
399
+
400
+ // Public interface
401
+ getUploadedFiles() {
402
+ return [...this.state.uploadedFiles]; // Return copy
403
+ }
404
+
405
+ getUploadedFilesForTask() {
406
+ if (this.state.uploadedFiles.length === 0) {
407
+ return null;
408
+ }
409
+
410
+ console.log('[FileManager] Raw uploaded files state:', this.state.uploadedFiles);
411
+
412
+ // Extract the first file path (backend expects single string)
413
+ const firstFile = this.state.uploadedFiles[0];
414
+ let filePath = null;
415
+
416
+ if (typeof firstFile === 'string') {
417
+ filePath = firstFile;
418
+ } else if (firstFile && typeof firstFile === 'object') {
419
+ // Extract path and normalize
420
+ filePath = firstFile.file_path || firstFile.path || firstFile.stored_filename || firstFile.file_path;
421
+ if (filePath) {
422
+ filePath = filePath.replace(/\\/g, '/');
423
+ console.log('[FileManager] Normalized file path:', filePath);
424
+ }
425
+ }
426
+
427
+ if (filePath) {
428
+ // Show info if multiple files uploaded but only first will be processed
429
+ if (this.state.uploadedFiles.length > 1) {
430
+ console.warn('[FileManager] Multiple files uploaded, but backend only supports single file. Using first file:', filePath);
431
+ this.emit('notification', {
432
+ message: `Multiple files uploaded. Only the first file "${firstFile.name || filePath}" will be processed.`,
433
+ type: 'warning'
434
+ });
435
+ }
436
+
437
+ return filePath;
438
+ } else {
439
+ console.error('[FileManager] Could not extract file path from uploaded file:', firstFile);
440
+ return null;
441
+ }
442
+ }
443
+
444
+ hasUploadedFiles() {
445
+ return this.state.uploadedFiles.length > 0;
446
+ }
447
+
448
+ // Enable/disable file attachment based on task running state
449
+ setEnabled(enabled) {
450
+ if (this.elements.attachFileBtn) {
451
+ this.elements.attachFileBtn.disabled = !enabled;
452
+
453
+ if (enabled) {
454
+ this.elements.attachFileBtn.classList.remove('task-running-disabled');
455
+ this.elements.attachFileBtn.removeAttribute('title');
456
+ } else {
457
+ this.elements.attachFileBtn.classList.add('task-running-disabled');
458
+ this.elements.attachFileBtn.setAttribute('title', 'Disabled while task is running');
459
+ }
460
+ }
461
+ }
462
+
463
+ // Get current state
464
+ getState() {
465
+ return { ...this.state };
466
+ }
467
+
468
+ // Update session manager reference
469
+ setSessionManager(sessionManager) {
470
+ this.sessionManager = sessionManager;
471
+
472
+ // Re-bind files uploaded event
473
+ if (this.sessionManager) {
474
+ this.sessionManager.on('filesUploaded', this.handleFilesUploaded.bind(this));
475
+ }
476
+ }
477
+ }
478
+
479
+ // Export for use in other modules
480
+ if (typeof window !== 'undefined') {
481
+ window.VibeSurfFileManager = VibeSurfFileManager;
482
+ }