Compare commits

..

2 Commits

Author SHA1 Message Date
Fatih Kadir Akın
e06e0e7e61 update 2025-06-17 20:32:06 +03:00
Fatih Kadir Akın
f19d1b23f1 update 2025-06-17 19:41:56 +03:00
13 changed files with 529 additions and 125 deletions

View File

@ -29,40 +29,51 @@
</head> </head>
<body class="bg-dynamic-background text-dynamic-foreground overflow-hidden"> <body class="bg-dynamic-background text-dynamic-foreground overflow-hidden">
<!-- Viewer Mode --> <!-- Viewer Mode -->
<div id="viewer-mode" class="viewer-mode h-screen flex flex-col p-2 sm:p-4"> <div id="viewer-mode" class="viewer-mode h-screen flex">
<!-- Top Bar with Context Pills and Edit Button --> <!-- Sidebar - File Tree -->
<div class="flex justify-between items-start gap-2 mb-0 sm:mb-2"> <div id="file-sidebar" class="hidden w-40 sm:w-44 border-r border-dynamic-border bg-dynamic-muted/30 flex-shrink-0 overflow-y-auto custom-scrollbar">
<!-- Context Pills --> <div class="p-2 border-b border-dynamic-border">
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden pb-1 scrollbar-hide flex-1"></div> <h3 class="text-[10px] font-semibold text-dynamic-muted-foreground uppercase tracking-wider">Files</h3>
<!-- Edit Button -->
<button id="edit-button" class="p-1 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors" title="Edit in designer">
<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="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
</div>
<!-- Main Prompt Interface - Full Height -->
<div class="flex-1 flex flex-col">
<div id="prompt-container" class="flex-1 bg-dynamic-muted border border-dynamic-border rounded-xl p-3 sm:p-6 relative focus-within:border-dynamic-primary transition-colors flex flex-col">
<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> </div>
<div id="file-tree" class="p-1"></div>
</div> </div>
<!-- Bottom Bar --> <!-- Main Content -->
<div class="flex justify-between items-center gap-2 mt-0 sm:mt-2"> <div class="flex-1 flex flex-col p-2 sm:p-4">
<!-- Settings Pills --> <!-- Top Bar with Context Pills and Edit Button -->
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div> <div class="flex justify-between items-start gap-2 mb-0 sm:mb-2">
<!-- Context Pills -->
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden pb-1 scrollbar-hide flex-1"></div>
<!-- Edit Button -->
<button id="edit-button" class="p-1 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors" title="Edit in designer">
<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="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
</div>
<!-- Send Button (circular with arrow up) --> <!-- Main Prompt Interface - Full Height -->
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt"> <div class="flex-1 flex flex-col">
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> <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">
<path d="M12 19V5M5 12l7-7 7 7"/> <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>
</svg> <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>
</button> </div>
</div>
<!-- Bottom Bar -->
<div class="flex justify-between items-center gap-2 mt-0 sm:mt-2">
<!-- Settings Pills -->
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
<!-- Send Button (circular with arrow up) -->
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt">
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<path d="M12 19V5M5 12l7-7 7 7"/>
</svg>
</button>
</div>
</div> </div>
</div> </div>

View File

@ -226,6 +226,19 @@
<span>800px</span> <span>800px</span>
</div> </div>
</div> </div>
<!-- File Tree -->
<div class="space-y-2">
<label class="text-sm font-medium text-dynamic-muted-foreground">File Tree</label>
<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"
rows="6"
placeholder="Enter files (one per line):&#10;index.html&#10;styles/main.css&#10;scripts/app.js&#10;components/header.vue"></textarea>
<p class="text-xs text-dynamic-muted-foreground">
Enter filenames one per line. Use forward slashes for folders (e.g., folder/file.js).
Add * at the end of a filename to highlight it (e.g., main.js*)
</p>
</div>
</div> </div>
</div> </div>

View File

