vibesurf 0.1.22__py3-none-any.whl → 0.1.24__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.
- vibe_surf/_version.py +2 -2
- vibe_surf/agents/prompts/vibe_surf_prompt.py +10 -0
- vibe_surf/agents/vibe_surf_agent.py +13 -2
- vibe_surf/backend/api/agent.py +38 -0
- vibe_surf/backend/api/config.py +3 -1
- vibe_surf/backend/api/task.py +1 -1
- vibe_surf/backend/main.py +2 -0
- vibe_surf/backend/utils/llm_factory.py +2 -1
- vibe_surf/browser/agent_browser_session.py +5 -5
- vibe_surf/chrome_extension/scripts/api-client.js +5 -0
- vibe_surf/chrome_extension/scripts/main.js +1 -1
- vibe_surf/chrome_extension/scripts/ui-manager.js +397 -20
- vibe_surf/chrome_extension/sidepanel.html +13 -1
- vibe_surf/chrome_extension/styles/input.css +115 -0
- vibe_surf/llm/openai_compatible.py +35 -10
- vibe_surf/tools/browser_use_tools.py +0 -90
- vibe_surf/tools/file_system.py +2 -2
- vibe_surf/tools/finance_tools.py +586 -0
- vibe_surf/tools/report_writer_tools.py +2 -1
- vibe_surf/tools/vibesurf_registry.py +52 -0
- vibe_surf/tools/vibesurf_tools.py +1044 -6
- vibe_surf/tools/views.py +93 -0
- {vibesurf-0.1.22.dist-info → vibesurf-0.1.24.dist-info}/METADATA +2 -1
- {vibesurf-0.1.22.dist-info → vibesurf-0.1.24.dist-info}/RECORD +28 -25
- {vibesurf-0.1.22.dist-info → vibesurf-0.1.24.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.22.dist-info → vibesurf-0.1.24.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.22.dist-info → vibesurf-0.1.24.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.22.dist-info → vibesurf-0.1.24.dist-info}/top_level.txt +0 -0
|
@@ -27,6 +27,7 @@ class VibeSurfUIManager {
|
|
|
27
27
|
|
|
28
28
|
this.bindElements();
|
|
29
29
|
this.initializeTabSelector(); // Initialize tab selector before binding events
|
|
30
|
+
this.initializeSkillSelector(); // Initialize skill selector
|
|
30
31
|
this.initializeManagers();
|
|
31
32
|
this.bindEvents();
|
|
32
33
|
this.setupSessionListeners();
|
|
@@ -67,6 +68,10 @@ class VibeSurfUIManager {
|
|
|
67
68
|
selectAllTabs: document.getElementById('select-all-tabs'),
|
|
68
69
|
tabOptionsList: document.getElementById('tab-options-list'),
|
|
69
70
|
|
|
71
|
+
// Skill selector elements
|
|
72
|
+
skillSelectorDropdown: document.getElementById('skill-selector-dropdown'),
|
|
73
|
+
skillOptionsList: document.getElementById('skill-options-list'),
|
|
74
|
+
|
|
70
75
|
// Loading
|
|
71
76
|
loadingOverlay: document.getElementById('loading-overlay')
|
|
72
77
|
};
|
|
@@ -581,7 +586,7 @@ class VibeSurfUIManager {
|
|
|
581
586
|
} else if (isRunning) {
|
|
582
587
|
this.elements.taskInput.placeholder = 'Task is running - please wait...';
|
|
583
588
|
} else {
|
|
584
|
-
this.elements.taskInput.placeholder = '
|
|
589
|
+
this.elements.taskInput.placeholder = 'Ask anything (/ for skills, @ to specify tab)';
|
|
585
590
|
}
|
|
586
591
|
}
|
|
587
592
|
|
|
@@ -962,8 +967,6 @@ class VibeSurfUIManager {
|
|
|
962
967
|
|
|
963
968
|
async handleSendTask() {
|
|
964
969
|
const taskDescription = this.elements.taskInput?.value.trim();
|
|
965
|
-
const taskStatus = this.sessionManager.getTaskStatus();
|
|
966
|
-
const isPaused = taskStatus === 'paused';
|
|
967
970
|
|
|
968
971
|
if (!taskDescription) {
|
|
969
972
|
this.showNotification('Please enter a task description', 'warning');
|
|
@@ -972,6 +975,12 @@ class VibeSurfUIManager {
|
|
|
972
975
|
}
|
|
973
976
|
|
|
974
977
|
try {
|
|
978
|
+
// Check task status from session manager first (more reliable than API check)
|
|
979
|
+
const sessionTaskStatus = this.sessionManager.getTaskStatus();
|
|
980
|
+
const isPaused = sessionTaskStatus === 'paused';
|
|
981
|
+
|
|
982
|
+
console.log('[UIManager] handleSendTask - session task status:', sessionTaskStatus, 'isPaused:', isPaused);
|
|
983
|
+
|
|
975
984
|
if (isPaused) {
|
|
976
985
|
// Handle adding new task to paused execution
|
|
977
986
|
|
|
@@ -988,7 +997,7 @@ class VibeSurfUIManager {
|
|
|
988
997
|
return;
|
|
989
998
|
}
|
|
990
999
|
|
|
991
|
-
//
|
|
1000
|
+
// For non-paused states, check if any task is running
|
|
992
1001
|
const statusCheck = await this.checkTaskStatus();
|
|
993
1002
|
if (statusCheck.isRunning) {
|
|
994
1003
|
const canProceed = await this.showTaskRunningWarning('send a new task');
|
|
@@ -1034,7 +1043,12 @@ class VibeSurfUIManager {
|
|
|
1034
1043
|
const selectedTabsData = this.getSelectedTabsForTask();
|
|
1035
1044
|
if (selectedTabsData) {
|
|
1036
1045
|
taskData.selected_tabs = selectedTabsData;
|
|
1037
|
-
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Add selected skills information if any
|
|
1049
|
+
const selectedSkillsData = this.getSelectedSkillsForTask();
|
|
1050
|
+
if (selectedSkillsData) {
|
|
1051
|
+
taskData.selected_skills = selectedSkillsData;
|
|
1038
1052
|
}
|
|
1039
1053
|
|
|
1040
1054
|
|
|
@@ -1105,6 +1119,19 @@ class VibeSurfUIManager {
|
|
|
1105
1119
|
event.preventDefault();
|
|
1106
1120
|
return;
|
|
1107
1121
|
}
|
|
1122
|
+
// Also handle skill token deletion
|
|
1123
|
+
if (this.handleSkillTokenDeletion(event)) {
|
|
1124
|
+
event.preventDefault();
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Handle Tab key for skill auto-completion when only one skill matches
|
|
1130
|
+
if (event.key === 'Tab' && this.skillSelectorState.isVisible) {
|
|
1131
|
+
if (this.skillSelectorState.filteredSkills.length === 1) {
|
|
1132
|
+
event.preventDefault();
|
|
1133
|
+
this.selectSkill(this.skillSelectorState.filteredSkills[0]);
|
|
1134
|
+
}
|
|
1108
1135
|
}
|
|
1109
1136
|
}
|
|
1110
1137
|
|
|
@@ -1164,6 +1191,60 @@ class VibeSurfUIManager {
|
|
|
1164
1191
|
return false; // Allow default behavior
|
|
1165
1192
|
}
|
|
1166
1193
|
|
|
1194
|
+
handleSkillTokenDeletion(event) {
|
|
1195
|
+
const input = event.target;
|
|
1196
|
+
const cursorPos = input.selectionStart;
|
|
1197
|
+
const text = input.value;
|
|
1198
|
+
|
|
1199
|
+
// Unicode markers for skill tokens
|
|
1200
|
+
const startMarker = '\u200D'; // Zero-width joiner
|
|
1201
|
+
const endMarker = '\u200E'; // Left-to-right mark
|
|
1202
|
+
|
|
1203
|
+
let tokenStart = -1;
|
|
1204
|
+
let tokenEnd = -1;
|
|
1205
|
+
|
|
1206
|
+
if (event.key === 'Backspace') {
|
|
1207
|
+
// Only delete if cursor is directly adjacent to end of token
|
|
1208
|
+
if (cursorPos > 0 && text[cursorPos - 1] === endMarker) {
|
|
1209
|
+
tokenEnd = cursorPos; // Include the marker
|
|
1210
|
+
// Find the corresponding start marker backwards
|
|
1211
|
+
for (let j = cursorPos - 2; j >= 0; j--) {
|
|
1212
|
+
if (text[j] === startMarker) {
|
|
1213
|
+
tokenStart = j;
|
|
1214
|
+
break;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
} else if (event.key === 'Delete') {
|
|
1219
|
+
// Only delete if cursor is directly adjacent to start of token
|
|
1220
|
+
if (cursorPos < text.length && text[cursorPos] === startMarker) {
|
|
1221
|
+
tokenStart = cursorPos;
|
|
1222
|
+
// Find the corresponding end marker forwards
|
|
1223
|
+
for (let j = cursorPos + 1; j < text.length; j++) {
|
|
1224
|
+
if (text[j] === endMarker) {
|
|
1225
|
+
tokenEnd = j + 1; // Include the marker
|
|
1226
|
+
break;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// If we found a complete token, delete it
|
|
1233
|
+
if (tokenStart !== -1 && tokenEnd !== -1) {
|
|
1234
|
+
const beforeToken = text.substring(0, tokenStart);
|
|
1235
|
+
const afterToken = text.substring(tokenEnd);
|
|
1236
|
+
input.value = beforeToken + afterToken;
|
|
1237
|
+
input.setSelectionRange(tokenStart, tokenStart);
|
|
1238
|
+
|
|
1239
|
+
// Trigger input change event for validation
|
|
1240
|
+
this.handleTaskInputChange({ target: input });
|
|
1241
|
+
|
|
1242
|
+
return true; // Prevent default behavior
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
return false; // Allow default behavior
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1167
1248
|
async handleLlmProfileChange(event) {
|
|
1168
1249
|
const selectedProfile = event.target.value;
|
|
1169
1250
|
|
|
@@ -1229,8 +1310,6 @@ class VibeSurfUIManager {
|
|
|
1229
1310
|
}
|
|
1230
1311
|
|
|
1231
1312
|
handleTaskInputChange(event) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
1313
|
const hasText = event.target.value.trim().length > 0;
|
|
1235
1314
|
const textarea = event.target;
|
|
1236
1315
|
const llmProfile = this.elements.llmProfileSelect?.value;
|
|
@@ -1241,6 +1320,9 @@ class VibeSurfUIManager {
|
|
|
1241
1320
|
// Check for @ character to trigger tab selector
|
|
1242
1321
|
this.handleTabSelectorInput(event);
|
|
1243
1322
|
|
|
1323
|
+
// Check for / character to trigger skill selector
|
|
1324
|
+
this.handleSkillSelectorInput(event);
|
|
1325
|
+
|
|
1244
1326
|
// Update send button state - special handling for pause state
|
|
1245
1327
|
if (this.elements.sendBtn) {
|
|
1246
1328
|
if (isPaused) {
|
|
@@ -1723,10 +1805,12 @@ class VibeSurfUIManager {
|
|
|
1723
1805
|
|
|
1724
1806
|
// Remove href temporarily to prevent default browser behavior
|
|
1725
1807
|
const originalHref = link.getAttribute('href');
|
|
1808
|
+
const dataFilePath = link.getAttribute('data-file-path');
|
|
1726
1809
|
link.setAttribute('href', '#');
|
|
1727
1810
|
|
|
1728
|
-
|
|
1729
|
-
|
|
1811
|
+
// Use data-file-path if available (for file:// links), otherwise use href
|
|
1812
|
+
const targetUrl = dataFilePath || originalHref;
|
|
1813
|
+
if (!targetUrl || (targetUrl === '#' && !dataFilePath)) return;
|
|
1730
1814
|
|
|
1731
1815
|
// Debounce - prevent rapid repeated clicks
|
|
1732
1816
|
if (link.hasAttribute('data-link-processing')) {
|
|
@@ -1737,22 +1821,22 @@ class VibeSurfUIManager {
|
|
|
1737
1821
|
link.setAttribute('data-link-processing', 'true');
|
|
1738
1822
|
|
|
1739
1823
|
try {
|
|
1740
|
-
console.log('[VibeSurf] Processing link:',
|
|
1824
|
+
console.log('[VibeSurf] Processing link:', targetUrl);
|
|
1741
1825
|
|
|
1742
1826
|
// Handle file:// links using existing logic
|
|
1743
|
-
if (
|
|
1744
|
-
await this.handleFileLinkClick(
|
|
1827
|
+
if (targetUrl.startsWith('file://')) {
|
|
1828
|
+
await this.handleFileLinkClick(targetUrl);
|
|
1745
1829
|
}
|
|
1746
1830
|
// Handle HTTP/HTTPS links
|
|
1747
|
-
else if (
|
|
1748
|
-
await this.handleHttpLinkClick(
|
|
1831
|
+
else if (targetUrl.startsWith('http://') || targetUrl.startsWith('https://')) {
|
|
1832
|
+
await this.handleHttpLinkClick(targetUrl);
|
|
1749
1833
|
}
|
|
1750
1834
|
// Handle other protocols or relative URLs
|
|
1751
1835
|
else {
|
|
1752
|
-
await this.handleOtherLinkClick(
|
|
1836
|
+
await this.handleOtherLinkClick(targetUrl);
|
|
1753
1837
|
}
|
|
1754
1838
|
|
|
1755
|
-
console.log('[VibeSurf] Link processed successfully:',
|
|
1839
|
+
console.log('[VibeSurf] Link processed successfully:', targetUrl);
|
|
1756
1840
|
} catch (error) {
|
|
1757
1841
|
console.error('[VibeSurf] Error handling link click:', error);
|
|
1758
1842
|
this.showNotification(`Failed to open link: ${error.message}`, 'error');
|
|
@@ -1981,6 +2065,8 @@ class VibeSurfUIManager {
|
|
|
1981
2065
|
return '🔄';
|
|
1982
2066
|
case 'request':
|
|
1983
2067
|
return '💡';
|
|
2068
|
+
case 'additional_request':
|
|
2069
|
+
return '➕';
|
|
1984
2070
|
default:
|
|
1985
2071
|
return '💡';
|
|
1986
2072
|
}
|
|
@@ -3095,9 +3181,300 @@ class VibeSurfUIManager {
|
|
|
3095
3181
|
|
|
3096
3182
|
return selectedTabsData;
|
|
3097
3183
|
}
|
|
3184
|
+
|
|
3185
|
+
// Skill Selector Methods
|
|
3186
|
+
initializeSkillSelector() {
|
|
3187
|
+
// Initialize skill selector state
|
|
3188
|
+
this.skillSelectorState = {
|
|
3189
|
+
isVisible: false,
|
|
3190
|
+
selectedSkills: [],
|
|
3191
|
+
allSkills: [],
|
|
3192
|
+
slashPosition: -1, // Position where / was typed
|
|
3193
|
+
currentFilter: '', // Current filter text after /
|
|
3194
|
+
filteredSkills: [] // Filtered skills based on current input
|
|
3195
|
+
};
|
|
3196
|
+
|
|
3197
|
+
// Bind skill selector events
|
|
3198
|
+
this.bindSkillSelectorEvents();
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
bindSkillSelectorEvents() {
|
|
3202
|
+
// Hide on click outside
|
|
3203
|
+
document.addEventListener('click', (event) => {
|
|
3204
|
+
if (this.skillSelectorState.isVisible &&
|
|
3205
|
+
this.elements.skillSelectorDropdown &&
|
|
3206
|
+
!this.elements.skillSelectorDropdown.contains(event.target) &&
|
|
3207
|
+
!this.elements.taskInput?.contains(event.target)) {
|
|
3208
|
+
this.hideSkillSelector();
|
|
3209
|
+
}
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
handleSkillSelectorInput(event) {
|
|
3214
|
+
// Safety check - ensure skill selector state is initialized
|
|
3215
|
+
if (!this.skillSelectorState) {
|
|
3216
|
+
console.warn('[UIManager] Skill selector state not initialized');
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
const inputValue = event.target.value;
|
|
3221
|
+
const cursorPosition = event.target.selectionStart;
|
|
3222
|
+
|
|
3223
|
+
// Check if / was just typed
|
|
3224
|
+
if (inputValue[cursorPosition - 1] === '/') {
|
|
3225
|
+
this.skillSelectorState.slashPosition = cursorPosition - 1;
|
|
3226
|
+
this.skillSelectorState.currentFilter = '';
|
|
3227
|
+
this.showSkillSelector();
|
|
3228
|
+
} else if (this.skillSelectorState.isVisible) {
|
|
3229
|
+
// Check if / was deleted - hide skill selector immediately
|
|
3230
|
+
if (this.skillSelectorState.slashPosition >= 0 &&
|
|
3231
|
+
(this.skillSelectorState.slashPosition >= inputValue.length ||
|
|
3232
|
+
inputValue[this.skillSelectorState.slashPosition] !== '/')) {
|
|
3233
|
+
this.hideSkillSelector();
|
|
3234
|
+
return;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
// Update filter based on text after /
|
|
3238
|
+
const textAfterSlash = inputValue.substring(this.skillSelectorState.slashPosition + 1, cursorPosition);
|
|
3239
|
+
|
|
3240
|
+
// Only consider text up to the next space or special character
|
|
3241
|
+
const filterText = textAfterSlash.split(/[\s@]/)[0];
|
|
3242
|
+
|
|
3243
|
+
if (this.skillSelectorState.currentFilter !== filterText) {
|
|
3244
|
+
this.skillSelectorState.currentFilter = filterText;
|
|
3245
|
+
this.filterSkills();
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
// Hide skill selector if user typed a space or moved past the skill context
|
|
3249
|
+
if (textAfterSlash.includes(' ') || textAfterSlash.includes('@')) {
|
|
3250
|
+
this.hideSkillSelector();
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
async showSkillSelector() {
|
|
3256
|
+
if (!this.elements.skillSelectorDropdown || !this.elements.taskInput) {
|
|
3257
|
+
console.error('[UIManager] Skill selector elements not found', {
|
|
3258
|
+
dropdown: this.elements.skillSelectorDropdown,
|
|
3259
|
+
taskInput: this.elements.taskInput
|
|
3260
|
+
});
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
try {
|
|
3265
|
+
// Fetch skill data from backend if not already cached
|
|
3266
|
+
if (this.skillSelectorState.allSkills.length === 0) {
|
|
3267
|
+
await this.populateSkillSelector();
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
// Filter skills based on current input
|
|
3271
|
+
this.filterSkills();
|
|
3272
|
+
|
|
3273
|
+
// Position the dropdown relative to the input
|
|
3274
|
+
this.positionSkillSelector();
|
|
3275
|
+
|
|
3276
|
+
// Show the dropdown with explicit visibility
|
|
3277
|
+
this.elements.skillSelectorDropdown.classList.remove('hidden');
|
|
3278
|
+
this.elements.skillSelectorDropdown.style.display = 'block';
|
|
3279
|
+
this.elements.skillSelectorDropdown.style.visibility = 'visible';
|
|
3280
|
+
this.elements.skillSelectorDropdown.style.opacity = '1';
|
|
3281
|
+
this.skillSelectorState.isVisible = true;
|
|
3282
|
+
|
|
3283
|
+
} catch (error) {
|
|
3284
|
+
console.error('[UIManager] Failed to show skill selector:', error);
|
|
3285
|
+
this.showNotification('Failed to load skills', 'error');
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
hideSkillSelector() {
|
|
3290
|
+
if (this.elements.skillSelectorDropdown) {
|
|
3291
|
+
this.elements.skillSelectorDropdown.classList.add('hidden');
|
|
3292
|
+
this.elements.skillSelectorDropdown.style.display = 'none';
|
|
3293
|
+
}
|
|
3294
|
+
this.skillSelectorState.isVisible = false;
|
|
3295
|
+
this.skillSelectorState.slashPosition = -1;
|
|
3296
|
+
this.skillSelectorState.currentFilter = '';
|
|
3297
|
+
this.skillSelectorState.filteredSkills = [];
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
positionSkillSelector() {
|
|
3301
|
+
if (!this.elements.skillSelectorDropdown || !this.elements.taskInput) return;
|
|
3302
|
+
|
|
3303
|
+
const inputRect = this.elements.taskInput.getBoundingClientRect();
|
|
3304
|
+
const dropdown = this.elements.skillSelectorDropdown;
|
|
3305
|
+
|
|
3306
|
+
// Calculate 90% width of input
|
|
3307
|
+
const dropdownWidth = inputRect.width * 0.9;
|
|
3308
|
+
|
|
3309
|
+
// Position dropdown ABOVE the input (not below)
|
|
3310
|
+
dropdown.style.position = 'fixed';
|
|
3311
|
+
dropdown.style.bottom = `${window.innerHeight - inputRect.top + 5}px`; // Above the input
|
|
3312
|
+
dropdown.style.left = `${inputRect.left + (inputRect.width - dropdownWidth) / 2}px`; // Centered
|
|
3313
|
+
dropdown.style.width = `${dropdownWidth}px`; // 90% of input width
|
|
3314
|
+
dropdown.style.zIndex = '9999';
|
|
3315
|
+
dropdown.style.maxHeight = '300px';
|
|
3316
|
+
dropdown.style.overflowY = 'auto';
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
async populateSkillSelector() {
|
|
3320
|
+
try {
|
|
3321
|
+
console.log('[UIManager] Fetching skills from backend...');
|
|
3322
|
+
// Get all skills from backend
|
|
3323
|
+
const skills = await this.apiClient.getAllSkills();
|
|
3324
|
+
|
|
3325
|
+
console.log('[UIManager] Skills received from backend:', skills);
|
|
3326
|
+
|
|
3327
|
+
if (!skills || !Array.isArray(skills) || skills.length === 0) {
|
|
3328
|
+
console.warn('[UIManager] No skills returned from backend');
|
|
3329
|
+
this.skillSelectorState.allSkills = [];
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
this.skillSelectorState.allSkills = skills.map(skillName => ({
|
|
3334
|
+
name: skillName,
|
|
3335
|
+
displayName: skillName // Keep original skill name without transformation
|
|
3336
|
+
}));
|
|
3337
|
+
console.log('[UIManager] Processed skills:', this.skillSelectorState.allSkills);
|
|
3338
|
+
|
|
3339
|
+
} catch (error) {
|
|
3340
|
+
console.error('[UIManager] Failed to populate skill selector:', error);
|
|
3341
|
+
console.error('[UIManager] Error details:', {
|
|
3342
|
+
message: error.message,
|
|
3343
|
+
stack: error.stack,
|
|
3344
|
+
response: error.response,
|
|
3345
|
+
data: error.data
|
|
3346
|
+
});
|
|
3347
|
+
|
|
3348
|
+
// Show error to user
|
|
3349
|
+
this.showNotification(`Failed to load skills: ${error.message}`, 'error');
|
|
3350
|
+
|
|
3351
|
+
// Set empty array instead of fallback test data
|
|
3352
|
+
this.skillSelectorState.allSkills = [];
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
filterSkills() {
|
|
3357
|
+
const filter = this.skillSelectorState.currentFilter.toLowerCase();
|
|
3358
|
+
|
|
3359
|
+
if (!filter) {
|
|
3360
|
+
this.skillSelectorState.filteredSkills = this.skillSelectorState.allSkills;
|
|
3361
|
+
} else {
|
|
3362
|
+
this.skillSelectorState.filteredSkills = this.skillSelectorState.allSkills.filter(skill =>
|
|
3363
|
+
skill.name.toLowerCase().startsWith(filter) ||
|
|
3364
|
+
skill.displayName.toLowerCase().startsWith(filter)
|
|
3365
|
+
);
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
this.renderSkillOptions();
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
renderSkillOptions() {
|
|
3372
|
+
if (!this.elements.skillOptionsList) return;
|
|
3373
|
+
|
|
3374
|
+
// Clear existing options
|
|
3375
|
+
this.elements.skillOptionsList.innerHTML = '';
|
|
3376
|
+
|
|
3377
|
+
if (this.skillSelectorState.filteredSkills.length === 0) {
|
|
3378
|
+
const noResults = document.createElement('div');
|
|
3379
|
+
noResults.className = 'skill-option';
|
|
3380
|
+
noResults.innerHTML = '<span class="skill-name">No skills found</span>';
|
|
3381
|
+
noResults.style.opacity = '0.6';
|
|
3382
|
+
noResults.style.cursor = 'not-allowed';
|
|
3383
|
+
this.elements.skillOptionsList.appendChild(noResults);
|
|
3384
|
+
return;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// Add skill options
|
|
3388
|
+
this.skillSelectorState.filteredSkills.forEach((skill, index) => {
|
|
3389
|
+
const option = this.createSkillOption(skill, index);
|
|
3390
|
+
this.elements.skillOptionsList.appendChild(option);
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
createSkillOption(skill, index) {
|
|
3395
|
+
const option = document.createElement('div');
|
|
3396
|
+
option.className = 'skill-option';
|
|
3397
|
+
option.dataset.skillName = skill.name;
|
|
3398
|
+
option.dataset.skillIndex = index;
|
|
3399
|
+
|
|
3400
|
+
option.innerHTML = `
|
|
3401
|
+
<span class="skill-name">${this.escapeHtml(skill.displayName)}</span>
|
|
3402
|
+
`;
|
|
3403
|
+
|
|
3404
|
+
// Add click event for skill selection
|
|
3405
|
+
option.addEventListener('click', () => {
|
|
3406
|
+
this.selectSkill(skill);
|
|
3407
|
+
});
|
|
3408
|
+
|
|
3409
|
+
return option;
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
selectSkill(skill) {
|
|
3413
|
+
if (!this.elements.taskInput) return;
|
|
3414
|
+
|
|
3415
|
+
const input = this.elements.taskInput;
|
|
3416
|
+
const currentValue = input.value;
|
|
3417
|
+
const slashPosition = this.skillSelectorState.slashPosition;
|
|
3418
|
+
|
|
3419
|
+
// Use special Unicode characters as boundaries for easy deletion
|
|
3420
|
+
const SKILL_START_MARKER = '\u200D'; // Zero-width joiner
|
|
3421
|
+
const SKILL_END_MARKER = '\u200E'; // Left-to-right mark
|
|
3422
|
+
|
|
3423
|
+
// Create skill information string
|
|
3424
|
+
const skillInfo = `${SKILL_START_MARKER}/${skill.name}${SKILL_END_MARKER}`;
|
|
3425
|
+
|
|
3426
|
+
// Replace / with skill selection
|
|
3427
|
+
const beforeSlash = currentValue.substring(0, slashPosition);
|
|
3428
|
+
const afterSlash = currentValue.substring(slashPosition + 1 + this.skillSelectorState.currentFilter.length);
|
|
3429
|
+
const newValue = `${beforeSlash}${skillInfo} ${afterSlash}`;
|
|
3430
|
+
|
|
3431
|
+
input.value = newValue;
|
|
3432
|
+
|
|
3433
|
+
// Trigger input change event for validation
|
|
3434
|
+
this.handleTaskInputChange({ target: input });
|
|
3435
|
+
|
|
3436
|
+
// Set cursor position after the inserted text
|
|
3437
|
+
const newCursorPosition = beforeSlash.length + skillInfo.length + 1;
|
|
3438
|
+
input.setSelectionRange(newCursorPosition, newCursorPosition);
|
|
3439
|
+
input.focus();
|
|
3440
|
+
|
|
3441
|
+
// Hide the selector
|
|
3442
|
+
this.hideSkillSelector();
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
getSelectedSkillsForTask() {
|
|
3446
|
+
if (!this.elements.taskInput) return null;
|
|
3447
|
+
|
|
3448
|
+
const inputValue = this.elements.taskInput.value;
|
|
3449
|
+
const SKILL_START_MARKER = '\u200D'; // Zero-width joiner
|
|
3450
|
+
const SKILL_END_MARKER = '\u200E'; // Left-to-right mark
|
|
3451
|
+
|
|
3452
|
+
const skills = [];
|
|
3453
|
+
let startIndex = 0;
|
|
3454
|
+
|
|
3455
|
+
while ((startIndex = inputValue.indexOf(SKILL_START_MARKER, startIndex)) !== -1) {
|
|
3456
|
+
const endIndex = inputValue.indexOf(SKILL_END_MARKER, startIndex);
|
|
3457
|
+
if (endIndex !== -1) {
|
|
3458
|
+
const skillText = inputValue.substring(startIndex + 1, endIndex);
|
|
3459
|
+
if (skillText.startsWith('/')) {
|
|
3460
|
+
skills.push(skillText.substring(1)); // Remove the / prefix
|
|
3461
|
+
}
|
|
3462
|
+
startIndex = endIndex + 1;
|
|
3463
|
+
} else {
|
|
3464
|
+
break;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
return skills.length > 0 ? skills : null;
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
// Export for use in other modules
|
|
3472
|
+
static exportToWindow() {
|
|
3473
|
+
if (typeof window !== 'undefined') {
|
|
3474
|
+
window.VibeSurfUIManager = VibeSurfUIManager;
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3098
3477
|
}
|
|
3099
3478
|
|
|
3100
|
-
//
|
|
3101
|
-
|
|
3102
|
-
window.VibeSurfUIManager = VibeSurfUIManager;
|
|
3103
|
-
}
|
|
3479
|
+
// Call the export method
|
|
3480
|
+
VibeSurfUIManager.exportToWindow();
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
<textarea
|
|
155
155
|
id="task-input"
|
|
156
156
|
class="task-input"
|
|
157
|
-
placeholder="
|
|
157
|
+
placeholder="Ask anything (/ for skills, @ to specify tab)"
|
|
158
158
|
rows="3"></textarea>
|
|
159
159
|
<!-- Tab Selection Dropdown -->
|
|
160
160
|
<div id="tab-selector-dropdown" class="tab-selector-dropdown hidden">
|
|
@@ -173,6 +173,18 @@
|
|
|
173
173
|
</div>
|
|
174
174
|
</div>
|
|
175
175
|
</div>
|
|
176
|
+
|
|
177
|
+
<!-- Skill Selection Dropdown -->
|
|
178
|
+
<div id="skill-selector-dropdown" class="skill-selector-dropdown hidden">
|
|
179
|
+
<div class="skill-selector-header">
|
|
180
|
+
<span class="skill-selector-title">Select Skills</span>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="skill-selector-content">
|
|
183
|
+
<div id="skill-options-list" class="skill-options-list">
|
|
184
|
+
<!-- Skill options will be populated here -->
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
176
188
|
<div class="input-actions">
|
|
177
189
|
<button id="voice-record-btn" class="action-btn voice-record-btn" title="Voice Input">
|
|
178
190
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
@@ -642,4 +642,119 @@ select.task-running-disabled {
|
|
|
642
642
|
.voice-record-btn.recording .recording-duration {
|
|
643
643
|
opacity: 1;
|
|
644
644
|
visibility: visible;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/* Skill Selection Dropdown */
|
|
648
|
+
.skill-selector-dropdown {
|
|
649
|
+
position: fixed !important;
|
|
650
|
+
z-index: 1000;
|
|
651
|
+
background: var(--bg-primary);
|
|
652
|
+
border: 1px solid var(--border-color);
|
|
653
|
+
border-radius: var(--radius-md);
|
|
654
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
655
|
+
max-height: 300px;
|
|
656
|
+
overflow: hidden;
|
|
657
|
+
display: none;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.skill-selector-header {
|
|
661
|
+
display: flex;
|
|
662
|
+
align-items: center;
|
|
663
|
+
justify-content: space-between;
|
|
664
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
665
|
+
background: var(--bg-secondary);
|
|
666
|
+
border-bottom: 1px solid var(--border-color);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
.skill-selector-title {
|
|
670
|
+
font-size: var(--font-size-sm);
|
|
671
|
+
font-weight: var(--font-weight-medium);
|
|
672
|
+
color: var(--text-primary);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.skill-selector-close {
|
|
676
|
+
width: 24px;
|
|
677
|
+
height: 24px;
|
|
678
|
+
border: none;
|
|
679
|
+
background: none;
|
|
680
|
+
font-size: 18px;
|
|
681
|
+
color: var(--text-muted);
|
|
682
|
+
cursor: pointer;
|
|
683
|
+
border-radius: var(--radius-sm);
|
|
684
|
+
display: flex;
|
|
685
|
+
align-items: center;
|
|
686
|
+
justify-content: center;
|
|
687
|
+
transition: all var(--transition-fast);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.skill-selector-close:hover {
|
|
691
|
+
background: var(--bg-hover);
|
|
692
|
+
color: var(--text-primary);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.skill-selector-content {
|
|
696
|
+
max-height: 200px;
|
|
697
|
+
overflow-y: auto;
|
|
698
|
+
padding: var(--spacing-xs);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.skill-options-list {
|
|
702
|
+
display: flex;
|
|
703
|
+
flex-direction: column;
|
|
704
|
+
gap: 2px;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.skill-option {
|
|
708
|
+
display: flex;
|
|
709
|
+
align-items: center;
|
|
710
|
+
gap: var(--spacing-sm);
|
|
711
|
+
padding: var(--spacing-xs) var(--spacing-sm);
|
|
712
|
+
cursor: pointer;
|
|
713
|
+
border-radius: var(--radius-sm);
|
|
714
|
+
transition: background-color var(--transition-fast);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
.skill-option:hover {
|
|
718
|
+
background: var(--bg-hover);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.skill-option.selected {
|
|
722
|
+
background: rgba(0, 122, 204, 0.1);
|
|
723
|
+
border: 1px solid rgba(0, 122, 204, 0.3);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.skill-name {
|
|
727
|
+
flex: 1;
|
|
728
|
+
font-size: var(--font-size-sm);
|
|
729
|
+
color: var(--text-primary);
|
|
730
|
+
overflow: hidden;
|
|
731
|
+
text-overflow: ellipsis;
|
|
732
|
+
white-space: nowrap;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.skill-description {
|
|
736
|
+
font-size: var(--font-size-xs);
|
|
737
|
+
color: var(--text-muted);
|
|
738
|
+
margin-top: 2px;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/* Skill selection indicator in textarea */
|
|
742
|
+
.task-input.has-selected-skills {
|
|
743
|
+
border-color: var(--accent-color);
|
|
744
|
+
background: rgba(40, 167, 69, 0.02);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/* Selected skills display */
|
|
748
|
+
.selected-skills-indicator {
|
|
749
|
+
position: absolute;
|
|
750
|
+
bottom: 8px;
|
|
751
|
+
left: 8px;
|
|
752
|
+
background: var(--accent-color);
|
|
753
|
+
color: white;
|
|
754
|
+
padding: 2px 6px;
|
|
755
|
+
border-radius: var(--radius-sm);
|
|
756
|
+
font-size: 10px;
|
|
757
|
+
font-weight: var(--font-weight-medium);
|
|
758
|
+
pointer-events: none;
|
|
759
|
+
z-index: 2;
|
|
645
760
|
}
|