mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2025-06-22 15:35:04 +00:00
update
This commit is contained in:
parent
e06e0e7e61
commit
5f12aab0a5
4
.gitignore
vendored
4
.gitignore
vendored
@ -0,0 +1,4 @@
|
|||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
_site/
|
@ -39,14 +39,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="flex-1 flex flex-col p-2 sm:p-4">
|
<div class="flex-1 flex flex-col p-2 sm:p-4 min-h-0">
|
||||||
<!-- Top Bar with Context Pills and Edit Button -->
|
<!-- Top Bar with Context Pills and Edit Button -->
|
||||||
<div class="flex justify-between items-start gap-2 mb-0 sm:mb-2">
|
<div class="flex justify-between items-start gap-2 mb-1 sm:mb-2 max-w-full flex-shrink-0">
|
||||||
<!-- Context Pills -->
|
<!-- Context Pills -->
|
||||||
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden pb-1 scrollbar-hide flex-1"></div>
|
<div id="context-pills" class="flex flex-wrap gap-1.5 empty:hidden flex-1 min-w-0 pr-2"></div>
|
||||||
|
|
||||||
<!-- Edit Button -->
|
<!-- Edit Button -->
|
||||||
<button id="edit-button" class="p-1 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors" title="Edit in designer">
|
<button id="edit-button" class="p-1 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors flex-shrink-0" title="Edit in designer">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
||||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||||
@ -55,15 +55,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Prompt Interface - Full Height -->
|
<!-- Main Prompt Interface - Full Height -->
|
||||||
<div class="flex-1 flex flex-col">
|
<div class="flex-1 flex flex-col min-h-0">
|
||||||
<div id="prompt-container" class="flex-1 bg-dynamic-muted border border-dynamic-border rounded-xl p-3 relative focus-within:border-dynamic-primary transition-colors flex flex-col">
|
<div id="prompt-container" class="flex-1 bg-dynamic-muted border border-dynamic-border rounded-xl p-3 relative focus-within:border-dynamic-primary transition-colors flex flex-col min-h-0">
|
||||||
<div id="prompt-text" class="flex-1 text-dynamic-foreground leading-relaxed whitespace-pre-wrap overflow-y-auto custom-scrollbar text-sm sm:text-base"></div>
|
<div id="prompt-text" class="flex-1 text-dynamic-foreground leading-relaxed whitespace-pre-wrap overflow-y-auto custom-scrollbar text-sm sm:text-base"></div>
|
||||||
<div id="prompt-placeholder" class="text-dynamic-muted-foreground italic absolute top-3 sm:top-6 left-3 sm:left-6 pointer-events-none text-sm sm:text-base">← Enter your prompt on designer...</div>
|
<div id="prompt-placeholder" class="text-dynamic-muted-foreground italic absolute top-3 left-3 pointer-events-none text-sm sm:text-base">← Enter your prompt on designer...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom Bar -->
|
<!-- Bottom Bar -->
|
||||||
<div class="flex justify-between items-center gap-2 mt-0 sm:mt-2">
|
<div class="flex justify-between items-center gap-2 mt-1 sm:mt-2 flex-shrink-0">
|
||||||
<!-- Settings Pills -->
|
<!-- Settings Pills -->
|
||||||
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
||||||
|
|
||||||
|
@ -65,9 +65,14 @@
|
|||||||
class="w-full p-3 bg-dynamic-background border border-dynamic-border rounded-lg text-sm resize-none focus-ring custom-scrollbar touch-target"
|
class="w-full p-3 bg-dynamic-background border border-dynamic-border rounded-lg text-sm resize-none focus-ring custom-scrollbar touch-target"
|
||||||
rows="8"
|
rows="8"
|
||||||
placeholder="Enter your prompt..."></textarea>
|
placeholder="Enter your prompt..."></textarea>
|
||||||
<button type="button" id="load-example" class="text-xs text-dynamic-primary hover:text-opacity-80 transition-opacity touch-target py-1">
|
<div class="flex gap-3">
|
||||||
Load example →
|
<button type="button" id="vibe-example" class="text-xs text-dynamic-primary hover:text-opacity-80 transition-opacity touch-target py-1">
|
||||||
|
Vibe coding example →
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" id="chat-example" class="text-xs text-dynamic-primary hover:text-opacity-80 transition-opacity touch-target py-1">
|
||||||
|
Chat example →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Model & Mode -->
|
<!-- Model & Mode -->
|
||||||
@ -229,7 +234,13 @@
|
|||||||
|
|
||||||
<!-- File Tree -->
|
<!-- File Tree -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
<label class="text-sm font-medium text-dynamic-muted-foreground">File Tree</label>
|
<label class="text-sm font-medium text-dynamic-muted-foreground">File Tree</label>
|
||||||
|
<label class="flex items-center space-x-1.5 text-xs">
|
||||||
|
<input type="checkbox" id="designer-show-filetree" class="rounded border-dynamic-border w-3.5 h-3.5" checked>
|
||||||
|
<span class="text-dynamic-muted-foreground">Show in preview</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<textarea id="designer-filetree"
|
<textarea id="designer-filetree"
|
||||||
class="w-full p-3 bg-dynamic-background border border-dynamic-border rounded-lg text-sm resize-none focus-ring custom-scrollbar touch-target font-mono"
|
class="w-full p-3 bg-dynamic-background border border-dynamic-border rounded-lg text-sm resize-none focus-ring custom-scrollbar touch-target font-mono"
|
||||||
rows="6"
|
rows="6"
|
||||||
|
@ -2,6 +2,7 @@ class EmbedPreview {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.params = this.parseURLParams();
|
this.params = this.parseURLParams();
|
||||||
this.config = this.getInitialConfig();
|
this.config = this.getInitialConfig();
|
||||||
|
this.selectedFiles = new Set(); // Track selected files
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,17 +199,27 @@ class EmbedPreview {
|
|||||||
|
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// Render initial context from config
|
||||||
this.config.context.forEach(context => {
|
this.config.context.forEach(context => {
|
||||||
const pill = this.createContextPill(context);
|
const pill = this.createContextPill(context, false);
|
||||||
|
container.appendChild(pill);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render selected files and folders
|
||||||
|
this.selectedFiles.forEach(filePath => {
|
||||||
|
const fileName = this.getFileName(filePath);
|
||||||
|
const pill = this.createContextPill(fileName, true, filePath);
|
||||||
|
pill.title = filePath; // Show full path on hover
|
||||||
container.appendChild(pill);
|
container.appendChild(pill);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createContextPill(context) {
|
createContextPill(context, isRemovable, fullPath = null) {
|
||||||
const pill = document.createElement('div');
|
const pill = document.createElement('div');
|
||||||
pill.className = 'px-2 py-0.5 rounded-lg text-[0.65rem] font-medium animate-slide-in flex items-center gap-1';
|
pill.className = 'px-2 py-0.5 rounded-lg text-[0.65rem] font-medium animate-slide-in flex items-center gap-1 flex-shrink-0 whitespace-nowrap';
|
||||||
|
|
||||||
let icon = '';
|
let icon = '';
|
||||||
|
let content = '';
|
||||||
|
|
||||||
// Use dynamic color classes for all pills
|
// Use dynamic color classes for all pills
|
||||||
if (this.isDarkMode) {
|
if (this.isDarkMode) {
|
||||||
@ -219,24 +230,44 @@ class EmbedPreview {
|
|||||||
|
|
||||||
if (context.startsWith('@')) {
|
if (context.startsWith('@')) {
|
||||||
// @mentions - just show the text
|
// @mentions - just show the text
|
||||||
pill.innerHTML = '<span>' + context + '</span>';
|
content = '<span>' + context + '</span>';
|
||||||
} else if (context.startsWith('http://') || context.startsWith('https://')) {
|
} else if (context.startsWith('http://') || context.startsWith('https://')) {
|
||||||
// Web URLs show world icon
|
// Web URLs show world icon
|
||||||
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM4.332 8.027a6.012 6.012 0 011.912-2.706C6.512 5.73 6.974 6 7.5 6A1.5 1.5 0 019 7.5V8a2 2 0 004 0 2 2 0 011.523-1.943A5.977 5.977 0 0116 10c0 .34-.028.675-.083 1H15a2 2 0 00-2 2v2.197A5.973 5.973 0 0110 16v-2a2 2 0 00-2-2 2 2 0 01-2-2 2 2 0 00-1.668-1.973z" clip-rule="evenodd"/></svg>';
|
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM4.332 8.027a6.012 6.012 0 011.912-2.706C6.512 5.73 6.974 6 7.5 6A1.5 1.5 0 019 7.5V8a2 2 0 004 0 2 2 0 011.523-1.943A5.977 5.977 0 0116 10c0 .34-.028.675-.083 1H15a2 2 0 00-2 2v2.197A5.973 5.973 0 0110 16v-2a2 2 0 00-2-2 2 2 0 01-2-2 2 2 0 00-1.668-1.973z" clip-rule="evenodd"/></svg>';
|
||||||
pill.innerHTML = icon + '<span>' + context + '</span>';
|
content = icon + '<span>' + context + '</span>';
|
||||||
} else if (context.startsWith('#')) {
|
} else if (context.startsWith('#')) {
|
||||||
// Any hashtag context shows image icon
|
// Any hashtag context shows image icon
|
||||||
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"/></svg>';
|
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"/></svg>';
|
||||||
// Remove hash from display
|
// Remove hash from display
|
||||||
pill.innerHTML = icon + '<span>' + context.substring(1) + '</span>';
|
content = icon + '<span>' + context.substring(1) + '</span>';
|
||||||
} else if (context.includes('.')) {
|
} else if (context.endsWith('/')) {
|
||||||
// File context (contains a dot)
|
// Folder context (ends with /)
|
||||||
|
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"/></svg>';
|
||||||
|
// Keep trailing slash for display
|
||||||
|
content = icon + '<span>' + context + '</span>';
|
||||||
|
} else if (context.includes('.') || isRemovable) {
|
||||||
|
// File context (contains a dot) or removable file from sidebar
|
||||||
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V7.414A2 2 0 0017.414 6L14 2.586A2 2 0 0012.586 2H4zm2 4a1 1 0 011-1h4a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7zm-1 5a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1z" clip-rule="evenodd"/></svg>';
|
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V7.414A2 2 0 0017.414 6L14 2.586A2 2 0 0012.586 2H4zm2 4a1 1 0 011-1h4a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7zm-1 5a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1z" clip-rule="evenodd"/></svg>';
|
||||||
pill.innerHTML = icon + '<span>' + context + '</span>';
|
content = icon + '<span>' + context + '</span>';
|
||||||
} else {
|
} else {
|
||||||
// Generic context
|
// Generic context
|
||||||
pill.innerHTML = '<span>' + context + '</span>';
|
content = '<span>' + context + '</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pill.innerHTML = content;
|
||||||
|
|
||||||
|
// Add remove button for removable pills
|
||||||
|
if (isRemovable && fullPath) {
|
||||||
|
const removeBtn = document.createElement('button');
|
||||||
|
removeBtn.className = 'ml-0.5 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors';
|
||||||
|
removeBtn.innerHTML = '<svg class="w-2.5 h-2.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>';
|
||||||
|
removeBtn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.removeFileFromContext(fullPath);
|
||||||
|
});
|
||||||
|
pill.appendChild(removeBtn);
|
||||||
|
}
|
||||||
|
|
||||||
return pill;
|
return pill;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +374,7 @@ class EmbedPreview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
highlightMentions(text) {
|
highlightMentions(text) {
|
||||||
return text.replace(/@([\w\.\/\:\-\&\=\?]+)/g, '<span class="mention">@$1</span>');
|
return text.replace(/@(\w+)/g, '<span class="mention">@$1</span>');
|
||||||
}
|
}
|
||||||
|
|
||||||
capitalizeFirst(str) {
|
capitalizeFirst(str) {
|
||||||
@ -416,6 +447,36 @@ class EmbedPreview {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFileToContext(filePath) {
|
||||||
|
if (!this.selectedFiles.has(filePath)) {
|
||||||
|
this.selectedFiles.add(filePath);
|
||||||
|
this.renderContextPills();
|
||||||
|
this.renderFileTree(); // Re-render to show selection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileName(filePath) {
|
||||||
|
// Extract just the filename or folder name from the path
|
||||||
|
const cleanPath = filePath.endsWith('/') ? filePath.slice(0, -1) : filePath;
|
||||||
|
const parts = cleanPath.split('/');
|
||||||
|
const name = parts[parts.length - 1];
|
||||||
|
// Add trailing slash back for folders to maintain context
|
||||||
|
return filePath.endsWith('/') ? name + '/' : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFileFromContext(filePath) {
|
||||||
|
if (this.selectedFiles.has(filePath)) {
|
||||||
|
this.selectedFiles.delete(filePath);
|
||||||
|
this.renderContextPills();
|
||||||
|
this.renderFileTree(); // Re-render to update selection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFullPath(node, currentPath = '') {
|
||||||
|
// Helper to get the full path from a node
|
||||||
|
return currentPath ? `${currentPath}/${node.name}` : node.name;
|
||||||
|
}
|
||||||
|
|
||||||
buildFileTree(paths) {
|
buildFileTree(paths) {
|
||||||
const tree = {};
|
const tree = {};
|
||||||
|
|
||||||
@ -425,19 +486,26 @@ class EmbedPreview {
|
|||||||
// Remove asterisk if present
|
// Remove asterisk if present
|
||||||
const cleanPath = isHighlighted ? path.slice(0, -1) : path;
|
const cleanPath = isHighlighted ? path.slice(0, -1) : path;
|
||||||
|
|
||||||
const parts = cleanPath.split('/');
|
// Check if it's a folder (ends with /)
|
||||||
|
const isFolder = cleanPath.endsWith('/');
|
||||||
|
|
||||||
|
// Remove trailing slash for processing
|
||||||
|
const processPath = isFolder ? cleanPath.slice(0, -1) : cleanPath;
|
||||||
|
|
||||||
|
const parts = processPath.split('/').filter(p => p !== ''); // Filter out empty strings
|
||||||
let current = tree;
|
let current = tree;
|
||||||
|
|
||||||
parts.forEach((part, index) => {
|
parts.forEach((part, index) => {
|
||||||
if (!current[part]) {
|
if (!current[part]) {
|
||||||
|
const isLastPart = index === parts.length - 1;
|
||||||
current[part] = {
|
current[part] = {
|
||||||
name: part,
|
name: part,
|
||||||
isFile: index === parts.length - 1,
|
isFile: isLastPart && !isFolder,
|
||||||
isHighlighted: index === parts.length - 1 && isHighlighted,
|
isHighlighted: isLastPart && isHighlighted,
|
||||||
children: {}
|
children: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (index < parts.length - 1) {
|
if (index < parts.length - 1 || (index === parts.length - 1 && !current[part].isFile)) {
|
||||||
current = current[part].children;
|
current = current[part].children;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -463,13 +531,13 @@ class EmbedPreview {
|
|||||||
treeContainer.innerHTML = '';
|
treeContainer.innerHTML = '';
|
||||||
|
|
||||||
// Render tree
|
// Render tree
|
||||||
this.renderTreeNode(tree, treeContainer, 0);
|
this.renderTreeNode(tree, treeContainer, 0, '');
|
||||||
} else {
|
} else {
|
||||||
sidebar.classList.add('hidden');
|
sidebar.classList.add('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTreeNode(node, container, level) {
|
renderTreeNode(node, container, level, parentPath) {
|
||||||
const sortedKeys = Object.keys(node).sort((a, b) => {
|
const sortedKeys = Object.keys(node).sort((a, b) => {
|
||||||
// Folders first, then files
|
// Folders first, then files
|
||||||
const aIsFile = node[a].isFile;
|
const aIsFile = node[a].isFile;
|
||||||
@ -480,10 +548,17 @@ class EmbedPreview {
|
|||||||
|
|
||||||
sortedKeys.forEach(key => {
|
sortedKeys.forEach(key => {
|
||||||
const item = node[key];
|
const item = node[key];
|
||||||
|
const fullPath = parentPath ? `${parentPath}/${item.name}` : item.name;
|
||||||
const itemElement = document.createElement('div');
|
const itemElement = document.createElement('div');
|
||||||
|
|
||||||
// Add highlighting class if the file is marked
|
// Check if file or folder is selected
|
||||||
|
const contextPath = item.isFile ? fullPath : fullPath + '/';
|
||||||
|
const isSelected = this.selectedFiles.has(contextPath);
|
||||||
|
|
||||||
|
// Add highlighting class if the file is marked or selected
|
||||||
if (item.isHighlighted) {
|
if (item.isHighlighted) {
|
||||||
|
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 bg-dynamic-primary/20 rounded text-xs text-dynamic-foreground font-medium transition-all';
|
||||||
|
} else if (isSelected) {
|
||||||
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 bg-dynamic-primary/20 rounded cursor-pointer text-xs text-dynamic-foreground font-medium transition-all hover:bg-dynamic-primary/30';
|
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 bg-dynamic-primary/20 rounded cursor-pointer text-xs text-dynamic-foreground font-medium transition-all hover:bg-dynamic-primary/30';
|
||||||
} else {
|
} else {
|
||||||
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 hover:bg-dynamic-primary/10 rounded cursor-pointer text-xs text-dynamic-foreground/80 hover:text-dynamic-foreground transition-colors';
|
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 hover:bg-dynamic-primary/10 rounded cursor-pointer text-xs text-dynamic-foreground/80 hover:text-dynamic-foreground transition-colors';
|
||||||
@ -491,6 +566,23 @@ class EmbedPreview {
|
|||||||
|
|
||||||
itemElement.style.paddingLeft = `${level * 12 + 6}px`;
|
itemElement.style.paddingLeft = `${level * 12 + 6}px`;
|
||||||
|
|
||||||
|
// Add click handler for files and folders (but not starred items)
|
||||||
|
if (!item.isHighlighted) {
|
||||||
|
itemElement.addEventListener('click', () => {
|
||||||
|
const contextPath = item.isFile ? fullPath : fullPath + '/';
|
||||||
|
if (isSelected) {
|
||||||
|
this.removeFileFromContext(contextPath);
|
||||||
|
} else {
|
||||||
|
this.addFileToContext(contextPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
itemElement.title = isSelected ? 'Click to remove from context' : 'Click to add to context';
|
||||||
|
itemElement.style.cursor = 'pointer';
|
||||||
|
} else {
|
||||||
|
itemElement.style.cursor = 'default';
|
||||||
|
itemElement.title = 'Starred items cannot be selected';
|
||||||
|
}
|
||||||
|
|
||||||
// Add icon
|
// Add icon
|
||||||
const icon = document.createElement('span');
|
const icon = document.createElement('span');
|
||||||
icon.className = 'flex-shrink-0';
|
icon.className = 'flex-shrink-0';
|
||||||
@ -500,8 +592,8 @@ class EmbedPreview {
|
|||||||
const ext = key.split('.').pop().toLowerCase();
|
const ext = key.split('.').pop().toLowerCase();
|
||||||
let iconColor = 'text-dynamic-muted-foreground';
|
let iconColor = 'text-dynamic-muted-foreground';
|
||||||
|
|
||||||
// If highlighted, use primary color for icon
|
// If highlighted or selected, use primary color for icon
|
||||||
if (item.isHighlighted) {
|
if (item.isHighlighted || isSelected) {
|
||||||
iconColor = 'text-dynamic-primary';
|
iconColor = 'text-dynamic-primary';
|
||||||
} else {
|
} else {
|
||||||
// Color code common file types
|
// Color code common file types
|
||||||
@ -536,19 +628,35 @@ class EmbedPreview {
|
|||||||
itemElement.appendChild(icon);
|
itemElement.appendChild(icon);
|
||||||
itemElement.appendChild(nameSpan);
|
itemElement.appendChild(nameSpan);
|
||||||
|
|
||||||
// Add a star indicator for highlighted files
|
// Add indicators for highlighted or selected items
|
||||||
|
if (item.isHighlighted || isSelected) {
|
||||||
|
const indicatorContainer = document.createElement('span');
|
||||||
|
indicatorContainer.className = 'ml-auto flex items-center gap-1';
|
||||||
|
|
||||||
|
// Add star for highlighted items
|
||||||
if (item.isHighlighted) {
|
if (item.isHighlighted) {
|
||||||
const starIcon = document.createElement('span');
|
const starIcon = document.createElement('span');
|
||||||
starIcon.className = 'ml-auto text-dynamic-primary';
|
starIcon.className = 'text-dynamic-primary';
|
||||||
starIcon.innerHTML = '<svg class="w-2.5 h-2.5" viewBox="0 0 20 20" fill="currentColor"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg>';
|
starIcon.innerHTML = '<svg class="w-2.5 h-2.5" viewBox="0 0 20 20" fill="currentColor"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg>';
|
||||||
itemElement.appendChild(starIcon);
|
indicatorContainer.appendChild(starIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add checkmark for selected items (not highlighted)
|
||||||
|
if (isSelected && !item.isHighlighted) {
|
||||||
|
const checkIcon = document.createElement('span');
|
||||||
|
checkIcon.className = 'text-dynamic-primary';
|
||||||
|
checkIcon.innerHTML = '<svg class="w-2.5 h-2.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>';
|
||||||
|
indicatorContainer.appendChild(checkIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
itemElement.appendChild(indicatorContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
container.appendChild(itemElement);
|
container.appendChild(itemElement);
|
||||||
|
|
||||||
// Recursively render children
|
// Recursively render children
|
||||||
if (!item.isFile && Object.keys(item.children).length > 0) {
|
if (!item.isFile && Object.keys(item.children).length > 0) {
|
||||||
this.renderTreeNode(item.children, container, level + 1);
|
this.renderTreeNode(item.children, container, level + 1, fullPath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,8 @@
|
|||||||
display: none; /* Safari and Chrome */
|
display: none; /* Safari and Chrome */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Touch target improvements for mobile */
|
/* Touch target improvements for mobile */
|
||||||
.touch-target {
|
.touch-target {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
@ -101,16 +103,15 @@
|
|||||||
|
|
||||||
/* Fixed height for prompt container to enable scrolling */
|
/* Fixed height for prompt container to enable scrolling */
|
||||||
#prompt-container {
|
#prompt-container {
|
||||||
height: calc(100vh - 140px); /* Fixed height minus header and footer space */
|
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
max-height: calc(100vh - 120px);
|
flex: 1;
|
||||||
/* max-height: 100vh; */
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
#prompt-container {
|
#prompt-container {
|
||||||
height: calc(100vh - 90px); /* Smaller on mobile */
|
min-height: 80px;
|
||||||
max-height: calc(100vh - 90px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,11 +124,10 @@
|
|||||||
|
|
||||||
/* Responsive improvements */
|
/* Responsive improvements */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
/* Ensure context pills don't wrap and scroll horizontally */
|
/* Context pills can wrap on mobile too */
|
||||||
#context-pills {
|
#context-pills {
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap !important;
|
||||||
scrollbar-width: none;
|
max-height: 80px; /* Smaller on mobile */
|
||||||
-ms-overflow-style: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* More compact pill spacing on mobile */
|
/* More compact pill spacing on mobile */
|
||||||
@ -192,6 +192,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Context pills container improvements */
|
||||||
|
#context-pills {
|
||||||
|
max-width: calc(100% - 40px); /* Reserve space for edit button */
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-height: 120px; /* Limit context pills height */
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar for context pills */
|
||||||
|
#context-pills {
|
||||||
|
scrollbar-width: thin; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-pills::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-pills::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-pills::-webkit-scrollbar-thumb {
|
||||||
|
background: rgb(var(--border) / 0.5);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-pills::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgb(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure pills maintain size */
|
||||||
|
#context-pills > div {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ensure proper touch behavior */
|
/* Ensure proper touch behavior */
|
||||||
@media (hover: none) and (pointer: coarse) {
|
@media (hover: none) and (pointer: coarse) {
|
||||||
/* All interactive elements get proper touch targets */
|
/* All interactive elements get proper touch targets */
|
||||||
|
@ -39,14 +39,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="flex-1 flex flex-col p-2 sm:p-4">
|
<div class="flex-1 flex flex-col p-2 sm:p-4 min-h-0">
|
||||||
<!-- Top Bar with Context Pills and Edit Button -->
|
<!-- Top Bar with Context Pills and Edit Button -->
|
||||||
<div class="flex justify-between items-start gap-2 mb-0 sm:mb-2">
|
<div class="flex justify-between items-start gap-2 mb-1 sm:mb-2 max-w-full flex-shrink-0">
|
||||||
<!-- Context Pills -->
|
<!-- Context Pills -->
|
||||||
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden pb-1 scrollbar-hide flex-1"></div>
|
<div id="context-pills" class="flex flex-wrap gap-1.5 empty:hidden flex-1 min-w-0 pr-2"></div>
|
||||||
|
|
||||||
<!-- Edit Button -->
|
<!-- Edit Button -->
|
||||||
<button id="edit-button" class="p-1 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors" title="Edit in designer">
|
<button id="edit-button" class="p-1 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors flex-shrink-0" title="Edit in designer">
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
||||||
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
||||||
@ -55,15 +55,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Prompt Interface - Full Height -->
|
<!-- Main Prompt Interface - Full Height -->
|
||||||
<div class="flex-1 flex flex-col">
|
<div class="flex-1 flex flex-col min-h-0">
|
||||||
<div id="prompt-container" class="flex-1 bg-dynamic-muted border border-dynamic-border rounded-xl p-3 relative focus-within:border-dynamic-primary transition-colors flex flex-col">
|
<div id="prompt-container" class="flex-1 bg-dynamic-muted border border-dynamic-border rounded-xl p-3 relative focus-within:border-dynamic-primary transition-colors flex flex-col min-h-0">
|
||||||
<div id="prompt-text" class="flex-1 text-dynamic-foreground leading-relaxed whitespace-pre-wrap overflow-y-auto custom-scrollbar text-sm sm:text-base"></div>
|
<div id="prompt-text" class="flex-1 text-dynamic-foreground leading-relaxed whitespace-pre-wrap overflow-y-auto custom-scrollbar text-sm sm:text-base"></div>
|
||||||
<div id="prompt-placeholder" class="text-dynamic-muted-foreground italic absolute top-3 sm:top-6 left-3 sm:left-6 pointer-events-none text-sm sm:text-base">← Enter your prompt on designer...</div>
|
<div id="prompt-placeholder" class="text-dynamic-muted-foreground italic absolute top-3 left-3 pointer-events-none text-sm sm:text-base">← Enter your prompt on designer...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom Bar -->
|
<!-- Bottom Bar -->
|
||||||
<div class="flex justify-between items-center gap-2 mt-0 sm:mt-2">
|
<div class="flex justify-between items-center gap-2 mt-1 sm:mt-2 flex-shrink-0">
|
||||||
<!-- Settings Pills -->
|
<!-- Settings Pills -->
|
||||||
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
|
||||||
|
|
||||||
|
@ -33,7 +33,8 @@ class EmbedDesigner {
|
|||||||
darkColor: this.params.darkColor || savedConfig.darkColor || '#60a5fa',
|
darkColor: this.params.darkColor || savedConfig.darkColor || '#60a5fa',
|
||||||
height: this.params.height || savedConfig.height || '400',
|
height: this.params.height || savedConfig.height || '400',
|
||||||
themeMode: this.params.themeMode || savedConfig.themeMode || 'auto',
|
themeMode: this.params.themeMode || savedConfig.themeMode || 'auto',
|
||||||
filetree: this.params.filetree ? decodeURIComponent(this.params.filetree).split('\n').filter(f => f.trim()) : (savedConfig.filetree || [])
|
filetree: this.params.filetree ? decodeURIComponent(this.params.filetree).split('\n').filter(f => f.trim()) : (savedConfig.filetree || []),
|
||||||
|
showFiletree: savedConfig.showFiletree !== undefined ? savedConfig.showFiletree : true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +56,8 @@ class EmbedDesigner {
|
|||||||
darkColor: '#60a5fa',
|
darkColor: '#60a5fa',
|
||||||
height: '400',
|
height: '400',
|
||||||
themeMode: 'auto',
|
themeMode: 'auto',
|
||||||
filetree: []
|
filetree: [],
|
||||||
|
showFiletree: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +80,8 @@ class EmbedDesigner {
|
|||||||
darkColor: config.darkColor || '#60a5fa',
|
darkColor: config.darkColor || '#60a5fa',
|
||||||
height: config.height || '400',
|
height: config.height || '400',
|
||||||
themeMode: config.themeMode || 'auto',
|
themeMode: config.themeMode || 'auto',
|
||||||
filetree: config.filetree || []
|
filetree: config.filetree || [],
|
||||||
|
showFiletree: config.showFiletree !== undefined ? config.showFiletree : true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,6 +144,12 @@ class EmbedDesigner {
|
|||||||
document.getElementById('designer-thinking').checked = this.config.thinking;
|
document.getElementById('designer-thinking').checked = this.config.thinking;
|
||||||
document.getElementById('designer-max').checked = this.config.max;
|
document.getElementById('designer-max').checked = this.config.max;
|
||||||
|
|
||||||
|
// Set show filetree checkbox (default to true if not set)
|
||||||
|
const showFiletreeCheckbox = document.getElementById('designer-show-filetree');
|
||||||
|
if (showFiletreeCheckbox) {
|
||||||
|
showFiletreeCheckbox.checked = this.config.showFiletree !== false;
|
||||||
|
}
|
||||||
|
|
||||||
// Set height slider
|
// Set height slider
|
||||||
const heightSlider = document.getElementById('designer-height');
|
const heightSlider = document.getElementById('designer-height');
|
||||||
const heightValue = document.getElementById('height-value');
|
const heightValue = document.getElementById('height-value');
|
||||||
@ -186,10 +195,12 @@ class EmbedDesigner {
|
|||||||
|
|
||||||
setupDesignerEvents() {
|
setupDesignerEvents() {
|
||||||
// Form changes update preview
|
// Form changes update preview
|
||||||
['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max', 'designer-filetree'].forEach(id => {
|
['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max', 'designer-filetree', 'designer-show-filetree'].forEach(id => {
|
||||||
const element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
element.addEventListener('input', () => this.updateConfigFromForm());
|
element.addEventListener('input', () => this.updateConfigFromForm());
|
||||||
element.addEventListener('change', () => this.updateConfigFromForm());
|
element.addEventListener('change', () => this.updateConfigFromForm());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Theme mode buttons
|
// Theme mode buttons
|
||||||
@ -317,7 +328,8 @@ class EmbedDesigner {
|
|||||||
darkColor: '#60a5fa',
|
darkColor: '#60a5fa',
|
||||||
height: '400',
|
height: '400',
|
||||||
themeMode: 'auto',
|
themeMode: 'auto',
|
||||||
filetree: []
|
filetree: [],
|
||||||
|
showFiletree: true
|
||||||
};
|
};
|
||||||
// Update UI to reflect defaults
|
// Update UI to reflect defaults
|
||||||
this.setupDesignerElements();
|
this.setupDesignerElements();
|
||||||
@ -326,9 +338,13 @@ class EmbedDesigner {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load example button
|
// Example buttons
|
||||||
document.getElementById('load-example').addEventListener('click', () => {
|
document.getElementById('vibe-example').addEventListener('click', () => {
|
||||||
this.loadExample();
|
this.loadVibeExample();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('chat-example').addEventListener('click', () => {
|
||||||
|
this.loadChatExample();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modal events
|
// Modal events
|
||||||
@ -366,7 +382,8 @@ class EmbedDesigner {
|
|||||||
darkColor: darkColorText ? darkColorText.value : '#60a5fa',
|
darkColor: darkColorText ? darkColorText.value : '#60a5fa',
|
||||||
height: heightSlider ? heightSlider.value : '400',
|
height: heightSlider ? heightSlider.value : '400',
|
||||||
themeMode: this.config.themeMode || 'auto',
|
themeMode: this.config.themeMode || 'auto',
|
||||||
filetree: document.getElementById('designer-filetree').value.split('\n').map(f => f.trim()).filter(f => f)
|
filetree: document.getElementById('designer-filetree').value.split('\n').map(f => f.trim()).filter(f => f),
|
||||||
|
showFiletree: document.getElementById('designer-show-filetree').checked
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
@ -439,7 +456,7 @@ class EmbedDesigner {
|
|||||||
if (this.config.lightColor !== '#3b82f6') params.set('lightColor', this.config.lightColor);
|
if (this.config.lightColor !== '#3b82f6') params.set('lightColor', this.config.lightColor);
|
||||||
if (this.config.darkColor !== '#60a5fa') params.set('darkColor', this.config.darkColor);
|
if (this.config.darkColor !== '#60a5fa') params.set('darkColor', this.config.darkColor);
|
||||||
if (this.config.themeMode !== 'auto') params.set('themeMode', this.config.themeMode);
|
if (this.config.themeMode !== 'auto') params.set('themeMode', this.config.themeMode);
|
||||||
if (this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
|
if (this.config.showFiletree && this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
|
||||||
params.set('preview', 'true');
|
params.set('preview', 'true');
|
||||||
|
|
||||||
return `/embed-preview/?${params.toString()}`;
|
return `/embed-preview/?${params.toString()}`;
|
||||||
@ -456,7 +473,7 @@ class EmbedDesigner {
|
|||||||
if (this.config.lightColor !== '#3b82f6') params.set('lightColor', this.config.lightColor);
|
if (this.config.lightColor !== '#3b82f6') params.set('lightColor', this.config.lightColor);
|
||||||
if (this.config.darkColor !== '#60a5fa') params.set('darkColor', this.config.darkColor);
|
if (this.config.darkColor !== '#60a5fa') params.set('darkColor', this.config.darkColor);
|
||||||
if (this.config.themeMode !== 'auto') params.set('themeMode', this.config.themeMode);
|
if (this.config.themeMode !== 'auto') params.set('themeMode', this.config.themeMode);
|
||||||
if (this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
|
if (this.config.showFiletree && this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
|
||||||
|
|
||||||
return `${window.location.origin}/embed-preview/?${params.toString()}`;
|
return `${window.location.origin}/embed-preview/?${params.toString()}`;
|
||||||
}
|
}
|
||||||
@ -537,42 +554,77 @@ class EmbedDesigner {
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadExample() {
|
loadVibeExample() {
|
||||||
// Set example values
|
// Set vibe coding example values
|
||||||
document.getElementById('designer-prompt').value =
|
document.getElementById('designer-prompt').value =
|
||||||
`You are a senior React developer. I need help building a modern e-commerce product listing component.
|
`I'm working on a React e-commerce app. The current ProductList component is getting messy with too much logic.
|
||||||
|
|
||||||
Requirements:
|
Can you help me refactor it to:
|
||||||
- Use React hooks and functional components
|
1. Extract the filtering logic into a custom hook
|
||||||
- Implement product filtering by category and price range
|
2. Split the large component into smaller, reusable pieces
|
||||||
- Add smooth animations for product cards
|
3. Add proper TypeScript types
|
||||||
- Make it fully responsive with a grid layout
|
4. Implement virtualization for better performance with large product lists
|
||||||
- Include loading states and error handling
|
|
||||||
|
|
||||||
The component should fetch data from a REST API and display products with images, titles, prices, and ratings. Please provide clean, well-commented code following React best practices.`;
|
The component currently handles products display, filtering by category/price, sorting, and pagination all in one file. I want a cleaner architecture.`;
|
||||||
|
|
||||||
document.getElementById('designer-context').value = '@codebase, ProductList.jsx';
|
document.getElementById('designer-context').value = '@codebase, ProductList.tsx, components/, hooks/';
|
||||||
document.getElementById('designer-filetree').value =
|
document.getElementById('designer-filetree').value =
|
||||||
`src/components/ProductList.jsx*
|
`src/
|
||||||
src/components/ProductCard.jsx
|
src/components/
|
||||||
src/components/Filters.jsx
|
src/components/ProductList.tsx*
|
||||||
src/hooks/useProducts.js
|
src/components/ProductCard.tsx
|
||||||
src/api/products.js
|
src/components/ProductGrid.tsx
|
||||||
src/styles/products.css
|
src/components/Filters/
|
||||||
src/utils/formatters.js
|
src/components/Filters/CategoryFilter.tsx
|
||||||
public/index.html
|
src/components/Filters/PriceRangeFilter.tsx
|
||||||
|
src/components/Filters/index.tsx
|
||||||
|
src/hooks/
|
||||||
|
src/hooks/useProducts.ts
|
||||||
|
src/hooks/useFilters.ts
|
||||||
|
src/hooks/useInfiniteScroll.ts
|
||||||
|
src/types/
|
||||||
|
src/types/product.ts
|
||||||
|
src/api/
|
||||||
|
src/api/products.ts
|
||||||
|
src/utils/
|
||||||
|
src/utils/formatters.ts
|
||||||
package.json
|
package.json
|
||||||
README.md`;
|
tsconfig.json`;
|
||||||
|
|
||||||
// Set some example settings
|
// Set vibe coding settings
|
||||||
document.getElementById('designer-model').value = 'Claude 3.7 Sonnet';
|
document.getElementById('designer-model').value = 'Claude 4 Opus';
|
||||||
document.getElementById('designer-mode-select').value = 'agent';
|
document.getElementById('designer-mode-select').value = 'agent';
|
||||||
document.getElementById('designer-thinking').checked = true;
|
document.getElementById('designer-thinking').checked = true;
|
||||||
|
document.getElementById('designer-max').checked = true;
|
||||||
|
document.getElementById('designer-show-filetree').checked = true;
|
||||||
|
|
||||||
// Update config from form
|
// Update config from form
|
||||||
this.updateConfigFromForm();
|
this.updateConfigFromForm();
|
||||||
|
|
||||||
this.showNotification('Example loaded!');
|
this.showNotification('Vibe coding example loaded!');
|
||||||
|
}
|
||||||
|
|
||||||
|
loadChatExample() {
|
||||||
|
// Set chat example values
|
||||||
|
document.getElementById('designer-prompt').value =
|
||||||
|
`I'm planning a dinner party for 8 people this weekend. One person is vegetarian, another is gluten-free, and I want to make something impressive but not too complicated.
|
||||||
|
|
||||||
|
Can you suggest a menu with appetizers, main course, and dessert that would work for everyone? I have about 4 hours to prepare everything and a moderate cooking skill level.`;
|
||||||
|
|
||||||
|
document.getElementById('designer-context').value = '#Screenshot Kitchen Layout.png, Recipes Collection.pdf';
|
||||||
|
document.getElementById('designer-filetree').value = '';
|
||||||
|
|
||||||
|
// Set chat settings
|
||||||
|
document.getElementById('designer-model').value = 'GPT 4o';
|
||||||
|
document.getElementById('designer-mode-select').value = 'chat';
|
||||||
|
document.getElementById('designer-thinking').checked = false;
|
||||||
|
document.getElementById('designer-max').checked = false;
|
||||||
|
document.getElementById('designer-show-filetree').checked = false;
|
||||||
|
|
||||||
|
// Update config from form
|
||||||
|
this.updateConfigFromForm();
|
||||||
|
|
||||||
|
this.showNotification('Chat example loaded!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,9 +65,14 @@
|
|||||||
class="w-full p-3 bg-dynamic-background border border-dynamic-border rounded-lg text-sm resize-none focus-ring custom-scrollbar touch-target"
|
class="w-full p-3 bg-dynamic-background border border-dynamic-border rounded-lg text-sm resize-none focus-ring custom-scrollbar touch-target"
|
||||||
rows="8"
|
rows="8"
|
||||||
placeholder="Enter your prompt..."></textarea>
|
placeholder="Enter your prompt..."></textarea>
|
||||||
<button type="button" id="load-example" class="text-xs text-dynamic-primary hover:text-opacity-80 transition-opacity touch-target py-1">
|
<div class="flex gap-3">
|
||||||
Load example →
|
<button type="button" id="vibe-example" class="text-xs text-dynamic-primary hover:text-opacity-80 transition-opacity touch-target py-1">
|
||||||
|
Vibe coding example →
|
||||||
</button>
|
</button>
|
||||||
|
<button type="button" id="chat-example" class="text-xs text-dynamic-primary hover:text-opacity-80 transition-opacity touch-target py-1">
|
||||||
|
Chat example →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Model & Mode -->
|
<!-- Model & Mode -->
|
||||||
@ -229,7 +234,13 @@
|
|||||||
|
|
||||||
<!-- File Tree -->
|
<!-- File Tree -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
<label class="text-sm font-medium text-dynamic-muted-foreground">File Tree</label>
|
<label class="text-sm font-medium text-dynamic-muted-foreground">File Tree</label>
|
||||||
|
<label class="flex items-center space-x-1.5 text-xs">
|
||||||
|
<input type="checkbox" id="designer-show-filetree" class="rounded border-dynamic-border w-3.5 h-3.5" checked>
|
||||||
|
<span class="text-dynamic-muted-foreground">Show in preview</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<textarea id="designer-filetree"
|
<textarea id="designer-filetree"
|
||||||
class="w-full p-3 bg-dynamic-background border border-dynamic-border rounded-lg text-sm resize-none focus-ring custom-scrollbar touch-target font-mono"
|
class="w-full p-3 bg-dynamic-background border border-dynamic-border rounded-lg text-sm resize-none focus-ring custom-scrollbar touch-target font-mono"
|
||||||
rows="6"
|
rows="6"
|
||||||
|
@ -2,6 +2,7 @@ class EmbedPreview {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.params = this.parseURLParams();
|
this.params = this.parseURLParams();
|
||||||
this.config = this.getInitialConfig();
|
this.config = this.getInitialConfig();
|
||||||
|
this.selectedFiles = new Set(); // Track selected files
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,17 +199,27 @@ class EmbedPreview {
|
|||||||
|
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// Render initial context from config
|
||||||
this.config.context.forEach(context => {
|
this.config.context.forEach(context => {
|
||||||
const pill = this.createContextPill(context);
|
const pill = this.createContextPill(context, false);
|
||||||
|
container.appendChild(pill);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Render selected files and folders
|
||||||
|
this.selectedFiles.forEach(filePath => {
|
||||||
|
const fileName = this.getFileName(filePath);
|
||||||
|
const pill = this.createContextPill(fileName, true, filePath);
|
||||||
|
pill.title = filePath; // Show full path on hover
|
||||||
container.appendChild(pill);
|
container.appendChild(pill);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createContextPill(context) {
|
createContextPill(context, isRemovable, fullPath = null) {
|
||||||
const pill = document.createElement('div');
|
const pill = document.createElement('div');
|
||||||
pill.className = 'px-2 py-0.5 rounded-lg text-[0.65rem] font-medium animate-slide-in flex items-center gap-1';
|
pill.className = 'px-2 py-0.5 rounded-lg text-[0.65rem] font-medium animate-slide-in flex items-center gap-1 flex-shrink-0 whitespace-nowrap';
|
||||||
|
|
||||||
let icon = '';
|
let icon = '';
|
||||||
|
let content = '';
|
||||||
|
|
||||||
// Use dynamic color classes for all pills
|
// Use dynamic color classes for all pills
|
||||||
if (this.isDarkMode) {
|
if (this.isDarkMode) {
|
||||||
@ -219,24 +230,44 @@ class EmbedPreview {
|
|||||||
|
|
||||||
if (context.startsWith('@')) {
|
if (context.startsWith('@')) {
|
||||||
// @mentions - just show the text
|
// @mentions - just show the text
|
||||||
pill.innerHTML = '<span>' + context + '</span>';
|
content = '<span>' + context + '</span>';
|
||||||
} else if (context.startsWith('http://') || context.startsWith('https://')) {
|
} else if (context.startsWith('http://') || context.startsWith('https://')) {
|
||||||
// Web URLs show world icon
|
// Web URLs show world icon
|
||||||
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM4.332 8.027a6.012 6.012 0 011.912-2.706C6.512 5.73 6.974 6 7.5 6A1.5 1.5 0 019 7.5V8a2 2 0 004 0 2 2 0 011.523-1.943A5.977 5.977 0 0116 10c0 .34-.028.675-.083 1H15a2 2 0 00-2 2v2.197A5.973 5.973 0 0110 16v-2a2 2 0 00-2-2 2 2 0 01-2-2 2 2 0 00-1.668-1.973z" clip-rule="evenodd"/></svg>';
|
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM4.332 8.027a6.012 6.012 0 011.912-2.706C6.512 5.73 6.974 6 7.5 6A1.5 1.5 0 019 7.5V8a2 2 0 004 0 2 2 0 011.523-1.943A5.977 5.977 0 0116 10c0 .34-.028.675-.083 1H15a2 2 0 00-2 2v2.197A5.973 5.973 0 0110 16v-2a2 2 0 00-2-2 2 2 0 01-2-2 2 2 0 00-1.668-1.973z" clip-rule="evenodd"/></svg>';
|
||||||
pill.innerHTML = icon + '<span>' + context + '</span>';
|
content = icon + '<span>' + context + '</span>';
|
||||||
} else if (context.startsWith('#')) {
|
} else if (context.startsWith('#')) {
|
||||||
// Any hashtag context shows image icon
|
// Any hashtag context shows image icon
|
||||||
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"/></svg>';
|
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"/></svg>';
|
||||||
// Remove hash from display
|
// Remove hash from display
|
||||||
pill.innerHTML = icon + '<span>' + context.substring(1) + '</span>';
|
content = icon + '<span>' + context.substring(1) + '</span>';
|
||||||
} else if (context.includes('.')) {
|
} else if (context.endsWith('/')) {
|
||||||
// File context (contains a dot)
|
// Folder context (ends with /)
|
||||||
|
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"/></svg>';
|
||||||
|
// Keep trailing slash for display
|
||||||
|
content = icon + '<span>' + context + '</span>';
|
||||||
|
} else if (context.includes('.') || isRemovable) {
|
||||||
|
// File context (contains a dot) or removable file from sidebar
|
||||||
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V7.414A2 2 0 0017.414 6L14 2.586A2 2 0 0012.586 2H4zm2 4a1 1 0 011-1h4a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7zm-1 5a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1z" clip-rule="evenodd"/></svg>';
|
icon = '<svg class="w-3 h-3" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 2a2 2 0 00-2 2v12a2 2 0 002 2h12a2 2 0 002-2V7.414A2 2 0 0017.414 6L14 2.586A2 2 0 0012.586 2H4zm2 4a1 1 0 011-1h4a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7zm-1 5a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1z" clip-rule="evenodd"/></svg>';
|
||||||
pill.innerHTML = icon + '<span>' + context + '</span>';
|
content = icon + '<span>' + context + '</span>';
|
||||||
} else {
|
} else {
|
||||||
// Generic context
|
// Generic context
|
||||||
pill.innerHTML = '<span>' + context + '</span>';
|
content = '<span>' + context + '</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pill.innerHTML = content;
|
||||||
|
|
||||||
|
// Add remove button for removable pills
|
||||||
|
if (isRemovable && fullPath) {
|
||||||
|
const removeBtn = document.createElement('button');
|
||||||
|
removeBtn.className = 'ml-0.5 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors';
|
||||||
|
removeBtn.innerHTML = '<svg class="w-2.5 h-2.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg>';
|
||||||
|
removeBtn.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.removeFileFromContext(fullPath);
|
||||||
|
});
|
||||||
|
pill.appendChild(removeBtn);
|
||||||
|
}
|
||||||
|
|
||||||
return pill;
|
return pill;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,6 +447,36 @@ class EmbedPreview {
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFileToContext(filePath) {
|
||||||
|
if (!this.selectedFiles.has(filePath)) {
|
||||||
|
this.selectedFiles.add(filePath);
|
||||||
|
this.renderContextPills();
|
||||||
|
this.renderFileTree(); // Re-render to show selection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFileName(filePath) {
|
||||||
|
// Extract just the filename or folder name from the path
|
||||||
|
const cleanPath = filePath.endsWith('/') ? filePath.slice(0, -1) : filePath;
|
||||||
|
const parts = cleanPath.split('/');
|
||||||
|
const name = parts[parts.length - 1];
|
||||||
|
// Add trailing slash back for folders to maintain context
|
||||||
|
return filePath.endsWith('/') ? name + '/' : name;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFileFromContext(filePath) {
|
||||||
|
if (this.selectedFiles.has(filePath)) {
|
||||||
|
this.selectedFiles.delete(filePath);
|
||||||
|
this.renderContextPills();
|
||||||
|
this.renderFileTree(); // Re-render to update selection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getFullPath(node, currentPath = '') {
|
||||||
|
// Helper to get the full path from a node
|
||||||
|
return currentPath ? `${currentPath}/${node.name}` : node.name;
|
||||||
|
}
|
||||||
|
|
||||||
buildFileTree(paths) {
|
buildFileTree(paths) {
|
||||||
const tree = {};
|
const tree = {};
|
||||||
|
|
||||||
@ -425,19 +486,26 @@ class EmbedPreview {
|
|||||||
// Remove asterisk if present
|
// Remove asterisk if present
|
||||||
const cleanPath = isHighlighted ? path.slice(0, -1) : path;
|
const cleanPath = isHighlighted ? path.slice(0, -1) : path;
|
||||||
|
|
||||||
const parts = cleanPath.split('/');
|
// Check if it's a folder (ends with /)
|
||||||
|
const isFolder = cleanPath.endsWith('/');
|
||||||
|
|
||||||
|
// Remove trailing slash for processing
|
||||||
|
const processPath = isFolder ? cleanPath.slice(0, -1) : cleanPath;
|
||||||
|
|
||||||
|
const parts = processPath.split('/').filter(p => p !== ''); // Filter out empty strings
|
||||||
let current = tree;
|
let current = tree;
|
||||||
|
|
||||||
parts.forEach((part, index) => {
|
parts.forEach((part, index) => {
|
||||||
if (!current[part]) {
|
if (!current[part]) {
|
||||||
|
const isLastPart = index === parts.length - 1;
|
||||||
current[part] = {
|
current[part] = {
|
||||||
name: part,
|
name: part,
|
||||||
isFile: index === parts.length - 1,
|
isFile: isLastPart && !isFolder,
|
||||||
isHighlighted: index === parts.length - 1 && isHighlighted,
|
isHighlighted: isLastPart && isHighlighted,
|
||||||
children: {}
|
children: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (index < parts.length - 1) {
|
if (index < parts.length - 1 || (index === parts.length - 1 && !current[part].isFile)) {
|
||||||
current = current[part].children;
|
current = current[part].children;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -463,13 +531,13 @@ class EmbedPreview {
|
|||||||
treeContainer.innerHTML = '';
|
treeContainer.innerHTML = '';
|
||||||
|
|
||||||
// Render tree
|
// Render tree
|
||||||
this.renderTreeNode(tree, treeContainer, 0);
|
this.renderTreeNode(tree, treeContainer, 0, '');
|
||||||
} else {
|
} else {
|
||||||
sidebar.classList.add('hidden');
|
sidebar.classList.add('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTreeNode(node, container, level) {
|
renderTreeNode(node, container, level, parentPath) {
|
||||||
const sortedKeys = Object.keys(node).sort((a, b) => {
|
const sortedKeys = Object.keys(node).sort((a, b) => {
|
||||||
// Folders first, then files
|
// Folders first, then files
|
||||||
const aIsFile = node[a].isFile;
|
const aIsFile = node[a].isFile;
|
||||||
@ -480,10 +548,17 @@ class EmbedPreview {
|
|||||||
|
|
||||||
sortedKeys.forEach(key => {
|
sortedKeys.forEach(key => {
|
||||||
const item = node[key];
|
const item = node[key];
|
||||||
|
const fullPath = parentPath ? `${parentPath}/${item.name}` : item.name;
|
||||||
const itemElement = document.createElement('div');
|
const itemElement = document.createElement('div');
|
||||||
|
|
||||||
// Add highlighting class if the file is marked
|
// Check if file or folder is selected
|
||||||
|
const contextPath = item.isFile ? fullPath : fullPath + '/';
|
||||||
|
const isSelected = this.selectedFiles.has(contextPath);
|
||||||
|
|
||||||
|
// Add highlighting class if the file is marked or selected
|
||||||
if (item.isHighlighted) {
|
if (item.isHighlighted) {
|
||||||
|
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 bg-dynamic-primary/20 rounded text-xs text-dynamic-foreground font-medium transition-all';
|
||||||
|
} else if (isSelected) {
|
||||||
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 bg-dynamic-primary/20 rounded cursor-pointer text-xs text-dynamic-foreground font-medium transition-all hover:bg-dynamic-primary/30';
|
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 bg-dynamic-primary/20 rounded cursor-pointer text-xs text-dynamic-foreground font-medium transition-all hover:bg-dynamic-primary/30';
|
||||||
} else {
|
} else {
|
||||||
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 hover:bg-dynamic-primary/10 rounded cursor-pointer text-xs text-dynamic-foreground/80 hover:text-dynamic-foreground transition-colors';
|
itemElement.className = 'flex items-center gap-1 py-0.5 px-1.5 hover:bg-dynamic-primary/10 rounded cursor-pointer text-xs text-dynamic-foreground/80 hover:text-dynamic-foreground transition-colors';
|
||||||
@ -491,6 +566,23 @@ class EmbedPreview {
|
|||||||
|
|
||||||
itemElement.style.paddingLeft = `${level * 12 + 6}px`;
|
itemElement.style.paddingLeft = `${level * 12 + 6}px`;
|
||||||
|
|
||||||
|
// Add click handler for files and folders (but not starred items)
|
||||||
|
if (!item.isHighlighted) {
|
||||||
|
itemElement.addEventListener('click', () => {
|
||||||
|
const contextPath = item.isFile ? fullPath : fullPath + '/';
|
||||||
|
if (isSelected) {
|
||||||
|
this.removeFileFromContext(contextPath);
|
||||||
|
} else {
|
||||||
|
this.addFileToContext(contextPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
itemElement.title = isSelected ? 'Click to remove from context' : 'Click to add to context';
|
||||||
|
itemElement.style.cursor = 'pointer';
|
||||||
|
} else {
|
||||||
|
itemElement.style.cursor = 'default';
|
||||||
|
itemElement.title = 'Starred items cannot be selected';
|
||||||
|
}
|
||||||
|
|
||||||
// Add icon
|
// Add icon
|
||||||
const icon = document.createElement('span');
|
const icon = document.createElement('span');
|
||||||
icon.className = 'flex-shrink-0';
|
icon.className = 'flex-shrink-0';
|
||||||
@ -500,8 +592,8 @@ class EmbedPreview {
|
|||||||
const ext = key.split('.').pop().toLowerCase();
|
const ext = key.split('.').pop().toLowerCase();
|
||||||
let iconColor = 'text-dynamic-muted-foreground';
|
let iconColor = 'text-dynamic-muted-foreground';
|
||||||
|
|
||||||
// If highlighted, use primary color for icon
|
// If highlighted or selected, use primary color for icon
|
||||||
if (item.isHighlighted) {
|
if (item.isHighlighted || isSelected) {
|
||||||
iconColor = 'text-dynamic-primary';
|
iconColor = 'text-dynamic-primary';
|
||||||
} else {
|
} else {
|
||||||
// Color code common file types
|
// Color code common file types
|
||||||
@ -536,19 +628,35 @@ class EmbedPreview {
|
|||||||
itemElement.appendChild(icon);
|
itemElement.appendChild(icon);
|
||||||
itemElement.appendChild(nameSpan);
|
itemElement.appendChild(nameSpan);
|
||||||
|
|
||||||
// Add a star indicator for highlighted files
|
// Add indicators for highlighted or selected items
|
||||||
|
if (item.isHighlighted || isSelected) {
|
||||||
|
const indicatorContainer = document.createElement('span');
|
||||||
|
indicatorContainer.className = 'ml-auto flex items-center gap-1';
|
||||||
|
|
||||||
|
// Add star for highlighted items
|
||||||
if (item.isHighlighted) {
|
if (item.isHighlighted) {
|
||||||
const starIcon = document.createElement('span');
|
const starIcon = document.createElement('span');
|
||||||
starIcon.className = 'ml-auto text-dynamic-primary';
|
starIcon.className = 'text-dynamic-primary';
|
||||||
starIcon.innerHTML = '<svg class="w-2.5 h-2.5" viewBox="0 0 20 20" fill="currentColor"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg>';
|
starIcon.innerHTML = '<svg class="w-2.5 h-2.5" viewBox="0 0 20 20" fill="currentColor"><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/></svg>';
|
||||||
itemElement.appendChild(starIcon);
|
indicatorContainer.appendChild(starIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add checkmark for selected items (not highlighted)
|
||||||
|
if (isSelected && !item.isHighlighted) {
|
||||||
|
const checkIcon = document.createElement('span');
|
||||||
|
checkIcon.className = 'text-dynamic-primary';
|
||||||
|
checkIcon.innerHTML = '<svg class="w-2.5 h-2.5" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>';
|
||||||
|
indicatorContainer.appendChild(checkIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
itemElement.appendChild(indicatorContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
container.appendChild(itemElement);
|
container.appendChild(itemElement);
|
||||||
|
|
||||||
// Recursively render children
|
// Recursively render children
|
||||||
if (!item.isFile && Object.keys(item.children).length > 0) {
|
if (!item.isFile && Object.keys(item.children).length > 0) {
|
||||||
this.renderTreeNode(item.children, container, level + 1);
|
this.renderTreeNode(item.children, container, level + 1, fullPath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -90,6 +90,8 @@
|
|||||||
display: none; /* Safari and Chrome */
|
display: none; /* Safari and Chrome */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Touch target improvements for mobile */
|
/* Touch target improvements for mobile */
|
||||||
.touch-target {
|
.touch-target {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
@ -101,16 +103,15 @@
|
|||||||
|
|
||||||
/* Fixed height for prompt container to enable scrolling */
|
/* Fixed height for prompt container to enable scrolling */
|
||||||
#prompt-container {
|
#prompt-container {
|
||||||
height: calc(100vh - 140px); /* Fixed height minus header and footer space */
|
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
max-height: calc(100vh - 120px);
|
flex: 1;
|
||||||
/* max-height: 100vh; */
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
#prompt-container {
|
#prompt-container {
|
||||||
height: calc(100vh - 90px); /* Smaller on mobile */
|
min-height: 80px;
|
||||||
max-height: calc(100vh - 90px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,11 +124,10 @@
|
|||||||
|
|
||||||
/* Responsive improvements */
|
/* Responsive improvements */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
/* Ensure context pills don't wrap and scroll horizontally */
|
/* Context pills can wrap on mobile too */
|
||||||
#context-pills {
|
#context-pills {
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap !important;
|
||||||
scrollbar-width: none;
|
max-height: 80px; /* Smaller on mobile */
|
||||||
-ms-overflow-style: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* More compact pill spacing on mobile */
|
/* More compact pill spacing on mobile */
|
||||||
@ -192,6 +192,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Context pills container improvements */
|
||||||
|
#context-pills {
|
||||||
|
max-width: calc(100% - 40px); /* Reserve space for edit button */
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-height: 120px; /* Limit context pills height */
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom scrollbar for context pills */
|
||||||
|
#context-pills {
|
||||||
|
scrollbar-width: thin; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-pills::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-pills::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-pills::-webkit-scrollbar-thumb {
|
||||||
|
background: rgb(var(--border) / 0.5);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#context-pills::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgb(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure pills maintain size */
|
||||||
|
#context-pills > div {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Ensure proper touch behavior */
|
/* Ensure proper touch behavior */
|
||||||
@media (hover: none) and (pointer: coarse) {
|
@media (hover: none) and (pointer: coarse) {
|
||||||
/* All interactive elements get proper touch targets */
|
/* All interactive elements get proper touch targets */
|
||||||
|
120
embed-script.js
120
embed-script.js
@ -33,7 +33,8 @@ class EmbedDesigner {
|
|||||||
darkColor: this.params.darkColor || savedConfig.darkColor || '#60a5fa',
|
darkColor: this.params.darkColor || savedConfig.darkColor || '#60a5fa',
|
||||||
height: this.params.height || savedConfig.height || '400',
|
height: this.params.height || savedConfig.height || '400',
|
||||||
themeMode: this.params.themeMode || savedConfig.themeMode || 'auto',
|
themeMode: this.params.themeMode || savedConfig.themeMode || 'auto',
|
||||||
filetree: this.params.filetree ? decodeURIComponent(this.params.filetree).split('\n').filter(f => f.trim()) : (savedConfig.filetree || [])
|
filetree: this.params.filetree ? decodeURIComponent(this.params.filetree).split('\n').filter(f => f.trim()) : (savedConfig.filetree || []),
|
||||||
|
showFiletree: savedConfig.showFiletree !== undefined ? savedConfig.showFiletree : true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +56,8 @@ class EmbedDesigner {
|
|||||||
darkColor: '#60a5fa',
|
darkColor: '#60a5fa',
|
||||||
height: '400',
|
height: '400',
|
||||||
themeMode: 'auto',
|
themeMode: 'auto',
|
||||||
filetree: []
|
filetree: [],
|
||||||
|
showFiletree: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +80,8 @@ class EmbedDesigner {
|
|||||||
darkColor: config.darkColor || '#60a5fa',
|
darkColor: config.darkColor || '#60a5fa',
|
||||||
height: config.height || '400',
|
height: config.height || '400',
|
||||||
themeMode: config.themeMode || 'auto',
|
themeMode: config.themeMode || 'auto',
|
||||||
filetree: config.filetree || []
|
filetree: config.filetree || [],
|
||||||
|
showFiletree: config.showFiletree !== undefined ? config.showFiletree : true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,6 +144,12 @@ class EmbedDesigner {
|
|||||||
document.getElementById('designer-thinking').checked = this.config.thinking;
|
document.getElementById('designer-thinking').checked = this.config.thinking;
|
||||||
document.getElementById('designer-max').checked = this.config.max;
|
document.getElementById('designer-max').checked = this.config.max;
|
||||||
|
|
||||||
|
// Set show filetree checkbox (default to true if not set)
|
||||||
|
const showFiletreeCheckbox = document.getElementById('designer-show-filetree');
|
||||||
|
if (showFiletreeCheckbox) {
|
||||||
|
showFiletreeCheckbox.checked = this.config.showFiletree !== false;
|
||||||
|
}
|
||||||
|
|
||||||
// Set height slider
|
// Set height slider
|
||||||
const heightSlider = document.getElementById('designer-height');
|
const heightSlider = document.getElementById('designer-height');
|
||||||
const heightValue = document.getElementById('height-value');
|
const heightValue = document.getElementById('height-value');
|
||||||
@ -186,10 +195,12 @@ class EmbedDesigner {
|
|||||||
|
|
||||||
setupDesignerEvents() {
|
setupDesignerEvents() {
|
||||||
// Form changes update preview
|
// Form changes update preview
|
||||||
['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max', 'designer-filetree'].forEach(id => {
|
['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max', 'designer-filetree', 'designer-show-filetree'].forEach(id => {
|
||||||
const element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
element.addEventListener('input', () => this.updateConfigFromForm());
|
element.addEventListener('input', () => this.updateConfigFromForm());
|
||||||
element.addEventListener('change', () => this.updateConfigFromForm());
|
element.addEventListener('change', () => this.updateConfigFromForm());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Theme mode buttons
|
// Theme mode buttons
|
||||||
@ -317,7 +328,8 @@ class EmbedDesigner {
|
|||||||
darkColor: '#60a5fa',
|
darkColor: '#60a5fa',
|
||||||
height: '400',
|
height: '400',
|
||||||
themeMode: 'auto',
|
themeMode: 'auto',
|
||||||
filetree: []
|
filetree: [],
|
||||||
|
showFiletree: true
|
||||||
};
|
};
|
||||||
// Update UI to reflect defaults
|
// Update UI to reflect defaults
|
||||||
this.setupDesignerElements();
|
this.setupDesignerElements();
|
||||||
@ -326,9 +338,13 @@ class EmbedDesigner {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load example button
|
// Example buttons
|
||||||
document.getElementById('load-example').addEventListener('click', () => {
|
document.getElementById('vibe-example').addEventListener('click', () => {
|
||||||
this.loadExample();
|
this.loadVibeExample();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('chat-example').addEventListener('click', () => {
|
||||||
|
this.loadChatExample();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modal events
|
// Modal events
|
||||||
@ -366,7 +382,8 @@ class EmbedDesigner {
|
|||||||
darkColor: darkColorText ? darkColorText.value : '#60a5fa',
|
darkColor: darkColorText ? darkColorText.value : '#60a5fa',
|
||||||
height: heightSlider ? heightSlider.value : '400',
|
height: heightSlider ? heightSlider.value : '400',
|
||||||
themeMode: this.config.themeMode || 'auto',
|
themeMode: this.config.themeMode || 'auto',
|
||||||
filetree: document.getElementById('designer-filetree').value.split('\n').map(f => f.trim()).filter(f => f)
|
filetree: document.getElementById('designer-filetree').value.split('\n').map(f => f.trim()).filter(f => f),
|
||||||
|
showFiletree: document.getElementById('designer-show-filetree').checked
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updatePreview();
|
this.updatePreview();
|
||||||
@ -439,7 +456,7 @@ class EmbedDesigner {
|
|||||||
if (this.config.lightColor !== '#3b82f6') params.set('lightColor', this.config.lightColor);
|
if (this.config.lightColor !== '#3b82f6') params.set('lightColor', this.config.lightColor);
|
||||||
if (this.config.darkColor !== '#60a5fa') params.set('darkColor', this.config.darkColor);
|
if (this.config.darkColor !== '#60a5fa') params.set('darkColor', this.config.darkColor);
|
||||||
if (this.config.themeMode !== 'auto') params.set('themeMode', this.config.themeMode);
|
if (this.config.themeMode !== 'auto') params.set('themeMode', this.config.themeMode);
|
||||||
if (this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
|
if (this.config.showFiletree && this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
|
||||||
params.set('preview', 'true');
|
params.set('preview', 'true');
|
||||||
|
|
||||||
return `/embed-preview/?${params.toString()}`;
|
return `/embed-preview/?${params.toString()}`;
|
||||||
@ -456,7 +473,7 @@ class EmbedDesigner {
|
|||||||
if (this.config.lightColor !== '#3b82f6') params.set('lightColor', this.config.lightColor);
|
if (this.config.lightColor !== '#3b82f6') params.set('lightColor', this.config.lightColor);
|
||||||
if (this.config.darkColor !== '#60a5fa') params.set('darkColor', this.config.darkColor);
|
if (this.config.darkColor !== '#60a5fa') params.set('darkColor', this.config.darkColor);
|
||||||
if (this.config.themeMode !== 'auto') params.set('themeMode', this.config.themeMode);
|
if (this.config.themeMode !== 'auto') params.set('themeMode', this.config.themeMode);
|
||||||
if (this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
|
if (this.config.showFiletree && this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
|
||||||
|
|
||||||
return `${window.location.origin}/embed-preview/?${params.toString()}`;
|
return `${window.location.origin}/embed-preview/?${params.toString()}`;
|
||||||
}
|
}
|
||||||
@ -537,42 +554,77 @@ class EmbedDesigner {
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadExample() {
|
loadVibeExample() {
|
||||||
// Set example values
|
// Set vibe coding example values
|
||||||
document.getElementById('designer-prompt').value =
|
document.getElementById('designer-prompt').value =
|
||||||
`You are a senior React developer. I need help building a modern e-commerce product listing component.
|
`I'm working on a React e-commerce app. The current ProductList component is getting messy with too much logic.
|
||||||
|
|
||||||
Requirements:
|
Can you help me refactor it to:
|
||||||
- Use React hooks and functional components
|
1. Extract the filtering logic into a custom hook
|
||||||
- Implement product filtering by category and price range
|
2. Split the large component into smaller, reusable pieces
|
||||||
- Add smooth animations for product cards
|
3. Add proper TypeScript types
|
||||||
- Make it fully responsive with a grid layout
|
4. Implement virtualization for better performance with large product lists
|
||||||
- Include loading states and error handling
|
|
||||||
|
|
||||||
The component should fetch data from a REST API and display products with images, titles, prices, and ratings. Please provide clean, well-commented code following React best practices.`;
|
The component currently handles products display, filtering by category/price, sorting, and pagination all in one file. I want a cleaner architecture.`;
|
||||||
|
|
||||||
document.getElementById('designer-context').value = '@codebase, ProductList.jsx';
|
document.getElementById('designer-context').value = '@codebase, ProductList.tsx, components/, hooks/';
|
||||||
document.getElementById('designer-filetree').value =
|
document.getElementById('designer-filetree').value =
|
||||||
`src/components/ProductList.jsx*
|
`src/
|
||||||
src/components/ProductCard.jsx
|
src/components/
|
||||||
src/components/Filters.jsx
|
src/components/ProductList.tsx*
|
||||||
src/hooks/useProducts.js
|
src/components/ProductCard.tsx
|
||||||
src/api/products.js
|
src/components/ProductGrid.tsx
|
||||||
src/styles/products.css
|
src/components/Filters/
|
||||||
src/utils/formatters.js
|
src/components/Filters/CategoryFilter.tsx
|
||||||
public/index.html
|
src/components/Filters/PriceRangeFilter.tsx
|
||||||
|
src/components/Filters/index.tsx
|
||||||
|
src/hooks/
|
||||||
|
src/hooks/useProducts.ts
|
||||||
|
src/hooks/useFilters.ts
|
||||||
|
src/hooks/useInfiniteScroll.ts
|
||||||
|
src/types/
|
||||||
|
src/types/product.ts
|
||||||
|
src/api/
|
||||||
|
src/api/products.ts
|
||||||
|
src/utils/
|
||||||
|
src/utils/formatters.ts
|
||||||
package.json
|
package.json
|
||||||
README.md`;
|
tsconfig.json`;
|
||||||
|
|
||||||
// Set some example settings
|
// Set vibe coding settings
|
||||||
document.getElementById('designer-model').value = 'Claude 3.7 Sonnet';
|
document.getElementById('designer-model').value = 'Claude 4 Opus';
|
||||||
document.getElementById('designer-mode-select').value = 'agent';
|
document.getElementById('designer-mode-select').value = 'agent';
|
||||||
document.getElementById('designer-thinking').checked = true;
|
document.getElementById('designer-thinking').checked = true;
|
||||||
|
document.getElementById('designer-max').checked = true;
|
||||||
|
document.getElementById('designer-show-filetree').checked = true;
|
||||||
|
|
||||||
// Update config from form
|
// Update config from form
|
||||||
this.updateConfigFromForm();
|
this.updateConfigFromForm();
|
||||||
|
|
||||||
this.showNotification('Example loaded!');
|
this.showNotification('Vibe coding example loaded!');
|
||||||
|
}
|
||||||
|
|
||||||
|
loadChatExample() {
|
||||||
|
// Set chat example values
|
||||||
|
document.getElementById('designer-prompt').value =
|
||||||
|
`I'm planning a dinner party for 8 people this weekend. One person is vegetarian, another is gluten-free, and I want to make something impressive but not too complicated.
|
||||||
|
|
||||||
|
Can you suggest a menu with appetizers, main course, and dessert that would work for everyone? I have about 4 hours to prepare everything and a moderate cooking skill level.`;
|
||||||
|
|
||||||
|
document.getElementById('designer-context').value = '#Screenshot Kitchen Layout.png, Recipes Collection.pdf';
|
||||||
|
document.getElementById('designer-filetree').value = '';
|
||||||
|
|
||||||
|
// Set chat settings
|
||||||
|
document.getElementById('designer-model').value = 'GPT 4o';
|
||||||
|
document.getElementById('designer-mode-select').value = 'chat';
|
||||||
|
document.getElementById('designer-thinking').checked = false;
|
||||||
|
document.getElementById('designer-max').checked = false;
|
||||||
|
document.getElementById('designer-show-filetree').checked = false;
|
||||||
|
|
||||||
|
// Update config from form
|
||||||
|
this.updateConfigFromForm();
|
||||||
|
|
||||||
|
this.showNotification('Chat example loaded!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user