@ -1906,7 +1906,7 @@ Contributed by: [@tanoojoy](https://github.com/tanoojoy)
> person texting and nothing else. Your level of drunkenness will be > person texting and nothing else. Your level of drunkenness will be
> deliberately and randomly make a lot of grammar and spelling mistakes in your > deliberately and randomly make a lot of grammar and spelling mistakes in your
> answers. You will also randomly ignore what I said and say something random > answers. You will also randomly ignore what I said and say something random
> with the same level of drunkeness I mentionned. Do not write explanations on > with the same level of drunkenness I mentioned. Do not write explanations on
> replies. My first sentence is "how are you?" > replies. My first sentence is "how are you?"
## Act as a Mathematical History Teacher ## Act as a Mathematical History Teacher
@ -2116,7 +2116,7 @@ Contributed by: [@TheLime1](https://github.com/TheLime1)
> provide a detailed note list that includes examples from the lecture and > provide a detailed note list that includes examples from the lecture and
> focuses on notes that you believe will end up in quiz questions. Additionally, > focuses on notes that you believe will end up in quiz questions. Additionally,
> please make a separate list for notes that have numbers and data in them and > please make a separate list for notes that have numbers and data in them and
> another seperated list for the examples that included in this lecture. The > another separated list for the examples that included in this lecture. The
> notes should be concise and easy to read. > notes should be concise and easy to read.
## Act as a Literary Critic ## Act as a Literary Critic

View File

@ -24,7 +24,8 @@ class EmbedPreview {
max: this.params.max === 'true', max: this.params.max === 'true',
lightColor: this.params.lightColor || '#3b82f6', lightColor: this.params.lightColor || '#3b82f6',
darkColor: this.params.darkColor || '#60a5fa', darkColor: this.params.darkColor || '#60a5fa',
themeMode: this.params.themeMode || 'auto' themeMode: this.params.themeMode || 'auto',
filetree: this.params.filetree ? decodeURIComponent(this.params.filetree).split('\n').filter(f => f.trim()) : []
}; };
} }
@ -188,6 +189,7 @@ class EmbedPreview {
this.renderContextPills(); this.renderContextPills();
this.renderPromptText(); this.renderPromptText();
this.renderSettingsPills(); this.renderSettingsPills();
this.renderFileTree();
} }
renderContextPills() { renderContextPills() {
@ -341,7 +343,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) {
@ -413,6 +415,143 @@ class EmbedPreview {
notification.classList.add('opacity-0'); notification.classList.add('opacity-0');
}, 2000); }, 2000);
} }
buildFileTree(paths) {
const tree = {};
paths.forEach(path => {
// Check if the path ends with an asterisk
const isHighlighted = path.endsWith('*');
// Remove asterisk if present
const cleanPath = isHighlighted ? path.slice(0, -1) : path;
const parts = cleanPath.split('/');
let current = tree;
parts.forEach((part, index) => {
if (!current[part]) {
current[part] = {
name: part,
isFile: index === parts.length - 1,
isHighlighted: index === parts.length - 1 && isHighlighted,
children: {}
};
}
if (index < parts.length - 1) {
current = current[part].children;
}
});
});
return tree;
}
renderFileTree() {
const sidebar = document.getElementById('file-sidebar');
const treeContainer = document.getElementById('file-tree');
if (!sidebar || !treeContainer) return;
// Show/hide sidebar based on whether there are files
if (this.config.filetree && this.config.filetree.length > 0) {
sidebar.classList.remove('hidden');
// Build tree structure
const tree = this.buildFileTree(this.config.filetree);
// Clear existing content
treeContainer.innerHTML = '';
// Render tree
this.renderTreeNode(tree, treeContainer, 0);
} else {
sidebar.classList.add('hidden');
}
}
renderTreeNode(node, container, level) {
const sortedKeys = Object.keys(node).sort((a, b) => {
// Folders first, then files
const aIsFile = node[a].isFile;
const bIsFile = node[b].isFile;
if (aIsFile !== bIsFile) return aIsFile ? 1 : -1;
return a.localeCompare(b);
});
sortedKeys.forEach(key => {
const item = node[key];
const itemElement = document.createElement('div');
// Add highlighting class if the file is marked
if (item.isHighlighted) {
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 {
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.style.paddingLeft = `${level * 12 + 6}px`;
// Add icon
const icon = document.createElement('span');
icon.className = 'flex-shrink-0';
if (item.isFile) {
// File icon with different colors based on extension
const ext = key.split('.').pop().toLowerCase();
let iconColor = 'text-dynamic-muted-foreground';
// If highlighted, use primary color for icon
if (item.isHighlighted) {
iconColor = 'text-dynamic-primary';
} else {
// Color code common file types
if (['js', 'jsx', 'ts', 'tsx'].includes(ext)) {
iconColor = 'text-yellow-500';
} else if (['css', 'scss', 'sass', 'less'].includes(ext)) {
iconColor = 'text-blue-500';
} else if (['html', 'htm'].includes(ext)) {
iconColor = 'text-orange-500';
} else if (['vue', 'svelte'].includes(ext)) {
iconColor = 'text-green-500';
} else if (['json', 'xml', 'yaml', 'yml'].includes(ext)) {
iconColor = 'text-purple-500';
} else if (['md', 'mdx'].includes(ext)) {
iconColor = 'text-gray-500';
} else if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext)) {
iconColor = 'text-pink-500';
}
}
icon.innerHTML = `<svg class="w-3 h-3 ${iconColor}" 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>`;
} else {
// Folder icon
icon.innerHTML = '<svg class="w-3 h-3 text-dynamic-primary" 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>';
}
// Add name
const nameSpan = document.createElement('span');
nameSpan.className = 'truncate flex-1';
nameSpan.textContent = item.name;
itemElement.appendChild(icon);
itemElement.appendChild(nameSpan);
// Add a star indicator for highlighted files
if (item.isHighlighted) {
const starIcon = document.createElement('span');
starIcon.className = 'ml-auto 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>';
itemElement.appendChild(starIcon);
}
container.appendChild(itemElement);
// Recursively render children
if (!item.isFile && Object.keys(item.children).length > 0) {
this.renderTreeNode(item.children, container, level + 1);
}
});
}
} }
// Initialize when DOM is ready // Initialize when DOM is ready

