diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 35d0d343..250b8d7d 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -4,7 +4,11 @@
"Bash(cd:*)",
"Bash(python3:*)",
"Bash(python:*)",
- "Bash(grep:*)"
+ "Bash(grep:*)",
+ "Bash(mkdir:*)",
+ "Bash(cp:*)",
+ "Bash(rm:*)",
+ "Bash(true)"
]
},
"enableAllProjectMcpServers": false
diff --git a/docs/md_v2/apps/assets/DankMono-Bold.woff2 b/docs/md_v2/apps/assets/DankMono-Bold.woff2
new file mode 100644
index 00000000..3072fd85
Binary files /dev/null and b/docs/md_v2/apps/assets/DankMono-Bold.woff2 differ
diff --git a/docs/md_v2/apps/assets/DankMono-Italic.woff2 b/docs/md_v2/apps/assets/DankMono-Italic.woff2
new file mode 100644
index 00000000..1d01ea6d
Binary files /dev/null and b/docs/md_v2/apps/assets/DankMono-Italic.woff2 differ
diff --git a/docs/md_v2/apps/assets/DankMono-Regular.woff2 b/docs/md_v2/apps/assets/DankMono-Regular.woff2
new file mode 100644
index 00000000..99c1425c
Binary files /dev/null and b/docs/md_v2/apps/assets/DankMono-Regular.woff2 differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/README.md b/docs/md_v2/apps/crawl4ai-assistant/README.md
new file mode 100644
index 00000000..9d3841f1
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/README.md
@@ -0,0 +1,123 @@
+# Crawl4AI Chrome Extension
+
+Visual schema and script builder for Crawl4AI - Build extraction schemas by clicking on webpage elements!
+
+## ๐ Features
+
+- **Visual Schema Builder**: Click on elements to build extraction schemas
+- **Smart Element Selection**: Container and field selection with visual feedback
+- **Code Generation**: Generates complete Python code with LLM integration
+- **Beautiful Dark UI**: Consistent with Crawl4AI's design language
+- **One-Click Download**: Get your generated code instantly
+
+## ๐ฆ Installation
+
+### Method 1: Load Unpacked Extension (Recommended for Development)
+
+1. Open Chrome and navigate to `chrome://extensions/`
+2. Enable "Developer mode" in the top right corner
+3. Click "Load unpacked"
+4. Select the `crawl4ai-assistant` folder
+5. The extension icon (๐๐ค) will appear in your toolbar
+
+### Method 2: Generate Icons First
+
+If you want proper icons:
+
+1. Open `icons/generate_icons.html` in your browser
+2. Right-click each canvas and save as:
+ - `icon-16.png`
+ - `icon-48.png`
+ - `icon-128.png`
+3. Then follow Method 1 above
+
+## ๐ฏ How to Use
+
+### Building a Schema
+
+1. **Navigate to any website** you want to extract data from
+2. **Click the Crawl4AI extension icon** in your toolbar
+3. **Click "Schema Builder"** to start the capture mode
+4. **Select a container element**:
+ - Hover over elements (they'll highlight in blue)
+ - Click on a repeating container (e.g., product card, article block)
+5. **Select fields within the container**:
+ - Elements will now highlight in green
+ - Click on each piece of data you want to extract
+ - Name each field (e.g., "title", "price", "description")
+6. **Generate the code**:
+ - Click "Generate Code" in the extension popup
+ - A Python file will automatically download
+
+### Running the Generated Code
+
+The downloaded Python file contains:
+
+```python
+# 1. The HTML snippet of your selected container
+HTML_SNIPPET = """..."""
+
+# 2. The extraction query based on your selections
+EXTRACTION_QUERY = """..."""
+
+# 3. Functions to generate and test the schema
+async def generate_schema():
+ # Generates the extraction schema using LLM
+
+async def test_extraction():
+ # Tests the schema on the actual website
+```
+
+To use it:
+
+1. Install Crawl4AI: `pip install crawl4ai`
+2. Run the script: `python crawl4ai_schema_*.py`
+3. The script will generate a `generated_schema.json` file
+4. Use this schema in your Crawl4AI projects!
+
+## ๐จ Visual Feedback
+
+- **Blue dashed outline**: Container selection mode
+- **Green dashed outline**: Field selection mode
+- **Solid blue outline**: Selected container
+- **Solid green outline**: Selected fields
+- **Floating toolbar**: Shows current mode and selection status
+
+## โจ๏ธ Keyboard Shortcuts
+
+- **ESC**: Cancel current capture session
+
+## ๐ง Technical Details
+
+- Built with Manifest V3 for security and performance
+- Pure client-side - no data sent to external servers
+- Generates code that uses Crawl4AI's LLM integration
+- Smart selector generation prioritizes stable attributes
+
+## ๐ Troubleshooting
+
+### Extension doesn't load
+- Make sure you're in Developer Mode
+- Check the console for any errors
+- Ensure all files are in the correct directories
+
+### Can't select elements
+- Some websites may block extensions
+- Try refreshing the page
+- Make sure you clicked "Schema Builder" first
+
+### Generated code doesn't work
+- Ensure you have Crawl4AI installed
+- Check that you have an LLM API key configured
+- Make sure the website structure hasn't changed
+
+## ๐ค Contributing
+
+This extension is part of the Crawl4AI project. Contributions are welcome!
+
+- Report issues: [GitHub Issues](https://github.com/unclecode/crawl4ai/issues)
+- Join discussion: [Discord](https://discord.gg/crawl4ai)
+
+## ๐ License
+
+Same as Crawl4AI - see main project for details.
\ No newline at end of file
diff --git a/docs/md_v2/apps/crawl4ai-assistant/assets/DankMono-Bold.woff2 b/docs/md_v2/apps/crawl4ai-assistant/assets/DankMono-Bold.woff2
new file mode 100644
index 00000000..3072fd85
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/assets/DankMono-Bold.woff2 differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/assets/DankMono-Italic.woff2 b/docs/md_v2/apps/crawl4ai-assistant/assets/DankMono-Italic.woff2
new file mode 100644
index 00000000..1d01ea6d
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/assets/DankMono-Italic.woff2 differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/assets/DankMono-Regular.woff2 b/docs/md_v2/apps/crawl4ai-assistant/assets/DankMono-Regular.woff2
new file mode 100644
index 00000000..99c1425c
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/assets/DankMono-Regular.woff2 differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/assistant.css b/docs/md_v2/apps/crawl4ai-assistant/assistant.css
new file mode 100644
index 00000000..4b0aac90
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/assistant.css
@@ -0,0 +1,539 @@
+/* Crawl4AI Assistant Landing Page Styles */
+
+/* Font Face Definitions */
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Regular.woff2') format('woff2');
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Bold.woff2') format('woff2');
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Italic.woff2') format('woff2');
+ font-weight: 400;
+ font-style: italic;
+ font-display: swap;
+}
+
+:root {
+ --primary-green: #0fbbaa;
+ --primary-pink: #f380f5;
+ --bg-dark: #070708;
+ --bg-secondary: #1a1a1a;
+ --bg-tertiary: #3f3f44;
+ --text-primary: #e8e9ed;
+ --text-secondary: #a3abba;
+ --text-accent: #d5cec0;
+ --border-color: #3f3f44;
+ --code-bg: #070708;
+ --font-primary: 'Dank Mono', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
+ --font-code: 'Dank Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ background-color: var(--bg-dark);
+ color: var(--text-primary);
+ font-family: var(--font-primary);
+ line-height: 1.6;
+ overflow-x: hidden;
+}
+
+/* Terminal Container */
+.terminal-container {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Header */
+.header {
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-color);
+ padding: 1.5rem 0;
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ backdrop-filter: blur(10px);
+ background: rgba(26, 26, 26, 0.95);
+}
+
+.header-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.logo-section {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.logo {
+ width: 48px;
+ height: 48px;
+}
+
+.logo-section h1 {
+ font-size: 1.75rem;
+ font-weight: 700;
+ color: var(--text-primary);
+}
+
+.tagline {
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ margin-top: 0.25rem;
+}
+
+.nav-links {
+ display: flex;
+ gap: 2rem;
+}
+
+.nav-link {
+ color: var(--text-secondary);
+ text-decoration: none;
+ font-size: 0.875rem;
+ transition: color 0.2s ease;
+}
+
+.nav-link:hover {
+ color: var(--primary-green);
+}
+
+/* Content */
+.content {
+ flex: 1;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 2rem;
+ width: 100%;
+}
+
+/* Video Section */
+.video-section {
+ margin: 3rem 0;
+}
+
+.video-wrapper {
+ position: relative;
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
+ border: 1px solid var(--border-color);
+}
+
+.demo-video {
+ width: 100%;
+ height: auto;
+ display: block;
+}
+
+/* Terminal Windows */
+.terminal-window {
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ overflow: hidden;
+ margin-bottom: 2rem;
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
+}
+
+.terminal-header {
+ background: #2a2a2a;
+ padding: 0.75rem 1rem;
+ border-bottom: 1px solid var(--border-color);
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.terminal-header::before {
+ content: '';
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.terminal-header::before {
+ content: 'โ โ โ';
+ color: #ff5f57;
+ font-size: 12px;
+ letter-spacing: 8px;
+}
+
+.terminal-title {
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+ margin-left: 1rem;
+}
+
+.terminal-content {
+ padding: 2rem;
+}
+
+/* Features Grid */
+.features-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 1.5rem;
+ margin-top: 2rem;
+}
+
+.feature-card {
+ background: var(--bg-tertiary);
+ padding: 1.5rem;
+ border-radius: 8px;
+ border: 1px solid var(--border-color);
+ transition: all 0.2s ease;
+}
+
+.feature-card:hover {
+ border-color: var(--primary-green);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(15, 187, 170, 0.2);
+}
+
+.feature-icon {
+ font-size: 2rem;
+ margin-bottom: 1rem;
+ display: block;
+}
+
+.feature-card h3 {
+ font-size: 1.125rem;
+ margin-bottom: 0.5rem;
+ color: var(--text-primary);
+}
+
+.feature-card p {
+ font-size: 0.875rem;
+ color: var(--text-secondary);
+}
+
+/* Installation Steps */
+.installation-steps {
+ margin-top: 1.5rem;
+}
+
+.step {
+ display: flex;
+ gap: 1.5rem;
+ margin-bottom: 2rem;
+ align-items: flex-start;
+}
+
+.step-number {
+ background: var(--primary-green);
+ color: var(--bg-dark);
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: bold;
+ flex-shrink: 0;
+}
+
+.step-content h4 {
+ margin-bottom: 0.5rem;
+ color: var(--text-primary);
+}
+
+.step-content p {
+ color: var(--text-secondary);
+ margin-bottom: 1rem;
+}
+
+.download-button {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.75rem;
+ background: var(--primary-green);
+ color: var(--bg-dark);
+ padding: 0.75rem 1.5rem;
+ border-radius: 6px;
+ text-decoration: none;
+ font-weight: 600;
+ transition: all 0.2s ease;
+ margin-top: 1rem;
+}
+
+.download-button:hover {
+ background: #1fcbba;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(15, 187, 170, 0.3);
+}
+
+.button-icon {
+ font-size: 1.25rem;
+}
+
+/* Usage Flow */
+.usage-flow {
+ margin-top: 1.5rem;
+}
+
+.usage-step {
+ background: var(--bg-tertiary);
+ padding: 1.5rem;
+ border-radius: 8px;
+ margin-bottom: 1rem;
+ border: 1px solid var(--border-color);
+}
+
+.usage-header {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 0.75rem;
+}
+
+.usage-icon {
+ font-size: 1.5rem;
+}
+
+.usage-step h4 {
+ margin: 0;
+ color: var(--text-primary);
+}
+
+.usage-step p {
+ color: var(--text-secondary);
+ margin-bottom: 0.5rem;
+}
+
+/* Code Snippets */
+.code-snippet {
+ background: var(--code-bg);
+ padding: 0.75rem;
+ border-radius: 4px;
+ margin-top: 0.5rem;
+ font-family: var(--font-code);
+ font-size: 0.875rem;
+ border: 1px solid var(--border-color);
+}
+
+.comment {
+ color: var(--text-secondary);
+ font-style: italic;
+}
+
+/* Code Section */
+pre {
+ margin: 0;
+ overflow-x: auto;
+}
+
+code {
+ font-family: var(--font-code);
+ font-size: 0.875rem;
+ line-height: 1.6;
+}
+
+.keyword {
+ color: var(--primary-green);
+ font-weight: bold;
+}
+
+.string {
+ color: var(--primary-pink);
+}
+
+.function {
+ color: #ff3c74;
+}
+
+/* Coming Soon Section */
+.coming-soon-section {
+ margin: 4rem 0;
+}
+
+.coming-soon-section h2 {
+ font-size: 2rem;
+ margin-bottom: 2rem;
+ color: var(--text-primary);
+}
+
+.intro-text {
+ font-size: 1.125rem;
+ color: var(--text-secondary);
+ margin-bottom: 2rem;
+}
+
+.coming-features {
+ display: grid;
+ gap: 1.5rem;
+ margin-top: 2rem;
+}
+
+.coming-feature {
+ background: var(--bg-tertiary);
+ padding: 2rem;
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+ transition: all 0.3s ease;
+}
+
+.coming-feature:hover {
+ border-color: var(--primary-green);
+ transform: translateY(-2px);
+ box-shadow: 0 8px 24px rgba(15, 187, 170, 0.2);
+}
+
+.feature-header {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+.feature-badge {
+ background: var(--primary-green);
+ color: var(--bg-dark);
+ padding: 0.25rem 0.75rem;
+ border-radius: 20px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+}
+
+.coming-feature h3 {
+ font-size: 1.25rem;
+ color: var(--text-primary);
+ margin: 0;
+}
+
+.coming-feature p {
+ color: var(--text-secondary);
+ margin-bottom: 1rem;
+ line-height: 1.6;
+}
+
+.feature-preview {
+ background: var(--bg-secondary);
+ padding: 1rem;
+ border-radius: 6px;
+ font-family: var(--font-code);
+ font-size: 0.875rem;
+ color: var(--text-accent);
+ border: 1px solid var(--border-color);
+}
+
+.stay-tuned {
+ text-align: center;
+ margin-top: 3rem;
+ padding: 2rem;
+ background: var(--bg-tertiary);
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+}
+
+.stay-tuned p {
+ font-size: 1.125rem;
+ color: var(--text-primary);
+}
+
+.stay-tuned a {
+ color: var(--primary-green);
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.stay-tuned a:hover {
+ text-decoration: underline;
+}
+
+/* Footer */
+.footer {
+ background: var(--bg-secondary);
+ border-top: 1px solid var(--border-color);
+ margin-top: 4rem;
+ padding: 3rem 0 2rem;
+}
+
+.footer-content {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 2rem;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+ gap: 3rem;
+}
+
+.footer-section h4 {
+ margin-bottom: 1rem;
+ color: var(--text-primary);
+}
+
+.footer-section ul {
+ list-style: none;
+}
+
+.footer-section li {
+ margin-bottom: 0.5rem;
+}
+
+.footer-section a {
+ color: var(--text-secondary);
+ text-decoration: none;
+ transition: color 0.2s ease;
+}
+
+.footer-section a:hover {
+ color: var(--primary-green);
+}
+
+.footer-bottom {
+ text-align: center;
+ margin-top: 2rem;
+ padding-top: 2rem;
+ border-top: 1px solid var(--border-color);
+ color: var(--text-secondary);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .header-content {
+ flex-direction: column;
+ gap: 1.5rem;
+ }
+
+ .nav-links {
+ gap: 1rem;
+ }
+
+ .features-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .step {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .coming-soon-section h2 {
+ font-size: 1.5rem;
+ }
+}
\ No newline at end of file
diff --git a/docs/md_v2/apps/crawl4ai-assistant/background/service-worker.js b/docs/md_v2/apps/crawl4ai-assistant/background/service-worker.js
new file mode 100644
index 00000000..30b8b37b
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/background/service-worker.js
@@ -0,0 +1,39 @@
+// Service worker for Crawl4AI Assistant
+
+// Handle messages from content script
+chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+ if (message.action === 'downloadCode') {
+ try {
+ // Create a data URL for the Python code
+ const dataUrl = 'data:text/plain;charset=utf-8,' + encodeURIComponent(message.code);
+
+ // Download the file
+ chrome.downloads.download({
+ url: dataUrl,
+ filename: message.filename || 'crawl4ai_schema.py',
+ saveAs: true
+ }, (downloadId) => {
+ if (chrome.runtime.lastError) {
+ console.error('Download failed:', chrome.runtime.lastError);
+ sendResponse({ success: false, error: chrome.runtime.lastError.message });
+ } else {
+ console.log('Download started with ID:', downloadId);
+ sendResponse({ success: true, downloadId: downloadId });
+ }
+ });
+ } catch (error) {
+ console.error('Error creating download:', error);
+ sendResponse({ success: false, error: error.message });
+ }
+
+ return true; // Keep the message channel open for async response
+ }
+
+ return false;
+});
+
+// Clean up on extension install/update
+chrome.runtime.onInstalled.addListener(() => {
+ // Clear any stored state
+ chrome.storage.local.clear();
+});
\ No newline at end of file
diff --git a/docs/md_v2/apps/crawl4ai-assistant/content/content.js b/docs/md_v2/apps/crawl4ai-assistant/content/content.js
new file mode 100644
index 00000000..9c9519c0
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/content/content.js
@@ -0,0 +1,738 @@
+// Content script for Crawl4AI Assistant
+class SchemaBuilder {
+ constructor() {
+ this.mode = null;
+ this.container = null;
+ this.fields = [];
+ this.overlay = null;
+ this.toolbar = null;
+ this.highlightBox = null;
+ this.selectedElements = new Set();
+ this.isPaused = false;
+ this.codeModal = null;
+
+ this.handleMouseMove = this.handleMouseMove.bind(this);
+ this.handleClick = this.handleClick.bind(this);
+ this.handleKeyPress = this.handleKeyPress.bind(this);
+ }
+
+ start() {
+ this.mode = 'container';
+ this.createOverlay();
+ this.createToolbar();
+ this.attachEventListeners();
+ this.updateToolbar();
+ }
+
+ stop() {
+ this.detachEventListeners();
+ this.overlay?.remove();
+ this.toolbar?.remove();
+ this.highlightBox?.remove();
+ this.removeAllHighlights();
+ this.mode = null;
+ this.container = null;
+ this.fields = [];
+ this.selectedElements.clear();
+ }
+
+ createOverlay() {
+ // Create highlight box
+ this.highlightBox = document.createElement('div');
+ this.highlightBox.className = 'c4ai-highlight-box';
+ document.body.appendChild(this.highlightBox);
+ }
+
+ createToolbar() {
+ this.toolbar = document.createElement('div');
+ this.toolbar.className = 'c4ai-toolbar';
+ this.toolbar.innerHTML = `
+
+
+
+
+
+ Click on a container element (e.g., product card, article, etc.)
+
+
+
+ โธ Pause
+
+
+ โก Generate Code
+
+
+
+ `;
+ document.body.appendChild(this.toolbar);
+
+ // Add event listeners for toolbar buttons
+ document.getElementById('c4ai-pause').addEventListener('click', () => this.togglePause());
+ document.getElementById('c4ai-generate').addEventListener('click', () => this.stopAndGenerate());
+ document.getElementById('c4ai-close').addEventListener('click', () => this.stop());
+
+ // Make toolbar draggable
+ this.makeDraggable(this.toolbar);
+ }
+
+ attachEventListeners() {
+ document.addEventListener('mousemove', this.handleMouseMove, true);
+ document.addEventListener('click', this.handleClick, true);
+ document.addEventListener('keydown', this.handleKeyPress, true);
+ }
+
+ detachEventListeners() {
+ document.removeEventListener('mousemove', this.handleMouseMove, true);
+ document.removeEventListener('click', this.handleClick, true);
+ document.removeEventListener('keydown', this.handleKeyPress, true);
+ }
+
+ handleMouseMove(e) {
+ if (this.isPaused) return;
+
+ const element = document.elementFromPoint(e.clientX, e.clientY);
+ if (element && !this.isOurElement(element)) {
+ this.highlightElement(element);
+ }
+ }
+
+ handleClick(e) {
+ if (this.isPaused) return;
+
+ const element = e.target;
+
+ if (this.isOurElement(element)) {
+ return;
+ }
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (this.mode === 'container') {
+ this.selectContainer(element);
+ } else if (this.mode === 'field') {
+ this.selectField(element);
+ }
+ }
+
+ handleKeyPress(e) {
+ if (e.key === 'Escape') {
+ this.stop();
+ }
+ }
+
+ isOurElement(element) {
+ return element.classList.contains('c4ai-highlight-box') ||
+ element.classList.contains('c4ai-toolbar') ||
+ element.closest('.c4ai-toolbar') ||
+ element.closest('.c4ai-field-dialog') ||
+ element.closest('.c4ai-code-modal');
+ }
+
+ makeDraggable(element) {
+ let isDragging = false;
+ let startX, startY, initialX, initialY;
+
+ const titlebar = element.querySelector('.c4ai-toolbar-titlebar');
+
+ titlebar.addEventListener('mousedown', (e) => {
+ // Don't drag if clicking on buttons
+ if (e.target.classList.contains('c4ai-dot')) return;
+
+ isDragging = true;
+ startX = e.clientX;
+ startY = e.clientY;
+
+ const rect = element.getBoundingClientRect();
+ initialX = rect.left;
+ initialY = rect.top;
+
+ element.style.transition = 'none';
+ titlebar.style.cursor = 'grabbing';
+ });
+
+ document.addEventListener('mousemove', (e) => {
+ if (!isDragging) return;
+
+ const deltaX = e.clientX - startX;
+ const deltaY = e.clientY - startY;
+
+ element.style.left = `${initialX + deltaX}px`;
+ element.style.top = `${initialY + deltaY}px`;
+ element.style.right = 'auto';
+ });
+
+ document.addEventListener('mouseup', () => {
+ if (isDragging) {
+ isDragging = false;
+ element.style.transition = '';
+ titlebar.style.cursor = 'grab';
+ }
+ });
+ }
+
+ togglePause() {
+ this.isPaused = !this.isPaused;
+ const pauseBtn = document.getElementById('c4ai-pause');
+ if (this.isPaused) {
+ pauseBtn.innerHTML = 'โถ Resume';
+ pauseBtn.classList.add('c4ai-paused');
+ this.highlightBox.style.display = 'none';
+ } else {
+ pauseBtn.innerHTML = 'โธ Pause';
+ pauseBtn.classList.remove('c4ai-paused');
+ }
+ }
+
+ stopAndGenerate() {
+ if (!this.container || this.fields.length === 0) {
+ alert('Please select a container and at least one field before generating code.');
+ return;
+ }
+
+ const code = this.generateCode();
+ this.showCodeModal(code);
+ }
+
+ highlightElement(element) {
+ const rect = element.getBoundingClientRect();
+ this.highlightBox.style.cssText = `
+ left: ${rect.left + window.scrollX}px;
+ top: ${rect.top + window.scrollY}px;
+ width: ${rect.width}px;
+ height: ${rect.height}px;
+ display: block;
+ `;
+
+ if (this.mode === 'container') {
+ this.highlightBox.className = 'c4ai-highlight-box c4ai-container-mode';
+ } else {
+ this.highlightBox.className = 'c4ai-highlight-box c4ai-field-mode';
+ }
+ }
+
+ selectContainer(element) {
+ // Remove previous container highlight
+ if (this.container) {
+ this.container.element.classList.remove('c4ai-selected-container');
+ }
+
+ this.container = {
+ element: element,
+ html: element.outerHTML,
+ selector: this.generateSelector(element),
+ tagName: element.tagName.toLowerCase()
+ };
+
+ element.classList.add('c4ai-selected-container');
+ this.mode = 'field';
+ this.updateToolbar();
+ this.updateStats();
+ }
+
+ selectField(element) {
+ // Don't select the container itself
+ if (element === this.container.element) {
+ return;
+ }
+
+ // Check if already selected - if so, deselect it
+ if (this.selectedElements.has(element)) {
+ this.deselectField(element);
+ return;
+ }
+
+ // Must be inside the container
+ if (!this.container.element.contains(element)) {
+ return;
+ }
+
+ this.showFieldDialog(element);
+ }
+
+ deselectField(element) {
+ // Remove from fields array
+ this.fields = this.fields.filter(f => f.element !== element);
+
+ // Remove from selected elements set
+ this.selectedElements.delete(element);
+
+ // Remove visual selection
+ element.classList.remove('c4ai-selected-field');
+
+ // Update UI
+ this.updateToolbar();
+ this.updateStats();
+ }
+
+ showFieldDialog(element) {
+ const dialog = document.createElement('div');
+ dialog.className = 'c4ai-field-dialog';
+
+ const rect = element.getBoundingClientRect();
+ dialog.style.cssText = `
+ left: ${rect.left + window.scrollX}px;
+ top: ${rect.bottom + window.scrollY + 10}px;
+ `;
+
+ dialog.innerHTML = `
+
+
Name this field:
+
+
+ Content: ${element.textContent.trim().substring(0, 50)}...
+
+
+ Save
+ Cancel
+
+
+ `;
+
+ document.body.appendChild(dialog);
+
+ const input = dialog.querySelector('#c4ai-field-name');
+ const saveBtn = dialog.querySelector('#c4ai-field-save');
+ const cancelBtn = dialog.querySelector('#c4ai-field-cancel');
+
+ const save = () => {
+ const fieldName = input.value.trim();
+ if (fieldName) {
+ this.fields.push({
+ name: fieldName,
+ value: element.textContent.trim(),
+ element: element,
+ selector: this.generateSelector(element, this.container.element)
+ });
+
+ element.classList.add('c4ai-selected-field');
+ this.selectedElements.add(element);
+ this.updateToolbar();
+ this.updateStats();
+ }
+ dialog.remove();
+ };
+
+ const cancel = () => {
+ dialog.remove();
+ };
+
+ saveBtn.addEventListener('click', save);
+ cancelBtn.addEventListener('click', cancel);
+ input.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') save();
+ if (e.key === 'Escape') cancel();
+ });
+
+ input.focus();
+ }
+
+ generateSelector(element, context = document) {
+ // Try to generate a robust selector
+ if (element.id) {
+ return `#${CSS.escape(element.id)}`;
+ }
+
+ // Check for data attributes (most stable)
+ const dataAttrs = ['data-testid', 'data-id', 'data-test', 'data-cy'];
+ for (const attr of dataAttrs) {
+ const value = element.getAttribute(attr);
+ if (value) {
+ return `[${attr}="${value}"]`;
+ }
+ }
+
+ // Check for aria-label
+ if (element.getAttribute('aria-label')) {
+ return `[aria-label="${element.getAttribute('aria-label')}"]`;
+ }
+
+ // Try semantic HTML elements with text
+ const tagName = element.tagName.toLowerCase();
+ if (['button', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) {
+ const text = element.textContent.trim();
+ if (text && text.length < 50) {
+ // Use tag name with partial text match
+ return `${tagName}`;
+ }
+ }
+
+ // Check for simple, non-utility classes
+ const classes = Array.from(element.classList)
+ .filter(c => !c.startsWith('c4ai-')) // Exclude our classes
+ .filter(c => !c.includes('[') && !c.includes('(') && !c.includes(':')) // Exclude utility classes
+ .filter(c => c.length < 30); // Exclude very long classes
+
+ if (classes.length > 0 && classes.length <= 3) {
+ const selector = classes.map(c => `.${CSS.escape(c)}`).join('');
+ try {
+ if (context.querySelectorAll(selector).length === 1) {
+ return selector;
+ }
+ } catch (e) {
+ // Invalid selector, continue
+ }
+ }
+
+ // Use nth-child with simple parent tag
+ const parent = element.parentElement;
+ if (parent && parent !== context) {
+ const siblings = Array.from(parent.children);
+ const index = siblings.indexOf(element) + 1;
+ // Just use parent tag name to avoid recursion
+ const parentTag = parent.tagName.toLowerCase();
+ return `${parentTag} > ${tagName}:nth-child(${index})`;
+ }
+
+ // Final fallback
+ return tagName;
+ }
+
+ updateToolbar() {
+ document.getElementById('c4ai-mode').textContent =
+ this.mode === 'container' ? 'Select Container' : 'Select Fields';
+
+ document.getElementById('c4ai-container').textContent =
+ this.container ? `${this.container.tagName} โ` : 'Not selected';
+
+ // Update fields list
+ const fieldsList = document.getElementById('c4ai-fields-list');
+ const fieldsItems = document.getElementById('c4ai-fields-items');
+
+ if (this.fields.length > 0) {
+ fieldsList.style.display = 'block';
+ fieldsItems.innerHTML = this.fields.map(field => `
+
+ ${field.name}
+ ${field.value.substring(0, 30)}${field.value.length > 30 ? '...' : ''}
+
+ `).join('');
+ } else {
+ fieldsList.style.display = 'none';
+ }
+
+ const hint = document.getElementById('c4ai-hint');
+ if (this.mode === 'container') {
+ hint.textContent = 'Click on a container element (e.g., product card, article, etc.)';
+ } else if (this.fields.length === 0) {
+ hint.textContent = 'Click on fields inside the container to extract (title, price, etc.)';
+ } else {
+ hint.innerHTML = `Continue selecting fields or click Stop & Generate to finish.`;
+ }
+ }
+
+ updateStats() {
+ chrome.runtime.sendMessage({
+ action: 'updateStats',
+ stats: {
+ container: !!this.container,
+ fields: this.fields.length
+ }
+ });
+ }
+
+ removeAllHighlights() {
+ document.querySelectorAll('.c4ai-selected-container').forEach(el => {
+ el.classList.remove('c4ai-selected-container');
+ });
+ document.querySelectorAll('.c4ai-selected-field').forEach(el => {
+ el.classList.remove('c4ai-selected-field');
+ });
+ }
+
+ generateCode() {
+ const fieldDescriptions = this.fields.map(f =>
+ `- ${f.name} (example: "${f.value.substring(0, 50)}...")`
+ ).join('\n');
+
+ return `#!/usr/bin/env python3
+"""
+Generated by Crawl4AI Chrome Extension
+URL: ${window.location.href}
+Generated: ${new Date().toISOString()}
+"""
+
+import asyncio
+import json
+from pathlib import Path
+from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig
+from crawl4ai.extraction_strategy import JsonCssExtractionStrategy
+
+# HTML snippet of the selected container element
+HTML_SNIPPET = """
+${this.container.html}
+"""
+
+# Extraction query based on your field selections
+EXTRACTION_QUERY = """
+Create a JSON CSS extraction schema to extract the following fields:
+${fieldDescriptions}
+
+The schema should handle multiple ${this.container.tagName} elements on the page.
+Each item should be extracted as a separate object in the results array.
+"""
+
+async def generate_schema():
+ """Generate extraction schema using LLM"""
+ print("๐ง Generating extraction schema...")
+
+ try:
+ # Generate the schema using Crawl4AI's built-in LLM integration
+ schema = JsonCssExtractionStrategy.generate_schema(
+ html=HTML_SNIPPET,
+ query=EXTRACTION_QUERY,
+ )
+
+ # Save the schema for reuse
+ schema_path = Path('generated_schema.json')
+ with open(schema_path, 'w') as f:
+ json.dump(schema, f, indent=2)
+
+ print("โ
Schema generated successfully!")
+ print(f"๐ Schema saved to: {schema_path}")
+ print("\\nGenerated schema:")
+ print(json.dumps(schema, indent=2))
+
+ return schema
+
+ except Exception as e:
+ print(f"โ Error generating schema: {e}")
+ return None
+
+async def test_extraction(url: str = "${window.location.href}"):
+ """Test the generated schema on the actual webpage"""
+ print("\\n๐งช Testing extraction on live webpage...")
+
+ # Load the generated schema
+ try:
+ with open('generated_schema.json', 'r') as f:
+ schema = json.load(f)
+ except FileNotFoundError:
+ print("โ Schema file not found. Run generate_schema() first.")
+ return
+
+ # Configure browser
+ browser_config = BrowserConfig(
+ headless=True,
+ verbose=False
+ )
+
+ # Configure extraction
+ crawler_config = CrawlerRunConfig(
+ extraction_strategy=JsonCssExtractionStrategy(schema=schema)
+ )
+
+ async with AsyncWebCrawler(config=browser_config) as crawler:
+ result = await crawler.arun(
+ url=url,
+ config=crawler_config
+ )
+
+ if result.success and result.extracted_content:
+ data = json.loads(result.extracted_content)
+ print(f"\\nโ
Successfully extracted {len(data)} items!")
+
+ # Save results
+ with open('extracted_data.json', 'w') as f:
+ json.dump(data, f, indent=2)
+
+ # Show sample results
+ print("\\n๐ Sample results (first 2 items):")
+ for i, item in enumerate(data[:2], 1):
+ print(f"\\nItem {i}:")
+ for key, value in item.items():
+ print(f" {key}: {value}")
+ else:
+ print("โ Extraction failed:", result.error_message)
+
+if __name__ == "__main__":
+ # Step 1: Generate the schema from HTML snippet
+ asyncio.run(generate_schema())
+
+ # Step 2: Test extraction on the live webpage
+ # Uncomment the line below to test extraction:
+ # asyncio.run(test_extraction())
+
+ print("\\n๐ฏ Next steps:")
+ print("1. Review the generated schema in 'generated_schema.json'")
+ print("2. Uncomment the test_extraction() line to test on the live site")
+ print("3. Use the schema in your Crawl4AI projects!")
+`;
+
+ return code;
+ }
+
+ showCodeModal(code) {
+ // Create modal
+ this.codeModal = document.createElement('div');
+ this.codeModal.className = 'c4ai-code-modal';
+ this.codeModal.innerHTML = `
+
+
+
+
${this.escapeHtml(code)}
+
+
+
+ `;
+
+ document.body.appendChild(this.codeModal);
+
+ // Add event listeners
+ document.getElementById('c4ai-close-modal').addEventListener('click', () => {
+ this.codeModal.remove();
+ this.codeModal = null;
+ // Don't stop the capture session
+ });
+
+ document.getElementById('c4ai-download-code').addEventListener('click', () => {
+ chrome.runtime.sendMessage({
+ action: 'downloadCode',
+ code: code,
+ filename: `crawl4ai_schema_${Date.now()}.py`
+ }, (response) => {
+ if (response && response.success) {
+ const btn = document.getElementById('c4ai-download-code');
+ const originalHTML = btn.innerHTML;
+ btn.innerHTML = 'โ Downloaded!';
+ setTimeout(() => {
+ btn.innerHTML = originalHTML;
+ }, 2000);
+ } else {
+ console.error('Download failed:', response?.error);
+ alert('Download failed. Please check your browser settings.');
+ }
+ });
+ });
+
+ document.getElementById('c4ai-copy-code').addEventListener('click', () => {
+ navigator.clipboard.writeText(code).then(() => {
+ const btn = document.getElementById('c4ai-copy-code');
+ btn.innerHTML = 'โ Copied!';
+ setTimeout(() => {
+ btn.innerHTML = '๐ Copy to Clipboard';
+ }, 2000);
+ });
+ });
+
+ // Apply syntax highlighting if possible
+ this.applySyntaxHighlighting();
+ }
+
+ escapeHtml(text) {
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+ }
+
+ applySyntaxHighlighting() {
+ // Simple Python syntax highlighting - using a different approach
+ const codeElement = this.codeModal.querySelector('.language-python');
+ const code = codeElement.textContent;
+
+ // Split by lines to handle line-by-line
+ const lines = code.split('\n');
+ const highlightedLines = lines.map(line => {
+ let highlightedLine = this.escapeHtml(line);
+
+ // Skip if line is empty
+ if (!highlightedLine.trim()) return highlightedLine;
+
+ // Comments (lines starting with #)
+ if (highlightedLine.trim().startsWith('#')) {
+ return ``;
+ }
+
+ // Triple quoted strings
+ if (highlightedLine.includes('"""')) {
+ highlightedLine = highlightedLine.replace(/(""".*?""")/g, '$1 ');
+ }
+
+ // Regular strings - single and double quotes
+ highlightedLine = highlightedLine.replace(/(["'])([^"']*)\1/g, '$1$2$1 ');
+
+ // Keywords - only highlight if not inside a string
+ const keywords = ['import', 'from', 'async', 'def', 'await', 'try', 'except', 'with', 'as', 'for', 'if', 'else', 'elif', 'return', 'print', 'open', 'and', 'or', 'not', 'in', 'is', 'class', 'self', 'None', 'True', 'False', '__name__', '__main__'];
+
+ keywords.forEach(keyword => {
+ // Use word boundaries and lookahead/lookbehind to ensure we're not in a string
+ const regex = new RegExp(`\\b(${keyword})\\b(?![^<]*)`, 'g');
+ highlightedLine = highlightedLine.replace(regex, '$1 ');
+ });
+
+ // Functions (word followed by parenthesis)
+ highlightedLine = highlightedLine.replace(/\b([a-zA-Z_]\w*)\s*\(/g, '$1 (');
+
+ return highlightedLine;
+ });
+
+ codeElement.innerHTML = highlightedLines.join('\n');
+ }
+}
+
+// Initialize
+let schemaBuilder = null;
+
+// Listen for messages from popup
+chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+ switch (message.action) {
+ case 'startSchemaCapture':
+ if (!schemaBuilder) {
+ schemaBuilder = new SchemaBuilder();
+ }
+ schemaBuilder.start();
+ sendResponse({ success: true });
+ break;
+
+ case 'stopCapture':
+ if (schemaBuilder) {
+ schemaBuilder.stop();
+ schemaBuilder = null;
+ }
+ sendResponse({ success: true });
+ break;
+
+ case 'generateCode':
+ if (schemaBuilder) {
+ const code = schemaBuilder.generateCode();
+ schemaBuilder.showCodeModal(code);
+ }
+ sendResponse({ success: true });
+ break;
+ }
+
+ return true;
+});
\ No newline at end of file
diff --git a/docs/md_v2/apps/crawl4ai-assistant/content/overlay.css b/docs/md_v2/apps/crawl4ai-assistant/content/overlay.css
new file mode 100644
index 00000000..56dd5d6e
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/content/overlay.css
@@ -0,0 +1,561 @@
+/* Crawl4AI Assistant Overlay Styles */
+
+/* Font Face Definitions */
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Regular.woff2') format('woff2');
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Bold.woff2') format('woff2');
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Italic.woff2') format('woff2');
+ font-weight: 400;
+ font-style: italic;
+ font-display: swap;
+}
+
+:root {
+ --font-primary: 'Dank Mono', -apple-system, BlinkMacSystemFont, sans-serif;
+ --font-code: 'Dank Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
+}
+
+/* Highlight box for hovering */
+.c4ai-highlight-box {
+ position: absolute;
+ pointer-events: none;
+ z-index: 999999;
+ transition: all 0.1s ease;
+ display: none;
+}
+
+.c4ai-highlight-box.c4ai-container-mode {
+ border: 3px dashed #0fbbaa;
+ background: rgba(15, 187, 170, 0.1);
+ box-shadow: 0 0 0 2px rgba(15, 187, 170, 0.3);
+}
+
+.c4ai-highlight-box.c4ai-field-mode {
+ border: 2px dashed #f380f5;
+ background: rgba(243, 128, 245, 0.1);
+ box-shadow: 0 0 0 2px rgba(243, 128, 245, 0.3);
+}
+
+/* Selected elements */
+.c4ai-selected-container {
+ outline: 3px solid #0fbbaa !important;
+ outline-offset: 2px;
+ background: rgba(15, 187, 170, 0.05) !important;
+}
+
+.c4ai-selected-field {
+ outline: 2px solid #f380f5 !important;
+ outline-offset: 1px;
+ background: rgba(243, 128, 245, 0.1) !important;
+ position: relative;
+}
+
+.c4ai-selected-field::after {
+ content: attr(data-c4ai-field);
+ position: absolute;
+ top: -24px;
+ left: 0;
+ background: #f380f5;
+ color: #000;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: bold;
+ font-family: var(--font-primary);
+ z-index: 999999;
+ white-space: nowrap;
+}
+
+/* Toolbar */
+.c4ai-toolbar {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ background: #070708;
+ border: 1px solid #3f3f44;
+ border-radius: 8px;
+ z-index: 999999;
+ font-family: var(--font-primary);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8);
+ width: 320px;
+ color: #e8e9ed;
+ overflow: hidden;
+}
+
+.c4ai-toolbar * {
+ font-family: inherit;
+ box-sizing: border-box;
+}
+
+/* macOS-style titlebar */
+.c4ai-toolbar-titlebar {
+ display: flex;
+ align-items: center;
+ padding: 12px;
+ background: #1a1a1a;
+ border-bottom: 1px solid #3f3f44;
+ cursor: grab;
+ user-select: none;
+}
+
+.c4ai-titlebar-dots {
+ display: flex;
+ gap: 8px;
+ flex-shrink: 0;
+}
+
+.c4ai-dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ border: none;
+ cursor: pointer;
+ transition: opacity 0.2s ease;
+ padding: 0;
+}
+
+.c4ai-dot-close {
+ background: #ff5f57;
+}
+
+.c4ai-dot-close:hover {
+ background: #ff3030;
+}
+
+.c4ai-dot-minimize {
+ background: #ffbd2e;
+}
+
+.c4ai-dot-minimize:hover {
+ background: #ffaa00;
+}
+
+.c4ai-dot-maximize {
+ background: #28ca42;
+}
+
+.c4ai-dot-maximize:hover {
+ background: #1eb533;
+}
+
+.c4ai-titlebar-icon {
+ width: 16px;
+ height: 16px;
+ margin-left: 8px;
+ margin-right: 8px;
+}
+
+.c4ai-titlebar-title {
+ flex: 1;
+ text-align: left;
+ font-size: 13px;
+ font-weight: 600;
+ color: #e8e9ed;
+}
+
+.c4ai-toolbar-content {
+ padding: 16px;
+}
+
+.c4ai-toolbar-status {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ margin-bottom: 16px;
+ padding: 12px;
+ background: #3f3f44;
+ border-radius: 8px;
+}
+
+.c4ai-status-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.c4ai-status-label {
+ font-size: 12px;
+ color: #a3abba;
+ text-transform: uppercase;
+}
+
+.c4ai-status-value {
+ font-size: 14px;
+ font-weight: 600;
+ color: #e8e9ed;
+}
+
+.c4ai-toolbar-hint {
+ margin-top: 16px;
+ font-size: 13px;
+ line-height: 1.4;
+ color: #d5cec0;
+ padding: 12px;
+ background: #3f3f44;
+ border-radius: 8px;
+ border-left: 3px solid #0fbbaa;
+}
+
+.c4ai-toolbar-hint strong {
+ color: #0fbbaa;
+ font-weight: 600;
+}
+
+/* Fields list */
+.c4ai-fields-list {
+ margin-top: 16px;
+ padding: 12px;
+ background: #3f3f44;
+ border-radius: 8px;
+ border: 1px solid #3f3f44;
+}
+
+.c4ai-fields-header {
+ font-size: 12px;
+ text-transform: uppercase;
+ color: #a3abba;
+ margin-bottom: 8px;
+ font-weight: 600;
+}
+
+.c4ai-fields-items {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.c4ai-field-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 6px 8px;
+ margin-bottom: 4px;
+ background: #070708;
+ border-radius: 4px;
+ font-size: 12px;
+}
+
+.c4ai-field-name {
+ font-weight: 600;
+ color: #f380f5;
+ margin-right: 8px;
+}
+
+.c4ai-field-value {
+ color: #a3abba;
+ font-size: 11px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex: 1;
+ text-align: right;
+}
+
+/* Field naming dialog */
+.c4ai-field-dialog {
+ position: absolute;
+ background: #070708;
+ border: 2px solid #f380f5;
+ border-radius: 12px;
+ padding: 16px;
+ z-index: 999999;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8);
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
+ color: #e8e9ed;
+ min-width: 300px;
+}
+
+.c4ai-field-dialog h4 {
+ margin: 0 0 12px 0;
+ font-size: 14px;
+ color: #e8e9ed;
+}
+
+.c4ai-field-dialog input {
+ width: 100%;
+ padding: 8px 12px;
+ background: #3f3f44;
+ border: 1px solid #3f3f44;
+ border-radius: 6px;
+ color: #e8e9ed;
+ font-size: 14px;
+ margin-bottom: 12px;
+ outline: none;
+}
+
+.c4ai-field-dialog input:focus {
+ border-color: #f380f5;
+ box-shadow: 0 0 0 2px rgba(243, 128, 245, 0.2);
+}
+
+.c4ai-field-preview {
+ font-size: 12px;
+ color: #999;
+ margin-bottom: 12px;
+ padding: 8px;
+ background: #1a1a1a;
+ border-radius: 6px;
+ word-break: break-word;
+}
+
+.c4ai-field-preview strong {
+ color: #ccc;
+}
+
+.c4ai-field-actions {
+ display: flex;
+ gap: 8px;
+ justify-content: flex-end;
+}
+
+.c4ai-field-actions button {
+ padding: 6px 16px;
+ border: none;
+ border-radius: 6px;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+#c4ai-field-save {
+ background: #f380f5;
+ color: #070708;
+}
+
+#c4ai-field-save:hover {
+ background: #e370e5;
+ transform: translateY(-1px);
+}
+
+#c4ai-field-cancel {
+ background: #3f3f44;
+ color: #e8e9ed;
+}
+
+#c4ai-field-cancel:hover {
+ background: #4f4f54;
+}
+
+/* Toolbar action buttons */
+.c4ai-toolbar-actions {
+ display: flex;
+ gap: 8px;
+ margin-top: 16px;
+ padding-top: 16px;
+ border-top: 1px solid #2a2a2a;
+}
+
+.c4ai-action-btn {
+ flex: 1;
+ padding: 8px 16px;
+ background: #3f3f44;
+ border: 1px solid #3f3f44;
+ border-radius: 6px;
+ color: #e8e9ed;
+ font-size: 13px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+}
+
+.c4ai-action-btn:hover {
+ background: #4f4f54;
+ border-color: #5f5f64;
+ transform: translateY(-1px);
+}
+
+.c4ai-pause-btn {
+ background: #09b5a5;
+ border-color: #09b5a5;
+ color: #070708;
+}
+
+.c4ai-pause-btn:hover {
+ background: #0ac5b5;
+}
+
+.c4ai-pause-btn.c4ai-paused {
+ background: #ff3c74;
+ border-color: #ff3c74;
+}
+
+.c4ai-generate-btn {
+ background: #0fbbaa;
+ border-color: #0fbbaa;
+ color: #070708;
+}
+
+.c4ai-generate-btn:hover {
+ background: #1fcbba;
+}
+
+/* Code modal */
+.c4ai-code-modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.8);
+ z-index: 999999;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+}
+
+.c4ai-code-modal-content {
+ background: #070708;
+ border: 2px solid #3f3f44;
+ border-radius: 16px;
+ width: 90%;
+ max-width: 900px;
+ max-height: 90vh;
+ display: flex;
+ flex-direction: column;
+ box-shadow: 0 16px 64px rgba(0, 0, 0, 0.9);
+}
+
+.c4ai-code-modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px 24px;
+ border-bottom: 1px solid #2a2a2a;
+}
+
+.c4ai-code-modal-header h2 {
+ margin: 0;
+ font-size: 20px;
+ color: #fff;
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
+}
+
+.c4ai-close-modal {
+ background: none;
+ border: none;
+ color: #999;
+ font-size: 24px;
+ cursor: pointer;
+ padding: 0;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: all 0.2s ease;
+}
+
+.c4ai-close-modal:hover {
+ background: #2a2a2a;
+ color: #fff;
+}
+
+.c4ai-code-modal-body {
+ flex: 1;
+ overflow: auto;
+ padding: 24px;
+}
+
+.c4ai-code-block {
+ background: #3f3f44;
+ border: 1px solid #3f3f44;
+ border-radius: 8px;
+ padding: 20px;
+ overflow-x: auto;
+ margin: 0;
+}
+
+.c4ai-code-block code {
+ font-family: var(--font-code);
+ font-size: 13px;
+ line-height: 1.6;
+ color: #e8e9ed;
+ white-space: pre;
+ display: block;
+}
+
+/* Syntax highlighting */
+.c4ai-keyword {
+ color: #0fbbaa;
+ font-weight: bold;
+}
+
+.c4ai-string {
+ color: #f380f5;
+}
+
+.c4ai-comment {
+ color: #a3abba;
+ font-style: italic;
+}
+
+.c4ai-function {
+ color: #ff3c74;
+}
+
+.c4ai-code-modal-footer {
+ display: flex;
+ gap: 12px;
+ padding: 20px 24px;
+ border-top: 1px solid #2a2a2a;
+ justify-content: flex-end;
+}
+
+.c4ai-download-btn {
+ background: #0fbbaa;
+ color: #070708;
+ border: none;
+}
+
+.c4ai-download-btn:hover {
+ background: #1fcbba;
+}
+
+.c4ai-copy-btn {
+ background: #3f3f44;
+ border-color: #3f3f44;
+}
+
+.c4ai-copy-btn:hover {
+ background: #4f4f54;
+ border-color: #5f5f64;
+}
+
+.c4ai-cloud-btn {
+ background: #3f3f44;
+ border-color: #3f3f44;
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.c4ai-cloud-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.c4ai-cloud-btn:hover:disabled {
+ background: #3f3f44;
+ transform: none;
+}
\ No newline at end of file
diff --git a/docs/md_v2/apps/crawl4ai-assistant/crawl4ai-assistant-v1.0.0.zip b/docs/md_v2/apps/crawl4ai-assistant/crawl4ai-assistant-v1.0.0.zip
new file mode 100644
index 00000000..d8fec586
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/crawl4ai-assistant-v1.0.0.zip differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/crawl4ai-assistant-v1.0.1.zip b/docs/md_v2/apps/crawl4ai-assistant/crawl4ai-assistant-v1.0.1.zip
new file mode 100644
index 00000000..bc782873
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/crawl4ai-assistant-v1.0.1.zip differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/icons/favicon.ico b/docs/md_v2/apps/crawl4ai-assistant/icons/favicon.ico
new file mode 100644
index 00000000..42e1dd25
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/icons/favicon.ico differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/icons/icon-128.png b/docs/md_v2/apps/crawl4ai-assistant/icons/icon-128.png
new file mode 100644
index 00000000..ed82a3cc
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/icons/icon-128.png differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/icons/icon-16.png b/docs/md_v2/apps/crawl4ai-assistant/icons/icon-16.png
new file mode 100644
index 00000000..ed82a3cc
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/icons/icon-16.png differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/icons/icon-48.png b/docs/md_v2/apps/crawl4ai-assistant/icons/icon-48.png
new file mode 100644
index 00000000..ed82a3cc
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/icons/icon-48.png differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/index.html b/docs/md_v2/apps/crawl4ai-assistant/index.html
new file mode 100644
index 00000000..d0721a6a
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/index.html
@@ -0,0 +1,328 @@
+
+
+
+
+
+ Crawl4AI Assistant - Chrome Extension for Visual Web Scraping
+
+
+
+
+
+
+
+
+
+
+
+
+ Your browser does not support the video tag.
+
+
+
+
+
+
+
+
+
+
Transform any website into structured data with just a few clicks! The Crawl4AI Assistant Chrome Extension lets you visually select elements on any webpage and automatically generates Python code for web scraping.
+
+
+
+
๐ฏ
+
Visual Selection
+
Click on any element to select it - no CSS selectors needed
+
+
+
๐
+
Schema Builder
+
Build extraction schemas by clicking on container and field elements
+
+
+
๐
+
Python Code
+
Get production-ready Crawl4AI code with LLM extraction
+
+
+
๐จ
+
Beautiful UI
+
Draggable toolbar with macOS-style interface
+
+
+
+
+
+
+
+
+ Quick Start
+
+
+
+
+
+
+
2
+
+
Load in Chrome
+
Navigate to chrome://extensions/ and enable Developer Mode
+
+
+
+
3
+
+
Load Unpacked
+
Click "Load unpacked" and select the extracted extension folder
+
+
+
+
+
+
+
+
+
+ How to Use
+
+
+
+
+
+
+
Click the extension icon and select "Schema Builder" to begin
+
+
+
+
+
Click on a container element (e.g., product card, article, listing)
+
+
+
+
+
+
+
+
Click on individual fields inside the container and name them
+
+
+
+
+
+
+
+
+
Click "Stop & Generate" to create your Python extraction code
+
+
+
+
+
+
+
+
+ Generated Code Example
+
+
+
+
import asyncio
+import json
+from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
+from crawl4ai.extraction_strategy import JsonCssExtractionStrategy
+
+async def extract_products ():
+
+ schema = {
+ "name" : "Product Catalog" ,
+ "baseSelector" : "div.product-card" ,
+ "fields" : [
+ {
+ "name" : "title" ,
+ "selector" : "h3.product-title" ,
+ "type" : "text"
+ },
+ {
+ "name" : "price" ,
+ "selector" : "span.price" ,
+ "type" : "text"
+ },
+ {
+ "name" : "description" ,
+ "selector" : "p.description" ,
+ "type" : "text"
+ },
+ {
+ "name" : "image" ,
+ "selector" : "img.product-image" ,
+ "type" : "attribute" ,
+ "attribute" : "src"
+ }
+ ]
+ }
+
+
+ extraction_strategy = JsonCssExtractionStrategy(schema, verbose=True )
+
+
+ config = CrawlerRunConfig(
+ extraction_strategy=extraction_strategy
+ )
+
+ async with AsyncWebCrawler() as crawler:
+ result = await crawler.arun(
+ url="https://example.com/products" ,
+ config=config
+ )
+
+
+ products = json.loads(result.extracted_content)
+ print (f"Extracted {len(products)} products" )
+
+
+ if products:
+ print (json.dumps(products[0], indent=2))
+
+ return products
+
+
+if __name__ == "__main__" :
+ asyncio.run(extract_products())
+
+
+
+
+
+
+ Coming Soon: Even More Power
+
+
+
+
We're continuously expanding C4AI Assistant with powerful new features to make web scraping even easier:
+
+
+
+
+
Execute your extraction directly in the cloud without setting up any local environment. Just click "Run on Cloud" and get your data instantly.
+
+ โ๏ธ Instant results โข Auto-scaling
+
+
+
+
+
+
Skip the code generation entirely! Get extracted data directly in the extension as a CrawlResult object, ready to download as JSON.
+
+ ๐ One-click extraction โข No Python needed โข Export to JSON/CSV
+
+
+
+
+
+
AI-powered field detection that automatically suggests the most likely data fields on any page, making schema building even faster.
+
+ ๐ค Auto-detect fields โข Smart naming โข Pattern recognition
+
+
+
+
+
+
Visual automation script builder for complex interactions - fill forms, click buttons, handle pagination, all without writing code.
+
+ ๐ฏ Visual automation โข Record & replay โข Export as C4A script
+
+
+
+
+
+
๐ Stay tuned for updates! Follow our GitHub for the latest releases.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/md_v2/apps/crawl4ai-assistant/manifest.json b/docs/md_v2/apps/crawl4ai-assistant/manifest.json
new file mode 100644
index 00000000..7dbc972f
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/manifest.json
@@ -0,0 +1,44 @@
+{
+ "manifest_version": 3,
+ "name": "Crawl4AI Assistant",
+ "version": "1.0.1",
+ "description": "Visual schema and script builder for Crawl4AI - Build extraction schemas by clicking on elements",
+ "permissions": [
+ "activeTab",
+ "storage",
+ "downloads"
+ ],
+ "host_permissions": [
+ ""
+ ],
+ "action": {
+ "default_popup": "popup/popup.html",
+ "default_icon": {
+ "16": "icons/icon-16.png",
+ "48": "icons/icon-48.png",
+ "128": "icons/icon-128.png"
+ }
+ },
+ "content_scripts": [
+ {
+ "matches": [""],
+ "js": ["content/content.js"],
+ "css": ["content/overlay.css"],
+ "run_at": "document_idle"
+ }
+ ],
+ "background": {
+ "service_worker": "background/service-worker.js"
+ },
+ "icons": {
+ "16": "icons/icon-16.png",
+ "48": "icons/icon-48.png",
+ "128": "icons/icon-128.png"
+ },
+ "web_accessible_resources": [
+ {
+ "resources": ["icons/*", "assets/*"],
+ "matches": [""]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docs/md_v2/apps/crawl4ai-assistant/popup/icons/favicon.ico b/docs/md_v2/apps/crawl4ai-assistant/popup/icons/favicon.ico
new file mode 100644
index 00000000..42e1dd25
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/popup/icons/favicon.ico differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/popup/icons/icon-128.png b/docs/md_v2/apps/crawl4ai-assistant/popup/icons/icon-128.png
new file mode 100644
index 00000000..ed82a3cc
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/popup/icons/icon-128.png differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/popup/icons/icon-16.png b/docs/md_v2/apps/crawl4ai-assistant/popup/icons/icon-16.png
new file mode 100644
index 00000000..ed82a3cc
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/popup/icons/icon-16.png differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/popup/icons/icon-48.png b/docs/md_v2/apps/crawl4ai-assistant/popup/icons/icon-48.png
new file mode 100644
index 00000000..ed82a3cc
Binary files /dev/null and b/docs/md_v2/apps/crawl4ai-assistant/popup/icons/icon-48.png differ
diff --git a/docs/md_v2/apps/crawl4ai-assistant/popup/popup.css b/docs/md_v2/apps/crawl4ai-assistant/popup/popup.css
new file mode 100644
index 00000000..b1a46650
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/popup/popup.css
@@ -0,0 +1,324 @@
+/* Font Face Definitions */
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Regular.woff2') format('woff2');
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Bold.woff2') format('woff2');
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Italic.woff2') format('woff2');
+ font-weight: 400;
+ font-style: italic;
+ font-display: swap;
+}
+
+:root {
+ --font-primary: 'Dank Mono', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ width: 380px;
+ font-family: var(--font-primary);
+ background: #0a0a0a;
+ color: #e0e0e0;
+}
+
+.popup-container {
+ padding: 20px;
+}
+
+header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 20px;
+ padding-bottom: 16px;
+ border-bottom: 1px solid #2a2a2a;
+}
+
+.logo {
+ width: 48px;
+ height: 48px;
+ flex-shrink: 0;
+}
+
+.header-content {
+ flex: 1;
+}
+
+header h1 {
+ font-size: 20px;
+ font-weight: 600;
+ color: #fff;
+ margin: 0 0 4px 0;
+}
+
+.header-stats {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.github-stars {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ color: #999;
+ text-decoration: none;
+ font-size: 13px;
+ transition: color 0.2s ease;
+}
+
+.github-stars:hover {
+ color: #4a9eff;
+}
+
+.github-icon {
+ flex-shrink: 0;
+}
+
+.mode-selector {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ margin-bottom: 20px;
+}
+
+.mode-button {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ background: #1a1a1a;
+ border: 2px solid #2a2a2a;
+ border-radius: 12px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ width: 100%;
+ text-align: left;
+}
+
+.mode-button:hover:not(:disabled) {
+ background: #252525;
+ border-color: #4a9eff;
+ transform: translateY(-2px);
+}
+
+.mode-button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.mode-button .icon {
+ font-size: 32px;
+ width: 48px;
+ height: 48px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #252525;
+ border-radius: 8px;
+}
+
+.mode-button.schema .icon {
+ background: #1e3a5f;
+}
+
+.mode-button.script .icon {
+ background: #3a1e5f;
+}
+
+.mode-info h3 {
+ font-size: 16px;
+ color: #fff;
+ margin-bottom: 4px;
+}
+
+.mode-info p {
+ font-size: 13px;
+ color: #999;
+ line-height: 1.4;
+}
+
+.active-session {
+ background: #1a1a1a;
+ border: 2px solid #4a9eff;
+ border-radius: 12px;
+ padding: 16px;
+ margin-bottom: 20px;
+}
+
+.active-session.hidden {
+ display: none;
+}
+
+.session-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.status-dot {
+ width: 8px;
+ height: 8px;
+ background: #4a9eff;
+ border-radius: 50%;
+ animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+.session-title {
+ font-size: 14px;
+ font-weight: 600;
+ color: #4a9eff;
+}
+
+.session-stats {
+ display: flex;
+ gap: 20px;
+ margin-bottom: 16px;
+ padding: 12px;
+ background: #0a0a0a;
+ border-radius: 8px;
+}
+
+.stat {
+ flex: 1;
+}
+
+.stat-label {
+ display: block;
+ font-size: 11px;
+ color: #666;
+ text-transform: uppercase;
+ margin-bottom: 4px;
+}
+
+.stat-value {
+ font-size: 14px;
+ color: #fff;
+ font-weight: 600;
+}
+
+.session-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.action-button {
+ flex: 1;
+ padding: 10px 16px;
+ border: none;
+ border-radius: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.action-button.primary {
+ background: #4a9eff;
+ color: #000;
+}
+
+.action-button.primary:hover:not(:disabled) {
+ background: #3a8eef;
+ transform: translateY(-1px);
+}
+
+.action-button.primary:disabled {
+ background: #2a4a7f;
+ color: #666;
+ cursor: not-allowed;
+}
+
+.action-button.secondary {
+ background: #2a2a2a;
+ color: #fff;
+}
+
+.action-button.secondary:hover {
+ background: #3a3a3a;
+}
+
+.instructions {
+ background: #1a1a1a;
+ border-radius: 12px;
+ padding: 16px;
+ margin-bottom: 16px;
+}
+
+.instructions h4 {
+ font-size: 14px;
+ margin-bottom: 12px;
+ color: #fff;
+}
+
+.instructions ol {
+ padding-left: 20px;
+}
+
+.instructions li {
+ font-size: 13px;
+ line-height: 1.6;
+ color: #ccc;
+ margin-bottom: 6px;
+}
+
+footer {
+ padding-top: 16px;
+ border-top: 1px solid #2a2a2a;
+}
+
+.social-links {
+ display: flex;
+ justify-content: center;
+ gap: 16px;
+}
+
+.social-link {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ color: #999;
+ text-decoration: none;
+ font-size: 12px;
+ transition: all 0.2s ease;
+ padding: 6px 12px;
+ border-radius: 6px;
+ background: #1a1a1a;
+}
+
+.social-link:hover {
+ color: #0fbbaa;
+ background: #2a2a2a;
+ transform: translateY(-1px);
+}
+
+.social-link svg {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+}
\ No newline at end of file
diff --git a/docs/md_v2/apps/crawl4ai-assistant/popup/popup.html b/docs/md_v2/apps/crawl4ai-assistant/popup/popup.html
new file mode 100644
index 00000000..3ce1c729
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/popup/popup.html
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/md_v2/apps/crawl4ai-assistant/popup/popup.js b/docs/md_v2/apps/crawl4ai-assistant/popup/popup.js
new file mode 100644
index 00000000..59ab82a0
--- /dev/null
+++ b/docs/md_v2/apps/crawl4ai-assistant/popup/popup.js
@@ -0,0 +1,112 @@
+// Popup script for Crawl4AI Assistant
+let activeMode = null;
+
+document.addEventListener('DOMContentLoaded', () => {
+ // Fetch GitHub stars
+ fetchGitHubStars();
+
+ // Check current state
+ chrome.storage.local.get(['captureMode', 'captureStats'], (data) => {
+ if (data.captureMode) {
+ activeMode = data.captureMode;
+ showActiveSession(data.captureStats || {});
+ }
+ });
+
+ // Mode buttons
+ document.getElementById('schema-mode').addEventListener('click', () => {
+ startSchemaCapture();
+ });
+
+ // Session actions
+ document.getElementById('generate-code').addEventListener('click', () => {
+ generateCode();
+ });
+
+ document.getElementById('stop-capture').addEventListener('click', () => {
+ stopCapture();
+ });
+});
+
+async function fetchGitHubStars() {
+ try {
+ const response = await fetch('https://api.github.com/repos/unclecode/crawl4ai');
+ const data = await response.json();
+ const stars = data.stargazers_count;
+
+ // Format the number (e.g., 1.2k)
+ let formattedStars;
+ if (stars >= 1000) {
+ formattedStars = (stars / 1000).toFixed(1) + 'k';
+ } else {
+ formattedStars = stars.toString();
+ }
+
+ document.getElementById('stars-count').textContent = `โญ ${formattedStars}`;
+ } catch (error) {
+ console.error('Failed to fetch GitHub stars:', error);
+ document.getElementById('stars-count').textContent = 'โญ 2k+';
+ }
+}
+
+function startSchemaCapture() {
+ chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
+ chrome.tabs.sendMessage(tabs[0].id, {
+ action: 'startSchemaCapture'
+ }, (response) => {
+ if (response && response.success) {
+ // Close the popup to let user interact with the page
+ window.close();
+ }
+ });
+ });
+}
+
+function showActiveSession(stats) {
+ document.querySelector('.mode-selector').style.display = 'none';
+ document.getElementById('active-session').classList.remove('hidden');
+
+ updateSessionStats(stats);
+}
+
+function updateSessionStats(stats) {
+ document.getElementById('container-status').textContent =
+ stats.container ? 'Selected โ' : 'Not selected';
+ document.getElementById('fields-count').textContent = stats.fields || 0;
+
+ // Enable generate button if we have container and fields
+ document.getElementById('generate-code').disabled =
+ !stats.container || stats.fields === 0;
+}
+
+function generateCode() {
+ chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
+ chrome.tabs.sendMessage(tabs[0].id, {
+ action: 'generateCode'
+ });
+ });
+}
+
+function stopCapture() {
+ chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
+ chrome.tabs.sendMessage(tabs[0].id, {
+ action: 'stopCapture'
+ }, () => {
+ // Reset UI
+ document.querySelector('.mode-selector').style.display = 'flex';
+ document.getElementById('active-session').classList.add('hidden');
+ activeMode = null;
+
+ // Clear storage
+ chrome.storage.local.remove(['captureMode', 'captureStats']);
+ });
+ });
+}
+
+// Listen for stats updates from content script
+chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+ if (message.action === 'updateStats') {
+ updateSessionStats(message.stats);
+ chrome.storage.local.set({ captureStats: message.stats });
+ }
+});
\ No newline at end of file
diff --git a/docs/md_v2/apps/index.md b/docs/md_v2/apps/index.md
index 57e86e4c..78d6558c 100644
--- a/docs/md_v2/apps/index.md
+++ b/docs/md_v2/apps/index.md
@@ -186,19 +186,20 @@ Our apps are designed to make Crawl4AI more accessible and powerful. Whether you
-
Coming Soon
-
๐๏ธ Schema Builder
+
Available
+
๐ Crawl4AI Assistant (Chrome Extension)
- Visually design JSON extraction schemas by pointing and clicking on webpage elements. No more guessing CSS selectors!
+ Visual schema builder Chrome extension - click on webpage elements to generate extraction schemas and Python code!
- Point-and-click selector generation
- Automatic schema inference
- Live extraction preview
- Export to multiple formats
+ Visual element selection
+ Container & field selection modes
+ Smart selector generation
+ Complete Python code generation
+ One-click installation
diff --git a/docs/md_v2/apps/llmtxt/index.html b/docs/md_v2/apps/llmtxt/index.html
index 1e4bd965..e5fee16e 100644
--- a/docs/md_v2/apps/llmtxt/index.html
+++ b/docs/md_v2/apps/llmtxt/index.html
@@ -7,10 +7,25 @@
-
-
+
diff --git a/docs/md_v2/apps/llmtxt/llmtxt.css b/docs/md_v2/apps/llmtxt/llmtxt.css
index 7026b98d..fe0eb028 100644
--- a/docs/md_v2/apps/llmtxt/llmtxt.css
+++ b/docs/md_v2/apps/llmtxt/llmtxt.css
@@ -1,5 +1,30 @@
/* Terminal Theme CSS for LLM Context Builder */
+/* Font Face Definitions */
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Regular.woff2') format('woff2');
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Bold.woff2') format('woff2');
+ font-weight: 700;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: 'Dank Mono';
+ src: url('../assets/DankMono-Italic.woff2') format('woff2');
+ font-weight: 400;
+ font-style: italic;
+ font-display: swap;
+}
+
:root {
--background-color: #070708;
--font-color: #e8e9ed;
@@ -13,6 +38,9 @@
--border-color: #3f3f44;
--hover-bg: #1a1a1c;
--success-color: #50ff50;
+ --bg-secondary: #1a1a1a;
+ --primary-green: #0fbbaa;
+ --font-primary: 'Dank Mono', dm, Monaco, Courier New, monospace;
}
* {
@@ -22,38 +50,88 @@
body {
margin: 0;
padding: 0;
- font-family: dm, Monaco, Courier New, monospace;
+ font-family: var(--font-primary);
font-size: 14px;
line-height: 1.5;
background-color: var(--background-color);
color: var(--font-color);
}
-.container {
+/* Terminal Container */
+.terminal-container {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Header - matching assistant layout */
+.header {
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-color);
+ padding: 1.5rem 0;
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ backdrop-filter: blur(10px);
+ background: rgba(26, 26, 26, 0.95);
+}
+
+.header-content {
max-width: 1200px;
margin: 0 auto;
- padding: 20px;
+ padding: 0 2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
}
-/* Header */
-.header {
- text-align: center;
- margin-bottom: 40px;
- border-bottom: 1px dashed var(--tertiary-color);
- padding-bottom: 20px;
-}
-
-.header h1 {
- font-size: 24px;
- color: var(--primary-color);
- margin: 0;
- text-transform: uppercase;
- letter-spacing: 2px;
+.logo-section {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
}
.logo {
- font-size: 28px;
- vertical-align: middle;
+ width: 48px;
+ height: 48px;
+}
+
+.logo-section h1 {
+ font-size: 1.75rem;
+ font-weight: 700;
+ color: var(--font-color);
+ margin: 0;
+}
+
+.tagline {
+ font-size: 0.875rem;
+ color: var(--tertiary-color);
+ margin-top: 0.25rem;
+}
+
+.nav-links {
+ display: flex;
+ gap: 2rem;
+}
+
+.nav-link {
+ color: var(--tertiary-color);
+ text-decoration: none;
+ font-size: 0.875rem;
+ transition: color 0.2s ease;
+}
+
+.nav-link:hover {
+ color: var(--primary-green);
+}
+
+/* Content */
+.content {
+ flex: 1;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+ width: 100%;
}
/* Intro Section */
@@ -446,6 +524,15 @@ body {
/* Responsive Design */
@media (max-width: 768px) {
+ .header-content {
+ flex-direction: column;
+ gap: 1.5rem;
+ }
+
+ .nav-links {
+ gap: 1rem;
+ }
+
.preset-options {
flex-direction: column;
}
@@ -454,7 +541,7 @@ body {
grid-template-columns: 1fr;
}
- .container {
- padding: 10px;
+ .content {
+ padding: 1rem;
}
}
\ No newline at end of file
diff --git a/docs/md_v2/assets/styles.css b/docs/md_v2/assets/styles.css
index fcd56b7a..5d693197 100644
--- a/docs/md_v2/assets/styles.css
+++ b/docs/md_v2/assets/styles.css
@@ -28,7 +28,8 @@
--secondary-color: #d5cec0;
--tertiary-color: #a3abba;
--primary-dimmed-color: #09b5a5; /* Updated to the brand color */
- --primary-color: #0fbbaa; /* Updated to the brand color */
+ /* --primary-color: #0fbbaa; */
+ --primary-color: #50ffff; /* Updated to the brand color */
--accent-color: rgb(243, 128, 245);
--error-color: #ff3c74;
--progress-bar-background: #3f3f44;
diff --git a/docs/md_v2/core/llmtxt.md b/docs/md_v2/core/llmtxt.md
index f5e02d2b..b2c67f3a 100644
--- a/docs/md_v2/core/llmtxt.md
+++ b/docs/md_v2/core/llmtxt.md
@@ -1,4 +1,4 @@
-