vibesurf 0.1.9a5__py3-none-any.whl → 0.1.10__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.
Files changed (41) hide show
  1. vibe_surf/_version.py +2 -2
  2. vibe_surf/agents/vibe_surf_agent.py +25 -15
  3. vibe_surf/backend/api/browser.py +66 -0
  4. vibe_surf/backend/api/task.py +2 -1
  5. vibe_surf/backend/main.py +76 -1
  6. vibe_surf/backend/shared_state.py +2 -0
  7. vibe_surf/browser/agent_browser_session.py +312 -62
  8. vibe_surf/browser/browser_manager.py +57 -92
  9. vibe_surf/browser/watchdogs/dom_watchdog.py +43 -43
  10. vibe_surf/chrome_extension/background.js +84 -0
  11. vibe_surf/chrome_extension/manifest.json +3 -1
  12. vibe_surf/chrome_extension/scripts/file-manager.js +526 -0
  13. vibe_surf/chrome_extension/scripts/history-manager.js +658 -0
  14. vibe_surf/chrome_extension/scripts/modal-manager.js +487 -0
  15. vibe_surf/chrome_extension/scripts/session-manager.js +31 -8
  16. vibe_surf/chrome_extension/scripts/settings-manager.js +1214 -0
  17. vibe_surf/chrome_extension/scripts/ui-manager.js +770 -3186
  18. vibe_surf/chrome_extension/sidepanel.html +27 -4
  19. vibe_surf/chrome_extension/styles/activity.css +574 -0
  20. vibe_surf/chrome_extension/styles/base.css +76 -0
  21. vibe_surf/chrome_extension/styles/history-modal.css +791 -0
  22. vibe_surf/chrome_extension/styles/input.css +429 -0
  23. vibe_surf/chrome_extension/styles/layout.css +186 -0
  24. vibe_surf/chrome_extension/styles/responsive.css +454 -0
  25. vibe_surf/chrome_extension/styles/settings-environment.css +165 -0
  26. vibe_surf/chrome_extension/styles/settings-forms.css +389 -0
  27. vibe_surf/chrome_extension/styles/settings-modal.css +141 -0
  28. vibe_surf/chrome_extension/styles/settings-profiles.css +244 -0
  29. vibe_surf/chrome_extension/styles/settings-responsive.css +144 -0
  30. vibe_surf/chrome_extension/styles/settings-utilities.css +25 -0
  31. vibe_surf/chrome_extension/styles/variables.css +54 -0
  32. vibe_surf/cli.py +1 -0
  33. vibe_surf/controller/vibesurf_tools.py +0 -2
  34. {vibesurf-0.1.9a5.dist-info → vibesurf-0.1.10.dist-info}/METADATA +18 -2
  35. {vibesurf-0.1.9a5.dist-info → vibesurf-0.1.10.dist-info}/RECORD +39 -23
  36. vibe_surf/chrome_extension/styles/main.css +0 -2338
  37. vibe_surf/chrome_extension/styles/settings.css +0 -1100
  38. {vibesurf-0.1.9a5.dist-info → vibesurf-0.1.10.dist-info}/WHEEL +0 -0
  39. {vibesurf-0.1.9a5.dist-info → vibesurf-0.1.10.dist-info}/entry_points.txt +0 -0
  40. {vibesurf-0.1.9a5.dist-info → vibesurf-0.1.10.dist-info}/licenses/LICENSE +0 -0
  41. {vibesurf-0.1.9a5.dist-info → vibesurf-0.1.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,487 @@
1
+ // Modal Manager - Handles modal dialogs and user confirmations
2
+ // Manages warning modals, confirmation dialogs, and generic modal utilities
3
+
4
+ class VibeSurfModalManager {
5
+ constructor() {
6
+ this.state = {
7
+ activeModals: new Set(),
8
+ modalCounter: 0
9
+ };
10
+ this.eventListeners = new Map();
11
+
12
+ this.bindGlobalEvents();
13
+ }
14
+
15
+ bindGlobalEvents() {
16
+ // Handle escape key to close modals
17
+ document.addEventListener('keydown', (e) => {
18
+ if (e.key === 'Escape') {
19
+ this.closeTopModal();
20
+ }
21
+ });
22
+
23
+ // Handle background clicks to close modals
24
+ document.addEventListener('click', (e) => {
25
+ if (e.target.classList.contains('modal-overlay')) {
26
+ this.closeModal(e.target.querySelector('.modal'));
27
+ }
28
+ });
29
+ }
30
+
31
+ // Event system for communicating with main UI manager
32
+ on(event, callback) {
33
+ if (!this.eventListeners.has(event)) {
34
+ this.eventListeners.set(event, []);
35
+ }
36
+ this.eventListeners.get(event).push(callback);
37
+ }
38
+
39
+ emit(event, data) {
40
+ if (this.eventListeners.has(event)) {
41
+ this.eventListeners.get(event).forEach(callback => {
42
+ try {
43
+ callback(data);
44
+ } catch (error) {
45
+ console.error(`[ModalManager] Event callback error for ${event}:`, error);
46
+ }
47
+ });
48
+ }
49
+ }
50
+
51
+ // Warning Modal
52
+ showWarningModal(title, message, options = {}) {
53
+ const {
54
+ confirmText = 'OK',
55
+ showCancel = false,
56
+ cancelText = 'Cancel',
57
+ onConfirm = null,
58
+ onCancel = null,
59
+ className = ''
60
+ } = options;
61
+
62
+ const modalId = this.generateModalId();
63
+
64
+ const modalHTML = `
65
+ <div class="modal-overlay" id="${modalId}-overlay">
66
+ <div class="modal warning-modal ${className}" id="${modalId}">
67
+ <div class="modal-header">
68
+ <h3>${this.escapeHtml(title)}</h3>
69
+ <button class="modal-close-btn" data-modal-id="${modalId}">×</button>
70
+ </div>
71
+ <div class="modal-body">
72
+ <div class="warning-icon">⚠️</div>
73
+ <p>${this.escapeHtml(message)}</p>
74
+ </div>
75
+ <div class="modal-footer">
76
+ ${showCancel ? `<button class="btn-secondary modal-cancel-btn" data-modal-id="${modalId}">${this.escapeHtml(cancelText)}</button>` : ''}
77
+ <button class="btn-primary modal-confirm-btn" data-modal-id="${modalId}">${this.escapeHtml(confirmText)}</button>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ `;
82
+
83
+ // Add to DOM
84
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
85
+
86
+ const modal = document.getElementById(modalId);
87
+ const overlay = document.getElementById(`${modalId}-overlay`);
88
+
89
+ // Track active modal
90
+ this.state.activeModals.add(modalId);
91
+
92
+ // Add event listeners
93
+ this.bindModalEvents(modalId, { onConfirm, onCancel });
94
+
95
+ // Show modal
96
+ requestAnimationFrame(() => {
97
+ overlay.classList.add('show');
98
+ });
99
+
100
+ return modalId;
101
+ }
102
+
103
+ // Confirmation Modal
104
+ showConfirmModal(title, message, options = {}) {
105
+ const {
106
+ confirmText = 'Confirm',
107
+ cancelText = 'Cancel',
108
+ onConfirm = null,
109
+ onCancel = null,
110
+ className = '',
111
+ type = 'question' // question, danger, info
112
+ } = options;
113
+
114
+ const modalId = this.generateModalId();
115
+
116
+ const iconMap = {
117
+ question: '❓',
118
+ danger: '🚨',
119
+ info: 'ℹ️'
120
+ };
121
+
122
+ const icon = iconMap[type] || iconMap.question;
123
+
124
+ const modalHTML = `
125
+ <div class="modal-overlay" id="${modalId}-overlay">
126
+ <div class="modal confirm-modal ${className}" id="${modalId}">
127
+ <div class="modal-header">
128
+ <h3>${this.escapeHtml(title)}</h3>
129
+ <button class="modal-close-btn" data-modal-id="${modalId}">×</button>
130
+ </div>
131
+ <div class="modal-body">
132
+ <div class="confirm-icon">${icon}</div>
133
+ <p>${this.escapeHtml(message)}</p>
134
+ </div>
135
+ <div class="modal-footer">
136
+ <button class="btn-secondary modal-cancel-btn" data-modal-id="${modalId}">${this.escapeHtml(cancelText)}</button>
137
+ <button class="btn-primary modal-confirm-btn" data-modal-id="${modalId}" ${type === 'danger' ? 'data-danger="true"' : ''}>${this.escapeHtml(confirmText)}</button>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ `;
142
+
143
+ // Add to DOM
144
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
145
+
146
+ const modal = document.getElementById(modalId);
147
+ const overlay = document.getElementById(`${modalId}-overlay`);
148
+
149
+ // Track active modal
150
+ this.state.activeModals.add(modalId);
151
+
152
+ // Add event listeners
153
+ this.bindModalEvents(modalId, { onConfirm, onCancel });
154
+
155
+ // Show modal
156
+ requestAnimationFrame(() => {
157
+ overlay.classList.add('show');
158
+ });
159
+
160
+ return modalId;
161
+ }
162
+
163
+ // Generic Modal Creator
164
+ createModal(content, options = {}) {
165
+ const {
166
+ title = '',
167
+ className = '',
168
+ showCloseButton = true,
169
+ backdrop = true,
170
+ onShow = null,
171
+ onHide = null
172
+ } = options;
173
+
174
+ const modalId = this.generateModalId();
175
+
176
+ const modalHTML = `
177
+ <div class="modal-overlay ${backdrop ? 'backdrop' : ''}" id="${modalId}-overlay">
178
+ <div class="modal ${className}" id="${modalId}">
179
+ ${title || showCloseButton ? `
180
+ <div class="modal-header">
181
+ ${title ? `<h3>${this.escapeHtml(title)}</h3>` : ''}
182
+ ${showCloseButton ? `<button class="modal-close-btn" data-modal-id="${modalId}">×</button>` : ''}
183
+ </div>
184
+ ` : ''}
185
+ <div class="modal-body">
186
+ ${content}
187
+ </div>
188
+ </div>
189
+ </div>
190
+ `;
191
+
192
+ // Add to DOM
193
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
194
+
195
+ const modal = document.getElementById(modalId);
196
+ const overlay = document.getElementById(`${modalId}-overlay`);
197
+
198
+ // Track active modal
199
+ this.state.activeModals.add(modalId);
200
+
201
+ // Add basic event listeners
202
+ this.bindModalEvents(modalId, { onShow, onHide });
203
+
204
+ // Show modal
205
+ requestAnimationFrame(() => {
206
+ overlay.classList.add('show');
207
+ if (onShow) onShow(modal);
208
+ });
209
+
210
+ return {
211
+ modalId,
212
+ modal,
213
+ overlay,
214
+ close: () => this.closeModal(modal)
215
+ };
216
+ }
217
+
218
+ // Modal Event Binding
219
+ bindModalEvents(modalId, callbacks = {}) {
220
+ const { onConfirm, onCancel, onShow, onHide } = callbacks;
221
+
222
+ // Close button
223
+ const closeBtn = document.querySelector(`[data-modal-id="${modalId}"].modal-close-btn`);
224
+ if (closeBtn) {
225
+ closeBtn.addEventListener('click', () => {
226
+ this.closeModal(document.getElementById(modalId));
227
+ if (onCancel) onCancel();
228
+ });
229
+ }
230
+
231
+ // Confirm button
232
+ const confirmBtn = document.querySelector(`[data-modal-id="${modalId}"].modal-confirm-btn`);
233
+ if (confirmBtn) {
234
+ confirmBtn.addEventListener('click', () => {
235
+ if (onConfirm) {
236
+ const result = onConfirm();
237
+ // Only close if callback doesn't return false
238
+ if (result !== false) {
239
+ this.closeModal(document.getElementById(modalId));
240
+ }
241
+ } else {
242
+ this.closeModal(document.getElementById(modalId));
243
+ }
244
+ });
245
+ }
246
+
247
+ // Cancel button
248
+ const cancelBtn = document.querySelector(`[data-modal-id="${modalId}"].modal-cancel-btn`);
249
+ if (cancelBtn) {
250
+ cancelBtn.addEventListener('click', () => {
251
+ this.closeModal(document.getElementById(modalId));
252
+ if (onCancel) onCancel();
253
+ });
254
+ }
255
+ }
256
+
257
+ // Close Modal
258
+ closeModal(modal) {
259
+ if (!modal) return;
260
+
261
+ const modalId = modal.id;
262
+ const overlay = document.getElementById(`${modalId}-overlay`);
263
+
264
+ if (overlay) {
265
+ overlay.classList.remove('show');
266
+
267
+ // Remove from DOM after animation
268
+ setTimeout(() => {
269
+ if (overlay.parentNode) {
270
+ overlay.parentNode.removeChild(overlay);
271
+ }
272
+ this.state.activeModals.delete(modalId);
273
+ }, 300); // Match CSS transition duration
274
+ }
275
+ }
276
+
277
+ // Close the topmost modal
278
+ closeTopModal() {
279
+ const modalIds = Array.from(this.state.activeModals);
280
+ if (modalIds.length > 0) {
281
+ const topModalId = modalIds[modalIds.length - 1];
282
+ const modal = document.getElementById(topModalId);
283
+ if (modal) {
284
+ this.closeModal(modal);
285
+ }
286
+ }
287
+ }
288
+
289
+ // Close all modals
290
+ closeAllModals() {
291
+ const modalIds = Array.from(this.state.activeModals);
292
+ modalIds.forEach(modalId => {
293
+ const modal = document.getElementById(modalId);
294
+ if (modal) {
295
+ this.closeModal(modal);
296
+ }
297
+ });
298
+ }
299
+
300
+ // Utility Methods
301
+ generateModalId() {
302
+ return `modal-${++this.state.modalCounter}-${Date.now()}`;
303
+ }
304
+
305
+ escapeHtml(text) {
306
+ if (typeof text !== 'string') return '';
307
+ const div = document.createElement('div');
308
+ div.textContent = text;
309
+ return div.innerHTML;
310
+ }
311
+
312
+ // Promise-based modal methods
313
+ showWarningModalAsync(title, message, options = {}) {
314
+ return new Promise((resolve) => {
315
+ this.showWarningModal(title, message, {
316
+ ...options,
317
+ onConfirm: () => {
318
+ if (options.onConfirm) options.onConfirm();
319
+ resolve(true);
320
+ },
321
+ onCancel: () => {
322
+ if (options.onCancel) options.onCancel();
323
+ resolve(false);
324
+ }
325
+ });
326
+ });
327
+ }
328
+
329
+ showConfirmModalAsync(title, message, options = {}) {
330
+ return new Promise((resolve) => {
331
+ this.showConfirmModal(title, message, {
332
+ ...options,
333
+ onConfirm: async () => {
334
+ try {
335
+ let result = true;
336
+ if (options.onConfirm) {
337
+ result = await options.onConfirm();
338
+ // If onConfirm returns false, don't resolve with true
339
+ if (result === false) {
340
+ resolve(false);
341
+ return;
342
+ }
343
+ }
344
+ resolve(true);
345
+ } catch (error) {
346
+ console.error('[ModalManager] onConfirm error:', error);
347
+ resolve(false);
348
+ }
349
+ },
350
+ onCancel: async () => {
351
+ try {
352
+ if (options.onCancel) {
353
+ await options.onCancel();
354
+ }
355
+ resolve(false);
356
+ } catch (error) {
357
+ console.error('[ModalManager] onCancel error:', error);
358
+ resolve(false);
359
+ }
360
+ }
361
+ });
362
+ });
363
+ }
364
+
365
+ // Modal state queries
366
+ hasActiveModals() {
367
+ return this.state.activeModals.size > 0;
368
+ }
369
+
370
+ getActiveModalCount() {
371
+ return this.state.activeModals.size;
372
+ }
373
+
374
+ isModalActive(modalId) {
375
+ return this.state.activeModals.has(modalId);
376
+ }
377
+
378
+ // Quick notification modal (auto-close)
379
+ showNotificationModal(message, type = 'info', duration = 3000) {
380
+ const typeIcons = {
381
+ success: '✅',
382
+ error: '❌',
383
+ warning: '⚠️',
384
+ info: 'ℹ️'
385
+ };
386
+
387
+ const icon = typeIcons[type] || typeIcons.info;
388
+ const typeClass = `notification-${type}`;
389
+
390
+ const modalData = this.createModal(`
391
+ <div class="notification-content">
392
+ <div class="notification-icon">${icon}</div>
393
+ <div class="notification-message">${this.escapeHtml(message)}</div>
394
+ </div>
395
+ `, {
396
+ className: `notification-modal ${typeClass}`,
397
+ showCloseButton: false,
398
+ backdrop: false
399
+ });
400
+
401
+ // Auto-close after duration
402
+ if (duration > 0) {
403
+ setTimeout(() => {
404
+ modalData.close();
405
+ }, duration);
406
+ }
407
+
408
+ return modalData.modalId;
409
+ }
410
+
411
+ // Quick input modal
412
+ showInputModal(title, placeholder = '', defaultValue = '', options = {}) {
413
+ const {
414
+ inputType = 'text',
415
+ confirmText = 'OK',
416
+ cancelText = 'Cancel',
417
+ onConfirm = null,
418
+ onCancel = null,
419
+ validator = null
420
+ } = options;
421
+
422
+ return new Promise((resolve) => {
423
+ const inputId = `input-${Date.now()}`;
424
+
425
+ const modalData = this.createModal(`
426
+ <div class="input-modal-content">
427
+ <label for="${inputId}" class="input-label">${title}</label>
428
+ <input type="${inputType}" id="${inputId}" class="modal-input"
429
+ placeholder="${this.escapeHtml(placeholder)}"
430
+ value="${this.escapeHtml(defaultValue)}">
431
+ <div class="modal-footer">
432
+ <button class="btn-secondary input-cancel-btn">${this.escapeHtml(cancelText)}</button>
433
+ <button class="btn-primary input-confirm-btn">${this.escapeHtml(confirmText)}</button>
434
+ </div>
435
+ </div>
436
+ `, {
437
+ className: 'input-modal',
438
+ showCloseButton: true
439
+ });
440
+
441
+ const input = document.getElementById(inputId);
442
+ const confirmBtn = modalData.modal.querySelector('.input-confirm-btn');
443
+ const cancelBtn = modalData.modal.querySelector('.input-cancel-btn');
444
+
445
+ // Focus input
446
+ setTimeout(() => input.focus(), 100);
447
+
448
+ const handleConfirm = () => {
449
+ const value = input.value.trim();
450
+
451
+ if (validator && !validator(value)) {
452
+ return; // Don't close modal if validation fails
453
+ }
454
+
455
+ modalData.close();
456
+ if (onConfirm) onConfirm(value);
457
+ resolve(value);
458
+ };
459
+
460
+ const handleCancel = () => {
461
+ modalData.close();
462
+ if (onCancel) onCancel();
463
+ resolve(null);
464
+ };
465
+
466
+ confirmBtn.addEventListener('click', handleConfirm);
467
+ cancelBtn.addEventListener('click', handleCancel);
468
+
469
+ // Enter key to confirm
470
+ input.addEventListener('keydown', (e) => {
471
+ if (e.key === 'Enter') {
472
+ handleConfirm();
473
+ }
474
+ });
475
+ });
476
+ }
477
+
478
+ // Get current state
479
+ getState() {
480
+ return { ...this.state };
481
+ }
482
+ }
483
+
484
+ // Export for use in other modules
485
+ if (typeof window !== 'undefined') {
486
+ window.VibeSurfModalManager = VibeSurfModalManager;
487
+ }
@@ -7,7 +7,7 @@ class VibeSurfSessionManager {
7
7
  this.currentSession = null;
8
8
  this.activityLogs = [];
9
9
  this.pollingInterval = null;
10
- this.pollingFrequency = 1000; // 1 second
10
+ this.pollingFrequency = 300; // 300ms for faster response
11
11
  this.isPolling = false;
12
12
  this.eventListeners = new Map();
13
13
 
@@ -201,8 +201,18 @@ class VibeSurfSessionManager {
201
201
  }
202
202
 
203
203
  try {
204
- console.log('[SessionManager] 🔄 Syncing activity logs before task submission...');
205
- await this.syncActivityLogsFromServer();
204
+ // Stop any existing polling before starting new task
205
+ this.stopActivityPolling();
206
+
207
+ // Reset activity logs for new task to ensure proper index synchronization
208
+ this.activityLogs = [];
209
+
210
+ // Sync with server logs to get the correct starting state
211
+ try {
212
+ await this.syncActivityLogsFromServer();
213
+ } catch (error) {
214
+ this.activityLogs = [];
215
+ }
206
216
 
207
217
  const taskPayload = {
208
218
  session_id: this.currentSession.id,
@@ -220,12 +230,12 @@ class VibeSurfSessionManager {
220
230
  submittedAt: new Date().toISOString()
221
231
  };
222
232
 
223
- // Start activity polling
224
- this.startActivityPolling();
225
-
226
233
  // Store updated session
227
234
  await this.storeSessionData();
228
235
 
236
+ // Start polling after task submission and sync
237
+ this.startActivityPolling();
238
+
229
239
  this.emit('taskSubmitted', {
230
240
  sessionId: this.currentSession.id,
231
241
  task: this.currentSession.currentTask,
@@ -273,6 +283,13 @@ class VibeSurfSessionManager {
273
283
  await this.storeSessionData();
274
284
  }
275
285
 
286
+ // Sync activity logs before resuming polling to ensure index consistency
287
+ try {
288
+ await this.syncActivityLogsFromServer();
289
+ } catch (error) {
290
+ // Continue with existing logs if sync fails
291
+ }
292
+
276
293
  // Restart polling when task is resumed
277
294
  this.startActivityPolling();
278
295
 
@@ -298,6 +315,13 @@ class VibeSurfSessionManager {
298
315
 
299
316
  // Stop polling when task is stopped
300
317
  this.stopActivityPolling();
318
+
319
+ // Sync final activity logs to capture any termination messages
320
+ try {
321
+ await this.syncActivityLogsFromServer();
322
+ } catch (error) {
323
+ // Continue if sync fails
324
+ }
301
325
 
302
326
  this.emit('taskStopped', { sessionId: this.currentSession?.id, response });
303
327
 
@@ -358,8 +382,7 @@ class VibeSurfSessionManager {
358
382
 
359
383
  if (response && activityLog) {
360
384
  const prevActivityLog = this.activityLogs.length > 0 ? this.activityLogs[this.activityLogs.length - 1] : null;
361
-
362
- // 检查是否为新的、不重复的activity log
385
+
363
386
  const isNewLog = !prevActivityLog || !this.areLogsEqual(prevActivityLog, activityLog);
364
387
 
365
388
  if (isNewLog) {