View File

@ -109,8 +109,8 @@
@media (max-width: 640px) { @media (max-width: 640px) {
#prompt-container { #prompt-container {
height: calc(100vh - 120px); /* Smaller on mobile */ height: calc(100vh - 90px); /* Smaller on mobile */
max-height: calc(100vh - 120px); max-height: calc(100vh - 90px);
} }
} }
@ -119,7 +119,6 @@
height: 100%; height: 100%;
overflow-y: auto !important; overflow-y: auto !important;
flex: 1; flex: 1;
white-space: wrap;
} }
/* Responsive improvements */ /* Responsive improvements */
@ -186,6 +185,13 @@
} }
} }
/* Hide file sidebar on mobile devices */
@media (max-width: 640px) {
#file-sidebar {
display: none !important;
}
}
/* 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 */

View File

@ -29,40 +29,51 @@
</head> </head>
<body class="bg-dynamic-background text-dynamic-foreground overflow-hidden"> <body class="bg-dynamic-background text-dynamic-foreground overflow-hidden">
<!-- Viewer Mode --> <!-- Viewer Mode -->
<div id="viewer-mode" class="viewer-mode h-screen flex flex-col p-2 sm:p-4"> <div id="viewer-mode" class="viewer-mode h-screen flex">
<!-- Top Bar with Context Pills and Edit Button --> <!-- Sidebar - File Tree -->
<div class="flex justify-between items-start gap-2 mb-0 sm:mb-2"> <div id="file-sidebar" class="hidden w-40 sm:w-44 border-r border-dynamic-border bg-dynamic-muted/30 flex-shrink-0 overflow-y-auto custom-scrollbar">
<!-- Context Pills --> <div class="p-2 border-b border-dynamic-border">
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden pb-1 scrollbar-hide flex-1"></div> <h3 class="text-[10px] font-semibold text-dynamic-muted-foreground uppercase tracking-wider">Files</h3>
<!-- Edit Button -->
<button id="edit-button" class="p-1 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors" title="Edit in designer">
<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="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
</div>
<!-- Main Prompt Interface - Full Height -->
<div class="flex-1 flex flex-col">
<div id="prompt-container" class="flex-1 bg-dynamic-muted border border-dynamic-border rounded-xl p-3 sm:p-6 relative focus-within:border-dynamic-primary transition-colors flex flex-col">
<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> </div>
<div id="file-tree" class="p-1"></div>
</div> </div>
<!-- Bottom Bar --> <!-- Main Content -->
<div class="flex justify-between items-center gap-2 mt-0 sm:mt-2"> <div class="flex-1 flex flex-col p-2 sm:p-4">
<!-- Settings Pills --> <!-- Top Bar with Context Pills and Edit Button -->
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div> <div class="flex justify-between items-start gap-2 mb-0 sm:mb-2">
<!-- Context Pills -->
<div id="context-pills" class="flex overflow-x-auto gap-2 empty:hidden pb-1 scrollbar-hide flex-1"></div>
<!-- Edit Button -->
<button id="edit-button" class="p-1 text-dynamic-muted-foreground hover:text-dynamic-foreground transition-colors" title="Edit in designer">
<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="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
</div>
<!-- Send Button (circular with arrow up) --> <!-- Main Prompt Interface - Full Height -->
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt"> <div class="flex-1 flex flex-col">
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> <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">
<path d="M12 19V5M5 12l7-7 7 7"/> <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>
</svg> <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>
</button> </div>
</div>
<!-- Bottom Bar -->
<div class="flex justify-between items-center gap-2 mt-0 sm:mt-2">
<!-- Settings Pills -->
<div id="settings-pills" class="flex gap-1 sm:gap-2 flex-wrap flex-1 min-w-0"></div>
<!-- Send Button (circular with arrow up) -->
<button id="copy-button" class="w-8 h-8 sm:w-10 sm:h-10 bg-dynamic-primary text-white rounded-full flex items-center justify-center hover:opacity-90 transition-opacity focus-ring flex-shrink-0 shadow-lg touch-target" title="Send prompt">
<svg width="16" height="16" class="sm:w-5 sm:h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<path d="M12 19V5M5 12l7-7 7 7"/>
</svg>
</button>
</div>
</div> </div>
</div> </div>

