NeerajCodz commited on
Commit
f881423
·
1 Parent(s): 039e1ea

feat: redesign Dashboard and Settings with proper layout

Browse files

Dashboard:
- Add structured input bar with URL, instruction, task type
- Add left sidebar with accordions (Agents, MCPs, Skills, APIs, Vision)
- Add center area with stats header, main visualization, logs terminal
- Add right sidebar with memory stats, extracted data, recent actions
- Add popup modals for model/agent/plugin selection
- Add task complexity selector (Low/Medium/High)

Settings:
- Add left sidebar navigation with sections
- Add Budget & Limits section (disabled by default)
- Add Appearance and Notifications settings
- Add proper section-based layout

App:
- Add Docs tab in navigation
- Remove footer, use full height layout
- Compact navbar design

Docs:
- Add USER_GUIDE.md documentation
- Add DocsPage component with react-markdown
- Include agents, plugins, memory, API reference docs

Types:
- Add autoSave and debugMode to SystemSettings
- Add AgentOption and ModelOption interfaces

docs/USER_GUIDE.md ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ScrapeRL Documentation
2
+
3
+ Welcome to ScrapeRL - an advanced Reinforcement Learning-powered web scraping environment. This documentation covers all aspects of using and configuring ScrapeRL.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Getting Started](#getting-started)
10
+ 2. [Dashboard Overview](#dashboard-overview)
11
+ 3. [Agents](#agents)
12
+ 4. [Plugins](#plugins)
13
+ 5. [Memory System](#memory-system)
14
+ 6. [Models & Providers](#models--providers)
15
+ 7. [Settings](#settings)
16
+ 8. [API Reference](#api-reference)
17
+ 9. [Troubleshooting](#troubleshooting)
18
+
19
+ ---
20
+
21
+ ## Getting Started
22
+
23
+ ### What is ScrapeRL?
24
+
25
+ ScrapeRL is an intelligent web scraping system that uses Reinforcement Learning (RL) to learn and adapt scraping strategies. Unlike traditional scrapers, ScrapeRL can:
26
+
27
+ - **Learn from experience** - Improve scraping strategies over time
28
+ - **Adapt to changes** - Handle website structure changes automatically
29
+ - **Multi-agent coordination** - Use specialized agents for different tasks
30
+ - **Memory-enhanced** - Remember patterns and optimize future runs
31
+
32
+ ### Quick Start
33
+
34
+ 1. **Enter a Target URL** - Provide the webpage you want to scrape
35
+ 2. **Write an Instruction** - Describe what data you want to extract
36
+ 3. **Configure Options** - Select model, agents, and plugins
37
+ 4. **Start Episode** - Click Start and watch the magic happen!
38
+
39
+ ### Example Task
40
+
41
+ ```
42
+ URL: https://example.com/products
43
+ Instruction: Extract all product names, prices, and descriptions
44
+ Task Type: Medium
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Dashboard Overview
50
+
51
+ The dashboard is your command center for monitoring and controlling scraping operations.
52
+
53
+ ### Layout Structure
54
+
55
+ | Section | Description |
56
+ |---------|-------------|
57
+ | **Input Bar** | Enter URL, instruction, and configure task |
58
+ | **Left Sidebar** | View active agents, MCPs, skills, and tools |
59
+ | **Center Area** | Main visualization and current observation |
60
+ | **Right Sidebar** | Memory stats, extracted data, recent actions |
61
+ | **Bottom Logs** | Real-time terminal-style log output |
62
+
63
+ ### Stats Header
64
+
65
+ The header shows key metrics with expandable details:
66
+
67
+ - **Episodes** - Total scraping sessions completed
68
+ - **Steps** - Actions taken in current/total sessions
69
+ - **Reward** - Performance score (higher is better)
70
+ - **Time** - Current time and session duration
71
+
72
+ Click the **⋯** icon on any stat to see detailed statistics (min, max, average).
73
+
74
+ ### Task Configuration
75
+
76
+ #### Task Types
77
+
78
+ | Type | Description | Use Case |
79
+ |------|-------------|----------|
80
+ | 🟢 **Low** | Simple single-page scraping | Product page, article text |
81
+ | 🟡 **Medium** | Multi-page with navigation | Search results, listings |
82
+ | 🔴 **High** | Complex interactive tasks | Login-required, forms |
83
+
84
+ ---
85
+
86
+ ## Agents
87
+
88
+ ScrapeRL uses a multi-agent architecture where specialized agents handle different aspects of scraping.
89
+
90
+ ### Available Agents
91
+
92
+ | Agent | Role | Description |
93
+ |-------|------|-------------|
94
+ | **Coordinator** | 🎯 Orchestrator | Manages all other agents, decides strategy |
95
+ | **Scraper** | 📄 Extractor | Extracts data from page content |
96
+ | **Navigator** | 🧭 Navigation | Handles page navigation, clicking, scrolling |
97
+ | **Analyzer** | 🔍 Analysis | Analyzes extracted data for patterns |
98
+ | **Validator** | ✅ Validation | Validates data quality and completeness |
99
+
100
+ ### Agent Selection
101
+
102
+ 1. Click the **Agents** button in the input bar
103
+ 2. Select agents you want to enable
104
+ 3. Active agents appear in the left sidebar accordion
105
+ 4. Monitor agent activity in real-time
106
+
107
+ ### Agent Status Indicators
108
+
109
+ - 🟢 **Active** - Currently processing
110
+ - 🔵 **Ready** - Waiting for task
111
+ - 🟡 **Idle** - Not currently in use
112
+ - 🔴 **Error** - Encountered an issue
113
+
114
+ ---
115
+
116
+ ## Plugins
117
+
118
+ Extend ScrapeRL's capabilities with plugins organized by category.
119
+
120
+ ### Plugin Categories
121
+
122
+ #### 🔧 MCPs (Model Context Protocols)
123
+
124
+ Tools that provide browser automation and page interaction:
125
+
126
+ | Plugin | Description |
127
+ |--------|-------------|
128
+ | Browser Use | AI-powered browser automation |
129
+ | Puppeteer MCP | Headless Chrome control |
130
+ | Playwright MCP | Cross-browser automation |
131
+
132
+ #### ⚡ Skills
133
+
134
+ Specialized capabilities for specific tasks:
135
+
136
+ | Plugin | Description |
137
+ |--------|-------------|
138
+ | Web Scraping | Core extraction algorithms |
139
+ | Data Extraction | Structured data parsing |
140
+ | Form Filling | Automated form completion |
141
+
142
+ #### 🔌 APIs
143
+
144
+ External service integrations:
145
+
146
+ | Plugin | Description |
147
+ |--------|-------------|
148
+ | Firecrawl | High-performance web crawler |
149
+ | Jina Reader | Content reader API |
150
+ | Serper | Search engine results API |
151
+
152
+ #### 👁️ Vision
153
+
154
+ Visual understanding capabilities:
155
+
156
+ | Plugin | Description |
157
+ |--------|-------------|
158
+ | GPT-4 Vision | OpenAI visual analysis |
159
+ | Gemini Vision | Google visual AI |
160
+ | Claude Vision | Anthropic visual models |
161
+
162
+ ### Managing Plugins
163
+
164
+ 1. Go to **Plugins** tab
165
+ 2. Browse by category
166
+ 3. Click **Install** to add a plugin
167
+ 4. Enable plugins in Dashboard via the Plugins popup
168
+
169
+ ---
170
+
171
+ ## Memory System
172
+
173
+ ScrapeRL uses a hierarchical memory system for context retention.
174
+
175
+ ### Memory Layers
176
+
177
+ | Layer | Purpose | Retention |
178
+ |-------|---------|-----------|
179
+ | **Working** | Current task context | Session |
180
+ | **Episodic** | Experience records | Persistent |
181
+ | **Semantic** | Learned patterns | Persistent |
182
+ | **Procedural** | Action sequences | Persistent |
183
+
184
+ ### Memory Features
185
+
186
+ - **Auto-consolidation** - Promotes important data between layers
187
+ - **Similarity search** - Find related memories quickly
188
+ - **Pattern recognition** - Learn from past experiences
189
+
190
+ ---
191
+
192
+ ## Models & Providers
193
+
194
+ ### Supported Providers
195
+
196
+ | Provider | Models | Best For |
197
+ |----------|--------|----------|
198
+ | **Groq** | GPT-OSS 120B | Fast inference, default |
199
+ | **Google** | Gemini 2.5 Flash | Balanced performance |
200
+ | **OpenAI** | GPT-4 Turbo | High accuracy |
201
+ | **Anthropic** | Claude 3 Opus | Complex reasoning |
202
+
203
+ ### Model Selection
204
+
205
+ 1. Click **Model** button in input bar
206
+ 2. Select from available models
207
+ 3. Models require appropriate API keys
208
+
209
+ ### API Keys
210
+
211
+ Configure API keys in **Settings > API Keys**:
212
+
213
+ 1. Select provider
214
+ 2. Enter your API key
215
+ 3. Click Save
216
+ 4. Key status shows as "Active" when configured
217
+
218
+ ---
219
+
220
+ ## Settings
221
+
222
+ ### General Settings
223
+
224
+ | Setting | Description |
225
+ |---------|-------------|
226
+ | WebSocket Updates | Enable real-time updates |
227
+ | Memory Persistence | Save memory across sessions |
228
+ | Auto-save Episodes | Automatically save completed episodes |
229
+ | Debug Mode | Enable verbose logging |
230
+
231
+ ### Budget & Limits
232
+
233
+ Control API usage costs:
234
+
235
+ - **Daily Limit** - Maximum spend per day
236
+ - **Monthly Limit** - Maximum spend per month
237
+ - **Max Tokens** - Token limit per request
238
+ - **Alert Threshold** - Warning at 80% usage
239
+
240
+ > 💡 Budget limits are disabled by default. Enable in Settings to control spending.
241
+
242
+ ### Appearance
243
+
244
+ - **Theme** - Dark (default), Light, Auto
245
+ - **Compact Mode** - Reduce UI spacing
246
+ - **Animations** - Enable/disable transitions
247
+
248
+ ---
249
+
250
+ ## API Reference
251
+
252
+ ### Health Check
253
+
254
+ ```bash
255
+ GET /api/health
256
+ ```
257
+
258
+ Response:
259
+ ```json
260
+ {
261
+ "status": "healthy",
262
+ "version": "0.1.0",
263
+ "timestamp": "2026-03-28T00:00:00Z"
264
+ }
265
+ ```
266
+
267
+ ### Episode Management
268
+
269
+ ```bash
270
+ # Start new episode
271
+ POST /api/episode/reset
272
+ {
273
+ "task_id": "scrape-products",
274
+ "config": { ... }
275
+ }
276
+
277
+ # Take action
278
+ POST /api/episode/step
279
+ {
280
+ "action": "navigate",
281
+ "params": { "url": "..." }
282
+ }
283
+
284
+ # Get current state
285
+ GET /api/episode/state
286
+ ```
287
+
288
+ ### Memory API
289
+
290
+ ```bash
291
+ # Store entry
292
+ POST /api/memory/store
293
+ {
294
+ "content": "...",
295
+ "memory_type": "working",
296
+ "metadata": { ... }
297
+ }
298
+
299
+ # Query memories
300
+ POST /api/memory/query
301
+ {
302
+ "query": "product prices",
303
+ "memory_type": "semantic",
304
+ "limit": 10
305
+ }
306
+ ```
307
+
308
+ ### Plugins API
309
+
310
+ ```bash
311
+ # List plugins
312
+ GET /api/plugins/
313
+
314
+ # Install plugin
315
+ POST /api/plugins/install
316
+ { "plugin_id": "firecrawl" }
317
+
318
+ # Uninstall plugin
319
+ POST /api/plugins/uninstall
320
+ { "plugin_id": "firecrawl" }
321
+ ```
322
+
323
+ ---
324
+
325
+ ## Troubleshooting
326
+
327
+ ### Common Issues
328
+
329
+ #### "API Key Required" Error
330
+
331
+ **Solution:** Configure at least one API key in Settings > API Keys
332
+
333
+ #### Episode Not Starting
334
+
335
+ **Checklist:**
336
+ - [ ] Valid URL entered
337
+ - [ ] At least one agent selected
338
+ - [ ] API key configured
339
+ - [ ] System status shows "Online"
340
+
341
+ #### Slow Performance
342
+
343
+ **Tips:**
344
+ - Use Groq for faster inference
345
+ - Reduce enabled plugins
346
+ - Lower task complexity if possible
347
+
348
+ #### Memory Full
349
+
350
+ **Solution:** Clear memory layers in Settings > Advanced > Clear Cache
351
+
352
+ ### Getting Help
353
+
354
+ - Check the logs panel for error details
355
+ - View episode history for past issues
356
+ - Report bugs on GitHub
357
+
358
+ ---
359
+
360
+ ## Keyboard Shortcuts
361
+
362
+ | Shortcut | Action |
363
+ |----------|--------|
364
+ | `Ctrl + Enter` | Start/Stop episode |
365
+ | `Ctrl + L` | Clear logs |
366
+ | `Ctrl + ,` | Open settings |
367
+ | `Escape` | Close popups |
368
+
369
+ ---
370
+
371
+ ## Version History
372
+
373
+ ### v0.1.0 (Current)
374
+
375
+ - Initial release
376
+ - Multi-agent architecture
377
+ - Plugin system
378
+ - Memory layers
379
+ - Dashboard with real-time monitoring
380
+
381
+ ---
382
+
383
+ *Documentation last updated: March 2026*
384
+
385
+ *Built with ❤️ by NeerajCodz*
frontend/package-lock.json CHANGED
@@ -12,8 +12,10 @@
12
  "lucide-react": "^0.460.0",
13
  "react": "^18.3.1",
14
  "react-dom": "^18.3.1",
 
15
  "react-router-dom": "^6.28.0",
16
- "recharts": "^2.13.3"
 
17
  },
18
  "devDependencies": {
19
  "@eslint/js": "^9.13.0",
@@ -2249,6 +2251,15 @@
2249
  "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
2250
  "license": "MIT"
2251
  },
 
 
 
 
 
 
 
 
 
2252
  "node_modules/@types/deep-eql": {
2253
  "version": "4.0.2",
2254
  "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
@@ -2260,9 +2271,26 @@
2260
  "version": "1.0.8",
2261
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
2262
  "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
2263
- "dev": true,
2264
  "license": "MIT"
2265
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2266
  "node_modules/@types/json-schema": {
2267
  "version": "7.0.15",
2268
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -2270,18 +2298,31 @@
2270
  "dev": true,
2271
  "license": "MIT"
2272
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2273
  "node_modules/@types/prop-types": {
2274
  "version": "15.7.15",
2275
  "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
2276
  "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
2277
- "dev": true,
2278
  "license": "MIT"
2279
  },
2280
  "node_modules/@types/react": {
2281
  "version": "18.3.28",
2282
  "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
2283
  "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
2284
- "dev": true,
2285
  "license": "MIT",
2286
  "peer": true,
2287
  "dependencies": {
@@ -2300,6 +2341,12 @@
2300
  "@types/react": "^18.0.0"
2301
  }
2302
  },
 
 
 
 
 
 
2303
  "node_modules/@typescript-eslint/eslint-plugin": {
2304
  "version": "8.57.2",
2305
  "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
@@ -2596,6 +2643,12 @@
2596
  "url": "https://opencollective.com/eslint"
2597
  }
2598
  },
 
 
 
 
 
 
2599
  "node_modules/@vitejs/plugin-react": {
2600
  "version": "4.7.0",
2601
  "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
@@ -2862,6 +2915,16 @@
2862
  "postcss": "^8.1.0"
2863
  }
2864
  },
 
 
 
 
 
 
 
 
 
 
2865
  "node_modules/balanced-match": {
2866
  "version": "1.0.2",
2867
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3005,6 +3068,16 @@
3005
  ],
3006
  "license": "CC-BY-4.0"
3007
  },
 
 
 
 
 
 
 
 
 
 
3008
  "node_modules/chai": {
3009
  "version": "6.2.2",
3010
  "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
@@ -3032,6 +3105,46 @@
3032
  "url": "https://github.com/chalk/chalk?sponsor=1"
3033
  }
3034
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3035
  "node_modules/chokidar": {
3036
  "version": "3.6.0",
3037
  "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -3099,6 +3212,16 @@
3099
  "dev": true,
3100
  "license": "MIT"
3101
  },
 
 
 
 
 
 
 
 
 
 
3102
  "node_modules/commander": {
3103
  "version": "4.1.1",
3104
  "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@@ -3317,7 +3440,6 @@
3317
  "version": "4.4.3",
3318
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
3319
  "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
3320
- "dev": true,
3321
  "license": "MIT",
3322
  "dependencies": {
3323
  "ms": "^2.1.3"
@@ -3344,6 +3466,19 @@
3344
  "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
3345
  "license": "MIT"
3346
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
3347
  "node_modules/deep-is": {
3348
  "version": "0.1.4",
3349
  "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -3355,7 +3490,6 @@
3355
  "version": "2.0.3",
3356
  "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
3357
  "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
3358
- "dev": true,
3359
  "license": "MIT",
3360
  "engines": {
3361
  "node": ">=6"
@@ -3371,6 +3505,19 @@
3371
  "node": ">=8"
3372
  }
3373
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
3374
  "node_modules/didyoumean": {
3375
  "version": "1.2.2",
3376
  "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -3659,6 +3806,16 @@
3659
  "node": ">=4.0"
3660
  }
3661
  },
 
 
 
 
 
 
 
 
 
 
3662
  "node_modules/estree-walker": {
3663
  "version": "3.0.3",
3664
  "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
@@ -3695,6 +3852,12 @@
3695
  "node": ">=12.0.0"
3696
  }
3697
  },
 
 
 
 
 
 
3698
  "node_modules/fast-deep-equal": {
3699
  "version": "3.1.3",
3700
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3927,6 +4090,46 @@
3927
  "node": ">= 0.4"
3928
  }
3929
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3930
  "node_modules/html-encoding-sniffer": {
3931
  "version": "6.0.0",
3932
  "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
@@ -3940,6 +4143,16 @@
3940
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
3941
  }
3942
  },
 
 
 
 
 
 
 
 
 
 
3943
  "node_modules/ignore": {
3944
  "version": "5.3.2",
3945
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3987,6 +4200,12 @@
3987
  "node": ">=8"
3988
  }
3989
  },
 
 
 
 
 
 
3990
  "node_modules/internmap": {
3991
  "version": "2.0.3",
3992
  "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
@@ -3996,6 +4215,30 @@
3996
  "node": ">=12"
3997
  }
3998
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3999
  "node_modules/is-binary-path": {
4000
  "version": "2.1.0",
4001
  "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -4025,6 +4268,16 @@
4025
  "url": "https://github.com/sponsors/ljharb"
4026
  }
4027
  },
 
 
 
 
 
 
 
 
 
 
4028
  "node_modules/is-extglob": {
4029
  "version": "2.1.1",
4030
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -4048,6 +4301,16 @@
4048
  "node": ">=0.10.0"
4049
  }
4050
  },
 
 
 
 
 
 
 
 
 
 
4051
  "node_modules/is-number": {
4052
  "version": "7.0.0",
4053
  "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -4058,6 +4321,18 @@
4058
  "node": ">=0.12.0"
4059
  }
4060
  },
 
 
 
 
 
 
 
 
 
 
 
 
4061
  "node_modules/is-potential-custom-element-name": {
4062
  "version": "1.0.1",
4063
  "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -4535,6 +4810,16 @@
4535
  "dev": true,
4536
  "license": "MIT"
4537
  },
 
 
 
 
 
 
 
 
 
 
4538
  "node_modules/loose-envify": {
4539
  "version": "1.4.0",
4540
  "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -4586,102 +4871,956 @@
4586
  "@jridgewell/sourcemap-codec": "^1.5.5"
4587
  }
4588
  },
4589
- "node_modules/mdn-data": {
4590
- "version": "2.27.1",
4591
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
4592
- "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
4593
- "dev": true,
4594
- "license": "CC0-1.0"
4595
- },
4596
- "node_modules/merge2": {
4597
- "version": "1.4.1",
4598
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
4599
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
4600
- "dev": true,
4601
  "license": "MIT",
4602
- "engines": {
4603
- "node": ">= 8"
 
4604
  }
4605
  },
4606
- "node_modules/micromatch": {
4607
- "version": "4.0.8",
4608
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
4609
- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
4610
- "dev": true,
4611
  "license": "MIT",
4612
  "dependencies": {
4613
- "braces": "^3.0.3",
4614
- "picomatch": "^2.3.1"
 
 
4615
  },
4616
- "engines": {
4617
- "node": ">=8.6"
 
4618
  }
4619
  },
4620
- "node_modules/min-indent": {
4621
- "version": "1.0.1",
4622
- "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
4623
- "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
4624
- "dev": true,
4625
  "license": "MIT",
4626
  "engines": {
4627
- "node": ">=4"
 
 
 
4628
  }
4629
  },
4630
- "node_modules/minimatch": {
4631
- "version": "3.1.5",
4632
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
4633
- "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
4634
- "dev": true,
4635
- "license": "ISC",
4636
- "dependencies": {
4637
- "brace-expansion": "^1.1.7"
 
 
 
 
 
 
 
 
 
 
4638
  },
4639
- "engines": {
4640
- "node": "*"
 
4641
  }
4642
  },
4643
- "node_modules/ms": {
4644
- "version": "2.1.3",
4645
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
4646
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
4647
- "dev": true,
4648
- "license": "MIT"
4649
- },
4650
- "node_modules/mz": {
4651
- "version": "2.7.0",
4652
- "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
4653
- "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
4654
- "dev": true,
4655
  "license": "MIT",
4656
  "dependencies": {
4657
- "any-promise": "^1.0.0",
4658
- "object-assign": "^4.0.1",
4659
- "thenify-all": "^1.0.0"
 
 
 
 
 
 
 
 
4660
  }
4661
  },
4662
- "node_modules/nanoid": {
4663
- "version": "3.3.11",
4664
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
4665
- "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
4666
- "dev": true,
4667
- "funding": [
4668
- {
4669
- "type": "github",
4670
- "url": "https://github.com/sponsors/ai"
4671
- }
4672
- ],
4673
  "license": "MIT",
4674
- "bin": {
4675
- "nanoid": "bin/nanoid.cjs"
 
 
 
 
4676
  },
4677
- "engines": {
4678
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
 
4679
  }
4680
  },
4681
- "node_modules/natural-compare": {
4682
- "version": "1.4.0",
4683
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
4684
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4685
  "dev": true,
4686
  "license": "MIT"
4687
  },
@@ -4795,6 +5934,31 @@
4795
  "node": ">=6"
4796
  }
4797
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4798
  "node_modules/parse5": {
4799
  "version": "8.0.0",
4800
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
@@ -5101,6 +6265,16 @@
5101
  "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
5102
  "license": "MIT"
5103
  },
 
 
 
 
 
 
 
 
 
 
5104
  "node_modules/punycode": {
5105
  "version": "2.3.1",
5106
  "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5166,6 +6340,33 @@
5166
  "dev": true,
5167
  "license": "MIT"
5168
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5169
  "node_modules/react-refresh": {
5170
  "version": "0.17.0",
5171
  "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -5314,6 +6515,72 @@
5314
  "node": ">=8"
5315
  }
5316
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5317
  "node_modules/require-from-string": {
5318
  "version": "2.0.2",
5319
  "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -5548,6 +6815,16 @@
5548
  "node": ">=0.10.0"
5549
  }
5550
  },
 
 
 
 
 
 
 
 
 
 
5551
  "node_modules/stackback": {
5552
  "version": "0.0.2",
5553
  "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -5562,6 +6839,20 @@
5562
  "dev": true,
5563
  "license": "MIT"
5564
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5565
  "node_modules/strip-indent": {
5566
  "version": "3.0.0",
5567
  "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -5588,6 +6879,24 @@
5588
  "url": "https://github.com/sponsors/sindresorhus"
5589
  }
5590
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5591
  "node_modules/sucrase": {
5592
  "version": "3.35.1",
5593
  "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
@@ -5846,6 +7155,26 @@
5846
  "node": ">=20"
5847
  }
5848
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5849
  "node_modules/ts-api-utils": {
5850
  "version": "2.5.0",
5851
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
@@ -5936,6 +7265,93 @@
5936
  "node": ">=20.18.1"
5937
  }
5938
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5939
  "node_modules/update-browserslist-db": {
5940
  "version": "1.2.3",
5941
  "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -5984,6 +7400,34 @@
5984
  "dev": true,
5985
  "license": "MIT"
5986
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5987
  "node_modules/victory-vendor": {
5988
  "version": "36.9.2",
5989
  "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
@@ -6830,6 +8274,16 @@
6830
  "funding": {
6831
  "url": "https://github.com/sponsors/sindresorhus"
6832
  }
 
 
 
 
 
 
 
 
 
 
6833
  }
6834
  }
6835
  }
 
12
  "lucide-react": "^0.460.0",
13
  "react": "^18.3.1",
14
  "react-dom": "^18.3.1",
15
+ "react-markdown": "^10.1.0",
16
  "react-router-dom": "^6.28.0",
17
+ "recharts": "^2.13.3",
18
+ "remark-gfm": "^4.0.1"
19
  },
20
  "devDependencies": {
21
  "@eslint/js": "^9.13.0",
 
2251
  "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
2252
  "license": "MIT"
2253
  },
2254
+ "node_modules/@types/debug": {
2255
+ "version": "4.1.13",
2256
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
2257
+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
2258
+ "license": "MIT",
2259
+ "dependencies": {
2260
+ "@types/ms": "*"
2261
+ }
2262
+ },
2263
  "node_modules/@types/deep-eql": {
2264
  "version": "4.0.2",
2265
  "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
 
2271
  "version": "1.0.8",
2272
  "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
2273
  "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
 
2274
  "license": "MIT"
2275
  },
2276
+ "node_modules/@types/estree-jsx": {
2277
+ "version": "1.0.5",
2278
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
2279
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
2280
+ "license": "MIT",
2281
+ "dependencies": {
2282
+ "@types/estree": "*"
2283
+ }
2284
+ },
2285
+ "node_modules/@types/hast": {
2286
+ "version": "3.0.4",
2287
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
2288
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
2289
+ "license": "MIT",
2290
+ "dependencies": {
2291
+ "@types/unist": "*"
2292
+ }
2293
+ },
2294
  "node_modules/@types/json-schema": {
2295
  "version": "7.0.15",
2296
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
 
2298
  "dev": true,
2299
  "license": "MIT"
2300
  },
2301
+ "node_modules/@types/mdast": {
2302
+ "version": "4.0.4",
2303
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
2304
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
2305
+ "license": "MIT",
2306
+ "dependencies": {
2307
+ "@types/unist": "*"
2308
+ }
2309
+ },
2310
+ "node_modules/@types/ms": {
2311
+ "version": "2.1.0",
2312
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
2313
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
2314
+ "license": "MIT"
2315
+ },
2316
  "node_modules/@types/prop-types": {
2317
  "version": "15.7.15",
2318
  "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
2319
  "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
 
2320
  "license": "MIT"
2321
  },
2322
  "node_modules/@types/react": {
2323
  "version": "18.3.28",
2324
  "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
2325
  "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
 
2326
  "license": "MIT",
2327
  "peer": true,
2328
  "dependencies": {
 
2341
  "@types/react": "^18.0.0"
2342
  }
2343
  },
2344
+ "node_modules/@types/unist": {
2345
+ "version": "3.0.3",
2346
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
2347
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
2348
+ "license": "MIT"
2349
+ },
2350
  "node_modules/@typescript-eslint/eslint-plugin": {
2351
  "version": "8.57.2",
2352
  "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz",
 
2643
  "url": "https://opencollective.com/eslint"
2644
  }
2645
  },
2646
+ "node_modules/@ungap/structured-clone": {
2647
+ "version": "1.3.0",
2648
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
2649
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
2650
+ "license": "ISC"
2651
+ },
2652
  "node_modules/@vitejs/plugin-react": {
2653
  "version": "4.7.0",
2654
  "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
 
2915
  "postcss": "^8.1.0"
2916
  }
2917
  },
2918
+ "node_modules/bail": {
2919
+ "version": "2.0.2",
2920
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
2921
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
2922
+ "license": "MIT",
2923
+ "funding": {
2924
+ "type": "github",
2925
+ "url": "https://github.com/sponsors/wooorm"
2926
+ }
2927
+ },
2928
  "node_modules/balanced-match": {
2929
  "version": "1.0.2",
2930
  "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
 
3068
  ],
3069
  "license": "CC-BY-4.0"
3070
  },
3071
+ "node_modules/ccount": {
3072
+ "version": "2.0.1",
3073
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
3074
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
3075
+ "license": "MIT",
3076
+ "funding": {
3077
+ "type": "github",
3078
+ "url": "https://github.com/sponsors/wooorm"
3079
+ }
3080
+ },
3081
  "node_modules/chai": {
3082
  "version": "6.2.2",
3083
  "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
 
3105
  "url": "https://github.com/chalk/chalk?sponsor=1"
3106
  }
3107
  },
3108
+ "node_modules/character-entities": {
3109
+ "version": "2.0.2",
3110
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
3111
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
3112
+ "license": "MIT",
3113
+ "funding": {
3114
+ "type": "github",
3115
+ "url": "https://github.com/sponsors/wooorm"
3116
+ }
3117
+ },
3118
+ "node_modules/character-entities-html4": {
3119
+ "version": "2.1.0",
3120
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
3121
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
3122
+ "license": "MIT",
3123
+ "funding": {
3124
+ "type": "github",
3125
+ "url": "https://github.com/sponsors/wooorm"
3126
+ }
3127
+ },
3128
+ "node_modules/character-entities-legacy": {
3129
+ "version": "3.0.0",
3130
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
3131
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
3132
+ "license": "MIT",
3133
+ "funding": {
3134
+ "type": "github",
3135
+ "url": "https://github.com/sponsors/wooorm"
3136
+ }
3137
+ },
3138
+ "node_modules/character-reference-invalid": {
3139
+ "version": "2.0.1",
3140
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
3141
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
3142
+ "license": "MIT",
3143
+ "funding": {
3144
+ "type": "github",
3145
+ "url": "https://github.com/sponsors/wooorm"
3146
+ }
3147
+ },
3148
  "node_modules/chokidar": {
3149
  "version": "3.6.0",
3150
  "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
 
3212
  "dev": true,
3213
  "license": "MIT"
3214
  },
3215
+ "node_modules/comma-separated-tokens": {
3216
+ "version": "2.0.3",
3217
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
3218
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
3219
+ "license": "MIT",
3220
+ "funding": {
3221
+ "type": "github",
3222
+ "url": "https://github.com/sponsors/wooorm"
3223
+ }
3224
+ },
3225
  "node_modules/commander": {
3226
  "version": "4.1.1",
3227
  "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
 
3440
  "version": "4.4.3",
3441
  "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
3442
  "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
 
3443
  "license": "MIT",
3444
  "dependencies": {
3445
  "ms": "^2.1.3"
 
3466
  "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
3467
  "license": "MIT"
3468
  },
3469
+ "node_modules/decode-named-character-reference": {
3470
+ "version": "1.3.0",
3471
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
3472
+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
3473
+ "license": "MIT",
3474
+ "dependencies": {
3475
+ "character-entities": "^2.0.0"
3476
+ },
3477
+ "funding": {
3478
+ "type": "github",
3479
+ "url": "https://github.com/sponsors/wooorm"
3480
+ }
3481
+ },
3482
  "node_modules/deep-is": {
3483
  "version": "0.1.4",
3484
  "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
 
3490
  "version": "2.0.3",
3491
  "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
3492
  "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
 
3493
  "license": "MIT",
3494
  "engines": {
3495
  "node": ">=6"
 
3505
  "node": ">=8"
3506
  }
3507
  },
3508
+ "node_modules/devlop": {
3509
+ "version": "1.1.0",
3510
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
3511
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
3512
+ "license": "MIT",
3513
+ "dependencies": {
3514
+ "dequal": "^2.0.0"
3515
+ },
3516
+ "funding": {
3517
+ "type": "github",
3518
+ "url": "https://github.com/sponsors/wooorm"
3519
+ }
3520
+ },
3521
  "node_modules/didyoumean": {
3522
  "version": "1.2.2",
3523
  "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
 
3806
  "node": ">=4.0"
3807
  }
3808
  },
3809
+ "node_modules/estree-util-is-identifier-name": {
3810
+ "version": "3.0.0",
3811
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
3812
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
3813
+ "license": "MIT",
3814
+ "funding": {
3815
+ "type": "opencollective",
3816
+ "url": "https://opencollective.com/unified"
3817
+ }
3818
+ },
3819
  "node_modules/estree-walker": {
3820
  "version": "3.0.3",
3821
  "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
 
3852
  "node": ">=12.0.0"
3853
  }
3854
  },
3855
+ "node_modules/extend": {
3856
+ "version": "3.0.2",
3857
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
3858
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
3859
+ "license": "MIT"
3860
+ },
3861
  "node_modules/fast-deep-equal": {
3862
  "version": "3.1.3",
3863
  "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
 
4090
  "node": ">= 0.4"
4091
  }
4092
  },
4093
+ "node_modules/hast-util-to-jsx-runtime": {
4094
+ "version": "2.3.6",
4095
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
4096
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
4097
+ "license": "MIT",
4098
+ "dependencies": {
4099
+ "@types/estree": "^1.0.0",
4100
+ "@types/hast": "^3.0.0",
4101
+ "@types/unist": "^3.0.0",
4102
+ "comma-separated-tokens": "^2.0.0",
4103
+ "devlop": "^1.0.0",
4104
+ "estree-util-is-identifier-name": "^3.0.0",
4105
+ "hast-util-whitespace": "^3.0.0",
4106
+ "mdast-util-mdx-expression": "^2.0.0",
4107
+ "mdast-util-mdx-jsx": "^3.0.0",
4108
+ "mdast-util-mdxjs-esm": "^2.0.0",
4109
+ "property-information": "^7.0.0",
4110
+ "space-separated-tokens": "^2.0.0",
4111
+ "style-to-js": "^1.0.0",
4112
+ "unist-util-position": "^5.0.0",
4113
+ "vfile-message": "^4.0.0"
4114
+ },
4115
+ "funding": {
4116
+ "type": "opencollective",
4117
+ "url": "https://opencollective.com/unified"
4118
+ }
4119
+ },
4120
+ "node_modules/hast-util-whitespace": {
4121
+ "version": "3.0.0",
4122
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
4123
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
4124
+ "license": "MIT",
4125
+ "dependencies": {
4126
+ "@types/hast": "^3.0.0"
4127
+ },
4128
+ "funding": {
4129
+ "type": "opencollective",
4130
+ "url": "https://opencollective.com/unified"
4131
+ }
4132
+ },
4133
  "node_modules/html-encoding-sniffer": {
4134
  "version": "6.0.0",
4135
  "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
 
4143
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
4144
  }
4145
  },
4146
+ "node_modules/html-url-attributes": {
4147
+ "version": "3.0.1",
4148
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
4149
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
4150
+ "license": "MIT",
4151
+ "funding": {
4152
+ "type": "opencollective",
4153
+ "url": "https://opencollective.com/unified"
4154
+ }
4155
+ },
4156
  "node_modules/ignore": {
4157
  "version": "5.3.2",
4158
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
 
4200
  "node": ">=8"
4201
  }
4202
  },
4203
+ "node_modules/inline-style-parser": {
4204
+ "version": "0.2.7",
4205
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
4206
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
4207
+ "license": "MIT"
4208
+ },
4209
  "node_modules/internmap": {
4210
  "version": "2.0.3",
4211
  "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
 
4215
  "node": ">=12"
4216
  }
4217
  },
4218
+ "node_modules/is-alphabetical": {
4219
+ "version": "2.0.1",
4220
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
4221
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
4222
+ "license": "MIT",
4223
+ "funding": {
4224
+ "type": "github",
4225
+ "url": "https://github.com/sponsors/wooorm"
4226
+ }
4227
+ },
4228
+ "node_modules/is-alphanumerical": {
4229
+ "version": "2.0.1",
4230
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
4231
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
4232
+ "license": "MIT",
4233
+ "dependencies": {
4234
+ "is-alphabetical": "^2.0.0",
4235
+ "is-decimal": "^2.0.0"
4236
+ },
4237
+ "funding": {
4238
+ "type": "github",
4239
+ "url": "https://github.com/sponsors/wooorm"
4240
+ }
4241
+ },
4242
  "node_modules/is-binary-path": {
4243
  "version": "2.1.0",
4244
  "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
 
4268
  "url": "https://github.com/sponsors/ljharb"
4269
  }
4270
  },
4271
+ "node_modules/is-decimal": {
4272
+ "version": "2.0.1",
4273
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
4274
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
4275
+ "license": "MIT",
4276
+ "funding": {
4277
+ "type": "github",
4278
+ "url": "https://github.com/sponsors/wooorm"
4279
+ }
4280
+ },
4281
  "node_modules/is-extglob": {
4282
  "version": "2.1.1",
4283
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
 
4301
  "node": ">=0.10.0"
4302
  }
4303
  },
4304
+ "node_modules/is-hexadecimal": {
4305
+ "version": "2.0.1",
4306
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
4307
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
4308
+ "license": "MIT",
4309
+ "funding": {
4310
+ "type": "github",
4311
+ "url": "https://github.com/sponsors/wooorm"
4312
+ }
4313
+ },
4314
  "node_modules/is-number": {
4315
  "version": "7.0.0",
4316
  "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
 
4321
  "node": ">=0.12.0"
4322
  }
4323
  },
4324
+ "node_modules/is-plain-obj": {
4325
+ "version": "4.1.0",
4326
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
4327
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
4328
+ "license": "MIT",
4329
+ "engines": {
4330
+ "node": ">=12"
4331
+ },
4332
+ "funding": {
4333
+ "url": "https://github.com/sponsors/sindresorhus"
4334
+ }
4335
+ },
4336
  "node_modules/is-potential-custom-element-name": {
4337
  "version": "1.0.1",
4338
  "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
 
4810
  "dev": true,
4811
  "license": "MIT"
4812
  },
4813
+ "node_modules/longest-streak": {
4814
+ "version": "3.1.0",
4815
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
4816
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
4817
+ "license": "MIT",
4818
+ "funding": {
4819
+ "type": "github",
4820
+ "url": "https://github.com/sponsors/wooorm"
4821
+ }
4822
+ },
4823
  "node_modules/loose-envify": {
4824
  "version": "1.4.0",
4825
  "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
 
4871
  "@jridgewell/sourcemap-codec": "^1.5.5"
4872
  }
4873
  },
4874
+ "node_modules/markdown-table": {
4875
+ "version": "3.0.4",
4876
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
4877
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
 
 
 
 
 
 
 
 
4878
  "license": "MIT",
4879
+ "funding": {
4880
+ "type": "github",
4881
+ "url": "https://github.com/sponsors/wooorm"
4882
  }
4883
  },
4884
+ "node_modules/mdast-util-find-and-replace": {
4885
+ "version": "3.0.2",
4886
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
4887
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
 
4888
  "license": "MIT",
4889
  "dependencies": {
4890
+ "@types/mdast": "^4.0.0",
4891
+ "escape-string-regexp": "^5.0.0",
4892
+ "unist-util-is": "^6.0.0",
4893
+ "unist-util-visit-parents": "^6.0.0"
4894
  },
4895
+ "funding": {
4896
+ "type": "opencollective",
4897
+ "url": "https://opencollective.com/unified"
4898
  }
4899
  },
4900
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
4901
+ "version": "5.0.0",
4902
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
4903
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
 
4904
  "license": "MIT",
4905
  "engines": {
4906
+ "node": ">=12"
4907
+ },
4908
+ "funding": {
4909
+ "url": "https://github.com/sponsors/sindresorhus"
4910
  }
4911
  },
4912
+ "node_modules/mdast-util-from-markdown": {
4913
+ "version": "2.0.3",
4914
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
4915
+ "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
4916
+ "license": "MIT",
4917
+ "dependencies": {
4918
+ "@types/mdast": "^4.0.0",
4919
+ "@types/unist": "^3.0.0",
4920
+ "decode-named-character-reference": "^1.0.0",
4921
+ "devlop": "^1.0.0",
4922
+ "mdast-util-to-string": "^4.0.0",
4923
+ "micromark": "^4.0.0",
4924
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
4925
+ "micromark-util-decode-string": "^2.0.0",
4926
+ "micromark-util-normalize-identifier": "^2.0.0",
4927
+ "micromark-util-symbol": "^2.0.0",
4928
+ "micromark-util-types": "^2.0.0",
4929
+ "unist-util-stringify-position": "^4.0.0"
4930
  },
4931
+ "funding": {
4932
+ "type": "opencollective",
4933
+ "url": "https://opencollective.com/unified"
4934
  }
4935
  },
4936
+ "node_modules/mdast-util-gfm": {
4937
+ "version": "3.1.0",
4938
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
4939
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
 
 
 
 
 
 
 
 
4940
  "license": "MIT",
4941
  "dependencies": {
4942
+ "mdast-util-from-markdown": "^2.0.0",
4943
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
4944
+ "mdast-util-gfm-footnote": "^2.0.0",
4945
+ "mdast-util-gfm-strikethrough": "^2.0.0",
4946
+ "mdast-util-gfm-table": "^2.0.0",
4947
+ "mdast-util-gfm-task-list-item": "^2.0.0",
4948
+ "mdast-util-to-markdown": "^2.0.0"
4949
+ },
4950
+ "funding": {
4951
+ "type": "opencollective",
4952
+ "url": "https://opencollective.com/unified"
4953
  }
4954
  },
4955
+ "node_modules/mdast-util-gfm-autolink-literal": {
4956
+ "version": "2.0.1",
4957
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
4958
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
 
 
 
 
 
 
 
4959
  "license": "MIT",
4960
+ "dependencies": {
4961
+ "@types/mdast": "^4.0.0",
4962
+ "ccount": "^2.0.0",
4963
+ "devlop": "^1.0.0",
4964
+ "mdast-util-find-and-replace": "^3.0.0",
4965
+ "micromark-util-character": "^2.0.0"
4966
  },
4967
+ "funding": {
4968
+ "type": "opencollective",
4969
+ "url": "https://opencollective.com/unified"
4970
  }
4971
  },
4972
+ "node_modules/mdast-util-gfm-footnote": {
4973
+ "version": "2.1.0",
4974
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
4975
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
4976
+ "license": "MIT",
4977
+ "dependencies": {
4978
+ "@types/mdast": "^4.0.0",
4979
+ "devlop": "^1.1.0",
4980
+ "mdast-util-from-markdown": "^2.0.0",
4981
+ "mdast-util-to-markdown": "^2.0.0",
4982
+ "micromark-util-normalize-identifier": "^2.0.0"
4983
+ },
4984
+ "funding": {
4985
+ "type": "opencollective",
4986
+ "url": "https://opencollective.com/unified"
4987
+ }
4988
+ },
4989
+ "node_modules/mdast-util-gfm-strikethrough": {
4990
+ "version": "2.0.0",
4991
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
4992
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
4993
+ "license": "MIT",
4994
+ "dependencies": {
4995
+ "@types/mdast": "^4.0.0",
4996
+ "mdast-util-from-markdown": "^2.0.0",
4997
+ "mdast-util-to-markdown": "^2.0.0"
4998
+ },
4999
+ "funding": {
5000
+ "type": "opencollective",
5001
+ "url": "https://opencollective.com/unified"
5002
+ }
5003
+ },
5004
+ "node_modules/mdast-util-gfm-table": {
5005
+ "version": "2.0.0",
5006
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
5007
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
5008
+ "license": "MIT",
5009
+ "dependencies": {
5010
+ "@types/mdast": "^4.0.0",
5011
+ "devlop": "^1.0.0",
5012
+ "markdown-table": "^3.0.0",
5013
+ "mdast-util-from-markdown": "^2.0.0",
5014
+ "mdast-util-to-markdown": "^2.0.0"
5015
+ },
5016
+ "funding": {
5017
+ "type": "opencollective",
5018
+ "url": "https://opencollective.com/unified"
5019
+ }
5020
+ },
5021
+ "node_modules/mdast-util-gfm-task-list-item": {
5022
+ "version": "2.0.0",
5023
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
5024
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
5025
+ "license": "MIT",
5026
+ "dependencies": {
5027
+ "@types/mdast": "^4.0.0",
5028
+ "devlop": "^1.0.0",
5029
+ "mdast-util-from-markdown": "^2.0.0",
5030
+ "mdast-util-to-markdown": "^2.0.0"
5031
+ },
5032
+ "funding": {
5033
+ "type": "opencollective",
5034
+ "url": "https://opencollective.com/unified"
5035
+ }
5036
+ },
5037
+ "node_modules/mdast-util-mdx-expression": {
5038
+ "version": "2.0.1",
5039
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
5040
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
5041
+ "license": "MIT",
5042
+ "dependencies": {
5043
+ "@types/estree-jsx": "^1.0.0",
5044
+ "@types/hast": "^3.0.0",
5045
+ "@types/mdast": "^4.0.0",
5046
+ "devlop": "^1.0.0",
5047
+ "mdast-util-from-markdown": "^2.0.0",
5048
+ "mdast-util-to-markdown": "^2.0.0"
5049
+ },
5050
+ "funding": {
5051
+ "type": "opencollective",
5052
+ "url": "https://opencollective.com/unified"
5053
+ }
5054
+ },
5055
+ "node_modules/mdast-util-mdx-jsx": {
5056
+ "version": "3.2.0",
5057
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
5058
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
5059
+ "license": "MIT",
5060
+ "dependencies": {
5061
+ "@types/estree-jsx": "^1.0.0",
5062
+ "@types/hast": "^3.0.0",
5063
+ "@types/mdast": "^4.0.0",
5064
+ "@types/unist": "^3.0.0",
5065
+ "ccount": "^2.0.0",
5066
+ "devlop": "^1.1.0",
5067
+ "mdast-util-from-markdown": "^2.0.0",
5068
+ "mdast-util-to-markdown": "^2.0.0",
5069
+ "parse-entities": "^4.0.0",
5070
+ "stringify-entities": "^4.0.0",
5071
+ "unist-util-stringify-position": "^4.0.0",
5072
+ "vfile-message": "^4.0.0"
5073
+ },
5074
+ "funding": {
5075
+ "type": "opencollective",
5076
+ "url": "https://opencollective.com/unified"
5077
+ }
5078
+ },
5079
+ "node_modules/mdast-util-mdxjs-esm": {
5080
+ "version": "2.0.1",
5081
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
5082
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
5083
+ "license": "MIT",
5084
+ "dependencies": {
5085
+ "@types/estree-jsx": "^1.0.0",
5086
+ "@types/hast": "^3.0.0",
5087
+ "@types/mdast": "^4.0.0",
5088
+ "devlop": "^1.0.0",
5089
+ "mdast-util-from-markdown": "^2.0.0",
5090
+ "mdast-util-to-markdown": "^2.0.0"
5091
+ },
5092
+ "funding": {
5093
+ "type": "opencollective",
5094
+ "url": "https://opencollective.com/unified"
5095
+ }
5096
+ },
5097
+ "node_modules/mdast-util-phrasing": {
5098
+ "version": "4.1.0",
5099
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
5100
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
5101
+ "license": "MIT",
5102
+ "dependencies": {
5103
+ "@types/mdast": "^4.0.0",
5104
+ "unist-util-is": "^6.0.0"
5105
+ },
5106
+ "funding": {
5107
+ "type": "opencollective",
5108
+ "url": "https://opencollective.com/unified"
5109
+ }
5110
+ },
5111
+ "node_modules/mdast-util-to-hast": {
5112
+ "version": "13.2.1",
5113
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
5114
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
5115
+ "license": "MIT",
5116
+ "dependencies": {
5117
+ "@types/hast": "^3.0.0",
5118
+ "@types/mdast": "^4.0.0",
5119
+ "@ungap/structured-clone": "^1.0.0",
5120
+ "devlop": "^1.0.0",
5121
+ "micromark-util-sanitize-uri": "^2.0.0",
5122
+ "trim-lines": "^3.0.0",
5123
+ "unist-util-position": "^5.0.0",
5124
+ "unist-util-visit": "^5.0.0",
5125
+ "vfile": "^6.0.0"
5126
+ },
5127
+ "funding": {
5128
+ "type": "opencollective",
5129
+ "url": "https://opencollective.com/unified"
5130
+ }
5131
+ },
5132
+ "node_modules/mdast-util-to-markdown": {
5133
+ "version": "2.1.2",
5134
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
5135
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
5136
+ "license": "MIT",
5137
+ "dependencies": {
5138
+ "@types/mdast": "^4.0.0",
5139
+ "@types/unist": "^3.0.0",
5140
+ "longest-streak": "^3.0.0",
5141
+ "mdast-util-phrasing": "^4.0.0",
5142
+ "mdast-util-to-string": "^4.0.0",
5143
+ "micromark-util-classify-character": "^2.0.0",
5144
+ "micromark-util-decode-string": "^2.0.0",
5145
+ "unist-util-visit": "^5.0.0",
5146
+ "zwitch": "^2.0.0"
5147
+ },
5148
+ "funding": {
5149
+ "type": "opencollective",
5150
+ "url": "https://opencollective.com/unified"
5151
+ }
5152
+ },
5153
+ "node_modules/mdast-util-to-string": {
5154
+ "version": "4.0.0",
5155
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
5156
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
5157
+ "license": "MIT",
5158
+ "dependencies": {
5159
+ "@types/mdast": "^4.0.0"
5160
+ },
5161
+ "funding": {
5162
+ "type": "opencollective",
5163
+ "url": "https://opencollective.com/unified"
5164
+ }
5165
+ },
5166
+ "node_modules/mdn-data": {
5167
+ "version": "2.27.1",
5168
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
5169
+ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
5170
+ "dev": true,
5171
+ "license": "CC0-1.0"
5172
+ },
5173
+ "node_modules/merge2": {
5174
+ "version": "1.4.1",
5175
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
5176
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
5177
+ "dev": true,
5178
+ "license": "MIT",
5179
+ "engines": {
5180
+ "node": ">= 8"
5181
+ }
5182
+ },
5183
+ "node_modules/micromark": {
5184
+ "version": "4.0.2",
5185
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
5186
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
5187
+ "funding": [
5188
+ {
5189
+ "type": "GitHub Sponsors",
5190
+ "url": "https://github.com/sponsors/unifiedjs"
5191
+ },
5192
+ {
5193
+ "type": "OpenCollective",
5194
+ "url": "https://opencollective.com/unified"
5195
+ }
5196
+ ],
5197
+ "license": "MIT",
5198
+ "dependencies": {
5199
+ "@types/debug": "^4.0.0",
5200
+ "debug": "^4.0.0",
5201
+ "decode-named-character-reference": "^1.0.0",
5202
+ "devlop": "^1.0.0",
5203
+ "micromark-core-commonmark": "^2.0.0",
5204
+ "micromark-factory-space": "^2.0.0",
5205
+ "micromark-util-character": "^2.0.0",
5206
+ "micromark-util-chunked": "^2.0.0",
5207
+ "micromark-util-combine-extensions": "^2.0.0",
5208
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
5209
+ "micromark-util-encode": "^2.0.0",
5210
+ "micromark-util-normalize-identifier": "^2.0.0",
5211
+ "micromark-util-resolve-all": "^2.0.0",
5212
+ "micromark-util-sanitize-uri": "^2.0.0",
5213
+ "micromark-util-subtokenize": "^2.0.0",
5214
+ "micromark-util-symbol": "^2.0.0",
5215
+ "micromark-util-types": "^2.0.0"
5216
+ }
5217
+ },
5218
+ "node_modules/micromark-core-commonmark": {
5219
+ "version": "2.0.3",
5220
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
5221
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
5222
+ "funding": [
5223
+ {
5224
+ "type": "GitHub Sponsors",
5225
+ "url": "https://github.com/sponsors/unifiedjs"
5226
+ },
5227
+ {
5228
+ "type": "OpenCollective",
5229
+ "url": "https://opencollective.com/unified"
5230
+ }
5231
+ ],
5232
+ "license": "MIT",
5233
+ "dependencies": {
5234
+ "decode-named-character-reference": "^1.0.0",
5235
+ "devlop": "^1.0.0",
5236
+ "micromark-factory-destination": "^2.0.0",
5237
+ "micromark-factory-label": "^2.0.0",
5238
+ "micromark-factory-space": "^2.0.0",
5239
+ "micromark-factory-title": "^2.0.0",
5240
+ "micromark-factory-whitespace": "^2.0.0",
5241
+ "micromark-util-character": "^2.0.0",
5242
+ "micromark-util-chunked": "^2.0.0",
5243
+ "micromark-util-classify-character": "^2.0.0",
5244
+ "micromark-util-html-tag-name": "^2.0.0",
5245
+ "micromark-util-normalize-identifier": "^2.0.0",
5246
+ "micromark-util-resolve-all": "^2.0.0",
5247
+ "micromark-util-subtokenize": "^2.0.0",
5248
+ "micromark-util-symbol": "^2.0.0",
5249
+ "micromark-util-types": "^2.0.0"
5250
+ }
5251
+ },
5252
+ "node_modules/micromark-extension-gfm": {
5253
+ "version": "3.0.0",
5254
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
5255
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
5256
+ "license": "MIT",
5257
+ "dependencies": {
5258
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
5259
+ "micromark-extension-gfm-footnote": "^2.0.0",
5260
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
5261
+ "micromark-extension-gfm-table": "^2.0.0",
5262
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
5263
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
5264
+ "micromark-util-combine-extensions": "^2.0.0",
5265
+ "micromark-util-types": "^2.0.0"
5266
+ },
5267
+ "funding": {
5268
+ "type": "opencollective",
5269
+ "url": "https://opencollective.com/unified"
5270
+ }
5271
+ },
5272
+ "node_modules/micromark-extension-gfm-autolink-literal": {
5273
+ "version": "2.1.0",
5274
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
5275
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
5276
+ "license": "MIT",
5277
+ "dependencies": {
5278
+ "micromark-util-character": "^2.0.0",
5279
+ "micromark-util-sanitize-uri": "^2.0.0",
5280
+ "micromark-util-symbol": "^2.0.0",
5281
+ "micromark-util-types": "^2.0.0"
5282
+ },
5283
+ "funding": {
5284
+ "type": "opencollective",
5285
+ "url": "https://opencollective.com/unified"
5286
+ }
5287
+ },
5288
+ "node_modules/micromark-extension-gfm-footnote": {
5289
+ "version": "2.1.0",
5290
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
5291
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
5292
+ "license": "MIT",
5293
+ "dependencies": {
5294
+ "devlop": "^1.0.0",
5295
+ "micromark-core-commonmark": "^2.0.0",
5296
+ "micromark-factory-space": "^2.0.0",
5297
+ "micromark-util-character": "^2.0.0",
5298
+ "micromark-util-normalize-identifier": "^2.0.0",
5299
+ "micromark-util-sanitize-uri": "^2.0.0",
5300
+ "micromark-util-symbol": "^2.0.0",
5301
+ "micromark-util-types": "^2.0.0"
5302
+ },
5303
+ "funding": {
5304
+ "type": "opencollective",
5305
+ "url": "https://opencollective.com/unified"
5306
+ }
5307
+ },
5308
+ "node_modules/micromark-extension-gfm-strikethrough": {
5309
+ "version": "2.1.0",
5310
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
5311
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
5312
+ "license": "MIT",
5313
+ "dependencies": {
5314
+ "devlop": "^1.0.0",
5315
+ "micromark-util-chunked": "^2.0.0",
5316
+ "micromark-util-classify-character": "^2.0.0",
5317
+ "micromark-util-resolve-all": "^2.0.0",
5318
+ "micromark-util-symbol": "^2.0.0",
5319
+ "micromark-util-types": "^2.0.0"
5320
+ },
5321
+ "funding": {
5322
+ "type": "opencollective",
5323
+ "url": "https://opencollective.com/unified"
5324
+ }
5325
+ },
5326
+ "node_modules/micromark-extension-gfm-table": {
5327
+ "version": "2.1.1",
5328
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
5329
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
5330
+ "license": "MIT",
5331
+ "dependencies": {
5332
+ "devlop": "^1.0.0",
5333
+ "micromark-factory-space": "^2.0.0",
5334
+ "micromark-util-character": "^2.0.0",
5335
+ "micromark-util-symbol": "^2.0.0",
5336
+ "micromark-util-types": "^2.0.0"
5337
+ },
5338
+ "funding": {
5339
+ "type": "opencollective",
5340
+ "url": "https://opencollective.com/unified"
5341
+ }
5342
+ },
5343
+ "node_modules/micromark-extension-gfm-tagfilter": {
5344
+ "version": "2.0.0",
5345
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
5346
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
5347
+ "license": "MIT",
5348
+ "dependencies": {
5349
+ "micromark-util-types": "^2.0.0"
5350
+ },
5351
+ "funding": {
5352
+ "type": "opencollective",
5353
+ "url": "https://opencollective.com/unified"
5354
+ }
5355
+ },
5356
+ "node_modules/micromark-extension-gfm-task-list-item": {
5357
+ "version": "2.1.0",
5358
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
5359
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
5360
+ "license": "MIT",
5361
+ "dependencies": {
5362
+ "devlop": "^1.0.0",
5363
+ "micromark-factory-space": "^2.0.0",
5364
+ "micromark-util-character": "^2.0.0",
5365
+ "micromark-util-symbol": "^2.0.0",
5366
+ "micromark-util-types": "^2.0.0"
5367
+ },
5368
+ "funding": {
5369
+ "type": "opencollective",
5370
+ "url": "https://opencollective.com/unified"
5371
+ }
5372
+ },
5373
+ "node_modules/micromark-factory-destination": {
5374
+ "version": "2.0.1",
5375
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
5376
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
5377
+ "funding": [
5378
+ {
5379
+ "type": "GitHub Sponsors",
5380
+ "url": "https://github.com/sponsors/unifiedjs"
5381
+ },
5382
+ {
5383
+ "type": "OpenCollective",
5384
+ "url": "https://opencollective.com/unified"
5385
+ }
5386
+ ],
5387
+ "license": "MIT",
5388
+ "dependencies": {
5389
+ "micromark-util-character": "^2.0.0",
5390
+ "micromark-util-symbol": "^2.0.0",
5391
+ "micromark-util-types": "^2.0.0"
5392
+ }
5393
+ },
5394
+ "node_modules/micromark-factory-label": {
5395
+ "version": "2.0.1",
5396
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
5397
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
5398
+ "funding": [
5399
+ {
5400
+ "type": "GitHub Sponsors",
5401
+ "url": "https://github.com/sponsors/unifiedjs"
5402
+ },
5403
+ {
5404
+ "type": "OpenCollective",
5405
+ "url": "https://opencollective.com/unified"
5406
+ }
5407
+ ],
5408
+ "license": "MIT",
5409
+ "dependencies": {
5410
+ "devlop": "^1.0.0",
5411
+ "micromark-util-character": "^2.0.0",
5412
+ "micromark-util-symbol": "^2.0.0",
5413
+ "micromark-util-types": "^2.0.0"
5414
+ }
5415
+ },
5416
+ "node_modules/micromark-factory-space": {
5417
+ "version": "2.0.1",
5418
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
5419
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
5420
+ "funding": [
5421
+ {
5422
+ "type": "GitHub Sponsors",
5423
+ "url": "https://github.com/sponsors/unifiedjs"
5424
+ },
5425
+ {
5426
+ "type": "OpenCollective",
5427
+ "url": "https://opencollective.com/unified"
5428
+ }
5429
+ ],
5430
+ "license": "MIT",
5431
+ "dependencies": {
5432
+ "micromark-util-character": "^2.0.0",
5433
+ "micromark-util-types": "^2.0.0"
5434
+ }
5435
+ },
5436
+ "node_modules/micromark-factory-title": {
5437
+ "version": "2.0.1",
5438
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
5439
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
5440
+ "funding": [
5441
+ {
5442
+ "type": "GitHub Sponsors",
5443
+ "url": "https://github.com/sponsors/unifiedjs"
5444
+ },
5445
+ {
5446
+ "type": "OpenCollective",
5447
+ "url": "https://opencollective.com/unified"
5448
+ }
5449
+ ],
5450
+ "license": "MIT",
5451
+ "dependencies": {
5452
+ "micromark-factory-space": "^2.0.0",
5453
+ "micromark-util-character": "^2.0.0",
5454
+ "micromark-util-symbol": "^2.0.0",
5455
+ "micromark-util-types": "^2.0.0"
5456
+ }
5457
+ },
5458
+ "node_modules/micromark-factory-whitespace": {
5459
+ "version": "2.0.1",
5460
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
5461
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
5462
+ "funding": [
5463
+ {
5464
+ "type": "GitHub Sponsors",
5465
+ "url": "https://github.com/sponsors/unifiedjs"
5466
+ },
5467
+ {
5468
+ "type": "OpenCollective",
5469
+ "url": "https://opencollective.com/unified"
5470
+ }
5471
+ ],
5472
+ "license": "MIT",
5473
+ "dependencies": {
5474
+ "micromark-factory-space": "^2.0.0",
5475
+ "micromark-util-character": "^2.0.0",
5476
+ "micromark-util-symbol": "^2.0.0",
5477
+ "micromark-util-types": "^2.0.0"
5478
+ }
5479
+ },
5480
+ "node_modules/micromark-util-character": {
5481
+ "version": "2.1.1",
5482
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
5483
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
5484
+ "funding": [
5485
+ {
5486
+ "type": "GitHub Sponsors",
5487
+ "url": "https://github.com/sponsors/unifiedjs"
5488
+ },
5489
+ {
5490
+ "type": "OpenCollective",
5491
+ "url": "https://opencollective.com/unified"
5492
+ }
5493
+ ],
5494
+ "license": "MIT",
5495
+ "dependencies": {
5496
+ "micromark-util-symbol": "^2.0.0",
5497
+ "micromark-util-types": "^2.0.0"
5498
+ }
5499
+ },
5500
+ "node_modules/micromark-util-chunked": {
5501
+ "version": "2.0.1",
5502
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
5503
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
5504
+ "funding": [
5505
+ {
5506
+ "type": "GitHub Sponsors",
5507
+ "url": "https://github.com/sponsors/unifiedjs"
5508
+ },
5509
+ {
5510
+ "type": "OpenCollective",
5511
+ "url": "https://opencollective.com/unified"
5512
+ }
5513
+ ],
5514
+ "license": "MIT",
5515
+ "dependencies": {
5516
+ "micromark-util-symbol": "^2.0.0"
5517
+ }
5518
+ },
5519
+ "node_modules/micromark-util-classify-character": {
5520
+ "version": "2.0.1",
5521
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
5522
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
5523
+ "funding": [
5524
+ {
5525
+ "type": "GitHub Sponsors",
5526
+ "url": "https://github.com/sponsors/unifiedjs"
5527
+ },
5528
+ {
5529
+ "type": "OpenCollective",
5530
+ "url": "https://opencollective.com/unified"
5531
+ }
5532
+ ],
5533
+ "license": "MIT",
5534
+ "dependencies": {
5535
+ "micromark-util-character": "^2.0.0",
5536
+ "micromark-util-symbol": "^2.0.0",
5537
+ "micromark-util-types": "^2.0.0"
5538
+ }
5539
+ },
5540
+ "node_modules/micromark-util-combine-extensions": {
5541
+ "version": "2.0.1",
5542
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
5543
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
5544
+ "funding": [
5545
+ {
5546
+ "type": "GitHub Sponsors",
5547
+ "url": "https://github.com/sponsors/unifiedjs"
5548
+ },
5549
+ {
5550
+ "type": "OpenCollective",
5551
+ "url": "https://opencollective.com/unified"
5552
+ }
5553
+ ],
5554
+ "license": "MIT",
5555
+ "dependencies": {
5556
+ "micromark-util-chunked": "^2.0.0",
5557
+ "micromark-util-types": "^2.0.0"
5558
+ }
5559
+ },
5560
+ "node_modules/micromark-util-decode-numeric-character-reference": {
5561
+ "version": "2.0.2",
5562
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
5563
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
5564
+ "funding": [
5565
+ {
5566
+ "type": "GitHub Sponsors",
5567
+ "url": "https://github.com/sponsors/unifiedjs"
5568
+ },
5569
+ {
5570
+ "type": "OpenCollective",
5571
+ "url": "https://opencollective.com/unified"
5572
+ }
5573
+ ],
5574
+ "license": "MIT",
5575
+ "dependencies": {
5576
+ "micromark-util-symbol": "^2.0.0"
5577
+ }
5578
+ },
5579
+ "node_modules/micromark-util-decode-string": {
5580
+ "version": "2.0.1",
5581
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
5582
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
5583
+ "funding": [
5584
+ {
5585
+ "type": "GitHub Sponsors",
5586
+ "url": "https://github.com/sponsors/unifiedjs"
5587
+ },
5588
+ {
5589
+ "type": "OpenCollective",
5590
+ "url": "https://opencollective.com/unified"
5591
+ }
5592
+ ],
5593
+ "license": "MIT",
5594
+ "dependencies": {
5595
+ "decode-named-character-reference": "^1.0.0",
5596
+ "micromark-util-character": "^2.0.0",
5597
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
5598
+ "micromark-util-symbol": "^2.0.0"
5599
+ }
5600
+ },
5601
+ "node_modules/micromark-util-encode": {
5602
+ "version": "2.0.1",
5603
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
5604
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
5605
+ "funding": [
5606
+ {
5607
+ "type": "GitHub Sponsors",
5608
+ "url": "https://github.com/sponsors/unifiedjs"
5609
+ },
5610
+ {
5611
+ "type": "OpenCollective",
5612
+ "url": "https://opencollective.com/unified"
5613
+ }
5614
+ ],
5615
+ "license": "MIT"
5616
+ },
5617
+ "node_modules/micromark-util-html-tag-name": {
5618
+ "version": "2.0.1",
5619
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
5620
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
5621
+ "funding": [
5622
+ {
5623
+ "type": "GitHub Sponsors",
5624
+ "url": "https://github.com/sponsors/unifiedjs"
5625
+ },
5626
+ {
5627
+ "type": "OpenCollective",
5628
+ "url": "https://opencollective.com/unified"
5629
+ }
5630
+ ],
5631
+ "license": "MIT"
5632
+ },
5633
+ "node_modules/micromark-util-normalize-identifier": {
5634
+ "version": "2.0.1",
5635
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
5636
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
5637
+ "funding": [
5638
+ {
5639
+ "type": "GitHub Sponsors",
5640
+ "url": "https://github.com/sponsors/unifiedjs"
5641
+ },
5642
+ {
5643
+ "type": "OpenCollective",
5644
+ "url": "https://opencollective.com/unified"
5645
+ }
5646
+ ],
5647
+ "license": "MIT",
5648
+ "dependencies": {
5649
+ "micromark-util-symbol": "^2.0.0"
5650
+ }
5651
+ },
5652
+ "node_modules/micromark-util-resolve-all": {
5653
+ "version": "2.0.1",
5654
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
5655
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
5656
+ "funding": [
5657
+ {
5658
+ "type": "GitHub Sponsors",
5659
+ "url": "https://github.com/sponsors/unifiedjs"
5660
+ },
5661
+ {
5662
+ "type": "OpenCollective",
5663
+ "url": "https://opencollective.com/unified"
5664
+ }
5665
+ ],
5666
+ "license": "MIT",
5667
+ "dependencies": {
5668
+ "micromark-util-types": "^2.0.0"
5669
+ }
5670
+ },
5671
+ "node_modules/micromark-util-sanitize-uri": {
5672
+ "version": "2.0.1",
5673
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
5674
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
5675
+ "funding": [
5676
+ {
5677
+ "type": "GitHub Sponsors",
5678
+ "url": "https://github.com/sponsors/unifiedjs"
5679
+ },
5680
+ {
5681
+ "type": "OpenCollective",
5682
+ "url": "https://opencollective.com/unified"
5683
+ }
5684
+ ],
5685
+ "license": "MIT",
5686
+ "dependencies": {
5687
+ "micromark-util-character": "^2.0.0",
5688
+ "micromark-util-encode": "^2.0.0",
5689
+ "micromark-util-symbol": "^2.0.0"
5690
+ }
5691
+ },
5692
+ "node_modules/micromark-util-subtokenize": {
5693
+ "version": "2.1.0",
5694
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
5695
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
5696
+ "funding": [
5697
+ {
5698
+ "type": "GitHub Sponsors",
5699
+ "url": "https://github.com/sponsors/unifiedjs"
5700
+ },
5701
+ {
5702
+ "type": "OpenCollective",
5703
+ "url": "https://opencollective.com/unified"
5704
+ }
5705
+ ],
5706
+ "license": "MIT",
5707
+ "dependencies": {
5708
+ "devlop": "^1.0.0",
5709
+ "micromark-util-chunked": "^2.0.0",
5710
+ "micromark-util-symbol": "^2.0.0",
5711
+ "micromark-util-types": "^2.0.0"
5712
+ }
5713
+ },
5714
+ "node_modules/micromark-util-symbol": {
5715
+ "version": "2.0.1",
5716
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
5717
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
5718
+ "funding": [
5719
+ {
5720
+ "type": "GitHub Sponsors",
5721
+ "url": "https://github.com/sponsors/unifiedjs"
5722
+ },
5723
+ {
5724
+ "type": "OpenCollective",
5725
+ "url": "https://opencollective.com/unified"
5726
+ }
5727
+ ],
5728
+ "license": "MIT"
5729
+ },
5730
+ "node_modules/micromark-util-types": {
5731
+ "version": "2.0.2",
5732
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
5733
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
5734
+ "funding": [
5735
+ {
5736
+ "type": "GitHub Sponsors",
5737
+ "url": "https://github.com/sponsors/unifiedjs"
5738
+ },
5739
+ {
5740
+ "type": "OpenCollective",
5741
+ "url": "https://opencollective.com/unified"
5742
+ }
5743
+ ],
5744
+ "license": "MIT"
5745
+ },
5746
+ "node_modules/micromatch": {
5747
+ "version": "4.0.8",
5748
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
5749
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
5750
+ "dev": true,
5751
+ "license": "MIT",
5752
+ "dependencies": {
5753
+ "braces": "^3.0.3",
5754
+ "picomatch": "^2.3.1"
5755
+ },
5756
+ "engines": {
5757
+ "node": ">=8.6"
5758
+ }
5759
+ },
5760
+ "node_modules/min-indent": {
5761
+ "version": "1.0.1",
5762
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
5763
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
5764
+ "dev": true,
5765
+ "license": "MIT",
5766
+ "engines": {
5767
+ "node": ">=4"
5768
+ }
5769
+ },
5770
+ "node_modules/minimatch": {
5771
+ "version": "3.1.5",
5772
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
5773
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
5774
+ "dev": true,
5775
+ "license": "ISC",
5776
+ "dependencies": {
5777
+ "brace-expansion": "^1.1.7"
5778
+ },
5779
+ "engines": {
5780
+ "node": "*"
5781
+ }
5782
+ },
5783
+ "node_modules/ms": {
5784
+ "version": "2.1.3",
5785
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
5786
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
5787
+ "license": "MIT"
5788
+ },
5789
+ "node_modules/mz": {
5790
+ "version": "2.7.0",
5791
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
5792
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
5793
+ "dev": true,
5794
+ "license": "MIT",
5795
+ "dependencies": {
5796
+ "any-promise": "^1.0.0",
5797
+ "object-assign": "^4.0.1",
5798
+ "thenify-all": "^1.0.0"
5799
+ }
5800
+ },
5801
+ "node_modules/nanoid": {
5802
+ "version": "3.3.11",
5803
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
5804
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
5805
+ "dev": true,
5806
+ "funding": [
5807
+ {
5808
+ "type": "github",
5809
+ "url": "https://github.com/sponsors/ai"
5810
+ }
5811
+ ],
5812
+ "license": "MIT",
5813
+ "bin": {
5814
+ "nanoid": "bin/nanoid.cjs"
5815
+ },
5816
+ "engines": {
5817
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
5818
+ }
5819
+ },
5820
+ "node_modules/natural-compare": {
5821
+ "version": "1.4.0",
5822
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
5823
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
5824
  "dev": true,
5825
  "license": "MIT"
5826
  },
 
5934
  "node": ">=6"
5935
  }
5936
  },
5937
+ "node_modules/parse-entities": {
5938
+ "version": "4.0.2",
5939
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
5940
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
5941
+ "license": "MIT",
5942
+ "dependencies": {
5943
+ "@types/unist": "^2.0.0",
5944
+ "character-entities-legacy": "^3.0.0",
5945
+ "character-reference-invalid": "^2.0.0",
5946
+ "decode-named-character-reference": "^1.0.0",
5947
+ "is-alphanumerical": "^2.0.0",
5948
+ "is-decimal": "^2.0.0",
5949
+ "is-hexadecimal": "^2.0.0"
5950
+ },
5951
+ "funding": {
5952
+ "type": "github",
5953
+ "url": "https://github.com/sponsors/wooorm"
5954
+ }
5955
+ },
5956
+ "node_modules/parse-entities/node_modules/@types/unist": {
5957
+ "version": "2.0.11",
5958
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
5959
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
5960
+ "license": "MIT"
5961
+ },
5962
  "node_modules/parse5": {
5963
  "version": "8.0.0",
5964
  "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
 
6265
  "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
6266
  "license": "MIT"
6267
  },
6268
+ "node_modules/property-information": {
6269
+ "version": "7.1.0",
6270
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
6271
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
6272
+ "license": "MIT",
6273
+ "funding": {
6274
+ "type": "github",
6275
+ "url": "https://github.com/sponsors/wooorm"
6276
+ }
6277
+ },
6278
  "node_modules/punycode": {
6279
  "version": "2.3.1",
6280
  "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
 
6340
  "dev": true,
6341
  "license": "MIT"
6342
  },
6343
+ "node_modules/react-markdown": {
6344
+ "version": "10.1.0",
6345
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz",
6346
+ "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==",
6347
+ "license": "MIT",
6348
+ "dependencies": {
6349
+ "@types/hast": "^3.0.0",
6350
+ "@types/mdast": "^4.0.0",
6351
+ "devlop": "^1.0.0",
6352
+ "hast-util-to-jsx-runtime": "^2.0.0",
6353
+ "html-url-attributes": "^3.0.0",
6354
+ "mdast-util-to-hast": "^13.0.0",
6355
+ "remark-parse": "^11.0.0",
6356
+ "remark-rehype": "^11.0.0",
6357
+ "unified": "^11.0.0",
6358
+ "unist-util-visit": "^5.0.0",
6359
+ "vfile": "^6.0.0"
6360
+ },
6361
+ "funding": {
6362
+ "type": "opencollective",
6363
+ "url": "https://opencollective.com/unified"
6364
+ },
6365
+ "peerDependencies": {
6366
+ "@types/react": ">=18",
6367
+ "react": ">=18"
6368
+ }
6369
+ },
6370
  "node_modules/react-refresh": {
6371
  "version": "0.17.0",
6372
  "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
 
6515
  "node": ">=8"
6516
  }
6517
  },
6518
+ "node_modules/remark-gfm": {
6519
+ "version": "4.0.1",
6520
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
6521
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
6522
+ "license": "MIT",
6523
+ "dependencies": {
6524
+ "@types/mdast": "^4.0.0",
6525
+ "mdast-util-gfm": "^3.0.0",
6526
+ "micromark-extension-gfm": "^3.0.0",
6527
+ "remark-parse": "^11.0.0",
6528
+ "remark-stringify": "^11.0.0",
6529
+ "unified": "^11.0.0"
6530
+ },
6531
+ "funding": {
6532
+ "type": "opencollective",
6533
+ "url": "https://opencollective.com/unified"
6534
+ }
6535
+ },
6536
+ "node_modules/remark-parse": {
6537
+ "version": "11.0.0",
6538
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
6539
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
6540
+ "license": "MIT",
6541
+ "dependencies": {
6542
+ "@types/mdast": "^4.0.0",
6543
+ "mdast-util-from-markdown": "^2.0.0",
6544
+ "micromark-util-types": "^2.0.0",
6545
+ "unified": "^11.0.0"
6546
+ },
6547
+ "funding": {
6548
+ "type": "opencollective",
6549
+ "url": "https://opencollective.com/unified"
6550
+ }
6551
+ },
6552
+ "node_modules/remark-rehype": {
6553
+ "version": "11.1.2",
6554
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
6555
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
6556
+ "license": "MIT",
6557
+ "dependencies": {
6558
+ "@types/hast": "^3.0.0",
6559
+ "@types/mdast": "^4.0.0",
6560
+ "mdast-util-to-hast": "^13.0.0",
6561
+ "unified": "^11.0.0",
6562
+ "vfile": "^6.0.0"
6563
+ },
6564
+ "funding": {
6565
+ "type": "opencollective",
6566
+ "url": "https://opencollective.com/unified"
6567
+ }
6568
+ },
6569
+ "node_modules/remark-stringify": {
6570
+ "version": "11.0.0",
6571
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
6572
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
6573
+ "license": "MIT",
6574
+ "dependencies": {
6575
+ "@types/mdast": "^4.0.0",
6576
+ "mdast-util-to-markdown": "^2.0.0",
6577
+ "unified": "^11.0.0"
6578
+ },
6579
+ "funding": {
6580
+ "type": "opencollective",
6581
+ "url": "https://opencollective.com/unified"
6582
+ }
6583
+ },
6584
  "node_modules/require-from-string": {
6585
  "version": "2.0.2",
6586
  "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
 
6815
  "node": ">=0.10.0"
6816
  }
6817
  },
6818
+ "node_modules/space-separated-tokens": {
6819
+ "version": "2.0.2",
6820
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
6821
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
6822
+ "license": "MIT",
6823
+ "funding": {
6824
+ "type": "github",
6825
+ "url": "https://github.com/sponsors/wooorm"
6826
+ }
6827
+ },
6828
  "node_modules/stackback": {
6829
  "version": "0.0.2",
6830
  "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
 
6839
  "dev": true,
6840
  "license": "MIT"
6841
  },
6842
+ "node_modules/stringify-entities": {
6843
+ "version": "4.0.4",
6844
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
6845
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
6846
+ "license": "MIT",
6847
+ "dependencies": {
6848
+ "character-entities-html4": "^2.0.0",
6849
+ "character-entities-legacy": "^3.0.0"
6850
+ },
6851
+ "funding": {
6852
+ "type": "github",
6853
+ "url": "https://github.com/sponsors/wooorm"
6854
+ }
6855
+ },
6856
  "node_modules/strip-indent": {
6857
  "version": "3.0.0",
6858
  "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
 
6879
  "url": "https://github.com/sponsors/sindresorhus"
6880
  }
6881
  },
6882
+ "node_modules/style-to-js": {
6883
+ "version": "1.1.21",
6884
+ "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
6885
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
6886
+ "license": "MIT",
6887
+ "dependencies": {
6888
+ "style-to-object": "1.0.14"
6889
+ }
6890
+ },
6891
+ "node_modules/style-to-object": {
6892
+ "version": "1.0.14",
6893
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
6894
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
6895
+ "license": "MIT",
6896
+ "dependencies": {
6897
+ "inline-style-parser": "0.2.7"
6898
+ }
6899
+ },
6900
  "node_modules/sucrase": {
6901
  "version": "3.35.1",
6902
  "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
 
7155
  "node": ">=20"
7156
  }
7157
  },
7158
+ "node_modules/trim-lines": {
7159
+ "version": "3.0.1",
7160
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
7161
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
7162
+ "license": "MIT",
7163
+ "funding": {
7164
+ "type": "github",
7165
+ "url": "https://github.com/sponsors/wooorm"
7166
+ }
7167
+ },
7168
+ "node_modules/trough": {
7169
+ "version": "2.2.0",
7170
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
7171
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
7172
+ "license": "MIT",
7173
+ "funding": {
7174
+ "type": "github",
7175
+ "url": "https://github.com/sponsors/wooorm"
7176
+ }
7177
+ },
7178
  "node_modules/ts-api-utils": {
7179
  "version": "2.5.0",
7180
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
 
7265
  "node": ">=20.18.1"
7266
  }
7267
  },
7268
+ "node_modules/unified": {
7269
+ "version": "11.0.5",
7270
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
7271
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
7272
+ "license": "MIT",
7273
+ "dependencies": {
7274
+ "@types/unist": "^3.0.0",
7275
+ "bail": "^2.0.0",
7276
+ "devlop": "^1.0.0",
7277
+ "extend": "^3.0.0",
7278
+ "is-plain-obj": "^4.0.0",
7279
+ "trough": "^2.0.0",
7280
+ "vfile": "^6.0.0"
7281
+ },
7282
+ "funding": {
7283
+ "type": "opencollective",
7284
+ "url": "https://opencollective.com/unified"
7285
+ }
7286
+ },
7287
+ "node_modules/unist-util-is": {
7288
+ "version": "6.0.1",
7289
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
7290
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
7291
+ "license": "MIT",
7292
+ "dependencies": {
7293
+ "@types/unist": "^3.0.0"
7294
+ },
7295
+ "funding": {
7296
+ "type": "opencollective",
7297
+ "url": "https://opencollective.com/unified"
7298
+ }
7299
+ },
7300
+ "node_modules/unist-util-position": {
7301
+ "version": "5.0.0",
7302
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
7303
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
7304
+ "license": "MIT",
7305
+ "dependencies": {
7306
+ "@types/unist": "^3.0.0"
7307
+ },
7308
+ "funding": {
7309
+ "type": "opencollective",
7310
+ "url": "https://opencollective.com/unified"
7311
+ }
7312
+ },
7313
+ "node_modules/unist-util-stringify-position": {
7314
+ "version": "4.0.0",
7315
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
7316
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
7317
+ "license": "MIT",
7318
+ "dependencies": {
7319
+ "@types/unist": "^3.0.0"
7320
+ },
7321
+ "funding": {
7322
+ "type": "opencollective",
7323
+ "url": "https://opencollective.com/unified"
7324
+ }
7325
+ },
7326
+ "node_modules/unist-util-visit": {
7327
+ "version": "5.1.0",
7328
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
7329
+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
7330
+ "license": "MIT",
7331
+ "dependencies": {
7332
+ "@types/unist": "^3.0.0",
7333
+ "unist-util-is": "^6.0.0",
7334
+ "unist-util-visit-parents": "^6.0.0"
7335
+ },
7336
+ "funding": {
7337
+ "type": "opencollective",
7338
+ "url": "https://opencollective.com/unified"
7339
+ }
7340
+ },
7341
+ "node_modules/unist-util-visit-parents": {
7342
+ "version": "6.0.2",
7343
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
7344
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
7345
+ "license": "MIT",
7346
+ "dependencies": {
7347
+ "@types/unist": "^3.0.0",
7348
+ "unist-util-is": "^6.0.0"
7349
+ },
7350
+ "funding": {
7351
+ "type": "opencollective",
7352
+ "url": "https://opencollective.com/unified"
7353
+ }
7354
+ },
7355
  "node_modules/update-browserslist-db": {
7356
  "version": "1.2.3",
7357
  "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
 
7400
  "dev": true,
7401
  "license": "MIT"
7402
  },
7403
+ "node_modules/vfile": {
7404
+ "version": "6.0.3",
7405
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
7406
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
7407
+ "license": "MIT",
7408
+ "dependencies": {
7409
+ "@types/unist": "^3.0.0",
7410
+ "vfile-message": "^4.0.0"
7411
+ },
7412
+ "funding": {
7413
+ "type": "opencollective",
7414
+ "url": "https://opencollective.com/unified"
7415
+ }
7416
+ },
7417
+ "node_modules/vfile-message": {
7418
+ "version": "4.0.3",
7419
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
7420
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
7421
+ "license": "MIT",
7422
+ "dependencies": {
7423
+ "@types/unist": "^3.0.0",
7424
+ "unist-util-stringify-position": "^4.0.0"
7425
+ },
7426
+ "funding": {
7427
+ "type": "opencollective",
7428
+ "url": "https://opencollective.com/unified"
7429
+ }
7430
+ },
7431
  "node_modules/victory-vendor": {
7432
  "version": "36.9.2",
7433
  "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
 
8274
  "funding": {
8275
  "url": "https://github.com/sponsors/sindresorhus"
8276
  }
8277
+ },
8278
+ "node_modules/zwitch": {
8279
+ "version": "2.0.4",
8280
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
8281
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
8282
+ "license": "MIT",
8283
+ "funding": {
8284
+ "type": "github",
8285
+ "url": "https://github.com/sponsors/wooorm"
8286
+ }
8287
  }
8288
  }
8289
  }
frontend/package.json CHANGED
@@ -16,8 +16,10 @@
16
  "lucide-react": "^0.460.0",
17
  "react": "^18.3.1",
18
  "react-dom": "^18.3.1",
 
19
  "react-router-dom": "^6.28.0",
20
- "recharts": "^2.13.3"
 
21
  },
22
  "devDependencies": {
23
  "@eslint/js": "^9.13.0",
 
16
  "lucide-react": "^0.460.0",
17
  "react": "^18.3.1",
18
  "react-dom": "^18.3.1",
19
+ "react-markdown": "^10.1.0",
20
  "react-router-dom": "^6.28.0",
21
+ "recharts": "^2.13.3",
22
+ "remark-gfm": "^4.0.1"
23
  },
24
  "devDependencies": {
25
  "@eslint/js": "^9.13.0",
frontend/src/App.tsx CHANGED
@@ -1,9 +1,10 @@
1
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
  import { BrowserRouter, Routes, Route, Link, useLocation } from 'react-router-dom';
3
- import { Home, Settings as SettingsIcon, Package, Activity, Zap, Brain, Github } from 'lucide-react';
4
  import Dashboard from './components/Dashboard';
5
  import Settings from './components/Settings';
6
  import PluginsPage from './components/PluginsPage';
 
7
  import { classNames } from './utils/helpers';
8
 
9
  const queryClient = new QueryClient({
@@ -21,39 +22,40 @@ function NavBar() {
21
  const navItems = [
22
  { path: '/', label: 'Dashboard', icon: Home },
23
  { path: '/plugins', label: 'Plugins', icon: Package },
 
24
  { path: '/settings', label: 'Settings', icon: SettingsIcon },
25
  ];
26
 
27
  return (
28
  <nav className="bg-gradient-to-r from-gray-900 via-gray-900 to-gray-800 border-b border-gray-700/50 shadow-lg">
29
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
30
- <div className="flex items-center justify-between h-16">
31
  {/* Logo */}
32
  <div className="flex items-center gap-3">
33
  <div className="relative">
34
- <div className="w-10 h-10 bg-gradient-to-br from-emerald-500 via-cyan-500 to-blue-500 rounded-xl flex items-center justify-center shadow-lg shadow-emerald-500/20">
35
- <Brain className="w-6 h-6 text-white" />
36
  </div>
37
- <div className="absolute -top-1 -right-1 w-3 h-3 bg-emerald-400 rounded-full animate-pulse" />
38
  </div>
39
  <div className="flex flex-col">
40
- <span className="text-xl font-bold bg-gradient-to-r from-emerald-400 via-cyan-400 to-blue-400 bg-clip-text text-transparent">
41
  ScrapeRL
42
  </span>
43
- <span className="text-[10px] text-gray-500 font-medium tracking-wider">
44
  RL-POWERED SCRAPING
45
  </span>
46
  </div>
47
  </div>
48
 
49
  {/* Navigation */}
50
- <div className="flex items-center gap-2">
51
  {navItems.map(({ path, label, icon: Icon }) => (
52
  <Link
53
  key={path}
54
  to={path}
55
  className={classNames(
56
- 'flex items-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition-all duration-200',
57
  location.pathname === path
58
  ? 'bg-gradient-to-r from-emerald-500/20 to-cyan-500/20 text-emerald-400 shadow-lg shadow-emerald-500/10 border border-emerald-500/30'
59
  : 'text-gray-400 hover:text-gray-200 hover:bg-gray-800/50'
@@ -90,38 +92,16 @@ function App() {
90
  return (
91
  <QueryClientProvider client={queryClient}>
92
  <BrowserRouter>
93
- <div className="min-h-screen bg-gradient-to-br from-gray-950 via-gray-900 to-gray-950 text-gray-100">
94
  <NavBar />
95
- <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
96
  <Routes>
97
  <Route path="/" element={<Dashboard />} />
98
- <Route path="/plugins" element={<PluginsPage />} />
 
99
  <Route path="/settings" element={<Settings />} />
100
  </Routes>
101
  </main>
102
-
103
- {/* Footer */}
104
- <footer className="border-t border-gray-800/50 bg-gray-900/30">
105
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
106
- <div className="flex flex-col sm:flex-row items-center justify-between gap-2 text-xs text-gray-500">
107
- <div className="flex items-center gap-2">
108
- <Activity className="w-3.5 h-3.5 text-emerald-500" />
109
- <span>ScrapeRL v0.1.0 • Reinforcement Learning Web Scraping</span>
110
- </div>
111
- <div className="flex items-center gap-4">
112
- <span>Built with FastAPI + React</span>
113
- <a
114
- href="https://huggingface.co/spaces/NeerajCodz/scrapeRL"
115
- target="_blank"
116
- rel="noopener noreferrer"
117
- className="text-cyan-500 hover:text-cyan-400 transition-colors"
118
- >
119
- 🤗 HuggingFace
120
- </a>
121
- </div>
122
- </div>
123
- </div>
124
- </footer>
125
  </div>
126
  </BrowserRouter>
127
  </QueryClientProvider>
 
1
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
  import { BrowserRouter, Routes, Route, Link, useLocation } from 'react-router-dom';
3
+ import { Home, Settings as SettingsIcon, Package, Zap, Brain, Github, Book } from 'lucide-react';
4
  import Dashboard from './components/Dashboard';
5
  import Settings from './components/Settings';
6
  import PluginsPage from './components/PluginsPage';
7
+ import DocsPage from './components/DocsPage';
8
  import { classNames } from './utils/helpers';
9
 
10
  const queryClient = new QueryClient({
 
22
  const navItems = [
23
  { path: '/', label: 'Dashboard', icon: Home },
24
  { path: '/plugins', label: 'Plugins', icon: Package },
25
+ { path: '/docs', label: 'Docs', icon: Book },
26
  { path: '/settings', label: 'Settings', icon: SettingsIcon },
27
  ];
28
 
29
  return (
30
  <nav className="bg-gradient-to-r from-gray-900 via-gray-900 to-gray-800 border-b border-gray-700/50 shadow-lg">
31
+ <div className="px-4 sm:px-6 lg:px-8">
32
+ <div className="flex items-center justify-between h-14">
33
  {/* Logo */}
34
  <div className="flex items-center gap-3">
35
  <div className="relative">
36
+ <div className="w-9 h-9 bg-gradient-to-br from-emerald-500 via-cyan-500 to-blue-500 rounded-lg flex items-center justify-center shadow-lg shadow-emerald-500/20">
37
+ <Brain className="w-5 h-5 text-white" />
38
  </div>
39
+ <div className="absolute -top-0.5 -right-0.5 w-2.5 h-2.5 bg-emerald-400 rounded-full animate-pulse" />
40
  </div>
41
  <div className="flex flex-col">
42
+ <span className="text-lg font-bold bg-gradient-to-r from-emerald-400 via-cyan-400 to-blue-400 bg-clip-text text-transparent">
43
  ScrapeRL
44
  </span>
45
+ <span className="text-[9px] text-gray-500 font-medium tracking-wider -mt-0.5">
46
  RL-POWERED SCRAPING
47
  </span>
48
  </div>
49
  </div>
50
 
51
  {/* Navigation */}
52
+ <div className="flex items-center gap-1">
53
  {navItems.map(({ path, label, icon: Icon }) => (
54
  <Link
55
  key={path}
56
  to={path}
57
  className={classNames(
58
+ 'flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-200',
59
  location.pathname === path
60
  ? 'bg-gradient-to-r from-emerald-500/20 to-cyan-500/20 text-emerald-400 shadow-lg shadow-emerald-500/10 border border-emerald-500/30'
61
  : 'text-gray-400 hover:text-gray-200 hover:bg-gray-800/50'
 
92
  return (
93
  <QueryClientProvider client={queryClient}>
94
  <BrowserRouter>
95
+ <div className="min-h-screen bg-gradient-to-br from-gray-950 via-gray-900 to-gray-950 text-gray-100 flex flex-col">
96
  <NavBar />
97
+ <main className="flex-1">
98
  <Routes>
99
  <Route path="/" element={<Dashboard />} />
100
+ <Route path="/plugins" element={<PluginsPage className="p-6" />} />
101
+ <Route path="/docs" element={<DocsPage />} />
102
  <Route path="/settings" element={<Settings />} />
103
  </Routes>
104
  </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  </div>
106
  </BrowserRouter>
107
  </QueryClientProvider>
frontend/src/components/Dashboard.tsx CHANGED
@@ -1,8 +1,8 @@
1
- import React from 'react';
 
2
  import {
3
  Activity,
4
  Zap,
5
- Brain,
6
  Target,
7
  Clock,
8
  TrendingUp,
@@ -10,181 +10,907 @@ import {
10
  Cpu,
11
  Globe,
12
  Play,
13
- RotateCcw,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  } from 'lucide-react';
15
- import { EpisodePanel } from './EpisodePanel';
16
- import { AgentView } from './AgentView';
17
- import { MemoryPanel } from './MemoryPanel';
18
- import { ToolRegistry } from './ToolRegistry';
19
- import { RewardChart } from './RewardChart';
20
- import { ObservationView } from './ObservationView';
21
- import { ActionPanel } from './ActionPanel';
22
- import { useCurrentEpisode } from '@/hooks/useEpisode';
23
-
24
- interface StatCardProps {
25
- icon: React.ElementType;
26
- label: string;
27
- value: string | number;
28
- change?: string;
29
- color: 'emerald' | 'cyan' | 'purple' | 'amber';
30
  }
31
 
32
- const StatCard: React.FC<StatCardProps> = ({ icon: Icon, label, value, change, color }) => {
33
- const colorClasses = {
34
- emerald: 'from-emerald-500/20 to-emerald-600/10 border-emerald-500/30 text-emerald-400',
35
- cyan: 'from-cyan-500/20 to-cyan-600/10 border-cyan-500/30 text-cyan-400',
36
- purple: 'from-purple-500/20 to-purple-600/10 border-purple-500/30 text-purple-400',
37
- amber: 'from-amber-500/20 to-amber-600/10 border-amber-500/30 text-amber-400',
38
- };
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  return (
41
- <div className={`bg-gradient-to-br ${colorClasses[color]} border rounded-xl p-4 backdrop-blur-sm`}>
42
- <div className="flex items-center justify-between">
43
- <div className={`p-2 rounded-lg bg-${color}-500/20`}>
44
- <Icon className={`w-5 h-5 ${colorClasses[color].split(' ').pop()}`} />
 
 
 
45
  </div>
46
- {change && (
47
- <span className="text-xs text-emerald-400 flex items-center gap-1">
48
- <TrendingUp className="w-3 h-3" />
49
- {change}
50
- </span>
51
- )}
52
  </div>
53
- <div className="mt-3">
54
- <p className="text-2xl font-bold text-white">{value}</p>
55
- <p className="text-xs text-gray-400 mt-1">{label}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  </div>
57
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  );
59
  };
60
 
 
61
  export const Dashboard: React.FC = () => {
62
- const { data: episode } = useCurrentEpisode();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  return (
65
- <div className="space-y-6">
66
- {/* Header Section */}
67
- <div className="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
68
- <div>
69
- <h1 className="text-2xl font-bold text-white flex items-center gap-3">
70
- <div className="p-2 bg-gradient-to-br from-emerald-500/20 to-cyan-500/20 rounded-lg">
71
- <Activity className="w-6 h-6 text-emerald-400" />
72
- </div>
73
- Dashboard
74
- </h1>
75
- <p className="text-gray-400 mt-1">Monitor your RL scraping agents in real-time</p>
76
- </div>
77
-
78
- <div className="flex items-center gap-3">
79
- <button className="flex items-center gap-2 px-4 py-2 bg-emerald-500 hover:bg-emerald-600 text-white rounded-lg font-medium transition-colors shadow-lg shadow-emerald-500/20">
80
- <Play className="w-4 h-4" />
81
- Start Episode
82
- </button>
83
- <button className="flex items-center gap-2 px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg font-medium transition-colors">
84
- <RotateCcw className="w-4 h-4" />
85
- Reset
86
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  </div>
88
  </div>
89
 
90
- {/* Stats Grid */}
91
- <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
92
- <StatCard
93
- icon={Zap}
94
- label="Total Episodes"
95
- value={episode?.id ? '1' : '0'}
96
- color="emerald"
97
- />
98
- <StatCard
99
- icon={Target}
100
- label="Current Step"
101
- value={episode?.currentStep || 0}
102
- color="cyan"
103
- />
104
- <StatCard
105
- icon={TrendingUp}
106
- label="Total Reward"
107
- value={episode?.totalReward?.toFixed(2) || '0.00'}
108
- change="+12%"
109
- color="purple"
110
- />
111
- <StatCard
112
- icon={Clock}
113
- label="Avg Time/Step"
114
- value="1.2s"
115
- color="amber"
116
- />
117
- </div>
118
 
119
- {/* Main Content Grid */}
120
- <div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
121
- {/* Left Column - Episode & Agents */}
122
- <div className="lg:col-span-3 space-y-6">
123
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-4">
124
- <h3 className="text-sm font-semibold text-white flex items-center gap-2 mb-4">
125
- <Brain className="w-4 h-4 text-emerald-400" />
126
- Episode Status
127
- </h3>
128
- <EpisodePanel />
129
- </div>
130
-
131
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-4">
132
- <h3 className="text-sm font-semibold text-white flex items-center gap-2 mb-4">
133
- <Cpu className="w-4 h-4 text-cyan-400" />
134
- Active Agents
135
- </h3>
136
- <AgentView />
137
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  </div>
139
 
140
- {/* Center Column - Observation & Charts */}
141
- <div className="lg:col-span-6 space-y-6">
142
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-4">
143
- <h3 className="text-sm font-semibold text-white flex items-center gap-2 mb-4">
144
- <Globe className="w-4 h-4 text-purple-400" />
145
- Current Observation
146
- </h3>
147
- <ObservationView />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  </div>
149
-
150
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
151
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-4">
152
- <h3 className="text-sm font-semibold text-white flex items-center gap-2 mb-4">
153
- <TrendingUp className="w-4 h-4 text-emerald-400" />
154
- Reward History
155
- </h3>
156
- <RewardChart />
157
- </div>
158
-
159
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-4">
160
- <h3 className="text-sm font-semibold text-white flex items-center gap-2 mb-4">
161
- <Zap className="w-4 h-4 text-amber-400" />
162
- Actions
163
- </h3>
164
- <ActionPanel />
 
 
 
 
 
 
 
 
165
  </div>
166
  </div>
167
  </div>
168
 
169
- {/* Right Column - Memory & Tools */}
170
- <div className="lg:col-span-3 space-y-6">
171
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-4">
172
- <h3 className="text-sm font-semibold text-white flex items-center gap-2 mb-4">
 
173
  <Database className="w-4 h-4 text-pink-400" />
174
- Memory Layers
175
- </h3>
176
- <MemoryPanel />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  </div>
178
-
179
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-4">
180
- <h3 className="text-sm font-semibold text-white flex items-center gap-2 mb-4">
181
- <Cpu className="w-4 h-4 text-blue-400" />
182
- Tool Registry
183
- </h3>
184
- <ToolRegistry />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  </div>
186
  </div>
187
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  </div>
189
  );
190
  };
 
1
+ import React, { useState } from 'react';
2
+ import { useQuery } from '@tanstack/react-query';
3
  import {
4
  Activity,
5
  Zap,
 
6
  Target,
7
  Clock,
8
  TrendingUp,
 
10
  Cpu,
11
  Globe,
12
  Play,
13
+ Pause,
14
+ ChevronDown,
15
+ ChevronRight,
16
+ MoreHorizontal,
17
+ Terminal,
18
+ Settings,
19
+ Wrench,
20
+ Plug,
21
+ Eye,
22
+ Bot,
23
+ X,
24
+ Check,
25
+ Layers,
26
+ FileText,
27
+ List,
28
  } from 'lucide-react';
29
+ import { Badge } from '@/components/ui/Badge';
30
+ import { classNames } from '@/utils/helpers';
31
+ import { apiClient } from '@/api/client';
32
+
33
+ // Types
34
+ interface TaskInput {
35
+ url: string;
36
+ instruction: string;
37
+ taskType: 'low' | 'medium' | 'high';
38
+ selectedModel: string;
39
+ selectedAgents: string[];
40
+ enabledPlugins: string[];
 
 
 
41
  }
42
 
43
+ interface LogEntry {
44
+ id: string;
45
+ timestamp: string;
46
+ level: 'info' | 'warn' | 'error' | 'debug';
47
+ message: string;
48
+ source?: string;
49
+ }
50
 
51
+ interface EpisodeStats {
52
+ total: number;
53
+ min: number;
54
+ max: number;
55
+ avg: number;
56
+ }
57
+
58
+ interface AgentOption {
59
+ id: string;
60
+ name: string;
61
+ description: string;
62
+ active?: boolean;
63
+ }
64
+
65
+ interface ModelOption {
66
+ provider: string;
67
+ model: string;
68
+ name: string;
69
+ }
70
+
71
+ // Popup Components
72
+ interface PopupProps {
73
+ title: string;
74
+ isOpen: boolean;
75
+ onClose: () => void;
76
+ children: React.ReactNode;
77
+ }
78
+
79
+ const Popup: React.FC<PopupProps> = ({ title, isOpen, onClose, children }) => {
80
+ if (!isOpen) return null;
81
  return (
82
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
83
+ <div className="bg-gray-800 border border-gray-700 rounded-xl shadow-2xl w-full max-w-lg max-h-[80vh] overflow-hidden">
84
+ <div className="flex items-center justify-between px-4 py-3 border-b border-gray-700">
85
+ <h3 className="font-semibold text-white">{title}</h3>
86
+ <button onClick={onClose} className="p-1 text-gray-400 hover:text-white transition-colors">
87
+ <X className="w-5 h-5" />
88
+ </button>
89
  </div>
90
+ <div className="p-4 overflow-y-auto max-h-[60vh]">{children}</div>
 
 
 
 
 
91
  </div>
92
+ </div>
93
+ );
94
+ };
95
+
96
+ // Stats Popup
97
+ const StatsPopup: React.FC<{ isOpen: boolean; onClose: () => void; stats: EpisodeStats; title: string }> = ({
98
+ isOpen,
99
+ onClose,
100
+ stats,
101
+ title,
102
+ }) => (
103
+ <Popup title={title} isOpen={isOpen} onClose={onClose}>
104
+ <div className="grid grid-cols-2 gap-4">
105
+ <div className="p-4 bg-gray-900/50 rounded-lg text-center">
106
+ <p className="text-2xl font-bold text-emerald-400">{stats.total}</p>
107
+ <p className="text-xs text-gray-400">Total</p>
108
+ </div>
109
+ <div className="p-4 bg-gray-900/50 rounded-lg text-center">
110
+ <p className="text-2xl font-bold text-cyan-400">{stats.avg.toFixed(2)}</p>
111
+ <p className="text-xs text-gray-400">Average</p>
112
+ </div>
113
+ <div className="p-4 bg-gray-900/50 rounded-lg text-center">
114
+ <p className="text-2xl font-bold text-amber-400">{stats.min.toFixed(2)}</p>
115
+ <p className="text-xs text-gray-400">Minimum</p>
116
+ </div>
117
+ <div className="p-4 bg-gray-900/50 rounded-lg text-center">
118
+ <p className="text-2xl font-bold text-purple-400">{stats.max.toFixed(2)}</p>
119
+ <p className="text-xs text-gray-400">Maximum</p>
120
  </div>
121
  </div>
122
+ </Popup>
123
+ );
124
+
125
+ // Accordion Component
126
+ interface AccordionProps {
127
+ title: string;
128
+ icon: React.ElementType;
129
+ badge?: string | number;
130
+ color: string;
131
+ children: React.ReactNode;
132
+ defaultOpen?: boolean;
133
+ }
134
+
135
+ const Accordion: React.FC<AccordionProps> = ({ title, icon: Icon, badge, color, children, defaultOpen = false }) => {
136
+ const [isOpen, setIsOpen] = useState(defaultOpen);
137
+
138
+ return (
139
+ <div className="border border-gray-700/50 rounded-lg overflow-hidden">
140
+ <button
141
+ onClick={() => setIsOpen(!isOpen)}
142
+ className="w-full flex items-center justify-between px-3 py-2.5 bg-gray-800/50 hover:bg-gray-800 transition-colors"
143
+ >
144
+ <div className="flex items-center gap-2">
145
+ <Icon className={`w-4 h-4 ${color}`} />
146
+ <span className="text-sm font-medium text-white">{title}</span>
147
+ {badge !== undefined && (
148
+ <Badge variant="neutral" size="sm">{badge}</Badge>
149
+ )}
150
+ </div>
151
+ {isOpen ? <ChevronDown className="w-4 h-4 text-gray-400" /> : <ChevronRight className="w-4 h-4 text-gray-400" />}
152
+ </button>
153
+ {isOpen && <div className="p-3 bg-gray-900/30 border-t border-gray-700/50">{children}</div>}
154
+ </div>
155
  );
156
  };
157
 
158
+ // Main Dashboard Component
159
  export const Dashboard: React.FC = () => {
160
+ // State
161
+ const [taskInput, setTaskInput] = useState<TaskInput>({
162
+ url: '',
163
+ instruction: '',
164
+ taskType: 'medium',
165
+ selectedModel: 'groq/gpt-oss-120b',
166
+ selectedAgents: ['coordinator', 'scraper'],
167
+ enabledPlugins: ['browser-use', 'firecrawl'],
168
+ });
169
+ const [logs, setLogs] = useState<LogEntry[]>([
170
+ { id: '1', timestamp: new Date().toISOString(), level: 'info', message: 'System initialized', source: 'system' },
171
+ { id: '2', timestamp: new Date().toISOString(), level: 'info', message: 'Ready to start episode', source: 'coordinator' },
172
+ ]);
173
+ const [isRunning, setIsRunning] = useState(false);
174
+ const [showModelPopup, setShowModelPopup] = useState(false);
175
+ const [showAgentPopup, setShowAgentPopup] = useState(false);
176
+ const [showPluginPopup, setShowPluginPopup] = useState(false);
177
+ const [showTaskTypePopup, setShowTaskTypePopup] = useState(false);
178
+ const [showStatsPopup, setShowStatsPopup] = useState<'episodes' | 'steps' | 'reward' | null>(null);
179
+
180
+ // API Queries
181
+ const { data: health } = useQuery({
182
+ queryKey: ['health'],
183
+ queryFn: () => apiClient.healthCheck(),
184
+ refetchInterval: 5000,
185
+ });
186
+
187
+ const { data: agentsData } = useQuery({
188
+ queryKey: ['agents'],
189
+ queryFn: async () => {
190
+ const res = await fetch('/api/agents/');
191
+ return res.json();
192
+ },
193
+ });
194
+
195
+ useQuery({
196
+ queryKey: ['plugins'],
197
+ queryFn: async () => {
198
+ const res = await fetch('/api/plugins/');
199
+ return res.json();
200
+ },
201
+ });
202
+
203
+ const { data: memoryData } = useQuery({
204
+ queryKey: ['memory-stats'],
205
+ queryFn: async () => {
206
+ const res = await fetch('/api/memory/stats/overview');
207
+ return res.json();
208
+ },
209
+ refetchInterval: 3000,
210
+ });
211
+
212
+ const { data: settingsData } = useQuery({
213
+ queryKey: ['client-settings'],
214
+ queryFn: async () => {
215
+ const res = await fetch('/api/settings/');
216
+ return res.json();
217
+ },
218
+ });
219
+
220
+ // Episode Stats (mock data for now)
221
+ const episodeStats: EpisodeStats = { total: 12, min: 3, max: 47, avg: 18.5 };
222
+ const stepStats: EpisodeStats = { total: 156, min: 5, max: 89, avg: 23.4 };
223
+ const rewardStats: EpisodeStats = { total: 847.5, min: -12.3, max: 98.7, avg: 70.6 };
224
+
225
+ // Available options
226
+ const availableModels: ModelOption[] = settingsData?.available_models ?? [
227
+ { provider: 'groq', model: 'gpt-oss-120b', name: 'GPT-OSS 120B (Groq)' },
228
+ { provider: 'google', model: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash' },
229
+ { provider: 'openai', model: 'gpt-4-turbo', name: 'GPT-4 Turbo' },
230
+ { provider: 'anthropic', model: 'claude-3-opus', name: 'Claude 3 Opus' },
231
+ ];
232
+
233
+ const availableAgents: AgentOption[] = agentsData?.agents ?? [
234
+ { id: 'coordinator', name: 'Coordinator', description: 'Orchestrates all agents', active: true },
235
+ { id: 'scraper', name: 'Scraper', description: 'Extracts data from pages', active: true },
236
+ { id: 'navigator', name: 'Navigator', description: 'Handles page navigation', active: false },
237
+ { id: 'analyzer', name: 'Analyzer', description: 'Analyzes extracted data', active: false },
238
+ { id: 'validator', name: 'Validator', description: 'Validates data quality', active: false },
239
+ ];
240
+
241
+ const pluginCategories = {
242
+ mcps: [
243
+ { id: 'browser-use', name: 'Browser Use', enabled: true, status: 'active' },
244
+ { id: 'puppeteer-mcp', name: 'Puppeteer MCP', enabled: false, status: 'idle' },
245
+ { id: 'playwright-mcp', name: 'Playwright MCP', enabled: false, status: 'idle' },
246
+ ],
247
+ skills: [
248
+ { id: 'web-scraping', name: 'Web Scraping', enabled: true, status: 'active' },
249
+ { id: 'data-extraction', name: 'Data Extraction', enabled: true, status: 'active' },
250
+ { id: 'form-filling', name: 'Form Filling', enabled: false, status: 'idle' },
251
+ ],
252
+ apis: [
253
+ { id: 'firecrawl', name: 'Firecrawl', enabled: true, status: 'active' },
254
+ { id: 'jina-reader', name: 'Jina Reader', enabled: false, status: 'idle' },
255
+ { id: 'serper', name: 'Serper API', enabled: false, status: 'idle' },
256
+ ],
257
+ vision: [
258
+ { id: 'gpt4-vision', name: 'GPT-4 Vision', enabled: false, status: 'idle' },
259
+ { id: 'gemini-vision', name: 'Gemini Vision', enabled: false, status: 'idle' },
260
+ { id: 'claude-vision', name: 'Claude Vision', enabled: false, status: 'idle' },
261
+ ],
262
+ };
263
+
264
+ const taskTypes = [
265
+ { id: 'low', name: 'Low Complexity', description: 'Simple single-page scraping', color: 'emerald' },
266
+ { id: 'medium', name: 'Medium Complexity', description: 'Multi-page with navigation', color: 'amber' },
267
+ { id: 'high', name: 'High Complexity', description: 'Complex interactive tasks', color: 'red' },
268
+ ];
269
+
270
+ const handleStart = () => {
271
+ setIsRunning(true);
272
+ const newLog: LogEntry = {
273
+ id: Date.now().toString(),
274
+ timestamp: new Date().toISOString(),
275
+ level: 'info',
276
+ message: `Starting episode with URL: ${taskInput.url}`,
277
+ source: 'coordinator',
278
+ };
279
+ setLogs((prev) => [...prev, newLog]);
280
+ };
281
+
282
+ const handleStop = () => {
283
+ setIsRunning(false);
284
+ const newLog: LogEntry = {
285
+ id: Date.now().toString(),
286
+ timestamp: new Date().toISOString(),
287
+ level: 'warn',
288
+ message: 'Episode stopped by user',
289
+ source: 'system',
290
+ };
291
+ setLogs((prev) => [...prev, newLog]);
292
+ };
293
+
294
+ const formatTime = (isoString: string) => {
295
+ return new Date(isoString).toLocaleTimeString('en-US', { hour12: false });
296
+ };
297
+
298
+ const getLogLevelColor = (level: LogEntry['level']) => {
299
+ const colors = {
300
+ info: 'text-cyan-400',
301
+ warn: 'text-amber-400',
302
+ error: 'text-red-400',
303
+ debug: 'text-gray-400',
304
+ };
305
+ return colors[level];
306
+ };
307
 
308
  return (
309
+ <div className="h-[calc(100vh-80px)] flex flex-col">
310
+ {/* Input Section */}
311
+ <div className="flex-shrink-0 p-4 bg-gray-800/50 border-b border-gray-700/50">
312
+ <div className="flex flex-wrap items-end gap-4">
313
+ {/* URL Input */}
314
+ <div className="flex-1 min-w-[300px]">
315
+ <label className="text-xs text-gray-400 mb-1 block">Target URL</label>
316
+ <input
317
+ type="url"
318
+ placeholder="https://example.com/page-to-scrape"
319
+ value={taskInput.url}
320
+ onChange={(e) => setTaskInput((p) => ({ ...p, url: e.target.value }))}
321
+ className="w-full px-3 py-2 bg-gray-900/50 border border-gray-700/50 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 focus:border-emerald-500/50"
322
+ />
323
+ </div>
324
+
325
+ {/* Instruction */}
326
+ <div className="flex-1 min-w-[300px]">
327
+ <label className="text-xs text-gray-400 mb-1 block">Instruction</label>
328
+ <input
329
+ type="text"
330
+ placeholder="Extract all product prices and names..."
331
+ value={taskInput.instruction}
332
+ onChange={(e) => setTaskInput((p) => ({ ...p, instruction: e.target.value }))}
333
+ className="w-full px-3 py-2 bg-gray-900/50 border border-gray-700/50 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 focus:border-emerald-500/50"
334
+ />
335
+ </div>
336
+
337
+ {/* Selection Buttons */}
338
+ <div className="flex items-center gap-2">
339
+ <button
340
+ onClick={() => setShowModelPopup(true)}
341
+ className="px-3 py-2 bg-cyan-500/10 hover:bg-cyan-500/20 border border-cyan-500/30 text-cyan-400 rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
342
+ >
343
+ <Cpu className="w-4 h-4" />
344
+ Model
345
+ </button>
346
+ <button
347
+ onClick={() => setShowAgentPopup(true)}
348
+ className="px-3 py-2 bg-purple-500/10 hover:bg-purple-500/20 border border-purple-500/30 text-purple-400 rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
349
+ >
350
+ <Bot className="w-4 h-4" />
351
+ Agents
352
+ </button>
353
+ <button
354
+ onClick={() => setShowPluginPopup(true)}
355
+ className="px-3 py-2 bg-amber-500/10 hover:bg-amber-500/20 border border-amber-500/30 text-amber-400 rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
356
+ >
357
+ <Plug className="w-4 h-4" />
358
+ Plugins
359
+ </button>
360
+ <button
361
+ onClick={() => setShowTaskTypePopup(true)}
362
+ className={classNames(
363
+ 'px-3 py-2 border rounded-lg text-sm font-medium transition-colors flex items-center gap-2',
364
+ taskInput.taskType === 'low' && 'bg-emerald-500/10 border-emerald-500/30 text-emerald-400',
365
+ taskInput.taskType === 'medium' && 'bg-amber-500/10 border-amber-500/30 text-amber-400',
366
+ taskInput.taskType === 'high' && 'bg-red-500/10 border-red-500/30 text-red-400'
367
+ )}
368
+ >
369
+ <Target className="w-4 h-4" />
370
+ {taskInput.taskType.charAt(0).toUpperCase() + taskInput.taskType.slice(1)}
371
+ </button>
372
+ </div>
373
+
374
+ {/* Start/Stop Button */}
375
+ {isRunning ? (
376
+ <button
377
+ onClick={handleStop}
378
+ className="px-5 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg font-medium transition-colors flex items-center gap-2 shadow-lg shadow-red-500/20"
379
+ >
380
+ <Pause className="w-4 h-4" />
381
+ Stop
382
+ </button>
383
+ ) : (
384
+ <button
385
+ onClick={handleStart}
386
+ disabled={!taskInput.url}
387
+ className="px-5 py-2 bg-emerald-500 hover:bg-emerald-600 disabled:bg-gray-600 disabled:cursor-not-allowed text-white rounded-lg font-medium transition-colors flex items-center gap-2 shadow-lg shadow-emerald-500/20"
388
+ >
389
+ <Play className="w-4 h-4" />
390
+ Start
391
+ </button>
392
+ )}
393
  </div>
394
  </div>
395
 
396
+ {/* Main Content - 3 Column Layout */}
397
+ <div className="flex-1 flex overflow-hidden">
398
+ {/* Left Sidebar - Accordions */}
399
+ <div className="w-64 flex-shrink-0 bg-gray-800/30 border-r border-gray-700/50 overflow-y-auto p-3 space-y-2">
400
+ {/* Agents Accordion */}
401
+ <Accordion title="Agents" icon={Bot} badge={taskInput.selectedAgents.length} color="text-purple-400" defaultOpen>
402
+ <div className="space-y-2">
403
+ {availableAgents.map((agent) => {
404
+ const isActive = taskInput.selectedAgents.includes(agent.id);
405
+ return (
406
+ <div
407
+ key={agent.id}
408
+ className={classNames(
409
+ 'flex items-center justify-between p-2 rounded-lg transition-colors',
410
+ isActive ? 'bg-purple-500/10 border border-purple-500/30' : 'bg-gray-800/50'
411
+ )}
412
+ >
413
+ <div className="flex items-center gap-2">
414
+ <div className={classNames('w-2 h-2 rounded-full', isActive ? 'bg-emerald-400' : 'bg-gray-500')} />
415
+ <span className="text-xs text-white">{agent.name}</span>
416
+ </div>
417
+ {isActive && <Clock className="w-3 h-3 text-gray-400" />}
418
+ </div>
419
+ );
420
+ })}
421
+ </div>
422
+ </Accordion>
 
423
 
424
+ {/* MCPs Accordion */}
425
+ <Accordion title="MCPs" icon={Wrench} badge={pluginCategories.mcps.filter(p => p.enabled).length} color="text-amber-400">
426
+ <div className="space-y-2">
427
+ {pluginCategories.mcps.map((plugin) => (
428
+ <div
429
+ key={plugin.id}
430
+ className={classNames(
431
+ 'flex items-center justify-between p-2 rounded-lg',
432
+ plugin.enabled ? 'bg-amber-500/10 border border-amber-500/30' : 'bg-gray-800/50'
433
+ )}
434
+ >
435
+ <span className="text-xs text-white">{plugin.name}</span>
436
+ <Badge variant={plugin.status === 'active' ? 'success' : 'neutral'} size="sm">
437
+ {plugin.status}
438
+ </Badge>
439
+ </div>
440
+ ))}
441
+ </div>
442
+ </Accordion>
443
+
444
+ {/* Skills Accordion */}
445
+ <Accordion title="Skills" icon={Zap} badge={pluginCategories.skills.filter(p => p.enabled).length} color="text-cyan-400">
446
+ <div className="space-y-2">
447
+ {pluginCategories.skills.map((plugin) => (
448
+ <div
449
+ key={plugin.id}
450
+ className={classNames(
451
+ 'flex items-center justify-between p-2 rounded-lg',
452
+ plugin.enabled ? 'bg-cyan-500/10 border border-cyan-500/30' : 'bg-gray-800/50'
453
+ )}
454
+ >
455
+ <span className="text-xs text-white">{plugin.name}</span>
456
+ <Badge variant={plugin.status === 'active' ? 'success' : 'neutral'} size="sm">
457
+ {plugin.status}
458
+ </Badge>
459
+ </div>
460
+ ))}
461
+ </div>
462
+ </Accordion>
463
+
464
+ {/* APIs Accordion */}
465
+ <Accordion title="APIs" icon={Plug} badge={pluginCategories.apis.filter(p => p.enabled).length} color="text-emerald-400">
466
+ <div className="space-y-2">
467
+ {pluginCategories.apis.map((plugin) => (
468
+ <div
469
+ key={plugin.id}
470
+ className={classNames(
471
+ 'flex items-center justify-between p-2 rounded-lg',
472
+ plugin.enabled ? 'bg-emerald-500/10 border border-emerald-500/30' : 'bg-gray-800/50'
473
+ )}
474
+ >
475
+ <span className="text-xs text-white">{plugin.name}</span>
476
+ <Badge variant={plugin.status === 'active' ? 'success' : 'neutral'} size="sm">
477
+ {plugin.status}
478
+ </Badge>
479
+ </div>
480
+ ))}
481
+ </div>
482
+ </Accordion>
483
+
484
+ {/* Vision Accordion */}
485
+ <Accordion title="Vision" icon={Eye} badge={pluginCategories.vision.filter(p => p.enabled).length} color="text-pink-400">
486
+ <div className="space-y-2">
487
+ {pluginCategories.vision.map((plugin) => (
488
+ <div
489
+ key={plugin.id}
490
+ className={classNames(
491
+ 'flex items-center justify-between p-2 rounded-lg',
492
+ plugin.enabled ? 'bg-pink-500/10 border border-pink-500/30' : 'bg-gray-800/50'
493
+ )}
494
+ >
495
+ <span className="text-xs text-white">{plugin.name}</span>
496
+ <Badge variant={plugin.status === 'active' ? 'success' : 'neutral'} size="sm">
497
+ {plugin.status}
498
+ </Badge>
499
+ </div>
500
+ ))}
501
+ </div>
502
+ </Accordion>
503
+
504
+ {/* Tools Accordion */}
505
+ <Accordion title="Tools" icon={Wrench} color="text-blue-400">
506
+ <div className="space-y-2 text-xs text-gray-400">
507
+ <div className="flex items-center justify-between p-2 bg-gray-800/50 rounded-lg">
508
+ <span>HTTP Client</span>
509
+ <Badge variant="success" size="sm">ready</Badge>
510
+ </div>
511
+ <div className="flex items-center justify-between p-2 bg-gray-800/50 rounded-lg">
512
+ <span>HTML Parser</span>
513
+ <Badge variant="success" size="sm">ready</Badge>
514
+ </div>
515
+ <div className="flex items-center justify-between p-2 bg-gray-800/50 rounded-lg">
516
+ <span>JSON Extractor</span>
517
+ <Badge variant="success" size="sm">ready</Badge>
518
+ </div>
519
+ </div>
520
+ </Accordion>
521
  </div>
522
 
523
+ {/* Center Content */}
524
+ <div className="flex-1 flex flex-col overflow-hidden">
525
+ {/* Stats Header */}
526
+ <div className="flex-shrink-0 p-3 bg-gray-800/30 border-b border-gray-700/50">
527
+ <div className="flex items-center justify-between">
528
+ <div className="flex items-center gap-4">
529
+ {/* Episodes */}
530
+ <div className="flex items-center gap-2">
531
+ <div className="p-1.5 bg-emerald-500/20 rounded">
532
+ <Layers className="w-4 h-4 text-emerald-400" />
533
+ </div>
534
+ <div>
535
+ <p className="text-lg font-bold text-white">{episodeStats.total}</p>
536
+ <p className="text-[10px] text-gray-500">Episodes</p>
537
+ </div>
538
+ <button
539
+ onClick={() => setShowStatsPopup('episodes')}
540
+ className="p-1 text-gray-500 hover:text-gray-300"
541
+ >
542
+ <MoreHorizontal className="w-4 h-4" />
543
+ </button>
544
+ </div>
545
+
546
+ {/* Steps */}
547
+ <div className="flex items-center gap-2">
548
+ <div className="p-1.5 bg-cyan-500/20 rounded">
549
+ <Target className="w-4 h-4 text-cyan-400" />
550
+ </div>
551
+ <div>
552
+ <p className="text-lg font-bold text-white">{stepStats.total}</p>
553
+ <p className="text-[10px] text-gray-500">Steps</p>
554
+ </div>
555
+ <button
556
+ onClick={() => setShowStatsPopup('steps')}
557
+ className="p-1 text-gray-500 hover:text-gray-300"
558
+ >
559
+ <MoreHorizontal className="w-4 h-4" />
560
+ </button>
561
+ </div>
562
+
563
+ {/* Reward */}
564
+ <div className="flex items-center gap-2">
565
+ <div className="p-1.5 bg-purple-500/20 rounded">
566
+ <TrendingUp className="w-4 h-4 text-purple-400" />
567
+ </div>
568
+ <div>
569
+ <p className="text-lg font-bold text-white">{rewardStats.avg.toFixed(1)}</p>
570
+ <p className="text-[10px] text-gray-500">Avg Reward</p>
571
+ </div>
572
+ <button
573
+ onClick={() => setShowStatsPopup('reward')}
574
+ className="p-1 text-gray-500 hover:text-gray-300"
575
+ >
576
+ <MoreHorizontal className="w-4 h-4" />
577
+ </button>
578
+ </div>
579
+ </div>
580
+
581
+ {/* Time & Status */}
582
+ <div className="flex items-center gap-4">
583
+ <div className="text-right">
584
+ <p className="text-sm font-mono text-white">{new Date().toLocaleTimeString()}</p>
585
+ <p className="text-[10px] text-gray-500">Current Time</p>
586
+ </div>
587
+ <div className={classNames('px-3 py-1.5 rounded-lg flex items-center gap-2', isRunning ? 'bg-emerald-500/20' : 'bg-gray-700/50')}>
588
+ <div className={classNames('w-2 h-2 rounded-full', isRunning ? 'bg-emerald-400 animate-pulse' : 'bg-gray-500')} />
589
+ <span className={classNames('text-sm font-medium', isRunning ? 'text-emerald-400' : 'text-gray-400')}>
590
+ {isRunning ? 'Running' : 'Idle'}
591
+ </span>
592
+ </div>
593
+ </div>
594
+ </div>
595
+ </div>
596
+
597
+ {/* Main Visualization Area */}
598
+ <div className="flex-1 overflow-y-auto p-4">
599
+ <div className="h-full bg-gray-900/50 border border-gray-700/50 rounded-xl p-4">
600
+ {isRunning ? (
601
+ <div className="h-full flex flex-col">
602
+ {/* Current Action */}
603
+ <div className="flex-shrink-0 mb-4">
604
+ <div className="flex items-center gap-2 mb-2">
605
+ <Activity className="w-4 h-4 text-emerald-400 animate-pulse" />
606
+ <span className="text-sm font-medium text-white">Current Action</span>
607
+ </div>
608
+ <div className="p-3 bg-gray-800/50 rounded-lg">
609
+ <p className="text-sm text-gray-300">Navigating to: {taskInput.url}</p>
610
+ <p className="text-xs text-gray-500 mt-1">Agent: Scraper | Step: 1/10</p>
611
+ </div>
612
+ </div>
613
+
614
+ {/* Observation Preview */}
615
+ <div className="flex-1 overflow-auto">
616
+ <div className="flex items-center gap-2 mb-2">
617
+ <Globe className="w-4 h-4 text-cyan-400" />
618
+ <span className="text-sm font-medium text-white">Page Observation</span>
619
+ </div>
620
+ <div className="p-3 bg-gray-800/50 rounded-lg min-h-[200px]">
621
+ <pre className="text-xs text-gray-400 font-mono whitespace-pre-wrap">
622
+ {`{
623
+ "url": "${taskInput.url || 'N/A'}",
624
+ "title": "Loading...",
625
+ "elements": [],
626
+ "links": [],
627
+ "text_content": "..."
628
+ }`}
629
+ </pre>
630
+ </div>
631
+ </div>
632
+ </div>
633
+ ) : (
634
+ <div className="h-full flex flex-col items-center justify-center text-center">
635
+ <div className="w-16 h-16 bg-gray-800/50 rounded-full flex items-center justify-center mb-4">
636
+ <Play className="w-8 h-8 text-gray-500" />
637
+ </div>
638
+ <h3 className="text-lg font-medium text-gray-300 mb-2">Ready to Start</h3>
639
+ <p className="text-sm text-gray-500 max-w-md">
640
+ Enter a URL and instruction above, configure your agents and plugins, then click Start to begin scraping.
641
+ </p>
642
+ </div>
643
+ )}
644
+ </div>
645
  </div>
646
+
647
+ {/* Logs Terminal */}
648
+ <div className="flex-shrink-0 h-36 bg-gray-900 border-t border-gray-700/50">
649
+ <div className="flex items-center justify-between px-3 py-1.5 border-b border-gray-800">
650
+ <div className="flex items-center gap-2">
651
+ <Terminal className="w-4 h-4 text-gray-500" />
652
+ <span className="text-xs font-medium text-gray-400">Logs</span>
653
+ </div>
654
+ <button
655
+ onClick={() => setLogs([])}
656
+ className="text-xs text-gray-500 hover:text-gray-300"
657
+ >
658
+ Clear
659
+ </button>
660
+ </div>
661
+ <div className="h-[calc(100%-28px)] overflow-y-auto p-2 font-mono text-xs">
662
+ {logs.map((log) => (
663
+ <div key={log.id} className="flex items-start gap-2 py-0.5">
664
+ <span className="text-gray-600">[{formatTime(log.timestamp)}]</span>
665
+ <span className={getLogLevelColor(log.level)}>[{log.level.toUpperCase()}]</span>
666
+ {log.source && <span className="text-purple-400">[{log.source}]</span>}
667
+ <span className="text-gray-300">{log.message}</span>
668
+ </div>
669
+ ))}
670
  </div>
671
  </div>
672
  </div>
673
 
674
+ {/* Right Sidebar - Memory & Data */}
675
+ <div className="w-72 flex-shrink-0 bg-gray-800/30 border-l border-gray-700/50 overflow-y-auto p-3 space-y-3">
676
+ {/* Memory Stats */}
677
+ <div className="bg-gray-900/50 border border-gray-700/50 rounded-lg p-3">
678
+ <div className="flex items-center gap-2 mb-3">
679
  <Database className="w-4 h-4 text-pink-400" />
680
+ <span className="text-sm font-medium text-white">Memory</span>
681
+ </div>
682
+ <div className="grid grid-cols-2 gap-2">
683
+ <div className="p-2 bg-gray-800/50 rounded text-center">
684
+ <p className="text-lg font-bold text-emerald-400">{memoryData?.working?.count || 0}</p>
685
+ <p className="text-[10px] text-gray-500">Working</p>
686
+ </div>
687
+ <div className="p-2 bg-gray-800/50 rounded text-center">
688
+ <p className="text-lg font-bold text-cyan-400">{memoryData?.episodic?.count || 0}</p>
689
+ <p className="text-[10px] text-gray-500">Episodic</p>
690
+ </div>
691
+ <div className="p-2 bg-gray-800/50 rounded text-center">
692
+ <p className="text-lg font-bold text-purple-400">{memoryData?.semantic?.count || 0}</p>
693
+ <p className="text-[10px] text-gray-500">Semantic</p>
694
+ </div>
695
+ <div className="p-2 bg-gray-800/50 rounded text-center">
696
+ <p className="text-lg font-bold text-amber-400">{memoryData?.procedural?.count || 0}</p>
697
+ <p className="text-[10px] text-gray-500">Procedural</p>
698
+ </div>
699
+ </div>
700
  </div>
701
+
702
+ {/* Extracted Data */}
703
+ <div className="bg-gray-900/50 border border-gray-700/50 rounded-lg p-3">
704
+ <div className="flex items-center justify-between mb-3">
705
+ <div className="flex items-center gap-2">
706
+ <FileText className="w-4 h-4 text-cyan-400" />
707
+ <span className="text-sm font-medium text-white">Extracted Data</span>
708
+ </div>
709
+ <Badge variant="neutral" size="sm">0 items</Badge>
710
+ </div>
711
+ <div className="text-center py-6 text-gray-500 text-xs">
712
+ No data extracted yet.<br />Start an episode to begin.
713
+ </div>
714
+ </div>
715
+
716
+ {/* Recent Actions */}
717
+ <div className="bg-gray-900/50 border border-gray-700/50 rounded-lg p-3">
718
+ <div className="flex items-center gap-2 mb-3">
719
+ <List className="w-4 h-4 text-amber-400" />
720
+ <span className="text-sm font-medium text-white">Recent Actions</span>
721
+ </div>
722
+ <div className="space-y-2">
723
+ {isRunning ? (
724
+ <>
725
+ <div className="flex items-center gap-2 p-2 bg-emerald-500/10 rounded">
726
+ <Check className="w-3 h-3 text-emerald-400" />
727
+ <span className="text-xs text-gray-300">Navigate to URL</span>
728
+ </div>
729
+ <div className="flex items-center gap-2 p-2 bg-gray-800/50 rounded">
730
+ <Activity className="w-3 h-3 text-cyan-400 animate-pulse" />
731
+ <span className="text-xs text-gray-300">Loading page...</span>
732
+ </div>
733
+ </>
734
+ ) : (
735
+ <div className="text-center py-4 text-gray-500 text-xs">
736
+ No recent actions
737
+ </div>
738
+ )}
739
+ </div>
740
+ </div>
741
+
742
+ {/* System Info */}
743
+ <div className="bg-gray-900/50 border border-gray-700/50 rounded-lg p-3">
744
+ <div className="flex items-center gap-2 mb-3">
745
+ <Settings className="w-4 h-4 text-gray-400" />
746
+ <span className="text-sm font-medium text-white">System</span>
747
+ </div>
748
+ <div className="space-y-2 text-xs">
749
+ <div className="flex items-center justify-between">
750
+ <span className="text-gray-500">Status</span>
751
+ <Badge variant={health?.status === 'ok' ? 'success' : 'error'} size="sm">
752
+ {health?.status === 'ok' ? 'Online' : 'Offline'}
753
+ </Badge>
754
+ </div>
755
+ <div className="flex items-center justify-between">
756
+ <span className="text-gray-500">Model</span>
757
+ <span className="text-gray-300">{taskInput.selectedModel.split('/')[1]}</span>
758
+ </div>
759
+ <div className="flex items-center justify-between">
760
+ <span className="text-gray-500">Version</span>
761
+ <span className="text-gray-300">{health?.version || 'v0.1.0'}</span>
762
+ </div>
763
+ </div>
764
  </div>
765
  </div>
766
  </div>
767
+
768
+ {/* Popups */}
769
+ {/* Model Selection Popup */}
770
+ <Popup title="Select Model" isOpen={showModelPopup} onClose={() => setShowModelPopup(false)}>
771
+ <div className="space-y-2">
772
+ {availableModels.map((model: { provider: string; model: string; name: string }) => (
773
+ <button
774
+ key={`${model.provider}/${model.model}`}
775
+ onClick={() => {
776
+ setTaskInput((p) => ({ ...p, selectedModel: `${model.provider}/${model.model}` }));
777
+ setShowModelPopup(false);
778
+ }}
779
+ className={classNames(
780
+ 'w-full flex items-center justify-between p-3 rounded-lg transition-colors text-left',
781
+ taskInput.selectedModel === `${model.provider}/${model.model}`
782
+ ? 'bg-emerald-500/20 border border-emerald-500/30'
783
+ : 'bg-gray-900/50 hover:bg-gray-800'
784
+ )}
785
+ >
786
+ <div>
787
+ <p className="text-sm font-medium text-white">{model.name}</p>
788
+ <p className="text-xs text-gray-500">{model.provider}</p>
789
+ </div>
790
+ {taskInput.selectedModel === `${model.provider}/${model.model}` && (
791
+ <Check className="w-5 h-5 text-emerald-400" />
792
+ )}
793
+ </button>
794
+ ))}
795
+ </div>
796
+ </Popup>
797
+
798
+ {/* Agent Selection Popup */}
799
+ <Popup title="Select Agents" isOpen={showAgentPopup} onClose={() => setShowAgentPopup(false)}>
800
+ <div className="space-y-2">
801
+ {availableAgents.map((agent: { id: string; name: string; description: string }) => {
802
+ const isSelected = taskInput.selectedAgents.includes(agent.id);
803
+ return (
804
+ <button
805
+ key={agent.id}
806
+ onClick={() => {
807
+ setTaskInput((p) => ({
808
+ ...p,
809
+ selectedAgents: isSelected
810
+ ? p.selectedAgents.filter((a) => a !== agent.id)
811
+ : [...p.selectedAgents, agent.id],
812
+ }));
813
+ }}
814
+ className={classNames(
815
+ 'w-full flex items-center justify-between p-3 rounded-lg transition-colors text-left',
816
+ isSelected ? 'bg-purple-500/20 border border-purple-500/30' : 'bg-gray-900/50 hover:bg-gray-800'
817
+ )}
818
+ >
819
+ <div>
820
+ <p className="text-sm font-medium text-white">{agent.name}</p>
821
+ <p className="text-xs text-gray-500">{agent.description}</p>
822
+ </div>
823
+ {isSelected && <Check className="w-5 h-5 text-purple-400" />}
824
+ </button>
825
+ );
826
+ })}
827
+ </div>
828
+ </Popup>
829
+
830
+ {/* Plugin Selection Popup */}
831
+ <Popup title="Enable Plugins" isOpen={showPluginPopup} onClose={() => setShowPluginPopup(false)}>
832
+ <div className="space-y-4">
833
+ {Object.entries(pluginCategories).map(([category, plugins]) => (
834
+ <div key={category}>
835
+ <h4 className="text-xs font-medium text-gray-400 uppercase mb-2">{category}</h4>
836
+ <div className="space-y-1">
837
+ {plugins.map((plugin) => {
838
+ const isEnabled = taskInput.enabledPlugins.includes(plugin.id);
839
+ return (
840
+ <button
841
+ key={plugin.id}
842
+ onClick={() => {
843
+ setTaskInput((p) => ({
844
+ ...p,
845
+ enabledPlugins: isEnabled
846
+ ? p.enabledPlugins.filter((a) => a !== plugin.id)
847
+ : [...p.enabledPlugins, plugin.id],
848
+ }));
849
+ }}
850
+ className={classNames(
851
+ 'w-full flex items-center justify-between p-2 rounded-lg transition-colors text-left',
852
+ isEnabled ? 'bg-amber-500/20 border border-amber-500/30' : 'bg-gray-900/50 hover:bg-gray-800'
853
+ )}
854
+ >
855
+ <span className="text-sm text-white">{plugin.name}</span>
856
+ {isEnabled && <Check className="w-4 h-4 text-amber-400" />}
857
+ </button>
858
+ );
859
+ })}
860
+ </div>
861
+ </div>
862
+ ))}
863
+ </div>
864
+ </Popup>
865
+
866
+ {/* Task Type Popup */}
867
+ <Popup title="Select Task Complexity" isOpen={showTaskTypePopup} onClose={() => setShowTaskTypePopup(false)}>
868
+ <div className="space-y-2">
869
+ {taskTypes.map((type) => (
870
+ <button
871
+ key={type.id}
872
+ onClick={() => {
873
+ setTaskInput((p) => ({ ...p, taskType: type.id as 'low' | 'medium' | 'high' }));
874
+ setShowTaskTypePopup(false);
875
+ }}
876
+ className={classNames(
877
+ 'w-full flex items-center justify-between p-3 rounded-lg transition-colors text-left',
878
+ taskInput.taskType === type.id
879
+ ? `bg-${type.color}-500/20 border border-${type.color}-500/30`
880
+ : 'bg-gray-900/50 hover:bg-gray-800'
881
+ )}
882
+ >
883
+ <div>
884
+ <p className="text-sm font-medium text-white">{type.name}</p>
885
+ <p className="text-xs text-gray-500">{type.description}</p>
886
+ </div>
887
+ {taskInput.taskType === type.id && (
888
+ <Check className={`w-5 h-5 text-${type.color}-400`} />
889
+ )}
890
+ </button>
891
+ ))}
892
+ </div>
893
+ </Popup>
894
+
895
+ {/* Stats Popups */}
896
+ <StatsPopup
897
+ isOpen={showStatsPopup === 'episodes'}
898
+ onClose={() => setShowStatsPopup(null)}
899
+ stats={episodeStats}
900
+ title="Episode Statistics"
901
+ />
902
+ <StatsPopup
903
+ isOpen={showStatsPopup === 'steps'}
904
+ onClose={() => setShowStatsPopup(null)}
905
+ stats={stepStats}
906
+ title="Step Statistics"
907
+ />
908
+ <StatsPopup
909
+ isOpen={showStatsPopup === 'reward'}
910
+ onClose={() => setShowStatsPopup(null)}
911
+ stats={rewardStats}
912
+ title="Reward Statistics"
913
+ />
914
  </div>
915
  );
916
  };
frontend/src/components/DocsPage.tsx ADDED
@@ -0,0 +1,625 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import ReactMarkdown from 'react-markdown';
3
+ import remarkGfm from 'remark-gfm';
4
+ import {
5
+ Book,
6
+ Search,
7
+ ExternalLink,
8
+ Home,
9
+ Cpu,
10
+ Plug,
11
+ Database,
12
+ Terminal,
13
+ } from 'lucide-react';
14
+ import { classNames } from '@/utils/helpers';
15
+
16
+ interface DocsPageProps {
17
+ className?: string;
18
+ }
19
+
20
+ interface DocSection {
21
+ id: string;
22
+ title: string;
23
+ icon: React.ElementType;
24
+ content: string;
25
+ }
26
+
27
+ // Documentation content
28
+ const userGuideContent = `
29
+ # ScrapeRL Documentation
30
+
31
+ Welcome to ScrapeRL - an advanced Reinforcement Learning-powered web scraping environment.
32
+
33
+ ---
34
+
35
+ ## Getting Started
36
+
37
+ ### What is ScrapeRL?
38
+
39
+ ScrapeRL is an intelligent web scraping system that uses Reinforcement Learning (RL) to learn and adapt scraping strategies. Unlike traditional scrapers, ScrapeRL can:
40
+
41
+ - **Learn from experience** - Improve scraping strategies over time
42
+ - **Adapt to changes** - Handle website structure changes automatically
43
+ - **Multi-agent coordination** - Use specialized agents for different tasks
44
+ - **Memory-enhanced** - Remember patterns and optimize future runs
45
+
46
+ ### Quick Start
47
+
48
+ 1. **Enter a Target URL** - Provide the webpage you want to scrape
49
+ 2. **Write an Instruction** - Describe what data you want to extract
50
+ 3. **Configure Options** - Select model, agents, and plugins
51
+ 4. **Start Episode** - Click Start and watch the magic happen!
52
+
53
+ ### Example Task
54
+
55
+ \`\`\`
56
+ URL: https://example.com/products
57
+ Instruction: Extract all product names, prices, and descriptions
58
+ Task Type: Medium
59
+ \`\`\`
60
+
61
+ ---
62
+
63
+ ## Dashboard Overview
64
+
65
+ The dashboard is your command center for monitoring and controlling scraping operations.
66
+
67
+ ### Layout Structure
68
+
69
+ | Section | Description |
70
+ |---------|-------------|
71
+ | **Input Bar** | Enter URL, instruction, and configure task |
72
+ | **Left Sidebar** | View active agents, MCPs, skills, and tools |
73
+ | **Center Area** | Main visualization and current observation |
74
+ | **Right Sidebar** | Memory stats, extracted data, recent actions |
75
+ | **Bottom Logs** | Real-time terminal-style log output |
76
+
77
+ ### Task Types
78
+
79
+ | Type | Description | Use Case |
80
+ |------|-------------|----------|
81
+ | 🟢 **Low** | Simple single-page scraping | Product page, article text |
82
+ | 🟡 **Medium** | Multi-page with navigation | Search results, listings |
83
+ | 🔴 **High** | Complex interactive tasks | Login-required, forms |
84
+
85
+ ---
86
+
87
+ ## Agents
88
+
89
+ ScrapeRL uses a multi-agent architecture where specialized agents handle different aspects of scraping.
90
+
91
+ ### Available Agents
92
+
93
+ | Agent | Role | Description |
94
+ |-------|------|-------------|
95
+ | **Coordinator** | 🎯 Orchestrator | Manages all other agents |
96
+ | **Scraper** | 📄 Extractor | Extracts data from content |
97
+ | **Navigator** | 🧭 Navigation | Handles page navigation |
98
+ | **Analyzer** | 🔍 Analysis | Analyzes data patterns |
99
+ | **Validator** | ✅ Validation | Validates data quality |
100
+
101
+ ---
102
+
103
+ ## Plugins
104
+
105
+ Extend ScrapeRL's capabilities with plugins.
106
+
107
+ ### Categories
108
+
109
+ - **MCPs** - Browser automation (Browser Use, Puppeteer, Playwright)
110
+ - **Skills** - Task capabilities (Web Scraping, Data Extraction)
111
+ - **APIs** - External services (Firecrawl, Jina Reader, Serper)
112
+ - **Vision** - Visual AI (GPT-4V, Gemini Vision, Claude Vision)
113
+
114
+ ---
115
+
116
+ ## Memory System
117
+
118
+ | Layer | Purpose | Retention |
119
+ |-------|---------|-----------|
120
+ | **Working** | Current task | Session |
121
+ | **Episodic** | Experiences | Persistent |
122
+ | **Semantic** | Patterns | Persistent |
123
+ | **Procedural** | Actions | Persistent |
124
+
125
+ ---
126
+
127
+ ## API Keys
128
+
129
+ Configure in **Settings > API Keys**:
130
+
131
+ | Provider | Models |
132
+ |----------|--------|
133
+ | Groq | GPT-OSS 120B (Default) |
134
+ | Google | Gemini 2.5 Flash |
135
+ | OpenAI | GPT-4 Turbo |
136
+ | Anthropic | Claude 3 Opus |
137
+
138
+ ---
139
+
140
+ ## Keyboard Shortcuts
141
+
142
+ | Shortcut | Action |
143
+ |----------|--------|
144
+ | \`Ctrl + Enter\` | Start/Stop episode |
145
+ | \`Ctrl + L\` | Clear logs |
146
+ | \`Escape\` | Close popups |
147
+ `;
148
+
149
+ const agentsContent = `
150
+ # Agents Documentation
151
+
152
+ ## Multi-Agent Architecture
153
+
154
+ ScrapeRL employs a sophisticated multi-agent system where each agent specializes in specific tasks.
155
+
156
+ ### Coordinator Agent
157
+
158
+ The brain of the operation. It:
159
+ - Decides which agents to activate
160
+ - Plans the scraping strategy
161
+ - Handles error recovery
162
+ - Optimizes resource usage
163
+
164
+ ### Scraper Agent
165
+
166
+ Responsible for data extraction:
167
+ - HTML parsing and element selection
168
+ - Text content extraction
169
+ - Structured data identification
170
+ - Pattern recognition
171
+
172
+ ### Navigator Agent
173
+
174
+ Handles all page interactions:
175
+ - URL navigation
176
+ - Link clicking
177
+ - Form submissions
178
+ - Pagination handling
179
+
180
+ ### Analyzer Agent
181
+
182
+ Processes and analyzes data:
183
+ - Data validation
184
+ - Pattern detection
185
+ - Quality assessment
186
+ - Anomaly detection
187
+
188
+ ### Validator Agent
189
+
190
+ Ensures data quality:
191
+ - Schema validation
192
+ - Completeness checks
193
+ - Duplicate detection
194
+ - Format verification
195
+
196
+ ## Agent Communication
197
+
198
+ Agents communicate through a shared memory system:
199
+
200
+ \`\`\`
201
+ Coordinator -> Scraper: "Extract product data"
202
+ Scraper -> Memory: "Store extracted items"
203
+ Memory -> Analyzer: "New data available"
204
+ Analyzer -> Validator: "Validate these records"
205
+ Validator -> Coordinator: "Validation complete"
206
+ \`\`\`
207
+ `;
208
+
209
+ const pluginsContent = `
210
+ # Plugins Documentation
211
+
212
+ ## Plugin Categories
213
+
214
+ ### MCPs (Model Context Protocols)
215
+
216
+ Browser automation tools that integrate with AI models.
217
+
218
+ #### Browser Use
219
+ - AI-powered browser control
220
+ - Natural language commands
221
+ - Visual understanding
222
+ - Automatic element detection
223
+
224
+ #### Puppeteer MCP
225
+ - Headless Chrome automation
226
+ - Screenshot capture
227
+ - PDF generation
228
+ - Network interception
229
+
230
+ #### Playwright MCP
231
+ - Cross-browser support
232
+ - Mobile emulation
233
+ - Video recording
234
+ - Trace viewer
235
+
236
+ ### Skills
237
+
238
+ Specialized capabilities for specific tasks.
239
+
240
+ #### Web Scraping
241
+ - CSS/XPath selectors
242
+ - Data extraction patterns
243
+ - Pagination handling
244
+ - Rate limiting
245
+
246
+ #### Data Extraction
247
+ - JSON/XML parsing
248
+ - Table extraction
249
+ - List processing
250
+ - Content classification
251
+
252
+ ### APIs
253
+
254
+ External service integrations.
255
+
256
+ #### Firecrawl
257
+ - High-performance crawling
258
+ - JavaScript rendering
259
+ - Proxy rotation
260
+ - Rate limiting
261
+
262
+ #### Jina Reader
263
+ - Content extraction API
264
+ - Clean text output
265
+ - Structured data
266
+ - Multi-format support
267
+
268
+ ### Vision Models
269
+
270
+ Visual understanding capabilities.
271
+
272
+ #### GPT-4 Vision
273
+ - Image analysis
274
+ - Screenshot understanding
275
+ - UI element detection
276
+ - Text extraction from images
277
+
278
+ ## Installing Plugins
279
+
280
+ 1. Navigate to Plugins page
281
+ 2. Browse categories
282
+ 3. Click Install on desired plugin
283
+ 4. Configure API keys if required
284
+ `;
285
+
286
+ const memoryContent = `
287
+ # Memory System Documentation
288
+
289
+ ## Hierarchical Memory Architecture
290
+
291
+ ScrapeRL uses a four-layer memory system inspired by human cognitive architecture.
292
+
293
+ ### Working Memory
294
+
295
+ **Purpose:** Active task context
296
+
297
+ - Current URL and page state
298
+ - Active extraction targets
299
+ - Temporary calculations
300
+ - Session-specific data
301
+
302
+ **Retention:** Cleared after each episode
303
+
304
+ ### Episodic Memory
305
+
306
+ **Purpose:** Experience records
307
+
308
+ - Past scraping sessions
309
+ - Success/failure patterns
310
+ - Timing data
311
+ - Action sequences
312
+
313
+ **Retention:** Persistent across sessions
314
+
315
+ ### Semantic Memory
316
+
317
+ **Purpose:** Learned knowledge
318
+
319
+ - Website patterns
320
+ - Extraction rules
321
+ - Domain knowledge
322
+ - Best practices
323
+
324
+ **Retention:** Long-term persistent
325
+
326
+ ### Procedural Memory
327
+
328
+ **Purpose:** Action sequences
329
+
330
+ - Navigation patterns
331
+ - Interaction sequences
332
+ - Recovery procedures
333
+ - Optimization strategies
334
+
335
+ **Retention:** Long-term persistent
336
+
337
+ ## Memory Operations
338
+
339
+ ### Store
340
+ \`\`\`json
341
+ {
342
+ "content": "Product prices on example.com follow pattern...",
343
+ "memory_type": "semantic",
344
+ "metadata": {
345
+ "domain": "example.com",
346
+ "confidence": 0.95
347
+ }
348
+ }
349
+ \`\`\`
350
+
351
+ ### Query
352
+ \`\`\`json
353
+ {
354
+ "query": "price extraction patterns",
355
+ "memory_type": "semantic",
356
+ "limit": 10
357
+ }
358
+ \`\`\`
359
+
360
+ ### Consolidation
361
+
362
+ Automatic promotion of important memories:
363
+ - Working → Episodic: At episode end
364
+ - Episodic → Semantic: Pattern detection
365
+ - Episodic → Procedural: Action sequences
366
+ `;
367
+
368
+ const apiContent = `
369
+ # API Reference
370
+
371
+ ## Base URL
372
+
373
+ \`\`\`
374
+ http://localhost:7860/api
375
+ \`\`\`
376
+
377
+ ## Health Check
378
+
379
+ ### GET /health
380
+
381
+ Check system status.
382
+
383
+ **Response:**
384
+ \`\`\`json
385
+ {
386
+ "status": "healthy",
387
+ "version": "0.1.0",
388
+ "timestamp": "2026-03-28T00:00:00Z"
389
+ }
390
+ \`\`\`
391
+
392
+ ## Episode Endpoints
393
+
394
+ ### POST /episode/reset
395
+
396
+ Start a new episode.
397
+
398
+ **Request:**
399
+ \`\`\`json
400
+ {
401
+ "task_id": "scrape-products"
402
+ }
403
+ \`\`\`
404
+
405
+ ### POST /episode/step
406
+
407
+ Execute an action.
408
+
409
+ **Request:**
410
+ \`\`\`json
411
+ {
412
+ "action": "navigate",
413
+ "params": { "url": "https://example.com" }
414
+ }
415
+ \`\`\`
416
+
417
+ ### GET /episode/state
418
+
419
+ Get current state.
420
+
421
+ ## Memory Endpoints
422
+
423
+ ### POST /memory/store
424
+
425
+ Store a memory entry.
426
+
427
+ ### POST /memory/query
428
+
429
+ Query memories.
430
+
431
+ ### GET /memory/stats/overview
432
+
433
+ Get memory statistics.
434
+
435
+ ## Plugin Endpoints
436
+
437
+ ### GET /plugins/
438
+
439
+ List all plugins.
440
+
441
+ ### POST /plugins/install
442
+
443
+ Install a plugin.
444
+
445
+ ### POST /plugins/uninstall
446
+
447
+ Uninstall a plugin.
448
+
449
+ ## Settings Endpoints
450
+
451
+ ### GET /settings/
452
+
453
+ Get current settings.
454
+
455
+ ### POST /settings/api-key
456
+
457
+ Update API key.
458
+
459
+ ### POST /settings/model
460
+
461
+ Select active model.
462
+ `;
463
+
464
+ const docs: DocSection[] = [
465
+ { id: 'guide', title: 'User Guide', icon: Home, content: userGuideContent },
466
+ { id: 'agents', title: 'Agents', icon: Cpu, content: agentsContent },
467
+ { id: 'plugins', title: 'Plugins', icon: Plug, content: pluginsContent },
468
+ { id: 'memory', title: 'Memory System', icon: Database, content: memoryContent },
469
+ { id: 'api', title: 'API Reference', icon: Terminal, content: apiContent },
470
+ ];
471
+
472
+ export const DocsPage: React.FC<DocsPageProps> = ({ className }) => {
473
+ const [activeDoc, setActiveDoc] = useState<string>('guide');
474
+ const [searchQuery, setSearchQuery] = useState('');
475
+
476
+ const currentDoc = docs.find((d) => d.id === activeDoc) || docs[0];
477
+
478
+ return (
479
+ <div className={classNames('flex h-[calc(100vh-120px)]', className)}>
480
+ {/* Left Sidebar - Navigation */}
481
+ <div className="w-64 flex-shrink-0 bg-gray-800/30 border-r border-gray-700/50 flex flex-col">
482
+ <div className="p-4 border-b border-gray-700/50">
483
+ <h2 className="text-lg font-semibold text-white flex items-center gap-2">
484
+ <Book className="w-5 h-5 text-cyan-400" />
485
+ Documentation
486
+ </h2>
487
+ <p className="text-xs text-gray-500 mt-1">Learn how to use ScrapeRL</p>
488
+ </div>
489
+
490
+ {/* Search */}
491
+ <div className="p-3 border-b border-gray-700/50">
492
+ <div className="relative">
493
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500" />
494
+ <input
495
+ type="text"
496
+ placeholder="Search docs..."
497
+ value={searchQuery}
498
+ onChange={(e) => setSearchQuery(e.target.value)}
499
+ className="w-full pl-9 pr-3 py-2 bg-gray-900/50 border border-gray-700/50 rounded-lg text-sm text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/50"
500
+ />
501
+ </div>
502
+ </div>
503
+
504
+ {/* Navigation */}
505
+ <nav className="flex-1 p-3 space-y-1 overflow-y-auto">
506
+ {docs.map((doc) => {
507
+ const Icon = doc.icon;
508
+ const isActive = activeDoc === doc.id;
509
+ return (
510
+ <button
511
+ key={doc.id}
512
+ onClick={() => setActiveDoc(doc.id)}
513
+ className={classNames(
514
+ 'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all',
515
+ isActive
516
+ ? 'bg-cyan-500/20 border border-cyan-500/30 text-cyan-400'
517
+ : 'hover:bg-gray-700/50 text-gray-400 hover:text-gray-200'
518
+ )}
519
+ >
520
+ <Icon className={classNames('w-4 h-4', isActive ? 'text-cyan-400' : 'text-gray-500')} />
521
+ <span className="text-sm font-medium">{doc.title}</span>
522
+ </button>
523
+ );
524
+ })}
525
+ </nav>
526
+
527
+ {/* Footer */}
528
+ <div className="p-4 border-t border-gray-700/50">
529
+ <a
530
+ href="https://github.com/NeerajCodz/scrapeRL"
531
+ target="_blank"
532
+ rel="noopener noreferrer"
533
+ className="flex items-center gap-2 text-xs text-gray-500 hover:text-gray-300 transition-colors"
534
+ >
535
+ <ExternalLink className="w-3 h-3" />
536
+ View on GitHub
537
+ </a>
538
+ </div>
539
+ </div>
540
+
541
+ {/* Main Content - Markdown Viewer */}
542
+ <div className="flex-1 overflow-y-auto">
543
+ <div className="max-w-4xl mx-auto p-8">
544
+ <article className="prose prose-invert prose-sm max-w-none">
545
+ <ReactMarkdown
546
+ remarkPlugins={[remarkGfm]}
547
+ components={{
548
+ h1: ({ children }) => (
549
+ <h1 className="text-3xl font-bold text-white mb-6 pb-4 border-b border-gray-700/50">
550
+ {children}
551
+ </h1>
552
+ ),
553
+ h2: ({ children }) => (
554
+ <h2 className="text-2xl font-semibold text-white mt-8 mb-4">{children}</h2>
555
+ ),
556
+ h3: ({ children }) => (
557
+ <h3 className="text-xl font-semibold text-gray-200 mt-6 mb-3">{children}</h3>
558
+ ),
559
+ h4: ({ children }) => (
560
+ <h4 className="text-lg font-medium text-gray-300 mt-4 mb-2">{children}</h4>
561
+ ),
562
+ p: ({ children }) => <p className="text-gray-400 mb-4 leading-relaxed">{children}</p>,
563
+ ul: ({ children }) => <ul className="list-disc list-inside text-gray-400 mb-4 space-y-1">{children}</ul>,
564
+ ol: ({ children }) => <ol className="list-decimal list-inside text-gray-400 mb-4 space-y-1">{children}</ol>,
565
+ li: ({ children }) => <li className="text-gray-400">{children}</li>,
566
+ strong: ({ children }) => <strong className="text-white font-semibold">{children}</strong>,
567
+ em: ({ children }) => <em className="text-gray-300">{children}</em>,
568
+ code: ({ children, className }) => {
569
+ const isBlock = className?.includes('language-');
570
+ if (isBlock) {
571
+ return (
572
+ <code className="block bg-gray-900 rounded-lg p-4 text-sm font-mono text-gray-300 overflow-x-auto">
573
+ {children}
574
+ </code>
575
+ );
576
+ }
577
+ return (
578
+ <code className="bg-gray-800 text-cyan-400 px-1.5 py-0.5 rounded text-sm font-mono">
579
+ {children}
580
+ </code>
581
+ );
582
+ },
583
+ pre: ({ children }) => <pre className="mb-4">{children}</pre>,
584
+ blockquote: ({ children }) => (
585
+ <blockquote className="border-l-4 border-cyan-500/50 pl-4 italic text-gray-400 my-4">
586
+ {children}
587
+ </blockquote>
588
+ ),
589
+ table: ({ children }) => (
590
+ <div className="overflow-x-auto mb-4">
591
+ <table className="w-full border-collapse">{children}</table>
592
+ </div>
593
+ ),
594
+ thead: ({ children }) => <thead className="bg-gray-800/50">{children}</thead>,
595
+ th: ({ children }) => (
596
+ <th className="px-4 py-2 text-left text-xs font-semibold text-gray-300 border-b border-gray-700">
597
+ {children}
598
+ </th>
599
+ ),
600
+ td: ({ children }) => (
601
+ <td className="px-4 py-2 text-sm text-gray-400 border-b border-gray-800">{children}</td>
602
+ ),
603
+ hr: () => <hr className="border-gray-700/50 my-8" />,
604
+ a: ({ href, children }) => (
605
+ <a
606
+ href={href}
607
+ className="text-cyan-400 hover:text-cyan-300 underline underline-offset-2"
608
+ target="_blank"
609
+ rel="noopener noreferrer"
610
+ >
611
+ {children}
612
+ </a>
613
+ ),
614
+ }}
615
+ >
616
+ {currentDoc.content}
617
+ </ReactMarkdown>
618
+ </article>
619
+ </div>
620
+ </div>
621
+ </div>
622
+ );
623
+ };
624
+
625
+ export default DocsPage;
frontend/src/components/Settings.tsx CHANGED
@@ -9,6 +9,13 @@ import {
9
  EyeOff,
10
  Zap,
11
  Server,
 
 
 
 
 
 
 
12
  } from 'lucide-react';
13
  import { Button } from '@/components/ui/Button';
14
  import { Input } from '@/components/ui/Input';
@@ -16,6 +23,7 @@ import { Select, Toggle } from '@/components/ui/Select';
16
  import { Badge } from '@/components/ui/Badge';
17
  import { apiClient } from '@/api/client';
18
  import type { SystemSettings } from '@/types';
 
19
 
20
  interface SettingsProps {
21
  className?: string;
@@ -43,10 +51,24 @@ interface SettingsData {
43
  plugins_installed: string[];
44
  }
45
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  export const Settings: React.FC<SettingsProps> = ({ className }) => {
47
  const queryClient = useQueryClient();
 
48
  const [localSettings, setLocalSettings] = useState<Partial<SystemSettings>>({});
49
  const [showKeys, setShowKeys] = useState<Record<string, boolean>>({});
 
50
  const [apiKeys, setApiKeys] = useState<ApiKeyState>({
51
  openai: '',
52
  anthropic: '',
@@ -143,37 +165,16 @@ export const Settings: React.FC<SettingsProps> = ({ className }) => {
143
  ? `${settingsData.selected_model.provider}/${settingsData.selected_model.model}`
144
  : 'groq/gpt-oss-120b';
145
 
146
- return (
147
- <div className={`space-y-6 ${className}`}>
148
- {/* Header */}
149
- <div className="flex items-center justify-between">
150
- <div>
151
- <h1 className="text-2xl font-bold text-white flex items-center gap-3">
152
- <div className="p-2 bg-gradient-to-br from-purple-500/20 to-pink-500/20 rounded-lg">
153
- <SettingsIcon className="w-6 h-6 text-purple-400" />
154
  </div>
155
- Settings
156
- </h1>
157
- <p className="text-gray-400 mt-1">Configure your ScrapeRL environment</p>
158
- </div>
159
- {health && (
160
- <Badge variant={health.status === 'ok' ? 'success' : 'error'} dot>
161
- {health.status === 'ok' ? 'Connected' : 'Disconnected'}
162
- </Badge>
163
- )}
164
- </div>
165
 
166
- {settingsLoading ? (
167
- <div className="flex items-center justify-center py-16">
168
- <div className="flex flex-col items-center gap-3">
169
- <SettingsIcon className="w-8 h-8 text-gray-500 animate-spin" />
170
- <p className="text-gray-400">Loading settings...</p>
171
- </div>
172
- </div>
173
- ) : (
174
- <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
175
- {/* Left Column */}
176
- <div className="space-y-6">
177
  {/* API Key Required Warning */}
178
  {keyRequired?.required && (
179
  <div className="flex items-center gap-3 p-4 bg-amber-500/10 border border-amber-500/30 rounded-xl">
@@ -185,64 +186,68 @@ export const Settings: React.FC<SettingsProps> = ({ className }) => {
185
  </div>
186
  )}
187
 
188
- {/* Model Selection */}
189
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-5">
190
- <div className="flex items-center gap-2 text-sm font-semibold text-white mb-4">
191
- <Zap className="w-4 h-4 text-emerald-400" />
192
- Active Model
193
- </div>
194
- <Select
195
- label=""
196
- options={modelOptions}
197
- value={currentModel}
198
- onChange={(e) => handleModelChange(e.target.value)}
199
- placeholder="Select model"
 
 
 
 
 
 
 
 
 
 
 
 
200
  />
201
- {selectModelMutation.isPending && (
202
- <p className="text-xs text-gray-400 mt-2">Switching model...</p>
203
- )}
204
- <p className="text-xs text-gray-500 mt-3">
205
- Select the AI model to use for scraping tasks. Different models have different capabilities and costs.
206
- </p>
207
  </div>
208
 
209
- {/* Connection Settings */}
210
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-5">
211
- <div className="flex items-center gap-2 text-sm font-semibold text-white mb-4">
212
- <Server className="w-4 h-4 text-cyan-400" />
213
- Connection Settings
214
- </div>
215
- <div className="space-y-4">
216
- <Toggle
217
- label="WebSocket Updates"
218
- description="Enable real-time episode updates"
219
- checked={localSettings.enableWebSocket ?? true}
220
- onChange={(checked) => {
221
- setLocalSettings((prev) => ({ ...prev, enableWebSocket: checked }));
222
- }}
223
- />
224
- <Toggle
225
- label="Memory Persistence"
226
- description="Persist memory across episodes"
227
- checked={localSettings.memoryPersistence ?? false}
228
- onChange={(checked) => {
229
- setLocalSettings((prev) => ({ ...prev, memoryPersistence: checked }));
230
- }}
231
- />
232
  </div>
233
  </div>
234
  </div>
 
235
 
236
- {/* Right Column - API Keys */}
237
- <div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700/50 rounded-xl p-5">
238
- <div className="flex items-center gap-2 text-sm font-semibold text-white mb-2">
239
- <Key className="w-4 h-4 text-amber-400" />
240
- API Keys
 
 
 
241
  </div>
242
- <p className="text-xs text-gray-400 mb-5">
243
- Configure your API keys. Server keys are used by default, but you can override them here.
244
- </p>
245
-
246
  <div className="space-y-4">
247
  {providers.map((provider) => {
248
  const isConfigured = settingsData?.api_keys_configured?.[provider.id] ?? false;
@@ -252,9 +257,7 @@ export const Settings: React.FC<SettingsProps> = ({ className }) => {
252
  <div className="flex items-center gap-3">
253
  <span className="text-2xl">{provider.icon}</span>
254
  <div>
255
- <span className="text-sm font-medium text-white">
256
- {provider.name}
257
- </span>
258
  <p className="text-xs text-gray-500">{provider.description}</p>
259
  </div>
260
  </div>
@@ -281,11 +284,7 @@ export const Settings: React.FC<SettingsProps> = ({ className }) => {
281
  onClick={() => toggleShowKey(provider.id)}
282
  className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300 transition-colors"
283
  >
284
- {showKeys[provider.id] ? (
285
- <EyeOff className="w-4 h-4" />
286
- ) : (
287
- <Eye className="w-4 h-4" />
288
- )}
289
  </button>
290
  </div>
291
  <Button
@@ -302,16 +301,314 @@ export const Settings: React.FC<SettingsProps> = ({ className }) => {
302
  })}
303
  </div>
304
 
305
- {/* Status Messages */}
306
  {updateApiKeyMutation.isSuccess && (
307
- <div className="flex items-center gap-2 mt-4 p-3 bg-emerald-500/10 border border-emerald-500/30 rounded-lg">
308
  <CheckCircle className="w-4 h-4 text-emerald-400" />
309
  <span className="text-sm text-emerald-400">API key saved successfully</span>
310
  </div>
311
  )}
312
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  </div>
314
- )}
315
  </div>
316
  );
317
  };
 
9
  EyeOff,
10
  Zap,
11
  Server,
12
+ Palette,
13
+ Bell,
14
+ Shield,
15
+ Cpu,
16
+ Globe,
17
+ Wallet,
18
+ ChevronRight,
19
  } from 'lucide-react';
20
  import { Button } from '@/components/ui/Button';
21
  import { Input } from '@/components/ui/Input';
 
23
  import { Badge } from '@/components/ui/Badge';
24
  import { apiClient } from '@/api/client';
25
  import type { SystemSettings } from '@/types';
26
+ import { classNames } from '@/utils/helpers';
27
 
28
  interface SettingsProps {
29
  className?: string;
 
51
  plugins_installed: string[];
52
  }
53
 
54
+ type SettingsSection = 'general' | 'api-keys' | 'models' | 'budget' | 'appearance' | 'notifications' | 'advanced';
55
+
56
+ const sectionConfig: { id: SettingsSection; label: string; icon: React.ElementType; description: string }[] = [
57
+ { id: 'general', label: 'General', icon: SettingsIcon, description: 'Basic application settings' },
58
+ { id: 'api-keys', label: 'API Keys', icon: Key, description: 'Configure provider API keys' },
59
+ { id: 'models', label: 'Models', icon: Cpu, description: 'AI model selection' },
60
+ { id: 'budget', label: 'Budget & Limits', icon: Wallet, description: 'API usage limits' },
61
+ { id: 'appearance', label: 'Appearance', icon: Palette, description: 'UI customization' },
62
+ { id: 'notifications', label: 'Notifications', icon: Bell, description: 'Alert preferences' },
63
+ { id: 'advanced', label: 'Advanced', icon: Shield, description: 'Advanced settings' },
64
+ ];
65
+
66
  export const Settings: React.FC<SettingsProps> = ({ className }) => {
67
  const queryClient = useQueryClient();
68
+ const [activeSection, setActiveSection] = useState<SettingsSection>('general');
69
  const [localSettings, setLocalSettings] = useState<Partial<SystemSettings>>({});
70
  const [showKeys, setShowKeys] = useState<Record<string, boolean>>({});
71
+ const [budgetEnabled, setBudgetEnabled] = useState(false);
72
  const [apiKeys, setApiKeys] = useState<ApiKeyState>({
73
  openai: '',
74
  anthropic: '',
 
165
  ? `${settingsData.selected_model.provider}/${settingsData.selected_model.model}`
166
  : 'groq/gpt-oss-120b';
167
 
168
+ const renderSectionContent = () => {
169
+ switch (activeSection) {
170
+ case 'general':
171
+ return (
172
+ <div className="space-y-6">
173
+ <div>
174
+ <h3 className="text-lg font-semibold text-white mb-1">General Settings</h3>
175
+ <p className="text-sm text-gray-400">Configure basic application behavior</p>
176
  </div>
 
 
 
 
 
 
 
 
 
 
177
 
 
 
 
 
 
 
 
 
 
 
 
178
  {/* API Key Required Warning */}
179
  {keyRequired?.required && (
180
  <div className="flex items-center gap-3 p-4 bg-amber-500/10 border border-amber-500/30 rounded-xl">
 
186
  </div>
187
  )}
188
 
189
+ <div className="space-y-4">
190
+ <Toggle
191
+ label="WebSocket Updates"
192
+ description="Enable real-time episode updates via WebSocket connection"
193
+ checked={localSettings.enableWebSocket ?? true}
194
+ onChange={(checked) => setLocalSettings((prev) => ({ ...prev, enableWebSocket: checked }))}
195
+ />
196
+ <Toggle
197
+ label="Memory Persistence"
198
+ description="Persist memory data across episodes for better context retention"
199
+ checked={localSettings.memoryPersistence ?? false}
200
+ onChange={(checked) => setLocalSettings((prev) => ({ ...prev, memoryPersistence: checked }))}
201
+ />
202
+ <Toggle
203
+ label="Auto-save Episodes"
204
+ description="Automatically save episode data when completed"
205
+ checked={localSettings.autoSave ?? true}
206
+ onChange={(checked) => setLocalSettings((prev) => ({ ...prev, autoSave: checked }))}
207
+ />
208
+ <Toggle
209
+ label="Debug Mode"
210
+ description="Enable verbose logging and debugging information"
211
+ checked={localSettings.debugMode ?? false}
212
+ onChange={(checked) => setLocalSettings((prev) => ({ ...prev, debugMode: checked }))}
213
  />
 
 
 
 
 
 
214
  </div>
215
 
216
+ {/* System Status */}
217
+ <div className="pt-4 border-t border-gray-700/50">
218
+ <h4 className="text-sm font-medium text-gray-300 mb-3">System Status</h4>
219
+ <div className="grid grid-cols-2 gap-3">
220
+ <div className="p-3 bg-gray-900/50 rounded-lg">
221
+ <div className="flex items-center gap-2">
222
+ <Server className="w-4 h-4 text-emerald-400" />
223
+ <span className="text-xs text-gray-400">Backend</span>
224
+ </div>
225
+ <p className="text-sm font-medium text-white mt-1">
226
+ {health?.status === 'ok' ? 'Connected' : 'Disconnected'}
227
+ </p>
228
+ </div>
229
+ <div className="p-3 bg-gray-900/50 rounded-lg">
230
+ <div className="flex items-center gap-2">
231
+ <Globe className="w-4 h-4 text-cyan-400" />
232
+ <span className="text-xs text-gray-400">Version</span>
233
+ </div>
234
+ <p className="text-sm font-medium text-white mt-1">{health?.version || 'v0.1.0'}</p>
235
+ </div>
 
 
 
236
  </div>
237
  </div>
238
  </div>
239
+ );
240
 
241
+ case 'api-keys':
242
+ return (
243
+ <div className="space-y-6">
244
+ <div>
245
+ <h3 className="text-lg font-semibold text-white mb-1">API Keys</h3>
246
+ <p className="text-sm text-gray-400">
247
+ Configure your provider API keys. Server keys are used by default, but you can override them here.
248
+ </p>
249
  </div>
250
+
 
 
 
251
  <div className="space-y-4">
252
  {providers.map((provider) => {
253
  const isConfigured = settingsData?.api_keys_configured?.[provider.id] ?? false;
 
257
  <div className="flex items-center gap-3">
258
  <span className="text-2xl">{provider.icon}</span>
259
  <div>
260
+ <span className="text-sm font-medium text-white">{provider.name}</span>
 
 
261
  <p className="text-xs text-gray-500">{provider.description}</p>
262
  </div>
263
  </div>
 
284
  onClick={() => toggleShowKey(provider.id)}
285
  className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300 transition-colors"
286
  >
287
+ {showKeys[provider.id] ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
 
 
 
 
288
  </button>
289
  </div>
290
  <Button
 
301
  })}
302
  </div>
303
 
 
304
  {updateApiKeyMutation.isSuccess && (
305
+ <div className="flex items-center gap-2 p-3 bg-emerald-500/10 border border-emerald-500/30 rounded-lg">
306
  <CheckCircle className="w-4 h-4 text-emerald-400" />
307
  <span className="text-sm text-emerald-400">API key saved successfully</span>
308
  </div>
309
  )}
310
  </div>
311
+ );
312
+
313
+ case 'models':
314
+ return (
315
+ <div className="space-y-6">
316
+ <div>
317
+ <h3 className="text-lg font-semibold text-white mb-1">AI Models</h3>
318
+ <p className="text-sm text-gray-400">Select the AI model to use for scraping tasks</p>
319
+ </div>
320
+
321
+ <div className="p-4 bg-gray-900/50 border border-gray-700/30 rounded-xl">
322
+ <div className="flex items-center gap-2 text-sm font-medium text-white mb-3">
323
+ <Zap className="w-4 h-4 text-emerald-400" />
324
+ Active Model
325
+ </div>
326
+ <Select
327
+ label=""
328
+ options={modelOptions}
329
+ value={currentModel}
330
+ onChange={(e) => handleModelChange(e.target.value)}
331
+ placeholder="Select model"
332
+ />
333
+ {selectModelMutation.isPending && (
334
+ <p className="text-xs text-gray-400 mt-2">Switching model...</p>
335
+ )}
336
+ </div>
337
+
338
+ {/* Model Categories */}
339
+ <div className="grid grid-cols-2 gap-3">
340
+ <div className="p-4 bg-cyan-500/10 border border-cyan-500/30 rounded-xl">
341
+ <h4 className="text-sm font-medium text-cyan-400 mb-2">Text Models</h4>
342
+ <p className="text-xs text-gray-400">GPT-4, Claude, Gemini for text generation</p>
343
+ </div>
344
+ <div className="p-4 bg-purple-500/10 border border-purple-500/30 rounded-xl">
345
+ <h4 className="text-sm font-medium text-purple-400 mb-2">Vision Models</h4>
346
+ <p className="text-xs text-gray-400">GPT-4V, Gemini Pro Vision for image analysis</p>
347
+ </div>
348
+ <div className="p-4 bg-amber-500/10 border border-amber-500/30 rounded-xl">
349
+ <h4 className="text-sm font-medium text-amber-400 mb-2">Embedding Models</h4>
350
+ <p className="text-xs text-gray-400">Text embeddings for semantic search</p>
351
+ </div>
352
+ <div className="p-4 bg-emerald-500/10 border border-emerald-500/30 rounded-xl">
353
+ <h4 className="text-sm font-medium text-emerald-400 mb-2">Fast Models</h4>
354
+ <p className="text-xs text-gray-400">Groq, Mistral for fast inference</p>
355
+ </div>
356
+ </div>
357
+ </div>
358
+ );
359
+
360
+ case 'budget':
361
+ return (
362
+ <div className="space-y-6">
363
+ <div>
364
+ <h3 className="text-lg font-semibold text-white mb-1">Budget & Limits</h3>
365
+ <p className="text-sm text-gray-400">Configure API usage limits and budget controls</p>
366
+ </div>
367
+
368
+ <div className="p-4 bg-gray-900/50 border border-gray-700/30 rounded-xl">
369
+ <div className="flex items-center justify-between mb-4">
370
+ <div className="flex items-center gap-3">
371
+ <Wallet className="w-5 h-5 text-amber-400" />
372
+ <div>
373
+ <h4 className="text-sm font-medium text-white">Enable Budget Limits</h4>
374
+ <p className="text-xs text-gray-500">Control API spending with usage limits</p>
375
+ </div>
376
+ </div>
377
+ <button
378
+ onClick={() => setBudgetEnabled(!budgetEnabled)}
379
+ className={classNames(
380
+ 'relative w-12 h-6 rounded-full transition-colors',
381
+ budgetEnabled ? 'bg-emerald-500' : 'bg-gray-600'
382
+ )}
383
+ >
384
+ <span
385
+ className={classNames(
386
+ 'absolute top-1 w-4 h-4 bg-white rounded-full transition-transform',
387
+ budgetEnabled ? 'translate-x-7' : 'translate-x-1'
388
+ )}
389
+ />
390
+ </button>
391
+ </div>
392
+
393
+ {budgetEnabled ? (
394
+ <div className="space-y-4 pt-4 border-t border-gray-700/50">
395
+ <div>
396
+ <label className="text-xs text-gray-400 mb-2 block">Daily Limit ($)</label>
397
+ <Input type="number" placeholder="10.00" className="bg-gray-800/50" />
398
+ </div>
399
+ <div>
400
+ <label className="text-xs text-gray-400 mb-2 block">Monthly Limit ($)</label>
401
+ <Input type="number" placeholder="100.00" className="bg-gray-800/50" />
402
+ </div>
403
+ <div>
404
+ <label className="text-xs text-gray-400 mb-2 block">Max Tokens per Request</label>
405
+ <Input type="number" placeholder="4096" className="bg-gray-800/50" />
406
+ </div>
407
+ <Toggle
408
+ label="Alert at 80% usage"
409
+ description="Receive notification when approaching limit"
410
+ checked={true}
411
+ onChange={() => {}}
412
+ />
413
+ </div>
414
+ ) : (
415
+ <div className="pt-4 border-t border-gray-700/50">
416
+ <p className="text-sm text-gray-500 text-center py-4">
417
+ Budget limits are disabled. Enable to control API spending.
418
+ </p>
419
+ </div>
420
+ )}
421
+ </div>
422
+ </div>
423
+ );
424
+
425
+ case 'appearance':
426
+ return (
427
+ <div className="space-y-6">
428
+ <div>
429
+ <h3 className="text-lg font-semibold text-white mb-1">Appearance</h3>
430
+ <p className="text-sm text-gray-400">Customize the look and feel of ScrapeRL</p>
431
+ </div>
432
+
433
+ <div className="space-y-4">
434
+ <div className="p-4 bg-gray-900/50 border border-gray-700/30 rounded-xl">
435
+ <h4 className="text-sm font-medium text-white mb-3">Theme</h4>
436
+ <div className="grid grid-cols-3 gap-3">
437
+ <button className="p-3 bg-gray-800 border-2 border-emerald-500 rounded-lg text-center">
438
+ <div className="w-8 h-8 bg-gray-900 rounded mx-auto mb-2" />
439
+ <span className="text-xs text-white">Dark</span>
440
+ </button>
441
+ <button className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-center opacity-50">
442
+ <div className="w-8 h-8 bg-white rounded mx-auto mb-2" />
443
+ <span className="text-xs text-gray-400">Light</span>
444
+ </button>
445
+ <button className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-center opacity-50">
446
+ <div className="w-8 h-8 bg-gradient-to-b from-white to-gray-900 rounded mx-auto mb-2" />
447
+ <span className="text-xs text-gray-400">Auto</span>
448
+ </button>
449
+ </div>
450
+ </div>
451
+
452
+ <Toggle
453
+ label="Compact Mode"
454
+ description="Reduce padding and spacing for more content"
455
+ checked={false}
456
+ onChange={() => {}}
457
+ />
458
+ <Toggle
459
+ label="Show Animations"
460
+ description="Enable UI animations and transitions"
461
+ checked={true}
462
+ onChange={() => {}}
463
+ />
464
+ </div>
465
+ </div>
466
+ );
467
+
468
+ case 'notifications':
469
+ return (
470
+ <div className="space-y-6">
471
+ <div>
472
+ <h3 className="text-lg font-semibold text-white mb-1">Notifications</h3>
473
+ <p className="text-sm text-gray-400">Configure when and how you receive alerts</p>
474
+ </div>
475
+
476
+ <div className="space-y-4">
477
+ <Toggle
478
+ label="Episode Completion"
479
+ description="Notify when an episode finishes"
480
+ checked={true}
481
+ onChange={() => {}}
482
+ />
483
+ <Toggle
484
+ label="Error Alerts"
485
+ description="Alert on scraping errors"
486
+ checked={true}
487
+ onChange={() => {}}
488
+ />
489
+ <Toggle
490
+ label="Budget Warnings"
491
+ description="Warn when approaching budget limits"
492
+ checked={true}
493
+ onChange={() => {}}
494
+ />
495
+ <Toggle
496
+ label="System Updates"
497
+ description="Notify about new features and updates"
498
+ checked={false}
499
+ onChange={() => {}}
500
+ />
501
+ </div>
502
+ </div>
503
+ );
504
+
505
+ case 'advanced':
506
+ return (
507
+ <div className="space-y-6">
508
+ <div>
509
+ <h3 className="text-lg font-semibold text-white mb-1">Advanced Settings</h3>
510
+ <p className="text-sm text-gray-400">For power users and developers</p>
511
+ </div>
512
+
513
+ <div className="space-y-4">
514
+ <Toggle
515
+ label="Developer Mode"
516
+ description="Enable advanced debugging tools"
517
+ checked={false}
518
+ onChange={() => {}}
519
+ />
520
+ <Toggle
521
+ label="Experimental Features"
522
+ description="Try new features before release"
523
+ checked={false}
524
+ onChange={() => {}}
525
+ />
526
+ <Toggle
527
+ label="Verbose Logging"
528
+ description="Log all API requests and responses"
529
+ checked={false}
530
+ onChange={() => {}}
531
+ />
532
+
533
+ <div className="pt-4 border-t border-gray-700/50">
534
+ <h4 className="text-sm font-medium text-white mb-3">Data Management</h4>
535
+ <div className="flex gap-3">
536
+ <Button variant="secondary" size="sm">
537
+ Export Data
538
+ </Button>
539
+ <Button variant="ghost" size="sm" className="text-red-400 hover:text-red-300">
540
+ Clear Cache
541
+ </Button>
542
+ </div>
543
+ </div>
544
+ </div>
545
+ </div>
546
+ );
547
+
548
+ default:
549
+ return null;
550
+ }
551
+ };
552
+
553
+ return (
554
+ <div className={classNames('flex h-[calc(100vh-120px)]', className)}>
555
+ {/* Left Sidebar */}
556
+ <div className="w-64 bg-gray-800/30 border-r border-gray-700/50 flex flex-col">
557
+ <div className="p-4 border-b border-gray-700/50">
558
+ <h2 className="text-lg font-semibold text-white flex items-center gap-2">
559
+ <SettingsIcon className="w-5 h-5 text-purple-400" />
560
+ Settings
561
+ </h2>
562
+ <p className="text-xs text-gray-500 mt-1">Configure your environment</p>
563
+ </div>
564
+
565
+ <nav className="flex-1 p-3 space-y-1 overflow-y-auto">
566
+ {sectionConfig.map((section) => {
567
+ const Icon = section.icon;
568
+ const isActive = activeSection === section.id;
569
+ return (
570
+ <button
571
+ key={section.id}
572
+ onClick={() => setActiveSection(section.id)}
573
+ className={classNames(
574
+ 'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all group',
575
+ isActive
576
+ ? 'bg-emerald-500/20 border border-emerald-500/30 text-emerald-400'
577
+ : 'hover:bg-gray-700/50 text-gray-400 hover:text-gray-200'
578
+ )}
579
+ >
580
+ <Icon className={classNames('w-4 h-4', isActive ? 'text-emerald-400' : 'text-gray-500 group-hover:text-gray-300')} />
581
+ <span className="text-sm font-medium">{section.label}</span>
582
+ <ChevronRight className={classNames('w-4 h-4 ml-auto transition-transform', isActive ? 'rotate-90' : '')} />
583
+ </button>
584
+ );
585
+ })}
586
+ </nav>
587
+
588
+ {/* Status Footer */}
589
+ <div className="p-4 border-t border-gray-700/50">
590
+ <div className="flex items-center gap-2">
591
+ <div className={classNames('w-2 h-2 rounded-full', health?.status === 'ok' ? 'bg-emerald-400' : 'bg-red-400')} />
592
+ <span className="text-xs text-gray-400">{health?.status === 'ok' ? 'System Online' : 'System Offline'}</span>
593
+ </div>
594
+ </div>
595
+ </div>
596
+
597
+ {/* Main Content */}
598
+ <div className="flex-1 overflow-y-auto">
599
+ <div className="p-6">
600
+ {settingsLoading ? (
601
+ <div className="flex items-center justify-center py-16">
602
+ <div className="flex flex-col items-center gap-3">
603
+ <SettingsIcon className="w-8 h-8 text-gray-500 animate-spin" />
604
+ <p className="text-gray-400">Loading settings...</p>
605
+ </div>
606
+ </div>
607
+ ) : (
608
+ renderSectionContent()
609
+ )}
610
  </div>
611
+ </div>
612
  </div>
613
  );
614
  };
frontend/src/types/index.ts CHANGED
@@ -200,6 +200,8 @@ export interface SystemSettings {
200
  logLevel: 'debug' | 'info' | 'warn' | 'error';
201
  screenshotQuality: number;
202
  memoryPersistence: boolean;
 
 
203
  }
204
 
205
  export interface WebSocketMessage {
 
200
  logLevel: 'debug' | 'info' | 'warn' | 'error';
201
  screenshotQuality: number;
202
  memoryPersistence: boolean;
203
+ autoSave: boolean;
204
+ debugMode: boolean;
205
  }
206
 
207
  export interface WebSocketMessage {
frontend/tsconfig.tsbuildinfo CHANGED
@@ -1 +1 @@
1
- {"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/components/actionpanel.tsx","./src/components/agentview.tsx","./src/components/dashboard.tsx","./src/components/episodepanel.tsx","./src/components/memorypanel.tsx","./src/components/observationview.tsx","./src/components/pluginspage.tsx","./src/components/rewardchart.tsx","./src/components/settings.tsx","./src/components/toolregistry.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/input.tsx","./src/components/ui/select.tsx","./src/hooks/useagents.ts","./src/hooks/useepisode.ts","./src/hooks/usememory.ts","./src/hooks/usewebsocket.ts","./src/test/components.test.tsx","./src/test/helpers.test.ts","./src/test/setup.ts","./src/types/index.ts","./src/utils/helpers.ts"],"version":"5.6.3"}
 
1
+ {"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/components/actionpanel.tsx","./src/components/agentview.tsx","./src/components/dashboard.tsx","./src/components/docspage.tsx","./src/components/episodepanel.tsx","./src/components/memorypanel.tsx","./src/components/observationview.tsx","./src/components/pluginspage.tsx","./src/components/rewardchart.tsx","./src/components/settings.tsx","./src/components/toolregistry.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/input.tsx","./src/components/ui/select.tsx","./src/hooks/useagents.ts","./src/hooks/useepisode.ts","./src/hooks/usememory.ts","./src/hooks/usewebsocket.ts","./src/test/components.test.tsx","./src/test/helpers.test.ts","./src/test/setup.ts","./src/types/index.ts","./src/utils/helpers.ts"],"version":"5.6.3"}