diff --git a/_layouts/embed.html b/_layouts/embed.html
index 724fbb2..26c3247 100644
--- a/_layouts/embed.html
+++ b/_layouts/embed.html
@@ -50,6 +50,22 @@
diff --git a/embed-preview-script.js b/embed-preview-script.js
index a9830be..d5482a0 100644
--- a/embed-preview-script.js
+++ b/embed-preview-script.js
@@ -26,7 +26,11 @@ class EmbedPreview {
lightColor: this.params.lightColor || '#3b82f6',
darkColor: this.params.darkColor || '#60a5fa',
themeMode: this.params.themeMode || 'auto',
- filetree: this.params.filetree ? decodeURIComponent(this.params.filetree).split('\n').filter(f => f.trim()) : []
+ filetree: this.params.filetree ? decodeURIComponent(this.params.filetree).split('\n').filter(f => f.trim()) : [],
+ showDiff: this.params.showDiff === 'true',
+ diffFilename: this.params.diffFilename || '',
+ diffOldText: this.params.diffOldText ? decodeURIComponent(this.params.diffOldText) : '',
+ diffNewText: this.params.diffNewText ? decodeURIComponent(this.params.diffNewText) : ''
};
}
@@ -187,6 +191,7 @@ class EmbedPreview {
}
render() {
+ this.renderDiffView();
this.renderContextPills();
this.renderPromptText();
this.renderSettingsPills();
@@ -373,6 +378,57 @@ class EmbedPreview {
return pill;
}
+ renderDiffView() {
+ const diffView = document.getElementById('diff-view');
+ if (!diffView) return;
+
+ if (this.config.showDiff && (this.config.diffOldText || this.config.diffNewText)) {
+ diffView.classList.remove('hidden');
+
+ // Set filename
+ const filenameElement = document.getElementById('diff-filename');
+ if (filenameElement) {
+ filenameElement.textContent = this.config.diffFilename || 'untitled';
+ }
+
+ // Set diff content
+ const oldContent = document.getElementById('diff-old-content');
+ const newContent = document.getElementById('diff-new-content');
+
+ if (oldContent) {
+ oldContent.textContent = this.config.diffOldText || '(empty)';
+ // Update colors based on dark mode
+ if (this.isDarkMode) {
+ oldContent.style.backgroundColor = 'rgba(127, 29, 29, 0.15)';
+ oldContent.style.color = '#fca5a5';
+ oldContent.style.borderColor = 'rgba(127, 29, 29, 0.3)';
+ } else {
+ oldContent.style.backgroundColor = 'rgba(254, 226, 226, 0.7)';
+ oldContent.style.color = '#b91c1c';
+ oldContent.style.borderColor = 'rgba(252, 165, 165, 0.5)';
+ }
+ }
+ if (newContent) {
+ newContent.textContent = this.config.diffNewText || '(empty)';
+ // Update colors based on dark mode
+ if (this.isDarkMode) {
+ newContent.style.backgroundColor = 'rgba(20, 83, 45, 0.15)';
+ newContent.style.color = '#86efac';
+ newContent.style.borderColor = 'rgba(20, 83, 45, 0.3)';
+ } else {
+ newContent.style.backgroundColor = 'rgba(220, 252, 231, 0.7)';
+ newContent.style.color = '#15803d';
+ newContent.style.borderColor = 'rgba(134, 239, 172, 0.5)';
+ }
+ }
+
+ // Buttons are dummy - no click handlers needed
+ // They're just for visual representation
+ } else {
+ diffView.classList.add('hidden');
+ }
+ }
+
highlightMentions(text) {
return text.replaceAll(/@(https?:\/\/[^\s]+.*?|\w+\.\w+|\w+)/g, '
@$1');
}
diff --git a/embed-script.js b/embed-script.js
index eb54c1a..4456b85 100644
--- a/embed-script.js
+++ b/embed-script.js
@@ -34,7 +34,11 @@ class EmbedDesigner {
height: this.params.height || savedConfig.height || '400',
themeMode: this.params.themeMode || savedConfig.themeMode || 'auto',
filetree: this.params.filetree ? decodeURIComponent(this.params.filetree).split('\n').filter(f => f.trim()) : (savedConfig.filetree || []),
- showFiletree: savedConfig.showFiletree !== undefined ? savedConfig.showFiletree : true
+ showFiletree: savedConfig.showFiletree !== undefined ? savedConfig.showFiletree : true,
+ showDiff: this.params.showDiff === 'true' || savedConfig.showDiff || false,
+ diffFilename: this.params.diffFilename || savedConfig.diffFilename || '',
+ diffOldText: this.params.diffOldText ? decodeURIComponent(this.params.diffOldText) : (savedConfig.diffOldText || ''),
+ diffNewText: this.params.diffNewText ? decodeURIComponent(this.params.diffNewText) : (savedConfig.diffNewText || '')
};
}
@@ -57,7 +61,11 @@ class EmbedDesigner {
height: '400',
themeMode: 'auto',
filetree: [],
- showFiletree: true
+ showFiletree: true,
+ showDiff: false,
+ diffFilename: '',
+ diffOldText: '',
+ diffNewText: ''
};
}
@@ -81,7 +89,11 @@ class EmbedDesigner {
height: config.height || '400',
themeMode: config.themeMode || 'auto',
filetree: config.filetree || [],
- showFiletree: config.showFiletree !== undefined ? config.showFiletree : true
+ showFiletree: config.showFiletree !== undefined ? config.showFiletree : true,
+ showDiff: config.showDiff || false,
+ diffFilename: config.diffFilename || '',
+ diffOldText: config.diffOldText || '',
+ diffNewText: config.diffNewText || ''
};
}
}
@@ -177,6 +189,25 @@ class EmbedDesigner {
darkColorPicker.value = this.config.darkColor;
darkColorText.value = this.config.darkColor;
}
+
+ // Set up diff view
+ const showDiffCheckbox = document.getElementById('designer-show-diff');
+ const diffFields = document.getElementById('diff-fields');
+ if (showDiffCheckbox) {
+ showDiffCheckbox.checked = this.config.showDiff || false;
+ if (diffFields) {
+ diffFields.classList.toggle('hidden', !this.config.showDiff);
+ }
+ }
+
+ // Set diff field values
+ const diffFilename = document.getElementById('designer-diff-filename');
+ const diffOldText = document.getElementById('designer-diff-old');
+ const diffNewText = document.getElementById('designer-diff-new');
+
+ if (diffFilename) diffFilename.value = this.config.diffFilename || '';
+ if (diffOldText) diffOldText.value = this.config.diffOldText || '';
+ if (diffNewText) diffNewText.value = this.config.diffNewText || '';
}
updateThemeModeButtons() {
@@ -195,7 +226,7 @@ class EmbedDesigner {
setupDesignerEvents() {
// Form changes update preview
- ['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max', 'designer-filetree', 'designer-show-filetree'].forEach(id => {
+ ['designer-prompt', 'designer-context', 'designer-mode-select', 'designer-thinking', 'designer-max', 'designer-filetree', 'designer-show-filetree', 'designer-diff-filename', 'designer-diff-old', 'designer-diff-new'].forEach(id => {
const element = document.getElementById(id);
if (element) {
element.addEventListener('input', () => this.updateConfigFromForm());
@@ -203,6 +234,18 @@ class EmbedDesigner {
}
});
+ // Diff view toggle
+ const showDiffCheckbox = document.getElementById('designer-show-diff');
+ const diffFields = document.getElementById('diff-fields');
+ if (showDiffCheckbox) {
+ showDiffCheckbox.addEventListener('change', (e) => {
+ if (diffFields) {
+ diffFields.classList.toggle('hidden', !e.target.checked);
+ }
+ this.updateConfigFromForm();
+ });
+ }
+
// Theme mode buttons
document.querySelectorAll('.theme-mode-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
@@ -329,7 +372,11 @@ class EmbedDesigner {
height: '400',
themeMode: 'auto',
filetree: [],
- showFiletree: true
+ showFiletree: true,
+ showDiff: false,
+ diffFilename: '',
+ diffOldText: '',
+ diffNewText: ''
};
// Update UI to reflect defaults
this.setupDesignerElements();
@@ -338,14 +385,37 @@ class EmbedDesigner {
}
});
- // Example buttons
- document.getElementById('vibe-example').addEventListener('click', () => {
- this.loadVibeExample();
- });
-
- document.getElementById('chat-example').addEventListener('click', () => {
- this.loadChatExample();
- });
+ // Example select
+ const exampleSelect = document.getElementById('example-select');
+ if (exampleSelect) {
+ exampleSelect.addEventListener('change', (e) => {
+ const example = e.target.value;
+ if (example) {
+ switch (example) {
+ case 'vibe-coding':
+ this.loadVibeCodingExample();
+ break;
+ case 'vibe-coding-diff':
+ this.loadVibeCodingDiffExample();
+ break;
+ case 'chatgpt':
+ this.loadChatGPTExample();
+ break;
+ case 'claude':
+ this.loadClaudeExample();
+ break;
+ case 'image-analysis':
+ this.loadImageAnalysisExample();
+ break;
+ case 'api-design':
+ this.loadAPIDesignExample();
+ break;
+ }
+ // Reset select to placeholder after loading
+ exampleSelect.value = '';
+ }
+ });
+ }
// Modal events
document.getElementById('close-modal').addEventListener('click', () => this.hideEmbedModal());
@@ -383,7 +453,11 @@ class EmbedDesigner {
height: heightSlider ? heightSlider.value : '400',
themeMode: this.config.themeMode || 'auto',
filetree: document.getElementById('designer-filetree').value.split('\n').map(f => f.trim()).filter(f => f),
- showFiletree: document.getElementById('designer-show-filetree').checked
+ showFiletree: document.getElementById('designer-show-filetree').checked,
+ showDiff: document.getElementById('designer-show-diff').checked,
+ diffFilename: document.getElementById('designer-diff-filename').value,
+ diffOldText: document.getElementById('designer-diff-old').value,
+ diffNewText: document.getElementById('designer-diff-new').value
};
this.updatePreview();
@@ -457,6 +531,19 @@ class EmbedDesigner {
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.showFiletree && this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
+ if (this.config.showDiff) {
+ params.set('showDiff', 'true');
+ if (this.config.diffFilename) params.set('diffFilename', this.config.diffFilename);
+ // Truncate diff text if too long to prevent URL length issues
+ if (this.config.diffOldText) {
+ const truncated = this.config.diffOldText.substring(0, 100);
+ params.set('diffOldText', encodeURIComponent(truncated)+'...');
+ }
+ if (this.config.diffNewText) {
+ const truncated = this.config.diffNewText.substring(0, 100);
+ params.set('diffNewText', encodeURIComponent(truncated)+'...');
+ }
+ }
params.set('preview', 'true');
return `/embed-preview/?${params.toString()}`;
@@ -474,6 +561,19 @@ class EmbedDesigner {
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.showFiletree && this.config.filetree && this.config.filetree.length > 0) params.set('filetree', encodeURIComponent(this.config.filetree.join('\n')));
+ if (this.config.showDiff) {
+ params.set('showDiff', 'true');
+ if (this.config.diffFilename) params.set('diffFilename', this.config.diffFilename);
+ // Truncate diff text if too long to prevent URL length issues
+ if (this.config.diffOldText) {
+ const truncated = this.config.diffOldText.substring(0, 150);
+ params.set('diffOldText', encodeURIComponent(truncated));
+ }
+ if (this.config.diffNewText) {
+ const truncated = this.config.diffNewText.substring(0, 150);
+ params.set('diffNewText', encodeURIComponent(truncated));
+ }
+ }
return `${window.location.origin}/embed-preview/?${params.toString()}`;
}
@@ -554,44 +654,26 @@ class EmbedDesigner {
}, duration);
}
- loadVibeExample() {
+ loadVibeCodingExample() {
+ // Vibe coding example WITHOUT diff
// Set vibe coding example values
document.getElementById('designer-prompt').value =
-`I'm working on a React e-commerce app. The current ProductList component is getting messy with too much logic.
+`Refactor my React ProductList component:
+- Extract filtering logic to custom hook
+- Split into smaller components
+- Add TypeScript types
+- Implement virtualization
-Can you help me refactor it to:
-1. Extract the filtering logic into a custom hook
-2. Split the large component into smaller, reusable pieces
-3. Add proper TypeScript types
-4. Implement virtualization for better performance with large product lists
+Currently handles display, filtering, sorting, and pagination in one file.
-The component currently handles products display, filtering by category/price, sorting, and pagination all in one file. I want a cleaner architecture.
-
-Please check the @https://tanstack.com/query/latest/docs/react/overview for data fetching best practices and consider if we should integrate it for product data management.`;
+@web Check React Query docs for data fetching patterns.`;
- document.getElementById('designer-context').value = '@codebase, ProductList.tsx, components/, hooks/';
+ document.getElementById('designer-context').value = '@codebase, ProductList.tsx';
document.getElementById('designer-filetree').value =
-`src/
-src/components/
-src/components/ProductList.tsx*
+`src/components/ProductList.tsx*
src/components/ProductCard.tsx
-src/components/ProductGrid.tsx
-src/components/Filters/
-src/components/Filters/CategoryFilter.tsx
-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
-tsconfig.json`;
+src/types/product.ts`;
// Set vibe coding settings
document.getElementById('designer-model').value = 'Claude 4 Opus';
@@ -620,30 +702,94 @@ tsconfig.json`;
this.config.themeMode = 'dark';
this.updateThemeModeButtons();
+ // No diff for this example
+ document.getElementById('designer-show-diff').checked = false;
+ document.getElementById('diff-fields').classList.add('hidden');
+
// Update config from form
this.updateConfigFromForm();
this.showNotification('Vibe coding example loaded!');
}
- loadChatExample() {
- // Set chat example values
+ loadVibeCodingDiffExample() {
+ // Vibe coding WITH diff - user giving feedback on suggested changes
+ document.getElementById('designer-prompt').value =
+`Actually, don't use Error | null for the error state. Instead:
+- Create a custom ApiError type with code, message, and retry()
+- Add an AbortController for request cancellation
+- Include error boundary integration
+- Show me how to handle network vs API errors differently
+
+Also add a refetch function that the UI can call.`;
+
+ document.getElementById('designer-context').value = '@codebase, useProducts.ts';
+ document.getElementById('designer-filetree').value =
+`src/hooks/useProducts.ts*
+src/types/errors.ts
+src/utils/api.ts`;
+
+ // Set same settings as regular vibe coding
+ document.getElementById('designer-model').value = 'Claude 4 Opus';
+ document.getElementById('designer-mode-select').value = 'agent';
+ document.getElementById('designer-thinking').checked = true;
+ document.getElementById('designer-max').checked = true;
+ document.getElementById('designer-show-filetree').checked = true;
+
+ // Set height and colors
+ const heightSlider = document.getElementById('designer-height');
+ const heightValue = document.getElementById('height-value');
+ if (heightSlider) {
+ heightSlider.value = '500';
+ if (heightValue) {
+ heightValue.textContent = '500';
+ }
+ }
+
+ document.getElementById('designer-light-color').value = '#8b5cf6';
+ document.getElementById('designer-light-color-text').value = '#8b5cf6';
+ document.getElementById('designer-dark-color').value = '#a78bfa';
+ document.getElementById('designer-dark-color-text').value = '#a78bfa';
+
+ this.config.themeMode = 'dark';
+ this.updateThemeModeButtons();
+
+ // Add diff view showing the previous suggestion
+ document.getElementById('designer-show-diff').checked = true;
+ document.getElementById('diff-fields').classList.remove('hidden');
+ document.getElementById('designer-diff-filename').value = 'useProducts.ts';
+ document.getElementById('designer-diff-old').value =
+`const [products, setProducts] = useState([]);
+const [loading, setLoading] = useState(true);`;
+ document.getElementById('designer-diff-new').value =
+`const [products, setProducts] = useState
([]);
+const [loading, setLoading] = useState(true);
+const [error, setError] = useState(null);`;
+
+ // Update config from form
+ this.updateConfigFromForm();
+
+ this.showNotification('Vibe coding with diff loaded!');
+ }
+
+ loadChatGPTExample() {
+ // ChatGPT example with green colors
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-context').value = '#Kitchen Layout.png, #Pantry Inventory.jpg';
document.getElementById('designer-filetree').value = '';
- // Set chat settings
+ // ChatGPT 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;
- // Set height and colors for chat example
+ // Set height and colors for ChatGPT
const heightSlider = document.getElementById('designer-height');
const heightValue = document.getElementById('height-value');
if (heightSlider) {
@@ -653,20 +799,184 @@ Can you suggest a menu with appetizers, main course, and dessert that would work
}
}
- // Set friendly color scheme (green)
+ // ChatGPT green color scheme
document.getElementById('designer-light-color').value = '#10b981';
document.getElementById('designer-light-color-text').value = '#10b981';
document.getElementById('designer-dark-color').value = '#34d399';
document.getElementById('designer-dark-color-text').value = '#34d399';
- // Set light theme mode for chat
+ // Light theme for ChatGPT
this.config.themeMode = 'light';
this.updateThemeModeButtons();
+ // Clear diff view
+ document.getElementById('designer-show-diff').checked = false;
+ document.getElementById('diff-fields').classList.add('hidden');
+
// Update config from form
this.updateConfigFromForm();
- this.showNotification('Chat example loaded!');
+ this.showNotification('ChatGPT example loaded!');
+ }
+
+ loadClaudeExample() {
+ // Claude example with orange colors - daily usage
+ document.getElementById('designer-prompt').value =
+`Help me write a professional email to decline a job offer while keeping the door open for future opportunities.
+
+Context:
+- Received offer from TechCorp as Senior Engineer
+- Great team and compensation, but role doesn't align with my career goals
+- Want to maintain good relationship with the hiring manager Sarah
+- Interested in their upcoming ML team expansion next year
+
+Keep it warm but professional, around 150-200 words.`;
+
+ document.getElementById('designer-context').value = '';
+ document.getElementById('designer-filetree').value = '';
+
+ // Claude settings
+ document.getElementById('designer-model').value = 'Claude 3.7 Sonnet';
+ 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;
+
+ // Set height and colors for Claude
+ const heightSlider = document.getElementById('designer-height');
+ const heightValue = document.getElementById('height-value');
+ if (heightSlider) {
+ heightSlider.value = '350';
+ if (heightValue) {
+ heightValue.textContent = '350';
+ }
+ }
+
+ // Claude orange color scheme
+ document.getElementById('designer-light-color').value = '#f97316';
+ document.getElementById('designer-light-color-text').value = '#f97316';
+ document.getElementById('designer-dark-color').value = '#fb923c';
+ document.getElementById('designer-dark-color-text').value = '#fb923c';
+
+ // Auto theme for Claude
+ this.config.themeMode = 'auto';
+ this.updateThemeModeButtons();
+
+ // Clear diff view
+ document.getElementById('designer-show-diff').checked = false;
+ document.getElementById('diff-fields').classList.add('hidden');
+
+ // Update config from form
+ this.updateConfigFromForm();
+
+ this.showNotification('Claude example loaded!');
+ }
+
+ loadImageAnalysisExample() {
+ // Image analysis example
+ document.getElementById('designer-prompt').value =
+`Analyze these UI screenshots and provide detailed feedback on:
+- Visual hierarchy and layout
+- Color scheme and contrast
+- Typography choices
+- Accessibility concerns
+- Mobile responsiveness issues`;
+
+ document.getElementById('designer-context').value = '#Homepage Desktop.png, #Homepage Mobile.png, #Dashboard View.jpg';
+ document.getElementById('designer-filetree').value = '';
+
+ // Image analysis settings
+ document.getElementById('designer-model').value = 'GPT 4.1';
+ 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;
+
+ // Set height and colors
+ const heightSlider = document.getElementById('designer-height');
+ const heightValue = document.getElementById('height-value');
+ if (heightSlider) {
+ heightSlider.value = '400';
+ if (heightValue) {
+ heightValue.textContent = '400';
+ }
+ }
+
+ // Pink/purple color scheme for design
+ document.getElementById('designer-light-color').value = '#ec4899';
+ document.getElementById('designer-light-color-text').value = '#ec4899';
+ document.getElementById('designer-dark-color').value = '#f472b6';
+ document.getElementById('designer-dark-color-text').value = '#f472b6';
+
+ // Light theme
+ this.config.themeMode = 'light';
+ this.updateThemeModeButtons();
+
+ // Clear diff view
+ document.getElementById('designer-show-diff').checked = false;
+ document.getElementById('diff-fields').classList.add('hidden');
+
+ // Update config from form
+ this.updateConfigFromForm();
+
+ this.showNotification('Image analysis example loaded!');
+ }
+
+ loadAPIDesignExample() {
+ // API design example
+ document.getElementById('designer-prompt').value =
+`Design a RESTful API for a task management system with:
+- User authentication
+- Project and task CRUD operations
+- Team collaboration features
+- Real-time notifications
+
+Include endpoint definitions, request/response schemas, and error handling patterns.`;
+
+ document.getElementById('designer-context').value = '@web REST best practices, openapi.yaml';
+ document.getElementById('designer-filetree').value =
+`api/
+api/v1/
+api/v1/users/
+api/v1/projects/
+api/v1/tasks/
+docs/openapi.yaml*`;
+
+ // API design settings
+ document.getElementById('designer-model').value = 'Claude 4 Opus';
+ document.getElementById('designer-mode-select').value = 'manual';
+ document.getElementById('designer-thinking').checked = true;
+ document.getElementById('designer-max').checked = false;
+ document.getElementById('designer-show-filetree').checked = true;
+
+ // Set height and colors
+ const heightSlider = document.getElementById('designer-height');
+ const heightValue = document.getElementById('height-value');
+ if (heightSlider) {
+ heightSlider.value = '500';
+ if (heightValue) {
+ heightValue.textContent = '500';
+ }
+ }
+
+ // Blue color scheme for API
+ document.getElementById('designer-light-color').value = '#3b82f6';
+ document.getElementById('designer-light-color-text').value = '#3b82f6';
+ document.getElementById('designer-dark-color').value = '#60a5fa';
+ document.getElementById('designer-dark-color-text').value = '#60a5fa';
+
+ // Dark theme for technical
+ this.config.themeMode = 'dark';
+ this.updateThemeModeButtons();
+
+ // Clear diff view
+ document.getElementById('designer-show-diff').checked = false;
+ document.getElementById('diff-fields').classList.add('hidden');
+
+ // Update config from form
+ this.updateConfigFromForm();
+
+ this.showNotification('API design example loaded!');
}
}