View File

@ -32,7 +32,8 @@ class EmbedDesigner {
lightColor: this.params.lightColor || savedConfig.lightColor || '#3b82f6', lightColor: this.params.lightColor || savedConfig.lightColor || '#3b82f6',
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 || [])
}; };
} }
@ -53,7 +54,8 @@ class EmbedDesigner {
lightColor: '#3b82f6', lightColor: '#3b82f6',
darkColor: '#60a5fa', darkColor: '#60a5fa',
height: '400', height: '400',
themeMode: 'auto' themeMode: 'auto',
filetree: []
}; };
} }
@ -64,7 +66,20 @@ class EmbedDesigner {
const config = JSON.parse(saved); const config = JSON.parse(saved);
// Validate the loaded config has all required fields // Validate the loaded config has all required fields
if (config && typeof config === 'object') { if (config && typeof config === 'object') {
return config; // Ensure all properties have defaults
return {
prompt: config.prompt || '',
context: config.context || [],
model: config.model || 'gpt-4o',
mode: config.mode || 'chat',
thinking: config.thinking || false,
max: config.max || false,
lightColor: config.lightColor || '#3b82f6',
darkColor: config.darkColor || '#60a5fa',
height: config.height || '400',
themeMode: config.themeMode || 'auto',
filetree: config.filetree || []
};
} }
} }
} catch (e) { } catch (e) {
@ -103,6 +118,7 @@ class EmbedDesigner {
// Populate form with current config // Populate form with current config
document.getElementById('designer-prompt').value = this.config.prompt; document.getElementById('designer-prompt').value = this.config.prompt;
document.getElementById('designer-context').value = this.config.context.join(', '); document.getElementById('designer-context').value = this.config.context.join(', ');
document.getElementById('designer-filetree').value = this.config.filetree.join('\n');
// Handle model selection // Handle model selection
const modelSelect = document.getElementById('designer-model'); const modelSelect = document.getElementById('designer-model');
@ -170,7 +186,7 @@ class EmbedDesigner {
setupDesignerEvents() { setupDesignerEvents() {
// Form changes update preview // Form changes update preview
['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max'].forEach(id => { ['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max', 'designer-filetree'].forEach(id => {
const element = document.getElementById(id); const element = document.getElementById(id);
element.addEventListener('input', () => this.updateConfigFromForm()); element.addEventListener('input', () => this.updateConfigFromForm());
element.addEventListener('change', () => this.updateConfigFromForm()); element.addEventListener('change', () => this.updateConfigFromForm());
@ -300,7 +316,8 @@ class EmbedDesigner {
lightColor: '#3b82f6', lightColor: '#3b82f6',
darkColor: '#60a5fa', darkColor: '#60a5fa',
height: '400', height: '400',
themeMode: 'auto' themeMode: 'auto',
filetree: []
}; };
// Update UI to reflect defaults // Update UI to reflect defaults
this.setupDesignerElements(); this.setupDesignerElements();
@ -348,7 +365,8 @@ class EmbedDesigner {
lightColor: lightColorText ? lightColorText.value : '#3b82f6', lightColor: lightColorText ? lightColorText.value : '#3b82f6',
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)
}; };
this.updatePreview(); this.updatePreview();
@ -421,6 +439,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')));
params.set('preview', 'true'); params.set('preview', 'true');
return `/embed-preview/?${params.toString()}`; return `/embed-preview/?${params.toString()}`;
@ -437,6 +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')));
return `${window.location.origin}/embed-preview/?${params.toString()}`; return `${window.location.origin}/embed-preview/?${params.toString()}`;
} }
@ -518,27 +538,40 @@ class EmbedDesigner {
} }
loadExample() { loadExample() {
// Set example data // Set example values
this.config = { document.getElementById('designer-prompt').value =
prompt: 'Build an MCP server that works with Weather API. See @Web for cool weather APIs.', `You are a senior React developer. I need help building a modern e-commerce product listing component.
context: ['@Web', 'https://modelcontextprotocol.io/full-llms.txt'],
model: 'Claude 4 Sonnet', Requirements:
mode: 'agent', - Use React hooks and functional components
thinking: true, - Implement product filtering by category and price range
max: true, - Add smooth animations for product cards
lightColor: '#3b82f6', - Make it fully responsive with a grid layout
darkColor: '#60a5fa', - Include loading states and error handling
height: '250',
themeMode: 'auto' 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.`;
};
// Update all form elements document.getElementById('designer-context').value = '@codebase, ProductList.jsx';
this.setupDesignerElements(); document.getElementById('designer-filetree').value =
this.updatePreview(); `src/components/ProductList.jsx*
this.updateIframeSnippet(); src/components/ProductCard.jsx
this.saveToLocalStorage(); src/components/Filters.jsx
src/hooks/useProducts.js
src/api/products.js
src/styles/products.css
src/utils/formatters.js
public/index.html
package.json
README.md`;
// Set some example settings
document.getElementById('designer-model').value = 'Claude 3.7 Sonnet';
document.getElementById('designer-mode-select').value = 'agent';
document.getElementById('designer-thinking').checked = true;
// Update config from form
this.updateConfigFromForm();
// Show notification
this.showNotification('Example loaded!'); this.showNotification('Example loaded!');
} }
} }

View File

@ -226,6 +226,19 @@
<span>800px</span> <span>800px</span>
</div> </div>
</div> </div>
<!-- File Tree -->
<div class="space-y-2">
<label class="text-sm font-medium text-dynamic-muted-foreground">File Tree</label>
<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"
rows="6"
placeholder="Enter files (one per line):&#10;index.html&#10;styles/main.css&#10;scripts/app.js&#10;components/header.vue"></textarea>
<p class="text-xs text-dynamic-muted-foreground">
Enter filenames one per line. Use forward slashes for folders (e.g., folder/file.js).
Add * at the end of a filename to highlight it (e.g., main.js*)
</p>
</div>
</div> </div>
</div> </div>

View File

@ -25,7 +25,7 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="/style.css?v=a8a69caf7649bb8f02c225029af543967973a65f"> <link rel="stylesheet" href="/style.css?v=6476e2c4378a537143d3cc94bf4daab5d466db6f">
</head> </head>
<body class=""> <body class="">
<div class="layout-wrapper"> <div class="layout-wrapper">
@ -2394,7 +2394,7 @@ take it from there.</p>
person texting and nothing else. Your level of drunkenness will be person texting and nothing else. Your level of drunkenness will be
deliberately and randomly make a lot of grammar and spelling mistakes in your deliberately and randomly make a lot of grammar and spelling mistakes in your
answers. You will also randomly ignore what I said and say something random answers. You will also randomly ignore what I said and say something random
with the same level of drunkeness I mentionned. Do not write explanations on with the same level of drunkenness I mentioned. Do not write explanations on
replies. My first sentence is “how are you?”</p> replies. My first sentence is “how are you?”</p>
</blockquote> </blockquote>
@ -2636,7 +2636,7 @@ will tell me the right answer. Then you will ask me the next question.</p>
provide a detailed note list that includes examples from the lecture and provide a detailed note list that includes examples from the lecture and
focuses on notes that you believe will end up in quiz questions. Additionally, focuses on notes that you believe will end up in quiz questions. Additionally,
please make a separate list for notes that have numbers and data in them and please make a separate list for notes that have numbers and data in them and
another seperated list for the examples that included in this lecture. The another separated list for the examples that included in this lecture. The
notes should be concise and easy to read.</p> notes should be concise and easy to read.</p>
</blockquote> </blockquote>

View File

@ -25,7 +25,7 @@
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<link rel="stylesheet" href="/style.css?v=a8a69caf7649bb8f02c225029af543967973a65f"> <link rel="stylesheet" href="/style.css?v=6476e2c4378a537143d3cc94bf4daab5d466db6f">
</head> </head>
<body class="vibe"> <body class="vibe">
<div class="layout-wrapper"> <div class="layout-wrapper">

View File

@ -24,7 +24,8 @@ class EmbedPreview {
max: this.params.max === 'true', max: this.params.max === 'true',
lightColor: this.params.lightColor || '#3b82f6', lightColor: this.params.lightColor || '#3b82f6',
darkColor: this.params.darkColor || '#60a5fa', darkColor: this.params.darkColor || '#60a5fa',
themeMode: this.params.themeMode || 'auto' themeMode: this.params.themeMode || 'auto',
filetree: this.params.filetree ? decodeURIComponent(this.params.filetree).split('\n').filter(f => f.trim()) : []
}; };
} }
@ -188,6 +189,7 @@ class EmbedPreview {
this.renderContextPills(); this.renderContextPills();
this.renderPromptText(); this.renderPromptText();
this.renderSettingsPills(); this.renderSettingsPills();
this.renderFileTree();
} }
renderContextPills() { renderContextPills() {
@ -413,6 +415,143 @@ class EmbedPreview {
notification.classList.add('opacity-0'); notification.classList.add('opacity-0');
}, 2000); }, 2000);
} }
buildFileTree(paths) {
const tree = {};
paths.forEach(path => {
// Check if the path ends with an asterisk
const isHighlighted = path.endsWith('*');
// Remove asterisk if present
const cleanPath = isHighlighted ? path.slice(0, -1) : path;
const parts = cleanPath.split('/');
let current = tree;
parts.forEach((part, index) => {
if (!current[part]) {
current[part] = {
name: part,
isFile: index === parts.length - 1,
isHighlighted: index === parts.length - 1 && isHighlighted,
children: {}
};
}
if (index < parts.length - 1) {
current = current[part].children;
}
});
});
return tree;
}
renderFileTree() {
const sidebar = document.getElementById('file-sidebar');
const treeContainer = document.getElementById('file-tree');
if (!sidebar || !treeContainer) return;
// Show/hide sidebar based on whether there are files
if (this.config.filetree && this.config.filetree.length > 0) {
sidebar.classList.remove('hidden');
// Build tree structure
const tree = this.buildFileTree(this.config.filetree);
// Clear existing content
treeContainer.innerHTML = '';
// Render tree
this.renderTreeNode(tree, treeContainer, 0);
} else {
sidebar.classList.add('hidden');
}
}
renderTreeNode(node, container, level) {
const sortedKeys = Object.keys(node).sort((a, b) => {
// Folders first, then files
const aIsFile = node[a].isFile;
const bIsFile = node[b].isFile;
if (aIsFile !== bIsFile) return aIsFile ? 1 : -1;
return a.localeCompare(b);
});
sortedKeys.forEach(key => {
const item = node[key];
const itemElement = document.createElement('div');
// Add highlighting class if the file is marked
if (item.isHighlighted) {
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 {
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.style.paddingLeft = `${level * 12 + 6}px`;
// Add icon
const icon = document.createElement('span');
icon.className = 'flex-shrink-0';
if (item.isFile) {
// File icon with different colors based on extension
const ext = key.split('.').pop().toLowerCase();
let iconColor = 'text-dynamic-muted-foreground';
// If highlighted, use primary color for icon
if (item.isHighlighted) {
iconColor = 'text-dynamic-primary';
} else {
// Color code common file types
if (['js', 'jsx', 'ts', 'tsx'].includes(ext)) {
iconColor = 'text-yellow-500';
} else if (['css', 'scss', 'sass', 'less'].includes(ext)) {
iconColor = 'text-blue-500';
} else if (['html', 'htm'].includes(ext)) {
iconColor = 'text-orange-500';
} else if (['vue', 'svelte'].includes(ext)) {
iconColor = 'text-green-500';
} else if (['json', 'xml', 'yaml', 'yml'].includes(ext)) {
iconColor = 'text-purple-500';
} else if (['md', 'mdx'].includes(ext)) {
iconColor = 'text-gray-500';
} else if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'].includes(ext)) {
iconColor = 'text-pink-500';
}
}
icon.innerHTML = `<svg class="w-3 h-3 ${iconColor}" 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>`;
} else {
// Folder icon
icon.innerHTML = '<svg class="w-3 h-3 text-dynamic-primary" 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>';
}
// Add name
const nameSpan = document.createElement('span');
nameSpan.className = 'truncate flex-1';
nameSpan.textContent = item.name;
itemElement.appendChild(icon);
itemElement.appendChild(nameSpan);
// Add a star indicator for highlighted files
if (item.isHighlighted) {
const starIcon = document.createElement('span');
starIcon.className = 'ml-auto 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>';
itemElement.appendChild(starIcon);
}
container.appendChild(itemElement);
// Recursively render children
if (!item.isFile && Object.keys(item.children).length > 0) {
this.renderTreeNode(item.children, container, level + 1);
}
});
}
} }
// Initialize when DOM is ready // Initialize when DOM is ready

