Session 5: Rebuild frontend as Vite+React 3-panel website builder IDE
Browse files- Replaced vanilla JS chat with professional IDE (Bolt.new style)
- 3-panel layout: Sidebar (file tree + agent steps) | Editor (animated) | Preview (always-on)
- Plan Modal: asks tech stack + design style before generating
- Prompt Enhancer: transforms raw input into structured prompts
- Code animation: line-by-line fade-in at 15ms intervals
- File tree: real-time population during generation
- Live preview: always-visible iframe with console panel
- Demo fallback: landing page + dashboard demos when API quota exceeded
- Settings: API URL, HF token, temperature, max tokens (localStorage)
- ZeroGPU quota detection: isQuotaError() + isQuotaException() -> auto-fallback
- npm run build: 222KB JS (70KB gzip), 3.25s
- Updated context.md: full architecture + next steps for future sessions
- Updated README.md: current project state
- README.md +21 -24
- context.md +359 -669
- frontend/.gitignore +24 -0
- frontend/.gitkeep +0 -0
- frontend/README.md +16 -0
- frontend/agent.js +0 -266
- frontend/app.js +0 -2118
- frontend/eslint.config.js +21 -0
- frontend/index.html +5 -346
- frontend/package-lock.json +2448 -0
- frontend/package.json +29 -0
- frontend/public/favicon.svg +1 -0
- frontend/public/icons.svg +24 -0
- frontend/sandbox.js +0 -305
- frontend/src/App.css +476 -0
- frontend/src/App.jsx +346 -0
- frontend/src/components/Editor.jsx +77 -0
- frontend/src/components/PlanModal.jsx +52 -0
- frontend/src/components/Preview.jsx +80 -0
- frontend/src/components/PromptBar.jsx +68 -0
- frontend/src/components/SettingsModal.jsx +53 -0
- frontend/src/components/Sidebar.jsx +74 -0
- frontend/src/components/Toasts.jsx +13 -0
- frontend/src/index.css +189 -0
- frontend/src/main.jsx +10 -0
- frontend/src/services/api.js +148 -0
- frontend/src/services/fileParser.js +54 -0
- frontend/src/services/promptEnhancer.js +48 -0
- frontend/styles.css +0 -1479
- frontend/test_api.py +0 -202
- frontend/vite.config.js +7 -0
|
@@ -1,40 +1,37 @@
|
|
| 1 |
# MINDI 1.5 Vision-Coder
|
| 2 |
|
| 3 |
-
**Built by [MINDIGENOUS.AI](https://mindigenous.ai)**
|
| 4 |
-
|
| 5 |
-
**
|
| 6 |
-
|
| 7 |
-
**Started:** April 14, 2026
|
| 8 |
-
|
| 9 |
-
**Target Launch:** May 5, 2026
|
| 10 |
|
| 11 |
---
|
| 12 |
|
| 13 |
## What is MINDI 1.5?
|
| 14 |
|
| 15 |
-
MINDI 1.5 Vision-Coder is a multimodal
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
|
| 18 |
-
- Sees its own output via vision capabilities (CLIP ViT-L/14)
|
| 19 |
-
- Critiques its own UI/UX design and iterates
|
| 20 |
-
- Searches the internet for latest packages and documentation
|
| 21 |
-
- Tests code in an isolated sandbox environment
|
| 22 |
-
- Fixes its own errors automatically
|
| 23 |
-
- Suggests improvements to the user
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
- **Vision Encoder:** CLIP ViT-L/14
|
| 30 |
-
- **Agents:** Search + Sandbox + UI Critic + Code Generation
|
| 31 |
-
- **Training Data:** 500,000+ curated examples
|
| 32 |
-
- **Backend:** FastAPI
|
| 33 |
-
- **Output Format:** Next.js 14 + Tailwind CSS + TypeScript
|
| 34 |
|
| 35 |
## HuggingFace
|
| 36 |
|
| 37 |
-
|
|
|
|
|
|
|
| 38 |
|
| 39 |
## License
|
| 40 |
|
|
|
|
| 1 |
# MINDI 1.5 Vision-Coder
|
| 2 |
|
| 3 |
+
**Built by [MINDIGENOUS.AI](https://mindigenous.ai)**
|
| 4 |
+
**Builder:** Faaz ([@Mindigenous](https://huggingface.co/Mindigenous) on HuggingFace)
|
| 5 |
+
**Started:** April 14, 2026 | **Training Complete:** April 28, 2026 | **Frontend v2:** May 2, 2026
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
---
|
| 8 |
|
| 9 |
## What is MINDI 1.5?
|
| 10 |
|
| 11 |
+
MINDI 1.5 Vision-Coder is a multimodal AI model that generates frontend code (HTML/CSS/JS, React, Next.js, Tailwind) from text prompts and UI screenshots.
|
| 12 |
+
|
| 13 |
+
- **Architecture:** Qwen2.5-Coder-7B-Instruct + LoRA + CLIP ViT-L/14 + Vision-Language Fusion
|
| 14 |
+
- **Training:** 10,000 steps across 3 phases on AMD MI300X 192GB
|
| 15 |
+
- **Live API:** [Mindigenous/mindi-chat](https://huggingface.co/spaces/Mindigenous/mindi-chat) on HuggingFace Spaces
|
| 16 |
+
|
| 17 |
+
## Frontend — AI Website Builder
|
| 18 |
|
| 19 |
+
A professional 3-panel IDE (Bolt.new-style) for interacting with MINDI:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
```powershell
|
| 22 |
+
cd frontend
|
| 23 |
+
npm install
|
| 24 |
+
npm run dev # → http://localhost:5173
|
| 25 |
+
```
|
| 26 |
|
| 27 |
+
Features: Plan modal, prompt enhancement, code animation, live preview, file tree, demo fallback.
|
| 28 |
+
**Read `context.md` for full architecture and next steps.**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
## HuggingFace
|
| 31 |
|
| 32 |
+
- **Model:** `Mindigenous/MINDI-1.5-Vision-Coder` (private)
|
| 33 |
+
- **Dataset:** `Mindigenous/MINDI-1.5-training-data` (private)
|
| 34 |
+
- **Space:** `Mindigenous/mindi-chat` (live, ZeroGPU)
|
| 35 |
|
| 36 |
## License
|
| 37 |
|
|
@@ -1,8 +1,8 @@
|
|
| 1 |
# MINDI 1.5 Vision-Coder — Complete Project Context
|
| 2 |
|
| 3 |
-
> **Last updated:**
|
| 4 |
-
> **Purpose:** This file contains ALL context needed to continue development with any AI assistant.
|
| 5 |
-
> It covers architecture decisions, errors encountered, fixes applied, training state, and exact next steps.
|
| 6 |
|
| 7 |
---
|
| 8 |
|
|
@@ -21,791 +21,481 @@
|
|
| 21 |
- **GitHub:** `https://github.com/Faaz345/MINDI-1.5-Vision-Coder.git` (branch: `master`)
|
| 22 |
- **HuggingFace Model:** `Mindigenous/MINDI-1.5-Vision-Coder` (private, push as `master:main`)
|
| 23 |
- **HuggingFace Dataset:** `Mindigenous/MINDI-1.5-training-data` (private)
|
|
|
|
| 24 |
- **HF Token:** Set as `HF_TOKEN` environment variable (stored separately, not in repo)
|
| 25 |
|
| 26 |
---
|
| 27 |
|
| 28 |
-
## 2.
|
| 29 |
|
| 30 |
-
|
| 31 |
-
MINDI-1.5-Vision-Coder/
|
| 32 |
-
├── src/
|
| 33 |
-
│ ├── model/
|
| 34 |
-
│ │ ├── architecture.py # Qwen2.5-Coder + LoRA wrapper (NOT nn.Module)
|
| 35 |
-
│ │ ├── mindi_model.py # MINDI15 main class (nn.Module)
|
| 36 |
-
│ │ ├── vision_encoder.py # CLIP ViT-L/14 (frozen) + trainable projection
|
| 37 |
-
│ │ ├── fusion_layer.py # VisionLanguageFusion with text_gate
|
| 38 |
-
│ │ └── __init__.py
|
| 39 |
-
│ ├── training/
|
| 40 |
-
│ │ ├── mindi_trainer.py # MINDITrainer: 3-phase loop, streaming data
|
| 41 |
-
│ │ ├── data_pipeline.py # Data processing pipeline
|
| 42 |
-
│ │ └── __init__.py
|
| 43 |
-
│ ├── agents/ # Agentic pipeline (orchestrator, error fixer, UI critic)
|
| 44 |
-
│ ├── inference/ # Generation pipeline
|
| 45 |
-
│ ├── evaluation/ # Evaluation framework
|
| 46 |
-
│ ├── search/ # Tavily search agent
|
| 47 |
-
│ ├── sandbox/ # E2B/Docker code execution
|
| 48 |
-
│ ├── tokenizer/ # MINDI tokenizer wrapper
|
| 49 |
-
│ └── utils/ # Config & env loaders
|
| 50 |
-
├── scripts/
|
| 51 |
-
│ ├── train.py # Master training launcher (--dry_run, --phase, --resume)
|
| 52 |
-
│ ├── download_websight.py # Download WebSight v0.2 from HF
|
| 53 |
-
│ ├── upload_websight_images.py # Upload images to HF in batches (10K/dir limit)
|
| 54 |
-
│ ├── gpu_diagnostic.py # 6-stage GPU test for MI300X
|
| 55 |
-
│ └── ... (data processing scripts)
|
| 56 |
-
├── configs/
|
| 57 |
-
│ ├── training_config.yaml # Training hyperparameters
|
| 58 |
-
│ ├── model_config.yaml # Model architecture config
|
| 59 |
-
│ ├── data_config.yaml # Data sources and processing
|
| 60 |
-
│ └── search_config.yaml # Tavily search settings
|
| 61 |
-
├── data/
|
| 62 |
-
│ ├── processed/ # Text training data (train.jsonl, val.jsonl, test.jsonl)
|
| 63 |
-
│ ├── websight/ # Vision data (52,500 images in subdirs + JSONL)
|
| 64 |
-
│ │ ├── train.jsonl # 50,000 vision-code pairs
|
| 65 |
-
│ │ ├── val.jsonl # 2,500 vision-code pairs
|
| 66 |
-
│ │ └── images/
|
| 67 |
-
│ │ ├── 00/ # ws_0000000.jpg - ws_0009999.jpg (10K each)
|
| 68 |
-
│ │ ├── 01/
|
| 69 |
-
│ │ ├── 02/
|
| 70 |
-
│ │ ├── 03/
|
| 71 |
-
│ │ ├── 04/
|
| 72 |
-
│ │ └── 05/ # ws_0050000.jpg - ws_0052499.jpg (2,500)
|
| 73 |
-
│ ├── tokenizer/
|
| 74 |
-
│ │ ├── mindi_tokenizer/ # Custom tokenizer (vocab 151,685)
|
| 75 |
-
│ │ └── base_tokenizer/ # Original Qwen tokenizer
|
| 76 |
-
│ └── raw/ # Raw downloaded data sources
|
| 77 |
-
├── api/ # FastAPI endpoints
|
| 78 |
-
├── checkpoints/ # Model checkpoints
|
| 79 |
-
├── logs/ # Training logs
|
| 80 |
-
├── requirements.txt # Full requirements
|
| 81 |
-
├── requirements-training.txt # Lean MI300X Docker requirements
|
| 82 |
-
├── setup_mi300x.sh # MI300X Docker setup script
|
| 83 |
-
├── .gitattributes # LFS tracking for large tokenizer files
|
| 84 |
-
└── .gitignore
|
| 85 |
-
```
|
| 86 |
-
|
| 87 |
-
---
|
| 88 |
-
|
| 89 |
-
## 3. ARCHITECTURE DETAILS
|
| 90 |
-
|
| 91 |
-
### 3.1 Model Components
|
| 92 |
-
|
| 93 |
-
| Component | Class | File | Params | Trainable |
|
| 94 |
-
|-----------|-------|------|--------|-----------|
|
| 95 |
-
| Base LLM | `MINDIArchitecture` | `architecture.py` | 7.62B | No (frozen) |
|
| 96 |
-
| LoRA | via PEFT | `architecture.py` | 161.5M | Yes |
|
| 97 |
-
| CLIP Vision | `VisionEncoder` | `vision_encoder.py` | 304M | 4.2M (projection only) |
|
| 98 |
-
| Fusion | `VisionLanguageFusion` | `fusion_layer.py` | 16.8M | Yes |
|
| 99 |
-
| **Total** | `MINDI15` | `mindi_model.py` | **8.1B** | **182.5M (2.25%)** |
|
| 100 |
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
|
| 103 |
-
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
-
|
| 108 |
|
| 109 |
-
|
| 110 |
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
-
|
| 114 |
-
Image → CLIP (frozen) → 256 patches (1024) → projection (4096) → visual_tokens
|
| 115 |
-
Text → tokenizer → input_ids → LLM embedding layer → text_embeds
|
| 116 |
|
| 117 |
-
|
| 118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
| 121 |
```
|
| 122 |
|
| 123 |
-
###
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
|
|
|
| 125 |
```python
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
lora_dropout=0.05,
|
| 130 |
-
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
|
| 131 |
-
bias="none",
|
| 132 |
-
task_type=TaskType.CAUSAL_LM,
|
| 133 |
-
)
|
| 134 |
-
```
|
| 135 |
-
|
| 136 |
-
### 3.5 MINDI Special Tokens (22 total, 11 pairs)
|
| 137 |
-
|
| 138 |
-
```
|
| 139 |
-
<|think_start|> / <|think_end|> — Internal reasoning
|
| 140 |
-
<|code_start|> / <|code_end|> — Generated code blocks
|
| 141 |
-
<|file_start|> / <|file_end|> — File references
|
| 142 |
-
<|critique_start|> / <|critique_end|> — Self-critique
|
| 143 |
-
<|suggest_start|> / <|suggest_end|> — Suggestions
|
| 144 |
-
<|search_start|> / <|search_end|> — Search context
|
| 145 |
-
<|error_start|> / <|error_end|> — Error messages
|
| 146 |
-
<|fix_start|> / <|fix_end|> — Fix attempts
|
| 147 |
-
<|vision_start|> / <|vision_end|> — Vision input markers
|
| 148 |
-
<|sandbox_start|> / <|sandbox_end|> — Sandbox execution
|
| 149 |
-
<|context_start|> / <|context_end|> — Context block
|
| 150 |
```
|
| 151 |
|
| 152 |
---
|
| 153 |
|
| 154 |
-
## 4.
|
| 155 |
-
|
| 156 |
-
### 4.1 Three-Phase Training Strategy
|
| 157 |
|
| 158 |
-
|
| 159 |
-
|-------|------|-------|-----|-------|-----------|------|---------|
|
| 160 |
-
| 1 | `phase1_lora` | 5,000 | 2e-4 | 16 | LoRA only | Text-only code | Teach coding patterns |
|
| 161 |
-
| 2 | `phase2_vision_bridge` | 2,500 | 1e-5 | 8 | Vision+Fusion | WebSight images | Align visual tokens |
|
| 162 |
-
| 3 | `phase3_all` | 2,500 | 5e-5 | 12 | All trainable | Mixed text+vision | Joint fine-tuning |
|
| 163 |
|
| 164 |
-
**
|
| 165 |
|
| 166 |
-
|
| 167 |
|
| 168 |
-
|
| 169 |
-
- `data/processed/train.jsonl` — 1,304,486 examples, 4.18 GB
|
| 170 |
-
- `data/processed/val.jsonl` — 72,471 examples
|
| 171 |
-
- Sources: CodeAlpaca, CodeFeedback, EvolCode, MagicCoder, StarCoder (5 langs), Synthetic Next.js
|
| 172 |
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
- Source: HuggingFaceM4/WebSight v0.2 (UI screenshot → HTML/CSS pairs)
|
| 178 |
-
|
| 179 |
-
**WebSight JSONL format:**
|
| 180 |
-
```json
|
| 181 |
-
{
|
| 182 |
-
"id": "websight_0000001",
|
| 183 |
-
"type": "vision_code",
|
| 184 |
-
"source": "websight_v0.2",
|
| 185 |
-
"image_path": "data/websight/images/00/ws_0000001.jpg",
|
| 186 |
-
"messages": [
|
| 187 |
-
{"role": "system", "content": "You are MINDI 1.5 Vision-Coder..."},
|
| 188 |
-
{"role": "user", "content": "<|vision_start|><|vision_end|>\nGenerate the HTML/CSS code for this UI screenshot."},
|
| 189 |
-
{"role": "assistant", "content": "<|think_start|>...<|think_end|>\n<|code_start|>\n...HTML/CSS...\n<|code_end|>"}
|
| 190 |
-
],
|
| 191 |
-
"metadata": {"dataset": "websight", "version": "v0.2"}
|
| 192 |
-
}
|
| 193 |
```
|
| 194 |
|
| 195 |
-
|
| 196 |
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
**Date:** April 15, 2026 (on DigitalOcean MI300X)
|
| 222 |
-
**Command:** `python3 scripts/train.py --dry_run --no_wandb`
|
| 223 |
-
**Result:** Loss dropped from 1.94 → 0.85 in 10 steps, completed in 12.1 minutes
|
| 224 |
-
**VRAM usage:** ~14.3 GB
|
| 225 |
-
|
| 226 |
-
### 5.2 Phase 2 — First Attempt FAILED ❌
|
| 227 |
-
|
| 228 |
-
**Error:** `element 0 of tensors does not require grad and does not have a grad_fn`
|
| 229 |
-
**Root cause:** Phase 2 trains vision+fusion with LoRA frozen. Text-only data means fusion is pure passthrough (no gradient path). The fusion layer was getting zero gradients because without vision tokens, the text-only path was `return text_embeds, attention_mask` — a pure passthrough with no learnable operation.
|
| 230 |
-
**Fix:** Added `text_gate` learnable residual parameter to `VisionLanguageFusion`. Text-only path changed to: `text_embeds + sigmoid(text_gate) * (transformed - text_embeds)`. Also built the WebSight vision data pipeline to provide actual image+code pairs for Phase 2.
|
| 231 |
-
|
| 232 |
-
### 5.3 Full 3-Phase Dry Run — NOT YET COMPLETED
|
| 233 |
-
|
| 234 |
-
The MI300X GPU kept hanging/wedging (see Section 6). Phase 2 and 3 with the new WebSight data pipeline have NOT been tested yet.
|
| 235 |
-
|
| 236 |
-
---
|
| 237 |
-
|
| 238 |
-
## 6. ERRORS & FIXES — COMPLETE HISTORY
|
| 239 |
-
|
| 240 |
-
### 6.1 GPU Hang #1 — HSA_OVERRIDE_GFX_VERSION
|
| 241 |
-
|
| 242 |
-
**Symptom:** GPU completely unresponsive. `torch.cuda.get_device_name(0)` returns blank, any CUDA operation hangs.
|
| 243 |
-
**Root cause:** `HSA_OVERRIDE_GFX_VERSION=11.0.0` was set in the Docker container. This conflicts with ROCm 7.0's native MI300X/gfx942 support.
|
| 244 |
-
**Fix:** Do NOT set `HSA_OVERRIDE_GFX_VERSION`. ROCm 7.0 natively supports gfx942. Remove it from all scripts/env.
|
| 245 |
-
**Commit:** `4a33f96 Remove HSA_OVERRIDE_GFX_VERSION`
|
| 246 |
-
|
| 247 |
-
### 6.2 No Trainable Parameters in Optimizer
|
| 248 |
-
|
| 249 |
-
**Symptom:** `RuntimeError: No trainable parameters in phase 'phase1_lora'`
|
| 250 |
-
**Root cause:** `MINDIArchitecture` is a plain Python class (not `nn.Module`). When `MINDI15` calls `model.parameters()`, it doesn't find the LoRA parameters because the PeftModel isn't registered as a submodule.
|
| 251 |
-
**Fix:** Added `self.llm = self.architecture.get_model()` in `MINDI15.__init__()` to register the PeftModel as a proper nn.Module submodule. Updated `forward()` and `generate()` to use `self.llm` instead of `self.architecture.get_model()`.
|
| 252 |
-
**Commit:** `cdc806e Fix: register LLM as nn.Module submodule so optimizer finds LoRA params`
|
| 253 |
-
|
| 254 |
-
### 6.3 extra_special_tokens Format Error
|
| 255 |
-
|
| 256 |
-
**Symptom:** `TypeError` when loading tokenizer — transformers 4.55 expects `extra_special_tokens` as a dict, not a list.
|
| 257 |
-
**Fix:** Changed `data/tokenizer/mindi_tokenizer/tokenizer_config.json`: converted `extra_special_tokens` from list format to `{"token_name": {"content": "..."}}` dict format.
|
| 258 |
-
**Commit:** `02eef51 Fix extra_special_tokens: list to dict for transformers 4.55`
|
| 259 |
-
|
| 260 |
-
### 6.4 Phase 2 Gradient Flow Crash
|
| 261 |
-
|
| 262 |
-
**Symptom:** `element 0 of tensors does not require grad and does not have a grad_fn` during Phase 2
|
| 263 |
-
**Root cause:** Text-only data → no vision tokens → fusion is pure passthrough → no gradient path to fusion parameters.
|
| 264 |
-
**Fix:** (1) Added `text_gate` learnable residual gate in `VisionLanguageFusion` for text-only gradient flow. (2) Built WebSight vision data pipeline with actual image+code pairs.
|
| 265 |
-
**Commit:** `4e9835e Fix Phase 2: fusion layer processes text-only via learnable residual gate`
|
| 266 |
-
|
| 267 |
-
### 6.5 Git LFS Issues
|
| 268 |
|
| 269 |
-
|
| 270 |
-
**Fix:** Configured `.gitattributes` for LFS tracking. Ran `git lfs migrate import` to rewrite history. Force-pushed to both GitHub and HF.
|
| 271 |
-
**Commit:** `161c946 Track large tokenizer files with Git LFS`
|
| 272 |
|
| 273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
-
|
| 276 |
-
**Fix:** Use token as both username and password: `https://hf_TOKEN:hf_TOKEN@huggingface.co/Mindigenous/MINDI-1.5-Vision-Coder.git`
|
| 277 |
-
Also needed: `apt-get install -y git-lfs && git lfs install`
|
| 278 |
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
-
**
|
| 282 |
-
**Kernel log:** `amdgpu: GPU reset begin!` → `device wedged, but recovered through reset` → But GPU% stays at 100%.
|
| 283 |
-
**Fix:**
|
| 284 |
-
1. `docker stop rocm`
|
| 285 |
-
2. `echo 1 > /sys/bus/pci/devices/0000:83:00.0/reset` (PCI address from `lspci | grep AMD`)
|
| 286 |
-
3. If GPU% still 100%: `modprobe -r amdgpu && modprobe amdgpu`
|
| 287 |
-
4. Verify `rocm-smi` shows GPU% = 0% before restarting Docker
|
| 288 |
-
**Status:** Droplet was deleted. Session 2 is on `134.199.197.198`.
|
| 289 |
|
| 290 |
-
|
| 291 |
|
| 292 |
-
**
|
| 293 |
-
**Fix:** Reorganized 52,500 images into 6 subdirectories of ≤10K files (`00/` through `05/`). Upload in separate commits per subdirectory. Updated JSONL `image_path` fields to include subdirectory.
|
| 294 |
-
**Script:** `scripts/upload_websight_images.py`
|
| 295 |
|
| 296 |
-
-
|
| 297 |
|
| 298 |
-
|
| 299 |
|
| 300 |
-
|
| 301 |
|
| 302 |
-
|
| 303 |
-
- **GPU:** AMD Instinct MI300X (192GB HBM3 VRAM)
|
| 304 |
-
- **Cost:** $1.99/hr
|
| 305 |
-
- **Docker container:** Named `rocm`, accessed via `docker exec -it rocm /bin/bash`
|
| 306 |
-
- **ROCm/HIP:** 7.0.51831-a3e329ad8
|
| 307 |
-
- **PyTorch:** 2.9.0.dev20250821+rocm7.0.0
|
| 308 |
-
- **Python:** 3.10
|
| 309 |
|
| 310 |
-
|
|
|
|
|
|
|
|
|
|
| 311 |
|
| 312 |
-
|
| 313 |
-
export HF_TOKEN=<your-hf-token> # Get from HF settings page
|
| 314 |
-
export HF_HUB_DISABLE_PROGRESS_BARS=1
|
| 315 |
-
export PYTORCH_ROCM_ARCH=gfx942
|
| 316 |
-
export TOKENIZERS_PARALLELISM=false
|
| 317 |
-
# DO NOT SET: HSA_OVERRIDE_GFX_VERSION (causes GPU hang on ROCm 7.0)
|
| 318 |
```
|
| 319 |
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
```bash
|
| 323 |
-
# 1. SSH into droplet
|
| 324 |
-
ssh root@<DROPLET_IP>
|
| 325 |
-
|
| 326 |
-
# 2. Verify GPU health on host (must show 0% GPU)
|
| 327 |
-
rocm-smi
|
| 328 |
-
|
| 329 |
-
# 3. Start Docker
|
| 330 |
-
docker start rocm
|
| 331 |
-
docker exec -it rocm /bin/bash
|
| 332 |
-
|
| 333 |
-
# 4. Set environment (inside Docker)
|
| 334 |
-
export HF_TOKEN=<your-hf-token> # Get from HF settings page
|
| 335 |
-
export HF_HUB_DISABLE_PROGRESS_BARS=1
|
| 336 |
-
export PYTORCH_ROCM_ARCH=gfx942
|
| 337 |
-
export TOKENIZERS_PARALLELISM=false
|
| 338 |
-
|
| 339 |
-
# 5. Quick GPU test
|
| 340 |
-
python3 -c "import torch; print('GPU:', torch.cuda.get_device_name(0)); x=torch.randn(100,device='cuda'); print('OK:', x.sum().item())"
|
| 341 |
-
|
| 342 |
-
# 6. Install git-lfs (ignore AMD artifactory DNS warning — harmless)
|
| 343 |
-
apt-get update && apt-get install -y git-lfs
|
| 344 |
-
git lfs install
|
| 345 |
-
|
| 346 |
-
# 7. Clone code repo
|
| 347 |
-
cd /workspace
|
| 348 |
-
git clone https://$HF_TOKEN:$HF_TOKEN@huggingface.co/Mindigenous/MINDI-1.5-Vision-Coder.git
|
| 349 |
-
cd MINDI-1.5-Vision-Coder
|
| 350 |
-
|
| 351 |
-
# 8. Install requirements
|
| 352 |
-
pip install -r requirements-training.txt
|
| 353 |
|
| 354 |
-
#
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
git clone https://$HF_TOKEN:$HF_TOKEN@huggingface.co/datasets/Mindigenous/MINDI-1.5-training-data data
|
| 359 |
|
| 360 |
-
#
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
|
|
|
|
|
|
|
|
|
| 367 |
|
| 368 |
-
|
| 369 |
-
python3 scripts/gpu_diagnostic.py
|
| 370 |
|
| 371 |
-
#
|
| 372 |
-
python3 scripts/train.py --dry_run --no_wandb
|
| 373 |
|
| 374 |
-
# 14. Full training (background, survives SSH disconnect)
|
| 375 |
-
nohup python3 scripts/train.py --no_wandb > /workspace/training.log 2>&1 &
|
| 376 |
```
|
| 377 |
-
|
| 378 |
-
#
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
#
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
```
|
| 390 |
|
| 391 |
-
|
| 392 |
|
| 393 |
-
|
| 394 |
-
**Root cause:** The dataset has 52,500+ image files. `snapshot_download` paginates through the HF tree API listing all files, causing rate limiting.
|
| 395 |
-
**Fix:** Use `git clone` instead of `snapshot_download` for the dataset:
|
| 396 |
-
```bash
|
| 397 |
-
rm -rf data
|
| 398 |
-
git clone https://$HF_TOKEN:$HF_TOKEN@huggingface.co/datasets/Mindigenous/MINDI-1.5-training-data data
|
| 399 |
-
```
|
| 400 |
-
This downloads everything in a single git connection without hitting the API rate limiter.
|
| 401 |
-
**Discovered:** April 16, 2026 — Session 2
|
| 402 |
|
| 403 |
-
### 6.
|
| 404 |
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
|
|
|
|
|
|
|
|
|
| 409 |
|
| 410 |
-
### 6.
|
| 411 |
|
| 412 |
-
**
|
| 413 |
-
**Root cause:** The code repo clone creates an empty `data/` directory structure.
|
| 414 |
-
**Fix:** `rm -rf data` before cloning the dataset repo.
|
| 415 |
-
**Discovered:** April 16, 2026 — Session 2
|
| 416 |
|
| 417 |
-
|
| 418 |
|
| 419 |
-
|
| 420 |
|
| 421 |
-
|
| 422 |
|
| 423 |
```
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
│ ├── base_tokenizer/
|
| 436 |
-
│ └── mindi_tokenizer/
|
| 437 |
-
└── websight/
|
| 438 |
-
├── train.jsonl # 50K vision-code JSONL
|
| 439 |
-
├── val.jsonl # 2.5K vision-code JSONL
|
| 440 |
-
└── images/
|
| 441 |
-
├── 00/ # 10,000 JPGs
|
| 442 |
-
├── 01/ # 10,000 JPGs
|
| 443 |
-
├── 02/ # 10,000 JPGs
|
| 444 |
-
├── 03/ # 10,000 JPGs
|
| 445 |
-
├── 04/ # 10,000 JPGs (uploading as of April 16)
|
| 446 |
-
└── 05/ # 2,500 JPGs (uploading as of April 16)
|
| 447 |
```
|
| 448 |
|
| 449 |
-
**NOTE:** As of April 16, 2026, subdirectories 00-03 are uploaded. 04 and 05 are being uploaded via `scripts/upload_websight_images.py`. If upload was interrupted, re-run the script — it skips already-uploaded subdirs.
|
| 450 |
-
|
| 451 |
---
|
| 452 |
|
| 453 |
-
##
|
| 454 |
|
| 455 |
-
``
|
| 456 |
-
553fbf7 feat: initial project scaffold for MINDI 1.5 Vision-Coder
|
| 457 |
-
11e0d89 Day 1 Complete: Tokenizer setup — 22 MINDI special tokens (vocab 151,685)
|
| 458 |
-
59c6c97 Day 2 COMPLETE: 1.48M examples processed, 6GB dataset, WebSight done
|
| 459 |
-
2ff5c54 Day 3 COMPLETE: Full model architecture (7 files)
|
| 460 |
-
1c36b28 Fix train.py: mem -> memory on line 225
|
| 461 |
-
f04f58b Fix setup_mi300x.sh step 2 + add project context summary
|
| 462 |
-
35fd5fc Fix setup_mi300x.sh for Docker container on MI300X droplet
|
| 463 |
-
5fb9ec3 Add GPU diagnostic script, fix architecture loading with sync
|
| 464 |
-
161c946 Track large tokenizer files with Git LFS
|
| 465 |
-
4a33f96 Remove HSA_OVERRIDE_GFX_VERSION - ROCm 7.0 native MI300X support
|
| 466 |
-
24b5fb1 Add requirements-training.txt for MI300X Docker
|
| 467 |
-
02eef51 Fix extra_special_tokens: list to dict for transformers 4.55
|
| 468 |
-
cdc806e Fix: register LLM as nn.Module submodule so optimizer finds LoRA params
|
| 469 |
-
4e9835e Fix Phase 2: fusion layer text_gate for gradient flow
|
| 470 |
-
672896a Add WebSight vision data pipeline: download, image-aware loader, phase routing
|
| 471 |
-
```
|
| 472 |
|
| 473 |
-
|
|
|
|
| 474 |
|
| 475 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
|
| 486 |
---
|
| 487 |
|
| 488 |
-
##
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
|
| 514 |
---
|
| 515 |
|
| 516 |
-
##
|
| 517 |
|
| 518 |
-
|
| 519 |
-
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
### WATCH OUT FOR:
|
| 526 |
-
- GPU hanging after heavy I/O — check `rocm-smi` shows 0% GPU before training
|
| 527 |
-
- Data paths — WebSight images use **relative paths** from project root in JSONL
|
| 528 |
-
- `MINDIArchitecture` is NOT `nn.Module` — always use `self.llm` inside MINDI15
|
| 529 |
-
- The `text_gate` in fusion starts at 0 (sigmoid=0.5) — this is intentional
|
| 530 |
-
- On MI300X, Docker container named `rocm` — always `docker exec -it rocm /bin/bash`
|
| 531 |
|
| 532 |
---
|
| 533 |
|
| 534 |
-
##
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
``
|
| 538 |
-
|
| 539 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
|
| 541 |
-
|
| 542 |
-
$env:HF_TOKEN="<your-hf-token>"
|
| 543 |
-
python scripts/download_websight.py --num_train 50000 --num_val 2500
|
| 544 |
-
|
| 545 |
-
# Upload WebSight images to HF (handles subdirs, retry, skip)
|
| 546 |
-
python scripts/upload_websight_images.py
|
| 547 |
-
|
| 548 |
-
# Push code to GitHub + HF
|
| 549 |
-
git push origin master
|
| 550 |
-
git push hf master:main
|
| 551 |
-
```
|
| 552 |
-
|
| 553 |
-
### MI300X (Linux, Docker, inside container):
|
| 554 |
-
```bash
|
| 555 |
-
# Dry run (10 steps per phase)
|
| 556 |
-
python3 scripts/train.py --dry_run --no_wandb
|
| 557 |
|
| 558 |
-
#
|
| 559 |
-
python3 scripts/train.py --no_wandb
|
| 560 |
|
| 561 |
-
#
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
|
| 566 |
-
#
|
| 567 |
-
|
|
|
|
|
|
|
| 568 |
|
| 569 |
-
#
|
| 570 |
-
|
| 571 |
-
``
|
|
|
|
| 572 |
|
| 573 |
---
|
| 574 |
|
| 575 |
-
##
|
| 576 |
|
| 577 |
-
When
|
| 578 |
|
| 579 |
-
1. **
|
| 580 |
-
2. **
|
| 581 |
-
3. **Check WebSight upload status:**
|
| 582 |
```powershell
|
| 583 |
-
|
|
|
|
|
|
|
| 584 |
```
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
git add scripts/upload_websight_images.py context.md
|
| 589 |
-
git commit -m "Add WebSight batch uploader and project context"
|
| 590 |
-
git push origin master
|
| 591 |
-
git push hf master:main
|
| 592 |
-
```
|
| 593 |
-
6. **Spin up fresh MI300X droplet** on DigitalOcean
|
| 594 |
-
7. **Follow Section 7.3** for setup procedure
|
| 595 |
-
8. **IMPORTANT:** Use `git clone` for data download (NOT `snapshot_download` — see Section 6.9)
|
| 596 |
-
9. **IMPORTANT:** `rm -rf data` before cloning dataset repo (see Section 6.11)
|
| 597 |
-
10. **Run dry run first** to verify all 3 phases work
|
| 598 |
-
11. **Then full training** — `nohup python3 scripts/train.py --no_wandb > /workspace/training.log 2>&1 &`
|
| 599 |
|
| 600 |
---
|
| 601 |
|
| 602 |
-
##
|
| 603 |
-
|
| 604 |
-
When cloning data on MI300X using `snapshot_download`, files will land at:
|
| 605 |
-
|
| 606 |
-
| HF Repo Path | Local Path (relative to project root) |
|
| 607 |
-
|---|---|
|
| 608 |
-
| `processed/train.jsonl` | `data/processed/train.jsonl` |
|
| 609 |
-
| `processed/val.jsonl` | `data/processed/val.jsonl` |
|
| 610 |
-
| `websight/train.jsonl` | `data/websight/train.jsonl` |
|
| 611 |
-
| `websight/val.jsonl` | `data/websight/val.jsonl` |
|
| 612 |
-
| `websight/images/00/*.jpg` | `data/websight/images/00/*.jpg` |
|
| 613 |
-
| `tokenizer/mindi_tokenizer/*` | `data/tokenizer/mindi_tokenizer/*` |
|
| 614 |
-
|
| 615 |
-
The `snapshot_download(local_dir='data')` call places everything correctly because the HF repo structure mirrors the local `data/` directory.
|
| 616 |
-
|
| 617 |
-
---
|
| 618 |
|
| 619 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
|
| 621 |
-
#
|
|
|
|
| 622 |
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
rm -rf data
|
| 626 |
-
git clone https://$HF_TOKEN:$HF_TOKEN@huggingface.co/datasets/Mindigenous/MINDI-1.5-training-data data
|
| 627 |
```
|
| 628 |
|
| 629 |
-
###
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
```
|
| 636 |
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
|
|
|
|
|
|
| 641 |
```
|
| 642 |
|
| 643 |
-
###
|
| 644 |
-
|
| 645 |
```bash
|
| 646 |
-
|
| 647 |
-
|
|
|
|
|
|
|
| 648 |
|
| 649 |
-
#
|
|
|
|
| 650 |
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
docker exec rocm rocm-smi # GPU usage
|
| 654 |
-
docker exec rocm ps aux | grep train.py # Process check
|
| 655 |
```
|
| 656 |
|
| 657 |
-
Notes:
|
| 658 |
-
- Use the background command if you want the process detached from your SSH session.
|
| 659 |
-
- The `scripts/train.py` launcher does not accept a `--log_file` flag; redirect output into `/workspace/training.log` instead.
|
| 660 |
-
- Line-buffered stdout has been added to `src/training/mindi_trainer.py` so logs should appear in near real-time when using `tail -f`.
|
| 661 |
-
|
| 662 |
-
## 17. DROPLET HISTORY
|
| 663 |
-
|
| 664 |
-
| Session | Date | Droplet IP | Status | Notes |
|
| 665 |
-
|---------|------|-----------|--------|-------|
|
| 666 |
-
| 1 | April 15, 2026 | `134.199.194.245` | Deleted | Phase 1 dry run passed. GPU hung during heavy I/O. |
|
| 667 |
-
| 2 | April 16, 2026 | `134.199.197.198` | Deleted | Phase 1 steps 0→4250 completed. Credits exhausted. |
|
| 668 |
-
| 3 | April 19, 2026 | `165.245.141.141` | Active | Phase 1 resumed at step 4250. Resume bug fixed. |
|
| 669 |
-
|
| 670 |
-
---
|
| 671 |
-
|
| 672 |
-
*This context file was created on April 16, 2026 during Claude Opus 4.6 session to ensure project continuity.*
|
| 673 |
-
*Updated on April 16, 2026 — Session 2: snapshot_download 429 fix, bash escaping, fresh droplet setup.*
|
| 674 |
-
*Updated on April 28, 2026 — Training complete, frontend built, API deployed.*
|
| 675 |
-
*Updated on April 30, 2026 — Session 4: Fixed critical frontend bugs, Gradio 5.x API protocol, ZeroGPU quota handling.*
|
| 676 |
-
|
| 677 |
---
|
| 678 |
|
| 679 |
-
##
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
- *
|
| 685 |
-
-
|
| 686 |
-
- *
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
-
|
| 690 |
-
-
|
| 691 |
-
-
|
| 692 |
-
-
|
| 693 |
-
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
-
|
| 697 |
-
-
|
| 698 |
-
- **Fix:** For HF Spaces, use `fetch(base, {mode:'no-cors'})` which succeeds if the Space is reachable
|
| 699 |
-
- **File:** `frontend/app.js`
|
| 700 |
-
|
| 701 |
-
**Improvement: ZeroGPU quota error handling**
|
| 702 |
-
- Reduced `@spaces.GPU(duration=120)` → `@spaces.GPU(duration=60)` (inference is fast after model cache)
|
| 703 |
-
- Added try-except in `chat_fn()` to return clean JSON error instead of crashing when GPU quota exceeded
|
| 704 |
-
- **File:** `hf_space/app.py`
|
| 705 |
-
|
| 706 |
-
### Session 4 Status
|
| 707 |
-
- ✅ Frontend bugs fixed (handleSend reference, duplicate handlers)
|
| 708 |
-
- ✅ Gradio 5.x API protocol implemented (SSE v3 two-step flow)
|
| 709 |
-
- ✅ Health check fixed — shows green "MINDI · HF Space" status
|
| 710 |
-
- ✅ Space updated on HF — `Mindigenous/mindi-chat`
|
| 711 |
-
- ⚠️ ZeroGPU daily quota limit can block visitors — PRO users get 8x more quota
|
| 712 |
-
- ✅ Agent system (agent.js + sandbox.js) scaffolded — Plan→Generate→Execute→Verify→Fix loop
|
| 713 |
-
- 📋 Next: Wait for quota reset, then test full end-to-end flow with real model inference
|
| 714 |
-
|
| 715 |
-
### Training Summary
|
| 716 |
-
All 3 phases of MINDI 1.5 Vision-Coder training are COMPLETE:
|
| 717 |
-
|
| 718 |
-
| Phase | Steps | Status | Platform |
|
| 719 |
-
|-------|-------|--------|----------|
|
| 720 |
-
| Phase 1 (LoRA) | 5,000 | ✅ Complete | DigitalOcean MI300X |
|
| 721 |
-
| Phase 2 (Vision Bridge) | 2,500 | ✅ Complete | DigitalOcean MI300X |
|
| 722 |
-
| Phase 3 (Joint) steps 0-1500 | 1,500 | ✅ Complete | DigitalOcean MI300X |
|
| 723 |
-
| Phase 3 (Joint) steps 1500-2500 | 1,000 | ✅ Complete | Modal A100-40GB |
|
| 724 |
-
|
| 725 |
-
### Modal Training Details
|
| 726 |
-
- Resumed from step 1500 checkpoint on Modal A100-40GB ($2.10/hr)
|
| 727 |
-
- Config patched at runtime: batch_size=2, max_length=2048 (from 6/4096)
|
| 728 |
-
- Total Modal cost: ~$28 ($30 credits)
|
| 729 |
-
- Final loss: 0.25–0.40 range
|
| 730 |
-
|
| 731 |
-
### HuggingFace Checkpoints (Mindigenous/MINDI-1.5-Vision-Coder)
|
| 732 |
-
All checkpoints uploaded to `checkpoints/` directory:
|
| 733 |
-
- Phase 1: 16 checkpoints (step250 → step5000)
|
| 734 |
-
- Phase 2: 10 checkpoints (step250 → step2500)
|
| 735 |
-
- Phase 3: `phase3_all_step500`, `step1000`, `step1500`, `step2000`, `phase3_all_step2500_final`, `phase3_final`
|
| 736 |
-
|
| 737 |
-
### Model Test Results (April 28, 2026)
|
| 738 |
-
- ✅ Code generation (text-only): Matrix exponentiation fibonacci
|
| 739 |
-
- ✅ HTML/CSS generation: Gradient + responsive design
|
| 740 |
-
- ✅ Vision (image input): Processed dummy image
|
| 741 |
-
- ✅ Agentic (bug fix): Identified subtraction→addition bug
|
| 742 |
-
- VRAM usage: 17.2 GB (A100-40GB)
|
| 743 |
-
|
| 744 |
-
---
|
| 745 |
-
|
| 746 |
-
## 19. FRONTEND
|
| 747 |
-
|
| 748 |
-
### Location: `frontend/`
|
| 749 |
-
- `index.html` — Three-panel layout (sidebar + chat + code preview)
|
| 750 |
-
- `styles.css` — Premium dark theme with purple/blue gradients
|
| 751 |
-
- `app.js` — Chat logic, image upload, code extraction, demo mode
|
| 752 |
-
|
| 753 |
-
### Features
|
| 754 |
-
- Chat interface with code block rendering (Prism.js)
|
| 755 |
-
- Image upload for vision-to-code
|
| 756 |
-
- Code preview panel with tabs (Code / Preview / Sections)
|
| 757 |
-
- Special token parsing (thinking, critique, fix, error)
|
| 758 |
-
- Demo mode (works without API — simulated responses)
|
| 759 |
-
- Settings modal (double-click MINDI logo) to configure API endpoint
|
| 760 |
-
- Responsive design (mobile + desktop)
|
| 761 |
-
|
| 762 |
-
### To Run Locally
|
| 763 |
-
```bash
|
| 764 |
-
cd frontend
|
| 765 |
-
python -m http.server 8080
|
| 766 |
-
# Open http://localhost:8080
|
| 767 |
```
|
| 768 |
|
| 769 |
-
---
|
| 770 |
|
| 771 |
-
|
| 772 |
|
| 773 |
-
##
|
| 774 |
-
FastAPI web endpoint that:
|
| 775 |
-
1. Loads MINDI 1.5 from volume checkpoint on container startup
|
| 776 |
-
2. Exposes `/api/generate` (POST) and `/api/health` (GET)
|
| 777 |
-
3. Accepts text + optional base64 image
|
| 778 |
-
4. Returns response + parsed special token sections
|
| 779 |
-
5. CORS enabled for frontend
|
| 780 |
|
| 781 |
-
|
| 782 |
-
```bash
|
| 783 |
-
modal deploy modal_api.py
|
| 784 |
-
# Returns a URL like: https://mindigenous-ai--mindi-api-api.modal.run
|
| 785 |
-
```
|
| 786 |
|
| 787 |
-
|
| 788 |
-
-
|
| 789 |
-
|
| 790 |
-
|
|
|
|
|
|
|
| 791 |
|
| 792 |
-
|
| 793 |
-
1. Open frontend at http://localhost:8080
|
| 794 |
-
2. Double-click the MINDI logo (top-left sidebar)
|
| 795 |
-
3. Enter the Modal API URL
|
| 796 |
-
4. Save settings
|
| 797 |
|
| 798 |
---
|
| 799 |
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
### Budget
|
| 803 |
-
- Modal: $2.21 remaining (~1 hour A100 time)
|
| 804 |
-
- DigitalOcean: exhausted
|
| 805 |
-
|
| 806 |
-
### Next Steps
|
| 807 |
-
1. Deploy API when more credits available
|
| 808 |
-
2. Host frontend on Vercel/GitHub Pages (free)
|
| 809 |
-
3. Consider HuggingFace Spaces (free T4) with 4-bit quantization as alternative
|
| 810 |
-
4. Push frontend to GitHub/HF repos
|
| 811 |
-
|
|
|
|
| 1 |
# MINDI 1.5 Vision-Coder — Complete Project Context
|
| 2 |
|
| 3 |
+
> **Last updated:** May 2, 2026 (Session 5)
|
| 4 |
+
> **Purpose:** This file contains ALL context needed to continue development with any AI assistant.
|
| 5 |
+
> It covers architecture decisions, errors encountered, fixes applied, training state, frontend state, and exact next steps.
|
| 6 |
|
| 7 |
---
|
| 8 |
|
|
|
|
| 21 |
- **GitHub:** `https://github.com/Faaz345/MINDI-1.5-Vision-Coder.git` (branch: `master`)
|
| 22 |
- **HuggingFace Model:** `Mindigenous/MINDI-1.5-Vision-Coder` (private, push as `master:main`)
|
| 23 |
- **HuggingFace Dataset:** `Mindigenous/MINDI-1.5-training-data` (private)
|
| 24 |
+
- **HuggingFace Space:** `Mindigenous/mindi-chat` — live Gradio 5.x Space (ZeroGPU)
|
| 25 |
- **HF Token:** Set as `HF_TOKEN` environment variable (stored separately, not in repo)
|
| 26 |
|
| 27 |
---
|
| 28 |
|
| 29 |
+
## 2. TRAINING STATUS — COMPLETE ✅
|
| 30 |
|
| 31 |
+
All 3 phases of MINDI 1.5 Vision-Coder training are COMPLETE:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
+
| Phase | Steps | Status | Platform |
|
| 34 |
+
|-------|-------|--------|----------|
|
| 35 |
+
| Phase 1 (LoRA) | 5,000 | ✅ Complete | DigitalOcean MI300X |
|
| 36 |
+
| Phase 2 (Vision Bridge) | 2,500 | ✅ Complete | DigitalOcean MI300X |
|
| 37 |
+
| Phase 3 (Joint) 0→1500 | 1,500 | ✅ Complete | DigitalOcean MI300X |
|
| 38 |
+
| Phase 3 (Joint) 1500→2500 | 1,000 | ✅ Complete | Modal A100-40GB |
|
| 39 |
|
| 40 |
+
**Final loss:** 0.25–0.40 range
|
| 41 |
+
**VRAM:** 17.2 GB on A100-40GB
|
| 42 |
+
**All checkpoints:** Uploaded to `checkpoints/` in HF model repo
|
| 43 |
|
| 44 |
+
### HuggingFace Checkpoints (Mindigenous/MINDI-1.5-Vision-Coder)
|
| 45 |
+
- Phase 1: 16 checkpoints (step250 → step5000)
|
| 46 |
+
- Phase 2: 10 checkpoints (step250 → step2500)
|
| 47 |
+
- Phase 3: `phase3_all_step500`, `step1000`, `step1500`, `step2000`, `phase3_all_step2500_final`, `phase3_final`
|
| 48 |
|
| 49 |
+
---
|
| 50 |
|
| 51 |
+
## 3. LIVE API — HuggingFace SPACE
|
| 52 |
|
| 53 |
+
**Space URL:** `https://mindigenous-mindi-chat.hf.space`
|
| 54 |
+
**Space ID:** `Mindigenous/mindi-chat`
|
| 55 |
+
**Framework:** Gradio 5.23.0 (ZeroGPU)
|
| 56 |
+
**Protocol:** SSE v3 — two-step: POST to submit → GET to stream result
|
| 57 |
|
| 58 |
+
### API Call Pattern (Gradio 5.x SSE v3)
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
```javascript
|
| 61 |
+
// Step 1: Submit job
|
| 62 |
+
POST https://mindigenous-mindi-chat.hf.space/gradio_api/call/chat_fn
|
| 63 |
+
Headers: { "Content-Type": "application/json", "Authorization": "Bearer hf_..." }
|
| 64 |
+
Body: { "data": [prompt, imageArg, temperature, maxTokens, historyJson] }
|
| 65 |
+
Response: { "event_id": "..." }
|
| 66 |
|
| 67 |
+
// Step 2: Stream result
|
| 68 |
+
GET https://mindigenous-mindi-chat.hf.space/gradio_api/call/chat_fn/{event_id}
|
| 69 |
+
Parse SSE: find "event: complete" → next line "data: [...]"
|
| 70 |
+
Parse data[0] as JSON: { response: "...", sections: {} }
|
| 71 |
```
|
| 72 |
|
| 73 |
+
### ZeroGPU Quota
|
| 74 |
+
- **Anonymous users:** Very low quota (hits "GPU task aborted" error quickly)
|
| 75 |
+
- **Authenticated users (HF token):** ~8× higher quota
|
| 76 |
+
- **Quota errors throw as exceptions** with message containing "GPU task aborted" or "zerogpu"
|
| 77 |
+
- **Fix:** Always send `Authorization: Bearer <HF_TOKEN>` header
|
| 78 |
|
| 79 |
+
### Gradio Function Signature
|
| 80 |
```python
|
| 81 |
+
# hf_space/app.py — chat_fn
|
| 82 |
+
def chat_fn(prompt: str, image: dict|None, temperature: float, max_tokens: int, history_json: str) -> str:
|
| 83 |
+
# Returns JSON string: {"response": "...", "sections": {...}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
```
|
| 85 |
|
| 86 |
---
|
| 87 |
|
| 88 |
+
## 4. FRONTEND — NEW VITE + REACT WEBSITE BUILDER ⭐ (Session 5 Work)
|
|
|
|
|
|
|
| 89 |
|
| 90 |
+
### What Was Built (May 2, 2026)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
+
The old vanilla HTML/CSS/JS chat frontend was completely replaced with a **professional 3-panel website builder IDE** (similar to Bolt.new / v0.dev), built with Vite + React.
|
| 93 |
|
| 94 |
+
**The old frontend is backed up in:** `frontend/_legacy/`
|
| 95 |
|
| 96 |
+
### How to Run
|
|
|
|
|
|
|
|
|
|
| 97 |
|
| 98 |
+
```powershell
|
| 99 |
+
cd "d:\Desktop 31st Jan 2026\MINDI 1.5 vision-coder\frontend"
|
| 100 |
+
npm install # only first time
|
| 101 |
+
npm run dev # starts at http://localhost:5173
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
```
|
| 103 |
|
| 104 |
+
### New Frontend Structure
|
| 105 |
|
| 106 |
+
```
|
| 107 |
+
frontend/
|
| 108 |
+
├── index.html # Shell with Google Fonts (Inter + JetBrains Mono)
|
| 109 |
+
├── package.json # Vite 8.x + React 19 + prismjs + lucide-react
|
| 110 |
+
├── vite.config.js # Vite config
|
| 111 |
+
├── _legacy/ # Old vanilla JS chat frontend (backed up)
|
| 112 |
+
└── src/
|
| 113 |
+
├── main.jsx # React entry point
|
| 114 |
+
├── index.css # Design system (CSS tokens, reset, animations)
|
| 115 |
+
├── App.jsx # Main app — all state management + generation flow
|
| 116 |
+
├── App.css # All layout + component styles (3-panel IDE)
|
| 117 |
+
├── components/
|
| 118 |
+
│ ├── Sidebar.jsx # File tree + Agent Progress + status indicator
|
| 119 |
+
│ ├── Editor.jsx # Code editor with line-by-line animation + tabs
|
| 120 |
+
│ ├── Preview.jsx # Always-visible iframe preview + Console panel
|
| 121 |
+
│ ├── PromptBar.jsx # Bottom prompt input (auto-resize, send/stop)
|
| 122 |
+
│ ├── PlanModal.jsx # Clarifying questions (tech stack, design style)
|
| 123 |
+
│ ├── SettingsModal.jsx # API URL, HF token, temperature, max tokens
|
| 124 |
+
│ └── Toasts.jsx # Toast notifications
|
| 125 |
+
└── services/
|
| 126 |
+
├── api.js # Gradio SSE v3 integration + auth + demo fallback
|
| 127 |
+
├── promptEnhancer.js # Analyzes prompt → asks questions → structured prompt
|
| 128 |
+
└── fileParser.js # Extracts files from model response markdown
|
| 129 |
+
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
+
### Layout
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
```
|
| 134 |
+
┌──────────────┬─────────────────────────┬──────────────────┐
|
| 135 |
+
│ SIDEBAR │ CODE EDITOR │ LIVE PREVIEW │
|
| 136 |
+
│ (260px) │ (flex: 1) │ (420px) │
|
| 137 |
+
│ │ │ │
|
| 138 |
+
│ MINDI 1.5 │ 🌐 index.html │ ● Preview │
|
| 139 |
+
│ brand │ 1 <!DOCTYPE html> │ [Rendered HTML] │
|
| 140 |
+
│ │ 2 <html lang="en"> │ │
|
| 141 |
+
│ FILES (1) │ 3 <head> │ │
|
| 142 |
+
│ 🌐 index. │ ... │ CONSOLE │
|
| 143 |
+
│ html │ │ > Page rendered │
|
| 144 |
+
│ │ │ │
|
| 145 |
+
│ AGENT │ │ │
|
| 146 |
+
│ PROGRESS │ │ │
|
| 147 |
+
│ ✅ Enhancing│ │ │
|
| 148 |
+
│ ✅ Generating│ │ │
|
| 149 |
+
│ ✅ Complete │ │ │
|
| 150 |
+
│ │ │ │
|
| 151 |
+
│ ● MINDI · │ │ │
|
| 152 |
+
│ Connected │ │ │
|
| 153 |
+
├──────────────┴─────────────────────────┴──────────────────┤
|
| 154 |
+
│ [Describe what you want to build...] [Send] │
|
| 155 |
+
│ MINDI 1.5 Vision-Coder Shift+Enter new line │
|
| 156 |
+
└───��────────────────────────────────────────────────────────┘
|
| 157 |
+
```
|
| 158 |
|
| 159 |
+
### Key Features
|
|
|
|
|
|
|
| 160 |
|
| 161 |
+
1. **Plan Modal** — When user submits prompt without specifying tech stack or theme, a "Configure Your Project" modal appears with:
|
| 162 |
+
- Tech stack: HTML+CSS+JS / React / Next.js / Vue
|
| 163 |
+
- Design style: Dark / Light / Gradient / Minimal
|
| 164 |
+
- "Skip & Generate" and "Generate ⚡" buttons
|
| 165 |
|
| 166 |
+
2. **Prompt Enhancer** (`src/services/promptEnhancer.js`) — Transforms raw input into structured prompts with design requirements, responsiveness rules, font choices, no-placeholder rules.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
+
3. **Code Animation** — Lines appear one by one at 15ms intervals with `line-appear` CSS animation as code generates.
|
| 169 |
|
| 170 |
+
4. **File Tree** — Files parsed from model response appear in sidebar with fade-in animation. Click to switch active file in editor.
|
|
|
|
|
|
|
| 171 |
|
| 172 |
+
5. **Live Preview** — Always-visible iframe on right renders HTML output. "Open in new tab" and "Copy HTML" buttons.
|
| 173 |
|
| 174 |
+
6. **Demo Fallback** — When API quota exceeded or any error occurs, pre-built demo responses for common prompts (landing page, dashboard) render automatically. No white screen.
|
| 175 |
|
| 176 |
+
7. **Settings** — Click the MINDI logo (top-left) to open Settings: configure API URL, HF Token, Temperature, Max Tokens.
|
| 177 |
|
| 178 |
+
### Error Handling in api.js
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
+
```javascript
|
| 181 |
+
// Two separate detection mechanisms:
|
| 182 |
+
isQuotaError(result) // Response-level: checks result.response + result.sections.error
|
| 183 |
+
isQuotaException(errMsg) // Exception-level: checks thrown error message
|
| 184 |
|
| 185 |
+
// Both match: zerogpu | gpu quota | gpu task aborted | task aborted | unlogged user
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
```
|
| 187 |
|
| 188 |
+
When quota error detected → immediately falls back to `generateDemo(prompt)` which returns pre-built HTML.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
+
### Demo Responses Available
|
| 191 |
+
- `/landing|hero|page|website/i` → Lumina landing page (Tailwind, gradient, features section)
|
| 192 |
+
- `/dashboard|chart|analytics|admin/i` → Pulsegrid dashboard (sidebar, stat cards, bar chart)
|
| 193 |
+
- Default → Simple MINDI hello card
|
|
|
|
| 194 |
|
| 195 |
+
### Settings Persistence
|
| 196 |
+
Saved in `localStorage` under key `mindi.builder.v1`:
|
| 197 |
+
```json
|
| 198 |
+
{
|
| 199 |
+
"apiUrl": "https://mindigenous-mindi-chat.hf.space",
|
| 200 |
+
"hfToken": "hf_...",
|
| 201 |
+
"temperature": 0.7,
|
| 202 |
+
"maxTokens": 2048
|
| 203 |
+
}
|
| 204 |
+
```
|
| 205 |
|
| 206 |
+
---
|
|
|
|
| 207 |
|
| 208 |
+
## 5. DIRECTORY STRUCTURE (Full Project)
|
|
|
|
| 209 |
|
|
|
|
|
|
|
| 210 |
```
|
| 211 |
+
MINDI-1.5-Vision-Coder/
|
| 212 |
+
├── src/ # Model source code
|
| 213 |
+
│ ├── model/
|
| 214 |
+
│ │ ├── architecture.py # Qwen2.5-Coder + LoRA wrapper (NOT nn.Module)
|
| 215 |
+
│ │ ├── mindi_model.py # MINDI15 main class (nn.Module)
|
| 216 |
+
│ │ ├── vision_encoder.py # CLIP ViT-L/14 (frozen) + trainable projection
|
| 217 |
+
│ │ ├── fusion_layer.py # VisionLanguageFusion with text_gate
|
| 218 |
+
│ │ └── __init__.py
|
| 219 |
+
│ ├── training/
|
| 220 |
+
│ │ ├── mindi_trainer.py # MINDITrainer: 3-phase loop, streaming data
|
| 221 |
+
│ │ ├── data_pipeline.py # Data processing pipeline
|
| 222 |
+
│ │ └── __init__.py
|
| 223 |
+
│ └── ...
|
| 224 |
+
├── scripts/
|
| 225 |
+
│ ├── train.py # Master training launcher
|
| 226 |
+
│ ├── download_websight.py
|
| 227 |
+
│ ├── upload_websight_images.py
|
| 228 |
+
│ └── gpu_diagnostic.py
|
| 229 |
+
├── hf_space/
|
| 230 |
+
│ ├── app.py # Gradio Space — live at Mindigenous/mindi-chat
|
| 231 |
+
│ └── requirements.txt
|
| 232 |
+
├── frontend/ # ⭐ NEW: Vite + React website builder
|
| 233 |
+
│ ├── index.html
|
| 234 |
+
│ ├── package.json
|
| 235 |
+
│ ├── _legacy/ # Old vanilla JS chat (backup)
|
| 236 |
+
│ └── src/ # (see Section 4 above)
|
| 237 |
+
├── api/ # FastAPI endpoints (future)
|
| 238 |
+
├── modal_api.py # Modal.com A100 API server
|
| 239 |
+
├── modal_train.py # Modal.com training script
|
| 240 |
+
├── data/ # Local training data
|
| 241 |
+
├── configs/ # Training configs
|
| 242 |
+
├── context.md # ← THIS FILE
|
| 243 |
+
└── ...
|
| 244 |
```
|
| 245 |
|
| 246 |
+
---
|
| 247 |
|
| 248 |
+
## 6. ARCHITECTURE DETAILS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
|
| 250 |
+
### 6.1 Model Components
|
| 251 |
|
| 252 |
+
| Component | Class | File | Params | Trainable |
|
| 253 |
+
|-----------|-------|------|--------|-----------|
|
| 254 |
+
| Base LLM | `MINDIArchitecture` | `architecture.py` | 7.62B | No (frozen) |
|
| 255 |
+
| LoRA | via PEFT | `architecture.py` | 161.5M | Yes |
|
| 256 |
+
| CLIP Vision | `VisionEncoder` | `vision_encoder.py` | 304M | 4.2M (projection only) |
|
| 257 |
+
| Fusion | `VisionLanguageFusion` | `fusion_layer.py` | 16.8M | Yes |
|
| 258 |
+
| **Total** | `MINDI15` | `mindi_model.py` | **8.1B** | **182.5M (2.25%)** |
|
| 259 |
|
| 260 |
+
### 6.2 CRITICAL Architecture Notes
|
| 261 |
|
| 262 |
+
1. **`MINDIArchitecture` is NOT an `nn.Module`** — it's a plain Python wrapper. The actual trainable PeftModel is accessed via `self.architecture.get_model()` and registered as `self.llm` in `MINDI15.__init__()`.
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
+
2. **`self.llm = self.architecture.get_model()`** — Required so `model.parameters()` finds LoRA params.
|
| 265 |
|
| 266 |
+
3. **Fusion layer has `text_gate`** — Learnable scalar (init=0) for gradient flow during text-only batches.
|
| 267 |
|
| 268 |
+
### 6.3 MINDI Special Tokens (22 total, 11 pairs)
|
| 269 |
|
| 270 |
```
|
| 271 |
+
<|think_start|> / <|think_end|> — Internal reasoning
|
| 272 |
+
<|code_start|> / <|code_end|> — Generated code blocks
|
| 273 |
+
<|file_start|> / <|file_end|> — File references
|
| 274 |
+
<|critique_start|> / <|critique_end|> — Self-critique
|
| 275 |
+
<|suggest_start|> / <|suggest_end|> — Suggestions
|
| 276 |
+
<|search_start|> / <|search_end|> — Search context
|
| 277 |
+
<|error_start|> / <|error_end|> — Error messages
|
| 278 |
+
<|fix_start|> / <|fix_end|> — Fix attempts
|
| 279 |
+
<|vision_start|> / <|vision_end|> — Vision input markers
|
| 280 |
+
<|sandbox_start|> / <|sandbox_end|> — Sandbox execution
|
| 281 |
+
<|context_start|> / <|context_end|> — Context block
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
```
|
| 283 |
|
|
|
|
|
|
|
| 284 |
---
|
| 285 |
|
| 286 |
+
## 7. HF SPACE — app.py KEY DETAILS
|
| 287 |
|
| 288 |
+
**File:** `hf_space/app.py`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
+
### System Prompt (no identity hallucination fix)
|
| 291 |
+
The system prompt explicitly states: "You are MINDI 1.5 Vision-Coder, created by Mindigenous. You are NOT GPT-4, Claude, or any other AI..."
|
| 292 |
|
| 293 |
+
### chat_fn Signature
|
| 294 |
+
```python
|
| 295 |
+
@spaces.GPU(duration=60)
|
| 296 |
+
def chat_fn(prompt, image, temperature, max_tokens, history_json):
|
| 297 |
+
# history_json is a JSON string of [{"role": ..., "content": ...}, ...]
|
| 298 |
+
# Returns: JSON string {"response": "...", "sections": {...}}
|
| 299 |
+
```
|
| 300 |
|
| 301 |
+
### Gradio Interface
|
| 302 |
+
```python
|
| 303 |
+
gr.Interface(
|
| 304 |
+
fn=chat_fn,
|
| 305 |
+
inputs=[
|
| 306 |
+
gr.Textbox(label="Prompt"),
|
| 307 |
+
gr.Image(type="filepath", label="Image"),
|
| 308 |
+
gr.Slider(0, 2, value=0.7, label="Temperature"),
|
| 309 |
+
gr.Slider(128, 4096, value=2048, label="Max Tokens"),
|
| 310 |
+
gr.Textbox(label="History JSON", visible=False),
|
| 311 |
+
],
|
| 312 |
+
outputs=gr.Textbox(label="Response"),
|
| 313 |
+
api_name="chat_fn"
|
| 314 |
+
)
|
| 315 |
+
```
|
| 316 |
|
| 317 |
---
|
| 318 |
|
| 319 |
+
## 8. KNOWN ERRORS & FIXES HISTORY
|
| 320 |
+
|
| 321 |
+
### Training Errors (all fixed ✅)
|
| 322 |
+
| # | Error | Fix |
|
| 323 |
+
|---|-------|-----|
|
| 324 |
+
| 6.1 | GPU hang — HSA_OVERRIDE_GFX_VERSION | Do NOT set this var on ROCm 7.0 |
|
| 325 |
+
| 6.2 | No trainable params in optimizer | `self.llm = self.architecture.get_model()` |
|
| 326 |
+
| 6.3 | extra_special_tokens format error | Changed from list to dict in tokenizer_config.json |
|
| 327 |
+
| 6.4 | Phase 2 gradient flow crash | Added `text_gate` residual in VisionLanguageFusion |
|
| 328 |
+
| 6.5 | Git LFS push failures | `.gitattributes` + `git lfs migrate import` |
|
| 329 |
+
| 6.6 | HF auth for MI300X clone | Use token as both username+password in git URL |
|
| 330 |
+
| 6.7 | GPU hang after heavy I/O | PCI reset: `echo 1 > /sys/bus/pci/devices/0000:83:00.0/reset` |
|
| 331 |
+
| 6.8 | HF upload limits (10K/dir, 25K/commit) | Reorganized images into 6 subdirs |
|
| 332 |
+
| 6.9 | snapshot_download HTTP 429 | Use `git clone` instead |
|
| 333 |
+
| 6.10 | Bash history expansion `!'` | Use multi-line python or single-quoted strings |
|
| 334 |
+
| 6.11 | Data dir already exists on clone | `rm -rf data` before cloning dataset repo |
|
| 335 |
+
|
| 336 |
+
### Frontend API Errors (all fixed ✅)
|
| 337 |
+
| # | Error | Fix |
|
| 338 |
+
|---|-------|-----|
|
| 339 |
+
| 6.12 | `handleSend` ReferenceError in old app.js | `let activeSend = send` pattern (now in _legacy) |
|
| 340 |
+
| 6.13 | Gradio 3.x → 5.x API mismatch (404 on /api/predict) | Rewrote to SSE v3 two-step flow |
|
| 341 |
+
| 6.14 | Health check misdetects Space as offline | Use `fetch(base, {mode:'no-cors'})` for HF Spaces |
|
| 342 |
+
| 6.15 | GPU quota blocks demo — no fallback | `isQuotaError()` + `isQuotaException()` → auto demo |
|
| 343 |
+
| 6.16 | handlePlanSubmit catch had no demo fallback | Added demo fallback to all catch blocks in App.jsx |
|
| 344 |
|
| 345 |
---
|
| 346 |
|
| 347 |
+
## 9. SESSION HISTORY
|
| 348 |
|
| 349 |
+
| Session | Date | Key Work |
|
| 350 |
+
|---------|------|----------|
|
| 351 |
+
| 1 | April 15, 2026 | Phase 1 dry run. GPU hang resolved. |
|
| 352 |
+
| 2 | April 16, 2026 | Phase 1 training 0→4250. WebSight data uploaded. |
|
| 353 |
+
| 3 | April 19–28, 2026 | Phase 1→2→3 complete. Model deployed to HF Space. |
|
| 354 |
+
| 4 | April 30, 2026 | Fixed Gradio API protocol. HF token auth. ZeroGPU quota handling. Agent scaffolded. |
|
| 355 |
+
| 5 | May 2, 2026 | **Rebuilt frontend as Vite+React 3-panel IDE.** Prompt enhancer, plan modal, code animation, live preview, file tree, demo fallback. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
|
| 357 |
---
|
| 358 |
|
| 359 |
+
## 10. WHAT WORKS ✅
|
| 360 |
+
|
| 361 |
+
1. **Model training** — All 3 phases complete, checkpoints on HF
|
| 362 |
+
2. **HF Space** — Live at `Mindigenous/mindi-chat`, Gradio 5.x SSE v3
|
| 363 |
+
3. **New Frontend (Vite+React)** — `http://localhost:5173`
|
| 364 |
+
- 3-panel IDE (Sidebar | Editor | Preview)
|
| 365 |
+
- Plan Modal (tech stack + design style questions)
|
| 366 |
+
- Prompt Enhancer (raw input → structured prompt)
|
| 367 |
+
- Code animation (line-by-line fade-in)
|
| 368 |
+
- File tree (real-time population during generation)
|
| 369 |
+
- Live preview (always-visible iframe)
|
| 370 |
+
- Demo fallback (landing page + dashboard demos)
|
| 371 |
+
- Settings modal (API URL, HF token, temperature)
|
| 372 |
+
- ZeroGPU quota detection + auto-fallback
|
| 373 |
+
4. **Build** — `npm run build` → 222KB JS (70KB gzip), 3.25s
|
| 374 |
|
| 375 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
|
| 377 |
+
## 11. WHAT REMAINS ❌
|
|
|
|
| 378 |
|
| 379 |
+
### High Priority
|
| 380 |
+
1. **Add HF token to Settings** — Without token, demo fallback always used. Real MINDI output requires `hf_...` token in Settings modal.
|
| 381 |
+
2. **Make suggestion pills clickable** — "Landing Page", "Dashboard" etc. chips on welcome screen should trigger generation when clicked.
|
| 382 |
+
3. **Syntax highlighting** — Add Prism.js token coloring to the code editor.
|
| 383 |
|
| 384 |
+
### Medium Priority
|
| 385 |
+
4. **Vision loop** — Feed preview screenshots back to MINDI for automated visual QA (captureScreenshot → base64 → callMINDI).
|
| 386 |
+
5. **Multi-file support** — Model generates single-file HTML currently. Add prompt instruction for `// filename:` markers to split into HTML/CSS/JS.
|
| 387 |
+
6. **Download project button** — Let user download generated files as a ZIP.
|
| 388 |
|
| 389 |
+
### Low Priority
|
| 390 |
+
7. **WebContainer SDK** — For projects that need Node.js execution (Next.js, npm packages).
|
| 391 |
+
8. **Fine-tuning for multi-file output** — Train on structured output format with `// filename:` markers.
|
| 392 |
+
9. **Deploy frontend** — Host on Vercel or GitHub Pages (free).
|
| 393 |
|
| 394 |
---
|
| 395 |
|
| 396 |
+
## 12. NEXT SESSION CHECKLIST
|
| 397 |
|
| 398 |
+
When starting a new AI assistant session:
|
| 399 |
|
| 400 |
+
1. **Read this file** first (most important)
|
| 401 |
+
2. **Run frontend:**
|
|
|
|
| 402 |
```powershell
|
| 403 |
+
cd "d:\Desktop 31st Jan 2026\MINDI 1.5 vision-coder\frontend"
|
| 404 |
+
npm run dev
|
| 405 |
+
# Opens at http://localhost:5173
|
| 406 |
```
|
| 407 |
+
3. **Add HF token** in Settings (click MINDI logo → Settings → paste `hf_...` token)
|
| 408 |
+
4. **Test with real MINDI model** — type "landing page", skip plan modal, verify real response comes back
|
| 409 |
+
5. **Continue from "What Remains" section** above — start with suggestion chips or syntax highlighting
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
|
| 411 |
---
|
| 412 |
|
| 413 |
+
## 13. COMMANDS REFERENCE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
|
| 415 |
+
### Frontend (Windows PowerShell)
|
| 416 |
+
```powershell
|
| 417 |
+
# Run dev server
|
| 418 |
+
cd "d:\Desktop 31st Jan 2026\MINDI 1.5 vision-coder\frontend"
|
| 419 |
+
npm run dev # http://localhost:5173
|
| 420 |
|
| 421 |
+
# Build for production
|
| 422 |
+
npm run build # dist/ folder
|
| 423 |
|
| 424 |
+
# Check build
|
| 425 |
+
npx vite build 2>&1 | Select-Object -Last 10
|
|
|
|
|
|
|
| 426 |
```
|
| 427 |
|
| 428 |
+
### Git
|
| 429 |
+
```powershell
|
| 430 |
+
git add -A
|
| 431 |
+
git commit -m "..."
|
| 432 |
+
git push origin master # GitHub
|
| 433 |
+
git push hf master:main # HuggingFace
|
| 434 |
```
|
| 435 |
|
| 436 |
+
### Local (Windows, PowerShell, in venv)
|
| 437 |
+
```powershell
|
| 438 |
+
& ".\venv\Scripts\Activate.ps1"
|
| 439 |
+
$env:HF_TOKEN="<your-hf-token>"
|
| 440 |
+
python scripts/download_websight.py --num_train 50000 --num_val 2500
|
| 441 |
+
python scripts/upload_websight_images.py
|
| 442 |
```
|
| 443 |
|
| 444 |
+
### MI300X (if spinning up again)
|
|
|
|
| 445 |
```bash
|
| 446 |
+
export HF_TOKEN=<your-hf-token>
|
| 447 |
+
export PYTORCH_ROCM_ARCH=gfx942
|
| 448 |
+
export TOKENIZERS_PARALLELISM=false
|
| 449 |
+
# DO NOT SET: HSA_OVERRIDE_GFX_VERSION
|
| 450 |
|
| 451 |
+
# GPU test
|
| 452 |
+
python3 -c "import torch; print('GPU:', torch.cuda.get_device_name(0)); x=torch.randn(100,device='cuda'); print('OK:', x.sum().item())"
|
| 453 |
|
| 454 |
+
# Full training
|
| 455 |
+
nohup python3 scripts/train.py --no_wandb > /workspace/training.log 2>&1 &
|
|
|
|
|
|
|
| 456 |
```
|
| 457 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 458 |
---
|
| 459 |
|
| 460 |
+
## 14. DESIGN SYSTEM (Frontend)
|
| 461 |
+
|
| 462 |
+
CSS variables defined in `src/index.css`:
|
| 463 |
+
|
| 464 |
+
```css
|
| 465 |
+
--bg-0: #07070c; /* Deepest background */
|
| 466 |
+
--bg-1: #0a0a12;
|
| 467 |
+
--panel: #111120; /* Sidebar, modals */
|
| 468 |
+
--border: rgba(255,255,255,.06);
|
| 469 |
+
--text: #ececf1;
|
| 470 |
+
--text-2: #b4b4c4;
|
| 471 |
+
--text-mute: #7a7a8c;
|
| 472 |
+
--purple: #7c3aed;
|
| 473 |
+
--purple-light: #a78bfa;
|
| 474 |
+
--blue: #2563eb;
|
| 475 |
+
--grad: linear-gradient(135deg, #7c3aed 0%, #2563eb 100%);
|
| 476 |
+
--sans: 'Inter', ...;
|
| 477 |
+
--mono: 'JetBrains Mono', ...;
|
| 478 |
+
--sidebar-w: 260px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
```
|
| 480 |
|
| 481 |
+
Key animations: `fadeIn`, `line-appear`, `float`, `pulse`, `spin`, `pop-in`, `toast-in`
|
| 482 |
|
| 483 |
+
---
|
| 484 |
|
| 485 |
+
## 15. MODEL QUALITY NOTES
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 486 |
|
| 487 |
+
MINDI 1.5 is a 7B model with ~10K training steps. Known characteristics:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
|
| 489 |
+
| Issue | Status | Mitigation |
|
| 490 |
+
|-------|--------|-----------|
|
| 491 |
+
| Identity hallucination ("I am GPT-4") | ✅ Fixed via system prompt | Strong MINDI identity in `hf_space/app.py` |
|
| 492 |
+
| Basic/simple HTML output | ⚠️ Expected for 7B | Prompt enhancer adds design requirements |
|
| 493 |
+
| Weak image understanding | ⚠️ Only 2.5K vision steps | Prompt still works for text-only generation |
|
| 494 |
+
| No multi-file output | ⚠️ Not trained on it | Single complete file works fine |
|
| 495 |
|
| 496 |
+
**The prompt enhancer compensates for most quality issues** by structuring prompts with explicit design requirements (fonts, colors, responsiveness, no-placeholders rule, complete code requirement).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
|
| 498 |
---
|
| 499 |
|
| 500 |
+
*Updated May 2, 2026 — Session 5: Rebuilt frontend as Vite+React 3-panel website builder IDE.*
|
| 501 |
+
*Previous sessions: April 15–30, 2026 — Model training (3 phases), HF Space deployment, API fixes.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# React + Vite
|
| 2 |
+
|
| 3 |
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
| 4 |
+
|
| 5 |
+
Currently, two official plugins are available:
|
| 6 |
+
|
| 7 |
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
| 8 |
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
| 9 |
+
|
| 10 |
+
## React Compiler
|
| 11 |
+
|
| 12 |
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
| 13 |
+
|
| 14 |
+
## Expanding the ESLint configuration
|
| 15 |
+
|
| 16 |
+
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
|
@@ -1,266 +0,0 @@
|
|
| 1 |
-
/* =============================================================
|
| 2 |
-
MINDI Agent — Orchestrator
|
| 3 |
-
Plan → Generate → Execute → Verify → Fix loop.
|
| 4 |
-
Turns raw MINDI model into an autonomous coding agent.
|
| 5 |
-
============================================================= */
|
| 6 |
-
|
| 7 |
-
const MINDIAgent = (() => {
|
| 8 |
-
'use strict';
|
| 9 |
-
|
| 10 |
-
const MAX_RETRIES = 3;
|
| 11 |
-
const STEP_TYPES = {
|
| 12 |
-
PLAN: 'plan',
|
| 13 |
-
GENERATE: 'generate',
|
| 14 |
-
EXECUTE: 'execute',
|
| 15 |
-
VERIFY: 'verify',
|
| 16 |
-
FIX: 'fix',
|
| 17 |
-
DONE: 'done',
|
| 18 |
-
ERROR: 'error',
|
| 19 |
-
};
|
| 20 |
-
|
| 21 |
-
const STATUS = { PENDING: 'pending', RUNNING: 'running', SUCCESS: 'success', FAILED: 'failed' };
|
| 22 |
-
|
| 23 |
-
// ── Agent state ────────────────────────────────────────
|
| 24 |
-
function createRun() {
|
| 25 |
-
return {
|
| 26 |
-
id: 'run-' + Date.now().toString(36),
|
| 27 |
-
steps: [],
|
| 28 |
-
currentCode: null,
|
| 29 |
-
language: null,
|
| 30 |
-
iteration: 0,
|
| 31 |
-
startTime: Date.now(),
|
| 32 |
-
status: 'running',
|
| 33 |
-
};
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
function addStep(run, type, status = STATUS.RUNNING, detail = '') {
|
| 37 |
-
const step = {
|
| 38 |
-
id: run.steps.length,
|
| 39 |
-
type,
|
| 40 |
-
status,
|
| 41 |
-
detail,
|
| 42 |
-
startTime: Date.now(),
|
| 43 |
-
endTime: null,
|
| 44 |
-
};
|
| 45 |
-
run.steps.push(step);
|
| 46 |
-
return step;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
function completeStep(step, status, detail = '') {
|
| 50 |
-
step.status = status;
|
| 51 |
-
step.endTime = Date.now();
|
| 52 |
-
if (detail) step.detail = detail;
|
| 53 |
-
}
|
| 54 |
-
|
| 55 |
-
// ── Prompt templates ───────────────────────────────────
|
| 56 |
-
function planPrompt(userRequest) {
|
| 57 |
-
return `Break this coding request into clear, numbered implementation steps (max 5 steps). Only list the steps, nothing else.
|
| 58 |
-
|
| 59 |
-
Request: ${userRequest}`;
|
| 60 |
-
}
|
| 61 |
-
|
| 62 |
-
function generatePrompt(userRequest, plan, previousCode, previousError) {
|
| 63 |
-
let prompt = `Write COMPLETE, WORKING code for this request. Include ALL necessary HTML, CSS, and JavaScript in a single file. Do NOT leave any placeholders, TODOs, or "add more here" comments. Every feature must work.
|
| 64 |
-
|
| 65 |
-
Request: ${userRequest}`;
|
| 66 |
-
|
| 67 |
-
if (plan) prompt += `\n\nPlan:\n${plan}`;
|
| 68 |
-
|
| 69 |
-
if (previousCode && previousError) {
|
| 70 |
-
prompt += `\n\nPrevious code had this error:\n${previousError}\n\nPrevious code:\n\`\`\`\n${previousCode}\n\`\`\`\n\nFix the error and return the COMPLETE corrected code.`;
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
return prompt;
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
function verifyPrompt(code, output, errors, screenshotDescription) {
|
| 77 |
-
let prompt = `Review this code and its execution result. Is it working correctly?
|
| 78 |
-
|
| 79 |
-
Code:
|
| 80 |
-
\`\`\`
|
| 81 |
-
${code.slice(0, 3000)}
|
| 82 |
-
\`\`\`
|
| 83 |
-
|
| 84 |
-
Console output: ${output || '(none)'}
|
| 85 |
-
Errors: ${errors || '(none)'}`;
|
| 86 |
-
|
| 87 |
-
if (screenshotDescription) {
|
| 88 |
-
prompt += `\nScreenshot shows: ${screenshotDescription}`;
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
prompt += `\n\nRespond with either:
|
| 92 |
-
- "PASS" if the code works correctly
|
| 93 |
-
- "FAIL: <description of what's wrong>" if there are issues`;
|
| 94 |
-
|
| 95 |
-
return prompt;
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
// ── Extract code from response ─────────────────────────
|
| 99 |
-
function extractCode(response) {
|
| 100 |
-
// Try fenced code blocks first
|
| 101 |
-
const re = /```(\w+)?\s*\n([\s\S]*?)```/g;
|
| 102 |
-
let last = null, m;
|
| 103 |
-
while ((m = re.exec(response)) !== null) {
|
| 104 |
-
last = { language: (m[1] || '').toLowerCase(), code: m[2] };
|
| 105 |
-
}
|
| 106 |
-
if (last) return last;
|
| 107 |
-
|
| 108 |
-
// Try special tokens
|
| 109 |
-
const codeMatch = response.match(/<\|code_start\|>([\s\S]*?)<\|code_end\|>/);
|
| 110 |
-
if (codeMatch) {
|
| 111 |
-
return { language: '', code: codeMatch[1].trim() };
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
return null;
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
// ── Main agent run ─────────────────────────────────────
|
| 118 |
-
async function run(userPrompt, options = {}) {
|
| 119 |
-
const {
|
| 120 |
-
apiCall, // async (prompt, image?) => {response, sections}
|
| 121 |
-
sandboxContainer, // DOM element for iframe preview
|
| 122 |
-
onStep, // (run, step) => void — UI callback
|
| 123 |
-
image = null, // optional image for vision
|
| 124 |
-
} = options;
|
| 125 |
-
|
| 126 |
-
const agentRun = createRun();
|
| 127 |
-
const notify = (step) => onStep && onStep(agentRun, step);
|
| 128 |
-
|
| 129 |
-
try {
|
| 130 |
-
// ── Step 1: PLAN ──────────────────────────────────
|
| 131 |
-
const planStep = addStep(agentRun, STEP_TYPES.PLAN);
|
| 132 |
-
notify(planStep);
|
| 133 |
-
|
| 134 |
-
let plan = null;
|
| 135 |
-
try {
|
| 136 |
-
const planResult = await apiCall(planPrompt(userPrompt), image);
|
| 137 |
-
plan = planResult.response;
|
| 138 |
-
completeStep(planStep, STATUS.SUCCESS, plan.split('\n').filter(l => /^\d/.test(l.trim())).length + ' steps identified');
|
| 139 |
-
} catch (e) {
|
| 140 |
-
completeStep(planStep, STATUS.FAILED, e.message);
|
| 141 |
-
// Continue without plan
|
| 142 |
-
}
|
| 143 |
-
notify(planStep);
|
| 144 |
-
|
| 145 |
-
// ── Step 2+: GENERATE → EXECUTE → VERIFY → FIX loop
|
| 146 |
-
let previousCode = null;
|
| 147 |
-
let previousError = null;
|
| 148 |
-
|
| 149 |
-
for (let iteration = 0; iteration <= MAX_RETRIES; iteration++) {
|
| 150 |
-
agentRun.iteration = iteration;
|
| 151 |
-
|
| 152 |
-
// ── GENERATE ──────────────────────────────────
|
| 153 |
-
const genStep = addStep(agentRun, iteration === 0 ? STEP_TYPES.GENERATE : STEP_TYPES.FIX);
|
| 154 |
-
genStep.detail = iteration === 0 ? 'Generating code...' : `Fixing (attempt ${iteration}/${MAX_RETRIES})...`;
|
| 155 |
-
notify(genStep);
|
| 156 |
-
|
| 157 |
-
let codeResult;
|
| 158 |
-
try {
|
| 159 |
-
const genResult = await apiCall(
|
| 160 |
-
generatePrompt(userPrompt, plan, previousCode, previousError),
|
| 161 |
-
image
|
| 162 |
-
);
|
| 163 |
-
codeResult = extractCode(genResult.response);
|
| 164 |
-
|
| 165 |
-
if (!codeResult) {
|
| 166 |
-
// No code block found — use entire response as code
|
| 167 |
-
codeResult = { language: '', code: genResult.response };
|
| 168 |
-
}
|
| 169 |
-
|
| 170 |
-
agentRun.currentCode = codeResult.code;
|
| 171 |
-
agentRun.language = codeResult.language || CodeSandbox.detectLanguage(codeResult.code);
|
| 172 |
-
const lines = codeResult.code.split('\n').length;
|
| 173 |
-
completeStep(genStep, STATUS.SUCCESS, `${lines} lines of ${agentRun.language}`);
|
| 174 |
-
} catch (e) {
|
| 175 |
-
completeStep(genStep, STATUS.FAILED, e.message);
|
| 176 |
-
notify(genStep);
|
| 177 |
-
break;
|
| 178 |
-
}
|
| 179 |
-
notify(genStep);
|
| 180 |
-
|
| 181 |
-
// ── EXECUTE ───────────────────────────────────
|
| 182 |
-
const execStep = addStep(agentRun, STEP_TYPES.EXECUTE);
|
| 183 |
-
execStep.detail = `Running ${agentRun.language} code...`;
|
| 184 |
-
notify(execStep);
|
| 185 |
-
|
| 186 |
-
let execResult;
|
| 187 |
-
try {
|
| 188 |
-
execResult = await CodeSandbox.execute(
|
| 189 |
-
codeResult.code,
|
| 190 |
-
agentRun.language,
|
| 191 |
-
sandboxContainer
|
| 192 |
-
);
|
| 193 |
-
|
| 194 |
-
const output = execResult.logs.join('\n') || '(no output)';
|
| 195 |
-
if (execResult.success) {
|
| 196 |
-
completeStep(execStep, STATUS.SUCCESS, `Ran in ${execResult.duration}ms — ${output.slice(0, 100)}`);
|
| 197 |
-
} else {
|
| 198 |
-
completeStep(execStep, STATUS.FAILED, execResult.errors.join('\n').slice(0, 200));
|
| 199 |
-
}
|
| 200 |
-
} catch (e) {
|
| 201 |
-
execResult = { success: false, errors: [e.message], logs: [] };
|
| 202 |
-
completeStep(execStep, STATUS.FAILED, e.message);
|
| 203 |
-
}
|
| 204 |
-
notify(execStep);
|
| 205 |
-
|
| 206 |
-
// ── VERIFY ────────────────────────────────────
|
| 207 |
-
if (execResult.success) {
|
| 208 |
-
const verifyStep = addStep(agentRun, STEP_TYPES.VERIFY);
|
| 209 |
-
verifyStep.detail = 'Checking output...';
|
| 210 |
-
notify(verifyStep);
|
| 211 |
-
|
| 212 |
-
// Try to take a screenshot for visual verification
|
| 213 |
-
let screenshot = null;
|
| 214 |
-
if (execResult.iframe && agentRun.language === 'html') {
|
| 215 |
-
try {
|
| 216 |
-
screenshot = await CodeSandbox.captureScreenshot(execResult.iframe);
|
| 217 |
-
} catch { /* ignore */ }
|
| 218 |
-
}
|
| 219 |
-
|
| 220 |
-
// Simple check: if no errors and has output, consider it passing
|
| 221 |
-
// For a more thorough check, we'd send screenshot back to MINDI
|
| 222 |
-
const hasOutput = execResult.logs.length > 0 || agentRun.language === 'html';
|
| 223 |
-
if (hasOutput) {
|
| 224 |
-
completeStep(verifyStep, STATUS.SUCCESS, 'Code runs without errors ✓');
|
| 225 |
-
notify(verifyStep);
|
| 226 |
-
|
| 227 |
-
// DONE!
|
| 228 |
-
const doneStep = addStep(agentRun, STEP_TYPES.DONE);
|
| 229 |
-
completeStep(doneStep, STATUS.SUCCESS, `Completed in ${iteration + 1} iteration(s)`);
|
| 230 |
-
agentRun.status = 'success';
|
| 231 |
-
notify(doneStep);
|
| 232 |
-
break;
|
| 233 |
-
} else {
|
| 234 |
-
completeStep(verifyStep, STATUS.FAILED, 'No output produced');
|
| 235 |
-
previousCode = codeResult.code;
|
| 236 |
-
previousError = 'Code produced no output';
|
| 237 |
-
notify(verifyStep);
|
| 238 |
-
}
|
| 239 |
-
} else {
|
| 240 |
-
// Execution failed — prepare for retry
|
| 241 |
-
previousCode = codeResult.code;
|
| 242 |
-
previousError = execResult.errors.join('\n');
|
| 243 |
-
|
| 244 |
-
if (iteration === MAX_RETRIES) {
|
| 245 |
-
const errStep = addStep(agentRun, STEP_TYPES.ERROR);
|
| 246 |
-
completeStep(errStep, STATUS.FAILED, `Failed after ${MAX_RETRIES + 1} attempts: ${previousError.slice(0, 200)}`);
|
| 247 |
-
agentRun.status = 'failed';
|
| 248 |
-
notify(errStep);
|
| 249 |
-
}
|
| 250 |
-
}
|
| 251 |
-
}
|
| 252 |
-
} catch (e) {
|
| 253 |
-
const errStep = addStep(agentRun, STEP_TYPES.ERROR);
|
| 254 |
-
completeStep(errStep, STATUS.FAILED, e.message);
|
| 255 |
-
agentRun.status = 'failed';
|
| 256 |
-
notify(errStep);
|
| 257 |
-
}
|
| 258 |
-
|
| 259 |
-
agentRun.endTime = Date.now();
|
| 260 |
-
return agentRun;
|
| 261 |
-
}
|
| 262 |
-
|
| 263 |
-
return { run, STEP_TYPES, STATUS, extractCode };
|
| 264 |
-
})();
|
| 265 |
-
|
| 266 |
-
if (typeof module !== 'undefined') module.exports = MINDIAgent;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,2118 +0,0 @@
|
|
| 1 |
-
/* =============================================================
|
| 2 |
-
MINDI 1.5 — Vision-Coder · Frontend logic
|
| 3 |
-
============================================================= */
|
| 4 |
-
|
| 5 |
-
(() => {
|
| 6 |
-
'use strict';
|
| 7 |
-
|
| 8 |
-
// ----------------------------------------------------------------
|
| 9 |
-
// Constants
|
| 10 |
-
// ----------------------------------------------------------------
|
| 11 |
-
const API_DEFAULT = 'https://mindigenous-mindi-chat.hf.space';
|
| 12 |
-
const STORAGE_KEY = 'mindi.v1.state';
|
| 13 |
-
const MAX_TEXTAREA = 200;
|
| 14 |
-
const COLD_START_HINT_MS = 10_000; // show cold-start hint after 10s
|
| 15 |
-
|
| 16 |
-
const SECTION_ORDER = ['thinking', 'code', 'critique', 'fix', 'error', 'suggest', 'file'];
|
| 17 |
-
const SECTION_LABELS = {
|
| 18 |
-
thinking: 'Thinking',
|
| 19 |
-
code: 'Code',
|
| 20 |
-
critique: 'Critique',
|
| 21 |
-
fix: 'Fix',
|
| 22 |
-
error: 'Error',
|
| 23 |
-
suggest: 'Suggestion',
|
| 24 |
-
file: 'File',
|
| 25 |
-
};
|
| 26 |
-
// mapping from raw token name → sections key
|
| 27 |
-
const TOKEN_TO_KEY = {
|
| 28 |
-
think: 'thinking',
|
| 29 |
-
code: 'code',
|
| 30 |
-
critique: 'critique',
|
| 31 |
-
fix: 'fix',
|
| 32 |
-
error: 'error',
|
| 33 |
-
suggest: 'suggest',
|
| 34 |
-
file: 'file',
|
| 35 |
-
};
|
| 36 |
-
|
| 37 |
-
// ----------------------------------------------------------------
|
| 38 |
-
// State (persisted to localStorage)
|
| 39 |
-
// ----------------------------------------------------------------
|
| 40 |
-
const defaultState = () => ({
|
| 41 |
-
apiUrl: API_DEFAULT,
|
| 42 |
-
hfToken: '', // optional HF PRO token to bypass anonymous ZeroGPU quota
|
| 43 |
-
visionEnabled: false, // default OFF — see notes in Settings; vision-language fusion is currently low-quality
|
| 44 |
-
temperature: 0.7,
|
| 45 |
-
maxTokens: 2048,
|
| 46 |
-
chats: [], // [{id, title, createdAt, updatedAt, messages: [{role, content, images?}]}]
|
| 47 |
-
currentId: null,
|
| 48 |
-
});
|
| 49 |
-
|
| 50 |
-
const state = loadState();
|
| 51 |
-
|
| 52 |
-
function loadState() {
|
| 53 |
-
try {
|
| 54 |
-
const raw = localStorage.getItem(STORAGE_KEY);
|
| 55 |
-
if (!raw) return defaultState();
|
| 56 |
-
const parsed = JSON.parse(raw);
|
| 57 |
-
return Object.assign(defaultState(), parsed);
|
| 58 |
-
} catch {
|
| 59 |
-
return defaultState();
|
| 60 |
-
}
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
function saveState() {
|
| 64 |
-
try {
|
| 65 |
-
localStorage.setItem(STORAGE_KEY, JSON.stringify({
|
| 66 |
-
apiUrl: state.apiUrl,
|
| 67 |
-
hfToken: state.hfToken,
|
| 68 |
-
temperature: state.temperature,
|
| 69 |
-
maxTokens: state.maxTokens,
|
| 70 |
-
chats: state.chats,
|
| 71 |
-
currentId: state.currentId,
|
| 72 |
-
}));
|
| 73 |
-
} catch (e) {
|
| 74 |
-
console.warn('[mindi] failed to save state', e);
|
| 75 |
-
}
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
// Runtime-only state (not persisted)
|
| 79 |
-
const runtime = {
|
| 80 |
-
status: 'connecting', // connecting | online | demo | offline
|
| 81 |
-
authBlocked: false, // true if last API call hit a quota/auth error
|
| 82 |
-
pendingImages: [], // [{name, dataUrl}]
|
| 83 |
-
isSending: false,
|
| 84 |
-
lastCode: null, // {language, code}
|
| 85 |
-
lastSections: null, // {thinking: [], ...}
|
| 86 |
-
};
|
| 87 |
-
|
| 88 |
-
// ----------------------------------------------------------------
|
| 89 |
-
// DOM
|
| 90 |
-
// ----------------------------------------------------------------
|
| 91 |
-
const $ = (s) => document.querySelector(s);
|
| 92 |
-
const $$ = (s) => Array.from(document.querySelectorAll(s));
|
| 93 |
-
|
| 94 |
-
const els = {
|
| 95 |
-
body: document.body,
|
| 96 |
-
sidebar: $('#sidebar'),
|
| 97 |
-
scrim: $('#scrim'),
|
| 98 |
-
brand: $('#brand'),
|
| 99 |
-
newChatBtn: $('#new-chat-btn'),
|
| 100 |
-
search: $('#search'),
|
| 101 |
-
history: $('#chat-history'),
|
| 102 |
-
historyEmpty: $('#history-empty'),
|
| 103 |
-
statusDot: $('#status-dot'),
|
| 104 |
-
statusText: $('#status-text'),
|
| 105 |
-
|
| 106 |
-
chat: $('#chat'),
|
| 107 |
-
hamburger: $('#hamburger'),
|
| 108 |
-
chatTitle: $('#chat-title'),
|
| 109 |
-
togglePreview: $('#toggle-preview'),
|
| 110 |
-
|
| 111 |
-
welcome: $('#welcome'),
|
| 112 |
-
quickCards: $$('.quick-card'),
|
| 113 |
-
messages: $('#messages'),
|
| 114 |
-
|
| 115 |
-
composer: $('#composer'),
|
| 116 |
-
composerImages: $('#composer-images'),
|
| 117 |
-
attachBtn: $('#attach-btn'),
|
| 118 |
-
fileInput: $('#file-input'),
|
| 119 |
-
promptInput: $('#prompt-input'),
|
| 120 |
-
sendBtn: $('#send-btn'),
|
| 121 |
-
|
| 122 |
-
preview: $('#preview'),
|
| 123 |
-
tabs: $$('.tab'),
|
| 124 |
-
panes: $$('.preview-pane'),
|
| 125 |
-
copyCode: $('#copy-code'),
|
| 126 |
-
downloadCode: $('#download-code'),
|
| 127 |
-
codeOut: $('#code-out'),
|
| 128 |
-
codeOutInner: $('#code-out-inner'),
|
| 129 |
-
emptyCode: $('#empty-code'),
|
| 130 |
-
liveFrame: $('#live-frame'),
|
| 131 |
-
emptyLive: $('#empty-live'),
|
| 132 |
-
sections: $('#sections'),
|
| 133 |
-
emptySections: $('#empty-sections'),
|
| 134 |
-
|
| 135 |
-
settingsModal: $('#settings-modal'),
|
| 136 |
-
settingsUrl: $('#settings-url'),
|
| 137 |
-
settingsHfToken:$('#settings-hf-token'),
|
| 138 |
-
hfTokenStatus: $('#hf-token-status'),
|
| 139 |
-
settingsVision: $('#settings-vision'),
|
| 140 |
-
settingsTemp: $('#settings-temp'),
|
| 141 |
-
settingsTokens: $('#settings-tokens'),
|
| 142 |
-
tempVal: $('#temp-val'),
|
| 143 |
-
tokensVal: $('#tokens-val'),
|
| 144 |
-
saveSettings: $('#save-settings'),
|
| 145 |
-
|
| 146 |
-
toasts: $('#toasts'),
|
| 147 |
-
};
|
| 148 |
-
|
| 149 |
-
// ----------------------------------------------------------------
|
| 150 |
-
// Utilities
|
| 151 |
-
// ----------------------------------------------------------------
|
| 152 |
-
function uid() {
|
| 153 |
-
return 'c-' + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
|
| 154 |
-
}
|
| 155 |
-
function escapeHtml(s) {
|
| 156 |
-
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
| 157 |
-
.replace(/"/g, '"').replace(/'/g, ''');
|
| 158 |
-
}
|
| 159 |
-
function escapeAttr(s) {
|
| 160 |
-
return escapeHtml(s);
|
| 161 |
-
}
|
| 162 |
-
function fileToDataUrl(file) {
|
| 163 |
-
return new Promise((resolve, reject) => {
|
| 164 |
-
const reader = new FileReader();
|
| 165 |
-
reader.onload = () => resolve(reader.result);
|
| 166 |
-
reader.onerror = reject;
|
| 167 |
-
reader.readAsDataURL(file);
|
| 168 |
-
});
|
| 169 |
-
}
|
| 170 |
-
function debounce(fn, ms) {
|
| 171 |
-
let t = null;
|
| 172 |
-
return (...args) => {
|
| 173 |
-
clearTimeout(t);
|
| 174 |
-
t = setTimeout(() => fn(...args), ms);
|
| 175 |
-
};
|
| 176 |
-
}
|
| 177 |
-
function downloadFile(filename, content) {
|
| 178 |
-
const blob = new Blob([content], { type: 'text/plain' });
|
| 179 |
-
const url = URL.createObjectURL(blob);
|
| 180 |
-
const a = document.createElement('a');
|
| 181 |
-
a.href = url;
|
| 182 |
-
a.download = filename;
|
| 183 |
-
document.body.appendChild(a);
|
| 184 |
-
a.click();
|
| 185 |
-
document.body.removeChild(a);
|
| 186 |
-
URL.revokeObjectURL(url);
|
| 187 |
-
}
|
| 188 |
-
function relativeDateGroup(ts) {
|
| 189 |
-
const d = new Date(ts);
|
| 190 |
-
const now = new Date();
|
| 191 |
-
const start = (x) => { const z = new Date(x); z.setHours(0,0,0,0); return z; };
|
| 192 |
-
const today = start(now);
|
| 193 |
-
const yesterday = new Date(today); yesterday.setDate(today.getDate() - 1);
|
| 194 |
-
const week = new Date(today); week.setDate(today.getDate() - 7);
|
| 195 |
-
|
| 196 |
-
if (d >= today) return 'Today';
|
| 197 |
-
if (d >= yesterday) return 'Yesterday';
|
| 198 |
-
if (d >= week) return 'This Week';
|
| 199 |
-
return 'Earlier';
|
| 200 |
-
}
|
| 201 |
-
function languageFromCode(code) {
|
| 202 |
-
const trimmed = code.trim();
|
| 203 |
-
if (/^<!doctype|^<html|^<\w+[\s>]/i.test(trimmed)) return 'markup';
|
| 204 |
-
if (/^(import|from|def|class|print|if __name__)/m.test(trimmed)) return 'python';
|
| 205 |
-
if (/^(import|export|const|function|class|let|var|=>)/m.test(trimmed)) return 'javascript';
|
| 206 |
-
if (/^[\s\S]*\{[\s\S]*\}\s*$/.test(trimmed) && /^\s*"\w+"\s*:/m.test(trimmed)) return 'json';
|
| 207 |
-
if (/(SELECT|INSERT|UPDATE|DELETE|CREATE TABLE)/i.test(trimmed)) return 'sql';
|
| 208 |
-
if (/^[\.#]?[\w-]+\s*\{[^}]*:\s*[^;]+;/.test(trimmed)) return 'css';
|
| 209 |
-
return 'plaintext';
|
| 210 |
-
}
|
| 211 |
-
|
| 212 |
-
// ----------------------------------------------------------------
|
| 213 |
-
// Output cleaning + section parsing
|
| 214 |
-
// ----------------------------------------------------------------
|
| 215 |
-
// Strip all special tokens for chat display
|
| 216 |
-
function cleanForDisplay(raw) {
|
| 217 |
-
if (!raw) return '';
|
| 218 |
-
let t = String(raw);
|
| 219 |
-
|
| 220 |
-
// Section start/end tokens
|
| 221 |
-
Object.keys(TOKEN_TO_KEY).forEach((tok) => {
|
| 222 |
-
t = t.replace(new RegExp(`<\\|${tok}_start\\|>`, 'g'), '');
|
| 223 |
-
t = t.replace(new RegExp(`<\\|${tok}_end\\|>`, 'g'), '');
|
| 224 |
-
});
|
| 225 |
-
|
| 226 |
-
// Conversation tokens
|
| 227 |
-
t = t.replace(/<\|im_start\|>/g, '');
|
| 228 |
-
t = t.replace(/<\|im_end\|>/g, '');
|
| 229 |
-
t = t.replace(/<\|endoftext\|>/g, '');
|
| 230 |
-
|
| 231 |
-
// Role prefixes at line starts
|
| 232 |
-
t = t.replace(/^(system|user|assistant)\s*\n/gim, '');
|
| 233 |
-
|
| 234 |
-
return t.trim();
|
| 235 |
-
}
|
| 236 |
-
|
| 237 |
-
// Parse the special token sections out of the raw response
|
| 238 |
-
function parseSections(raw) {
|
| 239 |
-
const sections = {};
|
| 240 |
-
SECTION_ORDER.forEach((k) => { sections[k] = []; });
|
| 241 |
-
if (!raw) return sections;
|
| 242 |
-
|
| 243 |
-
const text = String(raw);
|
| 244 |
-
Object.entries(TOKEN_TO_KEY).forEach(([tok, key]) => {
|
| 245 |
-
const re = new RegExp(`<\\|${tok}_start\\|>([\\s\\S]*?)<\\|${tok}_end\\|>`, 'g');
|
| 246 |
-
let m;
|
| 247 |
-
while ((m = re.exec(text)) !== null) {
|
| 248 |
-
const body = m[1].trim();
|
| 249 |
-
if (body) sections[key].push(body);
|
| 250 |
-
}
|
| 251 |
-
});
|
| 252 |
-
return sections;
|
| 253 |
-
}
|
| 254 |
-
|
| 255 |
-
// Merge API-provided sections with parsed ones (API wins, parsed fills gaps)
|
| 256 |
-
function mergeSections(api, parsed) {
|
| 257 |
-
const merged = {};
|
| 258 |
-
SECTION_ORDER.forEach((k) => {
|
| 259 |
-
const a = (api && Array.isArray(api[k])) ? api[k] : [];
|
| 260 |
-
const p = (parsed && Array.isArray(parsed[k])) ? parsed[k] : [];
|
| 261 |
-
merged[k] = a.length ? a : p;
|
| 262 |
-
});
|
| 263 |
-
return merged;
|
| 264 |
-
}
|
| 265 |
-
|
| 266 |
-
// ----------------------------------------------------------------
|
| 267 |
-
// Cloud sandbox launcher (StackBlitz) — gives users real Next.js /
|
| 268 |
-
// Node / React / HTML execution by handing the generated code off
|
| 269 |
-
// to stackblitz.com via their public POST API. No backend required.
|
| 270 |
-
// Docs: https://developer.stackblitz.com/docs/platform/post-api
|
| 271 |
-
// ----------------------------------------------------------------
|
| 272 |
-
function isCloudRunnable(code, lang) {
|
| 273 |
-
const l = (lang || '').toLowerCase();
|
| 274 |
-
if (['html', 'markup', 'jsx', 'tsx', 'javascript', 'js', 'typescript', 'ts', 'json'].includes(l)) return true;
|
| 275 |
-
// Heuristic: short non-obvious snippets get the button if they parse
|
| 276 |
-
// like a web project (so the model can ship a partial JS file too).
|
| 277 |
-
return /<!doctype|<html|^\s*import |^\s*export |^\s*function |^\s*const |^\s*class /im.test(code);
|
| 278 |
-
}
|
| 279 |
-
|
| 280 |
-
// Detect the kind of project the model produced. Returns one of:
|
| 281 |
-
// 'next' | 'react' | 'node' | 'html' | 'snippet'
|
| 282 |
-
// The detection has to be permissive but ordered (Next.js before React
|
| 283 |
-
// before Node) so a Next.js file with `import 'react'` doesn't mis-route.
|
| 284 |
-
function detectProjectKind(code, lang) {
|
| 285 |
-
const l = (lang || '').toLowerCase();
|
| 286 |
-
const looksLikeNext = /from ['"]next\/|next\.config|app\/page\.[jt]sx|pages\/index|getServerSideProps|getStaticProps/i.test(code);
|
| 287 |
-
const looksLikeReact = /from ['"]react['"]|ReactDOM\.|useState\(|useEffect\(|<\w+\s+\w+={/i.test(code);
|
| 288 |
-
const looksLikeNode = /^\s*(?:const|import)\s+\w+\s*=?\s*require\(|process\.env|module\.exports/m.test(code);
|
| 289 |
-
const isHtmlDoc = /<!doctype|<html/i.test(code);
|
| 290 |
-
|
| 291 |
-
if (looksLikeNext) return 'next';
|
| 292 |
-
if (looksLikeReact || l === 'jsx' || l === 'tsx') return 'react';
|
| 293 |
-
if (looksLikeNode || l === 'json') return 'node';
|
| 294 |
-
if (isHtmlDoc || l === 'html' || l === 'markup') return 'html';
|
| 295 |
-
return 'snippet';
|
| 296 |
-
}
|
| 297 |
-
|
| 298 |
-
// Human-friendly label shown on the code-block header pill so users see
|
| 299 |
-
// exactly what we detected (and why a launcher might open as HTML when
|
| 300 |
-
// they asked for Next.js).
|
| 301 |
-
function projectKindLabel(kind) {
|
| 302 |
-
return ({ next: 'Next.js', react: 'React', node: 'Node.js', html: 'HTML', snippet: 'Snippet' })[kind] || kind;
|
| 303 |
-
}
|
| 304 |
-
|
| 305 |
-
// Decide which StackBlitz template + file layout to use based on what
|
| 306 |
-
// the model produced. We try to be permissive — anything that looks
|
| 307 |
-
// like a React/Next/Node project goes into the WebContainer-backed
|
| 308 |
-
// 'node' template; raw HTML uses the static 'html' template.
|
| 309 |
-
function buildStackBlitzProject(code, lang) {
|
| 310 |
-
const kind = detectProjectKind(code, lang);
|
| 311 |
-
const l = (lang || '').toLowerCase();
|
| 312 |
-
const isHtmlDoc = /<!doctype|<html/i.test(code);
|
| 313 |
-
|
| 314 |
-
const title = 'MINDI generated project';
|
| 315 |
-
const description = 'Generated by MINDI 1.5 Vision-Coder';
|
| 316 |
-
|
| 317 |
-
if (kind === 'next') {
|
| 318 |
-
// Minimal Next.js 14 app-router project.
|
| 319 |
-
return {
|
| 320 |
-
title, description,
|
| 321 |
-
template: 'node',
|
| 322 |
-
files: {
|
| 323 |
-
'package.json': JSON.stringify({
|
| 324 |
-
name: 'mindi-next-app',
|
| 325 |
-
private: true,
|
| 326 |
-
scripts: { dev: 'next dev', build: 'next build', start: 'next start' },
|
| 327 |
-
dependencies: { next: '^14.2.5', react: '^18.3.1', 'react-dom': '^18.3.1' },
|
| 328 |
-
}, null, 2),
|
| 329 |
-
'app/page.tsx': /export\s+default/i.test(code) ? code : `export default function Page() {\n return (\n <main>\n${code.split('\n').map(l => ' ' + l).join('\n')}\n </main>\n );\n}\n`,
|
| 330 |
-
'app/layout.tsx':
|
| 331 |
-
`export default function RootLayout({ children }: { children: React.ReactNode }) {
|
| 332 |
-
return (<html lang="en"><body>{children}</body></html>);
|
| 333 |
-
}
|
| 334 |
-
`,
|
| 335 |
-
'tsconfig.json': JSON.stringify({
|
| 336 |
-
compilerOptions: {
|
| 337 |
-
target: 'ES2020', lib: ['dom', 'dom.iterable', 'esnext'], jsx: 'preserve',
|
| 338 |
-
module: 'esnext', moduleResolution: 'bundler', strict: true, esModuleInterop: true,
|
| 339 |
-
skipLibCheck: true, allowJs: true, isolatedModules: true, noEmit: true,
|
| 340 |
-
plugins: [{ name: 'next' }],
|
| 341 |
-
},
|
| 342 |
-
include: ['next-env.d.ts', '**/*.ts', '**/*.tsx'],
|
| 343 |
-
}, null, 2),
|
| 344 |
-
'README.md': `# ${title}\n\n${description}\n\nRun:\n\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n`,
|
| 345 |
-
},
|
| 346 |
-
};
|
| 347 |
-
}
|
| 348 |
-
|
| 349 |
-
if (kind === 'react') {
|
| 350 |
-
// Vite + React project (faster boot in WebContainer than CRA).
|
| 351 |
-
const ext = (l === 'tsx' || /\:\s*\w+(\[\])?/.test(code)) ? 'tsx' : 'jsx';
|
| 352 |
-
return {
|
| 353 |
-
title, description,
|
| 354 |
-
template: 'node',
|
| 355 |
-
files: {
|
| 356 |
-
'package.json': JSON.stringify({
|
| 357 |
-
name: 'mindi-react-app',
|
| 358 |
-
private: true,
|
| 359 |
-
scripts: { dev: 'vite', build: 'vite build', preview: 'vite preview' },
|
| 360 |
-
dependencies: { react: '^18.3.1', 'react-dom': '^18.3.1' },
|
| 361 |
-
devDependencies: { '@vitejs/plugin-react': '^4.3.1', vite: '^5.4.1' },
|
| 362 |
-
}, null, 2),
|
| 363 |
-
'vite.config.js':
|
| 364 |
-
`import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nexport default defineConfig({ plugins: [react()] });\n`,
|
| 365 |
-
'index.html':
|
| 366 |
-
`<!doctype html><html><head><meta charset="utf-8"><title>${title}</title></head><body><div id="root"></div><script type="module" src="/src/main.${ext}"></script></body></html>`,
|
| 367 |
-
[`src/main.${ext}`]:
|
| 368 |
-
`import React from 'react';\nimport { createRoot } from 'react-dom/client';\nimport App from './App.${ext}';\ncreateRoot(document.getElementById('root')).render(<App />);\n`,
|
| 369 |
-
[`src/App.${ext}`]: /export\s+default/i.test(code) ? code : `export default function App() {\n return (<div>${'<pre>{`' + code.replace(/`/g, '\\`') + '`}</pre>'}</div>);\n}\n`,
|
| 370 |
-
},
|
| 371 |
-
};
|
| 372 |
-
}
|
| 373 |
-
|
| 374 |
-
if (kind === 'node') {
|
| 375 |
-
return {
|
| 376 |
-
title, description,
|
| 377 |
-
template: 'node',
|
| 378 |
-
files: {
|
| 379 |
-
'package.json': JSON.stringify({
|
| 380 |
-
name: 'mindi-node-app', private: true,
|
| 381 |
-
scripts: { start: 'node index.js' },
|
| 382 |
-
}, null, 2),
|
| 383 |
-
'index.js': l === 'json' ? `console.log(${code});` : code,
|
| 384 |
-
},
|
| 385 |
-
};
|
| 386 |
-
}
|
| 387 |
-
|
| 388 |
-
if (kind === 'html') {
|
| 389 |
-
return {
|
| 390 |
-
title, description,
|
| 391 |
-
template: 'html',
|
| 392 |
-
files: {
|
| 393 |
-
'index.html': isHtmlDoc ? code : `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title></head><body>\n${code}\n</body></html>`,
|
| 394 |
-
},
|
| 395 |
-
};
|
| 396 |
-
}
|
| 397 |
-
|
| 398 |
-
// Fallback: static html with the code dropped into a <pre> tag so
|
| 399 |
-
// the user at least sees their snippet rendered in the StackBlitz preview.
|
| 400 |
-
return {
|
| 401 |
-
title, description,
|
| 402 |
-
template: 'html',
|
| 403 |
-
files: {
|
| 404 |
-
'index.html': `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title></head><body><pre>${escapeHtml(code)}</pre></body></html>`,
|
| 405 |
-
},
|
| 406 |
-
};
|
| 407 |
-
}
|
| 408 |
-
|
| 409 |
-
// Pick the file the user most likely wants to land on when StackBlitz opens.
|
| 410 |
-
function chooseEntryFile(proj) {
|
| 411 |
-
const files = proj.files || {};
|
| 412 |
-
const preferred = [
|
| 413 |
-
'app/page.tsx', 'app/page.jsx',
|
| 414 |
-
'src/App.tsx', 'src/App.jsx',
|
| 415 |
-
'pages/index.tsx', 'pages/index.jsx',
|
| 416 |
-
'index.html', 'index.js',
|
| 417 |
-
];
|
| 418 |
-
for (const p of preferred) if (files[p]) return p;
|
| 419 |
-
return Object.keys(files)[0] || 'index.html';
|
| 420 |
-
}
|
| 421 |
-
|
| 422 |
-
// Hand the project off to stackblitz.com. We prefer the official SDK
|
| 423 |
-
// (https://developer.stackblitz.com/platform/api/javascript-sdk) because
|
| 424 |
-
// the bare /run form-POST endpoint silently rejects some payloads and
|
| 425 |
-
// falls back to opening their default Next.js starter — exactly what
|
| 426 |
-
// the user reported. The SDK uses an iframe handshake that's more
|
| 427 |
-
// reliable across browser SameSite / referrer policies.
|
| 428 |
-
// Form POST is kept as a fallback if the SDK script fails to load.
|
| 429 |
-
function launchInStackBlitz(code, lang) {
|
| 430 |
-
const proj = buildStackBlitzProject(code, lang);
|
| 431 |
-
const sdk = window.StackBlitzSDK;
|
| 432 |
-
|
| 433 |
-
if (sdk && typeof sdk.openProject === 'function') {
|
| 434 |
-
try {
|
| 435 |
-
sdk.openProject(
|
| 436 |
-
{
|
| 437 |
-
title: proj.title,
|
| 438 |
-
description: proj.description,
|
| 439 |
-
template: proj.template,
|
| 440 |
-
files: proj.files,
|
| 441 |
-
},
|
| 442 |
-
{ newWindow: true, openFile: chooseEntryFile(proj) }
|
| 443 |
-
);
|
| 444 |
-
return;
|
| 445 |
-
} catch (err) {
|
| 446 |
-
console.warn('StackBlitz SDK launch failed, falling back to form POST:', err);
|
| 447 |
-
}
|
| 448 |
-
}
|
| 449 |
-
|
| 450 |
-
// Fallback: classic form POST. Less reliable but works without the SDK
|
| 451 |
-
// script (useful if it's blocked by an offline / strict-CSP environment).
|
| 452 |
-
const form = document.createElement('form');
|
| 453 |
-
form.action = 'https://stackblitz.com/run';
|
| 454 |
-
form.method = 'POST';
|
| 455 |
-
form.target = '_blank';
|
| 456 |
-
form.rel = 'noopener';
|
| 457 |
-
form.style.display = 'none';
|
| 458 |
-
|
| 459 |
-
const add = (name, value) => {
|
| 460 |
-
const input = document.createElement('input');
|
| 461 |
-
input.type = 'hidden';
|
| 462 |
-
input.name = name;
|
| 463 |
-
input.value = value;
|
| 464 |
-
form.appendChild(input);
|
| 465 |
-
};
|
| 466 |
-
add('project[title]', proj.title);
|
| 467 |
-
add('project[description]', proj.description);
|
| 468 |
-
add('project[template]', proj.template);
|
| 469 |
-
Object.entries(proj.files).forEach(([path, content]) => {
|
| 470 |
-
add(`project[files][${path}]`, content);
|
| 471 |
-
});
|
| 472 |
-
|
| 473 |
-
document.body.appendChild(form);
|
| 474 |
-
form.submit();
|
| 475 |
-
setTimeout(() => form.remove(), 0);
|
| 476 |
-
}
|
| 477 |
-
|
| 478 |
-
// ----------------------------------------------------------------
|
| 479 |
-
// Cloud sandbox launcher (CodeSandbox) — second cloud IDE option.
|
| 480 |
-
// Uses the public Define API which returns a sandbox_id we redirect to.
|
| 481 |
-
// Docs: https://codesandbox.io/docs/learn/sandboxes/cli-api#define-api
|
| 482 |
-
// We reuse buildStackBlitzProject() for the file shape since both IDEs
|
| 483 |
-
// accept the same package.json / file layout; CodeSandbox auto-detects
|
| 484 |
-
// the template from package.json dependencies.
|
| 485 |
-
// ----------------------------------------------------------------
|
| 486 |
-
function buildCodeSandboxFiles(code, lang) {
|
| 487 |
-
const proj = buildStackBlitzProject(code, lang);
|
| 488 |
-
const files = {};
|
| 489 |
-
Object.entries(proj.files).forEach(([path, content]) => {
|
| 490 |
-
files[path] = { content };
|
| 491 |
-
});
|
| 492 |
-
// For raw HTML projects (StackBlitz template='html'), nudge CodeSandbox
|
| 493 |
-
// toward its 'static' template so it serves index.html as-is instead of
|
| 494 |
-
// trying to npm install nothing.
|
| 495 |
-
if (proj.template === 'html' && !files['package.json']) {
|
| 496 |
-
files['sandbox.config.json'] = { content: JSON.stringify({ template: 'static' }, null, 2) };
|
| 497 |
-
}
|
| 498 |
-
return files;
|
| 499 |
-
}
|
| 500 |
-
|
| 501 |
-
async function launchInCodeSandbox(code, lang) {
|
| 502 |
-
const files = buildCodeSandboxFiles(code, lang);
|
| 503 |
-
try {
|
| 504 |
-
const res = await fetch('https://codesandbox.io/api/v1/sandboxes/define?json=1', {
|
| 505 |
-
method: 'POST',
|
| 506 |
-
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
| 507 |
-
body: JSON.stringify({ files }),
|
| 508 |
-
});
|
| 509 |
-
if (!res.ok) {
|
| 510 |
-
const txt = await res.text().catch(() => '');
|
| 511 |
-
throw new Error(`HTTP ${res.status}: ${txt.slice(0, 160)}`);
|
| 512 |
-
}
|
| 513 |
-
const data = await res.json();
|
| 514 |
-
if (!data || !data.sandbox_id) throw new Error('No sandbox_id in response');
|
| 515 |
-
window.open(`https://codesandbox.io/s/${data.sandbox_id}`, '_blank', 'noopener');
|
| 516 |
-
} catch (err) {
|
| 517 |
-
toast(`CodeSandbox launch failed: ${err.message || err}`, 'error');
|
| 518 |
-
}
|
| 519 |
-
}
|
| 520 |
-
|
| 521 |
-
// Extract last fenced code block from the response text
|
| 522 |
-
function extractLastCodeBlock(text) {
|
| 523 |
-
if (!text) return null;
|
| 524 |
-
const re = /```(\w+)?\s*\n([\s\S]*?)```/g;
|
| 525 |
-
let last = null, m;
|
| 526 |
-
while ((m = re.exec(text)) !== null) {
|
| 527 |
-
last = { language: (m[1] || '').toLowerCase() || null, code: m[2] };
|
| 528 |
-
}
|
| 529 |
-
if (last) {
|
| 530 |
-
if (!last.language) last.language = languageFromCode(last.code);
|
| 531 |
-
return last;
|
| 532 |
-
}
|
| 533 |
-
return null;
|
| 534 |
-
}
|
| 535 |
-
|
| 536 |
-
// ----------------------------------------------------------------
|
| 537 |
-
// Markdown renderer (limited: paragraphs, fenced code, inline code, bold/italic)
|
| 538 |
-
// ----------------------------------------------------------------
|
| 539 |
-
function renderMarkdown(text) {
|
| 540 |
-
if (!text) return '';
|
| 541 |
-
|
| 542 |
-
// Tokenize: split into fenced-code parts and text parts
|
| 543 |
-
const segments = [];
|
| 544 |
-
const re = /```(\w+)?\s*\n([\s\S]*?)```/g;
|
| 545 |
-
let lastIdx = 0, m;
|
| 546 |
-
while ((m = re.exec(text)) !== null) {
|
| 547 |
-
if (m.index > lastIdx) {
|
| 548 |
-
segments.push({ type: 'text', value: text.slice(lastIdx, m.index) });
|
| 549 |
-
}
|
| 550 |
-
segments.push({ type: 'code', lang: (m[1] || '').toLowerCase() || null, value: m[2] });
|
| 551 |
-
lastIdx = re.lastIndex;
|
| 552 |
-
}
|
| 553 |
-
if (lastIdx < text.length) {
|
| 554 |
-
segments.push({ type: 'text', value: text.slice(lastIdx) });
|
| 555 |
-
}
|
| 556 |
-
|
| 557 |
-
return segments.map((seg) => {
|
| 558 |
-
if (seg.type === 'code') {
|
| 559 |
-
const lang = seg.lang || languageFromCode(seg.value);
|
| 560 |
-
const safe = escapeHtml(seg.value);
|
| 561 |
-
const dataCode = escapeAttr(seg.value);
|
| 562 |
-
const runnable = isCloudRunnable(seg.value, lang);
|
| 563 |
-
const kind = runnable ? detectProjectKind(seg.value, lang) : null;
|
| 564 |
-
const kindPill = kind
|
| 565 |
-
? `<span class="md-kind md-kind--${kind}" title="Project kind detected from the generated code. The launchers below open this exact code, even if it doesn't match what you originally asked for.">${projectKindLabel(kind)}</span>`
|
| 566 |
-
: '';
|
| 567 |
-
const launchBtns = runnable
|
| 568 |
-
? (
|
| 569 |
-
`<button class="md-run" data-code="${dataCode}" data-lang="${escapeAttr(lang)}" type="button" title="Run this code on stackblitz.com (real Node.js / WebContainer sandbox, supports Next.js / React / Node)">\u25B6 StackBlitz</button>` +
|
| 570 |
-
`<button class="md-sandbox" data-code="${dataCode}" data-lang="${escapeAttr(lang)}" type="button" title="Open this code in codesandbox.io (cloud IDE with live preview)">\u25B6 CodeSandbox</button>`
|
| 571 |
-
)
|
| 572 |
-
: '';
|
| 573 |
-
return (
|
| 574 |
-
`<pre class="md-code-block">` +
|
| 575 |
-
`<div class="md-code-head">` +
|
| 576 |
-
`<span class="md-lang">${escapeHtml(lang)}${kindPill}</span>` +
|
| 577 |
-
`<div class="md-code-actions">` +
|
| 578 |
-
launchBtns +
|
| 579 |
-
`<button class="md-copy" data-code="${dataCode}" type="button">Copy</button>` +
|
| 580 |
-
`</div>` +
|
| 581 |
-
`</div>` +
|
| 582 |
-
`<code class="language-${escapeHtml(lang)}">${safe}</code>` +
|
| 583 |
-
`</pre>`
|
| 584 |
-
);
|
| 585 |
-
}
|
| 586 |
-
// text segment
|
| 587 |
-
let h = seg.value.trim();
|
| 588 |
-
if (!h) return '';
|
| 589 |
-
h = escapeHtml(h);
|
| 590 |
-
h = h.replace(/`([^`\n]+)`/g, '<code class="md-inline">$1</code>');
|
| 591 |
-
h = h.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>');
|
| 592 |
-
h = h.replace(/(^|[\s(])\*([^*\n]+)\*(?=[\s).,!?:;]|$)/g, '$1<em>$2</em>');
|
| 593 |
-
return h.split(/\n{2,}/)
|
| 594 |
-
.map((p) => '<p>' + p.replace(/\n/g, '<br>') + '</p>')
|
| 595 |
-
.join('');
|
| 596 |
-
}).join('');
|
| 597 |
-
}
|
| 598 |
-
|
| 599 |
-
// ----------------------------------------------------------------
|
| 600 |
-
// API client
|
| 601 |
-
// ----------------------------------------------------------------
|
| 602 |
-
function authHeaders(extra) {
|
| 603 |
-
const h = Object.assign({}, extra || {});
|
| 604 |
-
if (state.hfToken) h['Authorization'] = `Bearer ${state.hfToken}`;
|
| 605 |
-
return h;
|
| 606 |
-
}
|
| 607 |
-
|
| 608 |
-
// Convert a data: URL into a Blob (used for Gradio image uploads).
|
| 609 |
-
function dataUrlToBlob(dataUrl) {
|
| 610 |
-
const match = /^data:([^;]+);base64,(.+)$/.exec(dataUrl || '');
|
| 611 |
-
if (!match) throw new Error('Invalid image data URL');
|
| 612 |
-
const mime = match[1];
|
| 613 |
-
const b64 = match[2];
|
| 614 |
-
const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
| 615 |
-
return { blob: new Blob([bytes], { type: mime }), mime };
|
| 616 |
-
}
|
| 617 |
-
|
| 618 |
-
// Upload an image to a Gradio HF Space via /gradio_api/upload.
|
| 619 |
-
// Returns the server-side file path that can be referenced as
|
| 620 |
-
// {path: ..., meta: {_type: "gradio.FileData"}} in a chat_fn data array.
|
| 621 |
-
// This is REQUIRED — gr.Image(type="pil") on the backend cannot decode
|
| 622 |
-
// a raw data URL string.
|
| 623 |
-
async function uploadImageToGradio(base, dataUrl, signal) {
|
| 624 |
-
const { blob, mime } = dataUrlToBlob(dataUrl);
|
| 625 |
-
const ext = (mime.split('/')[1] || 'png').replace('+xml', '').split(';')[0];
|
| 626 |
-
const filename = `mindi-upload-${Date.now()}.${ext}`;
|
| 627 |
-
const formData = new FormData();
|
| 628 |
-
formData.append('files', blob, filename);
|
| 629 |
-
|
| 630 |
-
// Don't pre-set Content-Type — the browser sets the multipart boundary.
|
| 631 |
-
const headers = authHeaders({});
|
| 632 |
-
delete headers['Content-Type'];
|
| 633 |
-
|
| 634 |
-
const res = await fetch(`${base}/gradio_api/upload`, {
|
| 635 |
-
method: 'POST',
|
| 636 |
-
headers,
|
| 637 |
-
body: formData,
|
| 638 |
-
signal,
|
| 639 |
-
});
|
| 640 |
-
if (!res.ok) {
|
| 641 |
-
const txt = await res.text().catch(() => '');
|
| 642 |
-
throw new Error(`Image upload ${res.status}: ${txt.slice(0, 200) || 'failed'}`);
|
| 643 |
-
}
|
| 644 |
-
const result = await res.json();
|
| 645 |
-
// Gradio 5.x returns ["/tmp/gradio/.../filename.png"]
|
| 646 |
-
const filePath = Array.isArray(result) ? result[0] : (result && result.files && result.files[0]);
|
| 647 |
-
if (!filePath || typeof filePath !== 'string') {
|
| 648 |
-
throw new Error(`Unexpected upload response: ${JSON.stringify(result).slice(0, 200)}`);
|
| 649 |
-
}
|
| 650 |
-
return filePath;
|
| 651 |
-
}
|
| 652 |
-
|
| 653 |
-
// Detect responses that came back as a quota / auth error from the
|
| 654 |
-
// backend's chat_fn try/except, so we can show actionable UX.
|
| 655 |
-
function detectAuthError(result) {
|
| 656 |
-
if (!result) return null;
|
| 657 |
-
const text = String(result.response || '');
|
| 658 |
-
const errs = (result.sections && result.sections.error) || [];
|
| 659 |
-
const blob = (text + ' ' + errs.join(' ')).toLowerCase();
|
| 660 |
-
if (/zerogpu|gpu quota|out of .* quota|exceeded .* quota|unlogged user/.test(blob)) {
|
| 661 |
-
return state.hfToken
|
| 662 |
-
? 'Your HF token hit its ZeroGPU quota. Wait for the daily reset or use a PRO token.'
|
| 663 |
-
: 'Anonymous ZeroGPU quota exhausted. Open Settings (double-click the MINDI logo) and paste your HF token.';
|
| 664 |
-
}
|
| 665 |
-
return null;
|
| 666 |
-
}
|
| 667 |
-
|
| 668 |
-
// Build a friendly assistant message shown inline in the chat when the
|
| 669 |
-
// request is (or would be) blocked by the ZeroGPU quota / auth wall.
|
| 670 |
-
// ZeroGPU quota is per-user — there is no way to bypass it without a
|
| 671 |
-
// logged-in HF token. See https://huggingface.co/docs/hub/en/spaces-zerogpu
|
| 672 |
-
function makeAuthBlockedResponse() {
|
| 673 |
-
if (state.hfToken) {
|
| 674 |
-
return {
|
| 675 |
-
response:
|
| 676 |
-
`**Your HF token hit its daily ZeroGPU quota.**
|
| 677 |
-
|
| 678 |
-
ZeroGPU enforces a per-user GPU-time budget that resets every 24 hours after first use. Free HF accounts get a small daily allowance, **PRO accounts get 8\u00d7 more**, and PRO/Team/Enterprise can also top up with [pre-paid credits](https://huggingface.co/settings/billing) at \\$1 per 10 GPU-minutes.
|
| 679 |
-
|
| 680 |
-
**What to do:**
|
| 681 |
-
- Wait for the daily reset (24h after your first call today), **or**
|
| 682 |
-
- Top up credits in your HF billing settings, **or**
|
| 683 |
-
- Open **Settings** (double-click the MINDI logo) and paste a different PRO token.
|
| 684 |
-
|
| 685 |
-
I'll keep further messages local until you update the token \u2014 sending them now would just hit the same wall.`,
|
| 686 |
-
sections: {},
|
| 687 |
-
};
|
| 688 |
-
}
|
| 689 |
-
return {
|
| 690 |
-
response:
|
| 691 |
-
`**Sign-in needed to use the live model.**
|
| 692 |
-
|
| 693 |
-
The MINDI 1.5 backend runs on HuggingFace ZeroGPU, which gives every IP a tiny anonymous quota (~3 minutes / day) before blocking further requests. That's why the first message worked but the next one didn't.
|
| 694 |
-
|
| 695 |
-
**To unlock real generation:**
|
| 696 |
-
1. Get a token at [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) (free account is fine; **PRO** gives 8\u00d7 more).
|
| 697 |
-
2. **Double-click the MINDI logo** \u2192 paste the token in the *HuggingFace token* field \u2192 *Save settings*.
|
| 698 |
-
3. Re-send your message.
|
| 699 |
-
|
| 700 |
-
Your token is stored only in this browser's local storage and sent as an \`Authorization: Bearer\` header to the Space.`,
|
| 701 |
-
sections: {},
|
| 702 |
-
};
|
| 703 |
-
}
|
| 704 |
-
|
| 705 |
-
async function pingHealth() {
|
| 706 |
-
// If a previous request was blocked by ZeroGPU quota / auth, stay in
|
| 707 |
-
// 'Auth required' until the user adds a token (applySettings clears it).
|
| 708 |
-
// Otherwise this would silently flip back to 'online' every 60s and the
|
| 709 |
-
// next user message would hit the same quota wall.
|
| 710 |
-
if (runtime.authBlocked) {
|
| 711 |
-
setStatus('demo', state.hfToken ? 'Quota exhausted' : 'Auth required');
|
| 712 |
-
return;
|
| 713 |
-
}
|
| 714 |
-
if (!state.apiUrl) {
|
| 715 |
-
setStatus('demo', 'Demo Mode');
|
| 716 |
-
return;
|
| 717 |
-
}
|
| 718 |
-
try {
|
| 719 |
-
const base = state.apiUrl.replace(/\/$/, '');
|
| 720 |
-
const isGradio = base.includes('hf.space') || base.includes('huggingface.co');
|
| 721 |
-
|
| 722 |
-
if (isGradio) {
|
| 723 |
-
// For Gradio/HF Spaces: check the root URL which returns the Gradio page
|
| 724 |
-
const res = await fetch(base, { method: 'HEAD', mode: 'no-cors' }).catch(() => null);
|
| 725 |
-
// no-cors always returns opaque response, so we check for network errors
|
| 726 |
-
if (res) {
|
| 727 |
-
setStatus('online', 'MINDI · HF Space');
|
| 728 |
-
} else {
|
| 729 |
-
setStatus('demo', 'Demo Mode (Space unreachable)');
|
| 730 |
-
}
|
| 731 |
-
} else {
|
| 732 |
-
// Direct REST API health check
|
| 733 |
-
try {
|
| 734 |
-
const res = await fetch(`${base}/api/health`, { method: 'GET', headers: { 'Accept': 'application/json' } });
|
| 735 |
-
if (res.ok) {
|
| 736 |
-
const d = await res.json().catch(() => ({}));
|
| 737 |
-
setStatus('online', `${d.model || 'MINDI'} · online`);
|
| 738 |
-
} else {
|
| 739 |
-
setStatus('demo', 'Demo Mode');
|
| 740 |
-
}
|
| 741 |
-
} catch {
|
| 742 |
-
setStatus('demo', 'Demo Mode');
|
| 743 |
-
}
|
| 744 |
-
}
|
| 745 |
-
} catch {
|
| 746 |
-
setStatus('demo', 'Demo Mode');
|
| 747 |
-
}
|
| 748 |
-
}
|
| 749 |
-
|
| 750 |
-
// Build a Qwen-style history list from the current chat, EXCLUDING the
|
| 751 |
-
// user message that's about to be sent (which is `prompt` itself) and any
|
| 752 |
-
// in-flight loading placeholder. Capped to keep the request small.
|
| 753 |
-
function buildHistory() {
|
| 754 |
-
const chat = currentChat();
|
| 755 |
-
if (!chat) return [];
|
| 756 |
-
const msgs = chat.messages || [];
|
| 757 |
-
// Drop trailing loading messages and the most-recent user message,
|
| 758 |
-
// since send() already pushed it just before calling us.
|
| 759 |
-
let end = msgs.length;
|
| 760 |
-
while (end > 0 && msgs[end - 1].loading) end--;
|
| 761 |
-
if (end > 0 && msgs[end - 1].role === 'user') end--;
|
| 762 |
-
const slice = msgs.slice(Math.max(0, end - 20), end);
|
| 763 |
-
return slice
|
| 764 |
-
.filter((m) => (m.role === 'user' || m.role === 'assistant') && !m.loading)
|
| 765 |
-
.map((m) => ({
|
| 766 |
-
role: m.role,
|
| 767 |
-
content: typeof m.content === 'string' ? m.content : String(m.content || ''),
|
| 768 |
-
}))
|
| 769 |
-
.filter((m) => m.content.trim().length > 0);
|
| 770 |
-
}
|
| 771 |
-
|
| 772 |
-
async function callGenerate(prompt, image, signal) {
|
| 773 |
-
const base = state.apiUrl.replace(/\/$/, '');
|
| 774 |
-
const history = buildHistory();
|
| 775 |
-
// Gradio expects each input as its own positional element. We pass
|
| 776 |
-
// history as a JSON-encoded string because the backend's chat_fn input
|
| 777 |
-
// is a Textbox (not a JSON component) — _coerce_history() decodes it.
|
| 778 |
-
const historyJson = history.length ? JSON.stringify(history) : '';
|
| 779 |
-
|
| 780 |
-
// Detect if this is a Gradio HF Space
|
| 781 |
-
const isGradio = base.includes('hf.space') || base.includes('huggingface.co/spaces');
|
| 782 |
-
|
| 783 |
-
if (isGradio) {
|
| 784 |
-
// Gradio 5.x SSE v3 protocol — two-step:
|
| 785 |
-
// 1. POST /gradio_api/call/{api_name} → get event_id
|
| 786 |
-
// 2. GET /gradio_api/call/{api_name}/{event_id} → stream result
|
| 787 |
-
|
| 788 |
-
// ── Image: upload first, then reference by path ──
|
| 789 |
-
// gr.Image(type="pil") cannot decode a raw data: URL — it expects a
|
| 790 |
-
// FileData reference produced by /gradio_api/upload. We do this
|
| 791 |
-
// unconditionally when an image is supplied so the backend's CLIP
|
| 792 |
-
// path actually receives pixels. If vision is disabled in settings,
|
| 793 |
-
// send() drops the image before calling us.
|
| 794 |
-
let imageArg = null;
|
| 795 |
-
if (image && typeof image === 'string' && image.startsWith('data:')) {
|
| 796 |
-
try {
|
| 797 |
-
const filePath = await uploadImageToGradio(base, image, signal);
|
| 798 |
-
imageArg = {
|
| 799 |
-
path: filePath,
|
| 800 |
-
meta: { _type: 'gradio.FileData' },
|
| 801 |
-
orig_name: filePath.split('/').pop() || 'image.png',
|
| 802 |
-
};
|
| 803 |
-
} catch (e) {
|
| 804 |
-
console.warn('[mindi] Image upload to Gradio failed:', e);
|
| 805 |
-
toast(`Image upload failed: ${e.message || e}. Sending text only.`, 'error', 5000);
|
| 806 |
-
imageArg = null;
|
| 807 |
-
}
|
| 808 |
-
}
|
| 809 |
-
|
| 810 |
-
// Step 1: Submit the request
|
| 811 |
-
const submitRes = await fetch(`${base}/gradio_api/call/chat_fn`, {
|
| 812 |
-
method: 'POST',
|
| 813 |
-
headers: authHeaders({ 'Content-Type': 'application/json' }),
|
| 814 |
-
body: JSON.stringify({
|
| 815 |
-
data: [prompt, imageArg, state.temperature, state.maxTokens, historyJson],
|
| 816 |
-
}),
|
| 817 |
-
signal,
|
| 818 |
-
});
|
| 819 |
-
if (!submitRes.ok) {
|
| 820 |
-
const txt = await submitRes.text().catch(() => '');
|
| 821 |
-
throw new Error(`API submit ${submitRes.status}: ${txt.slice(0, 200) || 'request failed'}`);
|
| 822 |
-
}
|
| 823 |
-
const { event_id } = await submitRes.json();
|
| 824 |
-
if (!event_id) {
|
| 825 |
-
throw new Error('No event_id returned from Gradio API');
|
| 826 |
-
}
|
| 827 |
-
|
| 828 |
-
// Step 2: Get the result via SSE stream
|
| 829 |
-
const resultRes = await fetch(`${base}/gradio_api/call/chat_fn/${event_id}`, {
|
| 830 |
-
method: 'GET',
|
| 831 |
-
headers: authHeaders(),
|
| 832 |
-
signal,
|
| 833 |
-
});
|
| 834 |
-
if (!resultRes.ok) {
|
| 835 |
-
const txt = await resultRes.text().catch(() => '');
|
| 836 |
-
throw new Error(`API result ${resultRes.status}: ${txt.slice(0, 200) || 'request failed'}`);
|
| 837 |
-
}
|
| 838 |
-
|
| 839 |
-
// Parse SSE response — look for the "complete" event with data
|
| 840 |
-
const sseText = await resultRes.text();
|
| 841 |
-
const lines = sseText.split('\n');
|
| 842 |
-
let raw = null;
|
| 843 |
-
for (let i = 0; i < lines.length; i++) {
|
| 844 |
-
if (lines[i].startsWith('event: complete')) {
|
| 845 |
-
// Next line(s) starting with "data: " contain the result
|
| 846 |
-
const dataLine = lines[i + 1];
|
| 847 |
-
if (dataLine && dataLine.startsWith('data: ')) {
|
| 848 |
-
try {
|
| 849 |
-
const parsed = JSON.parse(dataLine.slice(6));
|
| 850 |
-
// Gradio wraps in array
|
| 851 |
-
raw = Array.isArray(parsed) ? parsed[0] : parsed;
|
| 852 |
-
} catch {
|
| 853 |
-
raw = dataLine.slice(6);
|
| 854 |
-
}
|
| 855 |
-
}
|
| 856 |
-
break;
|
| 857 |
-
}
|
| 858 |
-
if (lines[i].startsWith('event: error')) {
|
| 859 |
-
const dataLine = lines[i + 1];
|
| 860 |
-
const errMsg = dataLine?.startsWith('data: ') ? dataLine.slice(6) : 'Unknown Gradio error';
|
| 861 |
-
throw new Error(`Gradio error: ${errMsg.slice(0, 300)}`);
|
| 862 |
-
}
|
| 863 |
-
}
|
| 864 |
-
|
| 865 |
-
if (raw === null) {
|
| 866 |
-
throw new Error('No complete event found in Gradio SSE response');
|
| 867 |
-
}
|
| 868 |
-
|
| 869 |
-
// raw is a JSON string from our chat_fn
|
| 870 |
-
try {
|
| 871 |
-
return JSON.parse(raw);
|
| 872 |
-
} catch {
|
| 873 |
-
return { response: String(raw), sections: {} };
|
| 874 |
-
}
|
| 875 |
-
|
| 876 |
-
} else {
|
| 877 |
-
// Direct REST API (Modal or custom)
|
| 878 |
-
const body = {
|
| 879 |
-
prompt,
|
| 880 |
-
temperature: state.temperature,
|
| 881 |
-
max_tokens: state.maxTokens,
|
| 882 |
-
history,
|
| 883 |
-
};
|
| 884 |
-
if (image) body.image = image;
|
| 885 |
-
const res = await fetch(`${base}/api/generate`, {
|
| 886 |
-
method: 'POST',
|
| 887 |
-
headers: authHeaders({ 'Content-Type': 'application/json', 'Accept': 'application/json' }),
|
| 888 |
-
body: JSON.stringify(body),
|
| 889 |
-
signal,
|
| 890 |
-
});
|
| 891 |
-
if (!res.ok) {
|
| 892 |
-
const txt = await res.text().catch(() => '');
|
| 893 |
-
throw new Error(`API ${res.status}: ${txt.slice(0, 200) || 'request failed'}`);
|
| 894 |
-
}
|
| 895 |
-
return res.json();
|
| 896 |
-
}
|
| 897 |
-
}
|
| 898 |
-
|
| 899 |
-
// ----------------------------------------------------------------
|
| 900 |
-
// Demo / Fallback responses
|
| 901 |
-
// ----------------------------------------------------------------
|
| 902 |
-
const DEMO_RESPONSES = [
|
| 903 |
-
{
|
| 904 |
-
match: /landing|hero|next\.?js/i,
|
| 905 |
-
response:
|
| 906 |
-
`Here's a clean Next.js landing page using Tailwind CSS:
|
| 907 |
-
|
| 908 |
-
\`\`\`tsx
|
| 909 |
-
// app/page.tsx
|
| 910 |
-
export default function Home() {
|
| 911 |
-
return (
|
| 912 |
-
<main className="min-h-screen bg-gradient-to-b from-slate-950 to-slate-900 text-white">
|
| 913 |
-
<section className="max-w-6xl mx-auto px-6 py-24 text-center">
|
| 914 |
-
<span className="inline-block px-3 py-1 rounded-full bg-violet-500/15 text-violet-300 text-xs font-mono tracking-widest uppercase mb-6">
|
| 915 |
-
Now in beta
|
| 916 |
-
</span>
|
| 917 |
-
<h1 className="text-5xl md:text-7xl font-semibold tracking-tight">
|
| 918 |
-
Build faster.<br/>
|
| 919 |
-
<span className="bg-gradient-to-r from-violet-400 to-blue-400 bg-clip-text text-transparent">
|
| 920 |
-
Ship sooner.
|
| 921 |
-
</span>
|
| 922 |
-
</h1>
|
| 923 |
-
<p className="mt-6 text-lg text-slate-300 max-w-2xl mx-auto">
|
| 924 |
-
The frontend you'd build if you had unlimited time, in a single prompt.
|
| 925 |
-
</p>
|
| 926 |
-
<div className="mt-10 flex justify-center gap-3">
|
| 927 |
-
<a className="px-6 py-3 rounded-full bg-gradient-to-r from-violet-600 to-blue-600 font-medium" href="#cta">
|
| 928 |
-
Get started
|
| 929 |
-
</a>
|
| 930 |
-
<a className="px-6 py-3 rounded-full border border-white/10 hover:bg-white/5" href="#features">
|
| 931 |
-
See features
|
| 932 |
-
</a>
|
| 933 |
-
</div>
|
| 934 |
-
</section>
|
| 935 |
-
</main>
|
| 936 |
-
);
|
| 937 |
-
}
|
| 938 |
-
\`\`\`
|
| 939 |
-
|
| 940 |
-
This sets up a hero section with a gradient headline, a kicker badge, and two CTAs. Drop in an \`<Image>\` background or particle layer next.`,
|
| 941 |
-
sections: {
|
| 942 |
-
thinking: ['User wants a Next.js landing page. Producing a single-file app/page.tsx using Tailwind for the hero, with two CTAs and accessible markup.'],
|
| 943 |
-
code: ['app/page.tsx generated with hero section + gradient headline.'],
|
| 944 |
-
critique: [],
|
| 945 |
-
fix: [],
|
| 946 |
-
},
|
| 947 |
-
},
|
| 948 |
-
{
|
| 949 |
-
match: /dashboard|chart|analytics/i,
|
| 950 |
-
response:
|
| 951 |
-
`Here's a self-contained dashboard UI in vanilla HTML/CSS:
|
| 952 |
-
|
| 953 |
-
\`\`\`html
|
| 954 |
-
<!DOCTYPE html>
|
| 955 |
-
<html lang="en">
|
| 956 |
-
<head>
|
| 957 |
-
<meta charset="UTF-8" />
|
| 958 |
-
<title>Pulsegrid · Dashboard</title>
|
| 959 |
-
<style>
|
| 960 |
-
:root { --bg:#0b0b14; --panel:#14141f; --border:rgba(255,255,255,.08); --text:#ececf1; --mute:#8b94a7; --acc:#7c3aed; }
|
| 961 |
-
* { box-sizing:border-box; margin:0; padding:0; }
|
| 962 |
-
body { background:var(--bg); color:var(--text); font:14px/1.55 'Inter', sans-serif; min-height:100vh; display:grid; grid-template-columns:240px 1fr; }
|
| 963 |
-
aside { background:var(--panel); border-right:1px solid var(--border); padding:20px; }
|
| 964 |
-
aside h1 { font-size:18px; background:linear-gradient(135deg,#7c3aed,#2563eb); -webkit-background-clip:text; color:transparent; margin-bottom:24px; }
|
| 965 |
-
nav a { display:block; padding:10px 12px; border-radius:8px; color:var(--mute); text-decoration:none; margin-bottom:2px; }
|
| 966 |
-
nav a.active { background:rgba(124,58,237,.15); color:#fff; }
|
| 967 |
-
main { padding:24px; overflow-y:auto; }
|
| 968 |
-
.stats { display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin-bottom:20px; }
|
| 969 |
-
.stat { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:16px; }
|
| 970 |
-
.stat .v { font-size:24px; font-weight:600; margin-top:6px; }
|
| 971 |
-
.stat .l { color:var(--mute); font-size:12px; text-transform:uppercase; letter-spacing:.1em; }
|
| 972 |
-
.chart { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:18px; height:260px; display:flex; align-items:end; gap:8px; }
|
| 973 |
-
.bar { flex:1; background:linear-gradient(180deg,#7c3aed,#2563eb); border-radius:6px 6px 0 0; }
|
| 974 |
-
</style>
|
| 975 |
-
</head>
|
| 976 |
-
<body>
|
| 977 |
-
<aside>
|
| 978 |
-
<h1>Pulsegrid</h1>
|
| 979 |
-
<nav>
|
| 980 |
-
<a class="active">Overview</a>
|
| 981 |
-
<a>Customers</a>
|
| 982 |
-
<a>Revenue</a>
|
| 983 |
-
<a>Settings</a>
|
| 984 |
-
</nav>
|
| 985 |
-
</aside>
|
| 986 |
-
<main>
|
| 987 |
-
<div class="stats">
|
| 988 |
-
<div class="stat"><div class="l">Revenue</div><div class="v">$48,210</div></div>
|
| 989 |
-
<div class="stat"><div class="l">Active users</div><div class="v">12,840</div></div>
|
| 990 |
-
<div class="stat"><div class="l">Conversion</div><div class="v">4.2%</div></div>
|
| 991 |
-
<div class="stat"><div class="l">Churn</div><div class="v">1.1%</div></div>
|
| 992 |
-
</div>
|
| 993 |
-
<div class="chart">
|
| 994 |
-
<div class="bar" style="height:40%"></div>
|
| 995 |
-
<div class="bar" style="height:65%"></div>
|
| 996 |
-
<div class="bar" style="height:30%"></div>
|
| 997 |
-
<div class="bar" style="height:80%"></div>
|
| 998 |
-
<div class="bar" style="height:55%"></div>
|
| 999 |
-
<div class="bar" style="height:90%"></div>
|
| 1000 |
-
<div class="bar" style="height:70%"></div>
|
| 1001 |
-
</div>
|
| 1002 |
-
</main>
|
| 1003 |
-
</body>
|
| 1004 |
-
</html>
|
| 1005 |
-
\`\`\`
|
| 1006 |
-
|
| 1007 |
-
Hit the **Preview** tab to see it rendered live.`,
|
| 1008 |
-
sections: {
|
| 1009 |
-
thinking: ['User wants a dashboard. Building a self-contained HTML page with sidebar nav, stat cards, and a CSS-only bar chart so it can render in the iframe preview.'],
|
| 1010 |
-
code: ['Single-file dashboard.html with grid layout, stats, sidebar.'],
|
| 1011 |
-
critique: ['No real charting library — bars are static. For production, swap in Recharts/Chart.js.'],
|
| 1012 |
-
fix: [],
|
| 1013 |
-
},
|
| 1014 |
-
},
|
| 1015 |
-
{
|
| 1016 |
-
match: /api|fastapi|backend|jwt|postgres/i,
|
| 1017 |
-
response:
|
| 1018 |
-
`Here's a minimal but production-shaped FastAPI service:
|
| 1019 |
-
|
| 1020 |
-
\`\`\`python
|
| 1021 |
-
# main.py
|
| 1022 |
-
from datetime import datetime, timedelta
|
| 1023 |
-
from fastapi import FastAPI, Depends, HTTPException, status
|
| 1024 |
-
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
| 1025 |
-
from sqlalchemy.orm import Session
|
| 1026 |
-
from passlib.context import CryptContext
|
| 1027 |
-
from jose import jwt
|
| 1028 |
-
from pydantic import BaseModel
|
| 1029 |
-
|
| 1030 |
-
from .database import SessionLocal, engine
|
| 1031 |
-
from . import models, schemas
|
| 1032 |
-
|
| 1033 |
-
models.Base.metadata.create_all(bind=engine)
|
| 1034 |
-
|
| 1035 |
-
app = FastAPI(title="Notes API")
|
| 1036 |
-
SECRET_KEY = "change-me"
|
| 1037 |
-
ALGORITHM = "HS256"
|
| 1038 |
-
EXPIRES = timedelta(hours=24)
|
| 1039 |
-
pwd = CryptContext(schemes=["bcrypt"])
|
| 1040 |
-
oauth2 = OAuth2PasswordBearer(tokenUrl="/auth/login")
|
| 1041 |
-
|
| 1042 |
-
def get_db():
|
| 1043 |
-
db = SessionLocal()
|
| 1044 |
-
try: yield db
|
| 1045 |
-
finally: db.close()
|
| 1046 |
-
|
| 1047 |
-
def make_token(sub: str) -> str:
|
| 1048 |
-
return jwt.encode({"sub": sub, "exp": datetime.utcnow() + EXPIRES}, SECRET_KEY, ALGORITHM)
|
| 1049 |
-
|
| 1050 |
-
@app.post("/auth/login")
|
| 1051 |
-
def login(form: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
|
| 1052 |
-
user = db.query(models.User).filter_by(email=form.username).first()
|
| 1053 |
-
if not user or not pwd.verify(form.password, user.hashed_password):
|
| 1054 |
-
raise HTTPException(401, "Invalid credentials")
|
| 1055 |
-
return {"access_token": make_token(user.email), "token_type": "bearer"}
|
| 1056 |
-
|
| 1057 |
-
@app.get("/notes", response_model=list[schemas.Note])
|
| 1058 |
-
def list_notes(db: Session = Depends(get_db), token: str = Depends(oauth2)):
|
| 1059 |
-
payload = jwt.decode(token, SECRET_KEY, [ALGORITHM])
|
| 1060 |
-
return db.query(models.Note).filter_by(owner_email=payload["sub"]).all()
|
| 1061 |
-
\`\`\`
|
| 1062 |
-
|
| 1063 |
-
Pair this with \`models.py\`, \`schemas.py\`, and a \`.env\` containing \`DATABASE_URL=postgresql://…\`.`,
|
| 1064 |
-
sections: {
|
| 1065 |
-
thinking: ['User asked for FastAPI + JWT + Postgres. Sketching the service entrypoint with auth flow and a protected GET endpoint.'],
|
| 1066 |
-
code: ['main.py with /auth/login and /notes endpoints.'],
|
| 1067 |
-
critique: ['SECRET_KEY hardcoded — move to env. bcrypt rounds are default — increase for production.'],
|
| 1068 |
-
suggest: ['Add Alembic migrations and pytest fixtures next.'],
|
| 1069 |
-
},
|
| 1070 |
-
},
|
| 1071 |
-
{
|
| 1072 |
-
match: /bug|fix|debug|divide/i,
|
| 1073 |
-
response:
|
| 1074 |
-
`Two bugs in that snippet:
|
| 1075 |
-
|
| 1076 |
-
1. **No zero-divisor guard** — \`divide_list([1,2], 0)\` throws \`ZeroDivisionError\`.
|
| 1077 |
-
2. **No type validation** — non-numeric items raise \`TypeError\` deep in the loop.
|
| 1078 |
-
|
| 1079 |
-
Here's the fix:
|
| 1080 |
-
|
| 1081 |
-
\`\`\`python
|
| 1082 |
-
def divide_list(numbers, divisor):
|
| 1083 |
-
if divisor == 0:
|
| 1084 |
-
raise ValueError("divisor must be non-zero")
|
| 1085 |
-
result = []
|
| 1086 |
-
for n in numbers:
|
| 1087 |
-
if not isinstance(n, (int, float)):
|
| 1088 |
-
raise TypeError(f"non-numeric item: {n!r}")
|
| 1089 |
-
result.append(n / divisor)
|
| 1090 |
-
return result
|
| 1091 |
-
\`\`\`
|
| 1092 |
-
|
| 1093 |
-
For very large lists, switch to a generator (\`yield\` instead of \`append\`) to keep memory flat.`,
|
| 1094 |
-
sections: {
|
| 1095 |
-
thinking: ['Two issues: zero-divisor crash, non-numeric crash. Adding explicit guards and a clearer error message.'],
|
| 1096 |
-
critique: ['Function silently coerces booleans because bool ⊂ int in Python — may want to exclude them explicitly.'],
|
| 1097 |
-
fix: ['Added divisor==0 guard, added isinstance validation, kept the original signature.'],
|
| 1098 |
-
code: ['Patched divide_list with safe input checking.'],
|
| 1099 |
-
},
|
| 1100 |
-
},
|
| 1101 |
-
];
|
| 1102 |
-
|
| 1103 |
-
const DEFAULT_DEMO = {
|
| 1104 |
-
response:
|
| 1105 |
-
`I'm running in **Demo Mode** because the live API isn't reachable.
|
| 1106 |
-
|
| 1107 |
-
Try one of the quick-action prompts on the welcome screen, or open Settings (double-click the brand logo) and paste your MINDI API URL.
|
| 1108 |
-
|
| 1109 |
-
\`\`\`javascript
|
| 1110 |
-
// You sent a prompt and I'm a placeholder.
|
| 1111 |
-
// Connect the real API to see actual generations.
|
| 1112 |
-
console.log("MINDI 1.5 — awaiting connection");
|
| 1113 |
-
\`\`\``,
|
| 1114 |
-
sections: {
|
| 1115 |
-
thinking: ['No API URL configured or the endpoint is unreachable. Returning a demo response.'],
|
| 1116 |
-
code: ['Stub response.'],
|
| 1117 |
-
},
|
| 1118 |
-
};
|
| 1119 |
-
|
| 1120 |
-
function pickDemo(prompt) {
|
| 1121 |
-
const found = DEMO_RESPONSES.find((d) => d.match.test(prompt));
|
| 1122 |
-
return found || DEFAULT_DEMO;
|
| 1123 |
-
}
|
| 1124 |
-
|
| 1125 |
-
async function generateDemo(prompt) {
|
| 1126 |
-
await new Promise((r) => setTimeout(r, 1200 + Math.random() * 700));
|
| 1127 |
-
const demo = pickDemo(prompt);
|
| 1128 |
-
return { response: demo.response, sections: demo.sections || {} };
|
| 1129 |
-
}
|
| 1130 |
-
|
| 1131 |
-
// ----------------------------------------------------------------
|
| 1132 |
-
// Status
|
| 1133 |
-
// ----------------------------------------------------------------
|
| 1134 |
-
function setStatus(status, label) {
|
| 1135 |
-
runtime.status = status;
|
| 1136 |
-
els.statusDot.classList.remove('status-dot--gray', 'status-dot--green', 'status-dot--yellow', 'status-dot--red');
|
| 1137 |
-
const map = { connecting: 'gray', online: 'green', demo: 'yellow', offline: 'red' };
|
| 1138 |
-
els.statusDot.classList.add(`status-dot--${map[status] || 'gray'}`);
|
| 1139 |
-
els.statusText.textContent = label;
|
| 1140 |
-
}
|
| 1141 |
-
|
| 1142 |
-
// ----------------------------------------------------------------
|
| 1143 |
-
// Toasts
|
| 1144 |
-
// ----------------------------------------------------------------
|
| 1145 |
-
function toast(msg, kind = 'info', ms = 2400) {
|
| 1146 |
-
const el = document.createElement('div');
|
| 1147 |
-
el.className = `toast toast--${kind}`;
|
| 1148 |
-
el.innerHTML = `<span class="toast-icon"></span><span>${escapeHtml(msg)}</span>`;
|
| 1149 |
-
els.toasts.appendChild(el);
|
| 1150 |
-
setTimeout(() => {
|
| 1151 |
-
el.classList.add('is-leaving');
|
| 1152 |
-
setTimeout(() => el.remove(), 260);
|
| 1153 |
-
}, ms);
|
| 1154 |
-
}
|
| 1155 |
-
|
| 1156 |
-
// ----------------------------------------------------------------
|
| 1157 |
-
// Chat data helpers
|
| 1158 |
-
// ----------------------------------------------------------------
|
| 1159 |
-
function currentChat() {
|
| 1160 |
-
return state.chats.find((c) => c.id === state.currentId) || null;
|
| 1161 |
-
}
|
| 1162 |
-
function ensureChat() {
|
| 1163 |
-
let chat = currentChat();
|
| 1164 |
-
if (!chat) {
|
| 1165 |
-
chat = { id: uid(), title: 'New chat', createdAt: Date.now(), updatedAt: Date.now(), messages: [] };
|
| 1166 |
-
state.chats.unshift(chat);
|
| 1167 |
-
state.currentId = chat.id;
|
| 1168 |
-
}
|
| 1169 |
-
return chat;
|
| 1170 |
-
}
|
| 1171 |
-
function deriveTitle(text) {
|
| 1172 |
-
const t = (text || '').replace(/\s+/g, ' ').trim();
|
| 1173 |
-
if (!t) return 'New chat';
|
| 1174 |
-
return t.length > 42 ? t.slice(0, 42).trim() + '…' : t;
|
| 1175 |
-
}
|
| 1176 |
-
|
| 1177 |
-
// ----------------------------------------------------------------
|
| 1178 |
-
// Render: messages
|
| 1179 |
-
// ----------------------------------------------------------------
|
| 1180 |
-
function renderMessages() {
|
| 1181 |
-
const chat = currentChat();
|
| 1182 |
-
const hasMessages = !!(chat && chat.messages.length);
|
| 1183 |
-
els.chat.classList.toggle('has-messages', hasMessages);
|
| 1184 |
-
els.messages.innerHTML = '';
|
| 1185 |
-
if (!hasMessages) return;
|
| 1186 |
-
|
| 1187 |
-
chat.messages.forEach((m) => els.messages.appendChild(renderMessageEl(m)));
|
| 1188 |
-
|
| 1189 |
-
// Highlight after insertion
|
| 1190 |
-
if (window.Prism) {
|
| 1191 |
-
try { Prism.highlightAllUnder(els.messages); } catch { /* noop */ }
|
| 1192 |
-
}
|
| 1193 |
-
scrollMessagesToBottom();
|
| 1194 |
-
}
|
| 1195 |
-
|
| 1196 |
-
function renderMessageEl(m) {
|
| 1197 |
-
const wrap = document.createElement('div');
|
| 1198 |
-
wrap.className = `msg msg--${m.role === 'user' ? 'user' : 'asst'}`;
|
| 1199 |
-
|
| 1200 |
-
const avatar = document.createElement('div');
|
| 1201 |
-
avatar.className = 'msg-avatar';
|
| 1202 |
-
avatar.textContent = m.role === 'user' ? 'You'.slice(0,1) : 'M';
|
| 1203 |
-
|
| 1204 |
-
const body = document.createElement('div');
|
| 1205 |
-
body.className = 'msg-body';
|
| 1206 |
-
|
| 1207 |
-
const meta = document.createElement('div');
|
| 1208 |
-
meta.className = 'msg-meta';
|
| 1209 |
-
meta.innerHTML = `<span class="msg-meta-name">${m.role === 'user' ? 'You' : 'MINDI 1.5'}</span>`;
|
| 1210 |
-
body.appendChild(meta);
|
| 1211 |
-
|
| 1212 |
-
if (Array.isArray(m.images) && m.images.length) {
|
| 1213 |
-
const imgsWrap = document.createElement('div');
|
| 1214 |
-
imgsWrap.className = 'msg-images';
|
| 1215 |
-
m.images.forEach((src) => {
|
| 1216 |
-
const img = document.createElement('img');
|
| 1217 |
-
img.src = src;
|
| 1218 |
-
img.alt = 'Attached image';
|
| 1219 |
-
imgsWrap.appendChild(img);
|
| 1220 |
-
});
|
| 1221 |
-
body.appendChild(imgsWrap);
|
| 1222 |
-
}
|
| 1223 |
-
|
| 1224 |
-
const bubble = document.createElement('div');
|
| 1225 |
-
bubble.className = 'msg-bubble';
|
| 1226 |
-
if (m.loading) {
|
| 1227 |
-
bubble.innerHTML = `<span>${escapeHtml(m.content || 'Thinking')}</span><span class="dots"><span></span><span></span><span></span></span>`;
|
| 1228 |
-
wrap.classList.add('msg-loading');
|
| 1229 |
-
} else {
|
| 1230 |
-
bubble.innerHTML = renderMarkdown(cleanForDisplay(m.content));
|
| 1231 |
-
}
|
| 1232 |
-
body.appendChild(bubble);
|
| 1233 |
-
|
| 1234 |
-
wrap.appendChild(avatar);
|
| 1235 |
-
wrap.appendChild(body);
|
| 1236 |
-
return wrap;
|
| 1237 |
-
}
|
| 1238 |
-
|
| 1239 |
-
function scrollMessagesToBottom() {
|
| 1240 |
-
requestAnimationFrame(() => {
|
| 1241 |
-
els.messages.scrollTop = els.messages.scrollHeight;
|
| 1242 |
-
});
|
| 1243 |
-
}
|
| 1244 |
-
|
| 1245 |
-
// ----------------------------------------------------------------
|
| 1246 |
-
// Render: history sidebar
|
| 1247 |
-
// ----------------------------------------------------------------
|
| 1248 |
-
function renderHistory() {
|
| 1249 |
-
const q = (els.search.value || '').toLowerCase().trim();
|
| 1250 |
-
const filtered = q
|
| 1251 |
-
? state.chats.filter((c) => c.title.toLowerCase().includes(q) ||
|
| 1252 |
-
c.messages.some((m) => (m.content || '').toLowerCase().includes(q)))
|
| 1253 |
-
: state.chats;
|
| 1254 |
-
|
| 1255 |
-
els.history.innerHTML = '';
|
| 1256 |
-
|
| 1257 |
-
if (!filtered.length) {
|
| 1258 |
-
els.history.innerHTML = `
|
| 1259 |
-
<div class="history-empty">
|
| 1260 |
-
<p>${q ? 'No matches.' : 'No chats yet.'}</p>
|
| 1261 |
-
<p class="muted">${q ? 'Try a different search.' : 'Start a conversation to see it here.'}</p>
|
| 1262 |
-
</div>`;
|
| 1263 |
-
return;
|
| 1264 |
-
}
|
| 1265 |
-
|
| 1266 |
-
// Group by date
|
| 1267 |
-
const groupOrder = ['Today', 'Yesterday', 'This Week', 'Earlier'];
|
| 1268 |
-
const groups = {};
|
| 1269 |
-
filtered.forEach((c) => {
|
| 1270 |
-
const g = relativeDateGroup(c.updatedAt || c.createdAt);
|
| 1271 |
-
(groups[g] ||= []).push(c);
|
| 1272 |
-
});
|
| 1273 |
-
|
| 1274 |
-
groupOrder.forEach((g) => {
|
| 1275 |
-
if (!groups[g]) return;
|
| 1276 |
-
const wrap = document.createElement('div');
|
| 1277 |
-
wrap.className = 'history-group';
|
| 1278 |
-
wrap.innerHTML = `<div class="history-group-title">${g}</div>`;
|
| 1279 |
-
groups[g].forEach((c) => {
|
| 1280 |
-
const btn = document.createElement('button');
|
| 1281 |
-
btn.className = 'history-item';
|
| 1282 |
-
if (c.id === state.currentId) btn.classList.add('is-active');
|
| 1283 |
-
btn.textContent = c.title || 'New chat';
|
| 1284 |
-
btn.title = c.title;
|
| 1285 |
-
btn.addEventListener('click', () => loadChat(c.id));
|
| 1286 |
-
wrap.appendChild(btn);
|
| 1287 |
-
});
|
| 1288 |
-
els.history.appendChild(wrap);
|
| 1289 |
-
});
|
| 1290 |
-
}
|
| 1291 |
-
|
| 1292 |
-
function loadChat(id) {
|
| 1293 |
-
state.currentId = id;
|
| 1294 |
-
const chat = currentChat();
|
| 1295 |
-
if (chat) {
|
| 1296 |
-
els.chatTitle.textContent = chat.title || 'New chat';
|
| 1297 |
-
// recompute preview panels from last assistant message
|
| 1298 |
-
const lastAssistant = [...chat.messages].reverse().find((m) => m.role === 'assistant' && !m.loading);
|
| 1299 |
-
if (lastAssistant) updatePreviewFromAssistant(lastAssistant);
|
| 1300 |
-
else clearPreview();
|
| 1301 |
-
}
|
| 1302 |
-
renderMessages();
|
| 1303 |
-
renderHistory();
|
| 1304 |
-
saveState();
|
| 1305 |
-
closeMobileSidebar();
|
| 1306 |
-
}
|
| 1307 |
-
|
| 1308 |
-
// ----------------------------------------------------------------
|
| 1309 |
-
// Preview panel updates
|
| 1310 |
-
// ----------------------------------------------------------------
|
| 1311 |
-
function clearPreview() {
|
| 1312 |
-
runtime.lastCode = null;
|
| 1313 |
-
runtime.lastSections = null;
|
| 1314 |
-
els.codeOut.hidden = true;
|
| 1315 |
-
els.emptyCode.hidden = false;
|
| 1316 |
-
els.liveFrame.hidden = true;
|
| 1317 |
-
els.emptyLive.hidden = false;
|
| 1318 |
-
els.sections.hidden = true;
|
| 1319 |
-
els.emptySections.hidden = false;
|
| 1320 |
-
els.sections.innerHTML = '';
|
| 1321 |
-
els.codeOutInner.textContent = '';
|
| 1322 |
-
}
|
| 1323 |
-
|
| 1324 |
-
function updatePreviewFromAssistant(msg) {
|
| 1325 |
-
const cleaned = cleanForDisplay(msg.content);
|
| 1326 |
-
const block = extractLastCodeBlock(cleaned);
|
| 1327 |
-
runtime.lastCode = block;
|
| 1328 |
-
if (block) renderCodeOut(block);
|
| 1329 |
-
else { els.codeOut.hidden = true; els.emptyCode.hidden = false; }
|
| 1330 |
-
|
| 1331 |
-
// Live HTML preview
|
| 1332 |
-
if (block && /^(markup|html)$/i.test(block.language || '')) {
|
| 1333 |
-
renderLivePreview(block.code);
|
| 1334 |
-
} else {
|
| 1335 |
-
els.liveFrame.hidden = true;
|
| 1336 |
-
els.emptyLive.hidden = false;
|
| 1337 |
-
}
|
| 1338 |
-
|
| 1339 |
-
// Sections
|
| 1340 |
-
const apiSections = msg.sections || {};
|
| 1341 |
-
const parsedSections = parseSections(msg.content);
|
| 1342 |
-
runtime.lastSections = mergeSections(apiSections, parsedSections);
|
| 1343 |
-
renderSections(runtime.lastSections);
|
| 1344 |
-
}
|
| 1345 |
-
|
| 1346 |
-
function renderCodeOut(block) {
|
| 1347 |
-
const lang = block.language || 'plaintext';
|
| 1348 |
-
els.codeOutInner.className = `language-${lang}`;
|
| 1349 |
-
els.codeOutInner.textContent = block.code;
|
| 1350 |
-
els.emptyCode.hidden = true;
|
| 1351 |
-
els.codeOut.hidden = false;
|
| 1352 |
-
if (window.Prism) {
|
| 1353 |
-
try { Prism.highlightElement(els.codeOutInner); } catch { /* noop */ }
|
| 1354 |
-
}
|
| 1355 |
-
}
|
| 1356 |
-
|
| 1357 |
-
function renderLivePreview(html) {
|
| 1358 |
-
els.emptyLive.hidden = true;
|
| 1359 |
-
els.liveFrame.hidden = false;
|
| 1360 |
-
const doc = els.liveFrame.contentDocument || els.liveFrame.contentWindow.document;
|
| 1361 |
-
doc.open();
|
| 1362 |
-
doc.write(html);
|
| 1363 |
-
doc.close();
|
| 1364 |
-
}
|
| 1365 |
-
|
| 1366 |
-
function renderSections(sections) {
|
| 1367 |
-
const hasAny = SECTION_ORDER.some((k) => (sections[k] || []).length);
|
| 1368 |
-
if (!hasAny) {
|
| 1369 |
-
els.sections.hidden = true;
|
| 1370 |
-
els.emptySections.hidden = false;
|
| 1371 |
-
els.sections.innerHTML = '';
|
| 1372 |
-
return;
|
| 1373 |
-
}
|
| 1374 |
-
els.emptySections.hidden = true;
|
| 1375 |
-
els.sections.hidden = false;
|
| 1376 |
-
els.sections.innerHTML = '';
|
| 1377 |
-
|
| 1378 |
-
SECTION_ORDER.forEach((kind) => {
|
| 1379 |
-
const items = sections[kind] || [];
|
| 1380 |
-
items.forEach((body, i) => {
|
| 1381 |
-
const card = document.createElement('div');
|
| 1382 |
-
card.className = 'section-card';
|
| 1383 |
-
card.dataset.kind = kind;
|
| 1384 |
-
card.innerHTML = `
|
| 1385 |
-
<div class="section-card-head">
|
| 1386 |
-
<span class="section-tag">${SECTION_LABELS[kind]}</span>
|
| 1387 |
-
<span>${items.length > 1 ? `${i + 1} / ${items.length}` : ''}</span>
|
| 1388 |
-
</div>
|
| 1389 |
-
<div class="section-card-body">${escapeHtml(body)}</div>
|
| 1390 |
-
`;
|
| 1391 |
-
els.sections.appendChild(card);
|
| 1392 |
-
});
|
| 1393 |
-
});
|
| 1394 |
-
}
|
| 1395 |
-
|
| 1396 |
-
// ----------------------------------------------------------------
|
| 1397 |
-
// Send flow
|
| 1398 |
-
// ----------------------------------------------------------------
|
| 1399 |
-
async function send() {
|
| 1400 |
-
if (runtime.isSending) return;
|
| 1401 |
-
const text = els.promptInput.value.trim();
|
| 1402 |
-
if (!text && !runtime.pendingImages.length) return;
|
| 1403 |
-
|
| 1404 |
-
const chat = ensureChat();
|
| 1405 |
-
const wasEmpty = chat.messages.length === 0;
|
| 1406 |
-
|
| 1407 |
-
const userMsg = {
|
| 1408 |
-
role: 'user',
|
| 1409 |
-
content: text,
|
| 1410 |
-
images: runtime.pendingImages.map((p) => p.dataUrl),
|
| 1411 |
-
ts: Date.now(),
|
| 1412 |
-
};
|
| 1413 |
-
chat.messages.push(userMsg);
|
| 1414 |
-
chat.updatedAt = Date.now();
|
| 1415 |
-
|
| 1416 |
-
if (wasEmpty) {
|
| 1417 |
-
chat.title = deriveTitle(text);
|
| 1418 |
-
els.chatTitle.textContent = chat.title;
|
| 1419 |
-
}
|
| 1420 |
-
|
| 1421 |
-
// Reset input.
|
| 1422 |
-
// If vision is disabled in Settings, drop the image before calling
|
| 1423 |
-
// the API so we don't waste an upload round-trip on something the
|
| 1424 |
-
// backend will ignore. The image still appears in the user message
|
| 1425 |
-
// for the chat record.
|
| 1426 |
-
const imageForApi = state.visionEnabled
|
| 1427 |
-
? (runtime.pendingImages[0]?.dataUrl || null)
|
| 1428 |
-
: null;
|
| 1429 |
-
if (!state.visionEnabled && runtime.pendingImages.length) {
|
| 1430 |
-
toast('Vision is disabled \u2014 image attached for record only. Enable it in Settings to send to the model.', 'info', 4500);
|
| 1431 |
-
}
|
| 1432 |
-
els.promptInput.value = '';
|
| 1433 |
-
autosizeTextarea();
|
| 1434 |
-
clearPendingImages();
|
| 1435 |
-
updateSendEnabled();
|
| 1436 |
-
|
| 1437 |
-
// Loading message
|
| 1438 |
-
const loadingMsg = { role: 'assistant', content: 'Thinking', loading: true, ts: Date.now() };
|
| 1439 |
-
chat.messages.push(loadingMsg);
|
| 1440 |
-
renderMessages();
|
| 1441 |
-
renderHistory();
|
| 1442 |
-
saveState();
|
| 1443 |
-
|
| 1444 |
-
runtime.isSending = true;
|
| 1445 |
-
let coldStartTimer = setTimeout(() => {
|
| 1446 |
-
loadingMsg.content = 'Cold start — booting the GPU. First request can take ~4 minutes';
|
| 1447 |
-
renderMessages();
|
| 1448 |
-
}, COLD_START_HINT_MS);
|
| 1449 |
-
|
| 1450 |
-
let result, errored = null;
|
| 1451 |
-
try {
|
| 1452 |
-
// If we already know auth is blocked, don't call the API again — the
|
| 1453 |
-
// request would just consume more anonymous quota and return the same
|
| 1454 |
-
// error. Show the inline 'add your token' card instead.
|
| 1455 |
-
if (runtime.authBlocked) {
|
| 1456 |
-
result = makeAuthBlockedResponse();
|
| 1457 |
-
} else if (runtime.status === 'demo' || !state.apiUrl) {
|
| 1458 |
-
result = await generateDemo(text);
|
| 1459 |
-
} else {
|
| 1460 |
-
result = await callGenerate(text, imageForApi);
|
| 1461 |
-
}
|
| 1462 |
-
} catch (e) {
|
| 1463 |
-
errored = e;
|
| 1464 |
-
// Auto-fallback to demo so the UI never feels dead
|
| 1465 |
-
result = await generateDemo(text).catch(() => ({ response: 'Generation failed.' }));
|
| 1466 |
-
if (!/cold|abort|signal/i.test(String(e?.message || ''))) {
|
| 1467 |
-
toast('API error — falling back to demo', 'error', 3500);
|
| 1468 |
-
}
|
| 1469 |
-
} finally {
|
| 1470 |
-
clearTimeout(coldStartTimer);
|
| 1471 |
-
runtime.isSending = false;
|
| 1472 |
-
}
|
| 1473 |
-
|
| 1474 |
-
// If the API returned a quota / auth error, surface it clearly and
|
| 1475 |
-
// stop calling the API on subsequent messages until the user adds a token.
|
| 1476 |
-
const authMsg = detectAuthError(result);
|
| 1477 |
-
if (authMsg) {
|
| 1478 |
-
runtime.authBlocked = true;
|
| 1479 |
-
toast(authMsg, 'error', 7000);
|
| 1480 |
-
setStatus('demo', state.hfToken ? 'Quota exhausted' : 'Auth required');
|
| 1481 |
-
// Replace the raw backend error with a friendlier inline card so the
|
| 1482 |
-
// chat doesn't show a wall of quota-error text.
|
| 1483 |
-
result = makeAuthBlockedResponse();
|
| 1484 |
-
}
|
| 1485 |
-
|
| 1486 |
-
// Remove loading, push assistant
|
| 1487 |
-
const idx = chat.messages.indexOf(loadingMsg);
|
| 1488 |
-
if (idx !== -1) chat.messages.splice(idx, 1);
|
| 1489 |
-
|
| 1490 |
-
const assistantMsg = {
|
| 1491 |
-
role: 'assistant',
|
| 1492 |
-
content: result?.response || '(no response)',
|
| 1493 |
-
sections: result?.sections || null,
|
| 1494 |
-
ts: Date.now(),
|
| 1495 |
-
};
|
| 1496 |
-
chat.messages.push(assistantMsg);
|
| 1497 |
-
chat.updatedAt = Date.now();
|
| 1498 |
-
|
| 1499 |
-
renderMessages();
|
| 1500 |
-
renderHistory();
|
| 1501 |
-
updatePreviewFromAssistant(assistantMsg);
|
| 1502 |
-
saveState();
|
| 1503 |
-
|
| 1504 |
-
if (errored) console.warn('[mindi] generate error:', errored);
|
| 1505 |
-
}
|
| 1506 |
-
|
| 1507 |
-
// ----------------------------------------------------------------
|
| 1508 |
-
// Composer interactions
|
| 1509 |
-
// ----------------------------------------------------------------
|
| 1510 |
-
function autosizeTextarea() {
|
| 1511 |
-
const ta = els.promptInput;
|
| 1512 |
-
ta.style.height = 'auto';
|
| 1513 |
-
const next = Math.min(ta.scrollHeight, MAX_TEXTAREA);
|
| 1514 |
-
ta.style.height = next + 'px';
|
| 1515 |
-
}
|
| 1516 |
-
function updateSendEnabled() {
|
| 1517 |
-
const has = els.promptInput.value.trim().length > 0 || runtime.pendingImages.length > 0;
|
| 1518 |
-
els.sendBtn.disabled = !has || runtime.isSending;
|
| 1519 |
-
}
|
| 1520 |
-
function clearPendingImages() {
|
| 1521 |
-
runtime.pendingImages = [];
|
| 1522 |
-
renderPendingImages();
|
| 1523 |
-
}
|
| 1524 |
-
function renderPendingImages() {
|
| 1525 |
-
if (!runtime.pendingImages.length) {
|
| 1526 |
-
els.composerImages.hidden = true;
|
| 1527 |
-
els.composerImages.innerHTML = '';
|
| 1528 |
-
return;
|
| 1529 |
-
}
|
| 1530 |
-
els.composerImages.hidden = false;
|
| 1531 |
-
els.composerImages.innerHTML = '';
|
| 1532 |
-
runtime.pendingImages.forEach((p, i) => {
|
| 1533 |
-
const tile = document.createElement('div');
|
| 1534 |
-
tile.className = 'composer-image';
|
| 1535 |
-
tile.style.backgroundImage = `url("${p.dataUrl}")`;
|
| 1536 |
-
tile.title = p.name;
|
| 1537 |
-
const rm = document.createElement('button');
|
| 1538 |
-
rm.className = 'composer-image-remove';
|
| 1539 |
-
rm.setAttribute('aria-label', 'Remove image');
|
| 1540 |
-
rm.textContent = '×';
|
| 1541 |
-
rm.addEventListener('click', () => {
|
| 1542 |
-
runtime.pendingImages.splice(i, 1);
|
| 1543 |
-
renderPendingImages();
|
| 1544 |
-
updateSendEnabled();
|
| 1545 |
-
});
|
| 1546 |
-
tile.appendChild(rm);
|
| 1547 |
-
els.composerImages.appendChild(tile);
|
| 1548 |
-
});
|
| 1549 |
-
}
|
| 1550 |
-
|
| 1551 |
-
async function handleFileChosen(file) {
|
| 1552 |
-
if (!file || !file.type.startsWith('image/')) {
|
| 1553 |
-
toast('Only image files are supported.', 'error');
|
| 1554 |
-
return;
|
| 1555 |
-
}
|
| 1556 |
-
if (file.size > 6 * 1024 * 1024) {
|
| 1557 |
-
toast('Image too large (max 6 MB).', 'error');
|
| 1558 |
-
return;
|
| 1559 |
-
}
|
| 1560 |
-
try {
|
| 1561 |
-
const dataUrl = await fileToDataUrl(file);
|
| 1562 |
-
runtime.pendingImages = [{ name: file.name, dataUrl }]; // single image per request
|
| 1563 |
-
renderPendingImages();
|
| 1564 |
-
updateSendEnabled();
|
| 1565 |
-
} catch {
|
| 1566 |
-
toast('Could not read that image.', 'error');
|
| 1567 |
-
}
|
| 1568 |
-
}
|
| 1569 |
-
|
| 1570 |
-
// ----------------------------------------------------------------
|
| 1571 |
-
// Tabs
|
| 1572 |
-
// ----------------------------------------------------------------
|
| 1573 |
-
function activateTab(tabName) {
|
| 1574 |
-
els.tabs.forEach((t) => {
|
| 1575 |
-
const on = t.dataset.tab === tabName;
|
| 1576 |
-
t.classList.toggle('is-active', on);
|
| 1577 |
-
t.setAttribute('aria-selected', on ? 'true' : 'false');
|
| 1578 |
-
});
|
| 1579 |
-
els.panes.forEach((p) => {
|
| 1580 |
-
p.classList.toggle('is-active', p.dataset.pane === tabName);
|
| 1581 |
-
});
|
| 1582 |
-
}
|
| 1583 |
-
|
| 1584 |
-
// ----------------------------------------------------------------
|
| 1585 |
-
// Settings modal
|
| 1586 |
-
// ----------------------------------------------------------------
|
| 1587 |
-
function maskToken(t) {
|
| 1588 |
-
if (!t) return 'none';
|
| 1589 |
-
if (t.length <= 8) return 'set';
|
| 1590 |
-
return `${t.slice(0, 4)}…${t.slice(-4)}`;
|
| 1591 |
-
}
|
| 1592 |
-
function refreshTokenStatus() {
|
| 1593 |
-
if (els.hfTokenStatus) els.hfTokenStatus.textContent = maskToken(state.hfToken);
|
| 1594 |
-
}
|
| 1595 |
-
function openSettings() {
|
| 1596 |
-
els.settingsUrl.value = state.apiUrl || '';
|
| 1597 |
-
if (els.settingsHfToken) els.settingsHfToken.value = state.hfToken || '';
|
| 1598 |
-
if (els.settingsVision) els.settingsVision.checked = !!state.visionEnabled;
|
| 1599 |
-
els.settingsTemp.value = state.temperature;
|
| 1600 |
-
els.settingsTokens.value = state.maxTokens;
|
| 1601 |
-
els.tempVal.textContent = Number(state.temperature).toFixed(2);
|
| 1602 |
-
els.tokensVal.textContent = state.maxTokens;
|
| 1603 |
-
refreshTokenStatus();
|
| 1604 |
-
els.settingsModal.hidden = false;
|
| 1605 |
-
setTimeout(() => els.settingsUrl.focus(), 50);
|
| 1606 |
-
}
|
| 1607 |
-
function closeSettings() {
|
| 1608 |
-
els.settingsModal.hidden = true;
|
| 1609 |
-
}
|
| 1610 |
-
function applySettings() {
|
| 1611 |
-
const url = els.settingsUrl.value.trim();
|
| 1612 |
-
const token = els.settingsHfToken ? els.settingsHfToken.value.trim() : '';
|
| 1613 |
-
const vision = !!(els.settingsVision && els.settingsVision.checked);
|
| 1614 |
-
const temp = parseFloat(els.settingsTemp.value);
|
| 1615 |
-
const tokens = parseInt(els.settingsTokens.value, 10);
|
| 1616 |
-
const tokenChanged = token !== state.hfToken;
|
| 1617 |
-
state.apiUrl = url || API_DEFAULT;
|
| 1618 |
-
state.hfToken = token;
|
| 1619 |
-
state.visionEnabled = vision;
|
| 1620 |
-
state.temperature = isFinite(temp) ? temp : 0.7;
|
| 1621 |
-
state.maxTokens = isFinite(tokens) ? tokens : 2048;
|
| 1622 |
-
// If the user just saved a new (non-empty) token, give the API another shot.
|
| 1623 |
-
if (tokenChanged && token) {
|
| 1624 |
-
runtime.authBlocked = false;
|
| 1625 |
-
}
|
| 1626 |
-
saveState();
|
| 1627 |
-
refreshTokenStatus();
|
| 1628 |
-
closeSettings();
|
| 1629 |
-
toast(tokenChanged && token ? 'Token saved — retrying API' : 'Settings saved', 'success');
|
| 1630 |
-
pingHealth();
|
| 1631 |
-
}
|
| 1632 |
-
|
| 1633 |
-
// ----------------------------------------------------------------
|
| 1634 |
-
// Mobile sidebar / preview toggling
|
| 1635 |
-
// ----------------------------------------------------------------
|
| 1636 |
-
function openMobileSidebar() { els.body.classList.add('sidebar-open'); }
|
| 1637 |
-
function closeMobileSidebar() { els.body.classList.remove('sidebar-open'); }
|
| 1638 |
-
function togglePreview() {
|
| 1639 |
-
if (window.matchMedia('(max-width: 1024px)').matches) {
|
| 1640 |
-
els.body.classList.toggle('preview-open');
|
| 1641 |
-
} else {
|
| 1642 |
-
els.body.classList.toggle('preview-hidden');
|
| 1643 |
-
}
|
| 1644 |
-
}
|
| 1645 |
-
|
| 1646 |
-
// ----------------------------------------------------------------
|
| 1647 |
-
// Copy / download from preview panel
|
| 1648 |
-
// ----------------------------------------------------------------
|
| 1649 |
-
async function copyLastCode() {
|
| 1650 |
-
if (!runtime.lastCode) {
|
| 1651 |
-
toast('No code to copy yet', 'info');
|
| 1652 |
-
return;
|
| 1653 |
-
}
|
| 1654 |
-
try {
|
| 1655 |
-
await navigator.clipboard.writeText(runtime.lastCode.code);
|
| 1656 |
-
toast('Copied to clipboard', 'success', 1600);
|
| 1657 |
-
} catch {
|
| 1658 |
-
toast('Clipboard unavailable', 'error');
|
| 1659 |
-
}
|
| 1660 |
-
}
|
| 1661 |
-
function downloadLastCode() {
|
| 1662 |
-
if (!runtime.lastCode) {
|
| 1663 |
-
toast('No code to download yet', 'info');
|
| 1664 |
-
return;
|
| 1665 |
-
}
|
| 1666 |
-
const ext = (() => {
|
| 1667 |
-
const m = { javascript: 'js', typescript: 'ts', tsx: 'tsx', jsx: 'jsx',
|
| 1668 |
-
python: 'py', markup: 'html', html: 'html', css: 'css',
|
| 1669 |
-
json: 'json', sql: 'sql', bash: 'sh' };
|
| 1670 |
-
return m[runtime.lastCode.language] || 'txt';
|
| 1671 |
-
})();
|
| 1672 |
-
downloadFile(`mindi-output.${ext}`, runtime.lastCode.code);
|
| 1673 |
-
}
|
| 1674 |
-
|
| 1675 |
-
// ----------------------------------------------------------------
|
| 1676 |
-
// Bind events
|
| 1677 |
-
// ----------------------------------------------------------------
|
| 1678 |
-
// The active send handler — overridden by agent init if available
|
| 1679 |
-
let activeSend = send;
|
| 1680 |
-
|
| 1681 |
-
function bind() {
|
| 1682 |
-
// Composer
|
| 1683 |
-
els.promptInput.addEventListener('input', () => { autosizeTextarea(); updateSendEnabled(); });
|
| 1684 |
-
els.promptInput.addEventListener('keydown', (e) => {
|
| 1685 |
-
if (e.key === 'Enter' && !e.shiftKey) {
|
| 1686 |
-
e.preventDefault();
|
| 1687 |
-
activeSend();
|
| 1688 |
-
}
|
| 1689 |
-
});
|
| 1690 |
-
els.sendBtn.addEventListener('click', () => activeSend());
|
| 1691 |
-
|
| 1692 |
-
// Attach
|
| 1693 |
-
els.attachBtn.addEventListener('click', () => els.fileInput.click());
|
| 1694 |
-
els.fileInput.addEventListener('change', (e) => {
|
| 1695 |
-
const f = e.target.files?.[0];
|
| 1696 |
-
if (f) handleFileChosen(f);
|
| 1697 |
-
e.target.value = '';
|
| 1698 |
-
});
|
| 1699 |
-
|
| 1700 |
-
// Drag & drop on composer
|
| 1701 |
-
['dragenter', 'dragover'].forEach((ev) => {
|
| 1702 |
-
els.composer.addEventListener(ev, (e) => { e.preventDefault(); els.composer.style.borderColor = 'rgba(124, 58, 237, .6)'; });
|
| 1703 |
-
});
|
| 1704 |
-
['dragleave', 'drop'].forEach((ev) => {
|
| 1705 |
-
els.composer.addEventListener(ev, (e) => { e.preventDefault(); els.composer.style.borderColor = ''; });
|
| 1706 |
-
});
|
| 1707 |
-
els.composer.addEventListener('drop', (e) => {
|
| 1708 |
-
const file = e.dataTransfer?.files?.[0];
|
| 1709 |
-
if (file) handleFileChosen(file);
|
| 1710 |
-
});
|
| 1711 |
-
|
| 1712 |
-
// Quick action cards
|
| 1713 |
-
els.quickCards.forEach((card) => {
|
| 1714 |
-
card.addEventListener('click', () => {
|
| 1715 |
-
els.promptInput.value = card.dataset.prompt || '';
|
| 1716 |
-
autosizeTextarea();
|
| 1717 |
-
updateSendEnabled();
|
| 1718 |
-
els.promptInput.focus();
|
| 1719 |
-
});
|
| 1720 |
-
});
|
| 1721 |
-
|
| 1722 |
-
// Sidebar
|
| 1723 |
-
els.newChatBtn.addEventListener('click', () => {
|
| 1724 |
-
state.currentId = null;
|
| 1725 |
-
ensureChat();
|
| 1726 |
-
els.chatTitle.textContent = 'New chat';
|
| 1727 |
-
clearPreview();
|
| 1728 |
-
renderMessages();
|
| 1729 |
-
renderHistory();
|
| 1730 |
-
saveState();
|
| 1731 |
-
els.promptInput.focus();
|
| 1732 |
-
closeMobileSidebar();
|
| 1733 |
-
});
|
| 1734 |
-
els.search.addEventListener('input', debounce(renderHistory, 120));
|
| 1735 |
-
els.hamburger.addEventListener('click', openMobileSidebar);
|
| 1736 |
-
els.scrim.addEventListener('click', () => { closeMobileSidebar(); els.body.classList.remove('preview-open'); });
|
| 1737 |
-
els.togglePreview.addEventListener('click', togglePreview);
|
| 1738 |
-
|
| 1739 |
-
// Tabs
|
| 1740 |
-
els.tabs.forEach((t) => t.addEventListener('click', () => activateTab(t.dataset.tab)));
|
| 1741 |
-
|
| 1742 |
-
// Code copy / download
|
| 1743 |
-
els.copyCode.addEventListener('click', copyLastCode);
|
| 1744 |
-
els.downloadCode.addEventListener('click', downloadLastCode);
|
| 1745 |
-
|
| 1746 |
-
// Delegated click handler for code-block action buttons inside messages
|
| 1747 |
-
// (Copy, Run-in-StackBlitz, Open-in-CodeSandbox).
|
| 1748 |
-
els.messages.addEventListener('click', async (e) => {
|
| 1749 |
-
const copyBtn = e.target.closest('.md-copy');
|
| 1750 |
-
if (copyBtn) {
|
| 1751 |
-
try {
|
| 1752 |
-
await navigator.clipboard.writeText(copyBtn.dataset.code || '');
|
| 1753 |
-
const prev = copyBtn.textContent;
|
| 1754 |
-
copyBtn.textContent = 'Copied!';
|
| 1755 |
-
setTimeout(() => { copyBtn.textContent = prev; }, 1400);
|
| 1756 |
-
} catch {
|
| 1757 |
-
toast('Clipboard unavailable', 'error');
|
| 1758 |
-
}
|
| 1759 |
-
return;
|
| 1760 |
-
}
|
| 1761 |
-
|
| 1762 |
-
const runBtn = e.target.closest('.md-run');
|
| 1763 |
-
if (runBtn) {
|
| 1764 |
-
try {
|
| 1765 |
-
launchInStackBlitz(runBtn.dataset.code || '', runBtn.dataset.lang || '');
|
| 1766 |
-
} catch (err) {
|
| 1767 |
-
toast(`StackBlitz launch failed: ${err.message || err}`, 'error');
|
| 1768 |
-
}
|
| 1769 |
-
return;
|
| 1770 |
-
}
|
| 1771 |
-
|
| 1772 |
-
const sbxBtn = e.target.closest('.md-sandbox');
|
| 1773 |
-
if (sbxBtn) {
|
| 1774 |
-
const prev = sbxBtn.textContent;
|
| 1775 |
-
sbxBtn.disabled = true;
|
| 1776 |
-
sbxBtn.textContent = '\u25B6 Opening\u2026';
|
| 1777 |
-
try {
|
| 1778 |
-
await launchInCodeSandbox(sbxBtn.dataset.code || '', sbxBtn.dataset.lang || '');
|
| 1779 |
-
} finally {
|
| 1780 |
-
sbxBtn.textContent = prev;
|
| 1781 |
-
sbxBtn.disabled = false;
|
| 1782 |
-
}
|
| 1783 |
-
return;
|
| 1784 |
-
}
|
| 1785 |
-
});
|
| 1786 |
-
|
| 1787 |
-
// Brand → settings (double-click)
|
| 1788 |
-
els.brand.addEventListener('dblclick', openSettings);
|
| 1789 |
-
|
| 1790 |
-
// Settings modal
|
| 1791 |
-
els.settingsModal.addEventListener('click', (e) => {
|
| 1792 |
-
if (e.target.matches('[data-close]') || e.target.closest('[data-close]')) closeSettings();
|
| 1793 |
-
});
|
| 1794 |
-
els.settingsTemp.addEventListener('input', () => {
|
| 1795 |
-
els.tempVal.textContent = Number(els.settingsTemp.value).toFixed(2);
|
| 1796 |
-
});
|
| 1797 |
-
els.settingsTokens.addEventListener('input', () => {
|
| 1798 |
-
els.tokensVal.textContent = els.settingsTokens.value;
|
| 1799 |
-
});
|
| 1800 |
-
els.saveSettings.addEventListener('click', applySettings);
|
| 1801 |
-
|
| 1802 |
-
// Esc to close modal / mobile drawers
|
| 1803 |
-
document.addEventListener('keydown', (e) => {
|
| 1804 |
-
if (e.key === 'Escape') {
|
| 1805 |
-
if (!els.settingsModal.hidden) closeSettings();
|
| 1806 |
-
closeMobileSidebar();
|
| 1807 |
-
els.body.classList.remove('preview-open');
|
| 1808 |
-
}
|
| 1809 |
-
// Cmd/Ctrl + K → focus search
|
| 1810 |
-
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
| 1811 |
-
e.preventDefault();
|
| 1812 |
-
els.search.focus();
|
| 1813 |
-
}
|
| 1814 |
-
});
|
| 1815 |
-
|
| 1816 |
-
// Keep textarea sized after window resize (font reflow)
|
| 1817 |
-
window.addEventListener('resize', autosizeTextarea);
|
| 1818 |
-
}
|
| 1819 |
-
|
| 1820 |
-
// ----------------------------------------------------------------
|
| 1821 |
-
// Agent integration
|
| 1822 |
-
// ----------------------------------------------------------------
|
| 1823 |
-
const agentEls = {
|
| 1824 |
-
log: document.getElementById('agent-log'),
|
| 1825 |
-
sandbox: document.getElementById('agent-sandbox'),
|
| 1826 |
-
console: document.getElementById('agent-console'),
|
| 1827 |
-
consoleBody: document.getElementById('agent-console-body'),
|
| 1828 |
-
emptyAgent: document.getElementById('empty-agent'),
|
| 1829 |
-
};
|
| 1830 |
-
|
| 1831 |
-
const STEP_ICONS = {
|
| 1832 |
-
plan: '📋',
|
| 1833 |
-
generate: '⚡',
|
| 1834 |
-
execute: '▶️',
|
| 1835 |
-
verify: '✅',
|
| 1836 |
-
fix: '🔧',
|
| 1837 |
-
done: '🎉',
|
| 1838 |
-
error: '❌',
|
| 1839 |
-
};
|
| 1840 |
-
|
| 1841 |
-
const STEP_LABELS = {
|
| 1842 |
-
plan: 'Planning',
|
| 1843 |
-
generate: 'Generating Code',
|
| 1844 |
-
execute: 'Executing',
|
| 1845 |
-
verify: 'Verifying Output',
|
| 1846 |
-
fix: 'Fixing Error',
|
| 1847 |
-
done: 'Complete',
|
| 1848 |
-
error: 'Error',
|
| 1849 |
-
};
|
| 1850 |
-
|
| 1851 |
-
function isCodeRequest(text) {
|
| 1852 |
-
return /\b(build|create|make|write|generate|code|html|css|app|page|website|component|function|class|api|dashboard|landing|todo|form|navbar|button|layout|design)\b/i.test(text);
|
| 1853 |
-
}
|
| 1854 |
-
|
| 1855 |
-
function renderAgentStep(run, step) {
|
| 1856 |
-
if (!agentEls.log) return;
|
| 1857 |
-
|
| 1858 |
-
// Show agent panel, hide empty state
|
| 1859 |
-
agentEls.emptyAgent && (agentEls.emptyAgent.hidden = true);
|
| 1860 |
-
agentEls.log.hidden = false;
|
| 1861 |
-
|
| 1862 |
-
// Switch to agent tab
|
| 1863 |
-
const agentTab = document.querySelector('.tab[data-tab="agent"]');
|
| 1864 |
-
if (agentTab && !agentTab.classList.contains('is-active')) {
|
| 1865 |
-
agentTab.click();
|
| 1866 |
-
}
|
| 1867 |
-
|
| 1868 |
-
// Find or create step element
|
| 1869 |
-
let el = agentEls.log.querySelector(`[data-step-id="${step.id}"]`);
|
| 1870 |
-
if (!el) {
|
| 1871 |
-
el = document.createElement('div');
|
| 1872 |
-
el.className = 'agent-step';
|
| 1873 |
-
el.dataset.stepId = step.id;
|
| 1874 |
-
el.innerHTML = `
|
| 1875 |
-
<div class="agent-step-icon"></div>
|
| 1876 |
-
<div class="agent-step-body">
|
| 1877 |
-
<div class="agent-step-title"></div>
|
| 1878 |
-
<div class="agent-step-detail"></div>
|
| 1879 |
-
</div>`;
|
| 1880 |
-
agentEls.log.appendChild(el);
|
| 1881 |
-
}
|
| 1882 |
-
|
| 1883 |
-
// Update status class
|
| 1884 |
-
el.className = `agent-step agent-step--${step.status}`;
|
| 1885 |
-
|
| 1886 |
-
// Update icon
|
| 1887 |
-
const iconEl = el.querySelector('.agent-step-icon');
|
| 1888 |
-
const statusIcons = { running: '⏳', success: '✅', failed: '❌', pending: '⏺' };
|
| 1889 |
-
iconEl.textContent = step.status === 'success' || step.status === 'failed'
|
| 1890 |
-
? statusIcons[step.status]
|
| 1891 |
-
: (STEP_ICONS[step.type] || '⏳');
|
| 1892 |
-
|
| 1893 |
-
// Update title
|
| 1894 |
-
el.querySelector('.agent-step-title').textContent = STEP_LABELS[step.type] || step.type;
|
| 1895 |
-
|
| 1896 |
-
// Update detail
|
| 1897 |
-
el.querySelector('.agent-step-detail').textContent = step.detail || '';
|
| 1898 |
-
|
| 1899 |
-
// Auto-scroll
|
| 1900 |
-
agentEls.log.scrollTop = agentEls.log.scrollHeight;
|
| 1901 |
-
|
| 1902 |
-
// Show sandbox and console when executing
|
| 1903 |
-
if (step.type === 'execute') {
|
| 1904 |
-
agentEls.sandbox.hidden = false;
|
| 1905 |
-
agentEls.console.hidden = false;
|
| 1906 |
-
}
|
| 1907 |
-
|
| 1908 |
-
// Update console on execution results
|
| 1909 |
-
if (step.type === 'execute' && (step.status === 'success' || step.status === 'failed')) {
|
| 1910 |
-
const run_ = run; // closure
|
| 1911 |
-
if (run_.currentCode) {
|
| 1912 |
-
// Show code in the Code tab too
|
| 1913 |
-
const block = { language: run_.language || 'javascript', code: run_.currentCode };
|
| 1914 |
-
runtime.lastCode = block;
|
| 1915 |
-
renderCodeOut(block);
|
| 1916 |
-
}
|
| 1917 |
-
}
|
| 1918 |
-
|
| 1919 |
-
// When done, show final code in preview
|
| 1920 |
-
if (step.type === 'done' && step.status === 'success' && run.currentCode) {
|
| 1921 |
-
const lang = run.language || 'javascript';
|
| 1922 |
-
if (/^(html|markup)$/i.test(lang)) {
|
| 1923 |
-
// Render in live preview
|
| 1924 |
-
els.liveFrame.hidden = false;
|
| 1925 |
-
const emptyLive = document.getElementById('empty-live');
|
| 1926 |
-
if (emptyLive) emptyLive.hidden = true;
|
| 1927 |
-
els.liveFrame.srcdoc = run.currentCode;
|
| 1928 |
-
}
|
| 1929 |
-
}
|
| 1930 |
-
}
|
| 1931 |
-
|
| 1932 |
-
function clearAgentUI() {
|
| 1933 |
-
if (agentEls.log) {
|
| 1934 |
-
agentEls.log.innerHTML = '';
|
| 1935 |
-
agentEls.log.hidden = true;
|
| 1936 |
-
}
|
| 1937 |
-
if (agentEls.sandbox) {
|
| 1938 |
-
agentEls.sandbox.innerHTML = '';
|
| 1939 |
-
agentEls.sandbox.hidden = true;
|
| 1940 |
-
}
|
| 1941 |
-
if (agentEls.console) {
|
| 1942 |
-
agentEls.console.hidden = true;
|
| 1943 |
-
}
|
| 1944 |
-
if (agentEls.consoleBody) {
|
| 1945 |
-
agentEls.consoleBody.textContent = '';
|
| 1946 |
-
}
|
| 1947 |
-
if (agentEls.emptyAgent) {
|
| 1948 |
-
agentEls.emptyAgent.hidden = false;
|
| 1949 |
-
}
|
| 1950 |
-
}
|
| 1951 |
-
|
| 1952 |
-
async function runAgent(prompt, image) {
|
| 1953 |
-
clearAgentUI();
|
| 1954 |
-
|
| 1955 |
-
const apiCall = async (p, img) => {
|
| 1956 |
-
if (runtime.status === 'demo' || !state.apiUrl) {
|
| 1957 |
-
return generateDemo(p);
|
| 1958 |
-
}
|
| 1959 |
-
return callGenerate(p, img);
|
| 1960 |
-
};
|
| 1961 |
-
|
| 1962 |
-
const result = await MINDIAgent.run(prompt, {
|
| 1963 |
-
apiCall,
|
| 1964 |
-
sandboxContainer: agentEls.sandbox,
|
| 1965 |
-
image,
|
| 1966 |
-
onStep: (run, step) => {
|
| 1967 |
-
renderAgentStep(run, step);
|
| 1968 |
-
|
| 1969 |
-
// Update console output
|
| 1970 |
-
if (step.type === 'execute' && agentEls.consoleBody) {
|
| 1971 |
-
const detail = step.detail || '';
|
| 1972 |
-
if (step.status === 'failed') {
|
| 1973 |
-
agentEls.consoleBody.innerHTML += `<span class="console-error">${escapeHtml(detail)}</span>\n`;
|
| 1974 |
-
} else if (step.status === 'success') {
|
| 1975 |
-
agentEls.consoleBody.textContent += detail + '\n';
|
| 1976 |
-
}
|
| 1977 |
-
}
|
| 1978 |
-
},
|
| 1979 |
-
});
|
| 1980 |
-
|
| 1981 |
-
return result;
|
| 1982 |
-
}
|
| 1983 |
-
|
| 1984 |
-
// Agent-aware send handler: delegates to agent for code requests, standard send otherwise
|
| 1985 |
-
async function handleSendWithAgent() {
|
| 1986 |
-
const text = els.promptInput.value.trim();
|
| 1987 |
-
if (!text && !runtime.pendingImages.length) return;
|
| 1988 |
-
|
| 1989 |
-
// Determine if this should use the agent
|
| 1990 |
-
const useAgent = typeof MINDIAgent !== 'undefined' && isCodeRequest(text);
|
| 1991 |
-
|
| 1992 |
-
// If we know auth is blocked, the agent loop would just call the API
|
| 1993 |
-
// multiple times and fail every iteration. Fall back to send(), which
|
| 1994 |
-
// now renders the friendly inline 'add your token' card instead.
|
| 1995 |
-
if (!useAgent || runtime.authBlocked) {
|
| 1996 |
-
return send();
|
| 1997 |
-
}
|
| 1998 |
-
|
| 1999 |
-
// Agent mode
|
| 2000 |
-
const chat = ensureChat();
|
| 2001 |
-
const wasEmpty = chat.messages.length === 0;
|
| 2002 |
-
|
| 2003 |
-
const userMsg = {
|
| 2004 |
-
role: 'user',
|
| 2005 |
-
content: text,
|
| 2006 |
-
images: runtime.pendingImages.map((p) => p.dataUrl),
|
| 2007 |
-
ts: Date.now(),
|
| 2008 |
-
};
|
| 2009 |
-
chat.messages.push(userMsg);
|
| 2010 |
-
chat.updatedAt = Date.now();
|
| 2011 |
-
|
| 2012 |
-
if (wasEmpty) {
|
| 2013 |
-
chat.title = deriveTitle(text);
|
| 2014 |
-
els.chatTitle.textContent = chat.title;
|
| 2015 |
-
}
|
| 2016 |
-
|
| 2017 |
-
const imageForApi = runtime.pendingImages[0]?.dataUrl || null;
|
| 2018 |
-
els.promptInput.value = '';
|
| 2019 |
-
autosizeTextarea();
|
| 2020 |
-
clearPendingImages();
|
| 2021 |
-
updateSendEnabled();
|
| 2022 |
-
|
| 2023 |
-
// Show loading
|
| 2024 |
-
const loadingMsg = { role: 'assistant', content: '🤖 Agent working', loading: true, ts: Date.now() };
|
| 2025 |
-
chat.messages.push(loadingMsg);
|
| 2026 |
-
renderMessages();
|
| 2027 |
-
renderHistory();
|
| 2028 |
-
saveState();
|
| 2029 |
-
|
| 2030 |
-
runtime.isSending = true;
|
| 2031 |
-
|
| 2032 |
-
try {
|
| 2033 |
-
const agentResult = await runAgent(text, imageForApi);
|
| 2034 |
-
|
| 2035 |
-
// Remove loading
|
| 2036 |
-
const idx = chat.messages.indexOf(loadingMsg);
|
| 2037 |
-
if (idx !== -1) chat.messages.splice(idx, 1);
|
| 2038 |
-
|
| 2039 |
-
// Build response from agent
|
| 2040 |
-
const iterations = agentResult.iteration + 1;
|
| 2041 |
-
const status = agentResult.status === 'success' ? '✅' : '❌';
|
| 2042 |
-
let responseText = `${status} Agent completed in ${iterations} iteration(s).\n\n`;
|
| 2043 |
-
|
| 2044 |
-
if (agentResult.currentCode) {
|
| 2045 |
-
const lang = agentResult.language || 'javascript';
|
| 2046 |
-
responseText += `\`\`\`${lang}\n${agentResult.currentCode}\n\`\`\``;
|
| 2047 |
-
} else {
|
| 2048 |
-
// Fallback: get the last generate step's detail
|
| 2049 |
-
const lastGen = [...agentResult.steps].reverse().find(s => s.type === 'generate' || s.type === 'fix');
|
| 2050 |
-
if (lastGen) responseText += lastGen.detail || 'No code generated.';
|
| 2051 |
-
}
|
| 2052 |
-
|
| 2053 |
-
const assistantMsg = {
|
| 2054 |
-
role: 'assistant',
|
| 2055 |
-
content: responseText,
|
| 2056 |
-
ts: Date.now(),
|
| 2057 |
-
};
|
| 2058 |
-
chat.messages.push(assistantMsg);
|
| 2059 |
-
chat.updatedAt = Date.now();
|
| 2060 |
-
|
| 2061 |
-
renderMessages();
|
| 2062 |
-
renderHistory();
|
| 2063 |
-
updatePreviewFromAssistant(assistantMsg);
|
| 2064 |
-
saveState();
|
| 2065 |
-
|
| 2066 |
-
} catch (e) {
|
| 2067 |
-
const idx = chat.messages.indexOf(loadingMsg);
|
| 2068 |
-
if (idx !== -1) chat.messages.splice(idx, 1);
|
| 2069 |
-
|
| 2070 |
-
const errorMsg = { role: 'assistant', content: `Agent error: ${e.message}`, ts: Date.now() };
|
| 2071 |
-
chat.messages.push(errorMsg);
|
| 2072 |
-
renderMessages();
|
| 2073 |
-
toast('Agent encountered an error', 'error');
|
| 2074 |
-
} finally {
|
| 2075 |
-
runtime.isSending = false;
|
| 2076 |
-
}
|
| 2077 |
-
}
|
| 2078 |
-
|
| 2079 |
-
// ----------------------------------------------------------------
|
| 2080 |
-
// Init
|
| 2081 |
-
// ----------------------------------------------------------------
|
| 2082 |
-
function init() {
|
| 2083 |
-
bind();
|
| 2084 |
-
|
| 2085 |
-
// Override the active send handler with agent-aware version
|
| 2086 |
-
if (typeof MINDIAgent !== 'undefined') {
|
| 2087 |
-
activeSend = handleSendWithAgent;
|
| 2088 |
-
}
|
| 2089 |
-
|
| 2090 |
-
renderHistory();
|
| 2091 |
-
renderMessages();
|
| 2092 |
-
|
| 2093 |
-
// Restore current chat title
|
| 2094 |
-
const c = currentChat();
|
| 2095 |
-
if (c) {
|
| 2096 |
-
els.chatTitle.textContent = c.title || 'New chat';
|
| 2097 |
-
const lastAssistant = [...c.messages].reverse().find((m) => m.role === 'assistant' && !m.loading);
|
| 2098 |
-
if (lastAssistant) updatePreviewFromAssistant(lastAssistant);
|
| 2099 |
-
}
|
| 2100 |
-
|
| 2101 |
-
autosizeTextarea();
|
| 2102 |
-
updateSendEnabled();
|
| 2103 |
-
|
| 2104 |
-
// Health check (after a tick so UI paints first)
|
| 2105 |
-
setTimeout(pingHealth, 80);
|
| 2106 |
-
|
| 2107 |
-
// Periodically re-check health (every 60s)
|
| 2108 |
-
setInterval(pingHealth, 60_000);
|
| 2109 |
-
|
| 2110 |
-
console.log('[MINDI] Agent system loaded:', typeof MINDIAgent !== 'undefined' ? '✅' : '❌');
|
| 2111 |
-
}
|
| 2112 |
-
|
| 2113 |
-
if (document.readyState === 'loading') {
|
| 2114 |
-
document.addEventListener('DOMContentLoaded', init, { once: true });
|
| 2115 |
-
} else {
|
| 2116 |
-
init();
|
| 2117 |
-
}
|
| 2118 |
-
})();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import js from '@eslint/js'
|
| 2 |
+
import globals from 'globals'
|
| 3 |
+
import reactHooks from 'eslint-plugin-react-hooks'
|
| 4 |
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
| 5 |
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
| 6 |
+
|
| 7 |
+
export default defineConfig([
|
| 8 |
+
globalIgnores(['dist']),
|
| 9 |
+
{
|
| 10 |
+
files: ['**/*.{js,jsx}'],
|
| 11 |
+
extends: [
|
| 12 |
+
js.configs.recommended,
|
| 13 |
+
reactHooks.configs.flat.recommended,
|
| 14 |
+
reactRefresh.configs.vite,
|
| 15 |
+
],
|
| 16 |
+
languageOptions: {
|
| 17 |
+
globals: globals.browser,
|
| 18 |
+
parserOptions: { ecmaFeatures: { jsx: true } },
|
| 19 |
+
},
|
| 20 |
+
},
|
| 21 |
+
])
|
|
@@ -3,355 +3,14 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
-
<meta name="description" content="MINDI 1.5 Vision-Coder —
|
| 7 |
-
<title>MINDI 1.5 —
|
| 8 |
-
|
| 9 |
-
<!-- Fonts -->
|
| 10 |
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 11 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 12 |
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
| 13 |
-
|
| 14 |
-
<!-- Prism syntax highlighting -->
|
| 15 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" />
|
| 16 |
-
|
| 17 |
-
<link rel="stylesheet" href="styles.css" />
|
| 18 |
</head>
|
| 19 |
<body>
|
| 20 |
-
|
| 21 |
-
<
|
| 22 |
-
<div class="ambient" aria-hidden="true">
|
| 23 |
-
<div class="grid-pattern"></div>
|
| 24 |
-
<div class="blob blob--purple"></div>
|
| 25 |
-
<div class="blob blob--blue"></div>
|
| 26 |
-
</div>
|
| 27 |
-
|
| 28 |
-
<!-- Mobile sidebar scrim -->
|
| 29 |
-
<div class="scrim" id="scrim" aria-hidden="true"></div>
|
| 30 |
-
|
| 31 |
-
<!-- ============ APP SHELL ============ -->
|
| 32 |
-
<div class="app">
|
| 33 |
-
|
| 34 |
-
<!-- ============ LEFT SIDEBAR ============ -->
|
| 35 |
-
<aside class="sidebar" id="sidebar" aria-label="Sidebar">
|
| 36 |
-
<div class="sidebar-head">
|
| 37 |
-
<button class="brand" id="brand" title="Double-click for settings" aria-label="MINDIGENOUS.AI brand — double-click for settings">
|
| 38 |
-
<span class="brand-mark">
|
| 39 |
-
<svg viewBox="0 0 32 32" fill="none" aria-hidden="true">
|
| 40 |
-
<defs>
|
| 41 |
-
<linearGradient id="bgrad" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
|
| 42 |
-
<stop offset="0%" stop-color="#7c3aed" />
|
| 43 |
-
<stop offset="100%" stop-color="#2563eb" />
|
| 44 |
-
</linearGradient>
|
| 45 |
-
</defs>
|
| 46 |
-
<path d="M16 2 L28 9 L28 23 L16 30 L4 23 L4 9 Z" fill="url(#bgrad)" />
|
| 47 |
-
<path d="M11.5 12 L7.5 16 L11.5 20 M20.5 12 L24.5 16 L20.5 20" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
| 48 |
-
<circle cx="16" cy="16" r="1.6" fill="#fff" />
|
| 49 |
-
</svg>
|
| 50 |
-
</span>
|
| 51 |
-
<span class="brand-text">
|
| 52 |
-
<span class="brand-name">MINDIGENOUS<span class="brand-dot">.AI</span></span>
|
| 53 |
-
<span class="brand-version">MINDI 1.5 · Vision-Coder</span>
|
| 54 |
-
</span>
|
| 55 |
-
</button>
|
| 56 |
-
</div>
|
| 57 |
-
|
| 58 |
-
<div class="sidebar-actions">
|
| 59 |
-
<button class="btn btn--new" id="new-chat-btn" title="Start a new chat">
|
| 60 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 61 |
-
<line x1="12" y1="5" x2="12" y2="19"></line>
|
| 62 |
-
<line x1="5" y1="12" x2="19" y2="12"></line>
|
| 63 |
-
</svg>
|
| 64 |
-
New chat
|
| 65 |
-
</button>
|
| 66 |
-
|
| 67 |
-
<div class="search-wrap">
|
| 68 |
-
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 69 |
-
<circle cx="11" cy="11" r="8"></circle>
|
| 70 |
-
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
| 71 |
-
</svg>
|
| 72 |
-
<input id="search" type="search" placeholder="Search conversations…" autocomplete="off" />
|
| 73 |
-
</div>
|
| 74 |
-
</div>
|
| 75 |
-
|
| 76 |
-
<nav class="chat-history" id="chat-history" aria-label="Chat history">
|
| 77 |
-
<div class="history-empty" id="history-empty">
|
| 78 |
-
<p>No chats yet.</p>
|
| 79 |
-
<p class="muted">Start a conversation to see it here.</p>
|
| 80 |
-
</div>
|
| 81 |
-
</nav>
|
| 82 |
-
|
| 83 |
-
<div class="sidebar-foot">
|
| 84 |
-
<div class="status" id="status" title="Model status">
|
| 85 |
-
<span class="status-dot status-dot--gray" id="status-dot"></span>
|
| 86 |
-
<span class="status-text" id="status-text">Connecting…</span>
|
| 87 |
-
</div>
|
| 88 |
-
<a class="hf-link" href="https://huggingface.co/mindigenous-ai" target="_blank" rel="noopener noreferrer" title="MINDI on HuggingFace">
|
| 89 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 90 |
-
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
| 91 |
-
<polyline points="15 3 21 3 21 9"></polyline>
|
| 92 |
-
<line x1="10" y1="14" x2="21" y2="3"></line>
|
| 93 |
-
</svg>
|
| 94 |
-
HuggingFace
|
| 95 |
-
</a>
|
| 96 |
-
</div>
|
| 97 |
-
</aside>
|
| 98 |
-
|
| 99 |
-
<!-- ============ CENTER: CHAT ============ -->
|
| 100 |
-
<main class="chat" id="chat">
|
| 101 |
-
<header class="chat-head">
|
| 102 |
-
<button class="icon-btn icon-btn--menu" id="hamburger" aria-label="Open sidebar">
|
| 103 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 104 |
-
<line x1="3" y1="6" x2="21" y2="6"></line>
|
| 105 |
-
<line x1="3" y1="12" x2="21" y2="12"></line>
|
| 106 |
-
<line x1="3" y1="18" x2="21" y2="18"></line>
|
| 107 |
-
</svg>
|
| 108 |
-
</button>
|
| 109 |
-
<h1 class="chat-title" id="chat-title">New chat</h1>
|
| 110 |
-
<div class="chat-head-actions">
|
| 111 |
-
<button class="icon-btn" id="toggle-preview" aria-label="Toggle preview panel" title="Toggle preview panel">
|
| 112 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 113 |
-
<rect x="3" y="3" width="18" height="18" rx="2"></rect>
|
| 114 |
-
<line x1="15" y1="3" x2="15" y2="21"></line>
|
| 115 |
-
</svg>
|
| 116 |
-
</button>
|
| 117 |
-
</div>
|
| 118 |
-
</header>
|
| 119 |
-
|
| 120 |
-
<!-- Welcome screen -->
|
| 121 |
-
<section class="welcome" id="welcome">
|
| 122 |
-
<div class="welcome-icon" aria-hidden="true">
|
| 123 |
-
<svg viewBox="0 0 120 120" class="welcome-svg">
|
| 124 |
-
<defs>
|
| 125 |
-
<linearGradient id="wgrad" x1="0" y1="0" x2="120" y2="120" gradientUnits="userSpaceOnUse">
|
| 126 |
-
<stop offset="0%" stop-color="#7c3aed" />
|
| 127 |
-
<stop offset="100%" stop-color="#2563eb" />
|
| 128 |
-
</linearGradient>
|
| 129 |
-
<filter id="wglow" x="-30%" y="-30%" width="160%" height="160%">
|
| 130 |
-
<feGaussianBlur stdDeviation="8" />
|
| 131 |
-
</filter>
|
| 132 |
-
</defs>
|
| 133 |
-
<path d="M60 10 L102 33 L102 87 L60 110 L18 87 L18 33 Z" fill="url(#wgrad)" filter="url(#wglow)" opacity=".55" />
|
| 134 |
-
<path d="M60 12 L100 34 L100 86 L60 108 L20 86 L20 34 Z" fill="url(#wgrad)" />
|
| 135 |
-
<path d="M44 50 L30 60 L44 70 M76 50 L90 60 L76 70" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
| 136 |
-
<circle cx="60" cy="60" r="4.5" fill="#fff" />
|
| 137 |
-
<circle cx="60" cy="60" r="9" fill="none" stroke="#fff" stroke-opacity=".4" stroke-width="1.5" />
|
| 138 |
-
</svg>
|
| 139 |
-
</div>
|
| 140 |
-
|
| 141 |
-
<h2 class="welcome-title">Welcome to <span class="grad-text">MINDI 1.5</span></h2>
|
| 142 |
-
<p class="welcome-sub">A custom-trained 8B parameter Vision-Coder. Describe what you want to build, drop in a UI screenshot, and watch it generate production-ready code.</p>
|
| 143 |
-
|
| 144 |
-
<div class="quick-actions">
|
| 145 |
-
<button class="quick-card" data-prompt="Build a Next.js landing page with a responsive hero section, features grid, testimonial carousel, and a clear call-to-action. Use Tailwind CSS.">
|
| 146 |
-
<span class="qc-icon">🚀</span>
|
| 147 |
-
<h3>Landing Page</h3>
|
| 148 |
-
<p>Next.js + responsive hero section</p>
|
| 149 |
-
</button>
|
| 150 |
-
<button class="quick-card" data-prompt="Create a modern dashboard UI with a sidebar nav, top stats cards, a line chart, a recent activity table, and a user profile menu. Use HTML, CSS, and vanilla JS.">
|
| 151 |
-
<span class="qc-icon">📊</span>
|
| 152 |
-
<h3>Dashboard UI</h3>
|
| 153 |
-
<p>Charts, stats & navigation</p>
|
| 154 |
-
</button>
|
| 155 |
-
<button class="quick-card" data-prompt="Write a FastAPI backend for a notes app with JWT authentication, PostgreSQL via SQLAlchemy, and CRUD endpoints. Include Pydantic models and an example .env.">
|
| 156 |
-
<span class="qc-icon">⚡</span>
|
| 157 |
-
<h3>API Backend</h3>
|
| 158 |
-
<p>FastAPI + JWT + PostgreSQL</p>
|
| 159 |
-
</button>
|
| 160 |
-
<button class="quick-card" data-prompt="Find and fix the bugs in this Python function. Explain each issue and provide the corrected code: ```python def divide_list(numbers, divisor): result = [] for n in numbers: result.append(n / divisor) return result ```">
|
| 161 |
-
<span class="qc-icon">🔧</span>
|
| 162 |
-
<h3>Debug Code</h3>
|
| 163 |
-
<p>Find & fix bugs in your code</p>
|
| 164 |
-
</button>
|
| 165 |
-
</div>
|
| 166 |
-
</section>
|
| 167 |
-
|
| 168 |
-
<!-- Messages -->
|
| 169 |
-
<section class="messages" id="messages" aria-live="polite"></section>
|
| 170 |
-
|
| 171 |
-
<!-- Composer (input) -->
|
| 172 |
-
<div class="composer-wrap">
|
| 173 |
-
<div class="composer" id="composer">
|
| 174 |
-
<div class="composer-images" id="composer-images" hidden></div>
|
| 175 |
-
<div class="composer-row">
|
| 176 |
-
<button class="composer-btn" id="attach-btn" title="Attach image" aria-label="Attach image">
|
| 177 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 178 |
-
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
|
| 179 |
-
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
| 180 |
-
<polyline points="21 15 16 10 5 21"></polyline>
|
| 181 |
-
</svg>
|
| 182 |
-
</button>
|
| 183 |
-
<textarea id="prompt-input" rows="1" placeholder="Describe the code you want to generate…" autocomplete="off" autocorrect="off" spellcheck="false"></textarea>
|
| 184 |
-
<button class="composer-send" id="send-btn" disabled title="Send (Enter)" aria-label="Send message">
|
| 185 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 186 |
-
<line x1="22" y1="2" x2="11" y2="13"></line>
|
| 187 |
-
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
|
| 188 |
-
</svg>
|
| 189 |
-
</button>
|
| 190 |
-
</div>
|
| 191 |
-
</div>
|
| 192 |
-
<p class="composer-foot">
|
| 193 |
-
<span class="grad-text">MINDI 1.5 Vision-Coder</span> · 8B params · Trained on code & UI data
|
| 194 |
-
</p>
|
| 195 |
-
</div>
|
| 196 |
-
|
| 197 |
-
<input type="file" id="file-input" accept="image/*" hidden />
|
| 198 |
-
</main>
|
| 199 |
-
|
| 200 |
-
<!-- ============ RIGHT: PREVIEW PANEL ============ -->
|
| 201 |
-
<aside class="preview" id="preview" aria-label="Code preview panel">
|
| 202 |
-
<div class="preview-head">
|
| 203 |
-
<div class="tabs" role="tablist">
|
| 204 |
-
<button class="tab is-active" data-tab="code" role="tab" aria-selected="true">Code</button>
|
| 205 |
-
<button class="tab" data-tab="live" role="tab" aria-selected="false">Preview</button>
|
| 206 |
-
<button class="tab" data-tab="agent" role="tab" aria-selected="false">Agent</button>
|
| 207 |
-
<button class="tab" data-tab="sections" role="tab" aria-selected="false">Sections</button>
|
| 208 |
-
</div>
|
| 209 |
-
<div class="preview-actions">
|
| 210 |
-
<button class="icon-btn" id="copy-code" title="Copy code" aria-label="Copy code">
|
| 211 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 212 |
-
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
| 213 |
-
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
| 214 |
-
</svg>
|
| 215 |
-
</button>
|
| 216 |
-
<button class="icon-btn" id="download-code" title="Download code" aria-label="Download code">
|
| 217 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 218 |
-
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
| 219 |
-
<polyline points="7 10 12 15 17 10"></polyline>
|
| 220 |
-
<line x1="12" y1="15" x2="12" y2="3"></line>
|
| 221 |
-
</svg>
|
| 222 |
-
</button>
|
| 223 |
-
</div>
|
| 224 |
-
</div>
|
| 225 |
-
|
| 226 |
-
<!-- Code tab -->
|
| 227 |
-
<div class="preview-pane is-active" data-pane="code">
|
| 228 |
-
<div class="preview-empty" id="empty-code">
|
| 229 |
-
<div class="preview-empty-icon">{ }</div>
|
| 230 |
-
<p>Generated code will appear here</p>
|
| 231 |
-
<p class="muted">Send a prompt to see the last code block from the response.</p>
|
| 232 |
-
</div>
|
| 233 |
-
<pre class="code-out" id="code-out" hidden><code class="language-javascript" id="code-out-inner"></code></pre>
|
| 234 |
-
</div>
|
| 235 |
-
|
| 236 |
-
<!-- Live preview tab -->
|
| 237 |
-
<div class="preview-pane" data-pane="live">
|
| 238 |
-
<div class="preview-empty" id="empty-live">
|
| 239 |
-
<div class="preview-empty-icon">⚡</div>
|
| 240 |
-
<p>Live HTML preview</p>
|
| 241 |
-
<p class="muted">When the response contains an HTML code block, it'll render here in a sandboxed iframe.</p>
|
| 242 |
-
</div>
|
| 243 |
-
<iframe id="live-frame" sandbox="allow-scripts allow-same-origin" title="Live HTML preview" hidden></iframe>
|
| 244 |
-
</div>
|
| 245 |
-
|
| 246 |
-
<!-- Agent tab -->
|
| 247 |
-
<div class="preview-pane" data-pane="agent">
|
| 248 |
-
<div class="preview-empty" id="empty-agent">
|
| 249 |
-
<div class="preview-empty-icon">🤖</div>
|
| 250 |
-
<p>Agent Workspace</p>
|
| 251 |
-
<p class="muted">MINDI Agent will plan, generate, execute, verify, and fix code automatically. Steps appear here in real-time.</p>
|
| 252 |
-
</div>
|
| 253 |
-
<div class="agent-log" id="agent-log" hidden></div>
|
| 254 |
-
<div class="agent-sandbox" id="agent-sandbox" hidden></div>
|
| 255 |
-
<div class="agent-console" id="agent-console" hidden>
|
| 256 |
-
<div class="agent-console-head">Console Output</div>
|
| 257 |
-
<pre class="agent-console-body" id="agent-console-body"></pre>
|
| 258 |
-
</div>
|
| 259 |
-
</div>
|
| 260 |
-
|
| 261 |
-
<!-- Sections tab -->
|
| 262 |
-
<div class="preview-pane" data-pane="sections">
|
| 263 |
-
<div class="preview-empty" id="empty-sections">
|
| 264 |
-
<div class="preview-empty-icon">⌘</div>
|
| 265 |
-
<p>Parsed model sections</p>
|
| 266 |
-
<p class="muted">The model emits structured tokens — thinking, code, critique, fix, error, suggest, file. They'll show up here as colored cards.</p>
|
| 267 |
-
</div>
|
| 268 |
-
<div class="sections" id="sections" hidden></div>
|
| 269 |
-
</div>
|
| 270 |
-
</aside>
|
| 271 |
-
</div>
|
| 272 |
-
|
| 273 |
-
<!-- ============ SETTINGS MODAL ============ -->
|
| 274 |
-
<div class="modal" id="settings-modal" hidden role="dialog" aria-modal="true" aria-labelledby="settings-title">
|
| 275 |
-
<div class="modal-backdrop" data-close></div>
|
| 276 |
-
<div class="modal-card" role="document">
|
| 277 |
-
<div class="modal-head">
|
| 278 |
-
<h3 id="settings-title">Settings</h3>
|
| 279 |
-
<button class="icon-btn" data-close aria-label="Close">
|
| 280 |
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
| 281 |
-
<line x1="18" y1="6" x2="6" y2="18"></line>
|
| 282 |
-
<line x1="6" y1="6" x2="18" y2="18"></line>
|
| 283 |
-
</svg>
|
| 284 |
-
</button>
|
| 285 |
-
</div>
|
| 286 |
-
|
| 287 |
-
<div class="modal-body">
|
| 288 |
-
<label class="field">
|
| 289 |
-
<span class="field-label">API URL</span>
|
| 290 |
-
<input id="settings-url" type="url" placeholder="https://mindigenous-mindi-chat.hf.space" autocomplete="off" />
|
| 291 |
-
<span class="field-hint">Base URL for the MINDI API (HF Space or Modal). Endpoints are appended automatically.</span>
|
| 292 |
-
</label>
|
| 293 |
-
|
| 294 |
-
<label class="field">
|
| 295 |
-
<span class="field-label">HuggingFace token <em class="field-value" id="hf-token-status">none</em></span>
|
| 296 |
-
<input id="settings-hf-token" type="password" placeholder="hf_xxxxxxxxxxxxxxxxxxxx" autocomplete="off" spellcheck="false" />
|
| 297 |
-
<span class="field-hint">Paste a PRO HF token to bypass anonymous ZeroGPU quota. Stored only in this browser. <a href="https://huggingface.co/settings/tokens" target="_blank" rel="noopener">Get a token →</a></span>
|
| 298 |
-
</label>
|
| 299 |
-
|
| 300 |
-
<label class="field field-toggle">
|
| 301 |
-
<span class="field-toggle-row">
|
| 302 |
-
<span class="field-label">Vision input</span>
|
| 303 |
-
<span class="toggle">
|
| 304 |
-
<input id="settings-vision" type="checkbox" />
|
| 305 |
-
<span class="toggle-slider"></span>
|
| 306 |
-
</span>
|
| 307 |
-
</span>
|
| 308 |
-
<span class="field-hint">Send attached images to MINDI's CLIP encoder. <strong>Off by default</strong> — the current vision-language fusion is an early build and produces low-quality answers on images. Leave off until the next vision retraining ships. Attaching an image still records it in the chat.</span>
|
| 309 |
-
</label>
|
| 310 |
-
|
| 311 |
-
<label class="field">
|
| 312 |
-
<span class="field-label">Temperature <em class="field-value" id="temp-val">0.7</em></span>
|
| 313 |
-
<input id="settings-temp" type="range" min="0" max="2" step="0.05" value="0.7" />
|
| 314 |
-
<span class="field-hint">Lower = more focused. Higher = more creative.</span>
|
| 315 |
-
</label>
|
| 316 |
-
|
| 317 |
-
<label class="field">
|
| 318 |
-
<span class="field-label">Max tokens <em class="field-value" id="tokens-val">2048</em></span>
|
| 319 |
-
<input id="settings-tokens" type="range" min="128" max="4096" step="128" value="2048" />
|
| 320 |
-
<span class="field-hint">Maximum length of the generated response.</span>
|
| 321 |
-
</label>
|
| 322 |
-
</div>
|
| 323 |
-
|
| 324 |
-
<div class="modal-foot">
|
| 325 |
-
<button class="btn btn--ghost" data-close>Cancel</button>
|
| 326 |
-
<button class="btn btn--primary" id="save-settings">Save settings</button>
|
| 327 |
-
</div>
|
| 328 |
-
</div>
|
| 329 |
-
</div>
|
| 330 |
-
|
| 331 |
-
<!-- ============ TOAST CONTAINER ============ -->
|
| 332 |
-
<div class="toasts" id="toasts" aria-live="polite" aria-atomic="true"></div>
|
| 333 |
-
|
| 334 |
-
<!-- Prism.js core + languages -->
|
| 335 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
| 336 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script>
|
| 337 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script>
|
| 338 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-jsx.min.js"></script>
|
| 339 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-tsx.min.js"></script>
|
| 340 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
|
| 341 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-css.min.js"></script>
|
| 342 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markup.min.js"></script>
|
| 343 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
|
| 344 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
|
| 345 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-sql.min.js"></script>
|
| 346 |
-
|
| 347 |
-
<!-- StackBlitz SDK — drives the "Run in StackBlitz" launcher.
|
| 348 |
-
The form-POST /run endpoint is unreliable (gets silently rejected
|
| 349 |
-
and falls back to the default Next.js starter); the SDK's hidden
|
| 350 |
-
iframe handshake is the maintained, robust path. -->
|
| 351 |
-
<script src="https://unpkg.com/@stackblitz/sdk@1.11.0/bundles/sdk.umd.js"></script>
|
| 352 |
-
|
| 353 |
-
<script src="sandbox.js"></script>
|
| 354 |
-
<script src="agent.js"></script>
|
| 355 |
-
<script src="app.js"></script>
|
| 356 |
</body>
|
| 357 |
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<meta name="description" content="MINDI 1.5 Vision-Coder — AI Website Builder. Generate production-ready websites from text prompts." />
|
| 7 |
+
<title>MINDI 1.5 — AI Website Builder</title>
|
|
|
|
|
|
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
</head>
|
| 12 |
<body>
|
| 13 |
+
<div id="root"></div>
|
| 14 |
+
<script type="module" src="/src/main.jsx"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
</body>
|
| 16 |
</html>
|
|
@@ -0,0 +1,2448 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"version": "0.0.0",
|
| 4 |
+
"lockfileVersion": 3,
|
| 5 |
+
"requires": true,
|
| 6 |
+
"packages": {
|
| 7 |
+
"": {
|
| 8 |
+
"name": "frontend",
|
| 9 |
+
"version": "0.0.0",
|
| 10 |
+
"dependencies": {
|
| 11 |
+
"lucide-react": "^1.14.0",
|
| 12 |
+
"prismjs": "^1.30.0",
|
| 13 |
+
"react": "^19.2.5",
|
| 14 |
+
"react-dom": "^19.2.5"
|
| 15 |
+
},
|
| 16 |
+
"devDependencies": {
|
| 17 |
+
"@eslint/js": "^10.0.1",
|
| 18 |
+
"@types/react": "^19.2.14",
|
| 19 |
+
"@types/react-dom": "^19.2.3",
|
| 20 |
+
"@vitejs/plugin-react": "^6.0.1",
|
| 21 |
+
"eslint": "^10.2.1",
|
| 22 |
+
"eslint-plugin-react-hooks": "^7.1.1",
|
| 23 |
+
"eslint-plugin-react-refresh": "^0.5.2",
|
| 24 |
+
"globals": "^17.5.0",
|
| 25 |
+
"vite": "^8.0.10"
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
"node_modules/@babel/code-frame": {
|
| 29 |
+
"version": "7.29.0",
|
| 30 |
+
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
|
| 31 |
+
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
|
| 32 |
+
"dev": true,
|
| 33 |
+
"license": "MIT",
|
| 34 |
+
"dependencies": {
|
| 35 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 36 |
+
"js-tokens": "^4.0.0",
|
| 37 |
+
"picocolors": "^1.1.1"
|
| 38 |
+
},
|
| 39 |
+
"engines": {
|
| 40 |
+
"node": ">=6.9.0"
|
| 41 |
+
}
|
| 42 |
+
},
|
| 43 |
+
"node_modules/@babel/compat-data": {
|
| 44 |
+
"version": "7.29.3",
|
| 45 |
+
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz",
|
| 46 |
+
"integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==",
|
| 47 |
+
"dev": true,
|
| 48 |
+
"license": "MIT",
|
| 49 |
+
"engines": {
|
| 50 |
+
"node": ">=6.9.0"
|
| 51 |
+
}
|
| 52 |
+
},
|
| 53 |
+
"node_modules/@babel/core": {
|
| 54 |
+
"version": "7.29.0",
|
| 55 |
+
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
| 56 |
+
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
| 57 |
+
"dev": true,
|
| 58 |
+
"license": "MIT",
|
| 59 |
+
"dependencies": {
|
| 60 |
+
"@babel/code-frame": "^7.29.0",
|
| 61 |
+
"@babel/generator": "^7.29.0",
|
| 62 |
+
"@babel/helper-compilation-targets": "^7.28.6",
|
| 63 |
+
"@babel/helper-module-transforms": "^7.28.6",
|
| 64 |
+
"@babel/helpers": "^7.28.6",
|
| 65 |
+
"@babel/parser": "^7.29.0",
|
| 66 |
+
"@babel/template": "^7.28.6",
|
| 67 |
+
"@babel/traverse": "^7.29.0",
|
| 68 |
+
"@babel/types": "^7.29.0",
|
| 69 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 70 |
+
"convert-source-map": "^2.0.0",
|
| 71 |
+
"debug": "^4.1.0",
|
| 72 |
+
"gensync": "^1.0.0-beta.2",
|
| 73 |
+
"json5": "^2.2.3",
|
| 74 |
+
"semver": "^6.3.1"
|
| 75 |
+
},
|
| 76 |
+
"engines": {
|
| 77 |
+
"node": ">=6.9.0"
|
| 78 |
+
},
|
| 79 |
+
"funding": {
|
| 80 |
+
"type": "opencollective",
|
| 81 |
+
"url": "https://opencollective.com/babel"
|
| 82 |
+
}
|
| 83 |
+
},
|
| 84 |
+
"node_modules/@babel/generator": {
|
| 85 |
+
"version": "7.29.1",
|
| 86 |
+
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
|
| 87 |
+
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
|
| 88 |
+
"dev": true,
|
| 89 |
+
"license": "MIT",
|
| 90 |
+
"dependencies": {
|
| 91 |
+
"@babel/parser": "^7.29.0",
|
| 92 |
+
"@babel/types": "^7.29.0",
|
| 93 |
+
"@jridgewell/gen-mapping": "^0.3.12",
|
| 94 |
+
"@jridgewell/trace-mapping": "^0.3.28",
|
| 95 |
+
"jsesc": "^3.0.2"
|
| 96 |
+
},
|
| 97 |
+
"engines": {
|
| 98 |
+
"node": ">=6.9.0"
|
| 99 |
+
}
|
| 100 |
+
},
|
| 101 |
+
"node_modules/@babel/helper-compilation-targets": {
|
| 102 |
+
"version": "7.28.6",
|
| 103 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
|
| 104 |
+
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
|
| 105 |
+
"dev": true,
|
| 106 |
+
"license": "MIT",
|
| 107 |
+
"dependencies": {
|
| 108 |
+
"@babel/compat-data": "^7.28.6",
|
| 109 |
+
"@babel/helper-validator-option": "^7.27.1",
|
| 110 |
+
"browserslist": "^4.24.0",
|
| 111 |
+
"lru-cache": "^5.1.1",
|
| 112 |
+
"semver": "^6.3.1"
|
| 113 |
+
},
|
| 114 |
+
"engines": {
|
| 115 |
+
"node": ">=6.9.0"
|
| 116 |
+
}
|
| 117 |
+
},
|
| 118 |
+
"node_modules/@babel/helper-globals": {
|
| 119 |
+
"version": "7.28.0",
|
| 120 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
|
| 121 |
+
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
|
| 122 |
+
"dev": true,
|
| 123 |
+
"license": "MIT",
|
| 124 |
+
"engines": {
|
| 125 |
+
"node": ">=6.9.0"
|
| 126 |
+
}
|
| 127 |
+
},
|
| 128 |
+
"node_modules/@babel/helper-module-imports": {
|
| 129 |
+
"version": "7.28.6",
|
| 130 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
|
| 131 |
+
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
|
| 132 |
+
"dev": true,
|
| 133 |
+
"license": "MIT",
|
| 134 |
+
"dependencies": {
|
| 135 |
+
"@babel/traverse": "^7.28.6",
|
| 136 |
+
"@babel/types": "^7.28.6"
|
| 137 |
+
},
|
| 138 |
+
"engines": {
|
| 139 |
+
"node": ">=6.9.0"
|
| 140 |
+
}
|
| 141 |
+
},
|
| 142 |
+
"node_modules/@babel/helper-module-transforms": {
|
| 143 |
+
"version": "7.28.6",
|
| 144 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
|
| 145 |
+
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
|
| 146 |
+
"dev": true,
|
| 147 |
+
"license": "MIT",
|
| 148 |
+
"dependencies": {
|
| 149 |
+
"@babel/helper-module-imports": "^7.28.6",
|
| 150 |
+
"@babel/helper-validator-identifier": "^7.28.5",
|
| 151 |
+
"@babel/traverse": "^7.28.6"
|
| 152 |
+
},
|
| 153 |
+
"engines": {
|
| 154 |
+
"node": ">=6.9.0"
|
| 155 |
+
},
|
| 156 |
+
"peerDependencies": {
|
| 157 |
+
"@babel/core": "^7.0.0"
|
| 158 |
+
}
|
| 159 |
+
},
|
| 160 |
+
"node_modules/@babel/helper-string-parser": {
|
| 161 |
+
"version": "7.27.1",
|
| 162 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
|
| 163 |
+
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
|
| 164 |
+
"dev": true,
|
| 165 |
+
"license": "MIT",
|
| 166 |
+
"engines": {
|
| 167 |
+
"node": ">=6.9.0"
|
| 168 |
+
}
|
| 169 |
+
},
|
| 170 |
+
"node_modules/@babel/helper-validator-identifier": {
|
| 171 |
+
"version": "7.28.5",
|
| 172 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
| 173 |
+
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
| 174 |
+
"dev": true,
|
| 175 |
+
"license": "MIT",
|
| 176 |
+
"engines": {
|
| 177 |
+
"node": ">=6.9.0"
|
| 178 |
+
}
|
| 179 |
+
},
|
| 180 |
+
"node_modules/@babel/helper-validator-option": {
|
| 181 |
+
"version": "7.27.1",
|
| 182 |
+
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
|
| 183 |
+
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
|
| 184 |
+
"dev": true,
|
| 185 |
+
"license": "MIT",
|
| 186 |
+
"engines": {
|
| 187 |
+
"node": ">=6.9.0"
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
"node_modules/@babel/helpers": {
|
| 191 |
+
"version": "7.29.2",
|
| 192 |
+
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
|
| 193 |
+
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
|
| 194 |
+
"dev": true,
|
| 195 |
+
"license": "MIT",
|
| 196 |
+
"dependencies": {
|
| 197 |
+
"@babel/template": "^7.28.6",
|
| 198 |
+
"@babel/types": "^7.29.0"
|
| 199 |
+
},
|
| 200 |
+
"engines": {
|
| 201 |
+
"node": ">=6.9.0"
|
| 202 |
+
}
|
| 203 |
+
},
|
| 204 |
+
"node_modules/@babel/parser": {
|
| 205 |
+
"version": "7.29.3",
|
| 206 |
+
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz",
|
| 207 |
+
"integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==",
|
| 208 |
+
"dev": true,
|
| 209 |
+
"license": "MIT",
|
| 210 |
+
"dependencies": {
|
| 211 |
+
"@babel/types": "^7.29.0"
|
| 212 |
+
},
|
| 213 |
+
"bin": {
|
| 214 |
+
"parser": "bin/babel-parser.js"
|
| 215 |
+
},
|
| 216 |
+
"engines": {
|
| 217 |
+
"node": ">=6.0.0"
|
| 218 |
+
}
|
| 219 |
+
},
|
| 220 |
+
"node_modules/@babel/template": {
|
| 221 |
+
"version": "7.28.6",
|
| 222 |
+
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
| 223 |
+
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
|
| 224 |
+
"dev": true,
|
| 225 |
+
"license": "MIT",
|
| 226 |
+
"dependencies": {
|
| 227 |
+
"@babel/code-frame": "^7.28.6",
|
| 228 |
+
"@babel/parser": "^7.28.6",
|
| 229 |
+
"@babel/types": "^7.28.6"
|
| 230 |
+
},
|
| 231 |
+
"engines": {
|
| 232 |
+
"node": ">=6.9.0"
|
| 233 |
+
}
|
| 234 |
+
},
|
| 235 |
+
"node_modules/@babel/traverse": {
|
| 236 |
+
"version": "7.29.0",
|
| 237 |
+
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
|
| 238 |
+
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
|
| 239 |
+
"dev": true,
|
| 240 |
+
"license": "MIT",
|
| 241 |
+
"dependencies": {
|
| 242 |
+
"@babel/code-frame": "^7.29.0",
|
| 243 |
+
"@babel/generator": "^7.29.0",
|
| 244 |
+
"@babel/helper-globals": "^7.28.0",
|
| 245 |
+
"@babel/parser": "^7.29.0",
|
| 246 |
+
"@babel/template": "^7.28.6",
|
| 247 |
+
"@babel/types": "^7.29.0",
|
| 248 |
+
"debug": "^4.3.1"
|
| 249 |
+
},
|
| 250 |
+
"engines": {
|
| 251 |
+
"node": ">=6.9.0"
|
| 252 |
+
}
|
| 253 |
+
},
|
| 254 |
+
"node_modules/@babel/types": {
|
| 255 |
+
"version": "7.29.0",
|
| 256 |
+
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
|
| 257 |
+
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
|
| 258 |
+
"dev": true,
|
| 259 |
+
"license": "MIT",
|
| 260 |
+
"dependencies": {
|
| 261 |
+
"@babel/helper-string-parser": "^7.27.1",
|
| 262 |
+
"@babel/helper-validator-identifier": "^7.28.5"
|
| 263 |
+
},
|
| 264 |
+
"engines": {
|
| 265 |
+
"node": ">=6.9.0"
|
| 266 |
+
}
|
| 267 |
+
},
|
| 268 |
+
"node_modules/@emnapi/core": {
|
| 269 |
+
"version": "1.10.0",
|
| 270 |
+
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
| 271 |
+
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
| 272 |
+
"dev": true,
|
| 273 |
+
"license": "MIT",
|
| 274 |
+
"optional": true,
|
| 275 |
+
"dependencies": {
|
| 276 |
+
"@emnapi/wasi-threads": "1.2.1",
|
| 277 |
+
"tslib": "^2.4.0"
|
| 278 |
+
}
|
| 279 |
+
},
|
| 280 |
+
"node_modules/@emnapi/runtime": {
|
| 281 |
+
"version": "1.10.0",
|
| 282 |
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
| 283 |
+
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
| 284 |
+
"dev": true,
|
| 285 |
+
"license": "MIT",
|
| 286 |
+
"optional": true,
|
| 287 |
+
"dependencies": {
|
| 288 |
+
"tslib": "^2.4.0"
|
| 289 |
+
}
|
| 290 |
+
},
|
| 291 |
+
"node_modules/@emnapi/wasi-threads": {
|
| 292 |
+
"version": "1.2.1",
|
| 293 |
+
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
| 294 |
+
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
| 295 |
+
"dev": true,
|
| 296 |
+
"license": "MIT",
|
| 297 |
+
"optional": true,
|
| 298 |
+
"dependencies": {
|
| 299 |
+
"tslib": "^2.4.0"
|
| 300 |
+
}
|
| 301 |
+
},
|
| 302 |
+
"node_modules/@eslint-community/eslint-utils": {
|
| 303 |
+
"version": "4.9.1",
|
| 304 |
+
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
|
| 305 |
+
"integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
|
| 306 |
+
"dev": true,
|
| 307 |
+
"license": "MIT",
|
| 308 |
+
"dependencies": {
|
| 309 |
+
"eslint-visitor-keys": "^3.4.3"
|
| 310 |
+
},
|
| 311 |
+
"engines": {
|
| 312 |
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
| 313 |
+
},
|
| 314 |
+
"funding": {
|
| 315 |
+
"url": "https://opencollective.com/eslint"
|
| 316 |
+
},
|
| 317 |
+
"peerDependencies": {
|
| 318 |
+
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
|
| 319 |
+
}
|
| 320 |
+
},
|
| 321 |
+
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
|
| 322 |
+
"version": "3.4.3",
|
| 323 |
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
|
| 324 |
+
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
|
| 325 |
+
"dev": true,
|
| 326 |
+
"license": "Apache-2.0",
|
| 327 |
+
"engines": {
|
| 328 |
+
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
| 329 |
+
},
|
| 330 |
+
"funding": {
|
| 331 |
+
"url": "https://opencollective.com/eslint"
|
| 332 |
+
}
|
| 333 |
+
},
|
| 334 |
+
"node_modules/@eslint-community/regexpp": {
|
| 335 |
+
"version": "4.12.2",
|
| 336 |
+
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
|
| 337 |
+
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
|
| 338 |
+
"dev": true,
|
| 339 |
+
"license": "MIT",
|
| 340 |
+
"engines": {
|
| 341 |
+
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
| 342 |
+
}
|
| 343 |
+
},
|
| 344 |
+
"node_modules/@eslint/config-array": {
|
| 345 |
+
"version": "0.23.5",
|
| 346 |
+
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
|
| 347 |
+
"integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
|
| 348 |
+
"dev": true,
|
| 349 |
+
"license": "Apache-2.0",
|
| 350 |
+
"dependencies": {
|
| 351 |
+
"@eslint/object-schema": "^3.0.5",
|
| 352 |
+
"debug": "^4.3.1",
|
| 353 |
+
"minimatch": "^10.2.4"
|
| 354 |
+
},
|
| 355 |
+
"engines": {
|
| 356 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 357 |
+
}
|
| 358 |
+
},
|
| 359 |
+
"node_modules/@eslint/config-helpers": {
|
| 360 |
+
"version": "0.5.5",
|
| 361 |
+
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz",
|
| 362 |
+
"integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==",
|
| 363 |
+
"dev": true,
|
| 364 |
+
"license": "Apache-2.0",
|
| 365 |
+
"dependencies": {
|
| 366 |
+
"@eslint/core": "^1.2.1"
|
| 367 |
+
},
|
| 368 |
+
"engines": {
|
| 369 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 370 |
+
}
|
| 371 |
+
},
|
| 372 |
+
"node_modules/@eslint/core": {
|
| 373 |
+
"version": "1.2.1",
|
| 374 |
+
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
|
| 375 |
+
"integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
|
| 376 |
+
"dev": true,
|
| 377 |
+
"license": "Apache-2.0",
|
| 378 |
+
"dependencies": {
|
| 379 |
+
"@types/json-schema": "^7.0.15"
|
| 380 |
+
},
|
| 381 |
+
"engines": {
|
| 382 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 383 |
+
}
|
| 384 |
+
},
|
| 385 |
+
"node_modules/@eslint/js": {
|
| 386 |
+
"version": "10.0.1",
|
| 387 |
+
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
|
| 388 |
+
"integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
|
| 389 |
+
"dev": true,
|
| 390 |
+
"license": "MIT",
|
| 391 |
+
"engines": {
|
| 392 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 393 |
+
},
|
| 394 |
+
"funding": {
|
| 395 |
+
"url": "https://eslint.org/donate"
|
| 396 |
+
},
|
| 397 |
+
"peerDependencies": {
|
| 398 |
+
"eslint": "^10.0.0"
|
| 399 |
+
},
|
| 400 |
+
"peerDependenciesMeta": {
|
| 401 |
+
"eslint": {
|
| 402 |
+
"optional": true
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
},
|
| 406 |
+
"node_modules/@eslint/object-schema": {
|
| 407 |
+
"version": "3.0.5",
|
| 408 |
+
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
|
| 409 |
+
"integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
|
| 410 |
+
"dev": true,
|
| 411 |
+
"license": "Apache-2.0",
|
| 412 |
+
"engines": {
|
| 413 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 414 |
+
}
|
| 415 |
+
},
|
| 416 |
+
"node_modules/@eslint/plugin-kit": {
|
| 417 |
+
"version": "0.7.1",
|
| 418 |
+
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz",
|
| 419 |
+
"integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==",
|
| 420 |
+
"dev": true,
|
| 421 |
+
"license": "Apache-2.0",
|
| 422 |
+
"dependencies": {
|
| 423 |
+
"@eslint/core": "^1.2.1",
|
| 424 |
+
"levn": "^0.4.1"
|
| 425 |
+
},
|
| 426 |
+
"engines": {
|
| 427 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 428 |
+
}
|
| 429 |
+
},
|
| 430 |
+
"node_modules/@humanfs/core": {
|
| 431 |
+
"version": "0.19.2",
|
| 432 |
+
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
| 433 |
+
"integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
|
| 434 |
+
"dev": true,
|
| 435 |
+
"license": "Apache-2.0",
|
| 436 |
+
"dependencies": {
|
| 437 |
+
"@humanfs/types": "^0.15.0"
|
| 438 |
+
},
|
| 439 |
+
"engines": {
|
| 440 |
+
"node": ">=18.18.0"
|
| 441 |
+
}
|
| 442 |
+
},
|
| 443 |
+
"node_modules/@humanfs/node": {
|
| 444 |
+
"version": "0.16.8",
|
| 445 |
+
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
|
| 446 |
+
"integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
|
| 447 |
+
"dev": true,
|
| 448 |
+
"license": "Apache-2.0",
|
| 449 |
+
"dependencies": {
|
| 450 |
+
"@humanfs/core": "^0.19.2",
|
| 451 |
+
"@humanfs/types": "^0.15.0",
|
| 452 |
+
"@humanwhocodes/retry": "^0.4.0"
|
| 453 |
+
},
|
| 454 |
+
"engines": {
|
| 455 |
+
"node": ">=18.18.0"
|
| 456 |
+
}
|
| 457 |
+
},
|
| 458 |
+
"node_modules/@humanfs/types": {
|
| 459 |
+
"version": "0.15.0",
|
| 460 |
+
"resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
|
| 461 |
+
"integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
|
| 462 |
+
"dev": true,
|
| 463 |
+
"license": "Apache-2.0",
|
| 464 |
+
"engines": {
|
| 465 |
+
"node": ">=18.18.0"
|
| 466 |
+
}
|
| 467 |
+
},
|
| 468 |
+
"node_modules/@humanwhocodes/module-importer": {
|
| 469 |
+
"version": "1.0.1",
|
| 470 |
+
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
| 471 |
+
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
|
| 472 |
+
"dev": true,
|
| 473 |
+
"license": "Apache-2.0",
|
| 474 |
+
"engines": {
|
| 475 |
+
"node": ">=12.22"
|
| 476 |
+
},
|
| 477 |
+
"funding": {
|
| 478 |
+
"type": "github",
|
| 479 |
+
"url": "https://github.com/sponsors/nzakas"
|
| 480 |
+
}
|
| 481 |
+
},
|
| 482 |
+
"node_modules/@humanwhocodes/retry": {
|
| 483 |
+
"version": "0.4.3",
|
| 484 |
+
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
|
| 485 |
+
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
|
| 486 |
+
"dev": true,
|
| 487 |
+
"license": "Apache-2.0",
|
| 488 |
+
"engines": {
|
| 489 |
+
"node": ">=18.18"
|
| 490 |
+
},
|
| 491 |
+
"funding": {
|
| 492 |
+
"type": "github",
|
| 493 |
+
"url": "https://github.com/sponsors/nzakas"
|
| 494 |
+
}
|
| 495 |
+
},
|
| 496 |
+
"node_modules/@jridgewell/gen-mapping": {
|
| 497 |
+
"version": "0.3.13",
|
| 498 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 499 |
+
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 500 |
+
"dev": true,
|
| 501 |
+
"license": "MIT",
|
| 502 |
+
"dependencies": {
|
| 503 |
+
"@jridgewell/sourcemap-codec": "^1.5.0",
|
| 504 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 505 |
+
}
|
| 506 |
+
},
|
| 507 |
+
"node_modules/@jridgewell/remapping": {
|
| 508 |
+
"version": "2.3.5",
|
| 509 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 510 |
+
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
| 511 |
+
"dev": true,
|
| 512 |
+
"license": "MIT",
|
| 513 |
+
"dependencies": {
|
| 514 |
+
"@jridgewell/gen-mapping": "^0.3.5",
|
| 515 |
+
"@jridgewell/trace-mapping": "^0.3.24"
|
| 516 |
+
}
|
| 517 |
+
},
|
| 518 |
+
"node_modules/@jridgewell/resolve-uri": {
|
| 519 |
+
"version": "3.1.2",
|
| 520 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 521 |
+
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 522 |
+
"dev": true,
|
| 523 |
+
"license": "MIT",
|
| 524 |
+
"engines": {
|
| 525 |
+
"node": ">=6.0.0"
|
| 526 |
+
}
|
| 527 |
+
},
|
| 528 |
+
"node_modules/@jridgewell/sourcemap-codec": {
|
| 529 |
+
"version": "1.5.5",
|
| 530 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 531 |
+
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 532 |
+
"dev": true,
|
| 533 |
+
"license": "MIT"
|
| 534 |
+
},
|
| 535 |
+
"node_modules/@jridgewell/trace-mapping": {
|
| 536 |
+
"version": "0.3.31",
|
| 537 |
+
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 538 |
+
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 539 |
+
"dev": true,
|
| 540 |
+
"license": "MIT",
|
| 541 |
+
"dependencies": {
|
| 542 |
+
"@jridgewell/resolve-uri": "^3.1.0",
|
| 543 |
+
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 544 |
+
}
|
| 545 |
+
},
|
| 546 |
+
"node_modules/@napi-rs/wasm-runtime": {
|
| 547 |
+
"version": "1.1.4",
|
| 548 |
+
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
| 549 |
+
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
| 550 |
+
"dev": true,
|
| 551 |
+
"license": "MIT",
|
| 552 |
+
"optional": true,
|
| 553 |
+
"dependencies": {
|
| 554 |
+
"@tybys/wasm-util": "^0.10.1"
|
| 555 |
+
},
|
| 556 |
+
"funding": {
|
| 557 |
+
"type": "github",
|
| 558 |
+
"url": "https://github.com/sponsors/Brooooooklyn"
|
| 559 |
+
},
|
| 560 |
+
"peerDependencies": {
|
| 561 |
+
"@emnapi/core": "^1.7.1",
|
| 562 |
+
"@emnapi/runtime": "^1.7.1"
|
| 563 |
+
}
|
| 564 |
+
},
|
| 565 |
+
"node_modules/@oxc-project/types": {
|
| 566 |
+
"version": "0.127.0",
|
| 567 |
+
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
|
| 568 |
+
"integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
|
| 569 |
+
"dev": true,
|
| 570 |
+
"license": "MIT",
|
| 571 |
+
"funding": {
|
| 572 |
+
"url": "https://github.com/sponsors/Boshen"
|
| 573 |
+
}
|
| 574 |
+
},
|
| 575 |
+
"node_modules/@rolldown/binding-android-arm64": {
|
| 576 |
+
"version": "1.0.0-rc.17",
|
| 577 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
|
| 578 |
+
"integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
|
| 579 |
+
"cpu": [
|
| 580 |
+
"arm64"
|
| 581 |
+
],
|
| 582 |
+
"dev": true,
|
| 583 |
+
"license": "MIT",
|
| 584 |
+
"optional": true,
|
| 585 |
+
"os": [
|
| 586 |
+
"android"
|
| 587 |
+
],
|
| 588 |
+
"engines": {
|
| 589 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 590 |
+
}
|
| 591 |
+
},
|
| 592 |
+
"node_modules/@rolldown/binding-darwin-arm64": {
|
| 593 |
+
"version": "1.0.0-rc.17",
|
| 594 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
|
| 595 |
+
"integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
|
| 596 |
+
"cpu": [
|
| 597 |
+
"arm64"
|
| 598 |
+
],
|
| 599 |
+
"dev": true,
|
| 600 |
+
"license": "MIT",
|
| 601 |
+
"optional": true,
|
| 602 |
+
"os": [
|
| 603 |
+
"darwin"
|
| 604 |
+
],
|
| 605 |
+
"engines": {
|
| 606 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 607 |
+
}
|
| 608 |
+
},
|
| 609 |
+
"node_modules/@rolldown/binding-darwin-x64": {
|
| 610 |
+
"version": "1.0.0-rc.17",
|
| 611 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
|
| 612 |
+
"integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
|
| 613 |
+
"cpu": [
|
| 614 |
+
"x64"
|
| 615 |
+
],
|
| 616 |
+
"dev": true,
|
| 617 |
+
"license": "MIT",
|
| 618 |
+
"optional": true,
|
| 619 |
+
"os": [
|
| 620 |
+
"darwin"
|
| 621 |
+
],
|
| 622 |
+
"engines": {
|
| 623 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 624 |
+
}
|
| 625 |
+
},
|
| 626 |
+
"node_modules/@rolldown/binding-freebsd-x64": {
|
| 627 |
+
"version": "1.0.0-rc.17",
|
| 628 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
|
| 629 |
+
"integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
|
| 630 |
+
"cpu": [
|
| 631 |
+
"x64"
|
| 632 |
+
],
|
| 633 |
+
"dev": true,
|
| 634 |
+
"license": "MIT",
|
| 635 |
+
"optional": true,
|
| 636 |
+
"os": [
|
| 637 |
+
"freebsd"
|
| 638 |
+
],
|
| 639 |
+
"engines": {
|
| 640 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 641 |
+
}
|
| 642 |
+
},
|
| 643 |
+
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
| 644 |
+
"version": "1.0.0-rc.17",
|
| 645 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
|
| 646 |
+
"integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
|
| 647 |
+
"cpu": [
|
| 648 |
+
"arm"
|
| 649 |
+
],
|
| 650 |
+
"dev": true,
|
| 651 |
+
"license": "MIT",
|
| 652 |
+
"optional": true,
|
| 653 |
+
"os": [
|
| 654 |
+
"linux"
|
| 655 |
+
],
|
| 656 |
+
"engines": {
|
| 657 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 658 |
+
}
|
| 659 |
+
},
|
| 660 |
+
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
| 661 |
+
"version": "1.0.0-rc.17",
|
| 662 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
|
| 663 |
+
"integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
|
| 664 |
+
"cpu": [
|
| 665 |
+
"arm64"
|
| 666 |
+
],
|
| 667 |
+
"dev": true,
|
| 668 |
+
"license": "MIT",
|
| 669 |
+
"optional": true,
|
| 670 |
+
"os": [
|
| 671 |
+
"linux"
|
| 672 |
+
],
|
| 673 |
+
"engines": {
|
| 674 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 675 |
+
}
|
| 676 |
+
},
|
| 677 |
+
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
| 678 |
+
"version": "1.0.0-rc.17",
|
| 679 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
|
| 680 |
+
"integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
|
| 681 |
+
"cpu": [
|
| 682 |
+
"arm64"
|
| 683 |
+
],
|
| 684 |
+
"dev": true,
|
| 685 |
+
"license": "MIT",
|
| 686 |
+
"optional": true,
|
| 687 |
+
"os": [
|
| 688 |
+
"linux"
|
| 689 |
+
],
|
| 690 |
+
"engines": {
|
| 691 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 692 |
+
}
|
| 693 |
+
},
|
| 694 |
+
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
| 695 |
+
"version": "1.0.0-rc.17",
|
| 696 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
|
| 697 |
+
"integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
|
| 698 |
+
"cpu": [
|
| 699 |
+
"ppc64"
|
| 700 |
+
],
|
| 701 |
+
"dev": true,
|
| 702 |
+
"license": "MIT",
|
| 703 |
+
"optional": true,
|
| 704 |
+
"os": [
|
| 705 |
+
"linux"
|
| 706 |
+
],
|
| 707 |
+
"engines": {
|
| 708 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 709 |
+
}
|
| 710 |
+
},
|
| 711 |
+
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
| 712 |
+
"version": "1.0.0-rc.17",
|
| 713 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
|
| 714 |
+
"integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
|
| 715 |
+
"cpu": [
|
| 716 |
+
"s390x"
|
| 717 |
+
],
|
| 718 |
+
"dev": true,
|
| 719 |
+
"license": "MIT",
|
| 720 |
+
"optional": true,
|
| 721 |
+
"os": [
|
| 722 |
+
"linux"
|
| 723 |
+
],
|
| 724 |
+
"engines": {
|
| 725 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 726 |
+
}
|
| 727 |
+
},
|
| 728 |
+
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
| 729 |
+
"version": "1.0.0-rc.17",
|
| 730 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
|
| 731 |
+
"integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
|
| 732 |
+
"cpu": [
|
| 733 |
+
"x64"
|
| 734 |
+
],
|
| 735 |
+
"dev": true,
|
| 736 |
+
"license": "MIT",
|
| 737 |
+
"optional": true,
|
| 738 |
+
"os": [
|
| 739 |
+
"linux"
|
| 740 |
+
],
|
| 741 |
+
"engines": {
|
| 742 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 743 |
+
}
|
| 744 |
+
},
|
| 745 |
+
"node_modules/@rolldown/binding-linux-x64-musl": {
|
| 746 |
+
"version": "1.0.0-rc.17",
|
| 747 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
|
| 748 |
+
"integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
|
| 749 |
+
"cpu": [
|
| 750 |
+
"x64"
|
| 751 |
+
],
|
| 752 |
+
"dev": true,
|
| 753 |
+
"license": "MIT",
|
| 754 |
+
"optional": true,
|
| 755 |
+
"os": [
|
| 756 |
+
"linux"
|
| 757 |
+
],
|
| 758 |
+
"engines": {
|
| 759 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 760 |
+
}
|
| 761 |
+
},
|
| 762 |
+
"node_modules/@rolldown/binding-openharmony-arm64": {
|
| 763 |
+
"version": "1.0.0-rc.17",
|
| 764 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
|
| 765 |
+
"integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
|
| 766 |
+
"cpu": [
|
| 767 |
+
"arm64"
|
| 768 |
+
],
|
| 769 |
+
"dev": true,
|
| 770 |
+
"license": "MIT",
|
| 771 |
+
"optional": true,
|
| 772 |
+
"os": [
|
| 773 |
+
"openharmony"
|
| 774 |
+
],
|
| 775 |
+
"engines": {
|
| 776 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 777 |
+
}
|
| 778 |
+
},
|
| 779 |
+
"node_modules/@rolldown/binding-wasm32-wasi": {
|
| 780 |
+
"version": "1.0.0-rc.17",
|
| 781 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
|
| 782 |
+
"integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
|
| 783 |
+
"cpu": [
|
| 784 |
+
"wasm32"
|
| 785 |
+
],
|
| 786 |
+
"dev": true,
|
| 787 |
+
"license": "MIT",
|
| 788 |
+
"optional": true,
|
| 789 |
+
"dependencies": {
|
| 790 |
+
"@emnapi/core": "1.10.0",
|
| 791 |
+
"@emnapi/runtime": "1.10.0",
|
| 792 |
+
"@napi-rs/wasm-runtime": "^1.1.4"
|
| 793 |
+
},
|
| 794 |
+
"engines": {
|
| 795 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 796 |
+
}
|
| 797 |
+
},
|
| 798 |
+
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
| 799 |
+
"version": "1.0.0-rc.17",
|
| 800 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
|
| 801 |
+
"integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
|
| 802 |
+
"cpu": [
|
| 803 |
+
"arm64"
|
| 804 |
+
],
|
| 805 |
+
"dev": true,
|
| 806 |
+
"license": "MIT",
|
| 807 |
+
"optional": true,
|
| 808 |
+
"os": [
|
| 809 |
+
"win32"
|
| 810 |
+
],
|
| 811 |
+
"engines": {
|
| 812 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 813 |
+
}
|
| 814 |
+
},
|
| 815 |
+
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
| 816 |
+
"version": "1.0.0-rc.17",
|
| 817 |
+
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
|
| 818 |
+
"integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
|
| 819 |
+
"cpu": [
|
| 820 |
+
"x64"
|
| 821 |
+
],
|
| 822 |
+
"dev": true,
|
| 823 |
+
"license": "MIT",
|
| 824 |
+
"optional": true,
|
| 825 |
+
"os": [
|
| 826 |
+
"win32"
|
| 827 |
+
],
|
| 828 |
+
"engines": {
|
| 829 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 830 |
+
}
|
| 831 |
+
},
|
| 832 |
+
"node_modules/@rolldown/pluginutils": {
|
| 833 |
+
"version": "1.0.0-rc.7",
|
| 834 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
|
| 835 |
+
"integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
|
| 836 |
+
"dev": true,
|
| 837 |
+
"license": "MIT"
|
| 838 |
+
},
|
| 839 |
+
"node_modules/@tybys/wasm-util": {
|
| 840 |
+
"version": "0.10.1",
|
| 841 |
+
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
| 842 |
+
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
| 843 |
+
"dev": true,
|
| 844 |
+
"license": "MIT",
|
| 845 |
+
"optional": true,
|
| 846 |
+
"dependencies": {
|
| 847 |
+
"tslib": "^2.4.0"
|
| 848 |
+
}
|
| 849 |
+
},
|
| 850 |
+
"node_modules/@types/esrecurse": {
|
| 851 |
+
"version": "4.3.1",
|
| 852 |
+
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
| 853 |
+
"integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
|
| 854 |
+
"dev": true,
|
| 855 |
+
"license": "MIT"
|
| 856 |
+
},
|
| 857 |
+
"node_modules/@types/estree": {
|
| 858 |
+
"version": "1.0.8",
|
| 859 |
+
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
| 860 |
+
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
| 861 |
+
"dev": true,
|
| 862 |
+
"license": "MIT"
|
| 863 |
+
},
|
| 864 |
+
"node_modules/@types/json-schema": {
|
| 865 |
+
"version": "7.0.15",
|
| 866 |
+
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
| 867 |
+
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
| 868 |
+
"dev": true,
|
| 869 |
+
"license": "MIT"
|
| 870 |
+
},
|
| 871 |
+
"node_modules/@types/react": {
|
| 872 |
+
"version": "19.2.14",
|
| 873 |
+
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
| 874 |
+
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
| 875 |
+
"dev": true,
|
| 876 |
+
"license": "MIT",
|
| 877 |
+
"dependencies": {
|
| 878 |
+
"csstype": "^3.2.2"
|
| 879 |
+
}
|
| 880 |
+
},
|
| 881 |
+
"node_modules/@types/react-dom": {
|
| 882 |
+
"version": "19.2.3",
|
| 883 |
+
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
|
| 884 |
+
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
|
| 885 |
+
"dev": true,
|
| 886 |
+
"license": "MIT",
|
| 887 |
+
"peerDependencies": {
|
| 888 |
+
"@types/react": "^19.2.0"
|
| 889 |
+
}
|
| 890 |
+
},
|
| 891 |
+
"node_modules/@vitejs/plugin-react": {
|
| 892 |
+
"version": "6.0.1",
|
| 893 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
|
| 894 |
+
"integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
|
| 895 |
+
"dev": true,
|
| 896 |
+
"license": "MIT",
|
| 897 |
+
"dependencies": {
|
| 898 |
+
"@rolldown/pluginutils": "1.0.0-rc.7"
|
| 899 |
+
},
|
| 900 |
+
"engines": {
|
| 901 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 902 |
+
},
|
| 903 |
+
"peerDependencies": {
|
| 904 |
+
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
|
| 905 |
+
"babel-plugin-react-compiler": "^1.0.0",
|
| 906 |
+
"vite": "^8.0.0"
|
| 907 |
+
},
|
| 908 |
+
"peerDependenciesMeta": {
|
| 909 |
+
"@rolldown/plugin-babel": {
|
| 910 |
+
"optional": true
|
| 911 |
+
},
|
| 912 |
+
"babel-plugin-react-compiler": {
|
| 913 |
+
"optional": true
|
| 914 |
+
}
|
| 915 |
+
}
|
| 916 |
+
},
|
| 917 |
+
"node_modules/acorn": {
|
| 918 |
+
"version": "8.16.0",
|
| 919 |
+
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
| 920 |
+
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
| 921 |
+
"dev": true,
|
| 922 |
+
"license": "MIT",
|
| 923 |
+
"bin": {
|
| 924 |
+
"acorn": "bin/acorn"
|
| 925 |
+
},
|
| 926 |
+
"engines": {
|
| 927 |
+
"node": ">=0.4.0"
|
| 928 |
+
}
|
| 929 |
+
},
|
| 930 |
+
"node_modules/acorn-jsx": {
|
| 931 |
+
"version": "5.3.2",
|
| 932 |
+
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
| 933 |
+
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
| 934 |
+
"dev": true,
|
| 935 |
+
"license": "MIT",
|
| 936 |
+
"peerDependencies": {
|
| 937 |
+
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
| 938 |
+
}
|
| 939 |
+
},
|
| 940 |
+
"node_modules/ajv": {
|
| 941 |
+
"version": "6.15.0",
|
| 942 |
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
|
| 943 |
+
"integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
|
| 944 |
+
"dev": true,
|
| 945 |
+
"license": "MIT",
|
| 946 |
+
"dependencies": {
|
| 947 |
+
"fast-deep-equal": "^3.1.1",
|
| 948 |
+
"fast-json-stable-stringify": "^2.0.0",
|
| 949 |
+
"json-schema-traverse": "^0.4.1",
|
| 950 |
+
"uri-js": "^4.2.2"
|
| 951 |
+
},
|
| 952 |
+
"funding": {
|
| 953 |
+
"type": "github",
|
| 954 |
+
"url": "https://github.com/sponsors/epoberezkin"
|
| 955 |
+
}
|
| 956 |
+
},
|
| 957 |
+
"node_modules/balanced-match": {
|
| 958 |
+
"version": "4.0.4",
|
| 959 |
+
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
| 960 |
+
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
| 961 |
+
"dev": true,
|
| 962 |
+
"license": "MIT",
|
| 963 |
+
"engines": {
|
| 964 |
+
"node": "18 || 20 || >=22"
|
| 965 |
+
}
|
| 966 |
+
},
|
| 967 |
+
"node_modules/baseline-browser-mapping": {
|
| 968 |
+
"version": "2.10.25",
|
| 969 |
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.25.tgz",
|
| 970 |
+
"integrity": "sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA==",
|
| 971 |
+
"dev": true,
|
| 972 |
+
"license": "Apache-2.0",
|
| 973 |
+
"bin": {
|
| 974 |
+
"baseline-browser-mapping": "dist/cli.cjs"
|
| 975 |
+
},
|
| 976 |
+
"engines": {
|
| 977 |
+
"node": ">=6.0.0"
|
| 978 |
+
}
|
| 979 |
+
},
|
| 980 |
+
"node_modules/brace-expansion": {
|
| 981 |
+
"version": "5.0.5",
|
| 982 |
+
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
| 983 |
+
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
| 984 |
+
"dev": true,
|
| 985 |
+
"license": "MIT",
|
| 986 |
+
"dependencies": {
|
| 987 |
+
"balanced-match": "^4.0.2"
|
| 988 |
+
},
|
| 989 |
+
"engines": {
|
| 990 |
+
"node": "18 || 20 || >=22"
|
| 991 |
+
}
|
| 992 |
+
},
|
| 993 |
+
"node_modules/browserslist": {
|
| 994 |
+
"version": "4.28.2",
|
| 995 |
+
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
|
| 996 |
+
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
|
| 997 |
+
"dev": true,
|
| 998 |
+
"funding": [
|
| 999 |
+
{
|
| 1000 |
+
"type": "opencollective",
|
| 1001 |
+
"url": "https://opencollective.com/browserslist"
|
| 1002 |
+
},
|
| 1003 |
+
{
|
| 1004 |
+
"type": "tidelift",
|
| 1005 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 1006 |
+
},
|
| 1007 |
+
{
|
| 1008 |
+
"type": "github",
|
| 1009 |
+
"url": "https://github.com/sponsors/ai"
|
| 1010 |
+
}
|
| 1011 |
+
],
|
| 1012 |
+
"license": "MIT",
|
| 1013 |
+
"dependencies": {
|
| 1014 |
+
"baseline-browser-mapping": "^2.10.12",
|
| 1015 |
+
"caniuse-lite": "^1.0.30001782",
|
| 1016 |
+
"electron-to-chromium": "^1.5.328",
|
| 1017 |
+
"node-releases": "^2.0.36",
|
| 1018 |
+
"update-browserslist-db": "^1.2.3"
|
| 1019 |
+
},
|
| 1020 |
+
"bin": {
|
| 1021 |
+
"browserslist": "cli.js"
|
| 1022 |
+
},
|
| 1023 |
+
"engines": {
|
| 1024 |
+
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1025 |
+
}
|
| 1026 |
+
},
|
| 1027 |
+
"node_modules/caniuse-lite": {
|
| 1028 |
+
"version": "1.0.30001791",
|
| 1029 |
+
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz",
|
| 1030 |
+
"integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==",
|
| 1031 |
+
"dev": true,
|
| 1032 |
+
"funding": [
|
| 1033 |
+
{
|
| 1034 |
+
"type": "opencollective",
|
| 1035 |
+
"url": "https://opencollective.com/browserslist"
|
| 1036 |
+
},
|
| 1037 |
+
{
|
| 1038 |
+
"type": "tidelift",
|
| 1039 |
+
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
| 1040 |
+
},
|
| 1041 |
+
{
|
| 1042 |
+
"type": "github",
|
| 1043 |
+
"url": "https://github.com/sponsors/ai"
|
| 1044 |
+
}
|
| 1045 |
+
],
|
| 1046 |
+
"license": "CC-BY-4.0"
|
| 1047 |
+
},
|
| 1048 |
+
"node_modules/convert-source-map": {
|
| 1049 |
+
"version": "2.0.0",
|
| 1050 |
+
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
| 1051 |
+
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
| 1052 |
+
"dev": true,
|
| 1053 |
+
"license": "MIT"
|
| 1054 |
+
},
|
| 1055 |
+
"node_modules/cross-spawn": {
|
| 1056 |
+
"version": "7.0.6",
|
| 1057 |
+
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
| 1058 |
+
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
| 1059 |
+
"dev": true,
|
| 1060 |
+
"license": "MIT",
|
| 1061 |
+
"dependencies": {
|
| 1062 |
+
"path-key": "^3.1.0",
|
| 1063 |
+
"shebang-command": "^2.0.0",
|
| 1064 |
+
"which": "^2.0.1"
|
| 1065 |
+
},
|
| 1066 |
+
"engines": {
|
| 1067 |
+
"node": ">= 8"
|
| 1068 |
+
}
|
| 1069 |
+
},
|
| 1070 |
+
"node_modules/csstype": {
|
| 1071 |
+
"version": "3.2.3",
|
| 1072 |
+
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
| 1073 |
+
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
| 1074 |
+
"dev": true,
|
| 1075 |
+
"license": "MIT"
|
| 1076 |
+
},
|
| 1077 |
+
"node_modules/debug": {
|
| 1078 |
+
"version": "4.4.3",
|
| 1079 |
+
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
| 1080 |
+
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
| 1081 |
+
"dev": true,
|
| 1082 |
+
"license": "MIT",
|
| 1083 |
+
"dependencies": {
|
| 1084 |
+
"ms": "^2.1.3"
|
| 1085 |
+
},
|
| 1086 |
+
"engines": {
|
| 1087 |
+
"node": ">=6.0"
|
| 1088 |
+
},
|
| 1089 |
+
"peerDependenciesMeta": {
|
| 1090 |
+
"supports-color": {
|
| 1091 |
+
"optional": true
|
| 1092 |
+
}
|
| 1093 |
+
}
|
| 1094 |
+
},
|
| 1095 |
+
"node_modules/deep-is": {
|
| 1096 |
+
"version": "0.1.4",
|
| 1097 |
+
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
| 1098 |
+
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
| 1099 |
+
"dev": true,
|
| 1100 |
+
"license": "MIT"
|
| 1101 |
+
},
|
| 1102 |
+
"node_modules/detect-libc": {
|
| 1103 |
+
"version": "2.1.2",
|
| 1104 |
+
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 1105 |
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 1106 |
+
"dev": true,
|
| 1107 |
+
"license": "Apache-2.0",
|
| 1108 |
+
"engines": {
|
| 1109 |
+
"node": ">=8"
|
| 1110 |
+
}
|
| 1111 |
+
},
|
| 1112 |
+
"node_modules/electron-to-chromium": {
|
| 1113 |
+
"version": "1.5.349",
|
| 1114 |
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz",
|
| 1115 |
+
"integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==",
|
| 1116 |
+
"dev": true,
|
| 1117 |
+
"license": "ISC"
|
| 1118 |
+
},
|
| 1119 |
+
"node_modules/escalade": {
|
| 1120 |
+
"version": "3.2.0",
|
| 1121 |
+
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
| 1122 |
+
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
| 1123 |
+
"dev": true,
|
| 1124 |
+
"license": "MIT",
|
| 1125 |
+
"engines": {
|
| 1126 |
+
"node": ">=6"
|
| 1127 |
+
}
|
| 1128 |
+
},
|
| 1129 |
+
"node_modules/escape-string-regexp": {
|
| 1130 |
+
"version": "4.0.0",
|
| 1131 |
+
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
| 1132 |
+
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
| 1133 |
+
"dev": true,
|
| 1134 |
+
"license": "MIT",
|
| 1135 |
+
"engines": {
|
| 1136 |
+
"node": ">=10"
|
| 1137 |
+
},
|
| 1138 |
+
"funding": {
|
| 1139 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1140 |
+
}
|
| 1141 |
+
},
|
| 1142 |
+
"node_modules/eslint": {
|
| 1143 |
+
"version": "10.3.0",
|
| 1144 |
+
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.3.0.tgz",
|
| 1145 |
+
"integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==",
|
| 1146 |
+
"dev": true,
|
| 1147 |
+
"license": "MIT",
|
| 1148 |
+
"dependencies": {
|
| 1149 |
+
"@eslint-community/eslint-utils": "^4.8.0",
|
| 1150 |
+
"@eslint-community/regexpp": "^4.12.2",
|
| 1151 |
+
"@eslint/config-array": "^0.23.5",
|
| 1152 |
+
"@eslint/config-helpers": "^0.5.5",
|
| 1153 |
+
"@eslint/core": "^1.2.1",
|
| 1154 |
+
"@eslint/plugin-kit": "^0.7.1",
|
| 1155 |
+
"@humanfs/node": "^0.16.6",
|
| 1156 |
+
"@humanwhocodes/module-importer": "^1.0.1",
|
| 1157 |
+
"@humanwhocodes/retry": "^0.4.2",
|
| 1158 |
+
"@types/estree": "^1.0.6",
|
| 1159 |
+
"ajv": "^6.14.0",
|
| 1160 |
+
"cross-spawn": "^7.0.6",
|
| 1161 |
+
"debug": "^4.3.2",
|
| 1162 |
+
"escape-string-regexp": "^4.0.0",
|
| 1163 |
+
"eslint-scope": "^9.1.2",
|
| 1164 |
+
"eslint-visitor-keys": "^5.0.1",
|
| 1165 |
+
"espree": "^11.2.0",
|
| 1166 |
+
"esquery": "^1.7.0",
|
| 1167 |
+
"esutils": "^2.0.2",
|
| 1168 |
+
"fast-deep-equal": "^3.1.3",
|
| 1169 |
+
"file-entry-cache": "^8.0.0",
|
| 1170 |
+
"find-up": "^5.0.0",
|
| 1171 |
+
"glob-parent": "^6.0.2",
|
| 1172 |
+
"ignore": "^5.2.0",
|
| 1173 |
+
"imurmurhash": "^0.1.4",
|
| 1174 |
+
"is-glob": "^4.0.0",
|
| 1175 |
+
"json-stable-stringify-without-jsonify": "^1.0.1",
|
| 1176 |
+
"minimatch": "^10.2.4",
|
| 1177 |
+
"natural-compare": "^1.4.0",
|
| 1178 |
+
"optionator": "^0.9.3"
|
| 1179 |
+
},
|
| 1180 |
+
"bin": {
|
| 1181 |
+
"eslint": "bin/eslint.js"
|
| 1182 |
+
},
|
| 1183 |
+
"engines": {
|
| 1184 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 1185 |
+
},
|
| 1186 |
+
"funding": {
|
| 1187 |
+
"url": "https://eslint.org/donate"
|
| 1188 |
+
},
|
| 1189 |
+
"peerDependencies": {
|
| 1190 |
+
"jiti": "*"
|
| 1191 |
+
},
|
| 1192 |
+
"peerDependenciesMeta": {
|
| 1193 |
+
"jiti": {
|
| 1194 |
+
"optional": true
|
| 1195 |
+
}
|
| 1196 |
+
}
|
| 1197 |
+
},
|
| 1198 |
+
"node_modules/eslint-plugin-react-hooks": {
|
| 1199 |
+
"version": "7.1.1",
|
| 1200 |
+
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz",
|
| 1201 |
+
"integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==",
|
| 1202 |
+
"dev": true,
|
| 1203 |
+
"license": "MIT",
|
| 1204 |
+
"dependencies": {
|
| 1205 |
+
"@babel/core": "^7.24.4",
|
| 1206 |
+
"@babel/parser": "^7.24.4",
|
| 1207 |
+
"hermes-parser": "^0.25.1",
|
| 1208 |
+
"zod": "^3.25.0 || ^4.0.0",
|
| 1209 |
+
"zod-validation-error": "^3.5.0 || ^4.0.0"
|
| 1210 |
+
},
|
| 1211 |
+
"engines": {
|
| 1212 |
+
"node": ">=18"
|
| 1213 |
+
},
|
| 1214 |
+
"peerDependencies": {
|
| 1215 |
+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
|
| 1216 |
+
}
|
| 1217 |
+
},
|
| 1218 |
+
"node_modules/eslint-plugin-react-refresh": {
|
| 1219 |
+
"version": "0.5.2",
|
| 1220 |
+
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
|
| 1221 |
+
"integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
|
| 1222 |
+
"dev": true,
|
| 1223 |
+
"license": "MIT",
|
| 1224 |
+
"peerDependencies": {
|
| 1225 |
+
"eslint": "^9 || ^10"
|
| 1226 |
+
}
|
| 1227 |
+
},
|
| 1228 |
+
"node_modules/eslint-scope": {
|
| 1229 |
+
"version": "9.1.2",
|
| 1230 |
+
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
|
| 1231 |
+
"integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
|
| 1232 |
+
"dev": true,
|
| 1233 |
+
"license": "BSD-2-Clause",
|
| 1234 |
+
"dependencies": {
|
| 1235 |
+
"@types/esrecurse": "^4.3.1",
|
| 1236 |
+
"@types/estree": "^1.0.8",
|
| 1237 |
+
"esrecurse": "^4.3.0",
|
| 1238 |
+
"estraverse": "^5.2.0"
|
| 1239 |
+
},
|
| 1240 |
+
"engines": {
|
| 1241 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 1242 |
+
},
|
| 1243 |
+
"funding": {
|
| 1244 |
+
"url": "https://opencollective.com/eslint"
|
| 1245 |
+
}
|
| 1246 |
+
},
|
| 1247 |
+
"node_modules/eslint-visitor-keys": {
|
| 1248 |
+
"version": "5.0.1",
|
| 1249 |
+
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
|
| 1250 |
+
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
|
| 1251 |
+
"dev": true,
|
| 1252 |
+
"license": "Apache-2.0",
|
| 1253 |
+
"engines": {
|
| 1254 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 1255 |
+
},
|
| 1256 |
+
"funding": {
|
| 1257 |
+
"url": "https://opencollective.com/eslint"
|
| 1258 |
+
}
|
| 1259 |
+
},
|
| 1260 |
+
"node_modules/espree": {
|
| 1261 |
+
"version": "11.2.0",
|
| 1262 |
+
"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
|
| 1263 |
+
"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
|
| 1264 |
+
"dev": true,
|
| 1265 |
+
"license": "BSD-2-Clause",
|
| 1266 |
+
"dependencies": {
|
| 1267 |
+
"acorn": "^8.16.0",
|
| 1268 |
+
"acorn-jsx": "^5.3.2",
|
| 1269 |
+
"eslint-visitor-keys": "^5.0.1"
|
| 1270 |
+
},
|
| 1271 |
+
"engines": {
|
| 1272 |
+
"node": "^20.19.0 || ^22.13.0 || >=24"
|
| 1273 |
+
},
|
| 1274 |
+
"funding": {
|
| 1275 |
+
"url": "https://opencollective.com/eslint"
|
| 1276 |
+
}
|
| 1277 |
+
},
|
| 1278 |
+
"node_modules/esquery": {
|
| 1279 |
+
"version": "1.7.0",
|
| 1280 |
+
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
|
| 1281 |
+
"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
|
| 1282 |
+
"dev": true,
|
| 1283 |
+
"license": "BSD-3-Clause",
|
| 1284 |
+
"dependencies": {
|
| 1285 |
+
"estraverse": "^5.1.0"
|
| 1286 |
+
},
|
| 1287 |
+
"engines": {
|
| 1288 |
+
"node": ">=0.10"
|
| 1289 |
+
}
|
| 1290 |
+
},
|
| 1291 |
+
"node_modules/esrecurse": {
|
| 1292 |
+
"version": "4.3.0",
|
| 1293 |
+
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
| 1294 |
+
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
| 1295 |
+
"dev": true,
|
| 1296 |
+
"license": "BSD-2-Clause",
|
| 1297 |
+
"dependencies": {
|
| 1298 |
+
"estraverse": "^5.2.0"
|
| 1299 |
+
},
|
| 1300 |
+
"engines": {
|
| 1301 |
+
"node": ">=4.0"
|
| 1302 |
+
}
|
| 1303 |
+
},
|
| 1304 |
+
"node_modules/estraverse": {
|
| 1305 |
+
"version": "5.3.0",
|
| 1306 |
+
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
| 1307 |
+
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
| 1308 |
+
"dev": true,
|
| 1309 |
+
"license": "BSD-2-Clause",
|
| 1310 |
+
"engines": {
|
| 1311 |
+
"node": ">=4.0"
|
| 1312 |
+
}
|
| 1313 |
+
},
|
| 1314 |
+
"node_modules/esutils": {
|
| 1315 |
+
"version": "2.0.3",
|
| 1316 |
+
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
| 1317 |
+
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
| 1318 |
+
"dev": true,
|
| 1319 |
+
"license": "BSD-2-Clause",
|
| 1320 |
+
"engines": {
|
| 1321 |
+
"node": ">=0.10.0"
|
| 1322 |
+
}
|
| 1323 |
+
},
|
| 1324 |
+
"node_modules/fast-deep-equal": {
|
| 1325 |
+
"version": "3.1.3",
|
| 1326 |
+
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
| 1327 |
+
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
| 1328 |
+
"dev": true,
|
| 1329 |
+
"license": "MIT"
|
| 1330 |
+
},
|
| 1331 |
+
"node_modules/fast-json-stable-stringify": {
|
| 1332 |
+
"version": "2.1.0",
|
| 1333 |
+
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
| 1334 |
+
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
| 1335 |
+
"dev": true,
|
| 1336 |
+
"license": "MIT"
|
| 1337 |
+
},
|
| 1338 |
+
"node_modules/fast-levenshtein": {
|
| 1339 |
+
"version": "2.0.6",
|
| 1340 |
+
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
| 1341 |
+
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
| 1342 |
+
"dev": true,
|
| 1343 |
+
"license": "MIT"
|
| 1344 |
+
},
|
| 1345 |
+
"node_modules/fdir": {
|
| 1346 |
+
"version": "6.5.0",
|
| 1347 |
+
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1348 |
+
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1349 |
+
"dev": true,
|
| 1350 |
+
"license": "MIT",
|
| 1351 |
+
"engines": {
|
| 1352 |
+
"node": ">=12.0.0"
|
| 1353 |
+
},
|
| 1354 |
+
"peerDependencies": {
|
| 1355 |
+
"picomatch": "^3 || ^4"
|
| 1356 |
+
},
|
| 1357 |
+
"peerDependenciesMeta": {
|
| 1358 |
+
"picomatch": {
|
| 1359 |
+
"optional": true
|
| 1360 |
+
}
|
| 1361 |
+
}
|
| 1362 |
+
},
|
| 1363 |
+
"node_modules/file-entry-cache": {
|
| 1364 |
+
"version": "8.0.0",
|
| 1365 |
+
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
| 1366 |
+
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
|
| 1367 |
+
"dev": true,
|
| 1368 |
+
"license": "MIT",
|
| 1369 |
+
"dependencies": {
|
| 1370 |
+
"flat-cache": "^4.0.0"
|
| 1371 |
+
},
|
| 1372 |
+
"engines": {
|
| 1373 |
+
"node": ">=16.0.0"
|
| 1374 |
+
}
|
| 1375 |
+
},
|
| 1376 |
+
"node_modules/find-up": {
|
| 1377 |
+
"version": "5.0.0",
|
| 1378 |
+
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
| 1379 |
+
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
|
| 1380 |
+
"dev": true,
|
| 1381 |
+
"license": "MIT",
|
| 1382 |
+
"dependencies": {
|
| 1383 |
+
"locate-path": "^6.0.0",
|
| 1384 |
+
"path-exists": "^4.0.0"
|
| 1385 |
+
},
|
| 1386 |
+
"engines": {
|
| 1387 |
+
"node": ">=10"
|
| 1388 |
+
},
|
| 1389 |
+
"funding": {
|
| 1390 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1391 |
+
}
|
| 1392 |
+
},
|
| 1393 |
+
"node_modules/flat-cache": {
|
| 1394 |
+
"version": "4.0.1",
|
| 1395 |
+
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
|
| 1396 |
+
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
|
| 1397 |
+
"dev": true,
|
| 1398 |
+
"license": "MIT",
|
| 1399 |
+
"dependencies": {
|
| 1400 |
+
"flatted": "^3.2.9",
|
| 1401 |
+
"keyv": "^4.5.4"
|
| 1402 |
+
},
|
| 1403 |
+
"engines": {
|
| 1404 |
+
"node": ">=16"
|
| 1405 |
+
}
|
| 1406 |
+
},
|
| 1407 |
+
"node_modules/flatted": {
|
| 1408 |
+
"version": "3.4.2",
|
| 1409 |
+
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
| 1410 |
+
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
| 1411 |
+
"dev": true,
|
| 1412 |
+
"license": "ISC"
|
| 1413 |
+
},
|
| 1414 |
+
"node_modules/fsevents": {
|
| 1415 |
+
"version": "2.3.3",
|
| 1416 |
+
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1417 |
+
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1418 |
+
"dev": true,
|
| 1419 |
+
"hasInstallScript": true,
|
| 1420 |
+
"license": "MIT",
|
| 1421 |
+
"optional": true,
|
| 1422 |
+
"os": [
|
| 1423 |
+
"darwin"
|
| 1424 |
+
],
|
| 1425 |
+
"engines": {
|
| 1426 |
+
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
| 1427 |
+
}
|
| 1428 |
+
},
|
| 1429 |
+
"node_modules/gensync": {
|
| 1430 |
+
"version": "1.0.0-beta.2",
|
| 1431 |
+
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
| 1432 |
+
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
|
| 1433 |
+
"dev": true,
|
| 1434 |
+
"license": "MIT",
|
| 1435 |
+
"engines": {
|
| 1436 |
+
"node": ">=6.9.0"
|
| 1437 |
+
}
|
| 1438 |
+
},
|
| 1439 |
+
"node_modules/glob-parent": {
|
| 1440 |
+
"version": "6.0.2",
|
| 1441 |
+
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
| 1442 |
+
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
| 1443 |
+
"dev": true,
|
| 1444 |
+
"license": "ISC",
|
| 1445 |
+
"dependencies": {
|
| 1446 |
+
"is-glob": "^4.0.3"
|
| 1447 |
+
},
|
| 1448 |
+
"engines": {
|
| 1449 |
+
"node": ">=10.13.0"
|
| 1450 |
+
}
|
| 1451 |
+
},
|
| 1452 |
+
"node_modules/globals": {
|
| 1453 |
+
"version": "17.6.0",
|
| 1454 |
+
"resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz",
|
| 1455 |
+
"integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==",
|
| 1456 |
+
"dev": true,
|
| 1457 |
+
"license": "MIT",
|
| 1458 |
+
"engines": {
|
| 1459 |
+
"node": ">=18"
|
| 1460 |
+
},
|
| 1461 |
+
"funding": {
|
| 1462 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1463 |
+
}
|
| 1464 |
+
},
|
| 1465 |
+
"node_modules/hermes-estree": {
|
| 1466 |
+
"version": "0.25.1",
|
| 1467 |
+
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
| 1468 |
+
"integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
|
| 1469 |
+
"dev": true,
|
| 1470 |
+
"license": "MIT"
|
| 1471 |
+
},
|
| 1472 |
+
"node_modules/hermes-parser": {
|
| 1473 |
+
"version": "0.25.1",
|
| 1474 |
+
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
|
| 1475 |
+
"integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
|
| 1476 |
+
"dev": true,
|
| 1477 |
+
"license": "MIT",
|
| 1478 |
+
"dependencies": {
|
| 1479 |
+
"hermes-estree": "0.25.1"
|
| 1480 |
+
}
|
| 1481 |
+
},
|
| 1482 |
+
"node_modules/ignore": {
|
| 1483 |
+
"version": "5.3.2",
|
| 1484 |
+
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
| 1485 |
+
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
| 1486 |
+
"dev": true,
|
| 1487 |
+
"license": "MIT",
|
| 1488 |
+
"engines": {
|
| 1489 |
+
"node": ">= 4"
|
| 1490 |
+
}
|
| 1491 |
+
},
|
| 1492 |
+
"node_modules/imurmurhash": {
|
| 1493 |
+
"version": "0.1.4",
|
| 1494 |
+
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
| 1495 |
+
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
|
| 1496 |
+
"dev": true,
|
| 1497 |
+
"license": "MIT",
|
| 1498 |
+
"engines": {
|
| 1499 |
+
"node": ">=0.8.19"
|
| 1500 |
+
}
|
| 1501 |
+
},
|
| 1502 |
+
"node_modules/is-extglob": {
|
| 1503 |
+
"version": "2.1.1",
|
| 1504 |
+
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
| 1505 |
+
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
| 1506 |
+
"dev": true,
|
| 1507 |
+
"license": "MIT",
|
| 1508 |
+
"engines": {
|
| 1509 |
+
"node": ">=0.10.0"
|
| 1510 |
+
}
|
| 1511 |
+
},
|
| 1512 |
+
"node_modules/is-glob": {
|
| 1513 |
+
"version": "4.0.3",
|
| 1514 |
+
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
| 1515 |
+
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
| 1516 |
+
"dev": true,
|
| 1517 |
+
"license": "MIT",
|
| 1518 |
+
"dependencies": {
|
| 1519 |
+
"is-extglob": "^2.1.1"
|
| 1520 |
+
},
|
| 1521 |
+
"engines": {
|
| 1522 |
+
"node": ">=0.10.0"
|
| 1523 |
+
}
|
| 1524 |
+
},
|
| 1525 |
+
"node_modules/isexe": {
|
| 1526 |
+
"version": "2.0.0",
|
| 1527 |
+
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
| 1528 |
+
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
| 1529 |
+
"dev": true,
|
| 1530 |
+
"license": "ISC"
|
| 1531 |
+
},
|
| 1532 |
+
"node_modules/js-tokens": {
|
| 1533 |
+
"version": "4.0.0",
|
| 1534 |
+
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
| 1535 |
+
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
| 1536 |
+
"dev": true,
|
| 1537 |
+
"license": "MIT"
|
| 1538 |
+
},
|
| 1539 |
+
"node_modules/jsesc": {
|
| 1540 |
+
"version": "3.1.0",
|
| 1541 |
+
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
| 1542 |
+
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
| 1543 |
+
"dev": true,
|
| 1544 |
+
"license": "MIT",
|
| 1545 |
+
"bin": {
|
| 1546 |
+
"jsesc": "bin/jsesc"
|
| 1547 |
+
},
|
| 1548 |
+
"engines": {
|
| 1549 |
+
"node": ">=6"
|
| 1550 |
+
}
|
| 1551 |
+
},
|
| 1552 |
+
"node_modules/json-buffer": {
|
| 1553 |
+
"version": "3.0.1",
|
| 1554 |
+
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
| 1555 |
+
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
| 1556 |
+
"dev": true,
|
| 1557 |
+
"license": "MIT"
|
| 1558 |
+
},
|
| 1559 |
+
"node_modules/json-schema-traverse": {
|
| 1560 |
+
"version": "0.4.1",
|
| 1561 |
+
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
| 1562 |
+
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
| 1563 |
+
"dev": true,
|
| 1564 |
+
"license": "MIT"
|
| 1565 |
+
},
|
| 1566 |
+
"node_modules/json-stable-stringify-without-jsonify": {
|
| 1567 |
+
"version": "1.0.1",
|
| 1568 |
+
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
| 1569 |
+
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
| 1570 |
+
"dev": true,
|
| 1571 |
+
"license": "MIT"
|
| 1572 |
+
},
|
| 1573 |
+
"node_modules/json5": {
|
| 1574 |
+
"version": "2.2.3",
|
| 1575 |
+
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
| 1576 |
+
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
| 1577 |
+
"dev": true,
|
| 1578 |
+
"license": "MIT",
|
| 1579 |
+
"bin": {
|
| 1580 |
+
"json5": "lib/cli.js"
|
| 1581 |
+
},
|
| 1582 |
+
"engines": {
|
| 1583 |
+
"node": ">=6"
|
| 1584 |
+
}
|
| 1585 |
+
},
|
| 1586 |
+
"node_modules/keyv": {
|
| 1587 |
+
"version": "4.5.4",
|
| 1588 |
+
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
| 1589 |
+
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
| 1590 |
+
"dev": true,
|
| 1591 |
+
"license": "MIT",
|
| 1592 |
+
"dependencies": {
|
| 1593 |
+
"json-buffer": "3.0.1"
|
| 1594 |
+
}
|
| 1595 |
+
},
|
| 1596 |
+
"node_modules/levn": {
|
| 1597 |
+
"version": "0.4.1",
|
| 1598 |
+
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
| 1599 |
+
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
|
| 1600 |
+
"dev": true,
|
| 1601 |
+
"license": "MIT",
|
| 1602 |
+
"dependencies": {
|
| 1603 |
+
"prelude-ls": "^1.2.1",
|
| 1604 |
+
"type-check": "~0.4.0"
|
| 1605 |
+
},
|
| 1606 |
+
"engines": {
|
| 1607 |
+
"node": ">= 0.8.0"
|
| 1608 |
+
}
|
| 1609 |
+
},
|
| 1610 |
+
"node_modules/lightningcss": {
|
| 1611 |
+
"version": "1.32.0",
|
| 1612 |
+
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
| 1613 |
+
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
| 1614 |
+
"dev": true,
|
| 1615 |
+
"license": "MPL-2.0",
|
| 1616 |
+
"dependencies": {
|
| 1617 |
+
"detect-libc": "^2.0.3"
|
| 1618 |
+
},
|
| 1619 |
+
"engines": {
|
| 1620 |
+
"node": ">= 12.0.0"
|
| 1621 |
+
},
|
| 1622 |
+
"funding": {
|
| 1623 |
+
"type": "opencollective",
|
| 1624 |
+
"url": "https://opencollective.com/parcel"
|
| 1625 |
+
},
|
| 1626 |
+
"optionalDependencies": {
|
| 1627 |
+
"lightningcss-android-arm64": "1.32.0",
|
| 1628 |
+
"lightningcss-darwin-arm64": "1.32.0",
|
| 1629 |
+
"lightningcss-darwin-x64": "1.32.0",
|
| 1630 |
+
"lightningcss-freebsd-x64": "1.32.0",
|
| 1631 |
+
"lightningcss-linux-arm-gnueabihf": "1.32.0",
|
| 1632 |
+
"lightningcss-linux-arm64-gnu": "1.32.0",
|
| 1633 |
+
"lightningcss-linux-arm64-musl": "1.32.0",
|
| 1634 |
+
"lightningcss-linux-x64-gnu": "1.32.0",
|
| 1635 |
+
"lightningcss-linux-x64-musl": "1.32.0",
|
| 1636 |
+
"lightningcss-win32-arm64-msvc": "1.32.0",
|
| 1637 |
+
"lightningcss-win32-x64-msvc": "1.32.0"
|
| 1638 |
+
}
|
| 1639 |
+
},
|
| 1640 |
+
"node_modules/lightningcss-android-arm64": {
|
| 1641 |
+
"version": "1.32.0",
|
| 1642 |
+
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
|
| 1643 |
+
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
|
| 1644 |
+
"cpu": [
|
| 1645 |
+
"arm64"
|
| 1646 |
+
],
|
| 1647 |
+
"dev": true,
|
| 1648 |
+
"license": "MPL-2.0",
|
| 1649 |
+
"optional": true,
|
| 1650 |
+
"os": [
|
| 1651 |
+
"android"
|
| 1652 |
+
],
|
| 1653 |
+
"engines": {
|
| 1654 |
+
"node": ">= 12.0.0"
|
| 1655 |
+
},
|
| 1656 |
+
"funding": {
|
| 1657 |
+
"type": "opencollective",
|
| 1658 |
+
"url": "https://opencollective.com/parcel"
|
| 1659 |
+
}
|
| 1660 |
+
},
|
| 1661 |
+
"node_modules/lightningcss-darwin-arm64": {
|
| 1662 |
+
"version": "1.32.0",
|
| 1663 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
|
| 1664 |
+
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
|
| 1665 |
+
"cpu": [
|
| 1666 |
+
"arm64"
|
| 1667 |
+
],
|
| 1668 |
+
"dev": true,
|
| 1669 |
+
"license": "MPL-2.0",
|
| 1670 |
+
"optional": true,
|
| 1671 |
+
"os": [
|
| 1672 |
+
"darwin"
|
| 1673 |
+
],
|
| 1674 |
+
"engines": {
|
| 1675 |
+
"node": ">= 12.0.0"
|
| 1676 |
+
},
|
| 1677 |
+
"funding": {
|
| 1678 |
+
"type": "opencollective",
|
| 1679 |
+
"url": "https://opencollective.com/parcel"
|
| 1680 |
+
}
|
| 1681 |
+
},
|
| 1682 |
+
"node_modules/lightningcss-darwin-x64": {
|
| 1683 |
+
"version": "1.32.0",
|
| 1684 |
+
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
|
| 1685 |
+
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
|
| 1686 |
+
"cpu": [
|
| 1687 |
+
"x64"
|
| 1688 |
+
],
|
| 1689 |
+
"dev": true,
|
| 1690 |
+
"license": "MPL-2.0",
|
| 1691 |
+
"optional": true,
|
| 1692 |
+
"os": [
|
| 1693 |
+
"darwin"
|
| 1694 |
+
],
|
| 1695 |
+
"engines": {
|
| 1696 |
+
"node": ">= 12.0.0"
|
| 1697 |
+
},
|
| 1698 |
+
"funding": {
|
| 1699 |
+
"type": "opencollective",
|
| 1700 |
+
"url": "https://opencollective.com/parcel"
|
| 1701 |
+
}
|
| 1702 |
+
},
|
| 1703 |
+
"node_modules/lightningcss-freebsd-x64": {
|
| 1704 |
+
"version": "1.32.0",
|
| 1705 |
+
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
|
| 1706 |
+
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
|
| 1707 |
+
"cpu": [
|
| 1708 |
+
"x64"
|
| 1709 |
+
],
|
| 1710 |
+
"dev": true,
|
| 1711 |
+
"license": "MPL-2.0",
|
| 1712 |
+
"optional": true,
|
| 1713 |
+
"os": [
|
| 1714 |
+
"freebsd"
|
| 1715 |
+
],
|
| 1716 |
+
"engines": {
|
| 1717 |
+
"node": ">= 12.0.0"
|
| 1718 |
+
},
|
| 1719 |
+
"funding": {
|
| 1720 |
+
"type": "opencollective",
|
| 1721 |
+
"url": "https://opencollective.com/parcel"
|
| 1722 |
+
}
|
| 1723 |
+
},
|
| 1724 |
+
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
| 1725 |
+
"version": "1.32.0",
|
| 1726 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
|
| 1727 |
+
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
|
| 1728 |
+
"cpu": [
|
| 1729 |
+
"arm"
|
| 1730 |
+
],
|
| 1731 |
+
"dev": true,
|
| 1732 |
+
"license": "MPL-2.0",
|
| 1733 |
+
"optional": true,
|
| 1734 |
+
"os": [
|
| 1735 |
+
"linux"
|
| 1736 |
+
],
|
| 1737 |
+
"engines": {
|
| 1738 |
+
"node": ">= 12.0.0"
|
| 1739 |
+
},
|
| 1740 |
+
"funding": {
|
| 1741 |
+
"type": "opencollective",
|
| 1742 |
+
"url": "https://opencollective.com/parcel"
|
| 1743 |
+
}
|
| 1744 |
+
},
|
| 1745 |
+
"node_modules/lightningcss-linux-arm64-gnu": {
|
| 1746 |
+
"version": "1.32.0",
|
| 1747 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
|
| 1748 |
+
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
|
| 1749 |
+
"cpu": [
|
| 1750 |
+
"arm64"
|
| 1751 |
+
],
|
| 1752 |
+
"dev": true,
|
| 1753 |
+
"license": "MPL-2.0",
|
| 1754 |
+
"optional": true,
|
| 1755 |
+
"os": [
|
| 1756 |
+
"linux"
|
| 1757 |
+
],
|
| 1758 |
+
"engines": {
|
| 1759 |
+
"node": ">= 12.0.0"
|
| 1760 |
+
},
|
| 1761 |
+
"funding": {
|
| 1762 |
+
"type": "opencollective",
|
| 1763 |
+
"url": "https://opencollective.com/parcel"
|
| 1764 |
+
}
|
| 1765 |
+
},
|
| 1766 |
+
"node_modules/lightningcss-linux-arm64-musl": {
|
| 1767 |
+
"version": "1.32.0",
|
| 1768 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
|
| 1769 |
+
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
|
| 1770 |
+
"cpu": [
|
| 1771 |
+
"arm64"
|
| 1772 |
+
],
|
| 1773 |
+
"dev": true,
|
| 1774 |
+
"license": "MPL-2.0",
|
| 1775 |
+
"optional": true,
|
| 1776 |
+
"os": [
|
| 1777 |
+
"linux"
|
| 1778 |
+
],
|
| 1779 |
+
"engines": {
|
| 1780 |
+
"node": ">= 12.0.0"
|
| 1781 |
+
},
|
| 1782 |
+
"funding": {
|
| 1783 |
+
"type": "opencollective",
|
| 1784 |
+
"url": "https://opencollective.com/parcel"
|
| 1785 |
+
}
|
| 1786 |
+
},
|
| 1787 |
+
"node_modules/lightningcss-linux-x64-gnu": {
|
| 1788 |
+
"version": "1.32.0",
|
| 1789 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
|
| 1790 |
+
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
|
| 1791 |
+
"cpu": [
|
| 1792 |
+
"x64"
|
| 1793 |
+
],
|
| 1794 |
+
"dev": true,
|
| 1795 |
+
"license": "MPL-2.0",
|
| 1796 |
+
"optional": true,
|
| 1797 |
+
"os": [
|
| 1798 |
+
"linux"
|
| 1799 |
+
],
|
| 1800 |
+
"engines": {
|
| 1801 |
+
"node": ">= 12.0.0"
|
| 1802 |
+
},
|
| 1803 |
+
"funding": {
|
| 1804 |
+
"type": "opencollective",
|
| 1805 |
+
"url": "https://opencollective.com/parcel"
|
| 1806 |
+
}
|
| 1807 |
+
},
|
| 1808 |
+
"node_modules/lightningcss-linux-x64-musl": {
|
| 1809 |
+
"version": "1.32.0",
|
| 1810 |
+
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
|
| 1811 |
+
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
|
| 1812 |
+
"cpu": [
|
| 1813 |
+
"x64"
|
| 1814 |
+
],
|
| 1815 |
+
"dev": true,
|
| 1816 |
+
"license": "MPL-2.0",
|
| 1817 |
+
"optional": true,
|
| 1818 |
+
"os": [
|
| 1819 |
+
"linux"
|
| 1820 |
+
],
|
| 1821 |
+
"engines": {
|
| 1822 |
+
"node": ">= 12.0.0"
|
| 1823 |
+
},
|
| 1824 |
+
"funding": {
|
| 1825 |
+
"type": "opencollective",
|
| 1826 |
+
"url": "https://opencollective.com/parcel"
|
| 1827 |
+
}
|
| 1828 |
+
},
|
| 1829 |
+
"node_modules/lightningcss-win32-arm64-msvc": {
|
| 1830 |
+
"version": "1.32.0",
|
| 1831 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
|
| 1832 |
+
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
|
| 1833 |
+
"cpu": [
|
| 1834 |
+
"arm64"
|
| 1835 |
+
],
|
| 1836 |
+
"dev": true,
|
| 1837 |
+
"license": "MPL-2.0",
|
| 1838 |
+
"optional": true,
|
| 1839 |
+
"os": [
|
| 1840 |
+
"win32"
|
| 1841 |
+
],
|
| 1842 |
+
"engines": {
|
| 1843 |
+
"node": ">= 12.0.0"
|
| 1844 |
+
},
|
| 1845 |
+
"funding": {
|
| 1846 |
+
"type": "opencollective",
|
| 1847 |
+
"url": "https://opencollective.com/parcel"
|
| 1848 |
+
}
|
| 1849 |
+
},
|
| 1850 |
+
"node_modules/lightningcss-win32-x64-msvc": {
|
| 1851 |
+
"version": "1.32.0",
|
| 1852 |
+
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
|
| 1853 |
+
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
|
| 1854 |
+
"cpu": [
|
| 1855 |
+
"x64"
|
| 1856 |
+
],
|
| 1857 |
+
"dev": true,
|
| 1858 |
+
"license": "MPL-2.0",
|
| 1859 |
+
"optional": true,
|
| 1860 |
+
"os": [
|
| 1861 |
+
"win32"
|
| 1862 |
+
],
|
| 1863 |
+
"engines": {
|
| 1864 |
+
"node": ">= 12.0.0"
|
| 1865 |
+
},
|
| 1866 |
+
"funding": {
|
| 1867 |
+
"type": "opencollective",
|
| 1868 |
+
"url": "https://opencollective.com/parcel"
|
| 1869 |
+
}
|
| 1870 |
+
},
|
| 1871 |
+
"node_modules/locate-path": {
|
| 1872 |
+
"version": "6.0.0",
|
| 1873 |
+
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
| 1874 |
+
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
|
| 1875 |
+
"dev": true,
|
| 1876 |
+
"license": "MIT",
|
| 1877 |
+
"dependencies": {
|
| 1878 |
+
"p-locate": "^5.0.0"
|
| 1879 |
+
},
|
| 1880 |
+
"engines": {
|
| 1881 |
+
"node": ">=10"
|
| 1882 |
+
},
|
| 1883 |
+
"funding": {
|
| 1884 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1885 |
+
}
|
| 1886 |
+
},
|
| 1887 |
+
"node_modules/lru-cache": {
|
| 1888 |
+
"version": "5.1.1",
|
| 1889 |
+
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
| 1890 |
+
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
| 1891 |
+
"dev": true,
|
| 1892 |
+
"license": "ISC",
|
| 1893 |
+
"dependencies": {
|
| 1894 |
+
"yallist": "^3.0.2"
|
| 1895 |
+
}
|
| 1896 |
+
},
|
| 1897 |
+
"node_modules/lucide-react": {
|
| 1898 |
+
"version": "1.14.0",
|
| 1899 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz",
|
| 1900 |
+
"integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==",
|
| 1901 |
+
"license": "ISC",
|
| 1902 |
+
"peerDependencies": {
|
| 1903 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 1904 |
+
}
|
| 1905 |
+
},
|
| 1906 |
+
"node_modules/minimatch": {
|
| 1907 |
+
"version": "10.2.5",
|
| 1908 |
+
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
| 1909 |
+
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
| 1910 |
+
"dev": true,
|
| 1911 |
+
"license": "BlueOak-1.0.0",
|
| 1912 |
+
"dependencies": {
|
| 1913 |
+
"brace-expansion": "^5.0.5"
|
| 1914 |
+
},
|
| 1915 |
+
"engines": {
|
| 1916 |
+
"node": "18 || 20 || >=22"
|
| 1917 |
+
},
|
| 1918 |
+
"funding": {
|
| 1919 |
+
"url": "https://github.com/sponsors/isaacs"
|
| 1920 |
+
}
|
| 1921 |
+
},
|
| 1922 |
+
"node_modules/ms": {
|
| 1923 |
+
"version": "2.1.3",
|
| 1924 |
+
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
| 1925 |
+
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
| 1926 |
+
"dev": true,
|
| 1927 |
+
"license": "MIT"
|
| 1928 |
+
},
|
| 1929 |
+
"node_modules/nanoid": {
|
| 1930 |
+
"version": "3.3.12",
|
| 1931 |
+
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
|
| 1932 |
+
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
|
| 1933 |
+
"dev": true,
|
| 1934 |
+
"funding": [
|
| 1935 |
+
{
|
| 1936 |
+
"type": "github",
|
| 1937 |
+
"url": "https://github.com/sponsors/ai"
|
| 1938 |
+
}
|
| 1939 |
+
],
|
| 1940 |
+
"license": "MIT",
|
| 1941 |
+
"bin": {
|
| 1942 |
+
"nanoid": "bin/nanoid.cjs"
|
| 1943 |
+
},
|
| 1944 |
+
"engines": {
|
| 1945 |
+
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
| 1946 |
+
}
|
| 1947 |
+
},
|
| 1948 |
+
"node_modules/natural-compare": {
|
| 1949 |
+
"version": "1.4.0",
|
| 1950 |
+
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
| 1951 |
+
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
| 1952 |
+
"dev": true,
|
| 1953 |
+
"license": "MIT"
|
| 1954 |
+
},
|
| 1955 |
+
"node_modules/node-releases": {
|
| 1956 |
+
"version": "2.0.38",
|
| 1957 |
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
|
| 1958 |
+
"integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==",
|
| 1959 |
+
"dev": true,
|
| 1960 |
+
"license": "MIT"
|
| 1961 |
+
},
|
| 1962 |
+
"node_modules/optionator": {
|
| 1963 |
+
"version": "0.9.4",
|
| 1964 |
+
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
| 1965 |
+
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
|
| 1966 |
+
"dev": true,
|
| 1967 |
+
"license": "MIT",
|
| 1968 |
+
"dependencies": {
|
| 1969 |
+
"deep-is": "^0.1.3",
|
| 1970 |
+
"fast-levenshtein": "^2.0.6",
|
| 1971 |
+
"levn": "^0.4.1",
|
| 1972 |
+
"prelude-ls": "^1.2.1",
|
| 1973 |
+
"type-check": "^0.4.0",
|
| 1974 |
+
"word-wrap": "^1.2.5"
|
| 1975 |
+
},
|
| 1976 |
+
"engines": {
|
| 1977 |
+
"node": ">= 0.8.0"
|
| 1978 |
+
}
|
| 1979 |
+
},
|
| 1980 |
+
"node_modules/p-limit": {
|
| 1981 |
+
"version": "3.1.0",
|
| 1982 |
+
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
| 1983 |
+
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
| 1984 |
+
"dev": true,
|
| 1985 |
+
"license": "MIT",
|
| 1986 |
+
"dependencies": {
|
| 1987 |
+
"yocto-queue": "^0.1.0"
|
| 1988 |
+
},
|
| 1989 |
+
"engines": {
|
| 1990 |
+
"node": ">=10"
|
| 1991 |
+
},
|
| 1992 |
+
"funding": {
|
| 1993 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 1994 |
+
}
|
| 1995 |
+
},
|
| 1996 |
+
"node_modules/p-locate": {
|
| 1997 |
+
"version": "5.0.0",
|
| 1998 |
+
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
|
| 1999 |
+
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
|
| 2000 |
+
"dev": true,
|
| 2001 |
+
"license": "MIT",
|
| 2002 |
+
"dependencies": {
|
| 2003 |
+
"p-limit": "^3.0.2"
|
| 2004 |
+
},
|
| 2005 |
+
"engines": {
|
| 2006 |
+
"node": ">=10"
|
| 2007 |
+
},
|
| 2008 |
+
"funding": {
|
| 2009 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2010 |
+
}
|
| 2011 |
+
},
|
| 2012 |
+
"node_modules/path-exists": {
|
| 2013 |
+
"version": "4.0.0",
|
| 2014 |
+
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
| 2015 |
+
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
| 2016 |
+
"dev": true,
|
| 2017 |
+
"license": "MIT",
|
| 2018 |
+
"engines": {
|
| 2019 |
+
"node": ">=8"
|
| 2020 |
+
}
|
| 2021 |
+
},
|
| 2022 |
+
"node_modules/path-key": {
|
| 2023 |
+
"version": "3.1.1",
|
| 2024 |
+
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
| 2025 |
+
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
| 2026 |
+
"dev": true,
|
| 2027 |
+
"license": "MIT",
|
| 2028 |
+
"engines": {
|
| 2029 |
+
"node": ">=8"
|
| 2030 |
+
}
|
| 2031 |
+
},
|
| 2032 |
+
"node_modules/picocolors": {
|
| 2033 |
+
"version": "1.1.1",
|
| 2034 |
+
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 2035 |
+
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 2036 |
+
"dev": true,
|
| 2037 |
+
"license": "ISC"
|
| 2038 |
+
},
|
| 2039 |
+
"node_modules/picomatch": {
|
| 2040 |
+
"version": "4.0.4",
|
| 2041 |
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 2042 |
+
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 2043 |
+
"dev": true,
|
| 2044 |
+
"license": "MIT",
|
| 2045 |
+
"engines": {
|
| 2046 |
+
"node": ">=12"
|
| 2047 |
+
},
|
| 2048 |
+
"funding": {
|
| 2049 |
+
"url": "https://github.com/sponsors/jonschlinkert"
|
| 2050 |
+
}
|
| 2051 |
+
},
|
| 2052 |
+
"node_modules/postcss": {
|
| 2053 |
+
"version": "8.5.13",
|
| 2054 |
+
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
|
| 2055 |
+
"integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==",
|
| 2056 |
+
"dev": true,
|
| 2057 |
+
"funding": [
|
| 2058 |
+
{
|
| 2059 |
+
"type": "opencollective",
|
| 2060 |
+
"url": "https://opencollective.com/postcss/"
|
| 2061 |
+
},
|
| 2062 |
+
{
|
| 2063 |
+
"type": "tidelift",
|
| 2064 |
+
"url": "https://tidelift.com/funding/github/npm/postcss"
|
| 2065 |
+
},
|
| 2066 |
+
{
|
| 2067 |
+
"type": "github",
|
| 2068 |
+
"url": "https://github.com/sponsors/ai"
|
| 2069 |
+
}
|
| 2070 |
+
],
|
| 2071 |
+
"license": "MIT",
|
| 2072 |
+
"dependencies": {
|
| 2073 |
+
"nanoid": "^3.3.11",
|
| 2074 |
+
"picocolors": "^1.1.1",
|
| 2075 |
+
"source-map-js": "^1.2.1"
|
| 2076 |
+
},
|
| 2077 |
+
"engines": {
|
| 2078 |
+
"node": "^10 || ^12 || >=14"
|
| 2079 |
+
}
|
| 2080 |
+
},
|
| 2081 |
+
"node_modules/prelude-ls": {
|
| 2082 |
+
"version": "1.2.1",
|
| 2083 |
+
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
| 2084 |
+
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
| 2085 |
+
"dev": true,
|
| 2086 |
+
"license": "MIT",
|
| 2087 |
+
"engines": {
|
| 2088 |
+
"node": ">= 0.8.0"
|
| 2089 |
+
}
|
| 2090 |
+
},
|
| 2091 |
+
"node_modules/prismjs": {
|
| 2092 |
+
"version": "1.30.0",
|
| 2093 |
+
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
| 2094 |
+
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
| 2095 |
+
"license": "MIT",
|
| 2096 |
+
"engines": {
|
| 2097 |
+
"node": ">=6"
|
| 2098 |
+
}
|
| 2099 |
+
},
|
| 2100 |
+
"node_modules/punycode": {
|
| 2101 |
+
"version": "2.3.1",
|
| 2102 |
+
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
| 2103 |
+
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
| 2104 |
+
"dev": true,
|
| 2105 |
+
"license": "MIT",
|
| 2106 |
+
"engines": {
|
| 2107 |
+
"node": ">=6"
|
| 2108 |
+
}
|
| 2109 |
+
},
|
| 2110 |
+
"node_modules/react": {
|
| 2111 |
+
"version": "19.2.5",
|
| 2112 |
+
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
|
| 2113 |
+
"integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
|
| 2114 |
+
"license": "MIT",
|
| 2115 |
+
"engines": {
|
| 2116 |
+
"node": ">=0.10.0"
|
| 2117 |
+
}
|
| 2118 |
+
},
|
| 2119 |
+
"node_modules/react-dom": {
|
| 2120 |
+
"version": "19.2.5",
|
| 2121 |
+
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
|
| 2122 |
+
"integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
|
| 2123 |
+
"license": "MIT",
|
| 2124 |
+
"dependencies": {
|
| 2125 |
+
"scheduler": "^0.27.0"
|
| 2126 |
+
},
|
| 2127 |
+
"peerDependencies": {
|
| 2128 |
+
"react": "^19.2.5"
|
| 2129 |
+
}
|
| 2130 |
+
},
|
| 2131 |
+
"node_modules/rolldown": {
|
| 2132 |
+
"version": "1.0.0-rc.17",
|
| 2133 |
+
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
|
| 2134 |
+
"integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
|
| 2135 |
+
"dev": true,
|
| 2136 |
+
"license": "MIT",
|
| 2137 |
+
"dependencies": {
|
| 2138 |
+
"@oxc-project/types": "=0.127.0",
|
| 2139 |
+
"@rolldown/pluginutils": "1.0.0-rc.17"
|
| 2140 |
+
},
|
| 2141 |
+
"bin": {
|
| 2142 |
+
"rolldown": "bin/cli.mjs"
|
| 2143 |
+
},
|
| 2144 |
+
"engines": {
|
| 2145 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 2146 |
+
},
|
| 2147 |
+
"optionalDependencies": {
|
| 2148 |
+
"@rolldown/binding-android-arm64": "1.0.0-rc.17",
|
| 2149 |
+
"@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
|
| 2150 |
+
"@rolldown/binding-darwin-x64": "1.0.0-rc.17",
|
| 2151 |
+
"@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
|
| 2152 |
+
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
|
| 2153 |
+
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
|
| 2154 |
+
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
|
| 2155 |
+
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
|
| 2156 |
+
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
|
| 2157 |
+
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
|
| 2158 |
+
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
|
| 2159 |
+
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
|
| 2160 |
+
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
|
| 2161 |
+
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
|
| 2162 |
+
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
|
| 2163 |
+
}
|
| 2164 |
+
},
|
| 2165 |
+
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
|
| 2166 |
+
"version": "1.0.0-rc.17",
|
| 2167 |
+
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
|
| 2168 |
+
"integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
|
| 2169 |
+
"dev": true,
|
| 2170 |
+
"license": "MIT"
|
| 2171 |
+
},
|
| 2172 |
+
"node_modules/scheduler": {
|
| 2173 |
+
"version": "0.27.0",
|
| 2174 |
+
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
| 2175 |
+
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
| 2176 |
+
"license": "MIT"
|
| 2177 |
+
},
|
| 2178 |
+
"node_modules/semver": {
|
| 2179 |
+
"version": "6.3.1",
|
| 2180 |
+
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
| 2181 |
+
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
| 2182 |
+
"dev": true,
|
| 2183 |
+
"license": "ISC",
|
| 2184 |
+
"bin": {
|
| 2185 |
+
"semver": "bin/semver.js"
|
| 2186 |
+
}
|
| 2187 |
+
},
|
| 2188 |
+
"node_modules/shebang-command": {
|
| 2189 |
+
"version": "2.0.0",
|
| 2190 |
+
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
| 2191 |
+
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
| 2192 |
+
"dev": true,
|
| 2193 |
+
"license": "MIT",
|
| 2194 |
+
"dependencies": {
|
| 2195 |
+
"shebang-regex": "^3.0.0"
|
| 2196 |
+
},
|
| 2197 |
+
"engines": {
|
| 2198 |
+
"node": ">=8"
|
| 2199 |
+
}
|
| 2200 |
+
},
|
| 2201 |
+
"node_modules/shebang-regex": {
|
| 2202 |
+
"version": "3.0.0",
|
| 2203 |
+
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
| 2204 |
+
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
| 2205 |
+
"dev": true,
|
| 2206 |
+
"license": "MIT",
|
| 2207 |
+
"engines": {
|
| 2208 |
+
"node": ">=8"
|
| 2209 |
+
}
|
| 2210 |
+
},
|
| 2211 |
+
"node_modules/source-map-js": {
|
| 2212 |
+
"version": "1.2.1",
|
| 2213 |
+
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 2214 |
+
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 2215 |
+
"dev": true,
|
| 2216 |
+
"license": "BSD-3-Clause",
|
| 2217 |
+
"engines": {
|
| 2218 |
+
"node": ">=0.10.0"
|
| 2219 |
+
}
|
| 2220 |
+
},
|
| 2221 |
+
"node_modules/tinyglobby": {
|
| 2222 |
+
"version": "0.2.16",
|
| 2223 |
+
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
| 2224 |
+
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
| 2225 |
+
"dev": true,
|
| 2226 |
+
"license": "MIT",
|
| 2227 |
+
"dependencies": {
|
| 2228 |
+
"fdir": "^6.5.0",
|
| 2229 |
+
"picomatch": "^4.0.4"
|
| 2230 |
+
},
|
| 2231 |
+
"engines": {
|
| 2232 |
+
"node": ">=12.0.0"
|
| 2233 |
+
},
|
| 2234 |
+
"funding": {
|
| 2235 |
+
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 2236 |
+
}
|
| 2237 |
+
},
|
| 2238 |
+
"node_modules/tslib": {
|
| 2239 |
+
"version": "2.8.1",
|
| 2240 |
+
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2241 |
+
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 2242 |
+
"dev": true,
|
| 2243 |
+
"license": "0BSD",
|
| 2244 |
+
"optional": true
|
| 2245 |
+
},
|
| 2246 |
+
"node_modules/type-check": {
|
| 2247 |
+
"version": "0.4.0",
|
| 2248 |
+
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
| 2249 |
+
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
|
| 2250 |
+
"dev": true,
|
| 2251 |
+
"license": "MIT",
|
| 2252 |
+
"dependencies": {
|
| 2253 |
+
"prelude-ls": "^1.2.1"
|
| 2254 |
+
},
|
| 2255 |
+
"engines": {
|
| 2256 |
+
"node": ">= 0.8.0"
|
| 2257 |
+
}
|
| 2258 |
+
},
|
| 2259 |
+
"node_modules/update-browserslist-db": {
|
| 2260 |
+
"version": "1.2.3",
|
| 2261 |
+
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
| 2262 |
+
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
| 2263 |
+
"dev": true,
|
| 2264 |
+
"funding": [
|
| 2265 |
+
{
|
| 2266 |
+
"type": "opencollective",
|
| 2267 |
+
"url": "https://opencollective.com/browserslist"
|
| 2268 |
+
},
|
| 2269 |
+
{
|
| 2270 |
+
"type": "tidelift",
|
| 2271 |
+
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
| 2272 |
+
},
|
| 2273 |
+
{
|
| 2274 |
+
"type": "github",
|
| 2275 |
+
"url": "https://github.com/sponsors/ai"
|
| 2276 |
+
}
|
| 2277 |
+
],
|
| 2278 |
+
"license": "MIT",
|
| 2279 |
+
"dependencies": {
|
| 2280 |
+
"escalade": "^3.2.0",
|
| 2281 |
+
"picocolors": "^1.1.1"
|
| 2282 |
+
},
|
| 2283 |
+
"bin": {
|
| 2284 |
+
"update-browserslist-db": "cli.js"
|
| 2285 |
+
},
|
| 2286 |
+
"peerDependencies": {
|
| 2287 |
+
"browserslist": ">= 4.21.0"
|
| 2288 |
+
}
|
| 2289 |
+
},
|
| 2290 |
+
"node_modules/uri-js": {
|
| 2291 |
+
"version": "4.4.1",
|
| 2292 |
+
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
| 2293 |
+
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
| 2294 |
+
"dev": true,
|
| 2295 |
+
"license": "BSD-2-Clause",
|
| 2296 |
+
"dependencies": {
|
| 2297 |
+
"punycode": "^2.1.0"
|
| 2298 |
+
}
|
| 2299 |
+
},
|
| 2300 |
+
"node_modules/vite": {
|
| 2301 |
+
"version": "8.0.10",
|
| 2302 |
+
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
|
| 2303 |
+
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
|
| 2304 |
+
"dev": true,
|
| 2305 |
+
"license": "MIT",
|
| 2306 |
+
"dependencies": {
|
| 2307 |
+
"lightningcss": "^1.32.0",
|
| 2308 |
+
"picomatch": "^4.0.4",
|
| 2309 |
+
"postcss": "^8.5.10",
|
| 2310 |
+
"rolldown": "1.0.0-rc.17",
|
| 2311 |
+
"tinyglobby": "^0.2.16"
|
| 2312 |
+
},
|
| 2313 |
+
"bin": {
|
| 2314 |
+
"vite": "bin/vite.js"
|
| 2315 |
+
},
|
| 2316 |
+
"engines": {
|
| 2317 |
+
"node": "^20.19.0 || >=22.12.0"
|
| 2318 |
+
},
|
| 2319 |
+
"funding": {
|
| 2320 |
+
"url": "https://github.com/vitejs/vite?sponsor=1"
|
| 2321 |
+
},
|
| 2322 |
+
"optionalDependencies": {
|
| 2323 |
+
"fsevents": "~2.3.3"
|
| 2324 |
+
},
|
| 2325 |
+
"peerDependencies": {
|
| 2326 |
+
"@types/node": "^20.19.0 || >=22.12.0",
|
| 2327 |
+
"@vitejs/devtools": "^0.1.0",
|
| 2328 |
+
"esbuild": "^0.27.0 || ^0.28.0",
|
| 2329 |
+
"jiti": ">=1.21.0",
|
| 2330 |
+
"less": "^4.0.0",
|
| 2331 |
+
"sass": "^1.70.0",
|
| 2332 |
+
"sass-embedded": "^1.70.0",
|
| 2333 |
+
"stylus": ">=0.54.8",
|
| 2334 |
+
"sugarss": "^5.0.0",
|
| 2335 |
+
"terser": "^5.16.0",
|
| 2336 |
+
"tsx": "^4.8.1",
|
| 2337 |
+
"yaml": "^2.4.2"
|
| 2338 |
+
},
|
| 2339 |
+
"peerDependenciesMeta": {
|
| 2340 |
+
"@types/node": {
|
| 2341 |
+
"optional": true
|
| 2342 |
+
},
|
| 2343 |
+
"@vitejs/devtools": {
|
| 2344 |
+
"optional": true
|
| 2345 |
+
},
|
| 2346 |
+
"esbuild": {
|
| 2347 |
+
"optional": true
|
| 2348 |
+
},
|
| 2349 |
+
"jiti": {
|
| 2350 |
+
"optional": true
|
| 2351 |
+
},
|
| 2352 |
+
"less": {
|
| 2353 |
+
"optional": true
|
| 2354 |
+
},
|
| 2355 |
+
"sass": {
|
| 2356 |
+
"optional": true
|
| 2357 |
+
},
|
| 2358 |
+
"sass-embedded": {
|
| 2359 |
+
"optional": true
|
| 2360 |
+
},
|
| 2361 |
+
"stylus": {
|
| 2362 |
+
"optional": true
|
| 2363 |
+
},
|
| 2364 |
+
"sugarss": {
|
| 2365 |
+
"optional": true
|
| 2366 |
+
},
|
| 2367 |
+
"terser": {
|
| 2368 |
+
"optional": true
|
| 2369 |
+
},
|
| 2370 |
+
"tsx": {
|
| 2371 |
+
"optional": true
|
| 2372 |
+
},
|
| 2373 |
+
"yaml": {
|
| 2374 |
+
"optional": true
|
| 2375 |
+
}
|
| 2376 |
+
}
|
| 2377 |
+
},
|
| 2378 |
+
"node_modules/which": {
|
| 2379 |
+
"version": "2.0.2",
|
| 2380 |
+
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
| 2381 |
+
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
| 2382 |
+
"dev": true,
|
| 2383 |
+
"license": "ISC",
|
| 2384 |
+
"dependencies": {
|
| 2385 |
+
"isexe": "^2.0.0"
|
| 2386 |
+
},
|
| 2387 |
+
"bin": {
|
| 2388 |
+
"node-which": "bin/node-which"
|
| 2389 |
+
},
|
| 2390 |
+
"engines": {
|
| 2391 |
+
"node": ">= 8"
|
| 2392 |
+
}
|
| 2393 |
+
},
|
| 2394 |
+
"node_modules/word-wrap": {
|
| 2395 |
+
"version": "1.2.5",
|
| 2396 |
+
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
| 2397 |
+
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
| 2398 |
+
"dev": true,
|
| 2399 |
+
"license": "MIT",
|
| 2400 |
+
"engines": {
|
| 2401 |
+
"node": ">=0.10.0"
|
| 2402 |
+
}
|
| 2403 |
+
},
|
| 2404 |
+
"node_modules/yallist": {
|
| 2405 |
+
"version": "3.1.1",
|
| 2406 |
+
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
| 2407 |
+
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
| 2408 |
+
"dev": true,
|
| 2409 |
+
"license": "ISC"
|
| 2410 |
+
},
|
| 2411 |
+
"node_modules/yocto-queue": {
|
| 2412 |
+
"version": "0.1.0",
|
| 2413 |
+
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
| 2414 |
+
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
| 2415 |
+
"dev": true,
|
| 2416 |
+
"license": "MIT",
|
| 2417 |
+
"engines": {
|
| 2418 |
+
"node": ">=10"
|
| 2419 |
+
},
|
| 2420 |
+
"funding": {
|
| 2421 |
+
"url": "https://github.com/sponsors/sindresorhus"
|
| 2422 |
+
}
|
| 2423 |
+
},
|
| 2424 |
+
"node_modules/zod": {
|
| 2425 |
+
"version": "4.4.2",
|
| 2426 |
+
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.2.tgz",
|
| 2427 |
+
"integrity": "sha512-IynmDyxsEsb9RKzO3J9+4SxXnl2FTFSzNBaKKaMV6tsSk0rw9gYw9gs+JFCq/qk2LCZ78KDwyj+Z289TijSkUw==",
|
| 2428 |
+
"dev": true,
|
| 2429 |
+
"license": "MIT",
|
| 2430 |
+
"funding": {
|
| 2431 |
+
"url": "https://github.com/sponsors/colinhacks"
|
| 2432 |
+
}
|
| 2433 |
+
},
|
| 2434 |
+
"node_modules/zod-validation-error": {
|
| 2435 |
+
"version": "4.0.2",
|
| 2436 |
+
"resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
|
| 2437 |
+
"integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
|
| 2438 |
+
"dev": true,
|
| 2439 |
+
"license": "MIT",
|
| 2440 |
+
"engines": {
|
| 2441 |
+
"node": ">=18.0.0"
|
| 2442 |
+
},
|
| 2443 |
+
"peerDependencies": {
|
| 2444 |
+
"zod": "^3.25.0 || ^4.0.0"
|
| 2445 |
+
}
|
| 2446 |
+
}
|
| 2447 |
+
}
|
| 2448 |
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"lint": "eslint .",
|
| 10 |
+
"preview": "vite preview"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"lucide-react": "^1.14.0",
|
| 14 |
+
"prismjs": "^1.30.0",
|
| 15 |
+
"react": "^19.2.5",
|
| 16 |
+
"react-dom": "^19.2.5"
|
| 17 |
+
},
|
| 18 |
+
"devDependencies": {
|
| 19 |
+
"@eslint/js": "^10.0.1",
|
| 20 |
+
"@types/react": "^19.2.14",
|
| 21 |
+
"@types/react-dom": "^19.2.3",
|
| 22 |
+
"@vitejs/plugin-react": "^6.0.1",
|
| 23 |
+
"eslint": "^10.2.1",
|
| 24 |
+
"eslint-plugin-react-hooks": "^7.1.1",
|
| 25 |
+
"eslint-plugin-react-refresh": "^0.5.2",
|
| 26 |
+
"globals": "^17.5.0",
|
| 27 |
+
"vite": "^8.0.10"
|
| 28 |
+
}
|
| 29 |
+
}
|
|
|
|
|
|
@@ -1,305 +0,0 @@
|
|
| 1 |
-
/* =============================================================
|
| 2 |
-
MINDI Agent — Code Sandbox
|
| 3 |
-
Executes code in isolated environments (iframe for HTML/JS,
|
| 4 |
-
Pyodide for Python). Captures output, errors, and screenshots.
|
| 5 |
-
============================================================= */
|
| 6 |
-
|
| 7 |
-
const CodeSandbox = (() => {
|
| 8 |
-
'use strict';
|
| 9 |
-
|
| 10 |
-
// ── Pyodide loader ─────────────────────────────────────
|
| 11 |
-
let pyodideInstance = null;
|
| 12 |
-
let pyodideLoading = false;
|
| 13 |
-
|
| 14 |
-
async function loadPyodide() {
|
| 15 |
-
if (pyodideInstance) return pyodideInstance;
|
| 16 |
-
if (pyodideLoading) {
|
| 17 |
-
// Wait for existing load
|
| 18 |
-
while (pyodideLoading) await new Promise(r => setTimeout(r, 200));
|
| 19 |
-
return pyodideInstance;
|
| 20 |
-
}
|
| 21 |
-
pyodideLoading = true;
|
| 22 |
-
try {
|
| 23 |
-
// Load Pyodide from CDN
|
| 24 |
-
if (!window.loadPyodide) {
|
| 25 |
-
const script = document.createElement('script');
|
| 26 |
-
script.src = 'https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js';
|
| 27 |
-
document.head.appendChild(script);
|
| 28 |
-
await new Promise((res, rej) => { script.onload = res; script.onerror = rej; });
|
| 29 |
-
}
|
| 30 |
-
pyodideInstance = await window.loadPyodide();
|
| 31 |
-
console.log('[Sandbox] Pyodide loaded');
|
| 32 |
-
return pyodideInstance;
|
| 33 |
-
} finally {
|
| 34 |
-
pyodideLoading = false;
|
| 35 |
-
}
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
// ── HTML/JS execution in sandboxed iframe ──────────────
|
| 39 |
-
function executeHTML(code, containerEl) {
|
| 40 |
-
return new Promise((resolve) => {
|
| 41 |
-
const logs = [];
|
| 42 |
-
const errors = [];
|
| 43 |
-
const startTime = Date.now();
|
| 44 |
-
|
| 45 |
-
// Create sandboxed iframe
|
| 46 |
-
let iframe = containerEl.querySelector('.sandbox-iframe');
|
| 47 |
-
if (iframe) iframe.remove();
|
| 48 |
-
|
| 49 |
-
iframe = document.createElement('iframe');
|
| 50 |
-
iframe.className = 'sandbox-iframe';
|
| 51 |
-
iframe.sandbox = 'allow-scripts allow-modals';
|
| 52 |
-
iframe.style.cssText = 'width:100%;height:100%;border:none;background:#fff;border-radius:8px;';
|
| 53 |
-
containerEl.appendChild(iframe);
|
| 54 |
-
|
| 55 |
-
// Inject console capture + error handling into the code
|
| 56 |
-
const wrappedCode = `
|
| 57 |
-
<!DOCTYPE html>
|
| 58 |
-
<html>
|
| 59 |
-
<head>
|
| 60 |
-
<meta charset="UTF-8">
|
| 61 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 62 |
-
<script>
|
| 63 |
-
// Override console to send messages to parent
|
| 64 |
-
const _origLog = console.log;
|
| 65 |
-
const _origErr = console.error;
|
| 66 |
-
const _origWarn = console.warn;
|
| 67 |
-
|
| 68 |
-
function _send(type, args) {
|
| 69 |
-
try {
|
| 70 |
-
parent.postMessage({
|
| 71 |
-
type: 'sandbox-' + type,
|
| 72 |
-
data: Array.from(args).map(a => {
|
| 73 |
-
try { return typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a); }
|
| 74 |
-
catch { return String(a); }
|
| 75 |
-
}).join(' ')
|
| 76 |
-
}, '*');
|
| 77 |
-
} catch {}
|
| 78 |
-
}
|
| 79 |
-
|
| 80 |
-
console.log = function() { _origLog.apply(console, arguments); _send('log', arguments); };
|
| 81 |
-
console.error = function() { _origErr.apply(console, arguments); _send('error', arguments); };
|
| 82 |
-
console.warn = function() { _origWarn.apply(console, arguments); _send('warn', arguments); };
|
| 83 |
-
|
| 84 |
-
window.onerror = function(msg, src, line, col, err) {
|
| 85 |
-
_send('error', [msg + ' (line ' + line + ')']);
|
| 86 |
-
return true;
|
| 87 |
-
};
|
| 88 |
-
window.addEventListener('unhandledrejection', function(e) {
|
| 89 |
-
_send('error', ['Unhandled promise rejection: ' + e.reason]);
|
| 90 |
-
});
|
| 91 |
-
|
| 92 |
-
// Signal ready after load
|
| 93 |
-
window.addEventListener('load', function() {
|
| 94 |
-
_send('ready', ['Page loaded in ' + (performance.now()|0) + 'ms']);
|
| 95 |
-
});
|
| 96 |
-
</script>
|
| 97 |
-
</head>
|
| 98 |
-
<body>
|
| 99 |
-
${code.includes('<body') ? code.replace(/.*<body[^>]*>/is, '').replace(/<\/body>.*/is, '') : (code.includes('<html') ? '' : code)}
|
| 100 |
-
</body>
|
| 101 |
-
</html>`;
|
| 102 |
-
|
| 103 |
-
// If code is a full HTML document, use it directly with console injection
|
| 104 |
-
const finalCode = code.includes('<!DOCTYPE') || code.includes('<html')
|
| 105 |
-
? code.replace('<head>', `<head>
|
| 106 |
-
<script>
|
| 107 |
-
const _origLog = console.log;
|
| 108 |
-
const _origErr = console.error;
|
| 109 |
-
function _send(t, a) { try { parent.postMessage({type:'sandbox-'+t,data:Array.from(a).map(x=>{try{return typeof x==='object'?JSON.stringify(x,null,2):String(x)}catch{return String(x)}}).join(' ')},'*'); } catch{} }
|
| 110 |
-
console.log = function() { _origLog.apply(console,arguments); _send('log',arguments); };
|
| 111 |
-
console.error = function() { _origErr.apply(console,arguments); _send('error',arguments); };
|
| 112 |
-
window.onerror = function(m,s,l) { _send('error',[m+' (line '+l+')']); return true; };
|
| 113 |
-
window.addEventListener('load', function() { _send('ready', ['loaded']); });
|
| 114 |
-
</script>`)
|
| 115 |
-
: wrappedCode;
|
| 116 |
-
|
| 117 |
-
// Listen for messages from iframe
|
| 118 |
-
const handler = (event) => {
|
| 119 |
-
if (!event.data || !event.data.type) return;
|
| 120 |
-
const { type, data } = event.data;
|
| 121 |
-
if (type === 'sandbox-log') logs.push(data);
|
| 122 |
-
else if (type === 'sandbox-error') errors.push(data);
|
| 123 |
-
else if (type === 'sandbox-warn') logs.push(`[warn] ${data}`);
|
| 124 |
-
else if (type === 'sandbox-ready') {
|
| 125 |
-
// Give a moment for rendering, then resolve
|
| 126 |
-
setTimeout(() => {
|
| 127 |
-
window.removeEventListener('message', handler);
|
| 128 |
-
resolve({
|
| 129 |
-
success: errors.length === 0,
|
| 130 |
-
logs,
|
| 131 |
-
errors,
|
| 132 |
-
duration: Date.now() - startTime,
|
| 133 |
-
iframe,
|
| 134 |
-
});
|
| 135 |
-
}, 500);
|
| 136 |
-
}
|
| 137 |
-
};
|
| 138 |
-
window.addEventListener('message', handler);
|
| 139 |
-
|
| 140 |
-
// Set content
|
| 141 |
-
iframe.srcdoc = finalCode;
|
| 142 |
-
|
| 143 |
-
// Timeout fallback (10 seconds)
|
| 144 |
-
setTimeout(() => {
|
| 145 |
-
window.removeEventListener('message', handler);
|
| 146 |
-
resolve({
|
| 147 |
-
success: errors.length === 0,
|
| 148 |
-
logs,
|
| 149 |
-
errors: errors.length ? errors : ['Timeout: page did not signal ready within 10s'],
|
| 150 |
-
duration: Date.now() - startTime,
|
| 151 |
-
iframe,
|
| 152 |
-
});
|
| 153 |
-
}, 10000);
|
| 154 |
-
});
|
| 155 |
-
}
|
| 156 |
-
|
| 157 |
-
// ── JavaScript execution ───────────────────────────────
|
| 158 |
-
function executeJS(code) {
|
| 159 |
-
return new Promise((resolve) => {
|
| 160 |
-
const logs = [];
|
| 161 |
-
const errors = [];
|
| 162 |
-
const startTime = Date.now();
|
| 163 |
-
|
| 164 |
-
// Create a sandboxed execution context
|
| 165 |
-
const origLog = console.log;
|
| 166 |
-
const origErr = console.error;
|
| 167 |
-
|
| 168 |
-
console.log = (...args) => {
|
| 169 |
-
logs.push(args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '));
|
| 170 |
-
};
|
| 171 |
-
console.error = (...args) => {
|
| 172 |
-
errors.push(args.map(String).join(' '));
|
| 173 |
-
};
|
| 174 |
-
|
| 175 |
-
try {
|
| 176 |
-
// Execute in indirect eval (global scope)
|
| 177 |
-
const result = (0, eval)(code);
|
| 178 |
-
if (result !== undefined) logs.push(String(result));
|
| 179 |
-
} catch (e) {
|
| 180 |
-
errors.push(`${e.name}: ${e.message}`);
|
| 181 |
-
} finally {
|
| 182 |
-
console.log = origLog;
|
| 183 |
-
console.error = origErr;
|
| 184 |
-
}
|
| 185 |
-
|
| 186 |
-
resolve({
|
| 187 |
-
success: errors.length === 0,
|
| 188 |
-
logs,
|
| 189 |
-
errors,
|
| 190 |
-
duration: Date.now() - startTime,
|
| 191 |
-
});
|
| 192 |
-
});
|
| 193 |
-
}
|
| 194 |
-
|
| 195 |
-
// ── Python execution via Pyodide ───────────────────────
|
| 196 |
-
async function executePython(code) {
|
| 197 |
-
const logs = [];
|
| 198 |
-
const errors = [];
|
| 199 |
-
const startTime = Date.now();
|
| 200 |
-
|
| 201 |
-
try {
|
| 202 |
-
const pyodide = await loadPyodide();
|
| 203 |
-
|
| 204 |
-
// Redirect stdout/stderr
|
| 205 |
-
pyodide.runPython(`
|
| 206 |
-
import sys, io
|
| 207 |
-
sys.stdout = io.StringIO()
|
| 208 |
-
sys.stderr = io.StringIO()
|
| 209 |
-
`);
|
| 210 |
-
|
| 211 |
-
try {
|
| 212 |
-
await pyodide.runPythonAsync(code);
|
| 213 |
-
} catch (e) {
|
| 214 |
-
errors.push(String(e));
|
| 215 |
-
}
|
| 216 |
-
|
| 217 |
-
// Capture output
|
| 218 |
-
const stdout = pyodide.runPython('sys.stdout.getvalue()');
|
| 219 |
-
const stderr = pyodide.runPython('sys.stderr.getvalue()');
|
| 220 |
-
if (stdout) logs.push(stdout);
|
| 221 |
-
if (stderr) errors.push(stderr);
|
| 222 |
-
|
| 223 |
-
// Reset streams
|
| 224 |
-
pyodide.runPython(`
|
| 225 |
-
sys.stdout = sys.__stdout__
|
| 226 |
-
sys.stderr = sys.__stderr__
|
| 227 |
-
`);
|
| 228 |
-
} catch (e) {
|
| 229 |
-
errors.push(`Pyodide error: ${e.message}`);
|
| 230 |
-
}
|
| 231 |
-
|
| 232 |
-
return {
|
| 233 |
-
success: errors.length === 0,
|
| 234 |
-
logs,
|
| 235 |
-
errors,
|
| 236 |
-
duration: Date.now() - startTime,
|
| 237 |
-
};
|
| 238 |
-
}
|
| 239 |
-
|
| 240 |
-
// ── Screenshot capture ─────────────────────────────────
|
| 241 |
-
async function captureScreenshot(iframe) {
|
| 242 |
-
try {
|
| 243 |
-
if (!window.html2canvas) {
|
| 244 |
-
const script = document.createElement('script');
|
| 245 |
-
script.src = 'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js';
|
| 246 |
-
document.head.appendChild(script);
|
| 247 |
-
await new Promise((res, rej) => { script.onload = res; script.onerror = rej; });
|
| 248 |
-
}
|
| 249 |
-
const canvas = await html2canvas(iframe, { useCORS: true, scale: 1 });
|
| 250 |
-
return canvas.toDataURL('image/png');
|
| 251 |
-
} catch (e) {
|
| 252 |
-
console.warn('[Sandbox] Screenshot failed:', e);
|
| 253 |
-
return null;
|
| 254 |
-
}
|
| 255 |
-
}
|
| 256 |
-
|
| 257 |
-
// ── Language detection ─────────────────────────────────
|
| 258 |
-
function detectLanguage(code) {
|
| 259 |
-
const t = code.trim();
|
| 260 |
-
if (/^<!doctype|^<html|^<div|^<section|^<main/i.test(t)) return 'html';
|
| 261 |
-
if (/^<style|^[.#]?\w+\s*\{/.test(t)) return 'css';
|
| 262 |
-
if (/^(import|from|def |class |print\(|if __name__)/m.test(t)) return 'python';
|
| 263 |
-
if (/^(import |export |const |function |class |let |var |=>)/m.test(t)) return 'javascript';
|
| 264 |
-
if (/^(package |func |type |import ")/m.test(t)) return 'go';
|
| 265 |
-
if (/^(use |fn |let |struct |impl |pub )/m.test(t)) return 'rust';
|
| 266 |
-
return 'javascript'; // default
|
| 267 |
-
}
|
| 268 |
-
|
| 269 |
-
// ── Main execute function ──────────────────────────────
|
| 270 |
-
async function execute(code, language = null, containerEl = null) {
|
| 271 |
-
const lang = language || detectLanguage(code);
|
| 272 |
-
|
| 273 |
-
switch (lang) {
|
| 274 |
-
case 'html':
|
| 275 |
-
case 'markup':
|
| 276 |
-
case 'css':
|
| 277 |
-
if (!containerEl) {
|
| 278 |
-
return { success: false, errors: ['No container for HTML preview'], logs: [], duration: 0 };
|
| 279 |
-
}
|
| 280 |
-
return executeHTML(code, containerEl);
|
| 281 |
-
|
| 282 |
-
case 'python':
|
| 283 |
-
return executePython(code);
|
| 284 |
-
|
| 285 |
-
case 'javascript':
|
| 286 |
-
case 'typescript':
|
| 287 |
-
case 'js':
|
| 288 |
-
case 'ts':
|
| 289 |
-
return executeJS(code);
|
| 290 |
-
|
| 291 |
-
default:
|
| 292 |
-
return {
|
| 293 |
-
success: false,
|
| 294 |
-
logs: [],
|
| 295 |
-
errors: [`Language "${lang}" execution not supported in browser. Supported: HTML, JavaScript, Python.`],
|
| 296 |
-
duration: 0,
|
| 297 |
-
};
|
| 298 |
-
}
|
| 299 |
-
}
|
| 300 |
-
|
| 301 |
-
return { execute, executeHTML, executeJS, executePython, detectLanguage, captureScreenshot };
|
| 302 |
-
})();
|
| 303 |
-
|
| 304 |
-
// Export for module usage
|
| 305 |
-
if (typeof module !== 'undefined') module.exports = CodeSandbox;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ============ APP SHELL ============ */
|
| 2 |
+
.app-shell {
|
| 3 |
+
display: grid;
|
| 4 |
+
grid-template-columns: var(--sidebar-w) 1fr 420px;
|
| 5 |
+
height: 100vh;
|
| 6 |
+
position: relative;
|
| 7 |
+
z-index: 1;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
/* Ambient background */
|
| 11 |
+
.ambient { position: fixed; inset: 0; pointer-events: none; z-index: 0; overflow: hidden; }
|
| 12 |
+
.grid-pattern {
|
| 13 |
+
position: absolute; inset: 0;
|
| 14 |
+
background-image: linear-gradient(rgba(255,255,255,.02) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.02) 1px, transparent 1px);
|
| 15 |
+
background-size: 64px 64px;
|
| 16 |
+
mask-image: radial-gradient(ellipse at center, #000 0%, transparent 80%);
|
| 17 |
+
}
|
| 18 |
+
.blob { position: absolute; width: 500px; height: 500px; border-radius: 50%; filter: blur(120px); opacity: .25; }
|
| 19 |
+
.blob--purple { background: radial-gradient(circle, var(--purple), transparent 70%); top: -150px; left: -100px; animation: drift-1 26s ease-in-out infinite alternate; }
|
| 20 |
+
.blob--blue { background: radial-gradient(circle, var(--blue), transparent 70%); bottom: -150px; right: -100px; animation: drift-2 30s ease-in-out infinite alternate; }
|
| 21 |
+
|
| 22 |
+
/* ============ MAIN AREA ============ */
|
| 23 |
+
.main-area {
|
| 24 |
+
display: flex;
|
| 25 |
+
flex-direction: column;
|
| 26 |
+
height: 100vh;
|
| 27 |
+
min-width: 0;
|
| 28 |
+
position: relative;
|
| 29 |
+
z-index: 1;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
/* ============ SIDEBAR ============ */
|
| 33 |
+
.sidebar {
|
| 34 |
+
display: flex;
|
| 35 |
+
flex-direction: column;
|
| 36 |
+
height: 100vh;
|
| 37 |
+
background: rgba(17, 17, 32, .85);
|
| 38 |
+
border-right: 1px solid var(--border);
|
| 39 |
+
backdrop-filter: blur(20px);
|
| 40 |
+
z-index: 10;
|
| 41 |
+
position: relative;
|
| 42 |
+
}
|
| 43 |
+
.sidebar-brand {
|
| 44 |
+
padding: 14px 16px;
|
| 45 |
+
display: flex;
|
| 46 |
+
align-items: center;
|
| 47 |
+
gap: 10px;
|
| 48 |
+
border-bottom: 1px solid var(--border);
|
| 49 |
+
cursor: pointer;
|
| 50 |
+
transition: background .2s;
|
| 51 |
+
}
|
| 52 |
+
.sidebar-brand:hover { background: var(--hover); }
|
| 53 |
+
.brand-icon {
|
| 54 |
+
width: 32px; height: 32px;
|
| 55 |
+
background: var(--grad);
|
| 56 |
+
border-radius: var(--r-sm);
|
| 57 |
+
display: grid; place-items: center;
|
| 58 |
+
box-shadow: 0 0 20px rgba(124, 58, 237, .3);
|
| 59 |
+
flex-shrink: 0;
|
| 60 |
+
}
|
| 61 |
+
.brand-icon svg { width: 20px; height: 20px; }
|
| 62 |
+
.brand-info { min-width: 0; }
|
| 63 |
+
.brand-name { font-size: 13px; font-weight: 700; letter-spacing: -.01em; }
|
| 64 |
+
.brand-sub { font-size: 9px; font-weight: 500; color: var(--text-mute); font-family: var(--mono); letter-spacing: .04em; }
|
| 65 |
+
|
| 66 |
+
/* File tree */
|
| 67 |
+
.sidebar-section { padding: 12px 0; }
|
| 68 |
+
.sidebar-section-title {
|
| 69 |
+
padding: 0 16px 8px;
|
| 70 |
+
font-size: 10px;
|
| 71 |
+
font-weight: 600;
|
| 72 |
+
text-transform: uppercase;
|
| 73 |
+
letter-spacing: .12em;
|
| 74 |
+
color: var(--text-dim);
|
| 75 |
+
}
|
| 76 |
+
.file-tree { flex: 1; overflow-y: auto; padding: 0 8px; }
|
| 77 |
+
.file-item {
|
| 78 |
+
display: flex;
|
| 79 |
+
align-items: center;
|
| 80 |
+
gap: 8px;
|
| 81 |
+
padding: 6px 10px;
|
| 82 |
+
border-radius: var(--r-sm);
|
| 83 |
+
font-size: 12px;
|
| 84 |
+
color: var(--text-2);
|
| 85 |
+
cursor: pointer;
|
| 86 |
+
transition: background .15s, color .15s;
|
| 87 |
+
animation: fadeInLeft .3s var(--ease) both;
|
| 88 |
+
width: 100%;
|
| 89 |
+
text-align: left;
|
| 90 |
+
}
|
| 91 |
+
.file-item:hover { background: var(--hover); color: var(--text); }
|
| 92 |
+
.file-item.active { background: rgba(124,58,237,.12); color: #fff; border: 1px solid rgba(124,58,237,.2); }
|
| 93 |
+
.file-icon { font-size: 14px; flex-shrink: 0; }
|
| 94 |
+
.file-name { font-family: var(--mono); font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 95 |
+
|
| 96 |
+
/* Agent steps */
|
| 97 |
+
.agent-steps { padding: 4px 8px; display: flex; flex-direction: column; gap: 4px; max-height: 280px; overflow-y: auto; }
|
| 98 |
+
.agent-step {
|
| 99 |
+
display: flex; align-items: center; gap: 8px;
|
| 100 |
+
padding: 7px 10px; border-radius: var(--r-sm);
|
| 101 |
+
font-size: 11px; color: var(--text-2);
|
| 102 |
+
animation: fadeIn .25s var(--ease) both;
|
| 103 |
+
background: rgba(255,255,255,.02);
|
| 104 |
+
}
|
| 105 |
+
.step-icon { width: 18px; height: 18px; display: grid; place-items: center; border-radius: 50%; font-size: 10px; flex-shrink: 0; }
|
| 106 |
+
.step-icon.running { background: rgba(124,58,237,.2); color: var(--purple-light); animation: pulse 1.5s ease infinite; }
|
| 107 |
+
.step-icon.success { background: rgba(16,185,129,.15); color: var(--ok); }
|
| 108 |
+
.step-icon.failed { background: rgba(239,68,68,.15); color: var(--err); }
|
| 109 |
+
.step-detail { font-family: var(--mono); font-size: 10px; color: var(--text-mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
| 110 |
+
|
| 111 |
+
/* Status */
|
| 112 |
+
.sidebar-footer {
|
| 113 |
+
padding: 10px 14px;
|
| 114 |
+
border-top: 1px solid var(--border);
|
| 115 |
+
display: flex; align-items: center; gap: 8px;
|
| 116 |
+
font-size: 10px; color: var(--text-mute); font-family: var(--mono);
|
| 117 |
+
}
|
| 118 |
+
.status-dot {
|
| 119 |
+
width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
|
| 120 |
+
}
|
| 121 |
+
.status-dot.online { background: var(--ok); box-shadow: 0 0 8px var(--ok); }
|
| 122 |
+
.status-dot.demo { background: var(--warn); box-shadow: 0 0 8px var(--warn); }
|
| 123 |
+
.status-dot.connecting { background: var(--text-dim); }
|
| 124 |
+
|
| 125 |
+
/* ============ EDITOR ============ */
|
| 126 |
+
.editor {
|
| 127 |
+
flex: 1;
|
| 128 |
+
display: flex;
|
| 129 |
+
flex-direction: column;
|
| 130 |
+
min-height: 0;
|
| 131 |
+
background: rgba(10, 10, 18, .6);
|
| 132 |
+
}
|
| 133 |
+
.editor-tabs {
|
| 134 |
+
display: flex;
|
| 135 |
+
align-items: center;
|
| 136 |
+
height: 38px;
|
| 137 |
+
border-bottom: 1px solid var(--border);
|
| 138 |
+
padding: 0 8px;
|
| 139 |
+
gap: 2px;
|
| 140 |
+
overflow-x: auto;
|
| 141 |
+
flex-shrink: 0;
|
| 142 |
+
background: rgba(17, 17, 32, .5);
|
| 143 |
+
}
|
| 144 |
+
.editor-tab {
|
| 145 |
+
display: flex; align-items: center; gap: 6px;
|
| 146 |
+
padding: 0 14px; height: 100%;
|
| 147 |
+
font-size: 11.5px; font-family: var(--mono);
|
| 148 |
+
color: var(--text-mute);
|
| 149 |
+
border-bottom: 2px solid transparent;
|
| 150 |
+
white-space: nowrap;
|
| 151 |
+
transition: color .15s, border-color .15s;
|
| 152 |
+
cursor: pointer;
|
| 153 |
+
}
|
| 154 |
+
.editor-tab:hover { color: var(--text-2); }
|
| 155 |
+
.editor-tab.active { color: var(--text); border-bottom-color: var(--purple); }
|
| 156 |
+
.editor-tab .tab-icon { font-size: 12px; }
|
| 157 |
+
|
| 158 |
+
.editor-content {
|
| 159 |
+
flex: 1;
|
| 160 |
+
overflow: auto;
|
| 161 |
+
padding: 16px 0;
|
| 162 |
+
font-family: var(--mono);
|
| 163 |
+
font-size: 13px;
|
| 164 |
+
line-height: 1.7;
|
| 165 |
+
counter-reset: line;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.code-line {
|
| 169 |
+
display: flex;
|
| 170 |
+
padding: 0 16px;
|
| 171 |
+
min-height: 22px;
|
| 172 |
+
animation: line-appear .2s var(--ease) both;
|
| 173 |
+
}
|
| 174 |
+
.code-line:hover { background: rgba(255,255,255,.02); }
|
| 175 |
+
.line-number {
|
| 176 |
+
width: 48px;
|
| 177 |
+
text-align: right;
|
| 178 |
+
padding-right: 16px;
|
| 179 |
+
color: var(--text-dim);
|
| 180 |
+
font-size: 12px;
|
| 181 |
+
user-select: none;
|
| 182 |
+
flex-shrink: 0;
|
| 183 |
+
}
|
| 184 |
+
.line-content {
|
| 185 |
+
flex: 1;
|
| 186 |
+
white-space: pre;
|
| 187 |
+
overflow-x: auto;
|
| 188 |
+
color: var(--text-2);
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/* Welcome state */
|
| 192 |
+
.editor-welcome {
|
| 193 |
+
flex: 1;
|
| 194 |
+
display: flex;
|
| 195 |
+
flex-direction: column;
|
| 196 |
+
align-items: center;
|
| 197 |
+
justify-content: center;
|
| 198 |
+
text-align: center;
|
| 199 |
+
padding: 40px;
|
| 200 |
+
gap: 16px;
|
| 201 |
+
}
|
| 202 |
+
.welcome-logo {
|
| 203 |
+
width: 80px; height: 80px;
|
| 204 |
+
background: var(--grad);
|
| 205 |
+
border-radius: 20px;
|
| 206 |
+
display: grid; place-items: center;
|
| 207 |
+
box-shadow: 0 16px 48px rgba(124,58,237,.3);
|
| 208 |
+
animation: float 5s ease-in-out infinite;
|
| 209 |
+
}
|
| 210 |
+
.welcome-logo svg { width: 44px; height: 44px; color: #fff; }
|
| 211 |
+
.welcome-title { font-size: 28px; font-weight: 700; letter-spacing: -.02em; }
|
| 212 |
+
.welcome-sub { font-size: 14px; color: var(--text-2); max-width: 420px; line-height: 1.6; }
|
| 213 |
+
|
| 214 |
+
/* Generating overlay */
|
| 215 |
+
.generating-indicator {
|
| 216 |
+
display: flex; align-items: center; gap: 8px;
|
| 217 |
+
padding: 8px 16px;
|
| 218 |
+
background: rgba(124,58,237,.1);
|
| 219 |
+
border-bottom: 1px solid rgba(124,58,237,.2);
|
| 220 |
+
font-size: 12px; color: var(--purple-light);
|
| 221 |
+
font-family: var(--mono);
|
| 222 |
+
}
|
| 223 |
+
.gen-spinner { width: 14px; height: 14px; border: 2px solid rgba(124,58,237,.3); border-top-color: var(--purple); border-radius: 50%; animation: spin .8s linear infinite; }
|
| 224 |
+
|
| 225 |
+
/* ============ PREVIEW ============ */
|
| 226 |
+
.preview-panel {
|
| 227 |
+
display: flex;
|
| 228 |
+
flex-direction: column;
|
| 229 |
+
height: 100vh;
|
| 230 |
+
background: rgba(14, 14, 22, .9);
|
| 231 |
+
border-left: 1px solid var(--border);
|
| 232 |
+
backdrop-filter: blur(20px);
|
| 233 |
+
z-index: 5;
|
| 234 |
+
position: relative;
|
| 235 |
+
}
|
| 236 |
+
.preview-header {
|
| 237 |
+
height: 38px;
|
| 238 |
+
display: flex; align-items: center; justify-content: space-between;
|
| 239 |
+
padding: 0 14px;
|
| 240 |
+
border-bottom: 1px solid var(--border);
|
| 241 |
+
flex-shrink: 0;
|
| 242 |
+
}
|
| 243 |
+
.preview-title {
|
| 244 |
+
font-size: 11px; font-weight: 600; text-transform: uppercase;
|
| 245 |
+
letter-spacing: .1em; color: var(--text-mute);
|
| 246 |
+
display: flex; align-items: center; gap: 8px;
|
| 247 |
+
}
|
| 248 |
+
.preview-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 8px var(--ok); }
|
| 249 |
+
.preview-actions { display: flex; gap: 4px; }
|
| 250 |
+
.preview-btn {
|
| 251 |
+
width: 28px; height: 28px;
|
| 252 |
+
display: grid; place-items: center;
|
| 253 |
+
border-radius: var(--r-xs);
|
| 254 |
+
color: var(--text-mute);
|
| 255 |
+
transition: background .15s, color .15s;
|
| 256 |
+
}
|
| 257 |
+
.preview-btn:hover { background: var(--hover); color: var(--text); }
|
| 258 |
+
.preview-btn svg { width: 14px; height: 14px; }
|
| 259 |
+
|
| 260 |
+
.preview-frame-wrap { flex: 1; position: relative; background: #fff; }
|
| 261 |
+
.preview-frame { width: 100%; height: 100%; border: 0; background: #fff; }
|
| 262 |
+
.preview-empty {
|
| 263 |
+
position: absolute; inset: 0;
|
| 264 |
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
| 265 |
+
background: var(--bg-1); gap: 12px; text-align: center;
|
| 266 |
+
}
|
| 267 |
+
.preview-empty-icon {
|
| 268 |
+
width: 48px; height: 48px; border-radius: var(--r-md);
|
| 269 |
+
background: var(--grad-soft);
|
| 270 |
+
border: 1px solid rgba(124,58,237,.2);
|
| 271 |
+
display: grid; place-items: center;
|
| 272 |
+
font-size: 20px; color: var(--purple-light);
|
| 273 |
+
}
|
| 274 |
+
.preview-empty p { font-size: 12px; color: var(--text-mute); }
|
| 275 |
+
.preview-empty p:last-child { font-size: 11px; color: var(--text-dim); max-width: 200px; }
|
| 276 |
+
|
| 277 |
+
/* Console */
|
| 278 |
+
.console-panel {
|
| 279 |
+
height: 120px;
|
| 280 |
+
border-top: 1px solid var(--border);
|
| 281 |
+
display: flex; flex-direction: column;
|
| 282 |
+
flex-shrink: 0;
|
| 283 |
+
}
|
| 284 |
+
.console-header {
|
| 285 |
+
padding: 6px 14px;
|
| 286 |
+
font-size: 10px; font-weight: 600; text-transform: uppercase;
|
| 287 |
+
letter-spacing: .1em; color: var(--text-dim);
|
| 288 |
+
background: rgba(255,255,255,.02);
|
| 289 |
+
border-bottom: 1px solid var(--border);
|
| 290 |
+
}
|
| 291 |
+
.console-body {
|
| 292 |
+
flex: 1; overflow-y: auto; padding: 8px 14px;
|
| 293 |
+
font-family: var(--mono); font-size: 11px; line-height: 1.6;
|
| 294 |
+
color: var(--ok);
|
| 295 |
+
}
|
| 296 |
+
.console-body .error { color: var(--err); }
|
| 297 |
+
|
| 298 |
+
/* ============ PROMPT BAR ============ */
|
| 299 |
+
.prompt-bar {
|
| 300 |
+
padding: 12px 16px 16px;
|
| 301 |
+
flex-shrink: 0;
|
| 302 |
+
border-top: 1px solid var(--border);
|
| 303 |
+
background: rgba(17,17,32,.7);
|
| 304 |
+
backdrop-filter: blur(16px);
|
| 305 |
+
}
|
| 306 |
+
.prompt-input-wrap {
|
| 307 |
+
display: flex; align-items: flex-end; gap: 8px;
|
| 308 |
+
background: rgba(20,20,36,.9);
|
| 309 |
+
border: 1px solid var(--border-2);
|
| 310 |
+
border-radius: var(--r-lg);
|
| 311 |
+
padding: 10px 14px;
|
| 312 |
+
transition: border-color .2s, box-shadow .2s;
|
| 313 |
+
}
|
| 314 |
+
.prompt-input-wrap:focus-within {
|
| 315 |
+
border-color: rgba(124,58,237,.5);
|
| 316 |
+
box-shadow: 0 0 0 3px rgba(124,58,237,.1), 0 20px 50px -20px rgba(124,58,237,.4);
|
| 317 |
+
}
|
| 318 |
+
.prompt-input {
|
| 319 |
+
flex: 1; font-size: 13.5px; line-height: 1.5;
|
| 320 |
+
color: var(--text); background: transparent;
|
| 321 |
+
resize: none; max-height: 120px; min-height: 20px;
|
| 322 |
+
font-family: var(--sans);
|
| 323 |
+
}
|
| 324 |
+
.prompt-input::placeholder { color: var(--text-mute); }
|
| 325 |
+
.prompt-send {
|
| 326 |
+
width: 34px; height: 34px;
|
| 327 |
+
background: var(--grad);
|
| 328 |
+
border-radius: var(--r-sm);
|
| 329 |
+
display: grid; place-items: center;
|
| 330 |
+
color: #fff; flex-shrink: 0;
|
| 331 |
+
transition: filter .15s, transform .1s, box-shadow .2s;
|
| 332 |
+
box-shadow: 0 4px 14px -4px rgba(124,58,237,.4);
|
| 333 |
+
}
|
| 334 |
+
.prompt-send:hover:not(:disabled) { filter: brightness(1.1); transform: translateY(-1px); }
|
| 335 |
+
.prompt-send:disabled { opacity: .3; cursor: not-allowed; }
|
| 336 |
+
.prompt-send svg { width: 16px; height: 16px; }
|
| 337 |
+
.prompt-stop {
|
| 338 |
+
width: 34px; height: 34px;
|
| 339 |
+
background: var(--err);
|
| 340 |
+
border-radius: var(--r-sm);
|
| 341 |
+
display: grid; place-items: center;
|
| 342 |
+
color: #fff; flex-shrink: 0;
|
| 343 |
+
animation: pulse 1.5s ease infinite;
|
| 344 |
+
}
|
| 345 |
+
.prompt-stop svg { width: 14px; height: 14px; }
|
| 346 |
+
.prompt-footer {
|
| 347 |
+
display: flex; align-items: center; justify-content: space-between;
|
| 348 |
+
padding: 8px 4px 0;
|
| 349 |
+
font-size: 10px; color: var(--text-dim); font-family: var(--mono);
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
/* ============ PLAN MODAL ============ */
|
| 353 |
+
.modal-overlay {
|
| 354 |
+
position: fixed; inset: 0;
|
| 355 |
+
background: rgba(0,0,0,.6);
|
| 356 |
+
backdrop-filter: blur(8px);
|
| 357 |
+
z-index: 100;
|
| 358 |
+
display: grid; place-items: center;
|
| 359 |
+
animation: fadeIn .2s var(--ease);
|
| 360 |
+
}
|
| 361 |
+
.modal-card {
|
| 362 |
+
background: var(--panel);
|
| 363 |
+
border: 1px solid var(--border-2);
|
| 364 |
+
border-radius: var(--r-xl);
|
| 365 |
+
width: min(480px, 90%);
|
| 366 |
+
box-shadow: 0 30px 80px rgba(0,0,0,.5);
|
| 367 |
+
animation: pop-in .25s var(--ease);
|
| 368 |
+
overflow: hidden;
|
| 369 |
+
}
|
| 370 |
+
.modal-header {
|
| 371 |
+
display: flex; align-items: center; justify-content: space-between;
|
| 372 |
+
padding: 16px 20px;
|
| 373 |
+
border-bottom: 1px solid var(--border);
|
| 374 |
+
}
|
| 375 |
+
.modal-header h3 { font-size: 15px; font-weight: 600; }
|
| 376 |
+
.modal-close {
|
| 377 |
+
width: 32px; height: 32px; display: grid; place-items: center;
|
| 378 |
+
border-radius: var(--r-sm); color: var(--text-mute);
|
| 379 |
+
transition: background .15s;
|
| 380 |
+
}
|
| 381 |
+
.modal-close:hover { background: var(--hover); }
|
| 382 |
+
.modal-body { padding: 20px; display: flex; flex-direction: column; gap: 18px; }
|
| 383 |
+
.modal-field label { display: block; font-size: 12px; font-weight: 500; color: var(--text-2); margin-bottom: 8px; }
|
| 384 |
+
.modal-options { display: flex; flex-wrap: wrap; gap: 6px; }
|
| 385 |
+
.modal-option {
|
| 386 |
+
padding: 7px 14px; border-radius: var(--r-md);
|
| 387 |
+
font-size: 12px; font-weight: 500;
|
| 388 |
+
background: rgba(255,255,255,.04);
|
| 389 |
+
border: 1px solid var(--border-2);
|
| 390 |
+
color: var(--text-2);
|
| 391 |
+
transition: all .15s;
|
| 392 |
+
}
|
| 393 |
+
.modal-option:hover { background: rgba(255,255,255,.08); color: var(--text); }
|
| 394 |
+
.modal-option.selected { background: rgba(124,58,237,.15); border-color: rgba(124,58,237,.4); color: #fff; }
|
| 395 |
+
.modal-footer {
|
| 396 |
+
padding: 14px 20px;
|
| 397 |
+
border-top: 1px solid var(--border);
|
| 398 |
+
display: flex; justify-content: flex-end; gap: 8px;
|
| 399 |
+
}
|
| 400 |
+
.modal-btn {
|
| 401 |
+
padding: 8px 18px; border-radius: var(--r-md);
|
| 402 |
+
font-size: 12.5px; font-weight: 500;
|
| 403 |
+
transition: all .15s;
|
| 404 |
+
}
|
| 405 |
+
.modal-btn.ghost { color: var(--text-mute); border: 1px solid var(--border-2); }
|
| 406 |
+
.modal-btn.ghost:hover { background: var(--hover); }
|
| 407 |
+
.modal-btn.primary {
|
| 408 |
+
background: var(--grad); color: #fff;
|
| 409 |
+
box-shadow: 0 6px 20px -6px rgba(124,58,237,.4);
|
| 410 |
+
}
|
| 411 |
+
.modal-btn.primary:hover { filter: brightness(1.1); }
|
| 412 |
+
|
| 413 |
+
/* Settings modal fields */
|
| 414 |
+
.settings-field { display: flex; flex-direction: column; gap: 6px; }
|
| 415 |
+
.settings-field label { font-size: 12px; font-weight: 500; color: var(--text-2); }
|
| 416 |
+
.settings-input {
|
| 417 |
+
padding: 9px 12px;
|
| 418 |
+
border-radius: var(--r-sm);
|
| 419 |
+
border: 1px solid var(--border-2);
|
| 420 |
+
background: var(--bg-1);
|
| 421 |
+
color: var(--text);
|
| 422 |
+
font-family: var(--mono);
|
| 423 |
+
font-size: 12px;
|
| 424 |
+
transition: border-color .2s, box-shadow .2s;
|
| 425 |
+
}
|
| 426 |
+
.settings-input:focus {
|
| 427 |
+
border-color: rgba(124,58,237,.5);
|
| 428 |
+
box-shadow: 0 0 0 3px rgba(124,58,237,.1);
|
| 429 |
+
}
|
| 430 |
+
.settings-hint { font-size: 10.5px; color: var(--text-dim); line-height: 1.5; }
|
| 431 |
+
.settings-range-wrap { display: flex; align-items: center; gap: 12px; }
|
| 432 |
+
.settings-range {
|
| 433 |
+
flex: 1; -webkit-appearance: none; appearance: none;
|
| 434 |
+
height: 4px; background: var(--bg-1); border-radius: 2px; border: 1px solid var(--border);
|
| 435 |
+
}
|
| 436 |
+
.settings-range::-webkit-slider-thumb {
|
| 437 |
+
-webkit-appearance: none; width: 14px; height: 14px;
|
| 438 |
+
border-radius: 50%; background: var(--grad);
|
| 439 |
+
border: 2px solid #fff; cursor: pointer;
|
| 440 |
+
box-shadow: 0 0 0 2px rgba(124,58,237,.2);
|
| 441 |
+
}
|
| 442 |
+
.settings-range-val {
|
| 443 |
+
font-family: var(--mono); font-size: 12px;
|
| 444 |
+
color: var(--purple-light); min-width: 42px; text-align: right;
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
/* ============ TOASTS ============ */
|
| 448 |
+
.toasts {
|
| 449 |
+
position: fixed; bottom: 20px; right: 20px;
|
| 450 |
+
display: flex; flex-direction: column; gap: 8px;
|
| 451 |
+
z-index: 200; pointer-events: none;
|
| 452 |
+
}
|
| 453 |
+
.toast {
|
| 454 |
+
pointer-events: auto;
|
| 455 |
+
display: flex; align-items: center; gap: 10px;
|
| 456 |
+
padding: 10px 16px;
|
| 457 |
+
border-radius: var(--r-md);
|
| 458 |
+
background: var(--panel-2);
|
| 459 |
+
border: 1px solid var(--border-2);
|
| 460 |
+
box-shadow: 0 16px 32px rgba(0,0,0,.4);
|
| 461 |
+
font-size: 12.5px; color: var(--text);
|
| 462 |
+
animation: toast-in .3s var(--ease);
|
| 463 |
+
min-width: 220px; max-width: 340px;
|
| 464 |
+
}
|
| 465 |
+
.toast-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
|
| 466 |
+
.toast-dot.success { background: var(--ok); box-shadow: 0 0 8px var(--ok); }
|
| 467 |
+
.toast-dot.error { background: var(--err); box-shadow: 0 0 8px var(--err); }
|
| 468 |
+
.toast-dot.info { background: var(--info); box-shadow: 0 0 8px var(--info); }
|
| 469 |
+
|
| 470 |
+
/* ============ RESPONSIVE ============ */
|
| 471 |
+
@media (max-width: 1200px) { .app-shell { grid-template-columns: var(--sidebar-w) 1fr 360px; } }
|
| 472 |
+
@media (max-width: 1024px) {
|
| 473 |
+
.app-shell { grid-template-columns: 1fr; }
|
| 474 |
+
.sidebar { display: none; }
|
| 475 |
+
.preview-panel { display: none; }
|
| 476 |
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
| 2 |
+
import Sidebar from './components/Sidebar';
|
| 3 |
+
import Editor from './components/Editor';
|
| 4 |
+
import Preview from './components/Preview';
|
| 5 |
+
import PromptBar from './components/PromptBar';
|
| 6 |
+
import PlanModal from './components/PlanModal';
|
| 7 |
+
import SettingsModal from './components/SettingsModal';
|
| 8 |
+
import Toasts from './components/Toasts';
|
| 9 |
+
import { callMINDI, generateDemo, isQuotaError, isQuotaException, pingAPI } from './services/api';
|
| 10 |
+
import { analyzePrompt, enhancePrompt, getQuickEnhancement } from './services/promptEnhancer';
|
| 11 |
+
import { parseFiles, buildPreviewHTML } from './services/fileParser';
|
| 12 |
+
import './App.css';
|
| 13 |
+
|
| 14 |
+
const STORAGE_KEY = 'mindi.builder.v1';
|
| 15 |
+
|
| 16 |
+
function loadSettings() {
|
| 17 |
+
try {
|
| 18 |
+
return JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
|
| 19 |
+
} catch { return {}; }
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
export default function App() {
|
| 23 |
+
const saved = loadSettings();
|
| 24 |
+
const [settings, setSettings] = useState({
|
| 25 |
+
apiUrl: saved.apiUrl || 'https://mindigenous-mindi-chat.hf.space',
|
| 26 |
+
hfToken: saved.hfToken || '',
|
| 27 |
+
temperature: saved.temperature ?? 0.7,
|
| 28 |
+
maxTokens: saved.maxTokens ?? 2048,
|
| 29 |
+
});
|
| 30 |
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
| 31 |
+
const [planModal, setPlanModal] = useState(null);
|
| 32 |
+
const [toasts, setToasts] = useState([]);
|
| 33 |
+
const [status, setStatus] = useState('connecting');
|
| 34 |
+
const [files, setFiles] = useState([]);
|
| 35 |
+
const [activeFile, setActiveFile] = useState(null);
|
| 36 |
+
const [previewHTML, setPreviewHTML] = useState(null);
|
| 37 |
+
const [isGenerating, setIsGenerating] = useState(false);
|
| 38 |
+
const [generationProgress, setGenerationProgress] = useState('');
|
| 39 |
+
const [agentSteps, setAgentSteps] = useState([]);
|
| 40 |
+
const [codeLines, setCodeLines] = useState([]);
|
| 41 |
+
const [consoleOutput, setConsoleOutput] = useState([]);
|
| 42 |
+
const [history, setHistory] = useState([]);
|
| 43 |
+
const abortRef = useRef(null);
|
| 44 |
+
|
| 45 |
+
const addToast = useCallback((msg, type = 'info', ms = 3000) => {
|
| 46 |
+
const id = Date.now() + Math.random();
|
| 47 |
+
setToasts(t => [...t, { id, msg, type }]);
|
| 48 |
+
setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), ms);
|
| 49 |
+
}, []);
|
| 50 |
+
|
| 51 |
+
const saveSettings = useCallback((s) => {
|
| 52 |
+
setSettings(s);
|
| 53 |
+
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } catch {}
|
| 54 |
+
}, []);
|
| 55 |
+
|
| 56 |
+
// Health check
|
| 57 |
+
useEffect(() => {
|
| 58 |
+
const check = async () => {
|
| 59 |
+
const ok = await pingAPI(settings.apiUrl, settings.hfToken);
|
| 60 |
+
setStatus(ok ? 'online' : 'demo');
|
| 61 |
+
};
|
| 62 |
+
check();
|
| 63 |
+
const iv = setInterval(check, 60000);
|
| 64 |
+
return () => clearInterval(iv);
|
| 65 |
+
}, [settings.apiUrl, settings.hfToken]);
|
| 66 |
+
|
| 67 |
+
const addAgentStep = useCallback((type, detail, status = 'running') => {
|
| 68 |
+
const step = { id: Date.now(), type, detail, status, time: new Date() };
|
| 69 |
+
setAgentSteps(prev => [...prev, step]);
|
| 70 |
+
return step.id;
|
| 71 |
+
}, []);
|
| 72 |
+
|
| 73 |
+
const updateAgentStep = useCallback((id, updates) => {
|
| 74 |
+
setAgentSteps(prev => prev.map(s => s.id === id ? { ...s, ...updates } : s));
|
| 75 |
+
}, []);
|
| 76 |
+
|
| 77 |
+
// Animate code appearing line by line
|
| 78 |
+
const animateCode = useCallback((code, fileList) => {
|
| 79 |
+
const lines = code.split('\n');
|
| 80 |
+
setCodeLines([]);
|
| 81 |
+
let i = 0;
|
| 82 |
+
const interval = setInterval(() => {
|
| 83 |
+
if (i < lines.length) {
|
| 84 |
+
setCodeLines(prev => [...prev, { text: lines[i], id: i, visible: true }]);
|
| 85 |
+
i++;
|
| 86 |
+
} else {
|
| 87 |
+
clearInterval(interval);
|
| 88 |
+
}
|
| 89 |
+
}, 15); // ~15ms per line for smooth animation
|
| 90 |
+
return () => clearInterval(interval);
|
| 91 |
+
}, []);
|
| 92 |
+
|
| 93 |
+
// Main generate function
|
| 94 |
+
const handleGenerate = useCallback(async (userPrompt, skipPlan = false) => {
|
| 95 |
+
if (!userPrompt.trim() || isGenerating) return;
|
| 96 |
+
|
| 97 |
+
// Analyze prompt for planning
|
| 98 |
+
if (!skipPlan) {
|
| 99 |
+
const analysis = analyzePrompt(userPrompt);
|
| 100 |
+
if (analysis.questions.length > 0) {
|
| 101 |
+
setPlanModal({ userPrompt, questions: analysis.questions });
|
| 102 |
+
return;
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
setIsGenerating(true);
|
| 107 |
+
setAgentSteps([]);
|
| 108 |
+
setConsoleOutput([]);
|
| 109 |
+
setCodeLines([]);
|
| 110 |
+
setFiles([]);
|
| 111 |
+
setPreviewHTML(null);
|
| 112 |
+
abortRef.current = new AbortController();
|
| 113 |
+
|
| 114 |
+
// Step 1: Plan
|
| 115 |
+
const planId = addAgentStep('plan', 'Analyzing your request...');
|
| 116 |
+
setGenerationProgress('Planning...');
|
| 117 |
+
await new Promise(r => setTimeout(r, 400));
|
| 118 |
+
updateAgentStep(planId, { status: 'success', detail: 'Requirements analyzed' });
|
| 119 |
+
|
| 120 |
+
// Step 2: Enhance prompt
|
| 121 |
+
const enhanceId = addAgentStep('enhance', 'Enhancing prompt for best output...');
|
| 122 |
+
const enhanced = getQuickEnhancement(userPrompt);
|
| 123 |
+
await new Promise(r => setTimeout(r, 300));
|
| 124 |
+
updateAgentStep(enhanceId, { status: 'success', detail: 'Prompt optimized' });
|
| 125 |
+
|
| 126 |
+
// Step 3: Generate
|
| 127 |
+
const genId = addAgentStep('generate', 'Generating code with MINDI 1.5...');
|
| 128 |
+
setGenerationProgress('Generating code...');
|
| 129 |
+
|
| 130 |
+
let result;
|
| 131 |
+
try {
|
| 132 |
+
if (status === 'demo' || !settings.apiUrl) {
|
| 133 |
+
result = await generateDemo(userPrompt);
|
| 134 |
+
} else {
|
| 135 |
+
result = await callMINDI({
|
| 136 |
+
prompt: enhanced,
|
| 137 |
+
temperature: settings.temperature,
|
| 138 |
+
maxTokens: settings.maxTokens,
|
| 139 |
+
history,
|
| 140 |
+
hfToken: settings.hfToken,
|
| 141 |
+
apiUrl: settings.apiUrl,
|
| 142 |
+
signal: abortRef.current.signal,
|
| 143 |
+
});
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
if (isQuotaError(result)) {
|
| 147 |
+
updateAgentStep(genId, { status: 'failed', detail: 'GPU quota — using demo fallback' });
|
| 148 |
+
addToast('GPU quota exceeded — showing demo. Add HF token in Settings for real generation.', 'error', 5000);
|
| 149 |
+
result = await generateDemo(userPrompt);
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
updateAgentStep(genId, { status: 'success', detail: `Response received (${(result.response || '').length} chars)` });
|
| 153 |
+
|
| 154 |
+
// Step 4: Parse files
|
| 155 |
+
const parseId = addAgentStep('parse', 'Extracting files...');
|
| 156 |
+
const parsedFiles = parseFiles(result.response);
|
| 157 |
+
setFiles(parsedFiles);
|
| 158 |
+
if (parsedFiles.length > 0) {
|
| 159 |
+
setActiveFile(parsedFiles[0].id);
|
| 160 |
+
// Animate the code
|
| 161 |
+
animateCode(parsedFiles[0].content, parsedFiles);
|
| 162 |
+
}
|
| 163 |
+
updateAgentStep(parseId, { status: 'success', detail: `${parsedFiles.length} file(s) extracted` });
|
| 164 |
+
|
| 165 |
+
// Step 5: Preview
|
| 166 |
+
const previewId = addAgentStep('preview', 'Rendering preview...');
|
| 167 |
+
const html = buildPreviewHTML(parsedFiles);
|
| 168 |
+
if (html) {
|
| 169 |
+
setPreviewHTML(html);
|
| 170 |
+
updateAgentStep(previewId, { status: 'success', detail: 'Preview rendered' });
|
| 171 |
+
setConsoleOutput(prev => [...prev, { type: 'log', text: '✓ Page rendered successfully' }]);
|
| 172 |
+
} else {
|
| 173 |
+
updateAgentStep(previewId, { status: 'success', detail: 'No HTML to preview' });
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
// Update history
|
| 177 |
+
setHistory(prev => [
|
| 178 |
+
...prev.slice(-18),
|
| 179 |
+
{ role: 'user', content: userPrompt },
|
| 180 |
+
{ role: 'assistant', content: result.response },
|
| 181 |
+
]);
|
| 182 |
+
|
| 183 |
+
// Done
|
| 184 |
+
addAgentStep('done', 'Generation complete!', 'success');
|
| 185 |
+
setGenerationProgress('');
|
| 186 |
+
|
| 187 |
+
} catch (err) {
|
| 188 |
+
updateAgentStep(genId, { status: 'failed', detail: err.message });
|
| 189 |
+
addToast(`Error: ${err.message}`, 'error');
|
| 190 |
+
|
| 191 |
+
// Fallback to demo
|
| 192 |
+
try {
|
| 193 |
+
result = await generateDemo(userPrompt);
|
| 194 |
+
const parsedFiles = parseFiles(result.response);
|
| 195 |
+
setFiles(parsedFiles);
|
| 196 |
+
if (parsedFiles.length > 0) {
|
| 197 |
+
setActiveFile(parsedFiles[0].id);
|
| 198 |
+
animateCode(parsedFiles[0].content, parsedFiles);
|
| 199 |
+
}
|
| 200 |
+
const html = buildPreviewHTML(parsedFiles);
|
| 201 |
+
if (html) setPreviewHTML(html);
|
| 202 |
+
addAgentStep('done', 'Demo response used as fallback', 'success');
|
| 203 |
+
} catch {}
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
setIsGenerating(false);
|
| 207 |
+
setGenerationProgress('');
|
| 208 |
+
}, [isGenerating, settings, status, history, addToast, addAgentStep, updateAgentStep, animateCode]);
|
| 209 |
+
|
| 210 |
+
const handlePlanSubmit = useCallback((userPrompt, answers) => {
|
| 211 |
+
setPlanModal(null);
|
| 212 |
+
const enhanced = enhancePrompt(userPrompt, answers);
|
| 213 |
+
// Re-call generate with the enhanced prompt, skipping plan
|
| 214 |
+
setIsGenerating(true);
|
| 215 |
+
setAgentSteps([]);
|
| 216 |
+
setConsoleOutput([]);
|
| 217 |
+
setCodeLines([]);
|
| 218 |
+
setFiles([]);
|
| 219 |
+
setPreviewHTML(null);
|
| 220 |
+
|
| 221 |
+
(async () => {
|
| 222 |
+
const genId = addAgentStep('generate', 'Generating with your preferences...');
|
| 223 |
+
setGenerationProgress('Generating...');
|
| 224 |
+
let result;
|
| 225 |
+
try {
|
| 226 |
+
if (status === 'demo') {
|
| 227 |
+
result = await generateDemo(userPrompt);
|
| 228 |
+
} else {
|
| 229 |
+
result = await callMINDI({ prompt: enhanced, temperature: settings.temperature, maxTokens: settings.maxTokens, history, hfToken: settings.hfToken, apiUrl: settings.apiUrl });
|
| 230 |
+
}
|
| 231 |
+
if (isQuotaError(result)) {
|
| 232 |
+
updateAgentStep(genId, { status: 'failed', detail: 'GPU quota — using demo' });
|
| 233 |
+
addToast('GPU quota exceeded — showing demo.', 'error', 4000);
|
| 234 |
+
result = await generateDemo(userPrompt);
|
| 235 |
+
}
|
| 236 |
+
updateAgentStep(genId, { status: 'success', detail: 'Code generated' });
|
| 237 |
+
const parsedFiles = parseFiles(result.response);
|
| 238 |
+
setFiles(parsedFiles);
|
| 239 |
+
if (parsedFiles.length > 0) { setActiveFile(parsedFiles[0].id); animateCode(parsedFiles[0].content, parsedFiles); }
|
| 240 |
+
const html = buildPreviewHTML(parsedFiles);
|
| 241 |
+
if (html) setPreviewHTML(html);
|
| 242 |
+
setHistory(prev => [...prev.slice(-18), { role: 'user', content: userPrompt }, { role: 'assistant', content: result.response }]);
|
| 243 |
+
addAgentStep('done', 'Complete!', 'success');
|
| 244 |
+
} catch (err) {
|
| 245 |
+
updateAgentStep(genId, { status: 'failed', detail: err.message });
|
| 246 |
+
// Fallback to demo on any error
|
| 247 |
+
try {
|
| 248 |
+
if (isQuotaException(err.message)) {
|
| 249 |
+
addToast('GPU quota exceeded — showing demo.', 'error', 4000);
|
| 250 |
+
}
|
| 251 |
+
result = await generateDemo(userPrompt);
|
| 252 |
+
const parsedFiles = parseFiles(result.response);
|
| 253 |
+
setFiles(parsedFiles);
|
| 254 |
+
if (parsedFiles.length > 0) { setActiveFile(parsedFiles[0].id); animateCode(parsedFiles[0].content, parsedFiles); }
|
| 255 |
+
const html = buildPreviewHTML(parsedFiles);
|
| 256 |
+
if (html) { setPreviewHTML(html); setConsoleOutput(prev => [...prev, { type: 'log', text: '✓ Demo preview rendered' }]); }
|
| 257 |
+
addAgentStep('done', 'Demo fallback used', 'success');
|
| 258 |
+
} catch {}
|
| 259 |
+
}
|
| 260 |
+
setIsGenerating(false);
|
| 261 |
+
setGenerationProgress('');
|
| 262 |
+
})();
|
| 263 |
+
}, [settings, status, history, addToast, addAgentStep, updateAgentStep, animateCode]);
|
| 264 |
+
|
| 265 |
+
const handleFileSelect = useCallback((fileId) => {
|
| 266 |
+
setActiveFile(fileId);
|
| 267 |
+
const file = files.find(f => f.id === fileId);
|
| 268 |
+
if (file) {
|
| 269 |
+
setCodeLines(file.content.split('\n').map((text, i) => ({ text, id: i, visible: true })));
|
| 270 |
+
}
|
| 271 |
+
}, [files]);
|
| 272 |
+
|
| 273 |
+
const handleStop = useCallback(() => {
|
| 274 |
+
abortRef.current?.abort();
|
| 275 |
+
setIsGenerating(false);
|
| 276 |
+
setGenerationProgress('');
|
| 277 |
+
addAgentStep('stop', 'Generation stopped by user', 'failed');
|
| 278 |
+
}, [addAgentStep]);
|
| 279 |
+
|
| 280 |
+
const activeFileData = files.find(f => f.id === activeFile);
|
| 281 |
+
|
| 282 |
+
return (
|
| 283 |
+
<div className="app-shell">
|
| 284 |
+
<div className="ambient">
|
| 285 |
+
<div className="grid-pattern" />
|
| 286 |
+
<div className="blob blob--purple" />
|
| 287 |
+
<div className="blob blob--blue" />
|
| 288 |
+
</div>
|
| 289 |
+
|
| 290 |
+
<Sidebar
|
| 291 |
+
files={files}
|
| 292 |
+
activeFile={activeFile}
|
| 293 |
+
onFileSelect={handleFileSelect}
|
| 294 |
+
agentSteps={agentSteps}
|
| 295 |
+
status={status}
|
| 296 |
+
isGenerating={isGenerating}
|
| 297 |
+
onSettingsOpen={() => setSettingsOpen(true)}
|
| 298 |
+
/>
|
| 299 |
+
|
| 300 |
+
<main className="main-area">
|
| 301 |
+
<Editor
|
| 302 |
+
file={activeFileData}
|
| 303 |
+
codeLines={codeLines}
|
| 304 |
+
isGenerating={isGenerating}
|
| 305 |
+
generationProgress={generationProgress}
|
| 306 |
+
files={files}
|
| 307 |
+
activeFile={activeFile}
|
| 308 |
+
onFileSelect={handleFileSelect}
|
| 309 |
+
/>
|
| 310 |
+
|
| 311 |
+
<PromptBar
|
| 312 |
+
onSubmit={handleGenerate}
|
| 313 |
+
onStop={handleStop}
|
| 314 |
+
isGenerating={isGenerating}
|
| 315 |
+
generationProgress={generationProgress}
|
| 316 |
+
status={status}
|
| 317 |
+
/>
|
| 318 |
+
</main>
|
| 319 |
+
|
| 320 |
+
<Preview
|
| 321 |
+
html={previewHTML}
|
| 322 |
+
consoleOutput={consoleOutput}
|
| 323 |
+
isGenerating={isGenerating}
|
| 324 |
+
/>
|
| 325 |
+
|
| 326 |
+
{planModal && (
|
| 327 |
+
<PlanModal
|
| 328 |
+
userPrompt={planModal.userPrompt}
|
| 329 |
+
questions={planModal.questions}
|
| 330 |
+
onSubmit={handlePlanSubmit}
|
| 331 |
+
onClose={() => setPlanModal(null)}
|
| 332 |
+
/>
|
| 333 |
+
)}
|
| 334 |
+
|
| 335 |
+
{settingsOpen && (
|
| 336 |
+
<SettingsModal
|
| 337 |
+
settings={settings}
|
| 338 |
+
onSave={(s) => { saveSettings(s); setSettingsOpen(false); addToast('Settings saved', 'success'); }}
|
| 339 |
+
onClose={() => setSettingsOpen(false)}
|
| 340 |
+
/>
|
| 341 |
+
)}
|
| 342 |
+
|
| 343 |
+
<Toasts toasts={toasts} />
|
| 344 |
+
</div>
|
| 345 |
+
);
|
| 346 |
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getFileIcon } from '../services/fileParser';
|
| 2 |
+
|
| 3 |
+
export default function Editor({ file, codeLines, isGenerating, generationProgress, files, activeFile, onFileSelect }) {
|
| 4 |
+
if (!file && !isGenerating) {
|
| 5 |
+
return (
|
| 6 |
+
<div className="editor">
|
| 7 |
+
<div className="editor-welcome">
|
| 8 |
+
<div className="welcome-logo">
|
| 9 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
| 10 |
+
<path d="M12 2L2 7l10 5 10-5-10-5z" />
|
| 11 |
+
<path d="M2 17l10 5 10-5" />
|
| 12 |
+
<path d="M2 12l10 5 10-5" />
|
| 13 |
+
</svg>
|
| 14 |
+
</div>
|
| 15 |
+
<h1 className="welcome-title">
|
| 16 |
+
What do you want to <span className="grad-text">build</span>?
|
| 17 |
+
</h1>
|
| 18 |
+
<p className="welcome-sub">
|
| 19 |
+
Describe your website, app, or component and MINDI will generate production-ready code with live preview.
|
| 20 |
+
</p>
|
| 21 |
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', justifyContent: 'center', maxWidth: '400px' }}>
|
| 22 |
+
{['Landing Page', 'Dashboard', 'Portfolio', 'E-commerce'].map(tag => (
|
| 23 |
+
<span key={tag} style={{
|
| 24 |
+
padding: '6px 14px', borderRadius: '20px', fontSize: '11px',
|
| 25 |
+
background: 'rgba(124,58,237,.1)', border: '1px solid rgba(124,58,237,.2)',
|
| 26 |
+
color: 'var(--purple-light)', fontWeight: 500,
|
| 27 |
+
}}>{tag}</span>
|
| 28 |
+
))}
|
| 29 |
+
</div>
|
| 30 |
+
</div>
|
| 31 |
+
</div>
|
| 32 |
+
);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
return (
|
| 36 |
+
<div className="editor">
|
| 37 |
+
{/* Tabs */}
|
| 38 |
+
{files.length > 0 && (
|
| 39 |
+
<div className="editor-tabs">
|
| 40 |
+
{files.map(f => (
|
| 41 |
+
<button
|
| 42 |
+
key={f.id}
|
| 43 |
+
className={`editor-tab ${activeFile === f.id ? 'active' : ''}`}
|
| 44 |
+
onClick={() => onFileSelect(f.id)}
|
| 45 |
+
>
|
| 46 |
+
<span className="tab-icon">{getFileIcon(f.path)}</span>
|
| 47 |
+
{f.path}
|
| 48 |
+
</button>
|
| 49 |
+
))}
|
| 50 |
+
</div>
|
| 51 |
+
)}
|
| 52 |
+
|
| 53 |
+
{/* Generating indicator */}
|
| 54 |
+
{isGenerating && (
|
| 55 |
+
<div className="generating-indicator">
|
| 56 |
+
<div className="gen-spinner" />
|
| 57 |
+
<span>{generationProgress || 'Generating...'}</span>
|
| 58 |
+
</div>
|
| 59 |
+
)}
|
| 60 |
+
|
| 61 |
+
{/* Code */}
|
| 62 |
+
<div className="editor-content">
|
| 63 |
+
{codeLines.map((line, i) => (
|
| 64 |
+
<div key={line.id} className="code-line" style={{ animationDelay: `${Math.min(i * 12, 600)}ms` }}>
|
| 65 |
+
<span className="line-number">{i + 1}</span>
|
| 66 |
+
<span className="line-content">{line.text}</span>
|
| 67 |
+
</div>
|
| 68 |
+
))}
|
| 69 |
+
{isGenerating && codeLines.length === 0 && (
|
| 70 |
+
<div style={{ padding: '20px 64px', color: 'var(--text-dim)', fontFamily: 'var(--mono)', fontSize: '12px' }}>
|
| 71 |
+
Waiting for code...
|
| 72 |
+
</div>
|
| 73 |
+
)}
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
);
|
| 77 |
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState } from 'react';
|
| 2 |
+
|
| 3 |
+
export default function PlanModal({ userPrompt, questions, onSubmit, onClose }) {
|
| 4 |
+
const [answers, setAnswers] = useState(() => {
|
| 5 |
+
const init = {};
|
| 6 |
+
questions.forEach(q => { init[q.id] = q.default; });
|
| 7 |
+
return init;
|
| 8 |
+
});
|
| 9 |
+
|
| 10 |
+
return (
|
| 11 |
+
<div className="modal-overlay" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
|
| 12 |
+
<div className="modal-card">
|
| 13 |
+
<div className="modal-header">
|
| 14 |
+
<h3>🎯 Configure Your Project</h3>
|
| 15 |
+
<button className="modal-close" onClick={onClose}>✕</button>
|
| 16 |
+
</div>
|
| 17 |
+
|
| 18 |
+
<div className="modal-body">
|
| 19 |
+
<div style={{ padding: '10px 14px', background: 'rgba(124,58,237,.08)', borderRadius: 'var(--r-md)', border: '1px solid rgba(124,58,237,.15)', fontSize: '12px', color: 'var(--text-2)', lineHeight: '1.5' }}>
|
| 20 |
+
<strong style={{ color: 'var(--text)' }}>Your prompt:</strong> {userPrompt}
|
| 21 |
+
</div>
|
| 22 |
+
|
| 23 |
+
{questions.map(q => (
|
| 24 |
+
<div key={q.id} className="modal-field">
|
| 25 |
+
<label>{q.question}</label>
|
| 26 |
+
<div className="modal-options">
|
| 27 |
+
{q.options.map(opt => (
|
| 28 |
+
<button
|
| 29 |
+
key={opt.value}
|
| 30 |
+
className={`modal-option ${answers[q.id] === opt.value ? 'selected' : ''}`}
|
| 31 |
+
onClick={() => setAnswers(prev => ({ ...prev, [q.id]: opt.value }))}
|
| 32 |
+
>
|
| 33 |
+
{opt.label}
|
| 34 |
+
</button>
|
| 35 |
+
))}
|
| 36 |
+
</div>
|
| 37 |
+
</div>
|
| 38 |
+
))}
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<div className="modal-footer">
|
| 42 |
+
<button className="modal-btn ghost" onClick={() => { onSubmit(userPrompt, {}); }}>
|
| 43 |
+
Skip & Generate
|
| 44 |
+
</button>
|
| 45 |
+
<button className="modal-btn primary" onClick={() => { onSubmit(userPrompt, answers); }}>
|
| 46 |
+
Generate ⚡
|
| 47 |
+
</button>
|
| 48 |
+
</div>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
);
|
| 52 |
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useRef, useEffect } from 'react';
|
| 2 |
+
|
| 3 |
+
export default function Preview({ html, consoleOutput, isGenerating }) {
|
| 4 |
+
const iframeRef = useRef(null);
|
| 5 |
+
|
| 6 |
+
useEffect(() => {
|
| 7 |
+
if (iframeRef.current && html) {
|
| 8 |
+
iframeRef.current.srcdoc = html;
|
| 9 |
+
}
|
| 10 |
+
}, [html]);
|
| 11 |
+
|
| 12 |
+
return (
|
| 13 |
+
<div className="preview-panel">
|
| 14 |
+
<div className="preview-header">
|
| 15 |
+
<div className="preview-title">
|
| 16 |
+
{html && <span className="preview-dot" />}
|
| 17 |
+
<span>Preview</span>
|
| 18 |
+
</div>
|
| 19 |
+
<div className="preview-actions">
|
| 20 |
+
{html && (
|
| 21 |
+
<button className="preview-btn" title="Open in new tab" onClick={() => {
|
| 22 |
+
const w = window.open('', '_blank');
|
| 23 |
+
if (w) { w.document.open(); w.document.write(html); w.document.close(); }
|
| 24 |
+
}}>
|
| 25 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
| 26 |
+
<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" />
|
| 27 |
+
<polyline points="15 3 21 3 21 9" />
|
| 28 |
+
<line x1="10" y1="14" x2="21" y2="3" />
|
| 29 |
+
</svg>
|
| 30 |
+
</button>
|
| 31 |
+
)}
|
| 32 |
+
{html && (
|
| 33 |
+
<button className="preview-btn" title="Copy HTML" onClick={() => {
|
| 34 |
+
navigator.clipboard.writeText(html).catch(() => {});
|
| 35 |
+
}}>
|
| 36 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
| 37 |
+
<rect x="9" y="9" width="13" height="13" rx="2" />
|
| 38 |
+
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
|
| 39 |
+
</svg>
|
| 40 |
+
</button>
|
| 41 |
+
)}
|
| 42 |
+
</div>
|
| 43 |
+
</div>
|
| 44 |
+
|
| 45 |
+
<div className="preview-frame-wrap">
|
| 46 |
+
{html ? (
|
| 47 |
+
<iframe
|
| 48 |
+
ref={iframeRef}
|
| 49 |
+
className="preview-frame"
|
| 50 |
+
title="Live Preview"
|
| 51 |
+
sandbox="allow-scripts allow-same-origin"
|
| 52 |
+
/>
|
| 53 |
+
) : (
|
| 54 |
+
<div className="preview-empty">
|
| 55 |
+
<div className="preview-empty-icon">
|
| 56 |
+
{isGenerating ? '⏳' : '👁️'}
|
| 57 |
+
</div>
|
| 58 |
+
<p>{isGenerating ? 'Generating preview...' : 'Live Preview'}</p>
|
| 59 |
+
<p>{isGenerating ? 'Your website will appear here' : 'Generated code will render here in real-time'}</p>
|
| 60 |
+
</div>
|
| 61 |
+
)}
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div className="console-panel">
|
| 65 |
+
<div className="console-header">Console</div>
|
| 66 |
+
<div className="console-body">
|
| 67 |
+
{consoleOutput.length === 0 ? (
|
| 68 |
+
<span style={{ color: 'var(--text-dim)' }}>No output yet</span>
|
| 69 |
+
) : (
|
| 70 |
+
consoleOutput.map((entry, i) => (
|
| 71 |
+
<div key={i} className={entry.type === 'error' ? 'error' : ''}>
|
| 72 |
+
{entry.text}
|
| 73 |
+
</div>
|
| 74 |
+
))
|
| 75 |
+
)}
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
</div>
|
| 79 |
+
);
|
| 80 |
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useRef, useEffect } from 'react';
|
| 2 |
+
|
| 3 |
+
export default function PromptBar({ onSubmit, onStop, isGenerating, generationProgress, status }) {
|
| 4 |
+
const [input, setInput] = useState('');
|
| 5 |
+
const textareaRef = useRef(null);
|
| 6 |
+
|
| 7 |
+
useEffect(() => {
|
| 8 |
+
if (textareaRef.current) {
|
| 9 |
+
textareaRef.current.style.height = 'auto';
|
| 10 |
+
textareaRef.current.style.height = Math.min(textareaRef.current.scrollHeight, 120) + 'px';
|
| 11 |
+
}
|
| 12 |
+
}, [input]);
|
| 13 |
+
|
| 14 |
+
const handleSubmit = () => {
|
| 15 |
+
if (!input.trim() || isGenerating) return;
|
| 16 |
+
onSubmit(input.trim());
|
| 17 |
+
setInput('');
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
const handleKeyDown = (e) => {
|
| 21 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 22 |
+
e.preventDefault();
|
| 23 |
+
handleSubmit();
|
| 24 |
+
}
|
| 25 |
+
};
|
| 26 |
+
|
| 27 |
+
return (
|
| 28 |
+
<div className="prompt-bar">
|
| 29 |
+
<div className="prompt-input-wrap">
|
| 30 |
+
<textarea
|
| 31 |
+
ref={textareaRef}
|
| 32 |
+
className="prompt-input"
|
| 33 |
+
placeholder="Describe what you want to build..."
|
| 34 |
+
value={input}
|
| 35 |
+
onChange={e => setInput(e.target.value)}
|
| 36 |
+
onKeyDown={handleKeyDown}
|
| 37 |
+
rows={1}
|
| 38 |
+
disabled={isGenerating}
|
| 39 |
+
/>
|
| 40 |
+
{isGenerating ? (
|
| 41 |
+
<button className="prompt-stop" onClick={onStop} title="Stop generation">
|
| 42 |
+
<svg viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2" /></svg>
|
| 43 |
+
</button>
|
| 44 |
+
) : (
|
| 45 |
+
<button
|
| 46 |
+
className="prompt-send"
|
| 47 |
+
onClick={handleSubmit}
|
| 48 |
+
disabled={!input.trim()}
|
| 49 |
+
title="Generate"
|
| 50 |
+
>
|
| 51 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
| 52 |
+
<line x1="22" y1="2" x2="11" y2="13" />
|
| 53 |
+
<polygon points="22 2 15 22 11 13 2 9 22 2" />
|
| 54 |
+
</svg>
|
| 55 |
+
</button>
|
| 56 |
+
)}
|
| 57 |
+
</div>
|
| 58 |
+
<div className="prompt-footer">
|
| 59 |
+
<span>
|
| 60 |
+
{status === 'demo' ? '⚠ Demo mode · add HF token in Settings' : 'MINDI 1.5 Vision-Coder'}
|
| 61 |
+
</span>
|
| 62 |
+
<span>
|
| 63 |
+
{isGenerating ? generationProgress : 'Shift+Enter for new line'}
|
| 64 |
+
</span>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
);
|
| 68 |
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState } from 'react';
|
| 2 |
+
|
| 3 |
+
export default function SettingsModal({ settings, onSave, onClose }) {
|
| 4 |
+
const [form, setForm] = useState({ ...settings });
|
| 5 |
+
|
| 6 |
+
const update = (key, val) => setForm(prev => ({ ...prev, [key]: val }));
|
| 7 |
+
|
| 8 |
+
return (
|
| 9 |
+
<div className="modal-overlay" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
|
| 10 |
+
<div className="modal-card">
|
| 11 |
+
<div className="modal-header">
|
| 12 |
+
<h3>⚙️ Settings</h3>
|
| 13 |
+
<button className="modal-close" onClick={onClose}>✕</button>
|
| 14 |
+
</div>
|
| 15 |
+
|
| 16 |
+
<div className="modal-body">
|
| 17 |
+
<div className="settings-field">
|
| 18 |
+
<label>API Endpoint</label>
|
| 19 |
+
<input className="settings-input" type="url" value={form.apiUrl} onChange={e => update('apiUrl', e.target.value)} placeholder="https://mindigenous-mindi-chat.hf.space" />
|
| 20 |
+
<div className="settings-hint">HuggingFace Space or custom API URL</div>
|
| 21 |
+
</div>
|
| 22 |
+
|
| 23 |
+
<div className="settings-field">
|
| 24 |
+
<label>HuggingFace Token</label>
|
| 25 |
+
<input className="settings-input" type="password" value={form.hfToken} onChange={e => update('hfToken', e.target.value)} placeholder="hf_..." />
|
| 26 |
+
<div className="settings-hint">Required for ZeroGPU access. Get one at huggingface.co/settings/tokens</div>
|
| 27 |
+
</div>
|
| 28 |
+
|
| 29 |
+
<div className="settings-field">
|
| 30 |
+
<label>Temperature</label>
|
| 31 |
+
<div className="settings-range-wrap">
|
| 32 |
+
<input className="settings-range" type="range" min="0" max="2" step="0.05" value={form.temperature} onChange={e => update('temperature', parseFloat(e.target.value))} />
|
| 33 |
+
<span className="settings-range-val">{Number(form.temperature).toFixed(2)}</span>
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<div className="settings-field">
|
| 38 |
+
<label>Max Tokens</label>
|
| 39 |
+
<div className="settings-range-wrap">
|
| 40 |
+
<input className="settings-range" type="range" min="128" max="4096" step="128" value={form.maxTokens} onChange={e => update('maxTokens', parseInt(e.target.value))} />
|
| 41 |
+
<span className="settings-range-val">{form.maxTokens}</span>
|
| 42 |
+
</div>
|
| 43 |
+
</div>
|
| 44 |
+
</div>
|
| 45 |
+
|
| 46 |
+
<div className="modal-footer">
|
| 47 |
+
<button className="modal-btn ghost" onClick={onClose}>Cancel</button>
|
| 48 |
+
<button className="modal-btn primary" onClick={() => onSave(form)}>Save Settings</button>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
);
|
| 53 |
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getFileIcon } from '../services/fileParser';
|
| 2 |
+
|
| 3 |
+
const STEP_ICONS = { plan: '📋', enhance: '✨', generate: '⚡', parse: '📦', preview: '👁️', done: '🎉', stop: '⏹️' };
|
| 4 |
+
const STEP_LABELS = { plan: 'Planning', enhance: 'Enhancing', generate: 'Generating', parse: 'Parsing', preview: 'Previewing', done: 'Complete', stop: 'Stopped' };
|
| 5 |
+
|
| 6 |
+
export default function Sidebar({ files, activeFile, onFileSelect, agentSteps, status, isGenerating, onSettingsOpen }) {
|
| 7 |
+
return (
|
| 8 |
+
<aside className="sidebar">
|
| 9 |
+
<button className="sidebar-brand" onClick={onSettingsOpen} title="Settings">
|
| 10 |
+
<div className="brand-icon">
|
| 11 |
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 2L2 7l10 5 10-5-10-5z" /><path d="M2 17l10 5 10-5" /><path d="M2 12l10 5 10-5" /></svg>
|
| 12 |
+
</div>
|
| 13 |
+
<div className="brand-info">
|
| 14 |
+
<div className="brand-name">MINDI<span className="grad-text"> 1.5</span></div>
|
| 15 |
+
<div className="brand-sub">VISION-CODER • AI BUILDER</div>
|
| 16 |
+
</div>
|
| 17 |
+
</button>
|
| 18 |
+
|
| 19 |
+
{/* File Tree */}
|
| 20 |
+
<div className="sidebar-section">
|
| 21 |
+
<div className="sidebar-section-title">
|
| 22 |
+
{files.length > 0 ? `Files (${files.length})` : 'Project'}
|
| 23 |
+
</div>
|
| 24 |
+
<div className="file-tree">
|
| 25 |
+
{files.length === 0 ? (
|
| 26 |
+
<div style={{ padding: '8px 16px', fontSize: '11px', color: 'var(--text-dim)' }}>
|
| 27 |
+
{isGenerating ? 'Generating files...' : 'No files yet. Describe what to build.'}
|
| 28 |
+
</div>
|
| 29 |
+
) : (
|
| 30 |
+
files.map((f, i) => (
|
| 31 |
+
<button
|
| 32 |
+
key={f.id}
|
| 33 |
+
className={`file-item ${activeFile === f.id ? 'active' : ''}`}
|
| 34 |
+
onClick={() => onFileSelect(f.id)}
|
| 35 |
+
style={{ animationDelay: `${i * 80}ms` }}
|
| 36 |
+
>
|
| 37 |
+
<span className="file-icon">{getFileIcon(f.path)}</span>
|
| 38 |
+
<span className="file-name">{f.path}</span>
|
| 39 |
+
</button>
|
| 40 |
+
))
|
| 41 |
+
)}
|
| 42 |
+
</div>
|
| 43 |
+
</div>
|
| 44 |
+
|
| 45 |
+
{/* Agent Steps */}
|
| 46 |
+
{agentSteps.length > 0 && (
|
| 47 |
+
<div className="sidebar-section" style={{ borderTop: '1px solid var(--border)', paddingTop: '8px' }}>
|
| 48 |
+
<div className="sidebar-section-title">Agent Progress</div>
|
| 49 |
+
<div className="agent-steps">
|
| 50 |
+
{agentSteps.map((step, i) => (
|
| 51 |
+
<div key={step.id} className="agent-step" style={{ animationDelay: `${i * 60}ms` }}>
|
| 52 |
+
<div className={`step-icon ${step.status}`}>
|
| 53 |
+
{step.status === 'running' ? '⏳' : step.status === 'success' ? '✅' : step.status === 'failed' ? '❌' : (STEP_ICONS[step.type] || '⏺')}
|
| 54 |
+
</div>
|
| 55 |
+
<div>
|
| 56 |
+
<div style={{ fontWeight: 500, color: 'var(--text)' }}>{STEP_LABELS[step.type] || step.type}</div>
|
| 57 |
+
<div className="step-detail">{step.detail}</div>
|
| 58 |
+
</div>
|
| 59 |
+
</div>
|
| 60 |
+
))}
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
)}
|
| 64 |
+
|
| 65 |
+
<div style={{ flex: 1 }} />
|
| 66 |
+
|
| 67 |
+
{/* Footer */}
|
| 68 |
+
<div className="sidebar-footer">
|
| 69 |
+
<div className={`status-dot ${status}`} />
|
| 70 |
+
<span>{status === 'online' ? 'MINDI · Connected' : status === 'demo' ? 'Demo Mode' : 'Connecting...'}</span>
|
| 71 |
+
</div>
|
| 72 |
+
</aside>
|
| 73 |
+
);
|
| 74 |
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default function Toasts({ toasts }) {
|
| 2 |
+
if (!toasts.length) return null;
|
| 3 |
+
return (
|
| 4 |
+
<div className="toasts">
|
| 5 |
+
{toasts.map(t => (
|
| 6 |
+
<div key={t.id} className="toast">
|
| 7 |
+
<div className={`toast-dot ${t.type}`} />
|
| 8 |
+
<span>{t.msg}</span>
|
| 9 |
+
</div>
|
| 10 |
+
))}
|
| 11 |
+
</div>
|
| 12 |
+
);
|
| 13 |
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* =============================================================
|
| 2 |
+
MINDI 1.5 — AI Website Builder
|
| 3 |
+
Design System — Premium dark IDE theme
|
| 4 |
+
============================================================= */
|
| 5 |
+
|
| 6 |
+
/* ============ TOKENS ============ */
|
| 7 |
+
:root {
|
| 8 |
+
/* Surfaces */
|
| 9 |
+
--bg-0: #07070c;
|
| 10 |
+
--bg-1: #0a0a12;
|
| 11 |
+
--bg-2: #0d0d16;
|
| 12 |
+
--panel: #111120;
|
| 13 |
+
--panel-2: #16162a;
|
| 14 |
+
--panel-3: #1c1c36;
|
| 15 |
+
--elevated: #1f1f38;
|
| 16 |
+
--hover: rgba(255, 255, 255, .04);
|
| 17 |
+
--hover-2: rgba(255, 255, 255, .07);
|
| 18 |
+
|
| 19 |
+
/* Lines */
|
| 20 |
+
--border: rgba(255, 255, 255, .06);
|
| 21 |
+
--border-2: rgba(255, 255, 255, .10);
|
| 22 |
+
--border-3: rgba(255, 255, 255, .16);
|
| 23 |
+
|
| 24 |
+
/* Text */
|
| 25 |
+
--text: #ececf1;
|
| 26 |
+
--text-2: #b4b4c4;
|
| 27 |
+
--text-mute: #7a7a8c;
|
| 28 |
+
--text-dim: #565669;
|
| 29 |
+
|
| 30 |
+
/* Brand */
|
| 31 |
+
--purple: #7c3aed;
|
| 32 |
+
--purple-light: #a78bfa;
|
| 33 |
+
--blue: #2563eb;
|
| 34 |
+
--blue-light: #60a5fa;
|
| 35 |
+
--grad: linear-gradient(135deg, #7c3aed 0%, #2563eb 100%);
|
| 36 |
+
--grad-soft: linear-gradient(135deg, rgba(124, 58, 237, .15) 0%, rgba(37, 99, 235, .15) 100%);
|
| 37 |
+
--grad-glow: linear-gradient(135deg, rgba(124, 58, 237, .55) 0%, rgba(37, 99, 235, .55) 100%);
|
| 38 |
+
|
| 39 |
+
/* Status */
|
| 40 |
+
--ok: #10b981;
|
| 41 |
+
--warn: #f59e0b;
|
| 42 |
+
--err: #ef4444;
|
| 43 |
+
--info: #3b82f6;
|
| 44 |
+
|
| 45 |
+
/* Code colors */
|
| 46 |
+
--c-keyword: #a78bfa;
|
| 47 |
+
--c-string: #34d399;
|
| 48 |
+
--c-number: #fbbf24;
|
| 49 |
+
--c-comment: #6a6a85;
|
| 50 |
+
--c-function: #60a5fa;
|
| 51 |
+
--c-tag: #f87171;
|
| 52 |
+
--c-attr: #fbbf24;
|
| 53 |
+
--c-operator: #5eead4;
|
| 54 |
+
|
| 55 |
+
/* Geometry */
|
| 56 |
+
--r-xs: 4px;
|
| 57 |
+
--r-sm: 6px;
|
| 58 |
+
--r-md: 10px;
|
| 59 |
+
--r-lg: 14px;
|
| 60 |
+
--r-xl: 18px;
|
| 61 |
+
--r-2xl: 24px;
|
| 62 |
+
|
| 63 |
+
--sidebar-w: 260px;
|
| 64 |
+
--header-h: 48px;
|
| 65 |
+
|
| 66 |
+
/* Motion */
|
| 67 |
+
--ease: cubic-bezier(.16, 1, .3, 1);
|
| 68 |
+
--ease-2: cubic-bezier(.4, 0, .2, 1);
|
| 69 |
+
--ease-spring: cubic-bezier(.34, 1.56, .64, 1);
|
| 70 |
+
|
| 71 |
+
/* Fonts */
|
| 72 |
+
--sans: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
| 73 |
+
--mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/* ============ RESET ============ */
|
| 77 |
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 78 |
+
html, body, #root { height: 100%; }
|
| 79 |
+
body {
|
| 80 |
+
font-family: var(--sans);
|
| 81 |
+
font-size: 13px;
|
| 82 |
+
line-height: 1.5;
|
| 83 |
+
color: var(--text);
|
| 84 |
+
background: var(--bg-0);
|
| 85 |
+
overflow: hidden;
|
| 86 |
+
-webkit-font-smoothing: antialiased;
|
| 87 |
+
-moz-osx-font-smoothing: grayscale;
|
| 88 |
+
}
|
| 89 |
+
button, input, textarea, select {
|
| 90 |
+
font: inherit;
|
| 91 |
+
color: inherit;
|
| 92 |
+
background: none;
|
| 93 |
+
border: 0;
|
| 94 |
+
outline: 0;
|
| 95 |
+
}
|
| 96 |
+
button { cursor: pointer; }
|
| 97 |
+
a { color: inherit; text-decoration: none; }
|
| 98 |
+
img, svg { display: block; }
|
| 99 |
+
|
| 100 |
+
/* Scrollbars */
|
| 101 |
+
::-webkit-scrollbar { width: 8px; height: 8px; }
|
| 102 |
+
::-webkit-scrollbar-track { background: transparent; }
|
| 103 |
+
::-webkit-scrollbar-thumb {
|
| 104 |
+
background: rgba(255, 255, 255, .06);
|
| 105 |
+
border-radius: 999px;
|
| 106 |
+
border: 2px solid transparent;
|
| 107 |
+
background-clip: content-box;
|
| 108 |
+
}
|
| 109 |
+
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, .12); background-clip: content-box; }
|
| 110 |
+
* { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, .08) transparent; }
|
| 111 |
+
::selection { background: rgba(124, 58, 237, .35); color: #fff; }
|
| 112 |
+
|
| 113 |
+
/* ============ UTILITY CLASSES ============ */
|
| 114 |
+
.grad-text {
|
| 115 |
+
background: var(--grad);
|
| 116 |
+
-webkit-background-clip: text;
|
| 117 |
+
background-clip: text;
|
| 118 |
+
color: transparent;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
/* ============ ANIMATIONS ============ */
|
| 122 |
+
@keyframes fadeIn {
|
| 123 |
+
from { opacity: 0; transform: translateY(6px); }
|
| 124 |
+
to { opacity: 1; transform: translateY(0); }
|
| 125 |
+
}
|
| 126 |
+
@keyframes fadeInLeft {
|
| 127 |
+
from { opacity: 0; transform: translateX(-8px); }
|
| 128 |
+
to { opacity: 1; transform: translateX(0); }
|
| 129 |
+
}
|
| 130 |
+
@keyframes fadeInScale {
|
| 131 |
+
from { opacity: 0; transform: scale(.96); }
|
| 132 |
+
to { opacity: 1; transform: scale(1); }
|
| 133 |
+
}
|
| 134 |
+
@keyframes slideUp {
|
| 135 |
+
from { opacity: 0; transform: translateY(16px); }
|
| 136 |
+
to { opacity: 1; transform: translateY(0); }
|
| 137 |
+
}
|
| 138 |
+
@keyframes pulse {
|
| 139 |
+
0%, 100% { opacity: 1; }
|
| 140 |
+
50% { opacity: .4; }
|
| 141 |
+
}
|
| 142 |
+
@keyframes spin {
|
| 143 |
+
to { transform: rotate(360deg); }
|
| 144 |
+
}
|
| 145 |
+
@keyframes shimmer {
|
| 146 |
+
0% { background-position: -200% 0; }
|
| 147 |
+
100% { background-position: 200% 0; }
|
| 148 |
+
}
|
| 149 |
+
@keyframes typewriter-cursor {
|
| 150 |
+
0%, 100% { opacity: 1; }
|
| 151 |
+
50% { opacity: 0; }
|
| 152 |
+
}
|
| 153 |
+
@keyframes glow-pulse {
|
| 154 |
+
0%, 100% { box-shadow: 0 0 20px rgba(124, 58, 237, .2); }
|
| 155 |
+
50% { box-shadow: 0 0 40px rgba(124, 58, 237, .4); }
|
| 156 |
+
}
|
| 157 |
+
@keyframes line-appear {
|
| 158 |
+
from { opacity: 0; transform: translateX(-4px); }
|
| 159 |
+
to { opacity: 1; transform: translateX(0); }
|
| 160 |
+
}
|
| 161 |
+
@keyframes float {
|
| 162 |
+
0%, 100% { transform: translateY(0); }
|
| 163 |
+
50% { transform: translateY(-8px); }
|
| 164 |
+
}
|
| 165 |
+
@keyframes drift-1 {
|
| 166 |
+
to { transform: translate(80px, 60px) scale(1.1); }
|
| 167 |
+
}
|
| 168 |
+
@keyframes drift-2 {
|
| 169 |
+
to { transform: translate(-60px, -80px) scale(1.15); }
|
| 170 |
+
}
|
| 171 |
+
@keyframes pop-in {
|
| 172 |
+
from { opacity: 0; transform: scale(.92) translateY(8px); }
|
| 173 |
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
| 174 |
+
}
|
| 175 |
+
@keyframes toast-in {
|
| 176 |
+
from { opacity: 0; transform: translateX(20px); }
|
| 177 |
+
to { opacity: 1; transform: translateX(0); }
|
| 178 |
+
}
|
| 179 |
+
@keyframes toast-out {
|
| 180 |
+
to { opacity: 0; transform: translateX(20px); }
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
/* ============ REDUCE MOTION ============ */
|
| 184 |
+
@media (prefers-reduced-motion: reduce) {
|
| 185 |
+
*, *::before, *::after {
|
| 186 |
+
animation-duration: .01ms !important;
|
| 187 |
+
transition-duration: .01ms !important;
|
| 188 |
+
}
|
| 189 |
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from 'react';
|
| 2 |
+
import ReactDOM from 'react-dom/client';
|
| 3 |
+
import App from './App.jsx';
|
| 4 |
+
import './index.css';
|
| 5 |
+
|
| 6 |
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
| 7 |
+
<React.StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</React.StrictMode>
|
| 10 |
+
);
|
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* =============================================================
|
| 2 |
+
MINDI API Service — Gradio SSE v3 integration
|
| 3 |
+
Connects to HuggingFace-hosted MINDI 1.5 Vision-Coder
|
| 4 |
+
============================================================= */
|
| 5 |
+
|
| 6 |
+
const API_DEFAULT = 'https://mindigenous-mindi-chat.hf.space';
|
| 7 |
+
|
| 8 |
+
function authHeaders(hfToken, extra = {}) {
|
| 9 |
+
const h = { ...extra };
|
| 10 |
+
if (hfToken) h['Authorization'] = `Bearer ${hfToken}`;
|
| 11 |
+
return h;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
function dataUrlToBlob(dataUrl) {
|
| 15 |
+
const match = /^data:([^;]+);base64,(.+)$/.exec(dataUrl || '');
|
| 16 |
+
if (!match) throw new Error('Invalid image data URL');
|
| 17 |
+
const bytes = Uint8Array.from(atob(match[2]), c => c.charCodeAt(0));
|
| 18 |
+
return { blob: new Blob([bytes], { type: match[1] }), mime: match[1] };
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
async function uploadImageToGradio(base, dataUrl, hfToken, signal) {
|
| 22 |
+
const { blob, mime } = dataUrlToBlob(dataUrl);
|
| 23 |
+
const ext = (mime.split('/')[1] || 'png').replace('+xml', '').split(';')[0];
|
| 24 |
+
const filename = `mindi-upload-${Date.now()}.${ext}`;
|
| 25 |
+
const formData = new FormData();
|
| 26 |
+
formData.append('files', blob, filename);
|
| 27 |
+
|
| 28 |
+
const headers = authHeaders(hfToken);
|
| 29 |
+
delete headers['Content-Type'];
|
| 30 |
+
|
| 31 |
+
const res = await fetch(`${base}/gradio_api/upload`, {
|
| 32 |
+
method: 'POST', headers, body: formData, signal,
|
| 33 |
+
});
|
| 34 |
+
if (!res.ok) throw new Error(`Image upload ${res.status}`);
|
| 35 |
+
const result = await res.json();
|
| 36 |
+
const filePath = Array.isArray(result) ? result[0] : result?.files?.[0];
|
| 37 |
+
if (!filePath) throw new Error('Upload failed');
|
| 38 |
+
return filePath;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
export async function callMINDI({ prompt, image, temperature = 0.7, maxTokens = 2048, history = [], hfToken = '', apiUrl = API_DEFAULT, signal }) {
|
| 42 |
+
const base = (apiUrl || API_DEFAULT).replace(/\/$/, '');
|
| 43 |
+
const isGradio = base.includes('hf.space') || base.includes('huggingface.co');
|
| 44 |
+
const historyJson = history.length ? JSON.stringify(history) : '';
|
| 45 |
+
|
| 46 |
+
if (isGradio) {
|
| 47 |
+
let imageArg = null;
|
| 48 |
+
if (image && image.startsWith('data:')) {
|
| 49 |
+
try {
|
| 50 |
+
const filePath = await uploadImageToGradio(base, image, hfToken, signal);
|
| 51 |
+
imageArg = { path: filePath, meta: { _type: 'gradio.FileData' }, orig_name: filePath.split('/').pop() };
|
| 52 |
+
} catch { imageArg = null; }
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
const submitRes = await fetch(`${base}/gradio_api/call/chat_fn`, {
|
| 56 |
+
method: 'POST',
|
| 57 |
+
headers: authHeaders(hfToken, { 'Content-Type': 'application/json' }),
|
| 58 |
+
body: JSON.stringify({ data: [prompt, imageArg, temperature, maxTokens, historyJson] }),
|
| 59 |
+
signal,
|
| 60 |
+
});
|
| 61 |
+
if (!submitRes.ok) {
|
| 62 |
+
const txt = await submitRes.text().catch(() => '');
|
| 63 |
+
throw new Error(`API ${submitRes.status}: ${txt.slice(0, 200)}`);
|
| 64 |
+
}
|
| 65 |
+
const { event_id } = await submitRes.json();
|
| 66 |
+
if (!event_id) throw new Error('No event_id returned');
|
| 67 |
+
|
| 68 |
+
const resultRes = await fetch(`${base}/gradio_api/call/chat_fn/${event_id}`, {
|
| 69 |
+
method: 'GET', headers: authHeaders(hfToken), signal,
|
| 70 |
+
});
|
| 71 |
+
if (!resultRes.ok) throw new Error(`API result ${resultRes.status}`);
|
| 72 |
+
|
| 73 |
+
const sseText = await resultRes.text();
|
| 74 |
+
const lines = sseText.split('\n');
|
| 75 |
+
for (let i = 0; i < lines.length; i++) {
|
| 76 |
+
if (lines[i].startsWith('event: complete')) {
|
| 77 |
+
const dataLine = lines[i + 1];
|
| 78 |
+
if (dataLine?.startsWith('data: ')) {
|
| 79 |
+
try {
|
| 80 |
+
const parsed = JSON.parse(dataLine.slice(6));
|
| 81 |
+
const raw = Array.isArray(parsed) ? parsed[0] : parsed;
|
| 82 |
+
try { return JSON.parse(raw); } catch { return { response: String(raw), sections: {} }; }
|
| 83 |
+
} catch { return { response: dataLine.slice(6), sections: {} }; }
|
| 84 |
+
}
|
| 85 |
+
break;
|
| 86 |
+
}
|
| 87 |
+
if (lines[i].startsWith('event: error')) {
|
| 88 |
+
const errMsg = lines[i + 1]?.startsWith('data: ') ? lines[i + 1].slice(6) : 'Gradio error';
|
| 89 |
+
throw new Error(errMsg.slice(0, 300));
|
| 90 |
+
}
|
| 91 |
+
}
|
| 92 |
+
throw new Error('No complete event in response');
|
| 93 |
+
} else {
|
| 94 |
+
const body = { prompt, temperature, max_tokens: maxTokens, history };
|
| 95 |
+
if (image) body.image = image;
|
| 96 |
+
const res = await fetch(`${base}/api/generate`, {
|
| 97 |
+
method: 'POST',
|
| 98 |
+
headers: authHeaders(hfToken, { 'Content-Type': 'application/json', 'Accept': 'application/json' }),
|
| 99 |
+
body: JSON.stringify(body), signal,
|
| 100 |
+
});
|
| 101 |
+
if (!res.ok) throw new Error(`API ${res.status}`);
|
| 102 |
+
return res.json();
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
export async function pingAPI(apiUrl, hfToken) {
|
| 107 |
+
const base = (apiUrl || API_DEFAULT).replace(/\/$/, '');
|
| 108 |
+
try {
|
| 109 |
+
const res = await fetch(base, { method: 'HEAD', mode: 'no-cors' }).catch(() => null);
|
| 110 |
+
return !!res;
|
| 111 |
+
} catch { return false; }
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
export function isQuotaError(result) {
|
| 115 |
+
if (!result) return false;
|
| 116 |
+
const text = String(result.response || '');
|
| 117 |
+
const errs = result.sections?.error || [];
|
| 118 |
+
const blob = (text + ' ' + errs.join(' ')).toLowerCase();
|
| 119 |
+
return /zerogpu|gpu quota|out of .* quota|exceeded .* quota|unlogged user|gpu task aborted|task aborted/.test(blob);
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
export function isQuotaException(errMessage) {
|
| 123 |
+
const msg = (errMessage || '').toLowerCase();
|
| 124 |
+
return /gpu quota|zerogpu|gpu task aborted|task aborted|unlogged user|out of .* quota|exceeded .* quota/.test(msg);
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
// Demo responses
|
| 128 |
+
const DEMOS = [
|
| 129 |
+
{
|
| 130 |
+
match: /landing|hero|page|website/i,
|
| 131 |
+
response: `Here's a complete landing page:\n\n\`\`\`html\n<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8">\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\n<title>Lumina — Future of Design</title>\n<script src="https://cdn.tailwindcss.com"><\/script>\n<style>\n@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');\nbody { font-family: 'Inter', sans-serif; }\n.gradient-bg { background: linear-gradient(135deg, #0f0c29, #302b63, #24243e); }\n.glow { box-shadow: 0 0 40px rgba(124, 58, 237, 0.3); }\n.card-hover:hover { transform: translateY(-4px); box-shadow: 0 20px 40px rgba(0,0,0,0.3); }\n</style>\n</head>\n<body class="gradient-bg text-white min-h-screen">\n<nav class="flex items-center justify-between px-8 py-5 max-w-7xl mx-auto">\n <div class="text-2xl font-bold bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent">Lumina</div>\n <div class="hidden md:flex gap-8 text-sm text-gray-300">\n <a href="#features" class="hover:text-white transition">Features</a>\n <a href="#pricing" class="hover:text-white transition">Pricing</a>\n <a href="#about" class="hover:text-white transition">About</a>\n </div>\n <button class="px-5 py-2 bg-purple-600 rounded-full text-sm font-medium hover:bg-purple-500 transition glow">Get Started</button>\n</nav>\n<main class="max-w-7xl mx-auto px-8">\n <section class="py-24 text-center">\n <span class="inline-block px-4 py-1.5 bg-purple-500/20 border border-purple-500/30 rounded-full text-purple-300 text-xs font-medium tracking-wider uppercase mb-6">Now in Beta</span>\n <h1 class="text-5xl md:text-7xl font-extrabold leading-tight mb-6">\n Build faster.<br>\n <span class="bg-gradient-to-r from-purple-400 via-pink-400 to-blue-400 bg-clip-text text-transparent">Ship smarter.</span>\n </h1>\n <p class="text-lg text-gray-400 max-w-2xl mx-auto mb-10">The next-generation platform that turns your ideas into reality. No complexity, just results.</p>\n <div class="flex justify-center gap-4">\n <button class="px-8 py-3 bg-gradient-to-r from-purple-600 to-blue-600 rounded-full font-semibold hover:shadow-lg hover:shadow-purple-500/25 transition-all">Start Free Trial</button>\n <button class="px-8 py-3 border border-white/20 rounded-full font-medium hover:bg-white/5 transition">Watch Demo</button>\n </div>\n </section>\n <section id="features" class="py-20 grid md:grid-cols-3 gap-6">\n <div class="p-8 bg-white/5 border border-white/10 rounded-2xl card-hover transition-all">\n <div class="w-12 h-12 bg-purple-500/20 rounded-xl flex items-center justify-center text-2xl mb-4">⚡</div>\n <h3 class="text-lg font-semibold mb-2">Lightning Fast</h3>\n <p class="text-gray-400 text-sm">Deploy in seconds. Our edge network ensures your app loads instantly worldwide.</p>\n </div>\n <div class="p-8 bg-white/5 border border-white/10 rounded-2xl card-hover transition-all">\n <div class="w-12 h-12 bg-blue-500/20 rounded-xl flex items-center justify-center text-2xl mb-4">🔒</div>\n <h3 class="text-lg font-semibold mb-2">Enterprise Security</h3>\n <p class="text-gray-400 text-sm">SOC 2 compliant with end-to-end encryption. Your data is always protected.</p>\n </div>\n <div class="p-8 bg-white/5 border border-white/10 rounded-2xl card-hover transition-all">\n <div class="w-12 h-12 bg-pink-500/20 rounded-xl flex items-center justify-center text-2xl mb-4">🎨</div>\n <h3 class="text-lg font-semibold mb-2">Beautiful UI</h3>\n <p class="text-gray-400 text-sm">Pre-built components that look stunning out of the box. Customize everything.</p>\n </div>\n </section>\n</main>\n<footer class="border-t border-white/10 py-8 text-center text-gray-500 text-sm">\n <p>© 2026 Lumina. Crafted with AI.</p>\n</footer>\n</body>\n</html>\n\`\`\``,
|
| 132 |
+
},
|
| 133 |
+
{
|
| 134 |
+
match: /dashboard|chart|analytics|admin/i,
|
| 135 |
+
response: `Here's a dashboard UI:\n\n\`\`\`html\n<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">\n<title>Dashboard</title>\n<style>\n:root{--bg:#0b0b14;--panel:#14141f;--border:rgba(255,255,255,.08);--text:#ececf1;--mute:#8b94a7;--acc:#7c3aed}\n*{box-sizing:border-box;margin:0;padding:0}\nbody{background:var(--bg);color:var(--text);font:14px/1.55 'Inter',sans-serif;min-height:100vh;display:grid;grid-template-columns:240px 1fr}\naside{background:var(--panel);border-right:1px solid var(--border);padding:20px}\naside h1{font-size:18px;background:linear-gradient(135deg,#7c3aed,#2563eb);-webkit-background-clip:text;color:transparent;margin-bottom:24px}\nnav a{display:block;padding:10px 12px;border-radius:8px;color:var(--mute);text-decoration:none;margin-bottom:2px}\nnav a.active{background:rgba(124,58,237,.15);color:#fff}\nmain{padding:24px;overflow-y:auto}\n.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:20px}\n.stat{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:16px}\n.stat .v{font-size:24px;font-weight:600;margin-top:6px}\n.stat .l{color:var(--mute);font-size:12px;text-transform:uppercase;letter-spacing:.1em}\n.chart{background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:18px;height:260px;display:flex;align-items:end;gap:8px}\n.bar{flex:1;background:linear-gradient(180deg,#7c3aed,#2563eb);border-radius:6px 6px 0 0;transition:height .5s}\n</style>\n</head>\n<body>\n<aside><h1>Pulsegrid</h1>\n<nav><a class="active">Overview</a><a>Customers</a><a>Revenue</a><a>Settings</a></nav>\n</aside>\n<main>\n<div class="stats">\n<div class="stat"><div class="l">Revenue</div><div class="v">$48,210</div></div>\n<div class="stat"><div class="l">Users</div><div class="v">12,840</div></div>\n<div class="stat"><div class="l">Conversion</div><div class="v">4.2%</div></div>\n<div class="stat"><div class="l">Churn</div><div class="v">1.1%</div></div>\n</div>\n<div class="chart">\n<div class="bar" style="height:40%"></div><div class="bar" style="height:65%"></div>\n<div class="bar" style="height:30%"></div><div class="bar" style="height:80%"></div>\n<div class="bar" style="height:55%"></div><div class="bar" style="height:90%"></div>\n<div class="bar" style="height:70%"></div>\n</div>\n</main>\n</body>\n</html>\n\`\`\``,
|
| 136 |
+
},
|
| 137 |
+
];
|
| 138 |
+
|
| 139 |
+
const DEFAULT_DEMO = {
|
| 140 |
+
response: `Here's a starter template:\n\n\`\`\`html\n<!DOCTYPE html>\n<html lang="en">\n<head>\n<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">\n<title>MINDI Generated</title>\n<style>\n*{margin:0;padding:0;box-sizing:border-box}\nbody{min-height:100vh;background:#0f0c29;color:#fff;font-family:Inter,sans-serif;display:grid;place-items:center}\n.card{text-align:center;padding:48px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);border-radius:20px;backdrop-filter:blur(10px)}\nh1{font-size:2.5rem;margin-bottom:12px;background:linear-gradient(135deg,#7c3aed,#2563eb);-webkit-background-clip:text;color:transparent}\np{color:#a0a0b8;font-size:1.1rem}\n</style>\n</head>\n<body>\n<div class="card">\n<h1>Hello from MINDI</h1>\n<p>Describe what you want to build and I'll generate it.</p>\n</div>\n</body>\n</html>\n\`\`\``,
|
| 141 |
+
sections: {},
|
| 142 |
+
};
|
| 143 |
+
|
| 144 |
+
export async function generateDemo(prompt) {
|
| 145 |
+
await new Promise(r => setTimeout(r, 800 + Math.random() * 600));
|
| 146 |
+
const found = DEMOS.find(d => d.match.test(prompt));
|
| 147 |
+
return { response: (found || DEFAULT_DEMO).response, sections: {} };
|
| 148 |
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* File Parser — Extracts files from model response */
|
| 2 |
+
|
| 3 |
+
export function parseFiles(responseText) {
|
| 4 |
+
if (!responseText) return [];
|
| 5 |
+
const files = [];
|
| 6 |
+
const re = /```(\w+)?\s*\n([\s\S]*?)```/g;
|
| 7 |
+
let m, idx = 0;
|
| 8 |
+
while ((m = re.exec(responseText)) !== null) {
|
| 9 |
+
const lang = (m[1] || '').toLowerCase();
|
| 10 |
+
const code = m[2];
|
| 11 |
+
const filename = detectFilename(code, lang, idx);
|
| 12 |
+
files.push({ id: `file-${idx}`, path: filename, content: code, language: lang || detectLang(code) });
|
| 13 |
+
idx++;
|
| 14 |
+
}
|
| 15 |
+
if (files.length === 0 && responseText.trim()) {
|
| 16 |
+
files.push({ id: 'file-0', path: 'index.html', content: responseText, language: 'html' });
|
| 17 |
+
}
|
| 18 |
+
return files;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
function detectFilename(code, lang, idx) {
|
| 22 |
+
// Check for filename comments
|
| 23 |
+
const fnMatch = code.match(/^\/\/\s*([\w/.-]+\.\w+)/m) || code.match(/^<!--\s*([\w/.-]+\.\w+)/m) || code.match(/^\/\*\s*([\w/.-]+\.\w+)/m);
|
| 24 |
+
if (fnMatch) return fnMatch[1];
|
| 25 |
+
const extMap = { html: 'index.html', markup: 'index.html', css: 'styles.css', javascript: 'script.js', js: 'script.js', typescript: 'index.ts', tsx: 'page.tsx', jsx: 'App.jsx', python: 'main.py', json: 'package.json', vue: 'App.vue' };
|
| 26 |
+
if (idx === 0 && /<!doctype|<html/i.test(code)) return 'index.html';
|
| 27 |
+
return extMap[lang] || `file${idx}.${lang || 'txt'}`;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
function detectLang(code) {
|
| 31 |
+
const t = code.trim();
|
| 32 |
+
if (/^<!doctype|^<html/i.test(t)) return 'html';
|
| 33 |
+
if (/^import.*from|^export|^const |^function /m.test(t)) return 'javascript';
|
| 34 |
+
if (/^from |^import |^def |^class /m.test(t)) return 'python';
|
| 35 |
+
if (/^\{[\s\S]*\}$/.test(t)) return 'json';
|
| 36 |
+
return 'plaintext';
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
export function buildPreviewHTML(files) {
|
| 40 |
+
const htmlFile = files.find(f => f.language === 'html' || f.path.endsWith('.html'));
|
| 41 |
+
if (htmlFile) return htmlFile.content;
|
| 42 |
+
const cssFile = files.find(f => f.language === 'css');
|
| 43 |
+
const jsFile = files.find(f => f.language === 'javascript' || f.language === 'js');
|
| 44 |
+
if (cssFile || jsFile) {
|
| 45 |
+
return `<!DOCTYPE html><html><head><meta charset="UTF-8"><style>${cssFile?.content || ''}</style></head><body><script>${jsFile?.content || ''}<\/script></body></html>`;
|
| 46 |
+
}
|
| 47 |
+
return null;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
export function getFileIcon(path) {
|
| 51 |
+
const ext = path.split('.').pop().toLowerCase();
|
| 52 |
+
const icons = { html: '🌐', css: '🎨', js: '⚡', jsx: '⚛️', tsx: '⚛️', ts: '📘', py: '🐍', json: '📋', vue: '💚', md: '📝', svg: '🖼️' };
|
| 53 |
+
return icons[ext] || '📄';
|
| 54 |
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Prompt Enhancer — Transforms user input into structured prompts */
|
| 2 |
+
|
| 3 |
+
const TECH_CONFIGS = {
|
| 4 |
+
html: { label: 'HTML + CSS + JS', instructions: 'Output a SINGLE complete <!DOCTYPE html> document. Include ALL CSS in <style> and JS in <script>. Use Tailwind CDN when appropriate.' },
|
| 5 |
+
react: { label: 'React', instructions: 'Output a single React JSX component with export default. Use hooks when needed.' },
|
| 6 |
+
nextjs: { label: 'Next.js', instructions: 'Output a single app/page.tsx. Use client directive only if needed. Use Tailwind classes.' },
|
| 7 |
+
vue: { label: 'Vue', instructions: 'Output a single .vue SFC with template, script setup, style scoped.' },
|
| 8 |
+
};
|
| 9 |
+
|
| 10 |
+
const DESIGN_PRESETS = {
|
| 11 |
+
dark: 'Dark theme with deep navy backgrounds, subtle gradients, glassmorphism.',
|
| 12 |
+
light: 'Light theme with clean white/gray backgrounds, subtle shadows.',
|
| 13 |
+
gradient: 'Rich gradient backgrounds, purple-to-blue or teal-to-emerald.',
|
| 14 |
+
minimal: 'Minimalist with whitespace, clean typography, elegant simplicity.',
|
| 15 |
+
};
|
| 16 |
+
|
| 17 |
+
export function analyzePrompt(userInput) {
|
| 18 |
+
const input = userInput.toLowerCase();
|
| 19 |
+
const questions = [];
|
| 20 |
+
const hasTech = /\b(html|react|next\.?js|vue|svelte|tailwind)\b/i.test(input);
|
| 21 |
+
if (!hasTech) {
|
| 22 |
+
questions.push({ id: 'tech', question: 'Tech stack?', options: Object.entries(TECH_CONFIGS).map(([k, v]) => ({ value: k, label: v.label })), default: 'html' });
|
| 23 |
+
}
|
| 24 |
+
const hasTheme = /\b(dark|light|gradient|minimal)\b/i.test(input);
|
| 25 |
+
if (!hasTheme) {
|
| 26 |
+
questions.push({ id: 'theme', question: 'Design style?', options: Object.entries(DESIGN_PRESETS).map(([k]) => ({ value: k, label: k.charAt(0).toUpperCase() + k.slice(1) })), default: 'dark' });
|
| 27 |
+
}
|
| 28 |
+
return { questions, hasTech, hasTheme, detectedTech: detectTech(input) };
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
function detectTech(input) {
|
| 32 |
+
if (/next\.?js/i.test(input)) return 'nextjs';
|
| 33 |
+
if (/\breact\b/i.test(input)) return 'react';
|
| 34 |
+
if (/\bvue\b/i.test(input)) return 'vue';
|
| 35 |
+
return 'html';
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
export function enhancePrompt(userInput, answers = {}) {
|
| 39 |
+
const tech = answers.tech || detectTech(userInput);
|
| 40 |
+
const theme = answers.theme || 'dark';
|
| 41 |
+
const config = TECH_CONFIGS[tech] || TECH_CONFIGS.html;
|
| 42 |
+
const design = DESIGN_PRESETS[theme] || DESIGN_PRESETS.dark;
|
| 43 |
+
return `${userInput}\n\n--- REQUIREMENTS ---\nTech: ${config.label}\n${config.instructions}\nDesign: ${design}\nRules: Production-ready, responsive, Inter font, smooth animations, no placeholders, complete code.`;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
export function getQuickEnhancement(userInput) {
|
| 47 |
+
return enhancePrompt(userInput, {});
|
| 48 |
+
}
|
|
@@ -1,1479 +0,0 @@
|
|
| 1 |
-
/* =============================================================
|
| 2 |
-
MINDI 1.5 — Vision-Coder
|
| 3 |
-
Premium dark UI · glassmorphism · gradient brand
|
| 4 |
-
============================================================= */
|
| 5 |
-
|
| 6 |
-
/* ============ TOKENS ============ */
|
| 7 |
-
:root {
|
| 8 |
-
/* Surfaces */
|
| 9 |
-
--bg-0: #08080d;
|
| 10 |
-
--bg-1: #0b0b14;
|
| 11 |
-
--bg-2: #0e0e16;
|
| 12 |
-
--panel: #14141f;
|
| 13 |
-
--panel-2: #1a1a2e;
|
| 14 |
-
--elevated: #1f1f33;
|
| 15 |
-
--hover: rgba(255, 255, 255, .04);
|
| 16 |
-
--hover-2: rgba(255, 255, 255, .07);
|
| 17 |
-
|
| 18 |
-
/* Lines */
|
| 19 |
-
--border: rgba(255, 255, 255, .06);
|
| 20 |
-
--border-2: rgba(255, 255, 255, .10);
|
| 21 |
-
--border-3: rgba(255, 255, 255, .16);
|
| 22 |
-
|
| 23 |
-
/* Text */
|
| 24 |
-
--text: #ececf1;
|
| 25 |
-
--text-2: #b4b4c4;
|
| 26 |
-
--text-mute: #7a7a8c;
|
| 27 |
-
--text-dim: #565669;
|
| 28 |
-
|
| 29 |
-
/* Brand */
|
| 30 |
-
--purple: #7c3aed;
|
| 31 |
-
--blue: #2563eb;
|
| 32 |
-
--grad: linear-gradient(135deg, #7c3aed 0%, #2563eb 100%);
|
| 33 |
-
--grad-soft: linear-gradient(135deg, rgba(124, 58, 237, .18) 0%, rgba(37, 99, 235, .18) 100%);
|
| 34 |
-
--grad-glow: linear-gradient(135deg, rgba(124, 58, 237, .55) 0%, rgba(37, 99, 235, .55) 100%);
|
| 35 |
-
|
| 36 |
-
/* Status colors */
|
| 37 |
-
--ok: #10b981;
|
| 38 |
-
--warn: #f59e0b;
|
| 39 |
-
--err: #ef4444;
|
| 40 |
-
|
| 41 |
-
/* Section card colors */
|
| 42 |
-
--c-thinking: #a78bfa; /* purple */
|
| 43 |
-
--c-code: #34d399; /* green */
|
| 44 |
-
--c-critique: #fbbf24; /* yellow */
|
| 45 |
-
--c-fix: #60a5fa; /* blue */
|
| 46 |
-
--c-error: #f87171; /* red */
|
| 47 |
-
--c-suggest: #5eead4; /* teal */
|
| 48 |
-
--c-file: #cbd5e1; /* gray */
|
| 49 |
-
|
| 50 |
-
/* Geometry */
|
| 51 |
-
--r-xs: 6px;
|
| 52 |
-
--r-sm: 8px;
|
| 53 |
-
--r-md: 12px;
|
| 54 |
-
--r-lg: 16px;
|
| 55 |
-
--r-xl: 20px;
|
| 56 |
-
|
| 57 |
-
--sidebar-w: 280px;
|
| 58 |
-
--preview-w: 480px;
|
| 59 |
-
--header-h: 56px;
|
| 60 |
-
|
| 61 |
-
/* Motion */
|
| 62 |
-
--ease: cubic-bezier(.16, 1, .3, 1);
|
| 63 |
-
--ease-2: cubic-bezier(.4, 0, .2, 1);
|
| 64 |
-
|
| 65 |
-
/* Fonts */
|
| 66 |
-
--sans: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
| 67 |
-
--mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
/* ============ RESET ============ */
|
| 71 |
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 72 |
-
html, body { height: 100%; }
|
| 73 |
-
body {
|
| 74 |
-
font-family: var(--sans);
|
| 75 |
-
font-size: 14px;
|
| 76 |
-
line-height: 1.55;
|
| 77 |
-
color: var(--text);
|
| 78 |
-
background: linear-gradient(180deg, var(--bg-0), var(--bg-2));
|
| 79 |
-
overflow: hidden;
|
| 80 |
-
-webkit-font-smoothing: antialiased;
|
| 81 |
-
-moz-osx-font-smoothing: grayscale;
|
| 82 |
-
}
|
| 83 |
-
button, input, textarea, select {
|
| 84 |
-
font: inherit;
|
| 85 |
-
color: inherit;
|
| 86 |
-
background: none;
|
| 87 |
-
border: 0;
|
| 88 |
-
outline: 0;
|
| 89 |
-
}
|
| 90 |
-
button { cursor: pointer; }
|
| 91 |
-
input, textarea { font-family: inherit; }
|
| 92 |
-
a { color: inherit; text-decoration: none; }
|
| 93 |
-
[hidden] { display: none !important; }
|
| 94 |
-
img, svg { display: block; }
|
| 95 |
-
|
| 96 |
-
/* Scrollbars */
|
| 97 |
-
::-webkit-scrollbar { width: 10px; height: 10px; }
|
| 98 |
-
::-webkit-scrollbar-track { background: transparent; }
|
| 99 |
-
::-webkit-scrollbar-thumb {
|
| 100 |
-
background: rgba(255, 255, 255, .06);
|
| 101 |
-
border-radius: 999px;
|
| 102 |
-
border: 2px solid transparent;
|
| 103 |
-
background-clip: content-box;
|
| 104 |
-
}
|
| 105 |
-
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, .12); background-clip: content-box; }
|
| 106 |
-
* { scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, .08) transparent; }
|
| 107 |
-
|
| 108 |
-
::selection { background: rgba(124, 58, 237, .35); color: #fff; }
|
| 109 |
-
|
| 110 |
-
/* ============ AMBIENT BACKGROUND ============ */
|
| 111 |
-
.ambient {
|
| 112 |
-
position: fixed; inset: 0;
|
| 113 |
-
pointer-events: none;
|
| 114 |
-
z-index: 0;
|
| 115 |
-
overflow: hidden;
|
| 116 |
-
}
|
| 117 |
-
.grid-pattern {
|
| 118 |
-
position: absolute; inset: 0;
|
| 119 |
-
background-image:
|
| 120 |
-
linear-gradient(rgba(255, 255, 255, .025) 1px, transparent 1px),
|
| 121 |
-
linear-gradient(90deg, rgba(255, 255, 255, .025) 1px, transparent 1px);
|
| 122 |
-
background-size: 64px 64px;
|
| 123 |
-
mask-image: radial-gradient(ellipse at center, #000 0%, transparent 80%);
|
| 124 |
-
-webkit-mask-image: radial-gradient(ellipse at center, #000 0%, transparent 80%);
|
| 125 |
-
}
|
| 126 |
-
.blob {
|
| 127 |
-
position: absolute;
|
| 128 |
-
width: 640px; height: 640px;
|
| 129 |
-
border-radius: 50%;
|
| 130 |
-
filter: blur(120px);
|
| 131 |
-
opacity: .35;
|
| 132 |
-
will-change: transform;
|
| 133 |
-
}
|
| 134 |
-
.blob--purple {
|
| 135 |
-
background: radial-gradient(circle, var(--purple), transparent 70%);
|
| 136 |
-
top: -180px; left: -120px;
|
| 137 |
-
animation: drift-1 26s ease-in-out infinite alternate;
|
| 138 |
-
}
|
| 139 |
-
.blob--blue {
|
| 140 |
-
background: radial-gradient(circle, var(--blue), transparent 70%);
|
| 141 |
-
bottom: -180px; right: -120px;
|
| 142 |
-
animation: drift-2 30s ease-in-out infinite alternate;
|
| 143 |
-
}
|
| 144 |
-
@keyframes drift-1 {
|
| 145 |
-
to { transform: translate(80px, 60px) scale(1.1); }
|
| 146 |
-
}
|
| 147 |
-
@keyframes drift-2 {
|
| 148 |
-
to { transform: translate(-60px, -80px) scale(1.15); }
|
| 149 |
-
}
|
| 150 |
-
|
| 151 |
-
/* ============ APP SHELL ============ */
|
| 152 |
-
.app {
|
| 153 |
-
position: relative;
|
| 154 |
-
display: grid;
|
| 155 |
-
grid-template-columns: var(--sidebar-w) 1fr var(--preview-w);
|
| 156 |
-
height: 100vh;
|
| 157 |
-
z-index: 1;
|
| 158 |
-
}
|
| 159 |
-
.scrim {
|
| 160 |
-
position: fixed; inset: 0;
|
| 161 |
-
background: rgba(0, 0, 0, .55);
|
| 162 |
-
backdrop-filter: blur(2px);
|
| 163 |
-
z-index: 30;
|
| 164 |
-
opacity: 0;
|
| 165 |
-
pointer-events: none;
|
| 166 |
-
transition: opacity .25s var(--ease);
|
| 167 |
-
}
|
| 168 |
-
body.sidebar-open .scrim { opacity: 1; pointer-events: auto; }
|
| 169 |
-
|
| 170 |
-
/* ============ SIDEBAR ============ */
|
| 171 |
-
.sidebar {
|
| 172 |
-
display: flex;
|
| 173 |
-
flex-direction: column;
|
| 174 |
-
height: 100vh;
|
| 175 |
-
background: rgba(20, 20, 31, .72);
|
| 176 |
-
border-right: 1px solid var(--border);
|
| 177 |
-
backdrop-filter: blur(20px);
|
| 178 |
-
-webkit-backdrop-filter: blur(20px);
|
| 179 |
-
z-index: 35;
|
| 180 |
-
}
|
| 181 |
-
.sidebar-head {
|
| 182 |
-
padding: 18px 16px 10px;
|
| 183 |
-
}
|
| 184 |
-
.brand {
|
| 185 |
-
display: flex;
|
| 186 |
-
align-items: center;
|
| 187 |
-
gap: 12px;
|
| 188 |
-
width: 100%;
|
| 189 |
-
padding: 8px;
|
| 190 |
-
border-radius: var(--r-md);
|
| 191 |
-
text-align: left;
|
| 192 |
-
transition: background .2s var(--ease);
|
| 193 |
-
}
|
| 194 |
-
.brand:hover { background: var(--hover); }
|
| 195 |
-
.brand-mark {
|
| 196 |
-
flex-shrink: 0;
|
| 197 |
-
width: 36px; height: 36px;
|
| 198 |
-
display: grid; place-items: center;
|
| 199 |
-
border-radius: var(--r-sm);
|
| 200 |
-
box-shadow: 0 0 24px rgba(124, 58, 237, .4);
|
| 201 |
-
}
|
| 202 |
-
.brand-mark svg { width: 100%; height: 100%; }
|
| 203 |
-
.brand-text {
|
| 204 |
-
display: flex;
|
| 205 |
-
flex-direction: column;
|
| 206 |
-
min-width: 0;
|
| 207 |
-
}
|
| 208 |
-
.brand-name {
|
| 209 |
-
font-size: 14px;
|
| 210 |
-
font-weight: 700;
|
| 211 |
-
letter-spacing: -.01em;
|
| 212 |
-
white-space: nowrap;
|
| 213 |
-
}
|
| 214 |
-
.brand-dot {
|
| 215 |
-
background: var(--grad);
|
| 216 |
-
-webkit-background-clip: text;
|
| 217 |
-
background-clip: text;
|
| 218 |
-
color: transparent;
|
| 219 |
-
}
|
| 220 |
-
.brand-version {
|
| 221 |
-
font-family: var(--mono);
|
| 222 |
-
font-size: 10px;
|
| 223 |
-
font-weight: 500;
|
| 224 |
-
color: var(--text-mute);
|
| 225 |
-
letter-spacing: .04em;
|
| 226 |
-
}
|
| 227 |
-
|
| 228 |
-
/* Sidebar actions */
|
| 229 |
-
.sidebar-actions {
|
| 230 |
-
padding: 8px 12px 14px;
|
| 231 |
-
display: flex;
|
| 232 |
-
flex-direction: column;
|
| 233 |
-
gap: 8px;
|
| 234 |
-
}
|
| 235 |
-
.btn {
|
| 236 |
-
display: inline-flex;
|
| 237 |
-
align-items: center;
|
| 238 |
-
justify-content: center;
|
| 239 |
-
gap: 8px;
|
| 240 |
-
padding: 10px 14px;
|
| 241 |
-
border-radius: var(--r-md);
|
| 242 |
-
font-size: 13px;
|
| 243 |
-
font-weight: 500;
|
| 244 |
-
background: var(--panel);
|
| 245 |
-
border: 1px solid var(--border-2);
|
| 246 |
-
color: var(--text);
|
| 247 |
-
transition: background .2s var(--ease), border-color .2s var(--ease), transform .15s var(--ease), box-shadow .2s var(--ease);
|
| 248 |
-
}
|
| 249 |
-
.btn:hover { background: var(--panel-2); border-color: var(--border-3); }
|
| 250 |
-
.btn:active { transform: translateY(1px); }
|
| 251 |
-
.btn svg { width: 16px; height: 16px; }
|
| 252 |
-
.btn--primary {
|
| 253 |
-
background: var(--grad);
|
| 254 |
-
border-color: transparent;
|
| 255 |
-
color: #fff;
|
| 256 |
-
box-shadow: 0 8px 24px -8px rgba(124, 58, 237, .55);
|
| 257 |
-
}
|
| 258 |
-
.btn--primary:hover { filter: brightness(1.1); box-shadow: 0 10px 30px -8px rgba(124, 58, 237, .7); }
|
| 259 |
-
.btn--ghost { background: transparent; border-color: var(--border-2); }
|
| 260 |
-
.btn--ghost:hover { background: var(--hover); }
|
| 261 |
-
.btn--new {
|
| 262 |
-
background: var(--grad-soft);
|
| 263 |
-
border-color: rgba(124, 58, 237, .35);
|
| 264 |
-
color: var(--text);
|
| 265 |
-
font-weight: 500;
|
| 266 |
-
position: relative;
|
| 267 |
-
overflow: hidden;
|
| 268 |
-
}
|
| 269 |
-
.btn--new::before {
|
| 270 |
-
content: "";
|
| 271 |
-
position: absolute; inset: 0;
|
| 272 |
-
background: var(--grad);
|
| 273 |
-
opacity: 0;
|
| 274 |
-
transition: opacity .25s var(--ease);
|
| 275 |
-
z-index: 0;
|
| 276 |
-
}
|
| 277 |
-
.btn--new:hover::before { opacity: 1; }
|
| 278 |
-
.btn--new > * { position: relative; z-index: 1; }
|
| 279 |
-
.btn--new:hover {
|
| 280 |
-
border-color: transparent;
|
| 281 |
-
color: #fff;
|
| 282 |
-
box-shadow: 0 10px 28px -10px rgba(124, 58, 237, .6);
|
| 283 |
-
}
|
| 284 |
-
|
| 285 |
-
/* Search */
|
| 286 |
-
.search-wrap {
|
| 287 |
-
position: relative;
|
| 288 |
-
}
|
| 289 |
-
.search-wrap input {
|
| 290 |
-
width: 100%;
|
| 291 |
-
padding: 9px 12px 9px 34px;
|
| 292 |
-
border-radius: var(--r-md);
|
| 293 |
-
background: var(--panel);
|
| 294 |
-
border: 1px solid var(--border);
|
| 295 |
-
font-size: 13px;
|
| 296 |
-
color: var(--text);
|
| 297 |
-
transition: border-color .2s var(--ease), background .2s var(--ease);
|
| 298 |
-
}
|
| 299 |
-
.search-wrap input::placeholder { color: var(--text-mute); }
|
| 300 |
-
.search-wrap input:focus {
|
| 301 |
-
border-color: rgba(124, 58, 237, .5);
|
| 302 |
-
background: var(--panel-2);
|
| 303 |
-
box-shadow: 0 0 0 3px rgba(124, 58, 237, .12);
|
| 304 |
-
}
|
| 305 |
-
.search-icon {
|
| 306 |
-
position: absolute;
|
| 307 |
-
top: 50%;
|
| 308 |
-
left: 11px;
|
| 309 |
-
width: 14px; height: 14px;
|
| 310 |
-
color: var(--text-mute);
|
| 311 |
-
transform: translateY(-50%);
|
| 312 |
-
pointer-events: none;
|
| 313 |
-
}
|
| 314 |
-
|
| 315 |
-
/* Chat history */
|
| 316 |
-
.chat-history {
|
| 317 |
-
flex: 1;
|
| 318 |
-
overflow-y: auto;
|
| 319 |
-
padding: 4px 8px 16px;
|
| 320 |
-
display: flex;
|
| 321 |
-
flex-direction: column;
|
| 322 |
-
gap: 18px;
|
| 323 |
-
}
|
| 324 |
-
.history-empty {
|
| 325 |
-
text-align: center;
|
| 326 |
-
padding: 32px 12px;
|
| 327 |
-
color: var(--text-mute);
|
| 328 |
-
}
|
| 329 |
-
.history-empty p { font-size: 13px; }
|
| 330 |
-
.history-empty .muted { color: var(--text-dim); margin-top: 4px; font-size: 12px; }
|
| 331 |
-
|
| 332 |
-
.history-group { display: flex; flex-direction: column; gap: 2px; }
|
| 333 |
-
.history-group-title {
|
| 334 |
-
padding: 6px 10px;
|
| 335 |
-
font-family: var(--mono);
|
| 336 |
-
font-size: 10px;
|
| 337 |
-
font-weight: 500;
|
| 338 |
-
text-transform: uppercase;
|
| 339 |
-
letter-spacing: .15em;
|
| 340 |
-
color: var(--text-dim);
|
| 341 |
-
}
|
| 342 |
-
.history-item {
|
| 343 |
-
display: block;
|
| 344 |
-
width: 100%;
|
| 345 |
-
padding: 9px 10px;
|
| 346 |
-
border-radius: var(--r-sm);
|
| 347 |
-
font-size: 13px;
|
| 348 |
-
color: var(--text-2);
|
| 349 |
-
text-align: left;
|
| 350 |
-
white-space: nowrap;
|
| 351 |
-
overflow: hidden;
|
| 352 |
-
text-overflow: ellipsis;
|
| 353 |
-
border: 1px solid transparent;
|
| 354 |
-
transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
|
| 355 |
-
}
|
| 356 |
-
.history-item:hover { background: var(--hover); color: var(--text); }
|
| 357 |
-
.history-item.is-active {
|
| 358 |
-
background: var(--grad-soft);
|
| 359 |
-
border-color: rgba(124, 58, 237, .25);
|
| 360 |
-
color: var(--text);
|
| 361 |
-
}
|
| 362 |
-
|
| 363 |
-
/* Sidebar foot */
|
| 364 |
-
.sidebar-foot {
|
| 365 |
-
padding: 12px 14px 16px;
|
| 366 |
-
border-top: 1px solid var(--border);
|
| 367 |
-
display: flex;
|
| 368 |
-
align-items: center;
|
| 369 |
-
justify-content: space-between;
|
| 370 |
-
gap: 10px;
|
| 371 |
-
}
|
| 372 |
-
.status {
|
| 373 |
-
display: inline-flex;
|
| 374 |
-
align-items: center;
|
| 375 |
-
gap: 8px;
|
| 376 |
-
font-family: var(--mono);
|
| 377 |
-
font-size: 11px;
|
| 378 |
-
color: var(--text-mute);
|
| 379 |
-
letter-spacing: .04em;
|
| 380 |
-
}
|
| 381 |
-
.status-dot {
|
| 382 |
-
width: 8px; height: 8px;
|
| 383 |
-
border-radius: 50%;
|
| 384 |
-
background: var(--text-dim);
|
| 385 |
-
position: relative;
|
| 386 |
-
}
|
| 387 |
-
.status-dot--gray { background: var(--text-dim); }
|
| 388 |
-
.status-dot--green { background: var(--ok); box-shadow: 0 0 0 3px rgba(16, 185, 129, .18), 0 0 8px rgba(16, 185, 129, .8); }
|
| 389 |
-
.status-dot--yellow { background: var(--warn); box-shadow: 0 0 0 3px rgba(245, 158, 11, .15), 0 0 8px rgba(245, 158, 11, .7); }
|
| 390 |
-
.status-dot--red { background: var(--err); box-shadow: 0 0 0 3px rgba(239, 68, 68, .15), 0 0 8px rgba(239, 68, 68, .7); }
|
| 391 |
-
.status-dot--green::after,
|
| 392 |
-
.status-dot--yellow::after {
|
| 393 |
-
content: ""; position: absolute; inset: 0;
|
| 394 |
-
border-radius: 50%;
|
| 395 |
-
background: inherit;
|
| 396 |
-
animation: ping 2s var(--ease) infinite;
|
| 397 |
-
}
|
| 398 |
-
@keyframes ping {
|
| 399 |
-
0% { transform: scale(1); opacity: .8; }
|
| 400 |
-
100% { transform: scale(2.6); opacity: 0; }
|
| 401 |
-
}
|
| 402 |
-
.hf-link {
|
| 403 |
-
display: inline-flex;
|
| 404 |
-
align-items: center;
|
| 405 |
-
gap: 6px;
|
| 406 |
-
padding: 6px 10px;
|
| 407 |
-
border-radius: var(--r-sm);
|
| 408 |
-
font-size: 11px;
|
| 409 |
-
font-weight: 500;
|
| 410 |
-
color: var(--text-2);
|
| 411 |
-
border: 1px solid var(--border-2);
|
| 412 |
-
transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
|
| 413 |
-
}
|
| 414 |
-
.hf-link:hover { background: var(--hover); color: var(--text); border-color: var(--border-3); }
|
| 415 |
-
.hf-link svg { width: 12px; height: 12px; }
|
| 416 |
-
|
| 417 |
-
/* ============ CHAT (CENTER) ============ */
|
| 418 |
-
.chat {
|
| 419 |
-
display: flex;
|
| 420 |
-
flex-direction: column;
|
| 421 |
-
height: 100vh;
|
| 422 |
-
background: transparent;
|
| 423 |
-
position: relative;
|
| 424 |
-
min-width: 0;
|
| 425 |
-
}
|
| 426 |
-
.chat-head {
|
| 427 |
-
height: var(--header-h);
|
| 428 |
-
display: flex;
|
| 429 |
-
align-items: center;
|
| 430 |
-
gap: 12px;
|
| 431 |
-
padding: 0 18px;
|
| 432 |
-
border-bottom: 1px solid var(--border);
|
| 433 |
-
background: rgba(8, 8, 13, .65);
|
| 434 |
-
backdrop-filter: blur(16px);
|
| 435 |
-
-webkit-backdrop-filter: blur(16px);
|
| 436 |
-
z-index: 5;
|
| 437 |
-
}
|
| 438 |
-
.chat-title {
|
| 439 |
-
flex: 1;
|
| 440 |
-
font-size: 14px;
|
| 441 |
-
font-weight: 500;
|
| 442 |
-
letter-spacing: -.005em;
|
| 443 |
-
white-space: nowrap;
|
| 444 |
-
overflow: hidden;
|
| 445 |
-
text-overflow: ellipsis;
|
| 446 |
-
}
|
| 447 |
-
.chat-head-actions { display: flex; gap: 4px; }
|
| 448 |
-
.icon-btn {
|
| 449 |
-
width: 36px; height: 36px;
|
| 450 |
-
display: grid; place-items: center;
|
| 451 |
-
border-radius: var(--r-sm);
|
| 452 |
-
color: var(--text-2);
|
| 453 |
-
background: transparent;
|
| 454 |
-
border: 1px solid transparent;
|
| 455 |
-
transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
|
| 456 |
-
}
|
| 457 |
-
.icon-btn:hover { background: var(--hover); color: var(--text); border-color: var(--border-2); }
|
| 458 |
-
.icon-btn svg { width: 18px; height: 18px; }
|
| 459 |
-
.icon-btn--menu { display: none; }
|
| 460 |
-
|
| 461 |
-
/* Welcome screen */
|
| 462 |
-
.welcome {
|
| 463 |
-
flex: 1;
|
| 464 |
-
display: flex;
|
| 465 |
-
flex-direction: column;
|
| 466 |
-
align-items: center;
|
| 467 |
-
justify-content: center;
|
| 468 |
-
padding: 40px 24px 24px;
|
| 469 |
-
overflow-y: auto;
|
| 470 |
-
text-align: center;
|
| 471 |
-
}
|
| 472 |
-
.chat.has-messages .welcome { display: none; }
|
| 473 |
-
.welcome-icon {
|
| 474 |
-
margin-bottom: 28px;
|
| 475 |
-
animation: float 5.5s ease-in-out infinite;
|
| 476 |
-
}
|
| 477 |
-
.welcome-svg {
|
| 478 |
-
width: 96px; height: 96px;
|
| 479 |
-
filter: drop-shadow(0 16px 48px rgba(124, 58, 237, .35));
|
| 480 |
-
}
|
| 481 |
-
@keyframes float {
|
| 482 |
-
0%, 100% { transform: translateY(0) rotate(-1deg); }
|
| 483 |
-
50% { transform: translateY(-12px) rotate(1deg); }
|
| 484 |
-
}
|
| 485 |
-
.welcome-title {
|
| 486 |
-
font-size: 36px;
|
| 487 |
-
font-weight: 600;
|
| 488 |
-
letter-spacing: -.02em;
|
| 489 |
-
margin-bottom: 12px;
|
| 490 |
-
}
|
| 491 |
-
.grad-text {
|
| 492 |
-
background: var(--grad);
|
| 493 |
-
-webkit-background-clip: text;
|
| 494 |
-
background-clip: text;
|
| 495 |
-
color: transparent;
|
| 496 |
-
}
|
| 497 |
-
.welcome-sub {
|
| 498 |
-
font-size: 15px;
|
| 499 |
-
color: var(--text-2);
|
| 500 |
-
max-width: 540px;
|
| 501 |
-
margin-bottom: 36px;
|
| 502 |
-
line-height: 1.6;
|
| 503 |
-
}
|
| 504 |
-
.quick-actions {
|
| 505 |
-
display: grid;
|
| 506 |
-
grid-template-columns: repeat(2, minmax(0, 240px));
|
| 507 |
-
gap: 12px;
|
| 508 |
-
width: 100%;
|
| 509 |
-
max-width: 520px;
|
| 510 |
-
}
|
| 511 |
-
.quick-card {
|
| 512 |
-
text-align: left;
|
| 513 |
-
padding: 18px;
|
| 514 |
-
border-radius: var(--r-lg);
|
| 515 |
-
background: rgba(20, 20, 31, .55);
|
| 516 |
-
border: 1px solid var(--border);
|
| 517 |
-
backdrop-filter: blur(12px);
|
| 518 |
-
-webkit-backdrop-filter: blur(12px);
|
| 519 |
-
position: relative;
|
| 520 |
-
overflow: hidden;
|
| 521 |
-
transition: transform .25s var(--ease), border-color .25s var(--ease), background .25s var(--ease), box-shadow .25s var(--ease);
|
| 522 |
-
}
|
| 523 |
-
.quick-card::after {
|
| 524 |
-
content: "";
|
| 525 |
-
position: absolute;
|
| 526 |
-
inset: 0;
|
| 527 |
-
border-radius: inherit;
|
| 528 |
-
padding: 1px;
|
| 529 |
-
background: var(--grad);
|
| 530 |
-
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
| 531 |
-
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
| 532 |
-
-webkit-mask-composite: xor;
|
| 533 |
-
mask-composite: exclude;
|
| 534 |
-
opacity: 0;
|
| 535 |
-
transition: opacity .25s var(--ease);
|
| 536 |
-
pointer-events: none;
|
| 537 |
-
}
|
| 538 |
-
.quick-card:hover {
|
| 539 |
-
transform: translateY(-3px);
|
| 540 |
-
background: rgba(26, 26, 46, .7);
|
| 541 |
-
border-color: rgba(124, 58, 237, .25);
|
| 542 |
-
box-shadow: 0 24px 48px -24px rgba(124, 58, 237, .4);
|
| 543 |
-
}
|
| 544 |
-
.quick-card:hover::after { opacity: 1; }
|
| 545 |
-
.qc-icon {
|
| 546 |
-
display: inline-block;
|
| 547 |
-
font-size: 22px;
|
| 548 |
-
margin-bottom: 8px;
|
| 549 |
-
filter: drop-shadow(0 4px 12px rgba(124, 58, 237, .35));
|
| 550 |
-
}
|
| 551 |
-
.quick-card h3 {
|
| 552 |
-
font-size: 14px;
|
| 553 |
-
font-weight: 600;
|
| 554 |
-
margin-bottom: 4px;
|
| 555 |
-
letter-spacing: -.005em;
|
| 556 |
-
}
|
| 557 |
-
.quick-card p {
|
| 558 |
-
font-size: 12.5px;
|
| 559 |
-
color: var(--text-mute);
|
| 560 |
-
}
|
| 561 |
-
|
| 562 |
-
/* Messages list */
|
| 563 |
-
.messages {
|
| 564 |
-
flex: 1;
|
| 565 |
-
display: none;
|
| 566 |
-
flex-direction: column;
|
| 567 |
-
gap: 20px;
|
| 568 |
-
padding: 22px 24px 14px;
|
| 569 |
-
overflow-y: auto;
|
| 570 |
-
scroll-behavior: smooth;
|
| 571 |
-
}
|
| 572 |
-
.chat.has-messages .messages { display: flex; }
|
| 573 |
-
|
| 574 |
-
.msg {
|
| 575 |
-
display: flex;
|
| 576 |
-
gap: 14px;
|
| 577 |
-
max-width: 880px;
|
| 578 |
-
margin-inline: auto;
|
| 579 |
-
width: 100%;
|
| 580 |
-
animation: msg-in .35s var(--ease) both;
|
| 581 |
-
}
|
| 582 |
-
@keyframes msg-in {
|
| 583 |
-
from { opacity: 0; transform: translateY(8px); }
|
| 584 |
-
to { opacity: 1; transform: translateY(0); }
|
| 585 |
-
}
|
| 586 |
-
.msg-avatar {
|
| 587 |
-
flex-shrink: 0;
|
| 588 |
-
width: 32px; height: 32px;
|
| 589 |
-
border-radius: var(--r-sm);
|
| 590 |
-
display: grid; place-items: center;
|
| 591 |
-
font-size: 12px;
|
| 592 |
-
font-weight: 700;
|
| 593 |
-
color: #fff;
|
| 594 |
-
letter-spacing: -.01em;
|
| 595 |
-
}
|
| 596 |
-
.msg--user .msg-avatar {
|
| 597 |
-
background: var(--panel-2);
|
| 598 |
-
border: 1px solid var(--border-2);
|
| 599 |
-
color: var(--text-2);
|
| 600 |
-
}
|
| 601 |
-
.msg--asst .msg-avatar {
|
| 602 |
-
background: var(--grad);
|
| 603 |
-
box-shadow: 0 4px 16px -4px rgba(124, 58, 237, .55);
|
| 604 |
-
}
|
| 605 |
-
.msg-body {
|
| 606 |
-
flex: 1;
|
| 607 |
-
min-width: 0;
|
| 608 |
-
}
|
| 609 |
-
.msg-meta {
|
| 610 |
-
display: flex;
|
| 611 |
-
align-items: center;
|
| 612 |
-
gap: 8px;
|
| 613 |
-
font-size: 12px;
|
| 614 |
-
color: var(--text-mute);
|
| 615 |
-
margin-bottom: 6px;
|
| 616 |
-
}
|
| 617 |
-
.msg-meta-name {
|
| 618 |
-
font-weight: 600;
|
| 619 |
-
color: var(--text);
|
| 620 |
-
}
|
| 621 |
-
.msg--user { flex-direction: row-reverse; }
|
| 622 |
-
.msg--user .msg-body { display: flex; flex-direction: column; align-items: flex-end; }
|
| 623 |
-
.msg--user .msg-meta { justify-content: flex-end; }
|
| 624 |
-
.msg-bubble {
|
| 625 |
-
font-size: 14.5px;
|
| 626 |
-
line-height: 1.65;
|
| 627 |
-
color: var(--text);
|
| 628 |
-
word-wrap: break-word;
|
| 629 |
-
overflow-wrap: anywhere;
|
| 630 |
-
}
|
| 631 |
-
.msg--user .msg-bubble {
|
| 632 |
-
background: var(--panel-2);
|
| 633 |
-
border: 1px solid var(--border-2);
|
| 634 |
-
border-radius: var(--r-lg);
|
| 635 |
-
padding: 12px 16px;
|
| 636 |
-
max-width: 720px;
|
| 637 |
-
}
|
| 638 |
-
.msg-bubble p { margin: 0 0 10px; }
|
| 639 |
-
.msg-bubble p:last-child { margin-bottom: 0; }
|
| 640 |
-
.msg-bubble strong { font-weight: 600; color: #fff; }
|
| 641 |
-
.msg-bubble em { font-style: italic; color: var(--text-2); }
|
| 642 |
-
|
| 643 |
-
.msg-images {
|
| 644 |
-
display: flex;
|
| 645 |
-
flex-wrap: wrap;
|
| 646 |
-
gap: 8px;
|
| 647 |
-
margin-bottom: 10px;
|
| 648 |
-
}
|
| 649 |
-
.msg-images img {
|
| 650 |
-
max-width: 220px;
|
| 651 |
-
max-height: 160px;
|
| 652 |
-
width: auto; height: auto;
|
| 653 |
-
border-radius: var(--r-sm);
|
| 654 |
-
border: 1px solid var(--border-2);
|
| 655 |
-
}
|
| 656 |
-
|
| 657 |
-
/* Inline / fenced code inside messages */
|
| 658 |
-
.md-inline {
|
| 659 |
-
font-family: var(--mono);
|
| 660 |
-
font-size: 13px;
|
| 661 |
-
background: rgba(124, 58, 237, .15);
|
| 662 |
-
color: #d8c8ff;
|
| 663 |
-
padding: 2px 6px;
|
| 664 |
-
border-radius: 4px;
|
| 665 |
-
border: 1px solid rgba(124, 58, 237, .2);
|
| 666 |
-
}
|
| 667 |
-
.md-code-block {
|
| 668 |
-
margin: 12px 0;
|
| 669 |
-
background: #0c0c14;
|
| 670 |
-
border: 1px solid var(--border-2);
|
| 671 |
-
border-radius: var(--r-md);
|
| 672 |
-
overflow: hidden;
|
| 673 |
-
position: relative;
|
| 674 |
-
}
|
| 675 |
-
.md-code-head {
|
| 676 |
-
display: flex;
|
| 677 |
-
align-items: center;
|
| 678 |
-
justify-content: space-between;
|
| 679 |
-
padding: 8px 14px;
|
| 680 |
-
font-family: var(--mono);
|
| 681 |
-
font-size: 11px;
|
| 682 |
-
letter-spacing: .04em;
|
| 683 |
-
text-transform: uppercase;
|
| 684 |
-
color: var(--text-mute);
|
| 685 |
-
background: rgba(255, 255, 255, .025);
|
| 686 |
-
border-bottom: 1px solid var(--border);
|
| 687 |
-
}
|
| 688 |
-
.md-code-head span:first-child { color: var(--c-code); font-weight: 500; }
|
| 689 |
-
.md-lang {
|
| 690 |
-
display: inline-flex;
|
| 691 |
-
align-items: center;
|
| 692 |
-
gap: 8px;
|
| 693 |
-
}
|
| 694 |
-
.md-kind {
|
| 695 |
-
font-family: var(--mono);
|
| 696 |
-
font-size: 10px;
|
| 697 |
-
font-weight: 600;
|
| 698 |
-
letter-spacing: .04em;
|
| 699 |
-
text-transform: none;
|
| 700 |
-
padding: 2px 7px;
|
| 701 |
-
border-radius: 999px;
|
| 702 |
-
border: 1px solid currentColor;
|
| 703 |
-
background: rgba(255, 255, 255, .03);
|
| 704 |
-
cursor: help;
|
| 705 |
-
}
|
| 706 |
-
.md-kind--next { color: #f8fafc; }
|
| 707 |
-
.md-kind--react { color: #60a5fa; }
|
| 708 |
-
.md-kind--node { color: #34d399; }
|
| 709 |
-
.md-kind--html { color: #fb923c; }
|
| 710 |
-
.md-kind--snippet { color: var(--text-mute); }
|
| 711 |
-
.md-code-actions {
|
| 712 |
-
display: flex;
|
| 713 |
-
gap: 6px;
|
| 714 |
-
align-items: center;
|
| 715 |
-
flex-wrap: wrap;
|
| 716 |
-
justify-content: flex-end;
|
| 717 |
-
}
|
| 718 |
-
.md-copy,
|
| 719 |
-
.md-run,
|
| 720 |
-
.md-sandbox {
|
| 721 |
-
font-family: var(--mono);
|
| 722 |
-
font-size: 11px;
|
| 723 |
-
font-weight: 500;
|
| 724 |
-
letter-spacing: .02em;
|
| 725 |
-
color: var(--text-mute);
|
| 726 |
-
padding: 4px 10px;
|
| 727 |
-
border-radius: 4px;
|
| 728 |
-
border: 1px solid var(--border);
|
| 729 |
-
white-space: nowrap;
|
| 730 |
-
transition: background .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease), transform .15s var(--ease);
|
| 731 |
-
}
|
| 732 |
-
.md-copy:hover { background: var(--hover); color: var(--text); border-color: var(--border-2); }
|
| 733 |
-
.md-run {
|
| 734 |
-
color: #d8c8ff;
|
| 735 |
-
border-color: rgba(124, 58, 237, .35);
|
| 736 |
-
background: rgba(124, 58, 237, .08);
|
| 737 |
-
}
|
| 738 |
-
.md-run:hover {
|
| 739 |
-
background: rgba(124, 58, 237, .18);
|
| 740 |
-
color: #fff;
|
| 741 |
-
border-color: rgba(124, 58, 237, .55);
|
| 742 |
-
transform: translateY(-1px);
|
| 743 |
-
}
|
| 744 |
-
.md-sandbox {
|
| 745 |
-
color: #fde68a;
|
| 746 |
-
border-color: rgba(245, 158, 11, .32);
|
| 747 |
-
background: rgba(245, 158, 11, .06);
|
| 748 |
-
}
|
| 749 |
-
.md-sandbox:hover {
|
| 750 |
-
background: rgba(245, 158, 11, .15);
|
| 751 |
-
color: #fff;
|
| 752 |
-
border-color: rgba(245, 158, 11, .55);
|
| 753 |
-
transform: translateY(-1px);
|
| 754 |
-
}
|
| 755 |
-
.md-sandbox:disabled,
|
| 756 |
-
.md-run:disabled { opacity: .55; cursor: progress; transform: none; }
|
| 757 |
-
.md-code-block code {
|
| 758 |
-
display: block;
|
| 759 |
-
font-family: var(--mono);
|
| 760 |
-
font-size: 13px;
|
| 761 |
-
line-height: 1.6;
|
| 762 |
-
padding: 14px 16px;
|
| 763 |
-
overflow-x: auto;
|
| 764 |
-
white-space: pre;
|
| 765 |
-
max-height: 540px;
|
| 766 |
-
overflow-y: auto;
|
| 767 |
-
}
|
| 768 |
-
|
| 769 |
-
/* Loading message */
|
| 770 |
-
.msg-loading .msg-bubble {
|
| 771 |
-
display: inline-flex;
|
| 772 |
-
align-items: center;
|
| 773 |
-
gap: 8px;
|
| 774 |
-
padding: 12px 16px;
|
| 775 |
-
background: var(--panel);
|
| 776 |
-
border: 1px solid var(--border);
|
| 777 |
-
border-radius: var(--r-lg);
|
| 778 |
-
color: var(--text-mute);
|
| 779 |
-
font-size: 13px;
|
| 780 |
-
}
|
| 781 |
-
.dots { display: inline-flex; gap: 4px; }
|
| 782 |
-
.dots span {
|
| 783 |
-
width: 6px; height: 6px;
|
| 784 |
-
border-radius: 50%;
|
| 785 |
-
background: var(--purple);
|
| 786 |
-
opacity: .4;
|
| 787 |
-
animation: bounce 1.2s var(--ease-2) infinite;
|
| 788 |
-
}
|
| 789 |
-
.dots span:nth-child(2) { animation-delay: .2s; background: #5a4ee9; }
|
| 790 |
-
.dots span:nth-child(3) { animation-delay: .4s; background: var(--blue); }
|
| 791 |
-
@keyframes bounce {
|
| 792 |
-
0%, 80%, 100% { transform: translateY(0); opacity: .4; }
|
| 793 |
-
40% { transform: translateY(-4px); opacity: 1; }
|
| 794 |
-
}
|
| 795 |
-
|
| 796 |
-
/* ============ COMPOSER ============ */
|
| 797 |
-
.composer-wrap {
|
| 798 |
-
padding: 12px 24px 16px;
|
| 799 |
-
flex-shrink: 0;
|
| 800 |
-
}
|
| 801 |
-
.composer {
|
| 802 |
-
max-width: 880px;
|
| 803 |
-
margin: 0 auto;
|
| 804 |
-
background: rgba(20, 20, 31, .85);
|
| 805 |
-
border: 1px solid var(--border-2);
|
| 806 |
-
border-radius: var(--r-xl);
|
| 807 |
-
backdrop-filter: blur(16px);
|
| 808 |
-
-webkit-backdrop-filter: blur(16px);
|
| 809 |
-
transition: border-color .25s var(--ease), box-shadow .25s var(--ease), background .25s var(--ease);
|
| 810 |
-
overflow: hidden;
|
| 811 |
-
}
|
| 812 |
-
.composer:focus-within {
|
| 813 |
-
border-color: rgba(124, 58, 237, .55);
|
| 814 |
-
background: rgba(26, 26, 46, .9);
|
| 815 |
-
box-shadow:
|
| 816 |
-
0 0 0 4px rgba(124, 58, 237, .12),
|
| 817 |
-
0 24px 60px -28px rgba(124, 58, 237, .55);
|
| 818 |
-
}
|
| 819 |
-
.composer-images {
|
| 820 |
-
display: flex;
|
| 821 |
-
flex-wrap: wrap;
|
| 822 |
-
gap: 8px;
|
| 823 |
-
padding: 12px 14px 4px;
|
| 824 |
-
}
|
| 825 |
-
.composer-image {
|
| 826 |
-
position: relative;
|
| 827 |
-
width: 64px; height: 64px;
|
| 828 |
-
border-radius: var(--r-sm);
|
| 829 |
-
background-size: cover;
|
| 830 |
-
background-position: center;
|
| 831 |
-
border: 1px solid var(--border-2);
|
| 832 |
-
}
|
| 833 |
-
.composer-image-remove {
|
| 834 |
-
position: absolute;
|
| 835 |
-
top: -6px; right: -6px;
|
| 836 |
-
width: 20px; height: 20px;
|
| 837 |
-
border-radius: 50%;
|
| 838 |
-
display: grid; place-items: center;
|
| 839 |
-
background: var(--panel-2);
|
| 840 |
-
border: 1px solid var(--border-3);
|
| 841 |
-
color: var(--text);
|
| 842 |
-
font-size: 11px;
|
| 843 |
-
line-height: 1;
|
| 844 |
-
transition: background .2s var(--ease), transform .2s var(--ease);
|
| 845 |
-
}
|
| 846 |
-
.composer-image-remove:hover { background: var(--err); transform: scale(1.1); }
|
| 847 |
-
|
| 848 |
-
.composer-row {
|
| 849 |
-
display: flex;
|
| 850 |
-
align-items: flex-end;
|
| 851 |
-
gap: 8px;
|
| 852 |
-
padding: 10px 12px;
|
| 853 |
-
}
|
| 854 |
-
.composer-btn {
|
| 855 |
-
width: 36px; height: 36px;
|
| 856 |
-
flex-shrink: 0;
|
| 857 |
-
display: grid; place-items: center;
|
| 858 |
-
border-radius: var(--r-sm);
|
| 859 |
-
color: var(--text-mute);
|
| 860 |
-
transition: background .2s var(--ease), color .2s var(--ease);
|
| 861 |
-
}
|
| 862 |
-
.composer-btn:hover { background: var(--hover); color: var(--text); }
|
| 863 |
-
.composer-btn svg { width: 18px; height: 18px; }
|
| 864 |
-
|
| 865 |
-
.composer textarea {
|
| 866 |
-
flex: 1;
|
| 867 |
-
font-family: var(--sans);
|
| 868 |
-
font-size: 14.5px;
|
| 869 |
-
line-height: 1.55;
|
| 870 |
-
padding: 8px 4px;
|
| 871 |
-
color: var(--text);
|
| 872 |
-
background: transparent;
|
| 873 |
-
resize: none;
|
| 874 |
-
max-height: 200px;
|
| 875 |
-
min-height: 24px;
|
| 876 |
-
overflow-y: auto;
|
| 877 |
-
}
|
| 878 |
-
.composer textarea::placeholder { color: var(--text-mute); }
|
| 879 |
-
|
| 880 |
-
.composer-send {
|
| 881 |
-
flex-shrink: 0;
|
| 882 |
-
width: 36px; height: 36px;
|
| 883 |
-
border-radius: var(--r-sm);
|
| 884 |
-
display: grid; place-items: center;
|
| 885 |
-
background: var(--grad);
|
| 886 |
-
color: #fff;
|
| 887 |
-
transition: filter .2s var(--ease), transform .15s var(--ease), opacity .2s var(--ease), box-shadow .2s var(--ease);
|
| 888 |
-
box-shadow: 0 4px 16px -6px rgba(124, 58, 237, .5);
|
| 889 |
-
}
|
| 890 |
-
.composer-send:hover:not(:disabled) { filter: brightness(1.12); transform: translateY(-1px); box-shadow: 0 8px 22px -8px rgba(124, 58, 237, .7); }
|
| 891 |
-
.composer-send:active:not(:disabled) { transform: translateY(0); }
|
| 892 |
-
.composer-send:disabled { opacity: .35; cursor: not-allowed; box-shadow: none; }
|
| 893 |
-
.composer-send svg { width: 16px; height: 16px; }
|
| 894 |
-
|
| 895 |
-
.composer-foot {
|
| 896 |
-
text-align: center;
|
| 897 |
-
font-family: var(--mono);
|
| 898 |
-
font-size: 10.5px;
|
| 899 |
-
color: var(--text-dim);
|
| 900 |
-
margin-top: 10px;
|
| 901 |
-
letter-spacing: .03em;
|
| 902 |
-
}
|
| 903 |
-
|
| 904 |
-
/* ============ PREVIEW PANEL (RIGHT) ============ */
|
| 905 |
-
.preview {
|
| 906 |
-
display: flex;
|
| 907 |
-
flex-direction: column;
|
| 908 |
-
height: 100vh;
|
| 909 |
-
background: rgba(14, 14, 22, .8);
|
| 910 |
-
border-left: 1px solid var(--border);
|
| 911 |
-
backdrop-filter: blur(20px);
|
| 912 |
-
-webkit-backdrop-filter: blur(20px);
|
| 913 |
-
overflow: hidden;
|
| 914 |
-
}
|
| 915 |
-
body.preview-hidden .preview { display: none; }
|
| 916 |
-
.preview-head {
|
| 917 |
-
height: var(--header-h);
|
| 918 |
-
flex-shrink: 0;
|
| 919 |
-
display: flex;
|
| 920 |
-
align-items: center;
|
| 921 |
-
justify-content: space-between;
|
| 922 |
-
padding: 0 14px;
|
| 923 |
-
border-bottom: 1px solid var(--border);
|
| 924 |
-
}
|
| 925 |
-
.tabs {
|
| 926 |
-
display: inline-flex;
|
| 927 |
-
background: var(--panel);
|
| 928 |
-
border: 1px solid var(--border);
|
| 929 |
-
border-radius: var(--r-sm);
|
| 930 |
-
padding: 3px;
|
| 931 |
-
gap: 2px;
|
| 932 |
-
}
|
| 933 |
-
.tab {
|
| 934 |
-
padding: 6px 12px;
|
| 935 |
-
font-size: 12px;
|
| 936 |
-
font-weight: 500;
|
| 937 |
-
color: var(--text-mute);
|
| 938 |
-
border-radius: 6px;
|
| 939 |
-
transition: background .2s var(--ease), color .2s var(--ease);
|
| 940 |
-
}
|
| 941 |
-
.tab:hover { color: var(--text); }
|
| 942 |
-
.tab.is-active {
|
| 943 |
-
background: var(--grad);
|
| 944 |
-
color: #fff;
|
| 945 |
-
box-shadow: 0 4px 14px -4px rgba(124, 58, 237, .5);
|
| 946 |
-
}
|
| 947 |
-
.preview-actions { display: flex; gap: 4px; }
|
| 948 |
-
|
| 949 |
-
/* Preview panes */
|
| 950 |
-
.preview-pane {
|
| 951 |
-
flex: 1;
|
| 952 |
-
display: none;
|
| 953 |
-
flex-direction: column;
|
| 954 |
-
overflow: hidden;
|
| 955 |
-
position: relative;
|
| 956 |
-
}
|
| 957 |
-
.preview-pane.is-active { display: flex; }
|
| 958 |
-
.preview-empty {
|
| 959 |
-
flex: 1;
|
| 960 |
-
display: flex;
|
| 961 |
-
flex-direction: column;
|
| 962 |
-
align-items: center;
|
| 963 |
-
justify-content: center;
|
| 964 |
-
padding: 40px 24px;
|
| 965 |
-
text-align: center;
|
| 966 |
-
color: var(--text-mute);
|
| 967 |
-
gap: 8px;
|
| 968 |
-
}
|
| 969 |
-
.preview-empty-icon {
|
| 970 |
-
width: 56px; height: 56px;
|
| 971 |
-
display: grid; place-items: center;
|
| 972 |
-
border-radius: var(--r-md);
|
| 973 |
-
font-family: var(--mono);
|
| 974 |
-
font-size: 22px;
|
| 975 |
-
font-weight: 600;
|
| 976 |
-
color: var(--purple);
|
| 977 |
-
background: var(--grad-soft);
|
| 978 |
-
border: 1px solid rgba(124, 58, 237, .25);
|
| 979 |
-
margin-bottom: 8px;
|
| 980 |
-
}
|
| 981 |
-
.preview-empty p { font-size: 13px; }
|
| 982 |
-
.preview-empty .muted { color: var(--text-dim); font-size: 12px; max-width: 260px; line-height: 1.55; }
|
| 983 |
-
|
| 984 |
-
/* Code output */
|
| 985 |
-
.code-out {
|
| 986 |
-
flex: 1;
|
| 987 |
-
overflow: auto;
|
| 988 |
-
margin: 0;
|
| 989 |
-
background: #0c0c14;
|
| 990 |
-
}
|
| 991 |
-
.code-out code {
|
| 992 |
-
display: block;
|
| 993 |
-
font-family: var(--mono);
|
| 994 |
-
font-size: 13px;
|
| 995 |
-
line-height: 1.65;
|
| 996 |
-
padding: 16px 18px;
|
| 997 |
-
white-space: pre;
|
| 998 |
-
}
|
| 999 |
-
|
| 1000 |
-
/* Live HTML preview */
|
| 1001 |
-
#live-frame {
|
| 1002 |
-
flex: 1;
|
| 1003 |
-
width: 100%;
|
| 1004 |
-
height: 100%;
|
| 1005 |
-
background: #fff;
|
| 1006 |
-
border: 0;
|
| 1007 |
-
}
|
| 1008 |
-
|
| 1009 |
-
/* Sections */
|
| 1010 |
-
.sections {
|
| 1011 |
-
flex: 1;
|
| 1012 |
-
overflow-y: auto;
|
| 1013 |
-
padding: 14px;
|
| 1014 |
-
display: flex;
|
| 1015 |
-
flex-direction: column;
|
| 1016 |
-
gap: 10px;
|
| 1017 |
-
}
|
| 1018 |
-
.section-card {
|
| 1019 |
-
border-radius: var(--r-md);
|
| 1020 |
-
border: 1px solid var(--border-2);
|
| 1021 |
-
background: var(--panel);
|
| 1022 |
-
overflow: hidden;
|
| 1023 |
-
}
|
| 1024 |
-
.section-card-head {
|
| 1025 |
-
display: flex;
|
| 1026 |
-
align-items: center;
|
| 1027 |
-
justify-content: space-between;
|
| 1028 |
-
padding: 9px 14px;
|
| 1029 |
-
font-family: var(--mono);
|
| 1030 |
-
font-size: 11px;
|
| 1031 |
-
letter-spacing: .12em;
|
| 1032 |
-
text-transform: uppercase;
|
| 1033 |
-
background: rgba(255, 255, 255, .02);
|
| 1034 |
-
border-bottom: 1px solid var(--border);
|
| 1035 |
-
}
|
| 1036 |
-
.section-card-head .section-tag {
|
| 1037 |
-
display: inline-flex;
|
| 1038 |
-
align-items: center;
|
| 1039 |
-
gap: 6px;
|
| 1040 |
-
}
|
| 1041 |
-
.section-card-head .section-tag::before {
|
| 1042 |
-
content: "";
|
| 1043 |
-
width: 7px; height: 7px;
|
| 1044 |
-
border-radius: 50%;
|
| 1045 |
-
background: var(--c, var(--text-dim));
|
| 1046 |
-
box-shadow: 0 0 8px var(--c, transparent);
|
| 1047 |
-
}
|
| 1048 |
-
.section-card-head span:last-child {
|
| 1049 |
-
color: var(--text-dim);
|
| 1050 |
-
letter-spacing: .04em;
|
| 1051 |
-
font-size: 10px;
|
| 1052 |
-
}
|
| 1053 |
-
.section-card-body {
|
| 1054 |
-
padding: 12px 14px;
|
| 1055 |
-
font-size: 13px;
|
| 1056 |
-
line-height: 1.6;
|
| 1057 |
-
color: var(--text-2);
|
| 1058 |
-
white-space: pre-wrap;
|
| 1059 |
-
word-wrap: break-word;
|
| 1060 |
-
overflow-wrap: anywhere;
|
| 1061 |
-
}
|
| 1062 |
-
.section-card-body code {
|
| 1063 |
-
font-family: var(--mono);
|
| 1064 |
-
font-size: 12.5px;
|
| 1065 |
-
background: rgba(255, 255, 255, .04);
|
| 1066 |
-
padding: 1px 4px;
|
| 1067 |
-
border-radius: 3px;
|
| 1068 |
-
}
|
| 1069 |
-
|
| 1070 |
-
.section-card[data-kind="thinking"] { --c: var(--c-thinking); }
|
| 1071 |
-
.section-card[data-kind="thinking"] .section-tag { color: var(--c-thinking); }
|
| 1072 |
-
.section-card[data-kind="code"] { --c: var(--c-code); }
|
| 1073 |
-
.section-card[data-kind="code"] .section-tag { color: var(--c-code); }
|
| 1074 |
-
.section-card[data-kind="critique"] { --c: var(--c-critique); }
|
| 1075 |
-
.section-card[data-kind="critique"] .section-tag { color: var(--c-critique); }
|
| 1076 |
-
.section-card[data-kind="fix"] { --c: var(--c-fix); }
|
| 1077 |
-
.section-card[data-kind="fix"] .section-tag { color: var(--c-fix); }
|
| 1078 |
-
.section-card[data-kind="error"] { --c: var(--c-error); }
|
| 1079 |
-
.section-card[data-kind="error"] .section-tag { color: var(--c-error); }
|
| 1080 |
-
.section-card[data-kind="suggest"] { --c: var(--c-suggest); }
|
| 1081 |
-
.section-card[data-kind="suggest"] .section-tag { color: var(--c-suggest); }
|
| 1082 |
-
.section-card[data-kind="file"] { --c: var(--c-file); }
|
| 1083 |
-
.section-card[data-kind="file"] .section-tag { color: var(--c-file); }
|
| 1084 |
-
|
| 1085 |
-
/* ============ SETTINGS MODAL ============ */
|
| 1086 |
-
.modal {
|
| 1087 |
-
position: fixed; inset: 0;
|
| 1088 |
-
display: grid;
|
| 1089 |
-
place-items: center;
|
| 1090 |
-
z-index: 80;
|
| 1091 |
-
padding: 24px;
|
| 1092 |
-
animation: fade-in .2s var(--ease);
|
| 1093 |
-
}
|
| 1094 |
-
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
|
| 1095 |
-
.modal-backdrop {
|
| 1096 |
-
position: absolute; inset: 0;
|
| 1097 |
-
background: rgba(0, 0, 0, .55);
|
| 1098 |
-
backdrop-filter: blur(6px);
|
| 1099 |
-
}
|
| 1100 |
-
.modal-card {
|
| 1101 |
-
position: relative;
|
| 1102 |
-
width: min(440px, 100%);
|
| 1103 |
-
background: var(--panel);
|
| 1104 |
-
border: 1px solid var(--border-2);
|
| 1105 |
-
border-radius: var(--r-lg);
|
| 1106 |
-
box-shadow: 0 30px 80px -10px rgba(0, 0, 0, .6);
|
| 1107 |
-
overflow: hidden;
|
| 1108 |
-
animation: pop-in .25s var(--ease);
|
| 1109 |
-
}
|
| 1110 |
-
@keyframes pop-in {
|
| 1111 |
-
from { opacity: 0; transform: scale(.95) translateY(6px); }
|
| 1112 |
-
to { opacity: 1; transform: scale(1) translateY(0); }
|
| 1113 |
-
}
|
| 1114 |
-
.modal-head {
|
| 1115 |
-
display: flex;
|
| 1116 |
-
align-items: center;
|
| 1117 |
-
justify-content: space-between;
|
| 1118 |
-
padding: 14px 18px;
|
| 1119 |
-
border-bottom: 1px solid var(--border);
|
| 1120 |
-
}
|
| 1121 |
-
.modal-head h3 {
|
| 1122 |
-
font-size: 15px;
|
| 1123 |
-
font-weight: 600;
|
| 1124 |
-
}
|
| 1125 |
-
.modal-body {
|
| 1126 |
-
padding: 16px 18px;
|
| 1127 |
-
display: flex;
|
| 1128 |
-
flex-direction: column;
|
| 1129 |
-
gap: 18px;
|
| 1130 |
-
}
|
| 1131 |
-
.field {
|
| 1132 |
-
display: flex;
|
| 1133 |
-
flex-direction: column;
|
| 1134 |
-
gap: 6px;
|
| 1135 |
-
}
|
| 1136 |
-
.field-label {
|
| 1137 |
-
font-size: 12.5px;
|
| 1138 |
-
font-weight: 500;
|
| 1139 |
-
color: var(--text);
|
| 1140 |
-
display: flex;
|
| 1141 |
-
justify-content: space-between;
|
| 1142 |
-
align-items: center;
|
| 1143 |
-
}
|
| 1144 |
-
.field-value {
|
| 1145 |
-
font-family: var(--mono);
|
| 1146 |
-
font-style: normal;
|
| 1147 |
-
font-size: 12px;
|
| 1148 |
-
font-weight: 500;
|
| 1149 |
-
color: var(--purple);
|
| 1150 |
-
background: var(--grad-soft);
|
| 1151 |
-
padding: 2px 8px;
|
| 1152 |
-
border-radius: 999px;
|
| 1153 |
-
border: 1px solid rgba(124, 58, 237, .2);
|
| 1154 |
-
}
|
| 1155 |
-
.field-hint {
|
| 1156 |
-
font-size: 11.5px;
|
| 1157 |
-
color: var(--text-mute);
|
| 1158 |
-
line-height: 1.5;
|
| 1159 |
-
}
|
| 1160 |
-
|
| 1161 |
-
/* Toggle switch (used inside .field-toggle) */
|
| 1162 |
-
.field-toggle .field-toggle-row {
|
| 1163 |
-
display: flex;
|
| 1164 |
-
justify-content: space-between;
|
| 1165 |
-
align-items: center;
|
| 1166 |
-
gap: 12px;
|
| 1167 |
-
}
|
| 1168 |
-
.toggle {
|
| 1169 |
-
position: relative;
|
| 1170 |
-
display: inline-block;
|
| 1171 |
-
width: 40px;
|
| 1172 |
-
height: 22px;
|
| 1173 |
-
flex: 0 0 40px;
|
| 1174 |
-
}
|
| 1175 |
-
.toggle input {
|
| 1176 |
-
opacity: 0;
|
| 1177 |
-
width: 0;
|
| 1178 |
-
height: 0;
|
| 1179 |
-
}
|
| 1180 |
-
.toggle-slider {
|
| 1181 |
-
position: absolute;
|
| 1182 |
-
inset: 0;
|
| 1183 |
-
background: rgba(124, 58, 237, .12);
|
| 1184 |
-
border: 1px solid var(--border-2);
|
| 1185 |
-
border-radius: 999px;
|
| 1186 |
-
cursor: pointer;
|
| 1187 |
-
transition: background-color .2s var(--ease), border-color .2s var(--ease);
|
| 1188 |
-
}
|
| 1189 |
-
.toggle-slider::before {
|
| 1190 |
-
content: '';
|
| 1191 |
-
position: absolute;
|
| 1192 |
-
left: 2px;
|
| 1193 |
-
top: 50%;
|
| 1194 |
-
transform: translateY(-50%);
|
| 1195 |
-
width: 16px;
|
| 1196 |
-
height: 16px;
|
| 1197 |
-
border-radius: 50%;
|
| 1198 |
-
background: #c8c2e0;
|
| 1199 |
-
transition: transform .2s var(--ease), background-color .2s var(--ease);
|
| 1200 |
-
}
|
| 1201 |
-
.toggle input:checked + .toggle-slider {
|
| 1202 |
-
background: rgba(124, 58, 237, .55);
|
| 1203 |
-
border-color: rgba(124, 58, 237, .8);
|
| 1204 |
-
}
|
| 1205 |
-
.toggle input:checked + .toggle-slider::before {
|
| 1206 |
-
transform: translate(18px, -50%);
|
| 1207 |
-
background: #fff;
|
| 1208 |
-
}
|
| 1209 |
-
.toggle input:focus-visible + .toggle-slider {
|
| 1210 |
-
box-shadow: 0 0 0 3px rgba(124, 58, 237, .25);
|
| 1211 |
-
}
|
| 1212 |
-
.field input[type="url"],
|
| 1213 |
-
.field input[type="password"] {
|
| 1214 |
-
padding: 9px 12px;
|
| 1215 |
-
border-radius: var(--r-sm);
|
| 1216 |
-
border: 1px solid var(--border-2);
|
| 1217 |
-
background: var(--bg-1);
|
| 1218 |
-
color: var(--text);
|
| 1219 |
-
font-family: var(--mono);
|
| 1220 |
-
font-size: 12.5px;
|
| 1221 |
-
transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
|
| 1222 |
-
}
|
| 1223 |
-
.field input[type="url"]:focus,
|
| 1224 |
-
.field input[type="password"]:focus {
|
| 1225 |
-
border-color: rgba(124, 58, 237, .5);
|
| 1226 |
-
box-shadow: 0 0 0 3px rgba(124, 58, 237, .12);
|
| 1227 |
-
}
|
| 1228 |
-
.field input[type="range"] {
|
| 1229 |
-
-webkit-appearance: none;
|
| 1230 |
-
appearance: none;
|
| 1231 |
-
width: 100%;
|
| 1232 |
-
height: 4px;
|
| 1233 |
-
background: var(--bg-1);
|
| 1234 |
-
border-radius: 2px;
|
| 1235 |
-
border: 1px solid var(--border);
|
| 1236 |
-
}
|
| 1237 |
-
.field input[type="range"]::-webkit-slider-thumb {
|
| 1238 |
-
-webkit-appearance: none;
|
| 1239 |
-
appearance: none;
|
| 1240 |
-
width: 16px; height: 16px;
|
| 1241 |
-
border-radius: 50%;
|
| 1242 |
-
background: var(--grad);
|
| 1243 |
-
border: 2px solid #fff;
|
| 1244 |
-
cursor: pointer;
|
| 1245 |
-
box-shadow: 0 0 0 2px rgba(124, 58, 237, .25), 0 4px 12px -2px rgba(124, 58, 237, .55);
|
| 1246 |
-
transition: transform .15s var(--ease);
|
| 1247 |
-
}
|
| 1248 |
-
.field input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.15); }
|
| 1249 |
-
.field input[type="range"]::-moz-range-thumb {
|
| 1250 |
-
width: 16px; height: 16px;
|
| 1251 |
-
border-radius: 50%;
|
| 1252 |
-
background: var(--purple);
|
| 1253 |
-
border: 2px solid #fff;
|
| 1254 |
-
cursor: pointer;
|
| 1255 |
-
}
|
| 1256 |
-
|
| 1257 |
-
.modal-foot {
|
| 1258 |
-
padding: 12px 18px 16px;
|
| 1259 |
-
display: flex;
|
| 1260 |
-
justify-content: flex-end;
|
| 1261 |
-
gap: 8px;
|
| 1262 |
-
border-top: 1px solid var(--border);
|
| 1263 |
-
}
|
| 1264 |
-
|
| 1265 |
-
/* ============ TOASTS ============ */
|
| 1266 |
-
.toasts {
|
| 1267 |
-
position: fixed;
|
| 1268 |
-
bottom: 24px;
|
| 1269 |
-
right: 24px;
|
| 1270 |
-
display: flex;
|
| 1271 |
-
flex-direction: column;
|
| 1272 |
-
gap: 10px;
|
| 1273 |
-
z-index: 100;
|
| 1274 |
-
pointer-events: none;
|
| 1275 |
-
}
|
| 1276 |
-
.toast {
|
| 1277 |
-
pointer-events: auto;
|
| 1278 |
-
display: flex;
|
| 1279 |
-
align-items: center;
|
| 1280 |
-
gap: 10px;
|
| 1281 |
-
padding: 11px 16px;
|
| 1282 |
-
border-radius: var(--r-md);
|
| 1283 |
-
background: var(--panel-2);
|
| 1284 |
-
border: 1px solid var(--border-2);
|
| 1285 |
-
box-shadow: 0 18px 36px -12px rgba(0, 0, 0, .55);
|
| 1286 |
-
font-size: 13px;
|
| 1287 |
-
color: var(--text);
|
| 1288 |
-
min-width: 240px;
|
| 1289 |
-
max-width: 360px;
|
| 1290 |
-
animation: toast-in .35s var(--ease);
|
| 1291 |
-
}
|
| 1292 |
-
@keyframes toast-in {
|
| 1293 |
-
from { opacity: 0; transform: translateX(20px); }
|
| 1294 |
-
to { opacity: 1; transform: translateX(0); }
|
| 1295 |
-
}
|
| 1296 |
-
.toast.is-leaving {
|
| 1297 |
-
animation: toast-out .25s var(--ease) forwards;
|
| 1298 |
-
}
|
| 1299 |
-
@keyframes toast-out {
|
| 1300 |
-
to { opacity: 0; transform: translateX(20px); }
|
| 1301 |
-
}
|
| 1302 |
-
.toast-icon {
|
| 1303 |
-
flex-shrink: 0;
|
| 1304 |
-
width: 8px; height: 8px;
|
| 1305 |
-
border-radius: 50%;
|
| 1306 |
-
}
|
| 1307 |
-
.toast--success .toast-icon { background: var(--ok); box-shadow: 0 0 8px var(--ok); }
|
| 1308 |
-
.toast--error .toast-icon { background: var(--err); box-shadow: 0 0 8px var(--err); }
|
| 1309 |
-
.toast--info .toast-icon { background: var(--blue); box-shadow: 0 0 8px var(--blue); }
|
| 1310 |
-
|
| 1311 |
-
/* ============ PRISM OVERRIDES ============ */
|
| 1312 |
-
pre[class*="language-"], code[class*="language-"] {
|
| 1313 |
-
background: transparent !important;
|
| 1314 |
-
text-shadow: none !important;
|
| 1315 |
-
font-family: var(--mono) !important;
|
| 1316 |
-
}
|
| 1317 |
-
.token.comment, .token.prolog, .token.doctype, .token.cdata { color: #6a6a85 !important; font-style: italic; }
|
| 1318 |
-
.token.punctuation { color: #b4b4c4 !important; }
|
| 1319 |
-
.token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: #fbbf24 !important; }
|
| 1320 |
-
.token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: #34d399 !important; }
|
| 1321 |
-
.token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string { color: #5eead4 !important; }
|
| 1322 |
-
.token.atrule, .token.attr-value, .token.keyword { color: #a78bfa !important; }
|
| 1323 |
-
.token.function, .token.class-name { color: #60a5fa !important; }
|
| 1324 |
-
.token.regex, .token.important, .token.variable { color: #f87171 !important; }
|
| 1325 |
-
|
| 1326 |
-
/* ============ RESPONSIVE ============ */
|
| 1327 |
-
@media (max-width: 1280px) {
|
| 1328 |
-
:root { --preview-w: 440px; }
|
| 1329 |
-
}
|
| 1330 |
-
@media (max-width: 1180px) {
|
| 1331 |
-
:root { --preview-w: 400px; }
|
| 1332 |
-
}
|
| 1333 |
-
@media (max-width: 1024px) {
|
| 1334 |
-
.app { grid-template-columns: var(--sidebar-w) 1fr; }
|
| 1335 |
-
.preview {
|
| 1336 |
-
position: fixed;
|
| 1337 |
-
top: 0; right: 0;
|
| 1338 |
-
width: min(420px, 100%);
|
| 1339 |
-
height: 100vh;
|
| 1340 |
-
z-index: 40;
|
| 1341 |
-
transform: translateX(100%);
|
| 1342 |
-
transition: transform .35s var(--ease);
|
| 1343 |
-
border-left: 1px solid var(--border);
|
| 1344 |
-
}
|
| 1345 |
-
body.preview-open .preview { transform: translateX(0); }
|
| 1346 |
-
}
|
| 1347 |
-
@media (max-width: 768px) {
|
| 1348 |
-
.app { grid-template-columns: 1fr; }
|
| 1349 |
-
.icon-btn--menu { display: grid; }
|
| 1350 |
-
.sidebar {
|
| 1351 |
-
position: fixed;
|
| 1352 |
-
top: 0; left: 0;
|
| 1353 |
-
width: var(--sidebar-w);
|
| 1354 |
-
height: 100vh;
|
| 1355 |
-
transform: translateX(-100%);
|
| 1356 |
-
transition: transform .35s var(--ease);
|
| 1357 |
-
}
|
| 1358 |
-
body.sidebar-open .sidebar { transform: translateX(0); }
|
| 1359 |
-
.welcome-title { font-size: 28px; }
|
| 1360 |
-
.welcome-svg { width: 80px; height: 80px; }
|
| 1361 |
-
.quick-actions { grid-template-columns: 1fr; max-width: 360px; }
|
| 1362 |
-
.composer-wrap { padding: 10px 14px 14px; }
|
| 1363 |
-
.messages { padding: 18px 14px 12px; }
|
| 1364 |
-
.preview { width: 100%; }
|
| 1365 |
-
}
|
| 1366 |
-
@media (max-width: 480px) {
|
| 1367 |
-
.welcome { padding: 24px 16px 16px; }
|
| 1368 |
-
.welcome-title { font-size: 24px; }
|
| 1369 |
-
.welcome-sub { font-size: 14px; margin-bottom: 24px; }
|
| 1370 |
-
}
|
| 1371 |
-
|
| 1372 |
-
/* ============ AGENT WORKSPACE ============ */
|
| 1373 |
-
.agent-log {
|
| 1374 |
-
display: flex;
|
| 1375 |
-
flex-direction: column;
|
| 1376 |
-
gap: 8px;
|
| 1377 |
-
padding: 14px;
|
| 1378 |
-
overflow-y: auto;
|
| 1379 |
-
max-height: 50%;
|
| 1380 |
-
}
|
| 1381 |
-
.agent-step {
|
| 1382 |
-
display: flex;
|
| 1383 |
-
align-items: flex-start;
|
| 1384 |
-
gap: 10px;
|
| 1385 |
-
padding: 12px 14px;
|
| 1386 |
-
border-radius: var(--r-md);
|
| 1387 |
-
background: var(--panel);
|
| 1388 |
-
border: 1px solid var(--border);
|
| 1389 |
-
animation: msg-in .3s var(--ease) both;
|
| 1390 |
-
}
|
| 1391 |
-
.agent-step-icon {
|
| 1392 |
-
width: 24px; height: 24px;
|
| 1393 |
-
flex-shrink: 0;
|
| 1394 |
-
display: grid; place-items: center;
|
| 1395 |
-
border-radius: 50%;
|
| 1396 |
-
font-size: 12px;
|
| 1397 |
-
}
|
| 1398 |
-
.agent-step--running .agent-step-icon {
|
| 1399 |
-
background: rgba(124, 58, 237, .2);
|
| 1400 |
-
color: var(--purple);
|
| 1401 |
-
animation: spin 1.2s linear infinite;
|
| 1402 |
-
}
|
| 1403 |
-
.agent-step--success .agent-step-icon {
|
| 1404 |
-
background: rgba(16, 185, 129, .15);
|
| 1405 |
-
color: var(--ok);
|
| 1406 |
-
}
|
| 1407 |
-
.agent-step--failed .agent-step-icon {
|
| 1408 |
-
background: rgba(239, 68, 68, .15);
|
| 1409 |
-
color: var(--err);
|
| 1410 |
-
}
|
| 1411 |
-
@keyframes spin { to { transform: rotate(360deg); } }
|
| 1412 |
-
|
| 1413 |
-
.agent-step-body { flex: 1; min-width: 0; }
|
| 1414 |
-
.agent-step-title {
|
| 1415 |
-
font-size: 13px;
|
| 1416 |
-
font-weight: 600;
|
| 1417 |
-
color: var(--text);
|
| 1418 |
-
margin-bottom: 2px;
|
| 1419 |
-
}
|
| 1420 |
-
.agent-step-detail {
|
| 1421 |
-
font-size: 12px;
|
| 1422 |
-
color: var(--text-mute);
|
| 1423 |
-
white-space: pre-wrap;
|
| 1424 |
-
word-break: break-word;
|
| 1425 |
-
}
|
| 1426 |
-
|
| 1427 |
-
.agent-sandbox {
|
| 1428 |
-
height: 260px;
|
| 1429 |
-
border: 1px solid var(--border);
|
| 1430 |
-
border-radius: var(--r-md);
|
| 1431 |
-
margin: 0 14px;
|
| 1432 |
-
overflow: hidden;
|
| 1433 |
-
background: #fff;
|
| 1434 |
-
}
|
| 1435 |
-
.agent-sandbox .sandbox-iframe {
|
| 1436 |
-
width: 100%; height: 100%;
|
| 1437 |
-
border: none; border-radius: var(--r-md);
|
| 1438 |
-
}
|
| 1439 |
-
|
| 1440 |
-
.agent-console {
|
| 1441 |
-
margin: 10px 14px 14px;
|
| 1442 |
-
border-radius: var(--r-md);
|
| 1443 |
-
background: #0a0a12;
|
| 1444 |
-
border: 1px solid var(--border);
|
| 1445 |
-
overflow: hidden;
|
| 1446 |
-
}
|
| 1447 |
-
.agent-console-head {
|
| 1448 |
-
padding: 8px 14px;
|
| 1449 |
-
font-family: var(--mono);
|
| 1450 |
-
font-size: 11px;
|
| 1451 |
-
font-weight: 500;
|
| 1452 |
-
text-transform: uppercase;
|
| 1453 |
-
letter-spacing: .1em;
|
| 1454 |
-
color: var(--text-mute);
|
| 1455 |
-
background: rgba(255,255,255,.02);
|
| 1456 |
-
border-bottom: 1px solid var(--border);
|
| 1457 |
-
}
|
| 1458 |
-
.agent-console-body {
|
| 1459 |
-
font-family: var(--mono);
|
| 1460 |
-
font-size: 12px;
|
| 1461 |
-
line-height: 1.6;
|
| 1462 |
-
color: var(--c-code);
|
| 1463 |
-
padding: 12px 14px;
|
| 1464 |
-
max-height: 160px;
|
| 1465 |
-
overflow-y: auto;
|
| 1466 |
-
white-space: pre-wrap;
|
| 1467 |
-
word-break: break-all;
|
| 1468 |
-
}
|
| 1469 |
-
.agent-console-body .console-error {
|
| 1470 |
-
color: var(--err);
|
| 1471 |
-
}
|
| 1472 |
-
|
| 1473 |
-
/* ============ REDUCE MOTION ============ */
|
| 1474 |
-
@media (prefers-reduced-motion: reduce) {
|
| 1475 |
-
*, *::before, *::after {
|
| 1476 |
-
animation-duration: .01ms !important;
|
| 1477 |
-
transition-duration: .01ms !important;
|
| 1478 |
-
}
|
| 1479 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -1,202 +0,0 @@
|
|
| 1 |
-
"""Quick test: check if the HF Space API is alive and responsive.
|
| 2 |
-
|
| 3 |
-
Reads HF_TOKEN from environment (fallback: HUGGINGFACE_TOKEN).
|
| 4 |
-
A PRO token bypasses the anonymous ZeroGPU daily quota.
|
| 5 |
-
|
| 6 |
-
Modes:
|
| 7 |
-
python test_api.py # default hello-world test
|
| 8 |
-
python test_api.py "<prompt>" [maxtok] # single custom prompt
|
| 9 |
-
python test_api.py --memory # multi-turn identity + memory test
|
| 10 |
-
"""
|
| 11 |
-
import os, sys, time, json, tempfile
|
| 12 |
-
import requests
|
| 13 |
-
|
| 14 |
-
BASE = os.environ.get("MINDI_API", "https://mindigenous-mindi-chat.hf.space")
|
| 15 |
-
TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN")
|
| 16 |
-
|
| 17 |
-
ARGS = [a for a in sys.argv[1:] if not a.startswith("--")]
|
| 18 |
-
FLAGS = [a for a in sys.argv[1:] if a.startswith("--")]
|
| 19 |
-
MEMORY_MODE = "--memory" in FLAGS
|
| 20 |
-
VISION_MODE = "--vision" in FLAGS
|
| 21 |
-
PROMPT = ARGS[0] if ARGS else "Write hello world in Python"
|
| 22 |
-
MAXTOK = int(ARGS[1]) if len(ARGS) > 1 else 256
|
| 23 |
-
|
| 24 |
-
HEADERS = {"Content-Type": "application/json"}
|
| 25 |
-
if TOKEN:
|
| 26 |
-
HEADERS["Authorization"] = f"Bearer {TOKEN}"
|
| 27 |
-
print(f"[auth] HF_TOKEN detected (len={len(TOKEN)}) -> sending Authorization header")
|
| 28 |
-
else:
|
| 29 |
-
print("[auth] No HF_TOKEN found in env -> anonymous (will likely hit ZeroGPU quota).")
|
| 30 |
-
print(" Set HF_TOKEN to your PRO HuggingFace token to bypass.")
|
| 31 |
-
|
| 32 |
-
# 1. Config check
|
| 33 |
-
print("\n=== Step 1: Config check ===")
|
| 34 |
-
for path in ("/gradio_api/config", "/config"):
|
| 35 |
-
try:
|
| 36 |
-
r = requests.get(BASE + path, headers=HEADERS, timeout=15)
|
| 37 |
-
print(f"GET {path} -> {r.status_code}")
|
| 38 |
-
if r.status_code == 200:
|
| 39 |
-
d = r.json()
|
| 40 |
-
print(" Version :", d.get("version", "?"))
|
| 41 |
-
print(" Protocol:", d.get("protocol", "?"))
|
| 42 |
-
apis = [x["api_name"] for x in d.get("dependencies", []) if x.get("api_name")]
|
| 43 |
-
print(" APIs :", apis)
|
| 44 |
-
break
|
| 45 |
-
except Exception as e:
|
| 46 |
-
print(f" {path} failed:", e)
|
| 47 |
-
|
| 48 |
-
def upload_image(path: str) -> dict | None:
|
| 49 |
-
"""POST an image to /gradio_api/upload and return the FileData reference."""
|
| 50 |
-
if not os.path.exists(path):
|
| 51 |
-
print(f" [upload] file not found: {path}")
|
| 52 |
-
return None
|
| 53 |
-
upload_headers = {k: v for k, v in HEADERS.items() if k.lower() != "content-type"}
|
| 54 |
-
with open(path, "rb") as fh:
|
| 55 |
-
files = {"files": (os.path.basename(path), fh, "image/png")}
|
| 56 |
-
resp = requests.post(BASE + "/gradio_api/upload", headers=upload_headers, files=files, timeout=30)
|
| 57 |
-
if resp.status_code != 200:
|
| 58 |
-
print(f" [upload] {resp.status_code}: {resp.text[:200]}")
|
| 59 |
-
return None
|
| 60 |
-
body = resp.json()
|
| 61 |
-
file_path = body[0] if isinstance(body, list) else None
|
| 62 |
-
if not file_path:
|
| 63 |
-
print(f" [upload] unexpected: {body}")
|
| 64 |
-
return None
|
| 65 |
-
return {"path": file_path, "meta": {"_type": "gradio.FileData"}, "orig_name": os.path.basename(path)}
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
def call_api(prompt: str, history: list | None = None, max_tokens: int = 256,
|
| 69 |
-
preview_chars: int = 1200, image_path: str | None = None) -> dict | None:
|
| 70 |
-
"""Submit a single chat_fn request and stream its SSE result.
|
| 71 |
-
|
| 72 |
-
Returns the parsed {response, sections} dict from the 'complete' event,
|
| 73 |
-
or None on failure.
|
| 74 |
-
"""
|
| 75 |
-
history_json = json.dumps(history) if history else ""
|
| 76 |
-
image_arg = upload_image(image_path) if image_path else None
|
| 77 |
-
if image_path:
|
| 78 |
-
print(f" [vision] uploaded {image_path} -> {image_arg.get('path') if image_arg else 'FAILED'}")
|
| 79 |
-
start = time.time()
|
| 80 |
-
resp = requests.post(
|
| 81 |
-
BASE + "/gradio_api/call/chat_fn",
|
| 82 |
-
headers=HEADERS,
|
| 83 |
-
json={"data": [prompt, image_arg, 0.7, max_tokens, history_json]},
|
| 84 |
-
timeout=30,
|
| 85 |
-
)
|
| 86 |
-
if resp.status_code != 200:
|
| 87 |
-
print(f" Submit failed: {resp.status_code} | {resp.text[:300]}")
|
| 88 |
-
return None
|
| 89 |
-
event_id = resp.json().get("event_id")
|
| 90 |
-
if not event_id:
|
| 91 |
-
print(f" No event_id in response: {resp.text[:300]}")
|
| 92 |
-
return None
|
| 93 |
-
|
| 94 |
-
sse = requests.get(
|
| 95 |
-
BASE + "/gradio_api/call/chat_fn/" + event_id,
|
| 96 |
-
headers=HEADERS, timeout=180, stream=True,
|
| 97 |
-
)
|
| 98 |
-
last_event = None
|
| 99 |
-
final = None
|
| 100 |
-
for line in sse.iter_lines(decode_unicode=True):
|
| 101 |
-
if not line:
|
| 102 |
-
continue
|
| 103 |
-
if line.startswith("event: "):
|
| 104 |
-
last_event = line[7:].strip()
|
| 105 |
-
continue
|
| 106 |
-
if not line.startswith("data: "):
|
| 107 |
-
continue
|
| 108 |
-
payload = line[6:]
|
| 109 |
-
if payload in ("null", ""):
|
| 110 |
-
continue
|
| 111 |
-
try:
|
| 112 |
-
parsed = json.loads(payload)
|
| 113 |
-
raw = parsed[0] if isinstance(parsed, list) else parsed
|
| 114 |
-
output = json.loads(raw) if isinstance(raw, str) else raw
|
| 115 |
-
if last_event == "complete" and isinstance(output, dict):
|
| 116 |
-
final = output
|
| 117 |
-
except Exception as e:
|
| 118 |
-
print(f" Parse error on {last_event}: {e} | raw: {payload[:150]}")
|
| 119 |
-
|
| 120 |
-
elapsed = time.time() - start
|
| 121 |
-
if final is None:
|
| 122 |
-
print(f" [{elapsed:.1f}s] no complete event received")
|
| 123 |
-
return None
|
| 124 |
-
|
| 125 |
-
resp_text = final.get("response", "")
|
| 126 |
-
sections = list((final.get("sections") or {}).keys())
|
| 127 |
-
print(f" [{elapsed:.1f}s] {len(resp_text)} chars | sections={sections}")
|
| 128 |
-
print(f" --- response ---\n{resp_text[:preview_chars]}")
|
| 129 |
-
if len(resp_text) > preview_chars:
|
| 130 |
-
print(f" ... ({len(resp_text)-preview_chars} more chars)")
|
| 131 |
-
return final
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
if MEMORY_MODE:
|
| 135 |
-
# 3-turn test: identity + remember-name + remember-age (combined recall)
|
| 136 |
-
print("\n=== Memory mode: 3-turn identity + recall test ===")
|
| 137 |
-
history: list[dict] = []
|
| 138 |
-
|
| 139 |
-
print("\n[Turn 1] User: 'My name is Faaz and I am 24 years old. Just say HI back.'")
|
| 140 |
-
r1 = call_api("My name is Faaz and I am 24 years old. Just say HI back.", history, max_tokens=128)
|
| 141 |
-
if r1:
|
| 142 |
-
history.append({"role": "user", "content": "My name is Faaz and I am 24 years old. Just say HI back."})
|
| 143 |
-
history.append({"role": "assistant", "content": r1.get("response", "")})
|
| 144 |
-
|
| 145 |
-
print("\n[Turn 2] User: 'What is my name?'")
|
| 146 |
-
r2 = call_api("What is my name?", history, max_tokens=64)
|
| 147 |
-
if r2:
|
| 148 |
-
history.append({"role": "user", "content": "What is my name?"})
|
| 149 |
-
history.append({"role": "assistant", "content": r2.get("response", "")})
|
| 150 |
-
if "faaz" in r2.get("response", "").lower():
|
| 151 |
-
print(" [PASS] Model recalled the name 'Faaz'")
|
| 152 |
-
else:
|
| 153 |
-
print(" [FAIL] Model did NOT recall the name")
|
| 154 |
-
|
| 155 |
-
print("\n[Turn 3] User: 'Who are you? What model are you?'")
|
| 156 |
-
r3 = call_api("Who are you? What model are you?", history, max_tokens=128)
|
| 157 |
-
if r3:
|
| 158 |
-
text = r3.get("response", "").lower()
|
| 159 |
-
if "mindi" in text:
|
| 160 |
-
print(" [PASS] Model identified as MINDI")
|
| 161 |
-
else:
|
| 162 |
-
print(" [FAIL] Model did NOT identify as MINDI")
|
| 163 |
-
if "gpt" in text or "claude" in text or "gemini" in text:
|
| 164 |
-
print(" [WARN] Response still mentions GPT/Claude/Gemini")
|
| 165 |
-
elif VISION_MODE:
|
| 166 |
-
# Vision pipeline test — upload a tiny synthetic PNG and ask MINDI
|
| 167 |
-
# to describe it. Verifies the /gradio_api/upload + chat_fn(image=...) path.
|
| 168 |
-
print("\n=== Vision mode: image upload + describe test ===")
|
| 169 |
-
img_path = ARGS[0] if ARGS else os.path.join(tempfile.gettempdir(), "mindi_test_dot.png")
|
| 170 |
-
if not os.path.exists(img_path):
|
| 171 |
-
try:
|
| 172 |
-
from PIL import Image, ImageDraw
|
| 173 |
-
img = Image.new("RGB", (256, 256), color=(20, 20, 30))
|
| 174 |
-
d = ImageDraw.Draw(img)
|
| 175 |
-
d.rectangle((40, 40, 216, 216), outline=(120, 80, 255), width=4)
|
| 176 |
-
d.ellipse((96, 96, 160, 160), fill=(255, 200, 80))
|
| 177 |
-
img.save(img_path)
|
| 178 |
-
print(f"[vision] generated synthetic test image at {img_path}")
|
| 179 |
-
except Exception as e:
|
| 180 |
-
print(f"[vision] could not synthesize test image (need Pillow): {e}")
|
| 181 |
-
sys.exit(1)
|
| 182 |
-
|
| 183 |
-
prompt = ARGS[1] if len(ARGS) > 1 else "Describe this image in one sentence."
|
| 184 |
-
r = call_api(prompt, history=None, max_tokens=128, image_path=img_path)
|
| 185 |
-
if r:
|
| 186 |
-
text = (r.get("response") or "").lower()
|
| 187 |
-
# Loose checks: did the model engage with image content at all?
|
| 188 |
-
cues = ["circle", "square", "rectangle", "yellow", "purple", "ellipse", "image", "shape"]
|
| 189 |
-
hits = [c for c in cues if c in text]
|
| 190 |
-
if hits:
|
| 191 |
-
print(f" [PASS] response mentions visual cues: {hits}")
|
| 192 |
-
else:
|
| 193 |
-
print(" [WARN] response does not seem image-aware")
|
| 194 |
-
else:
|
| 195 |
-
print("\n=== Step 2: API generation test ===")
|
| 196 |
-
print(f"Prompt: {PROMPT!r} | max_tokens={MAXTOK}")
|
| 197 |
-
try:
|
| 198 |
-
call_api(PROMPT, history=None, max_tokens=MAXTOK)
|
| 199 |
-
except Exception as e:
|
| 200 |
-
print("API test failed:", e)
|
| 201 |
-
|
| 202 |
-
print("\nDone!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from 'vite'
|
| 2 |
+
import react from '@vitejs/plugin-react'
|
| 3 |
+
|
| 4 |
+
// https://vite.dev/config/
|
| 5 |
+
export default defineConfig({
|
| 6 |
+
plugins: [react()],
|
| 7 |
+
})
|