View File

@ -109,8 +109,8 @@
@media (max-width: 640px) { @media (max-width: 640px) {
#prompt-container { #prompt-container {
height: calc(100vh - 120px); /* Smaller on mobile */ height: calc(100vh - 90px); /* Smaller on mobile */
max-height: calc(100vh - 120px); max-height: calc(100vh - 90px);
} }
} }
@ -119,7 +119,6 @@
height: 100%; height: 100%;
overflow-y: auto !important; overflow-y: auto !important;
flex: 1; flex: 1;
white-space: wrap;
} }
/* Responsive improvements */ /* Responsive improvements */
@ -186,6 +185,13 @@
} }
} }
/* Hide file sidebar on mobile devices */
@media (max-width: 640px) {
#file-sidebar {
display: none !important;
}
}
/* 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 */

View File

@ -32,7 +32,8 @@ class EmbedDesigner {
lightColor: this.params.lightColor || savedConfig.lightColor || '#3b82f6', lightColor: this.params.lightColor || savedConfig.lightColor || '#3b82f6',
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 || [])
}; };
} }
@ -53,7 +54,8 @@ class EmbedDesigner {
lightColor: '#3b82f6', lightColor: '#3b82f6',
darkColor: '#60a5fa', darkColor: '#60a5fa',
height: '400', height: '400',
themeMode: 'auto' themeMode: 'auto',
filetree: []
}; };
} }
@ -64,7 +66,20 @@ class EmbedDesigner {
const config = JSON.parse(saved); const config = JSON.parse(saved);
// Validate the loaded config has all required fields // Validate the loaded config has all required fields
if (config && typeof config === 'object') { if (config && typeof config === 'object') {
return config; // Ensure all properties have defaults
return {
prompt: config.prompt || '',
context: config.context || [],
model: config.model || 'gpt-4o',
mode: config.mode || 'chat',
thinking: config.thinking || false,
max: config.max || false,
lightColor: config.lightColor || '#3b82f6',
darkColor: config.darkColor || '#60a5fa',
height: config.height || '400',
themeMode: config.themeMode || 'auto',
filetree: config.filetree || []
};
} }
} }
} catch (e) { } catch (e) {
@ -103,6 +118,7 @@ class EmbedDesigner {
// Populate form with current config // Populate form with current config
document.getElementById('designer-prompt').value = this.config.prompt; document.getElementById('designer-prompt').value = this.config.prompt;
document.getElementById('designer-context').value = this.config.context.join(', '); document.getElementById('designer-context').value = this.config.context.join(', ');
document.getElementById('designer-filetree').value = this.config.filetree.join('\n');
// Handle model selection // Handle model selection
const modelSelect = document.getElementById('designer-model'); const modelSelect = document.getElementById('designer-model');
@ -170,7 +186,7 @@ class EmbedDesigner {
setupDesignerEvents() { setupDesignerEvents() {
// Form changes update preview // Form changes update preview
['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max'].forEach(id => { ['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max', 'designer-filetree'].forEach(id => {
const element = document.getElementById(id); const element = document.getElementById(id);
element.addEventListener('input', () => this.updateConfigFromForm()); element.addEventListener('input', () => this.updateConfigFromForm());
element.addEventListener('change', () => this.updateConfigFromForm()); element.addEventListener('change', () => this.updateConfigFromForm());
@ -300,7 +316,8 @@ class EmbedDesigner {
lightColor: '#3b82f6', lightColor: '#3b82f6',
darkColor: '#60a5fa', darkColor: '#60a5fa',
height: '400', height: '400',
themeMode: 'auto' themeMode: 'auto',
filetree: []
}; };
// Update UI to reflect defaults // Update UI to reflect defaults
this.setupDesignerElements(); this.setupDesignerElements();
@ -348,7 +365,8 @@ class EmbedDesigner {
lightColor: lightColorText ? lightColorText.value : '#3b82f6', lightColor: lightColorText ? lightColorText.value : '#3b82f6',
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)
}; };
this.updatePreview(); this.updatePreview();
@ -421,6 +439,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')));
params.set('preview', 'true'); params.set('preview', 'true');
return `/embed-preview/?${params.toString()}`; return `/embed-preview/?${params.toString()}`;
@ -437,6 +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')));
return `${window.location.origin}/embed-preview/?${params.toString()}`; return `${window.location.origin}/embed-preview/?${params.toString()}`;
} }
@ -518,27 +538,40 @@ class EmbedDesigner {
} }
loadExample() { loadExample() {
// Set example data // Set example values
this.config = { document.getElementById('designer-prompt').value =
prompt: 'Build an MCP server that works with Weather API. See @Web for cool weather APIs.', `You are a senior React developer. I need help building a modern e-commerce product listing component.
context: ['@Web', 'https://modelcontextprotocol.io/full-llms.txt'],
model: 'Claude 4 Sonnet', Requirements:
mode: 'agent', - Use React hooks and functional components
thinking: true, - Implement product filtering by category and price range
max: true, - Add smooth animations for product cards
lightColor: '#3b82f6', - Make it fully responsive with a grid layout
darkColor: '#60a5fa', - Include loading states and error handling
height: '250',
themeMode: 'auto' 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.`;
};
// Update all form elements document.getElementById('designer-context').value = '@codebase, ProductList.jsx';
this.setupDesignerElements(); document.getElementById('designer-filetree').value =
this.updatePreview(); `src/components/ProductList.jsx*
this.updateIframeSnippet(); src/components/ProductCard.jsx
this.saveToLocalStorage(); src/components/Filters.jsx
src/hooks/useProducts.js
src/api/products.js
src/styles/products.css
src/utils/formatters.js
public/index.html
package.json
README.md`;
// Set some example settings
document.getElementById('designer-model').value = 'Claude 3.7 Sonnet';
document.getElementById('designer-mode-select').value = 'agent';
document.getElementById('designer-thinking').checked = true;
// Update config from form
this.updateConfigFromForm();
// Show notification
this.showNotification('Example loaded!'); this.showNotification('Example loaded!');
} }
} }