* fix(ai): add icon name aliases and fix multi-path SVG concatenation

Add 55+ common icon name aliases (burger→hamburger, sushi→fish, etc.)
to both client icon-resolver and server icon API for robust AI-generated
icon resolution. Register Lucide's own aliases for broader coverage.

Fix SVG path concatenation bug where joining multiple <path> d-values
caused incorrect rendering — a standalone <path> treats initial lowercase
"m" as absolute, but after concatenation it becomes relative to the
previous sub-path endpoint. Now ensures each sub-path starts with
absolute "M".

Add tryAsyncIconFontResolution for icon_font nodes that miss local
lookup — fetches from server API, caches result, and triggers canvas
re-render.

* fix(canvas): preserve badge/overlay absolute positioning in auto-layout

Add isBadgeOverlayNode() detector for badge, indicator, notification-dot,
and overlay nodes. These nodes now retain their x/y coordinates instead
of being stripped by layout sanitization.

Update computeLayoutPositions to exclude badge nodes from the layout flow
— they keep absolute positioning and render on top (prepended for correct
z-order in reverse iteration).

* fix(ai): prevent duplicate canvas objects and fix emoji-to-icon pipeline

Streaming path: add ensureUniqueNodeIds before inserting nodes to prevent
ID collisions across multiple AI generations. Track newly inserted IDs
so subsequent streaming nodes don't collide either.

Canvas sync: deduplicate Fabric objects sharing the same penNodeId —
keep only the one tracked in objMap, remove stale duplicates.

Badge nodes: use shared isBadgeOverlayNode() for z-order insertion
and skip x/y stripping in layout parents.

Fix emoji-to-icon pipeline: re-run applyIconPathResolution after
applyNoEmojiIconHeuristic converts emoji text nodes to path nodes,
so the icon resolver can match by name (e.g. "Pizza Emoji Path" → pizza).

* fix(canvas): add async icon resolution fallback for icon_font nodes

When lookupIconByName fails locally, queue tryAsyncIconFontResolution
to fetch from server API. Cache result in ICON_PATH_MAP and trigger
canvas re-render via store update. Store iconFontName and iconStyle
on Fabric object for sync tracking.

* fix(ai): strengthen emoji ban in prompts and improve orchestrator defaults

Update all AI prompts to explicitly ban emoji characters with concrete
examples and redirect to icon_font nodes instead of the previously
incorrect "path nodes" guidance.

Add z-order rule to orchestrator prompt: overlay elements must come
before content they overlap.

Add padding support to OrchestratorPlan rootFrame type. Default mobile
root frame gap to 16 for consistent spacing.

* feat(electron): add publisher name to Windows build configuration

Updated the `electron-builder.yml` to include a publisher name for Windows builds, enhancing the identification of the application during installation. Additionally, revised the README files across multiple languages to reflect the new project description and features, emphasizing OpenPencil as the world's first AI-native open-source vector design tool with concurrent agent teams and design-as-code capabilities.

---------

Co-authored-by: Fini <fini.yang@gmail.com>
This commit is contained in:
Kayshen Xu 2026-03-11 21:18:49 +08:00 committed by GitHub
parent 669c0dd38d
commit 4af8ef412b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
51 changed files with 2542 additions and 438 deletions

70
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View file

@ -0,0 +1,70 @@
name: Bug Report
description: Report a bug or unexpected behavior
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to report a bug! Please fill in the details below.
- type: input
id: version
attributes:
label: Version
description: OpenPencil version (check Help > About or `package.json`)
placeholder: e.g. 0.3.2
validations:
required: true
- type: dropdown
id: platform
attributes:
label: Platform
options:
- Web (browser)
- macOS (Electron)
- Windows (Electron)
- Linux (Electron)
validations:
required: true
- type: textarea
id: description
attributes:
label: Bug Description
description: A clear description of what the bug is.
placeholder: What happened?
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: Minimal steps to reproduce the behavior.
placeholder: |
1. Open the editor
2. Click on '...'
3. See error
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What you expected to happen.
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots / Recordings
description: If applicable, add screenshots or screen recordings.
- type: textarea
id: context
attributes:
label: Additional Context
description: Any other context — browser version, OS version, error logs, etc.

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Discord
url: https://discord.gg/KwXp6BJD
about: Ask questions and chat with the community

View file

@ -0,0 +1,52 @@
name: Feature Request
description: Suggest a new feature or improvement
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Have an idea to make OpenPencil better? We'd love to hear it!
- type: textarea
id: problem
attributes:
label: Problem
description: What problem does this feature solve? What's the pain point?
placeholder: I'm always frustrated when...
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: Describe your ideal solution.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Any alternative solutions or workarounds you've considered.
- type: dropdown
id: area
attributes:
label: Area
options:
- Canvas / Drawing
- AI / Generation
- MCP Server
- Design System / Variables
- Code Generation
- Electron / Desktop
- Figma Import
- UI / Panels
- Other
- type: textarea
id: context
attributes:
label: Additional Context
description: Mockups, screenshots, references, or any other context.

32
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,32 @@
## Summary
<!-- What does this PR do? Keep it brief — 1-3 sentences. -->
## Changes
<!-- Bullet list of key changes. -->
-
## Related Issues
<!-- Link any related issues: Fixes #123, Closes #456 -->
## Type
<!-- Check the one that applies. -->
- [ ] `feat` — New feature
- [ ] `fix` — Bug fix
- [ ] `refactor` — Code refactoring (no behavior change)
- [ ] `perf` — Performance improvement
- [ ] `docs` — Documentation
- [ ] `test` — Tests
- [ ] `chore` — Build / tooling / dependencies
## Checklist
- [ ] `npx tsc --noEmit` passes
- [ ] `bun --bun run test` passes
- [ ] No unrelated changes included
- [ ] Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/)

View file

@ -63,6 +63,8 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>KI-natives Open-Source-Designwerkzeug. Design-as-Code.</strong><br />
Vom Prompt zur Canvas-UI. Multi-Agenten-Orchestrierung. Eingebauter MCP-Server. Codegenerierung.
<strong>Das weltweit erste KI-native Open-Source-Vektordesign-Werkzeug.</strong><br />
<sub>Parallele Agententeams &bull; Design-as-Code &bull; Eingebauter MCP-Server &bull; Multi-Modell-Intelligenz</sub>
</p>
<p align="center">
@ -14,31 +14,76 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#schnellstart">Schnellstart</a> ·
<a href="#ki-natives-design">KI</a> ·
<a href="#funktionen">Funktionen</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#mitwirken">Mitwirken</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — click to watch demo" width="100%" />
<img src="./screenshot/op-cover.png" alt="OpenPencil — Klicken, um das Demo-Video anzusehen" width="100%" />
</a>
</p>
<p align="center"><sub>Auf das Bild klicken, um das Demo-Video anzusehen</sub></p>
<br />
## Warum OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Prompt → Canvas
Beschreiben Sie jede UI in natürlicher Sprache. Beobachten Sie, wie sie in Echtzeit mit Streaming-Animation auf der unendlichen Canvas erscheint. Ändern Sie bestehende Designs, indem Sie Elemente auswählen und chatten.
</td>
<td width="50%">
### 🤖 Parallele Agententeams
Der Orchestrierer zerlegt komplexe Seiten in räumliche Teilaufgaben. Mehrere KI-Agenten arbeiten gleichzeitig an verschiedenen Bereichen — Hero, Features, Footer — alle parallel streamend.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 Multi-Modell-Intelligenz
Passt sich automatisch an die Fähigkeiten jedes Modells an. Claude erhält vollständige Prompts mit Thinking; GPT-4o/Gemini deaktivieren Thinking; kleinere Modelle (MiniMax, Qwen, Llama) erhalten vereinfachte Prompts für zuverlässige Ausgabe.
</td>
<td width="50%">
### 🔌 MCP-Server
Ein-Klick-Installation in Claude Code, Codex, Gemini, OpenCode, Kiro oder Copilot CLIs. Designen Sie aus Ihrem Terminal — `.op`-Dateien über jeden MCP-kompatiblen Agenten lesen, erstellen und bearbeiten.
</td>
</tr>
<tr>
<td width="50%">
### 📦 Design-as-Code
`.op`-Dateien sind JSON — menschenlesbar, Git-freundlich, diff-fähig. Designvariablen generieren CSS Custom Properties. Code-Export nach React + Tailwind oder HTML + CSS.
</td>
<td width="50%">
### 🖥️ Läuft überall
Web-App + native Desktop-Anwendung auf macOS, Windows und Linux über Electron. Auto-Updates über GitHub Releases. `.op`-Dateizuordnung — Doppelklick zum Öffnen.
</td>
</tr>
</table>
## Schnellstart
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## KI-natives Design
OpenPencil wurde von Grund auf mit KI im Kern aufgebaut — nicht als Plugin, sondern als zentraler Arbeitsablauf.
**Vom Prompt zur UI**
- **Text-zu-Design** — eine Seite beschreiben und sie wird in Echtzeit mit Streaming-Animation auf der Canvas generiert
- **Orchestrierer** — zerlegt komplexe Seiten in räumliche Teilaufgaben zur parallelen Generierung
@ -74,10 +117,16 @@ OpenPencil wurde von Grund auf mit KI im Kern aufgebaut — nicht als Plugin, so
| **Claude Code** | Keine Konfiguration — verwendet Claude Agent SDK mit lokalem OAuth |
| **Codex CLI** | In den Agenteneinstellungen verbinden (`Cmd+,`) |
| **OpenCode** | In den Agenteneinstellungen verbinden (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` dann in den Agenteneinstellungen verbinden (`Cmd+,`) |
**Modell-Fähigkeitsprofile** — passt Prompts, Thinking-Modus und Timeouts automatisch pro Modellstufe an. Modelle der Vollstufe (Claude) erhalten vollständige Prompts; Standardstufe (GPT-4o, Gemini, DeepSeek) deaktiviert Thinking; Basisstufe (MiniMax, Qwen, Llama, Mistral) erhält vereinfachte verschachtelte JSON-Prompts für maximale Zuverlässigkeit.
**MCP-Server**
- Eingebauter MCP-Server — Ein-Klick-Installation in Claude Code / Codex / Gemini / OpenCode / Kiro CLIs
- Eingebauter MCP-Server — Ein-Klick-Installation in Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
- Design-Automatisierung vom Terminal aus: `.op`-Dateien über jeden MCP-kompatiblen Agenten lesen, erstellen und bearbeiten
- **Mehrstufiger Design-Workflow**`design_skeleton``design_content``design_refine` für hochwertigere mehrteilige Designs
- **Segmentierter Prompt-Abruf** — laden Sie nur das benötigte Design-Wissen (Schema, Layout, Rollen, Icons, Planung usw.)
- Mehrseitige Unterstützung — Seiten erstellen, umbenennen, neu ordnen und duplizieren über MCP-Tools
**Codegenerierung**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil wurde von Grund auf mit KI im Kern aufgebaut — nicht als Plugin, so
**Desktop-App**
- Natives macOS, Windows und Linux über Electron
- `.op`-Dateizuordnung — Doppelklick zum Öffnen, Einzelinstanzsperre
- Automatische Aktualisierung über GitHub Releases
- Natives Anwendungsmenü und Dateidialoge
@ -117,7 +167,7 @@ OpenPencil wurde von Grund auf mit KI im Kern aufgebaut — nicht als Plugin, so
| **Zustand** | Zustand v5 |
| **Server** | Nitro |
| **Desktop** | Electron 35 |
| **KI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **KI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **Laufzeit** | Bun · Vite 7 |
| **Dateiformat** | `.op` — JSON-basiert, menschenlesbar, Git-freundlich |
@ -136,7 +186,7 @@ src/
uikit/ Wiederverwendbares Komponenten-Kit-System
server/
api/ai/ Nitro-API — Streaming-Chat, Generierung, Validierung
utils/ Claude CLI, OpenCode, Codex-Client-Wrapper
utils/ Claude CLI, OpenCode, Codex, Copilot-Client-Wrapper
electron/
main.ts Fenster, Nitro-Fork, natives Menü, Auto-Updater
preload.ts IPC-Brücke
@ -186,10 +236,11 @@ Beiträge sind willkommen! Siehe [CLAUDE.md](./CLAUDE.md) für Architekturdetail
- [x] Designvariablen & Tokens mit CSS-Synchronisierung
- [x] Komponentensystem (Instanzen & Überschreibungen)
- [x] KI-Designgenerierung mit Orchestrierer
- [x] MCP-Server-Integration
- [x] MCP-Server-Integration mit mehrstufigem Design-Workflow
- [x] Mehrseitige Unterstützung
- [x] Figma-`.fig`-Import
- [x] Boolesche Operationen (Vereinigung, Subtraktion, Schnittmenge)
- [x] Multi-Modell-Fähigkeitsprofile
- [ ] Kollaboratives Bearbeiten
- [ ] Plugin-System
@ -207,6 +258,17 @@ Beiträge sind willkommen! Siehe [CLAUDE.md](./CLAUDE.md) für Architekturdetail
</a>
— Fragen stellen, Designs teilen, Funktionen vorschlagen.
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## Lizenz
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>Herramienta de diseño de código abierto nativa de IA. Diseño como Código.</strong><br />
De prompt a interfaz en el lienzo. Orquestación multiagente. Servidor MCP integrado. Generación de código.
<strong>La primera herramienta de diseño vectorial de código abierto nativa de IA del mundo.</strong><br />
<sub>Equipos de Agentes Concurrentes &bull; Diseño como Código &bull; Servidor MCP Integrado &bull; Inteligencia Multimodelo</sub>
</p>
<p align="center">
@ -14,31 +14,76 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#inicio-rápido">Inicio Rápido</a> ·
<a href="#diseño-nativo-de-ia">IA</a> ·
<a href="#características">Características</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contribuir">Contribuir</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — click to watch demo" width="100%" />
<img src="./screenshot/op-cover.png" alt="OpenPencil — haz clic para ver la demostración" width="100%" />
</a>
</p>
<p align="center"><sub>Haz clic en la imagen para ver el video de demostración</sub></p>
<br />
## Por Qué OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Prompt → Lienzo
Describe cualquier interfaz en lenguaje natural. Obsérvala aparecer en el lienzo infinito en tiempo real con animación de transmisión. Modifica diseños existentes seleccionando elementos y chateando.
</td>
<td width="50%">
### 🤖 Equipos de Agentes Concurrentes
El orquestador descompone páginas complejas en subtareas espaciales. Múltiples agentes de IA trabajan en diferentes secciones simultáneamente — hero, características, footer — todos transmitiendo en paralelo.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 Inteligencia Multimodelo
Se adapta automáticamente a las capacidades de cada modelo. Claude recibe prompts completos con pensamiento; GPT-4o/Gemini desactivan el pensamiento; modelos más pequeños (MiniMax, Qwen, Llama) reciben prompts simplificados para una salida confiable.
</td>
<td width="50%">
### 🔌 Servidor MCP
Instalación con un clic en Claude Code, Codex, Gemini, OpenCode, Kiro o Copilot CLIs. Diseña desde tu terminal — lee, crea y modifica archivos `.op` a través de cualquier agente compatible con MCP.
</td>
</tr>
<tr>
<td width="50%">
### 📦 Diseño como Código
Los archivos `.op` son JSON — legibles por humanos, compatibles con Git, comparables. Las variables de diseño generan propiedades personalizadas CSS. Exportación de código a React + Tailwind o HTML + CSS.
</td>
<td width="50%">
### 🖥️ Funciona en Todas Partes
Aplicación web + escritorio nativo en macOS, Windows y Linux mediante Electron. Actualizaciones automáticas desde GitHub Releases. Asociación de archivos `.op` — doble clic para abrir.
</td>
</tr>
</table>
## Inicio Rápido
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## Diseño Nativo de IA
OpenPencil está construido desde cero con IA en su núcleo — no como un plugin, sino como un flujo de trabajo central.
**De Prompt a Interfaz**
- **Texto a diseño** — describe una página y se genera en el lienzo en tiempo real con animación de transmisión
- **Orquestador** — descompone páginas complejas en subtareas espaciales para generación en paralelo
@ -74,10 +117,16 @@ OpenPencil está construido desde cero con IA en su núcleo — no como un plugi
| **Claude Code** | Sin configuración — usa Claude Agent SDK con OAuth local |
| **Codex CLI** | Conectar en Configuración de Agente (`Cmd+,`) |
| **OpenCode** | Conectar en Configuración de Agente (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` y luego conectar en Configuración de Agente (`Cmd+,`) |
**Perfiles de Capacidad de Modelos** — adapta automáticamente los prompts, el modo de pensamiento y los tiempos de espera según el nivel del modelo. Los modelos de nivel completo (Claude) reciben prompts completos; los de nivel estándar (GPT-4o, Gemini, DeepSeek) desactivan el pensamiento; los de nivel básico (MiniMax, Qwen, Llama, Mistral) reciben prompts simplificados de JSON anidado para máxima fiabilidad.
**Servidor MCP**
- Servidor MCP integrado — instalación con un clic en Claude Code / Codex / Gemini / OpenCode / Kiro CLIs
- Servidor MCP integrado — instalación con un clic en Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
- Automatización de diseño desde la terminal: leer, crear y modificar archivos `.op` a través de cualquier agente compatible con MCP
- **Flujo de diseño por capas**`design_skeleton``design_content``design_refine` para diseños multisección de mayor fidelidad
- **Recuperación segmentada de prompts** — carga solo el conocimiento de diseño que necesitas (schema, layout, roles, icons, planning, etc.)
- Soporte multipágina — crear, renombrar, reordenar y duplicar páginas mediante herramientas MCP
**Generación de Código**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil está construido desde cero con IA en su núcleo — no como un plugi
**Aplicación de Escritorio**
- Compatible de forma nativa con macOS, Windows y Linux mediante Electron
- Asociación de archivos `.op` — doble clic para abrir, bloqueo de instancia única
- Actualización automática desde GitHub Releases
- Menú de aplicación nativo y diálogos de archivo
@ -117,7 +167,7 @@ OpenPencil está construido desde cero con IA en su núcleo — no como un plugi
| **Estado** | Zustand v5 |
| **Servidor** | Nitro |
| **Escritorio** | Electron 35 |
| **IA** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **IA** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **Runtime** | Bun · Vite 7 |
| **Formato de archivo** | `.op` — basado en JSON, legible por humanos, compatible con Git |
@ -136,7 +186,7 @@ src/
uikit/ Sistema de kit de componentes reutilizables
server/
api/ai/ API Nitro — chat en streaming, generación, validación
utils/ Wrappers de cliente Claude CLI, OpenCode, Codex
utils/ Wrappers de cliente Claude CLI, OpenCode, Codex, Copilot
electron/
main.ts Ventana, fork Nitro, menú nativo, actualizador automático
preload.ts Puente IPC
@ -186,10 +236,11 @@ bun run electron:build # Empaquetado de Electron
- [x] Variables de diseño y tokens con sincronización CSS
- [x] Sistema de componentes (instancias y sobreescrituras)
- [x] Generación de diseño con IA y orquestador
- [x] Integración con servidor MCP
- [x] Integración con servidor MCP con flujo de diseño por capas
- [x] Soporte multipágina
- [x] Importación de Figma `.fig`
- [x] Operaciones booleanas (unión, sustracción, intersección)
- [x] Perfiles de capacidad multimodelo
- [ ] Edición colaborativa
- [ ] Sistema de plugins
@ -199,6 +250,17 @@ bun run electron:build # Empaquetado de Electron
<img src="https://contrib.rocks/image?repo=ZSeven-W/openpencil" alt="Contributors" />
</a>
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## Comunidad
<a href="https://discord.gg/KwXp6BJD">
@ -207,6 +269,4 @@ bun run electron:build # Empaquetado de Electron
</a>
— Haz preguntas, comparte diseños y sugiere funciones.
## Licencia
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>Outil de design open-source natif IA. Design-as-Code.</strong><br />
Du prompt à l'interface sur le canevas. Orchestration multi-agents. Serveur MCP intégré. Génération de code.
<strong>Le premier outil de design vectoriel open-source natif IA au monde.</strong><br />
<sub>Equipes d'agents concurrents &bull; Design-as-Code &bull; Serveur MCP intégré &bull; Intelligence multi-modèles</sub>
</p>
<p align="center">
@ -14,31 +14,76 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#quick-start">Démarrage rapide</a> ·
<a href="#ai-native-design">IA</a> ·
<a href="#features">Fonctionnalités</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">Contribuer</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — click to watch demo" width="100%" />
<img src="./screenshot/op-cover.png" alt="OpenPencil — cliquez pour regarder la démo" width="100%" />
</a>
</p>
<p align="center"><sub>Cliquez sur l'image pour regarder la vidéo de démonstration</sub></p>
<br />
## Pourquoi OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Prompt → Canevas
Décrivez n'importe quelle interface en langage naturel. Regardez-la apparaître sur le canevas infini en temps réel avec une animation en streaming. Modifiez des designs existants en sélectionnant des éléments et en conversant.
</td>
<td width="50%">
### 🤖 Équipes d'agents concurrents
L'orchestrateur décompose les pages complexes en sous-tâches spatiales. Plusieurs agents IA travaillent simultanément sur différentes sections — hero, fonctionnalités, pied de page — le tout en streaming parallèle.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 Intelligence multi-modèles
S'adapte automatiquement aux capacités de chaque modèle. Claude obtient des prompts complets avec réflexion ; GPT-4o/Gemini désactivent la réflexion ; les modèles plus petits (MiniMax, Qwen, Llama) reçoivent des prompts simplifiés pour une sortie fiable.
</td>
<td width="50%">
### 🔌 Serveur MCP
Installation en un clic dans les CLI Claude Code, Codex, Gemini, OpenCode, Kiro ou Copilot. Designez depuis votre terminal — lisez, créez et modifiez des fichiers `.op` via tout agent compatible MCP.
</td>
</tr>
<tr>
<td width="50%">
### 📦 Design-as-Code
Les fichiers `.op` sont du JSON — lisibles par l'humain, compatibles Git, comparables. Les variables de design génèrent des propriétés personnalisées CSS. Export de code vers React + Tailwind ou HTML + CSS.
</td>
<td width="50%">
### 🖥️ Fonctionne partout
Application web + bureau natif sur macOS, Windows et Linux via Electron. Mises à jour automatiques depuis GitHub Releases. Association de fichiers `.op` — double-cliquez pour ouvrir.
</td>
</tr>
</table>
## Démarrage rapide
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## Design natif IA
OpenPencil est conçu autour de l'IA dès le départ — non pas comme un plugin, mais comme un flux de travail central.
**Du prompt à l'interface**
- **Texte vers design** — décrivez une page, elle est générée en temps réel sur le canevas avec une animation en streaming
- **Orchestrateur** — décompose les pages complexes en sous-tâches spatiales pour une génération parallèle
@ -74,10 +117,16 @@ OpenPencil est conçu autour de l'IA dès le départ — non pas comme un plugin
| **Claude Code** | Aucune configuration — utilise le Claude Agent SDK avec OAuth local |
| **Codex CLI** | Connecter dans les Paramètres de l'agent (`Cmd+,`) |
| **OpenCode** | Connecter dans les Paramètres de l'agent (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` puis connecter dans les Paramètres de l'agent (`Cmd+,`) |
**Profils de capacités des modèles** — adapte automatiquement les prompts, le mode de réflexion et les délais d'attente par niveau de modèle. Les modèles de niveau complet (Claude) reçoivent des prompts complets ; le niveau standard (GPT-4o, Gemini, DeepSeek) désactive la réflexion ; le niveau basique (MiniMax, Qwen, Llama, Mistral) reçoit des prompts JSON imbriqués simplifiés pour une fiabilité maximale.
**Serveur MCP**
- Serveur MCP intégré — installation en un clic dans les CLI Claude Code / Codex / Gemini / OpenCode / Kiro
- Serveur MCP intégré — installation en un clic dans les CLI Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot
- Automatisation du design depuis le terminal : lire, créer et modifier des fichiers `.op` via tout agent compatible MCP
- **Workflow de design en couches**`design_skeleton``design_content``design_refine` pour des designs multi-sections de plus haute fidélité
- **Récupération segmentée des prompts** — chargez uniquement les connaissances de design nécessaires (schéma, layout, rôles, icônes, planification, etc.)
- Support multi-pages — créer, renommer, réordonner et dupliquer des pages via les outils MCP
**Génération de code**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil est conçu autour de l'IA dès le départ — non pas comme un plugin
**Application de bureau**
- macOS, Windows et Linux natifs via Electron
- Association de fichiers `.op` — double-cliquez pour ouvrir, verrouillage d'instance unique
- Mise à jour automatique depuis GitHub Releases
- Menu d'application natif et boîtes de dialogue de fichiers
@ -117,7 +167,7 @@ OpenPencil est conçu autour de l'IA dès le départ — non pas comme un plugin
| **État** | Zustand v5 |
| **Serveur** | Nitro |
| **Bureau** | Electron 35 |
| **IA** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **IA** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **Runtime** | Bun · Vite 7 |
| **Format de fichier** | `.op` — basé sur JSON, lisible par l'humain, compatible Git |
@ -136,7 +186,7 @@ src/
uikit/ Système de kits de composants réutilisables
server/
api/ai/ API Nitro — chat en streaming, génération, validation
utils/ Enveloppes client Claude CLI, OpenCode, Codex
utils/ Enveloppes client Claude CLI, OpenCode, Codex, Copilot
electron/
main.ts Fenêtre, fork Nitro, menu natif, mise à jour automatique
preload.ts Pont IPC
@ -186,10 +236,11 @@ Les contributions sont les bienvenues ! Consultez [CLAUDE.md](./CLAUDE.md) pour
- [x] Variables de design & tokens avec synchronisation CSS
- [x] Système de composants (instances & substitutions)
- [x] Génération de design IA avec orchestrateur
- [x] Intégration du serveur MCP
- [x] Intégration du serveur MCP avec workflow de design en couches
- [x] Support multi-pages
- [x] Import Figma `.fig`
- [x] Opérations booléennes (union, soustraction, intersection)
- [x] Profils de capacités multi-modèles
- [ ] Édition collaborative
- [ ] Système de plugins
@ -199,6 +250,17 @@ Les contributions sont les bienvenues ! Consultez [CLAUDE.md](./CLAUDE.md) pour
<img src="https://contrib.rocks/image?repo=ZSeven-W/openpencil" alt="Contributors" />
</a>
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## Communauté
<a href="https://discord.gg/KwXp6BJD">
@ -207,6 +269,4 @@ Les contributions sont les bienvenues ! Consultez [CLAUDE.md](./CLAUDE.md) pour
</a>
— Posez des questions, partagez vos designs, suggérez des fonctionnalités.
## Licence
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>AI-नेटिव ओपन-सोर्स डिज़ाइन टूल। डिज़ाइन-एज़-कोड</strong><br />
प्रॉम्प्ट से कैनवास UI तक। मल्टी-एजेंट ऑर्केस्ट्रेशन। बिल्ट-इन MCP सर्वर। कोड जनरेशन।
<strong>दुनिया का पहला ओपन-सोर्स AI-नेटिव वेक्टर डिज़ाइन टूल</strong><br />
<sub>समवर्ती एजेंट टीमें &bull; डिज़ाइन-एज़-कोड &bull; बिल्ट-इन MCP सर्वर &bull; मल्टी-मॉडल इंटेलिजेंस</sub>
</p>
<p align="center">
@ -14,31 +14,76 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#quick-start">त्वरित शुरुआत</a> ·
<a href="#ai-native-design">AI</a> ·
<a href="#features">विशेषताएँ</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">योगदान</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — click to watch demo" width="100%" />
<img src="./screenshot/op-cover.png" alt="OpenPencil — डेमो देखने के लिए क्लिक करें" width="100%" />
</a>
</p>
<p align="center"><sub>डेमो वीडियो देखने के लिए छवि पर क्लिक करें</sub></p>
<br />
## OpenPencil क्यों
<table>
<tr>
<td width="50%">
### 🎨 प्रॉम्प्ट → कैनवास
किसी भी UI का प्राकृतिक भाषा में वर्णन करें। स्ट्रीमिंग एनिमेशन के साथ रियल-टाइम में अनंत कैनवास पर प्रकट होते देखें। एलिमेंट चुनकर और चैट करके मौजूदा डिज़ाइन संशोधित करें।
</td>
<td width="50%">
### 🤖 समवर्ती एजेंट टीमें
ऑर्केस्ट्रेटर जटिल पेजों को स्थानिक सब-टास्क में विभाजित करता है। कई AI एजेंट एक साथ अलग-अलग सेक्शन पर काम करते हैं — हीरो, फ़ीचर, फ़ुटर — सभी समानांतर स्ट्रीमिंग करते हुए।
</td>
</tr>
<tr>
<td width="50%">
### 🧠 मल्टी-मॉडल इंटेलिजेंस
प्रत्येक मॉडल की क्षमताओं के अनुसार स्वचालित रूप से अनुकूलित होता है। Claude को थिंकिंग के साथ पूर्ण प्रॉम्प्ट मिलते हैं; GPT-4o/Gemini में थिंकिंग अक्षम होती है; छोटे मॉडल (MiniMax, Qwen, Llama) को विश्वसनीय आउटपुट के लिए सरलीकृत प्रॉम्प्ट मिलते हैं।
</td>
<td width="50%">
### 🔌 MCP सर्वर
Claude Code, Codex, Gemini, OpenCode, Kiro, या Copilot CLIs में वन-क्लिक इंस्टॉल। अपने टर्मिनल से डिज़ाइन करें — किसी भी MCP-संगत एजेंट के ज़रिए `.op` फ़ाइलें पढ़ें, बनाएँ और संशोधित करें।
</td>
</tr>
<tr>
<td width="50%">
### 📦 डिज़ाइन-एज़-कोड
`.op` फ़ाइलें JSON हैं — मानव-पठनीय, Git-फ्रेंडली, डिफ़ करने योग्य। डिज़ाइन वेरिएबल CSS कस्टम प्रॉपर्टीज़ जनरेट करते हैं। React + Tailwind या HTML + CSS में कोड एक्सपोर्ट।
</td>
<td width="50%">
### 🖥️ हर जगह चलता है
वेब ऐप + Electron के ज़रिए macOS, Windows और Linux पर नेटिव डेस्कटॉप। GitHub Releases से ऑटो-अपडेट। `.op` फ़ाइल एसोसिएशन — डबल-क्लिक से खोलें।
</td>
</tr>
</table>
## त्वरित शुरुआत
```bash
@ -59,11 +104,9 @@ bun run electron:dev
## AI-नेटिव डिज़ाइन
OpenPencil को AI के इर्द-गिर्द शुरू से बनाया गया है — एक प्लगइन के रूप में नहीं, बल्कि एक मुख्य वर्कफ़्लो के रूप में।
**प्रॉम्प्ट से UI तक**
- **टेक्स्ट-टू-डिज़ाइन** — एक पेज का विवरण दें, और स्ट्रीमिंग एनिमेशन के साथ रियल-टाइम में कैनवास पर जनरेट करें
- **ऑर्केस्ट्रेटर** — जटिल पेजों को स्थानिक सब-टास्क में विभाजित करता है, समानांतर जनरेशन के लिए
- **ऑर्केस्ट्रेटर** — जटिल पेजों को समानांतर जनरेशन के लिए स्थानिक सब-टास्क में विभाजित करता है
- **डिज़ाइन संशोधन** — एलिमेंट चुनें, फिर प्राकृतिक भाषा में बदलाव का विवरण दें
- **विज़न इनपुट** — संदर्भ-आधारित डिज़ाइन के लिए स्क्रीनशॉट या मॉकअप संलग्न करें
@ -74,10 +117,16 @@ OpenPencil को AI के इर्द-गिर्द शुरू से ब
| **Claude Code** | कोई कॉन्फ़िग नहीं — लोकल OAuth के साथ Claude Agent SDK का उपयोग करता है |
| **Codex CLI** | एजेंट सेटिंग्स में कनेक्ट करें (`Cmd+,`) |
| **OpenCode** | एजेंट सेटिंग्स में कनेक्ट करें (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` फिर एजेंट सेटिंग्स में कनेक्ट करें (`Cmd+,`) |
**मॉडल क्षमता प्रोफ़ाइल** — प्रत्येक मॉडल टियर के अनुसार प्रॉम्प्ट, थिंकिंग मोड और टाइमआउट को स्वचालित रूप से अनुकूलित करता है। फुल-टियर मॉडल (Claude) को पूर्ण प्रॉम्प्ट मिलते हैं; स्टैंडर्ड-टियर (GPT-4o, Gemini, DeepSeek) में थिंकिंग अक्षम होती है; बेसिक-टियर (MiniMax, Qwen, Llama, Mistral) को अधिकतम विश्वसनीयता के लिए सरलीकृत नेस्टेड-JSON प्रॉम्प्ट मिलते हैं।
**MCP सर्वर**
- बिल्ट-इन MCP सर्वर — Claude Code / Codex / Gemini / OpenCode / Kiro CLIs में वन-क्लिक इंस्टॉल
- बिल्ट-इन MCP सर्वर — Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs में वन-क्लिक इंस्टॉल
- टर्मिनल से डिज़ाइन ऑटोमेशन: किसी भी MCP-संगत एजेंट के ज़रिए `.op` फ़ाइलें पढ़ें, बनाएँ और संपादित करें
- **लेयर्ड डिज़ाइन वर्कफ़्लो** — उच्च-फ़िडेलिटी मल्टी-सेक्शन डिज़ाइन के लिए `design_skeleton``design_content``design_refine`
- **सेगमेंटेड प्रॉम्प्ट रिट्रीवल** — केवल आवश्यक डिज़ाइन ज्ञान लोड करें (schema, layout, roles, icons, planning, आदि)
- मल्टी-पेज सपोर्ट — MCP टूल के ज़रिए पेज बनाएँ, नाम बदलें, क्रम बदलें और डुप्लिकेट करें
**कोड जनरेशन**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil को AI के इर्द-गिर्द शुरू से ब
**डेस्कटॉप ऐप**
- Electron के ज़रिए नेटिव macOS, Windows और Linux सपोर्ट
- `.op` फ़ाइल एसोसिएशन — डबल-क्लिक से खोलें, सिंगल-इंस्टेंस लॉक
- GitHub Releases से ऑटो-अपडेट
- नेटिव एप्लिकेशन मेनू और फ़ाइल डायलॉग
@ -117,7 +167,7 @@ OpenPencil को AI के इर्द-गिर्द शुरू से ब
| **स्टेट** | Zustand v5 |
| **सर्वर** | Nitro |
| **डेस्कटॉप** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **रनटाइम** | Bun · Vite 7 |
| **फ़ाइल फ़ॉर्मेट** | `.op` — JSON-आधारित, मानव-पठनीय, Git-फ्रेंडली |
@ -136,7 +186,7 @@ src/
uikit/ पुन: उपयोगी कम्पोनेंट किट सिस्टम
server/
api/ai/ Nitro API — स्ट्रीमिंग चैट, जनरेशन, वैलिडेशन
utils/ Claude CLI, OpenCode, Codex क्लाइंट रैपर
utils/ Claude CLI, OpenCode, Codex, Copilot क्लाइंट रैपर
electron/
main.ts विंडो, Nitro फ़ोर्क, नेटिव मेनू, ऑटो-अपडेटर
preload.ts IPC ब्रिज
@ -186,10 +236,11 @@ bun run electron:build # Electron पैकेज
- [x] CSS सिंक के साथ डिज़ाइन वेरिएबल और टोकन
- [x] कम्पोनेंट सिस्टम (इंस्टेंस और ओवरराइड)
- [x] ऑर्केस्ट्रेटर के साथ AI डिज़ाइन जनरेशन
- [x] MCP सर्वर इंटीग्रेशन
- [x] लेयर्ड डिज़ाइन वर्कफ़्लो के साथ MCP सर्वर इंटीग्रेशन
- [x] मल्टी-पेज सपोर्ट
- [x] Figma `.fig` इम्पोर्ट
- [x] बूलियन ऑपरेशन (यूनियन, सबट्रैक्ट, इंटरसेक्ट)
- [x] मल्टी-मॉडल क्षमता प्रोफ़ाइल
- [ ] सहयोगी संपादन
- [ ] प्लगइन सिस्टम
@ -207,6 +258,17 @@ bun run electron:build # Electron पैकेज
</a>
— प्रश्न पूछें, डिज़ाइन साझा करें, सुविधाएँ सुझाएँ।
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## लाइसेंस
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>Alat desain open-source berbasis AI. Design-as-Code.</strong><br />
Dari prompt ke UI di kanvas. Orkestrasi multi-agen. Server MCP bawaan. Pembuatan kode.
<strong>Alat desain vektor open-source berbasis AI pertama di dunia.</strong><br />
<sub>Tim Agen Konkuren &bull; Design-as-Code &bull; Server MCP Bawaan &bull; Kecerdasan Multi-model</sub>
</p>
<p align="center">
@ -14,31 +14,76 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#mulai-cepat">Mulai Cepat</a> ·
<a href="#desain-berbasis-ai">AI</a> ·
<a href="#fitur">Fitur</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#berkontribusi">Berkontribusi</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — click to watch demo" width="100%" />
<img src="./screenshot/op-cover.png" alt="OpenPencil — klik untuk menonton demo" width="100%" />
</a>
</p>
<p align="center"><sub>Klik gambar untuk menonton video demo</sub></p>
<br />
## Mengapa OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Prompt → Kanvas
Deskripsikan UI apa pun dalam bahasa alami. Saksikan hasilnya muncul di kanvas tak terbatas secara real-time dengan animasi streaming. Modifikasi desain yang ada dengan memilih elemen dan berdialog.
</td>
<td width="50%">
### 🤖 Tim Agen Konkuren
Orkestrator menguraikan halaman kompleks menjadi sub-tugas spasial. Beberapa agen AI bekerja pada bagian yang berbeda secara bersamaan — hero, fitur, footer — semuanya streaming secara paralel.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 Kecerdasan Multi-Model
Secara otomatis menyesuaikan dengan kemampuan setiap model. Claude mendapat prompt lengkap dengan thinking; GPT-4o/Gemini menonaktifkan thinking; model yang lebih kecil (MiniMax, Qwen, Llama) mendapat prompt yang disederhanakan untuk keluaran yang andal.
</td>
<td width="50%">
### 🔌 Server MCP
Instal satu klik ke Claude Code, Codex, Gemini, OpenCode, Kiro, atau Copilot CLI. Desain dari terminal Anda — baca, buat, dan modifikasi file `.op` melalui agen yang kompatibel dengan MCP.
</td>
</tr>
<tr>
<td width="50%">
### 📦 Design-as-Code
File `.op` adalah JSON — mudah dibaca manusia, ramah Git, mudah dibandingkan. Variabel desain menghasilkan CSS custom properties. Ekspor kode ke React + Tailwind atau HTML + CSS.
</td>
<td width="50%">
### 🖥️ Berjalan di Mana Saja
Aplikasi web + desktop native di macOS, Windows, dan Linux melalui Electron. Pembaruan otomatis dari GitHub Releases. Asosiasi file `.op` — klik dua kali untuk membuka.
</td>
</tr>
</table>
## Mulai Cepat
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## Desain Berbasis AI
OpenPencil dibangun dengan AI sebagai inti — bukan sebagai plugin, melainkan sebagai alur kerja utama.
**Dari Prompt ke UI**
- **Teks ke desain** — deskripsikan halaman, dan hasilkan di kanvas secara real-time dengan animasi streaming
- **Orkestrator** — menguraikan halaman kompleks menjadi sub-tugas spasial untuk pembuatan secara paralel
@ -74,10 +117,16 @@ OpenPencil dibangun dengan AI sebagai inti — bukan sebagai plugin, melainkan s
| **Claude Code** | Tanpa konfigurasi — menggunakan Claude Agent SDK dengan OAuth lokal |
| **Codex CLI** | Hubungkan di Pengaturan Agen (`Cmd+,`) |
| **OpenCode** | Hubungkan di Pengaturan Agen (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` lalu hubungkan di Pengaturan Agen (`Cmd+,`) |
**Profil Kemampuan Model** — secara otomatis menyesuaikan prompt, mode thinking, dan timeout per tingkatan model. Model tingkat penuh (Claude) mendapat prompt lengkap; tingkat standar (GPT-4o, Gemini, DeepSeek) menonaktifkan thinking; tingkat dasar (MiniMax, Qwen, Llama, Mistral) mendapat prompt JSON bertingkat yang disederhanakan untuk keandalan maksimum.
**Server MCP**
- Server MCP bawaan — instal satu klik ke Claude Code / Codex / Gemini / OpenCode / Kiro CLI
- Server MCP bawaan — instal satu klik ke Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
- Otomasi desain dari terminal: baca, buat, dan modifikasi file `.op` melalui agen yang kompatibel dengan MCP
- **Alur kerja desain berlapis**`design_skeleton``design_content``design_refine` untuk desain multi-bagian dengan fidelitas lebih tinggi
- **Pengambilan prompt tersegmentasi** — muat hanya pengetahuan desain yang Anda butuhkan (schema, layout, roles, icons, planning, dll.)
- Dukungan multi-halaman — buat, ganti nama, urutkan ulang, dan duplikasi halaman melalui alat MCP
**Pembuatan Kode**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil dibangun dengan AI sebagai inti — bukan sebagai plugin, melainkan s
**Aplikasi Desktop**
- macOS, Windows, dan Linux native melalui Electron
- Asosiasi file `.op` — klik dua kali untuk membuka, kunci instans tunggal
- Pembaruan otomatis dari GitHub Releases
- Menu aplikasi native dan dialog file
@ -117,7 +167,7 @@ OpenPencil dibangun dengan AI sebagai inti — bukan sebagai plugin, melainkan s
| **State** | Zustand v5 |
| **Server** | Nitro |
| **Desktop** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **Runtime** | Bun · Vite 7 |
| **Format file** | `.op` — berbasis JSON, mudah dibaca manusia, ramah Git |
@ -136,7 +186,7 @@ src/
uikit/ Sistem kit komponen yang dapat digunakan ulang
server/
api/ai/ Nitro API — chat streaming, pembuatan, validasi
utils/ Pembungkus klien Claude CLI, OpenCode, Codex
utils/ Pembungkus klien Claude CLI, OpenCode, Codex, Copilot
electron/
main.ts Jendela, fork Nitro, menu native, pembaruan otomatis
preload.ts Jembatan IPC
@ -186,10 +236,11 @@ Kontribusi sangat disambut! Lihat [CLAUDE.md](./CLAUDE.md) untuk detail arsitekt
- [x] Variabel & token desain dengan sinkronisasi CSS
- [x] Sistem komponen (instans & penggantian)
- [x] Pembuatan desain AI dengan orkestrator
- [x] Integrasi server MCP
- [x] Integrasi server MCP dengan alur kerja desain berlapis
- [x] Dukungan multi-halaman
- [x] Impor Figma `.fig`
- [x] Operasi boolean (gabung, kurangi, potong)
- [x] Profil kemampuan multi-model
- [ ] Pengeditan kolaboratif
- [ ] Sistem plugin
@ -207,6 +258,17 @@ Kontribusi sangat disambut! Lihat [CLAUDE.md](./CLAUDE.md) untuk detail arsitekt
</a>
— Ajukan pertanyaan, bagikan desain, sarankan fitur.
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## Lisensi
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>AI ネイティブのオープンソースデザインツール。デザイン・アズ・コード</strong><br />
プロンプトからキャンバス UI へ。マルチエージェントオーケストレーション。内蔵 MCP サーバー。コード生成。
<strong>世界初のオープンソース AI ネイティブベクターデザインツール</strong><br />
<sub>並行エージェントチーム &bull; Design-as-Code &bull; 内蔵 MCP サーバー &bull; マルチモデルインテリジェンス</sub>
</p>
<p align="center">
@ -14,18 +14,10 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#quick-start">クイックスタート</a> ·
<a href="#ai-native-design">AI</a> ·
<a href="#features">機能</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">コントリビュート</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
@ -39,6 +31,59 @@
<br />
## Why OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 プロンプト → キャンバス
自然言語で任意の UI を記述。ストリーミングアニメーションでリアルタイムに無限キャンバス上に表示。要素を選択してチャットすることで既存のデザインを修正。
</td>
<td width="50%">
### 🤖 並行エージェントチーム
オーケストレーターが複雑なページを空間的なサブタスクに分解。複数の AI エージェントがヒーロー、機能紹介、フッターなど異なるセクションを同時に処理し、すべてが並列でストリーミング。
</td>
</tr>
<tr>
<td width="50%">
### 🧠 マルチモデルインテリジェンス
各モデルの能力に自動適応。Claude にはシンキング付きフルプロンプト、GPT-4o/Gemini ではシンキングを無効化、小規模モデルMiniMax、Qwen、Llamaには信頼性の高い出力のために簡略化プロンプトを使用。
</td>
<td width="50%">
### 🔌 MCP サーバー
Claude Code、Codex、Gemini、OpenCode、Kiro、Copilot CLI にワンクリックでインストール。ターミナルからデザイン — MCP 対応エージェントを通じて `.op` ファイルの読み取り、作成、編集が可能。
</td>
</tr>
<tr>
<td width="50%">
### 📦 Design-as-Code
`.op` ファイルは JSON — 人間が読みやすく、Git フレンドリーで差分比較可能。デザイン変数は CSS カスタムプロパティを生成。React + Tailwind または HTML + CSS へのコードエクスポート。
</td>
<td width="50%">
### 🖥️ どこでも動作
Web アプリ + Electron による macOS・Windows・Linux ネイティブデスクトップ。GitHub Releases からの自動アップデート。`.op` ファイル関連付け — ダブルクリックで開く。
</td>
</tr>
</table>
## クイックスタート
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## AI ネイティブデザイン
OpenPencil はプラグインとしてではなく、コアワークフローとして AI を中心に構築されています。
**プロンプトから UI へ**
- **テキストからデザインへ** — ページを説明すると、ストリーミングアニメーションでリアルタイムにキャンバス上に生成
- **オーケストレーター** — 複雑なページを空間サブタスクに分解し、並列生成をサポート
@ -74,10 +117,16 @@ OpenPencil はプラグインとしてではなく、コアワークフローと
| **Claude Code** | 設定不要 — ローカル OAuth で Claude Agent SDK を使用 |
| **Codex CLI** | エージェント設定で接続(`Cmd+,` |
| **OpenCode** | エージェント設定で接続(`Cmd+,` |
| **GitHub Copilot** | `copilot login` 後、エージェント設定で接続(`Cmd+,` |
**モデル能力プロファイル** — モデルの階層に応じてプロンプト、シンキングモード、タイムアウトを自動適応。フル階層モデルClaudeには完全なプロンプト、標準階層GPT-4o、Gemini、DeepSeekではシンキングを無効化、ベーシック階層MiniMax、Qwen、Llama、Mistralには最大限の信頼性のために簡略化されたネスト JSON プロンプトを使用。
**MCP サーバー**
- 内蔵 MCP サーバー — Claude Code / Codex / Gemini / OpenCode / Kiro CLI にワンクリックでインストール
- 内蔵 MCP サーバー — Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI にワンクリックでインストール
- ターミナルからのデザイン自動化MCP 対応エージェントを通じて `.op` ファイルの読み取り、作成、編集が可能
- **レイヤードデザインワークフロー**`design_skeleton``design_content``design_refine` による高忠実度マルチセクションデザイン
- **セグメント化プロンプト取得** — 必要なデザイン知識のみをロードschema、layout、roles、icons、planning など)
- マルチページサポート — MCP ツールを通じてページの作成、名前変更、並べ替え、複製が可能
**コード生成**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil はプラグインとしてではなく、コアワークフローと
**デスクトップアプリ**
- Electron によるネイティブ macOS・Windows・Linux 対応
- `.op` ファイル関連付け — ダブルクリックで開く、シングルインスタンスロック
- GitHub Releases からの自動アップデート
- ネイティブアプリケーションメニューとファイルダイアログ
@ -117,7 +167,7 @@ OpenPencil はプラグインとしてではなく、コアワークフローと
| **状態管理** | Zustand v5 |
| **サーバー** | Nitro |
| **デスクトップ** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **ランタイム** | Bun · Vite 7 |
| **ファイル形式** | `.op` — JSON ベース、人間が読みやすく、Git フレンドリー |
@ -136,7 +186,7 @@ src/
uikit/ 再利用可能なコンポーネントキットシステム
server/
api/ai/ Nitro API — ストリーミングチャット、生成、バリデーション
utils/ Claude CLI、OpenCode、Codex クライアントラッパー
utils/ Claude CLI、OpenCode、Codex、Copilot クライアントラッパー
electron/
main.ts ウィンドウ、Nitro フォーク、ネイティブメニュー、自動アップデーター
preload.ts IPC ブリッジ
@ -186,10 +236,11 @@ bun run electron:build # Electron パッケージング
- [x] CSS 同期付きデザイン変数とトークン
- [x] コンポーネントシステム(インスタンスとオーバーライド)
- [x] オーケストレーター付き AI デザイン生成
- [x] MCP サーバー統合
- [x] レイヤードデザインワークフロー付き MCP サーバー統合
- [x] マルチページサポート
- [x] Figma `.fig` インポート
- [x] ブール演算(結合、減算、交差)
- [x] ブーリアン演算(合体、型抜き、交差)
- [x] マルチモデル能力プロファイル
- [ ] 共同編集
- [ ] プラグインシステム
@ -207,6 +258,17 @@ bun run electron:build # Electron パッケージング
</a>
— 質問、デザインの共有、機能のリクエストはこちら。
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## ライセンス
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>AI 네이티브 오픈소스 디자인 툴. 디자인-애즈-코드.</strong><br />
프롬프트에서 캔버스 UI로. 멀티 에이전트 오케스트레이션. 내장 MCP 서버. 코드 생성.
<strong>세계 최초의 오픈소스 AI 네이티브 벡터 디자인 툴.</strong><br />
<sub>동시 에이전트 팀 &bull; 디자인-애즈-코드 &bull; 내장 MCP 서버 &bull; 멀티 모델 인텔리전스</sub>
</p>
<p align="center">
@ -14,18 +14,10 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#quick-start">빠른 시작</a> ·
<a href="#ai-native-design">AI</a> ·
<a href="#features">기능</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">기여하기</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
@ -39,6 +31,59 @@
<br />
## OpenPencil을 선택하는 이유
<table>
<tr>
<td width="50%">
### 🎨 프롬프트 → 캔버스
자연어로 어떤 UI든 설명하세요. 무한 캔버스 위에 스트리밍 애니메이션과 함께 실시간으로 나타납니다. 요소를 선택하고 대화하여 기존 디자인을 수정할 수 있습니다.
</td>
<td width="50%">
### 🤖 동시 에이전트 팀
오케스트레이터가 복잡한 페이지를 공간적 서브태스크로 분해합니다. 여러 AI 에이전트가 히어로, 기능 소개, 푸터 등 각기 다른 섹션을 동시에 작업하며 모두 병렬로 스트리밍됩니다.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 멀티 모델 인텔리전스
각 모델의 역량에 자동 적응합니다. Claude는 사고 모드가 포함된 전체 프롬프트를 받고, GPT-4o/Gemini는 사고 모드를 비활성화하며, 소형 모델(MiniMax, Qwen, Llama)은 안정적인 출력을 위해 단순화된 프롬프트를 받습니다.
</td>
<td width="50%">
### 🔌 MCP 서버
Claude Code, Codex, Gemini, OpenCode, Kiro 또는 Copilot CLI에 원클릭 설치. 터미널에서 디자인하세요 — MCP 호환 에이전트를 통해 `.op` 파일을 읽고, 생성하고, 편집할 수 있습니다.
</td>
</tr>
<tr>
<td width="50%">
### 📦 디자인-애즈-코드
`.op` 파일은 JSON입니다 — 사람이 읽을 수 있고, Git 친화적이며, diff가 가능합니다. 디자인 변수는 CSS 커스텀 프로퍼티를 생성합니다. React + Tailwind 또는 HTML + CSS로 코드 내보내기가 가능합니다.
</td>
<td width="50%">
### 🖥️ 어디서든 실행
웹 앱 + Electron을 통한 macOS, Windows, Linux 네이티브 데스크톱. GitHub Releases에서 자동 업데이트. `.op` 파일 연결 — 더블 클릭으로 열기.
</td>
</tr>
</table>
## 빠른 시작
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## AI 네이티브 디자인
OpenPencil은 AI를 플러그인이 아닌 핵심 워크플로로서 처음부터 구축했습니다.
**프롬프트에서 UI로**
- **텍스트-투-디자인** — 페이지를 설명하면 스트리밍 애니메이션으로 실시간으로 캔버스에 생성
- **오케스트레이터** — 복잡한 페이지를 공간적 서브태스크로 분해하여 병렬 생성 지원
@ -74,10 +117,16 @@ OpenPencil은 AI를 플러그인이 아닌 핵심 워크플로로서 처음부
| **Claude Code** | 설정 불필요 — 로컬 OAuth로 Claude Agent SDK 사용 |
| **Codex CLI** | 에이전트 설정에서 연결 (`Cmd+,`) |
| **OpenCode** | 에이전트 설정에서 연결 (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` 후 에이전트 설정에서 연결 (`Cmd+,`) |
**모델 역량 프로파일** — 모델 티어에 따라 프롬프트, 사고 모드, 타임아웃을 자동 조정합니다. 풀 티어 모델(Claude)은 완전한 프롬프트를 받고, 스탠다드 티어(GPT-4o, Gemini, DeepSeek)는 사고 모드를 비활성화하며, 베이직 티어(MiniMax, Qwen, Llama, Mistral)는 최대 안정성을 위해 단순화된 중첩 JSON 프롬프트를 받습니다.
**MCP 서버**
- 내장 MCP 서버 — Claude Code / Codex / Gemini / OpenCode / Kiro CLI에 원클릭 설치
- 내장 MCP 서버 — Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI에 원클릭 설치
- 터미널에서 디자인 자동화: MCP 호환 에이전트를 통해 `.op` 파일 읽기, 생성, 편집 가능
- **계층적 디자인 워크플로**`design_skeleton``design_content``design_refine`으로 더 높은 충실도의 멀티 섹션 디자인
- **세그먼트 프롬프트 검색** — 필요한 디자인 지식만 로드 (스키마, 레이아웃, 역할, 아이콘, 계획 등)
- 멀티 페이지 지원 — MCP 도구를 통해 페이지 생성, 이름 변경, 순서 변경, 복제
**코드 생성**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil은 AI를 플러그인이 아닌 핵심 워크플로로서 처음부
**데스크톱 앱**
- Electron을 통한 네이티브 macOS·Windows·Linux 지원
- `.op` 파일 연결 — 더블 클릭으로 열기, 단일 인스턴스 잠금
- GitHub Releases에서 자동 업데이트
- 네이티브 애플리케이션 메뉴와 파일 다이얼로그
@ -117,7 +167,7 @@ OpenPencil은 AI를 플러그인이 아닌 핵심 워크플로로서 처음부
| **상태 관리** | Zustand v5 |
| **서버** | Nitro |
| **데스크톱** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **런타임** | Bun · Vite 7 |
| **파일 형식** | `.op` — JSON 기반, 사람이 읽을 수 있는, Git 친화적 |
@ -136,7 +186,7 @@ src/
uikit/ 재사용 가능한 컴포넌트 킷 시스템
server/
api/ai/ Nitro API — 스트리밍 채팅, 생성, 유효성 검사
utils/ Claude CLI, OpenCode, Codex 클라이언트 래퍼
utils/ Claude CLI, OpenCode, Codex, Copilot 클라이언트 래퍼
electron/
main.ts 윈도우, Nitro 포크, 네이티브 메뉴, 자동 업데이터
preload.ts IPC 브리지
@ -186,10 +236,11 @@ bun run electron:build # Electron 패키징
- [x] CSS 동기화가 있는 디자인 변수 & 토큰
- [x] 컴포넌트 시스템(인스턴스 & 오버라이드)
- [x] 오케스트레이터를 통한 AI 디자인 생성
- [x] MCP 서버 통합
- [x] 계층적 디자인 워크플로가 포함된 MCP 서버 통합
- [x] 멀티 페이지 지원
- [x] Figma `.fig` 가져오기
- [x] 불리언 연산(결합, 빼기, 교차)
- [x] 멀티 모델 역량 프로파일
- [ ] 공동 편집
- [ ] 플러그인 시스템
@ -207,6 +258,17 @@ bun run electron:build # Electron 패키징
</a>
— 질문하기, 디자인 공유, 기능 제안.
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## 라이선스
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>AI-native open-source design tool. Design-as-Code.</strong><br />
Prompt to UI on canvas. Multi-agent orchestration. Built-in MCP server. Code generation.
<strong>The world's first open-source AI-native vector design tool.</strong><br />
<sub>Concurrent Agent Teams &bull; Design-as-Code &bull; Built-in MCP Server &bull; Multi-model Intelligence</sub>
</p>
<p align="center">
@ -14,18 +14,10 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#quick-start">Quick Start</a> ·
<a href="#ai-native-design">AI</a> ·
<a href="#features">Features</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">Contributing</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
@ -39,6 +31,59 @@
<br />
## Why OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Prompt → Canvas
Describe any UI in natural language. Watch it appear on the infinite canvas in real-time with streaming animation. Modify existing designs by selecting elements and chatting.
</td>
<td width="50%">
### 🤖 Concurrent Agent Teams
The orchestrator decomposes complex pages into spatial sub-tasks. Multiple AI agents work on different sections simultaneously — hero, features, footer — all streaming in parallel.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 Multi-Model Intelligence
Automatically adapts to each model's capabilities. Claude gets full prompts with thinking; GPT-4o/Gemini disable thinking; smaller models (MiniMax, Qwen, Llama) get simplified prompts for reliable output.
</td>
<td width="50%">
### 🔌 MCP Server
One-click install into Claude Code, Codex, Gemini, OpenCode, Kiro, or Copilot CLIs. Design from your terminal — read, create, and modify `.op` files through any MCP-compatible agent.
</td>
</tr>
<tr>
<td width="50%">
### 📦 Design-as-Code
`.op` files are JSON — human-readable, Git-friendly, diffable. Design variables generate CSS custom properties. Code export to React + Tailwind or HTML + CSS.
</td>
<td width="50%">
### 🖥️ Runs Everywhere
Web app + native desktop on macOS, Windows, and Linux via Electron. Auto-updates from GitHub Releases. `.op` file association — double-click to open.
</td>
</tr>
</table>
## Quick Start
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## AI-Native Design
OpenPencil is built around AI from the ground up — not as a plugin, but as a core workflow.
**Prompt to UI**
- **Text-to-design** — describe a page, get it generated on canvas in real-time with streaming animation
- **Orchestrator** — decomposes complex pages into spatial sub-tasks for parallel generation
@ -76,6 +119,8 @@ OpenPencil is built around AI from the ground up — not as a plugin, but as a c
| **OpenCode** | Connect in Agent Settings (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` then connect in Agent Settings (`Cmd+,`) |
**Model Capability Profiles** — automatically adapts prompts, thinking mode, and timeouts per model tier. Full-tier models (Claude) get complete prompts; standard-tier (GPT-4o, Gemini, DeepSeek) disable thinking; basic-tier (MiniMax, Qwen, Llama, Mistral) get simplified nested-JSON prompts for maximum reliability.
**MCP Server**
- Built-in MCP server — one-click install into Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
- Design automation from terminal: read, create, and modify `.op` files via any MCP-compatible agent
@ -195,6 +240,7 @@ Contributions are welcome! See [CLAUDE.md](./CLAUDE.md) for architecture details
- [x] Multi-page support
- [x] Figma `.fig` import
- [x] Boolean operations (union, subtract, intersect)
- [x] Multi-model capability profiles
- [ ] Collaborative editing
- [ ] Plugin system
@ -212,6 +258,16 @@ Contributions are welcome! See [CLAUDE.md](./CLAUDE.md) for architecture details
</a>
— Ask questions, share designs, suggest features.
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## License
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>Ferramenta de design open-source nativa com IA. Design-as-Code.</strong><br />
Do prompt à UI no canvas. Orquestração multi-agente. Servidor MCP integrado. Geração de código.
<strong>A primeira ferramenta de design vetorial open-source nativa com IA do mundo.</strong><br />
<sub>Equipes de Agentes Concorrentes &bull; Design-as-Code &bull; Servidor MCP Integrado &bull; Inteligência Multi-modelo</sub>
</p>
<p align="center">
@ -14,31 +14,76 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#início-rápido">Início Rápido</a> ·
<a href="#design-nativo-com-ia">IA</a> ·
<a href="#funcionalidades">Funcionalidades</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contribuindo">Contribuindo</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — click to watch demo" width="100%" />
<img src="./screenshot/op-cover.png" alt="OpenPencil — clique para assistir ao demo" width="100%" />
</a>
</p>
<p align="center"><sub>Clique na imagem para assistir ao vídeo de demonstração</sub></p>
<br />
## Por que OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Prompt → Canvas
Descreva qualquer UI em linguagem natural. Veja-a aparecer no canvas infinito em tempo real com animação de streaming. Modifique designs existentes selecionando elementos e conversando.
</td>
<td width="50%">
### 🤖 Equipes de Agentes Concorrentes
O orquestrador decompõe páginas complexas em sub-tarefas espaciais. Vários agentes de IA trabalham em diferentes seções simultaneamente — hero, features, footer — tudo em streaming paralelo.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 Inteligência Multi-Modelo
Adapta-se automaticamente às capacidades de cada modelo. Claude recebe prompts completos com thinking; GPT-4o/Gemini desativam thinking; modelos menores (MiniMax, Qwen, Llama) recebem prompts simplificados para saída confiável.
</td>
<td width="50%">
### 🔌 Servidor MCP
Instalação com um clique no Claude Code, Codex, Gemini, OpenCode, Kiro ou Copilot CLIs. Faça design pelo seu terminal — leia, crie e modifique arquivos `.op` através de qualquer agente compatível com MCP.
</td>
</tr>
<tr>
<td width="50%">
### 📦 Design-as-Code
Arquivos `.op` são JSON — legíveis por humanos, compatíveis com Git, com diff. Variáveis de design geram propriedades CSS personalizadas. Exportação de código para React + Tailwind ou HTML + CSS.
</td>
<td width="50%">
### 🖥️ Roda em Qualquer Lugar
App web + desktop nativo no macOS, Windows e Linux via Electron. Atualização automática a partir do GitHub Releases. Associação de arquivos `.op` — clique duplo para abrir.
</td>
</tr>
</table>
## Início Rápido
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## Design Nativo com IA
O OpenPencil é construído com IA desde o início — não como um plugin, mas como um fluxo de trabalho central.
**Do Prompt à UI**
- **Texto para design** — descreva uma página e ela será gerada no canvas em tempo real com animação de streaming
- **Orquestrador** — decompõe páginas complexas em sub-tarefas espaciais para geração paralela
@ -74,10 +117,16 @@ O OpenPencil é construído com IA desde o início — não como um plugin, mas
| **Claude Code** | Sem configuração — usa o Claude Agent SDK com OAuth local |
| **Codex CLI** | Conectar nas Configurações do Agente (`Cmd+,`) |
| **OpenCode** | Conectar nas Configurações do Agente (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` e depois conectar nas Configurações do Agente (`Cmd+,`) |
**Perfis de Capacidade de Modelo** — adapta automaticamente prompts, modo de thinking e timeouts por nível de modelo. Modelos de nível completo (Claude) recebem prompts completos; nível padrão (GPT-4o, Gemini, DeepSeek) desativam thinking; nível básico (MiniMax, Qwen, Llama, Mistral) recebem prompts simplificados de JSON aninhado para máxima confiabilidade.
**Servidor MCP**
- Servidor MCP integrado — instalação com um clique no Claude Code / Codex / Gemini / OpenCode / Kiro CLIs
- Servidor MCP integrado — instalação com um clique no Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
- Automação de design pelo terminal: leia, crie e modifique arquivos `.op` via qualquer agente compatível com MCP
- **Fluxo de design em camadas**`design_skeleton``design_content``design_refine` para designs multi-seção de maior fidelidade
- **Recuperação segmentada de prompts** — carregue apenas o conhecimento de design necessário (schema, layout, roles, icons, planning, etc.)
- Suporte a múltiplas páginas — crie, renomeie, reordene e duplique páginas via ferramentas MCP
**Geração de Código**
- React + Tailwind CSS
@ -105,6 +154,7 @@ O OpenPencil é construído com IA desde o início — não como um plugin, mas
**Aplicativo Desktop**
- macOS, Windows e Linux nativos via Electron
- Associação de arquivos `.op` — clique duplo para abrir, bloqueio de instância única
- Atualização automática a partir do GitHub Releases
- Menu de aplicativo nativo e diálogos de arquivo
@ -117,7 +167,7 @@ O OpenPencil é construído com IA desde o início — não como um plugin, mas
| **Estado** | Zustand v5 |
| **Servidor** | Nitro |
| **Desktop** | Electron 35 |
| **IA** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **IA** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **Runtime** | Bun · Vite 7 |
| **Formato de arquivo** | `.op` — baseado em JSON, legível por humanos, compatível com Git |
@ -136,7 +186,7 @@ src/
uikit/ Sistema de kit de componentes reutilizáveis
server/
api/ai/ API Nitro — chat em streaming, geração, validação
utils/ Wrappers de cliente Claude CLI, OpenCode, Codex
utils/ Wrappers de cliente Claude CLI, OpenCode, Codex, Copilot
electron/
main.ts Janela, fork do Nitro, menu nativo, atualizador automático
preload.ts Ponte IPC
@ -186,10 +236,11 @@ Contribuições são bem-vindas! Consulte o [CLAUDE.md](./CLAUDE.md) para detalh
- [x] Variáveis de design e tokens com sincronização CSS
- [x] Sistema de componentes (instâncias e substituições)
- [x] Geração de design com IA e orquestrador
- [x] Integração com servidor MCP
- [x] Integração com servidor MCP e fluxo de design em camadas
- [x] Suporte a múltiplas páginas
- [x] Importação do Figma `.fig`
- [x] Operações booleanas (união, subtração, interseção)
- [x] Perfis de capacidade multi-modelo
- [ ] Edição colaborativa
- [ ] Sistema de plugins
@ -207,6 +258,17 @@ Contribuições são bem-vindas! Consulte o [CLAUDE.md](./CLAUDE.md) para detalh
</a>
— Faça perguntas, compartilhe designs, sugira funcionalidades.
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## Licença
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>AI-нативный инструмент дизайна с открытым исходным кодом. Дизайн как код.</strong><br />
От текстового запроса к UI на холсте. Многоагентная оркестрация. Встроенный MCP-сервер. Генерация кода.
<strong>Первый в мире AI-нативный инструмент векторного дизайна с открытым исходным кодом.</strong><br />
<sub>Параллельные команды агентов &bull; Дизайн как код &bull; Встроенный MCP-сервер &bull; Мультимодельный интеллект</sub>
</p>
<p align="center">
@ -14,18 +14,10 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#quick-start">Быстрый старт</a> ·
<a href="#ai-native-design">AI</a> ·
<a href="#features">Возможности</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">Участие в разработке</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
@ -39,6 +31,59 @@
<br />
## Почему OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Запрос → Холст
Опишите любой интерфейс на естественном языке. Наблюдайте, как он появляется на бесконечном холсте в реальном времени со стриминговой анимацией. Изменяйте существующие дизайны, выбирая элементы и общаясь в чате.
</td>
<td width="50%">
### 🤖 Параллельные команды агентов
Оркестратор декомпозирует сложные страницы на пространственные подзадачи. Несколько AI-агентов работают над разными секциями одновременно — hero, features, footer — всё стримится параллельно.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 Мультимодельный интеллект
Автоматически адаптируется к возможностям каждой модели. Claude получает полные промпты с thinking; GPT-4o/Gemini — без thinking; маленькие модели (MiniMax, Qwen, Llama) получают упрощённые промпты для надёжного вывода.
</td>
<td width="50%">
### 🔌 MCP-сервер
Установка в один клик в Claude Code, Codex, Gemini, OpenCode, Kiro или Copilot CLI. Дизайн из терминала — чтение, создание и изменение файлов `.op` через любой MCP-совместимый агент.
</td>
</tr>
<tr>
<td width="50%">
### 📦 Дизайн как код
Файлы `.op` — это JSON: удобочитаемые, дружественные к Git, поддерживают diff. Переменные дизайна генерируют CSS custom properties. Экспорт кода в React + Tailwind или HTML + CSS.
</td>
<td width="50%">
### 🖥️ Работает везде
Веб-приложение + нативный десктоп на macOS, Windows и Linux через Electron. Автообновление из GitHub Releases. Ассоциация файлов `.op` — двойной клик для открытия.
</td>
</tr>
</table>
## Быстрый старт
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## AI-нативный дизайн
OpenPencil построен вокруг AI с самого начала — не как плагин, а как основной рабочий процесс.
**От запроса к UI**
- **Текст в дизайн** — опишите страницу и получите её на холсте в реальном времени со стриминговой анимацией
- **Оркестратор** — разбивает сложные страницы на пространственные подзадачи для параллельной генерации
@ -74,10 +117,16 @@ OpenPencil построен вокруг AI с самого начала — н
| **Claude Code** | Без настройки — использует Claude Agent SDK с локальным OAuth |
| **Codex CLI** | Подключить в настройках агента (`Cmd+,`) |
| **OpenCode** | Подключить в настройках агента (`Cmd+,`) |
| **GitHub Copilot** | `copilot login`, затем подключить в настройках агента (`Cmd+,`) |
**Профили возможностей моделей** — автоматически адаптирует промпты, режим thinking и таймауты для каждого уровня моделей. Модели полного уровня (Claude) получают полные промпты; стандартного уровня (GPT-4o, Gemini, DeepSeek) — с отключённым thinking; базового уровня (MiniMax, Qwen, Llama, Mistral) — упрощённые промпты с вложенным JSON для максимальной надёжности.
**MCP-сервер**
- Встроенный MCP-сервер — установка в один клик в Claude Code / Codex / Gemini / OpenCode / Kiro CLI
- Встроенный MCP-сервер — установка в один клик в Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
- Автоматизация дизайна из терминала: чтение, создание и изменение файлов `.op` через любой MCP-совместимый агент
- **Послойный рабочий процесс**`design_skeleton``design_content``design_refine` для дизайнов высокого качества с несколькими секциями
- **Сегментированное получение промптов** — загружайте только нужные знания о дизайне (schema, layout, roles, icons, planning и т.д.)
- Поддержка нескольких страниц — создание, переименование, переупорядочивание и дублирование страниц через инструменты MCP
**Генерация кода**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil построен вокруг AI с самого начала — н
**Десктопное приложение**
- Нативная поддержка macOS, Windows и Linux через Electron
- Ассоциация файлов `.op` — двойной клик для открытия, блокировка единственного экземпляра
- Автообновление из GitHub Releases
- Нативное меню приложения и диалоги файлов
@ -117,7 +167,7 @@ OpenPencil построен вокруг AI с самого начала — н
| **Состояние** | Zustand v5 |
| **Сервер** | Nitro |
| **Десктоп** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **Среда выполнения** | Bun · Vite 7 |
| **Формат файла** | `.op` — на основе JSON, удобочитаемый, дружественный к Git |
@ -136,7 +186,7 @@ src/
uikit/ Система переиспользуемых наборов компонентов
server/
api/ai/ Nitro API — стриминговый чат, генерация, валидация
utils/ Обёртки клиентов Claude CLI, OpenCode, Codex
utils/ Обёртки клиентов Claude CLI, OpenCode, Codex, Copilot
electron/
main.ts Окно, форк Nitro, нативное меню, автообновление
preload.ts IPC-мост
@ -186,10 +236,11 @@ bun run electron:build # Упаковка Electron
- [x] Переменные и токены дизайна с CSS-синхронизацией
- [x] Система компонентов (экземпляры и переопределения)
- [x] Генерация дизайна с помощью AI и оркестратора
- [x] Интеграция с MCP-сервером
- [x] Интеграция с MCP-сервером и послойный рабочий процесс
- [x] Поддержка нескольких страниц
- [x] Импорт Figma `.fig`
- [x] Булевы операции (объединение, вычитание, пересечение)
- [x] Мультимодельные профили возможностей
- [ ] Совместное редактирование
- [ ] Система плагинов
@ -207,6 +258,17 @@ bun run electron:build # Упаковка Electron
</a>
— Задавайте вопросы, делитесь дизайнами, предлагайте функции.
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## Лицензия
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>เครื่องมือออกแบบโอเพนซอร์สที่ขับเคลื่อนด้วย AI. Design-as-Code.</strong><br />
จาก Prompt สู่ UI บน Canvas. การจัดการหลาย Agent. MCP Server ในตัว. สร้างโค้ด.
<strong>เครื่องมือออกแบบเวกเตอร์โอเพนซอร์สที่ขับเคลื่อนด้วย AI ตัวแรกของโลก</strong><br />
<sub>ทีม Agent ทำงานพร้อมกัน &bull; Design-as-Code &bull; MCP Server ในตัว &bull; ปัญญาหลายโมเดล</sub>
</p>
<p align="center">
@ -14,31 +14,76 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#quick-start">เริ่มต้นอย่างรวดเร็ว</a> ·
<a href="#ai-native-design">AI</a> ·
<a href="#features">ฟีเจอร์</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">มีส่วนร่วม</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — click to watch demo" width="100%" />
<img src="./screenshot/op-cover.png" alt="OpenPencil — คลิกเพื่อดูวิดีโอสาธิต" width="100%" />
</a>
</p>
<p align="center"><sub>คลิกที่รูปภาพเพื่อดูวิดีโอสาธิต</sub></p>
<br />
## ทำไมต้อง OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Prompt → Canvas
อธิบาย UI ใดก็ได้ด้วยภาษาธรรมชาติ ดูมันปรากฏบน Canvas ไม่จำกัดขนาดแบบเรียลไทม์พร้อม animation แบบ streaming แก้ไขดีไซน์ที่มีอยู่โดยเลือกองค์ประกอบแล้วพิมพ์สนทนา
</td>
<td width="50%">
### 🤖 ทีม Agent ทำงานพร้อมกัน
Orchestrator แบ่งหน้าที่ซับซ้อนออกเป็น sub-task เชิงพื้นที่ AI agent หลายตัวทำงานในส่วนต่าง ๆ พร้อมกัน — hero, features, footer — ทั้งหมด streaming แบบขนาน
</td>
</tr>
<tr>
<td width="50%">
### 🧠 ปัญญาหลายโมเดล
ปรับตัวตามความสามารถของแต่ละโมเดลโดยอัตโนมัติ Claude ได้ prompt เต็มรูปแบบพร้อม thinking; GPT-4o/Gemini ปิด thinking; โมเดลขนาดเล็ก (MiniMax, Qwen, Llama) ได้ prompt แบบย่อเพื่อผลลัพธ์ที่เสถียร
</td>
<td width="50%">
### 🔌 MCP Server
ติดตั้งได้ด้วยคลิกเดียวใน Claude Code, Codex, Gemini, OpenCode, Kiro หรือ Copilot CLIs ออกแบบจาก terminal ของคุณ — อ่าน สร้าง และแก้ไขไฟล์ `.op` ผ่าน agent ที่รองรับ MCP
</td>
</tr>
<tr>
<td width="50%">
### 📦 Design-as-Code
ไฟล์ `.op` เป็น JSON — อ่านได้โดยมนุษย์, Git-friendly, เปรียบเทียบความแตกต่างได้ Design variables สร้าง CSS custom properties ส่งออกโค้ดเป็น React + Tailwind หรือ HTML + CSS
</td>
<td width="50%">
### 🖥️ ใช้งานได้ทุกที่
เว็บแอป + เดสก์ท็อปแบบ native บน macOS, Windows และ Linux ผ่าน Electron อัปเดตอัตโนมัติจาก GitHub Releases เชื่อมโยงไฟล์ `.op` — ดับเบิลคลิกเพื่อเปิด
</td>
</tr>
</table>
## เริ่มต้นอย่างรวดเร็ว
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## การออกแบบที่ขับเคลื่อนด้วย AI
OpenPencil ถูกสร้างขึ้นโดยมี AI เป็นแกนหลักตั้งแต่ต้น — ไม่ใช่ปลั๊กอิน แต่เป็นส่วนหนึ่งของกระบวนการทำงานหลัก
**จาก Prompt สู่ UI**
- **ข้อความเป็นดีไซน์** — อธิบายหน้า แล้วสร้างขึ้นบน Canvas แบบเรียลไทม์พร้อม animation แบบ streaming
- **Orchestrator** — แบ่งหน้าที่ซับซ้อนออกเป็น sub-task เชิงพื้นที่เพื่อการสร้างแบบขนาน
@ -74,10 +117,16 @@ OpenPencil ถูกสร้างขึ้นโดยมี AI เป็น
| **Claude Code** | ไม่ต้องตั้งค่า — ใช้ Claude Agent SDK พร้อม local OAuth |
| **Codex CLI** | เชื่อมต่อใน Agent Settings (`Cmd+,`) |
| **OpenCode** | เชื่อมต่อใน Agent Settings (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` จากนั้นเชื่อมต่อใน Agent Settings (`Cmd+,`) |
**โปรไฟล์ความสามารถของโมเดล** — ปรับ prompt, โหมด thinking และ timeout ตามระดับโมเดลโดยอัตโนมัติ โมเดลระดับเต็ม (Claude) ได้ prompt ครบถ้วน; โมเดลระดับมาตรฐาน (GPT-4o, Gemini, DeepSeek) ปิด thinking; โมเดลระดับพื้นฐาน (MiniMax, Qwen, Llama, Mistral) ได้ prompt แบบ nested-JSON ที่ย่อลงเพื่อความเสถียรสูงสุด
**MCP Server**
- MCP Server ในตัว — ติดตั้งได้ด้วยคลิกเดียวใน Claude Code / Codex / Gemini / OpenCode / Kiro CLIs
- MCP Server ในตัว — ติดตั้งได้ด้วยคลิกเดียวใน Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLIs
- การทำ Design Automation จาก Terminal: อ่าน สร้าง และแก้ไขไฟล์ `.op` ผ่าน agent ที่รองรับ MCP
- **Layered design workflow**`design_skeleton``design_content``design_refine` สำหรับดีไซน์หลายส่วนที่มีความละเอียดสูงขึ้น
- **Segmented prompt retrieval** — โหลดเฉพาะความรู้ด้านดีไซน์ที่ต้องการ (schema, layout, roles, icons, planning ฯลฯ)
- รองรับหลายหน้า — สร้าง เปลี่ยนชื่อ เรียงลำดับ และทำซ้ำหน้าผ่าน MCP tools
**การสร้างโค้ด**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil ถูกสร้างขึ้นโดยมี AI เป็น
**Desktop App**
- รองรับ macOS, Windows และ Linux แบบ native ผ่าน Electron
- เชื่อมโยงไฟล์ `.op` — ดับเบิลคลิกเพื่อเปิด, single-instance lock
- อัปเดตอัตโนมัติจาก GitHub Releases
- เมนูแอปพลิเคชันและ file dialog แบบ native
@ -117,7 +167,7 @@ OpenPencil ถูกสร้างขึ้นโดยมี AI เป็น
| **State** | Zustand v5 |
| **Server** | Nitro |
| **Desktop** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **Runtime** | Bun · Vite 7 |
| **รูปแบบไฟล์** | `.op` — ใช้ JSON, อ่านได้โดยมนุษย์, Git-friendly |
@ -136,7 +186,7 @@ src/
uikit/ ระบบ component kit ที่นำกลับมาใช้ใหม่ได้
server/
api/ai/ Nitro API — streaming chat, generation, validation
utils/ Claude CLI, OpenCode, Codex client wrappers
utils/ Claude CLI, OpenCode, Codex, Copilot client wrappers
electron/
main.ts Window, Nitro fork, native menu, auto-updater
preload.ts IPC bridge
@ -186,10 +236,11 @@ bun run electron:build # Electron package
- [x] Design variables และ tokens พร้อม CSS sync
- [x] ระบบ Component (instances และ overrides)
- [x] การสร้างดีไซน์ด้วย AI พร้อม orchestrator
- [x] การเชื่อมต่อ MCP server
- [x] การเชื่อมต่อ MCP server พร้อม layered design workflow
- [x] รองรับหลายหน้า
- [x] นำเข้า Figma `.fig`
- [x] Boolean operations (union, subtract, intersect)
- [x] โปรไฟล์ความสามารถหลายโมเดล
- [ ] การแก้ไขร่วมกัน
- [ ] ระบบปลั๊กอิน
@ -207,6 +258,17 @@ bun run electron:build # Electron package
</a>
— ถามคำถาม แชร์ดีไซน์ เสนอฟีเจอร์
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## สัญญาอนุญาต
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>AI destekli açık kaynak tasarım aracı. Kod Olarak Tasarım.</strong><br />
Prompttan kanvas UI'ye. Çok ajanlı orkestrasyon. Yerleşik MCP sunucusu. Kod üretimi.
<strong>Dunyanin ilk acik kaynakli AI-yerel vektor tasarim araci.</strong><br />
<sub>Eszamanli Ajan Ekipleri &bull; Kod Olarak Tasarim &bull; Yerlesik MCP Sunucusu &bull; Coklu Model Zekasi</sub>
</p>
<p align="center">
@ -14,31 +14,76 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#hızlı-başlangıç">Hızlı Başlangıç</a> ·
<a href="#ai-destekli-tasarım">AI</a> ·
<a href="#özellikler">Özellikler</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#katkıda-bulunma">Katkıda Bulunma</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — click to watch demo" width="100%" />
<img src="./screenshot/op-cover.png" alt="OpenPencil — demo videosunu izlemek için tıklayın" width="100%" />
</a>
</p>
<p align="center"><sub>Demo videosunu izlemek için görsele tıklayın</sub></p>
<br />
## Neden OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Prompt → Kanvas
Herhangi bir arayüzü doğal dilde tanımlayın. Gerçek zamanlı akış animasyonuyla sonsuz kanvasta belirmesini izleyin. Öğeleri seçip sohbet ederek mevcut tasarımları düzenleyin.
</td>
<td width="50%">
### 🤖 Eşzamanlı Ajan Ekipleri
Orkestratör, karmaşık sayfaları uzamsal alt görevlere ayırır. Birden fazla AI ajanı farklı bölümlerde eşzamanlı çalışır — hero, özellikler, footer — hepsi paralel olarak akış halinde.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 Çoklu Model Zekası
Her modelin yeteneklerine otomatik olarak uyum sağlar. Claude tam promptlar ve düşünme modu alır; GPT-4o/Gemini'de düşünme modu devre dışı bırakılır; küçük modeller (MiniMax, Qwen, Llama) güvenilir çıktı için basitleştirilmiş promptlar alır.
</td>
<td width="50%">
### 🔌 MCP Sunucusu
Claude Code, Codex, Gemini, OpenCode, Kiro veya Copilot CLI'larına tek tıkla kurulum. Terminalinizden tasarım yapın — herhangi bir MCP uyumlu ajan aracılığıyla `.op` dosyalarını okuyun, oluşturun ve düzenleyin.
</td>
</tr>
<tr>
<td width="50%">
### 📦 Kod Olarak Tasarım
`.op` dosyaları JSON formatındadır — insan tarafından okunabilir, Git dostu, diff edilebilir. Tasarım değişkenleri CSS özel özellikleri üretir. React + Tailwind veya HTML + CSS olarak kod dışa aktarımı.
</td>
<td width="50%">
### 🖥️ Her Yerde Çalışır
Web uygulaması + Electron ile macOS, Windows ve Linux'ta yerel masaüstü. GitHub Releases'ten otomatik güncelleme. `.op` dosya ilişkilendirmesi — açmak için çift tıklayın.
</td>
</tr>
</table>
## Hızlı Başlangıç
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## AI Destekli Tasarım
OpenPencil, AI'yi bir eklenti olarak değil, temel iş akışı olarak sıfırdan inşa edilmiştir.
**Prompttan UI'ye**
- **Metinden tasarıma** — bir sayfayı tanımlayın, gerçek zamanlı akış animasyonuyla kanvasta oluşturulsun
- **Orkestratör** — karmaşık sayfaları paralel üretim için uzamsal alt görevlere ayırır
@ -74,10 +117,16 @@ OpenPencil, AI'yi bir eklenti olarak değil, temel iş akışı olarak sıfırda
| **Claude Code** | Yapılandırma gerekmez — yerel OAuth ile Claude Agent SDK kullanır |
| **Codex CLI** | Ajan Ayarlarından bağlanın (`Cmd+,`) |
| **OpenCode** | Ajan Ayarlarından bağlanın (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` ardından Ajan Ayarlarından bağlanın (`Cmd+,`) |
**Model Yetenek Profilleri** — promptları, düşünme modunu ve zaman aşımlarını model katmanına göre otomatik olarak uyarlar. Tam katman modeller (Claude) eksiksiz promptlar alır; standart katman (GPT-4o, Gemini, DeepSeek) düşünme modunu devre dışı bırakır; temel katman (MiniMax, Qwen, Llama, Mistral) maksimum güvenilirlik için basitleştirilmiş iç içe JSON promptları alır.
**MCP Sunucusu**
- Yerleşik MCP sunucusu — Claude Code / Codex / Gemini / OpenCode / Kiro CLI'larına tek tıkla kurulum
- Yerleşik MCP sunucusu — Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI'larına tek tıkla kurulum
- Terminalden tasarım otomasyonu: herhangi bir MCP uyumlu ajan aracılığıyla `.op` dosyalarını okuyun, oluşturun ve düzenleyin
- **Katmanlı tasarım iş akışı** — daha yüksek kaliteli çok bölümlü tasarımlar için `design_skeleton``design_content``design_refine`
- **Bölümlenmiş prompt alımı** — yalnızca ihtiyacınız olan tasarım bilgisini yükleyin (şema, düzen, roller, simgeler, planlama vb.)
- Çok sayfa desteği — MCP araçları ile sayfaları oluşturun, yeniden adlandırın, sıralayın ve çoğaltın
**Kod Üretimi**
- React + Tailwind CSS
@ -89,7 +138,7 @@ OpenPencil, AI'yi bir eklenti olarak değil, temel iş akışı olarak sıfırda
**Kanvas ve Çizim**
- Kaydırma, yakınlaştırma, akıllı hizalama kılavuzları ve yakalamayı destekleyen sonsuz kanvas
- Dikdörtgen, Elips, Çizgi, Çokgen, Kalem (Bezier), Frame, Metin
- Boolean işlemler — birleştir, çıkar, kesiştir bağlamsal araç çubuğuyla
- Boolean işlemler — bağlamsal araç çubuğuyla birleştir, çıkar, kesiştir
- Simge seçici (Iconify) ve görsel içe aktarma (PNG/JPEG/SVG/WebP/GIF)
- Otomatik düzen — boşluk, dolgu, justify, align ile dikey/yatay
- Sekme navigasyonlu çok sayfalı belgeler
@ -105,6 +154,7 @@ OpenPencil, AI'yi bir eklenti olarak değil, temel iş akışı olarak sıfırda
**Masaüstü Uygulaması**
- Electron aracılığıyla yerel macOS, Windows ve Linux desteği
- `.op` dosya ilişkilendirmesi — açmak için çift tıklayın, tekli örnek kilidi
- GitHub Releases'ten otomatik güncelleme
- Yerel uygulama menüsü ve dosya iletişim kutuları
@ -117,7 +167,7 @@ OpenPencil, AI'yi bir eklenti olarak değil, temel iş akışı olarak sıfırda
| **Durum Yönetimi** | Zustand v5 |
| **Sunucu** | Nitro |
| **Masaüstü** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **Çalışma Ortamı** | Bun · Vite 7 |
| **Dosya Formatı** | `.op` — JSON tabanlı, insan tarafından okunabilir, Git dostu |
@ -136,7 +186,7 @@ src/
uikit/ Yeniden kullanılabilir bileşen kiti sistemi
server/
api/ai/ Nitro API — akış sohbet, üretim, doğrulama
utils/ Claude CLI, OpenCode, Codex istemci sarmalayıcıları
utils/ Claude CLI, OpenCode, Codex, Copilot istemci sarmalayıcıları
electron/
main.ts Pencere, Nitro çatallanması, yerel menü, otomatik güncelleyici
preload.ts IPC köprüsü
@ -186,10 +236,11 @@ Katkılarınızı bekliyoruz! Mimari ayrıntılar ve kod stili için [CLAUDE.md]
- [x] CSS senkronizasyonlu tasarım değişkenleri ve tokenları
- [x] Bileşen sistemi (örnekler ve geçersiz kılmalar)
- [x] Orkestratörlü AI tasarım üretimi
- [x] MCP sunucu entegrasyonu
- [x] Katmanlı tasarım iş akışı ile MCP sunucu entegrasyonu
- [x] Çok sayfa desteği
- [x] Figma `.fig` içe aktarma
- [x] Boolean işlemler (birleştirme, çıkarma, kesişim)
- [x] Çoklu model yetenek profilleri
- [ ] Ortak düzenleme
- [ ] Eklenti sistemi
@ -207,6 +258,17 @@ Katkılarınızı bekliyoruz! Mimari ayrıntılar ve kod stili için [CLAUDE.md]
</a>
— Soru sorun, tasarımlarınızı paylaşın, özellik önerin.
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## Lisans
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>Công cụ thiết kế mã nguồn mở thuần AI. Design-as-Code.</strong><br />
Từ prompt đến giao diện trên canvas. Điều phối đa tác nhân. Máy chủ MCP tích hợp sẵn. Tạo mã nguồn.
<strong>Công cụ thiết kế vector mã nguồn mở thuần AI đầu tiên trên thế giới.</strong><br />
<sub>Đội Tác nhân Đồng thời &bull; Design-as-Code &bull; Máy chủ MCP Tích hợp &bull; Trí tuệ Đa mô hình</sub>
</p>
<p align="center">
@ -14,31 +14,76 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
</p>
<p align="center">
<a href="#quick-start">Bắt đầu nhanh</a> ·
<a href="#ai-native-design">AI</a> ·
<a href="#features">Tính năng</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">Đóng góp</a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — click to watch demo" width="100%" />
<img src="./screenshot/op-cover.png" alt="OpenPencil — nhấp để xem demo" width="100%" />
</a>
</p>
<p align="center"><sub>Nhấp vào hình ảnh để xem video demo</sub></p>
<br />
## Tại sao chọn OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 Prompt → Canvas
Mô tả bất kỳ giao diện nào bằng ngôn ngữ tự nhiên. Xem nó xuất hiện trên canvas vô hạn theo thời gian thực với hiệu ứng streaming. Chỉnh sửa thiết kế hiện có bằng cách chọn các phần tử và trò chuyện.
</td>
<td width="50%">
### 🤖 Đội Tác nhân Đồng thời
Bộ điều phối phân rã các trang phức tạp thành các tác vụ con theo không gian. Nhiều tác nhân AI làm việc trên các phần khác nhau đồng thời — hero, features, footer — tất cả streaming song song.
</td>
</tr>
<tr>
<td width="50%">
### 🧠 Trí tuệ Đa mô hình
Tự động thích ứng với khả năng của từng mô hình. Claude nhận prompt đầy đủ với thinking; GPT-4o/Gemini tắt thinking; các mô hình nhỏ hơn (MiniMax, Qwen, Llama) nhận prompt đơn giản hóa cho đầu ra đáng tin cậy.
</td>
<td width="50%">
### 🔌 Máy chủ MCP
Cài đặt một cú nhấp vào Claude Code, Codex, Gemini, OpenCode, Kiro hoặc Copilot CLI. Thiết kế từ terminal — đọc, tạo và chỉnh sửa tệp `.op` thông qua bất kỳ tác nhân tương thích MCP nào.
</td>
</tr>
<tr>
<td width="50%">
### 📦 Design-as-Code
Tệp `.op` là JSON — dễ đọc, thân thiện Git, dễ so sánh khác biệt. Biến thiết kế tạo ra thuộc tính tùy chỉnh CSS. Xuất mã sang React + Tailwind hoặc HTML + CSS.
</td>
<td width="50%">
### 🖥️ Chạy Mọi nơi
Ứng dụng web + desktop gốc trên macOS, Windows và Linux qua Electron. Tự động cập nhật từ GitHub Releases. Liên kết tệp `.op` — nhấp đúp để mở.
</td>
</tr>
</table>
## Bắt đầu nhanh
```bash
@ -59,8 +104,6 @@ bun run electron:dev
## Thiết kế thuần AI
OpenPencil được xây dựng xung quanh AI từ nền tảng — không phải như một plugin mà là một quy trình làm việc cốt lõi.
**Từ Prompt đến Giao diện**
- **Văn bản thành thiết kế** — mô tả một trang, nhận kết quả được tạo ra trên canvas theo thời gian thực với hiệu ứng streaming
- **Orchestrator** — phân rã các trang phức tạp thành các tác vụ con không gian để tạo song song
@ -74,10 +117,16 @@ OpenPencil được xây dựng xung quanh AI từ nền tảng — không phả
| **Claude Code** | Không cần cấu hình — sử dụng Claude Agent SDK với OAuth cục bộ |
| **Codex CLI** | Kết nối trong Cài đặt tác nhân (`Cmd+,`) |
| **OpenCode** | Kết nối trong Cài đặt tác nhân (`Cmd+,`) |
| **GitHub Copilot** | `copilot login` rồi kết nối trong Cài đặt tác nhân (`Cmd+,`) |
**Hồ sơ Năng lực Mô hình** — tự động thích ứng prompt, chế độ thinking và thời gian chờ theo từng cấp mô hình. Mô hình cấp đầy đủ (Claude) nhận prompt hoàn chỉnh; cấp tiêu chuẩn (GPT-4o, Gemini, DeepSeek) tắt thinking; cấp cơ bản (MiniMax, Qwen, Llama, Mistral) nhận prompt JSON lồng nhau đơn giản hóa để đảm bảo độ tin cậy tối đa.
**Máy chủ MCP**
- Máy chủ MCP tích hợp sẵn — cài đặt một cú nhấp vào Claude Code / Codex / Gemini / OpenCode / Kiro CLI
- Máy chủ MCP tích hợp sẵn — cài đặt một cú nhấp vào Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
- Tự động hóa thiết kế từ terminal: đọc, tạo và chỉnh sửa các tệp `.op` qua bất kỳ tác nhân tương thích MCP nào
- **Quy trình thiết kế phân lớp**`design_skeleton``design_content``design_refine` cho thiết kế đa phần có độ trung thực cao hơn
- **Truy xuất prompt phân đoạn** — chỉ tải kiến thức thiết kế cần thiết (schema, layout, roles, icons, planning, v.v.)
- Hỗ trợ nhiều trang — tạo, đổi tên, sắp xếp lại và nhân bản trang qua các công cụ MCP
**Tạo mã nguồn**
- React + Tailwind CSS
@ -105,6 +154,7 @@ OpenPencil được xây dựng xung quanh AI từ nền tảng — không phả
**Ứng dụng Desktop**
- macOS, Windows và Linux gốc qua Electron
- Liên kết tệp `.op` — nhấp đúp để mở, khóa phiên bản đơn
- Tự động cập nhật từ GitHub Releases
- Menu ứng dụng gốc và hộp thoại tệp
@ -117,7 +167,7 @@ OpenPencil được xây dựng xung quanh AI từ nền tảng — không phả
| **Trạng thái** | Zustand v5 |
| **Máy chủ** | Nitro |
| **Desktop** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **Runtime** | Bun · Vite 7 |
| **Định dạng tệp** | `.op` — dựa trên JSON, dễ đọc, thân thiện với Git |
@ -136,7 +186,7 @@ src/
uikit/ Hệ thống kit component có thể tái sử dụng
server/
api/ai/ Nitro API — streaming chat, generation, validation
utils/ Claude CLI, OpenCode, Codex client wrappers
utils/ Claude CLI, OpenCode, Codex, Copilot client wrappers
electron/
main.ts Cửa sổ, Nitro fork, menu gốc, auto-updater
preload.ts IPC bridge
@ -186,10 +236,11 @@ Chào mừng đóng góp! Xem [CLAUDE.md](./CLAUDE.md) để biết chi tiết v
- [x] Biến thiết kế & token với đồng bộ CSS
- [x] Hệ thống component (instances & overrides)
- [x] Tạo thiết kế AI với orchestrator
- [x] Tích hợp máy chủ MCP
- [x] Tích hợp máy chủ MCP với quy trình thiết kế phân lớp
- [x] Hỗ trợ nhiều trang
- [x] Nhập Figma `.fig`
- [x] Phép toán Boolean (hợp nhất, trừ, giao)
- [x] Hồ sơ năng lực đa mô hình
- [ ] Chỉnh sửa cộng tác
- [ ] Hệ thống plugin
@ -207,6 +258,17 @@ Chào mừng đóng góp! Xem [CLAUDE.md](./CLAUDE.md) để biết chi tiết v
</a>
— Đặt câu hỏi, chia sẻ thiết kế, đề xuất tính năng.
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## Giấy phép
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>AI 原生開源設計工具。設計即程式碼</strong><br />
從提示詞到畫布 UI。多智能體編排。內建 MCP 伺服器。程式碼生成。
<strong>全球首個開源 AI 原生向量設計工具</strong><br />
<sub>並行智能體團隊 &bull; 設計即程式碼 &bull; 內建 MCP 伺服器 &bull; 多模型智慧</sub>
</p>
<p align="center">
@ -14,19 +14,75 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="#quick-start">快速開始</a> ·
<a href="#ai-native-design">AI</a> ·
<a href="#features">功能特色</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">參與貢獻</a>
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — 點擊觀看示範影片" width="100%" />
</a>
</p>
<p align="center"><sub>點擊圖片觀看示範影片</sub></p>
<br />
## 為什麼選擇 OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 提示詞 → 畫布
用自然語言描述任何 UI。即時以串流動畫在無限畫布上生成。選取元素並透過對話修改現有設計。
</td>
<td width="50%">
### 🤖 並行智能體團隊
編排器將複雜頁面分解為空間子任務。多個 AI 智能體同時處理不同區塊 — 主視覺、功能區塊、頁尾 — 全部並行串流生成。
</td>
</tr>
<tr>
<td width="50%">
### 🧠 多模型智慧
自動適配每個模型的能力。Claude 獲得完整提示詞與思考模式GPT-4o/Gemini 停用思考模式較小模型MiniMax、Qwen、Llama獲得精簡提示詞確保輸出可靠。
</td>
<td width="50%">
### 🔌 MCP 伺服器
一鍵安裝至 Claude Code、Codex、Gemini、OpenCode、Kiro 或 Copilot CLI。從終端機進行設計 — 透過任意 MCP 相容的智能體讀取、建立和修改 `.op` 檔案。
</td>
</tr>
<tr>
<td width="50%">
### 📦 設計即程式碼
`.op` 檔案是 JSON — 人類可讀、對 Git 友好、可差異比較。設計變數生成 CSS 自訂屬性。程式碼匯出為 React + Tailwind 或 HTML + CSS。
</td>
<td width="50%">
### 🖥️ 隨處執行
Web 應用程式 + 透過 Electron 在 macOS、Windows 和 Linux 上原生執行。從 GitHub Releases 自動更新。`.op` 檔案關聯 — 雙擊即可開啟。
</td>
</tr>
</table>
## 快速開始
@ -48,8 +104,6 @@ bun run electron:dev
## AI 原生設計
OpenPencil 從底層就圍繞 AI 構建——不是作為外掛程式,而是作為核心工作流程。
**提示詞生成 UI**
- **文字轉設計** — 描述一個頁面,即時以串流動畫在畫布上生成
- **編排器** — 將複雜頁面分解為空間子任務,支援並行生成
@ -63,10 +117,16 @@ OpenPencil 從底層就圍繞 AI 構建——不是作為外掛程式,而是
| **Claude Code** | 無需設定 — 使用 Claude Agent SDK 本地 OAuth |
| **Codex CLI** | 在 Agent 設定中連接(`Cmd+,` |
| **OpenCode** | 在 Agent 設定中連接(`Cmd+,` |
| **GitHub Copilot** | 執行 `copilot login` 後在 Agent 設定中連接(`Cmd+,` |
**模型能力設定檔** — 自動依據模型層級調整提示詞、思考模式和逾時設定。完整層級模型Claude獲得完整提示詞標準層級GPT-4o、Gemini、DeepSeek停用思考模式基礎層級MiniMax、Qwen、Llama、Mistral獲得精簡巢狀 JSON 提示詞,確保最大可靠性。
**MCP 伺服器**
- 內建 MCP 伺服器 — 一鍵安裝至 Claude Code / Codex / Gemini / OpenCode / Kiro CLI
- 內建 MCP 伺服器 — 一鍵安裝至 Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
- 從終端機進行設計自動化:透過任意 MCP 相容的智能體讀取、建立和修改 `.op` 檔案
- **分層設計工作流**`design_skeleton``design_content``design_refine`,適用於高保真多區塊設計
- **分段提示詞擷取** — 僅載入所需的設計知識schema、layout、roles、icons、planning 等)
- 多頁面支援 — 透過 MCP 工具建立、重新命名、重新排序和複製頁面
**程式碼生成**
- React + Tailwind CSS
@ -94,6 +154,7 @@ OpenPencil 從底層就圍繞 AI 構建——不是作為外掛程式,而是
**桌面應用程式**
- 透過 Electron 支援原生 macOS、Windows 和 Linux
- `.op` 檔案關聯 — 雙擊即可開啟,支援單一實體鎖定
- 從 GitHub Releases 自動更新
- 原生應用程式選單和檔案對話框
@ -106,7 +167,7 @@ OpenPencil 從底層就圍繞 AI 構建——不是作為外掛程式,而是
| **狀態管理** | Zustand v5 |
| **伺服器** | Nitro |
| **桌面端** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **執行環境** | Bun · Vite 7 |
| **檔案格式** | `.op` — 基於 JSON人類可讀對 Git 友好 |
@ -125,7 +186,7 @@ src/
uikit/ 可重複使用元件套件系統
server/
api/ai/ Nitro API — 串流聊天、生成、驗證
utils/ Claude CLI、OpenCode、Codex 客戶端封裝
utils/ Claude CLI、OpenCode、Codex、Copilot 客戶端封裝
electron/
main.ts 視窗、Nitro 子處理序、原生選單、自動更新
preload.ts IPC 橋接
@ -175,10 +236,11 @@ bun run electron:build # Electron 封裝
- [x] 設計變數與令牌,支援 CSS 同步
- [x] 元件系統(實體與覆寫)
- [x] 帶編排器的 AI 設計生成
- [x] MCP 伺服器整合
- [x] MCP 伺服器整合,支援分層設計工作流
- [x] 多頁面支援
- [x] Figma `.fig` 匯入
- [x] 布林運算(聯集、減去、交集)
- [x] 多模型能力設定檔
- [ ] 協同編輯
- [ ] 外掛程式系統
@ -196,6 +258,17 @@ bun run electron:build # Electron 封裝
</a>
— 提問、分享設計、提出功能建議。
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## 授權條款
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -5,8 +5,8 @@
<h1 align="center">OpenPencil</h1>
<p align="center">
<strong>AI 原生开源设计工具。设计即代码。</strong><br />
从提示词到画布 UI。多智能体编排。内置 MCP 服务器。代码生成。
<strong>全球首个 AI 原生开源矢量设计工具。</strong><br />
<sub>并发 Agent 团队 &bull; 设计即代码 &bull; 内置 MCP 服务器 &bull; 多模型智能</sub>
</p>
<p align="center">
@ -14,19 +14,75 @@
</p>
<p align="center">
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/stargazers"><img src="https://img.shields.io/github/stars/ZSeven-W/openpencil?style=flat&color=cfb537" alt="Stars" /></a>
<a href="https://github.com/ZSeven-W/openpencil/blob/main/LICENSE"><img src="https://img.shields.io/github/license/ZSeven-W/openpencil?color=64748b" alt="License" /></a>
<a href="https://github.com/ZSeven-W/openpencil/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/ZSeven-W/openpencil/ci.yml?branch=main&label=CI" alt="CI" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white" alt="Discord" /></a>
<a href="https://discord.gg/KwXp6BJD"><img src="https://img.shields.io/discord/1476517942949580952?label=Discord&logo=discord&logoColor=white&color=5865F2" alt="Discord" /></a>
</p>
<br />
<p align="center">
<a href="#quick-start">快速开始</a> ·
<a href="#ai-native-design">AI</a> ·
<a href="#features">功能特性</a> ·
<a href="https://discord.gg/KwXp6BJD">Discord</a> ·
<a href="#contributing">参与贡献</a>
<a href="https://oss.ioa.tech/zseven/openpencil/a46e24733239ce24de36702342201033.mp4">
<img src="./screenshot/op-cover.png" alt="OpenPencil — 点击观看演示视频" width="100%" />
</a>
</p>
<p align="center"><sub>点击图片观看演示视频</sub></p>
<br />
## 为什么选择 OpenPencil
<table>
<tr>
<td width="50%">
### 🎨 提示词 → 画布
用自然语言描述任意 UI实时以流式动画在无限画布上生成。选中已有元素通过对话即可修改设计。
</td>
<td width="50%">
### 🤖 并发 Agent 团队
编排器将复杂页面分解为空间子任务。多个 AI 智能体同时处理不同区块 — Hero、功能区、页脚 — 全部并行流式生成。
</td>
</tr>
<tr>
<td width="50%">
### 🧠 多模型智能
自动适配每个模型的能力。Claude 获得完整提示词和思考模式GPT-4o/Gemini 关闭思考模式小模型MiniMax、通义千问、Llama使用简化提示词以确保输出可靠性。
</td>
<td width="50%">
### 🔌 MCP 服务器
一键安装到 Claude Code、Codex、Gemini、OpenCode、Kiro 或 Copilot CLI。从终端进行设计 — 通过任意 MCP 兼容的智能体读取、创建和修改 `.op` 文件。
</td>
</tr>
<tr>
<td width="50%">
### 📦 设计即代码
`.op` 文件是 JSON — 人类可读、对 Git 友好、可进行 diff 对比。设计变量生成 CSS 自定义属性。代码导出为 React + Tailwind 或 HTML + CSS。
</td>
<td width="50%">
### 🖥️ 全平台运行
Web 应用 + 通过 Electron 支持 macOS、Windows 和 Linux 原生桌面端。从 GitHub Releases 自动更新。`.op` 文件关联 — 双击即可打开。
</td>
</tr>
</table>
## 快速开始
@ -48,8 +104,6 @@ bun run electron:dev
## AI 原生设计
OpenPencil 从底层就围绕 AI 构建——不是作为插件,而是作为核心工作流。
**提示词生成 UI**
- **文字转设计** — 描述一个页面,实时以流式动画在画布上生成
- **编排器** — 将复杂页面分解为空间子任务,支持并行生成
@ -63,10 +117,16 @@ OpenPencil 从底层就围绕 AI 构建——不是作为插件,而是作为
| **Claude Code** | 无需配置 — 使用 Claude Agent SDK 本地 OAuth |
| **Codex CLI** | 在 Agent 设置中连接(`Cmd+,` |
| **OpenCode** | 在 Agent 设置中连接(`Cmd+,` |
| **GitHub Copilot** | 运行 `copilot login` 后在 Agent 设置中连接(`Cmd+,` |
**模型能力配置** — 自动根据模型层级适配提示词、思考模式和超时时间。完整层级模型Claude获得完整提示词标准层级模型GPT-4o、Gemini、DeepSeek关闭思考模式基础层级模型MiniMax、通义千问、Llama、Mistral使用简化的嵌套 JSON 提示词以确保最大可靠性。
**MCP 服务器**
- 内置 MCP 服务器 — 一键安装到 Claude Code / Codex / Gemini / OpenCode / Kiro CLI
- 内置 MCP 服务器 — 一键安装到 Claude Code / Codex / Gemini / OpenCode / Kiro / Copilot CLI
- 从终端进行设计自动化:通过任意 MCP 兼容的智能体读取、创建和修改 `.op` 文件
- **分层设计工作流**`design_skeleton``design_content``design_refine`,实现更高保真度的多区块设计
- **分段提示词检索** — 按需加载所需的设计知识schema、layout、roles、icons、planning 等)
- 多页面支持 — 通过 MCP 工具创建、重命名、重新排序和复制页面
**代码生成**
- React + Tailwind CSS
@ -94,6 +154,7 @@ OpenPencil 从底层就围绕 AI 构建——不是作为插件,而是作为
**桌面应用**
- 通过 Electron 支持原生 macOS、Windows 和 Linux
- `.op` 文件关联 — 双击即可打开,单实例锁定
- 从 GitHub Releases 自动更新
- 原生应用菜单和文件对话框
@ -106,7 +167,7 @@ OpenPencil 从底层就围绕 AI 构建——不是作为插件,而是作为
| **状态管理** | Zustand v5 |
| **服务器** | Nitro |
| **桌面端** | Electron 35 |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK |
| **AI** | Anthropic SDK · Claude Agent SDK · OpenCode SDK · Copilot SDK |
| **运行时** | Bun · Vite 7 |
| **文件格式** | `.op` — 基于 JSON人类可读对 Git 友好 |
@ -125,7 +186,7 @@ src/
uikit/ 可复用组件套件系统
server/
api/ai/ Nitro API — 流式聊天、生成、验证
utils/ Claude CLI、OpenCode、Codex 客户端封装
utils/ Claude CLI、OpenCode、Codex、Copilot 客户端封装
electron/
main.ts 窗口、Nitro 子进程、原生菜单、自动更新
preload.ts IPC 桥接
@ -175,10 +236,11 @@ bun run electron:build # Electron 打包
- [x] 设计变量与令牌,支持 CSS 同步
- [x] 组件系统(实例与覆盖)
- [x] 带编排器的 AI 设计生成
- [x] MCP 服务器集成
- [x] MCP 服务器集成与分层设计工作流
- [x] 多页面支持
- [x] Figma `.fig` 导入
- [x] 布尔运算(合并、减去、相交)
- [x] 多模型能力配置
- [ ] 协同编辑
- [ ] 插件系统
@ -200,6 +262,17 @@ bun run electron:build # Electron 打包
<img src="./screenshot/557517811-62010928-d91a-4223-bc10-9ee7a4fbf043.jpg" alt="飞书交流群" width="240" />
## Star History
<a href="https://star-history.com/#ZSeven-W/openpencil&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ZSeven-W/openpencil&type=Date" width="100%" />
</picture>
</a>
## 许可证
[MIT](./LICENSE) — Copyright (c) 2026 ZSeven-W

View file

@ -35,6 +35,7 @@ dmg:
win:
icon: build/icon.ico
artifactName: "${productName}-${version}-${arch}-win.${ext}"
publisherName: "OpenPencil"
target:
- nsis
- portable

View file

@ -1,8 +1,13 @@
import { app, BrowserWindow } from 'electron'
import { autoUpdater } from 'electron-updater'
import type { NsisUpdater } from 'electron-updater'
import { GitHubProvider } from 'electron-updater/out/providers/GitHubProvider'
import { execFile } from 'node:child_process'
import { promisify } from 'node:util'
import { GITHUB_OWNER, GITHUB_REPO } from './constants'
const execFileAsync = promisify(execFile)
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
@ -156,6 +161,41 @@ export function setupAutoUpdater(): void {
autoUpdater.autoInstallOnAppQuit = true
autoUpdater.allowPrerelease = true
// Windows: custom signature verification for self-signed certificate.
// The default verifier requires the cert to be in the Windows trusted root
// store, which self-signed certs are not. This custom function still verifies
// the publisher name from the Authenticode signature — it just skips the
// trust chain check. This is NOT disabling verification.
if (process.platform === 'win32') {
const nsisUpdater = autoUpdater as NsisUpdater
nsisUpdater.verifyUpdateCodeSignature = async (
publisherNames: string[],
tempUpdateFile: string,
): Promise<string | null> => {
try {
const { stdout } = await execFileAsync('powershell.exe', [
'-NoProfile', '-NonInteractive', '-Command',
`(Get-AuthenticodeSignature '${tempUpdateFile.replace(/'/g, "''")}').SignerCertificate.Subject`,
], { timeout: 30_000 })
const subject = stdout.trim()
if (!subject) {
return 'The update file is not signed.'
}
for (const name of publisherNames) {
if (subject.includes(name)) {
return null // Publisher name matches — verification passed
}
}
return `Publisher mismatch. Expected: ${publisherNames.join(', ')}. Got: ${subject}`
} catch (err) {
return `Signature verification failed: ${err instanceof Error ? err.message : String(err)}`
}
}
}
autoUpdater.on('checking-for-update', () => {
setUpdaterState({ status: 'checking', error: undefined, downloadProgress: undefined })
})

View file

@ -94,7 +94,21 @@ async function main(): Promise<void> {
await waitForServer(`http://localhost:${VITE_DEV_PORT}`)
console.log('[electron-dev] Vite is ready')
// 3. Compile Electron files
// 3. Compile MCP server + Electron files
console.log('[electron-dev] Compiling MCP server...')
await build({
platform: 'node',
bundle: true,
sourcemap: true,
target: 'node20',
format: 'cjs',
entryPoints: [join(ROOT, 'src', 'mcp', 'server.ts')],
outfile: join(ROOT, 'dist', 'mcp-server.cjs'),
alias: { '@': join(ROOT, 'src') },
define: { 'import.meta.env': '{}' },
})
console.log('[electron-dev] MCP server compiled')
await compileElectron()
// 4. Launch Electron

View file

@ -343,6 +343,58 @@ function streamViaAgentSDK(body: ChatBody, model?: string) {
return new Response(stream)
}
/** Error name → user-friendly label mapping */
const OPENCODE_ERROR_LABELS: Record<string, string> = {
APIError: 'API error',
ProviderAuthError: 'Authentication failed',
UnknownError: 'Unknown error',
MessageOutputLengthError: 'Response too long',
MessageAbortedError: 'Request aborted',
StructuredOutputError: 'Output format error',
ContextOverflowError: 'Context too long',
}
/**
* Extract a human-readable message from an OpenCode error object.
* Handles structured errors like { name: "APIError", data: { message: "..." } }
* and nested JSON in message strings.
*/
export function formatOpenCodeError(error: unknown): string {
if (!error) return 'Unknown error'
if (typeof error === 'string') return error
const err = error as Record<string, any>
// Structured OpenCode error: { name, data: { message, ... } }
if (err.name && err.data?.message) {
const label = OPENCODE_ERROR_LABELS[err.name] ?? err.name
let msg: string = err.data.message
// Try to extract nested error message from JSON in the message string
// e.g. 'Unauthorized: {"error":{"code":"invalid_api_key","message":"invalid access token"}}'
const jsonStart = msg.indexOf('{')
if (jsonStart > 0) {
try {
const nested = JSON.parse(msg.slice(jsonStart))
const nestedMsg = nested?.error?.message ?? nested?.message
if (nestedMsg) {
const prefix = msg.slice(0, jsonStart).replace(/:\s*$/, '').trim()
msg = prefix ? `${prefix}: ${nestedMsg}` : nestedMsg
}
} catch { /* not JSON, use as-is */ }
}
return `${label}${msg}`
}
// Plain { message } object
if (err.message) return err.message
// Fallback: truncated JSON
const json = JSON.stringify(error)
return json.length > 200 ? json.slice(0, 200) + '…' : json
}
/** Parse an OpenCode model string ("providerID/modelID") into its parts */
function parseOpenCodeModel(model?: string): { providerID: string; modelID: string } | undefined {
if (!model || !model.includes('/')) return undefined
@ -377,24 +429,19 @@ function buildOpenCodeReasoning(
return Object.keys(reasoning).length > 0 ? reasoning : undefined
}
async function promptOpenCodeWithThinking(
ocClient: any,
basePayload: Record<string, unknown>,
body: ChatBody,
): Promise<{ data: any; error: any }> {
const reasoning = buildOpenCodeReasoning(body)
if (!reasoning) {
return await ocClient.session.prompt(basePayload)
/** Wrap an async generator with a timeout — yields values until timeout fires */
async function* streamWithTimeout<T>(
stream: AsyncGenerator<T>,
timeoutPromise: Promise<{ done: true; value: undefined }>,
): AsyncGenerator<T> {
while (true) {
const result = await Promise.race([
stream.next(),
timeoutPromise,
]) as IteratorResult<T>
if (result.done) break
yield result.value
}
const enhanced = { ...basePayload, reasoning }
const firstTry = await ocClient.session.prompt(enhanced)
if (!firstTry.error) {
return firstTry
}
console.warn('[AI] OpenCode reasoning options rejected, retrying without reasoning.')
return await ocClient.session.prompt(basePayload)
}
function streamViaCodex(body: ChatBody, model?: string) {
@ -465,7 +512,7 @@ function streamViaCodex(body: ChatBody, model?: string) {
return new Response(stream)
}
/** Stream via OpenCode SDK (connects to a running OpenCode server) */
/** Stream via OpenCode SDK using event subscription for real-time streaming */
function streamViaOpenCode(body: ChatBody, model?: string) {
const stream = new ReadableStream({
async start(controller) {
@ -488,7 +535,7 @@ function streamViaOpenCode(body: ChatBody, model?: string) {
title: 'OpenPencil Chat',
})
if (sessionError || !session) {
throw new Error('Failed to create OpenCode session')
throw new Error(`Failed to create OpenCode session: ${formatOpenCodeError(sessionError)}`)
}
// Inject system prompt as context (no AI reply)
@ -503,6 +550,9 @@ function streamViaOpenCode(body: ChatBody, model?: string) {
const prompt = lastUserMsg?.content ?? ''
const parsed = parseOpenCodeModel(model)
if (model && !parsed) {
console.warn(`[AI] OpenCode: could not parse model string "${model}", sending without model override`)
}
// Build parts array, adding image attachments if present
const attachments = getLastUserAttachments(body)
@ -514,32 +564,87 @@ function streamViaOpenCode(body: ChatBody, model?: string) {
{ type: 'text', text: prompt || 'Analyze these images.' },
]
// Send prompt and await full response
console.log(`[AI] OpenCode streaming prompt: model=${model}, parsed=${JSON.stringify(parsed)}`)
// Build prompt payload with optional model and reasoning
const promptPayload: Record<string, unknown> = {
sessionID: session.id,
...(parsed ? { model: parsed } : {}),
parts,
}
const { data: result, error: promptError } = await promptOpenCodeWithThinking(
ocClient,
promptPayload,
body,
)
if (promptError) {
throw new Error('OpenCode prompt failed')
const reasoning = buildOpenCodeReasoning(body)
if (reasoning) {
promptPayload.reasoning = reasoning
}
// Extract text from response parts
clearInterval(pingTimer)
if (result?.parts) {
for (const part of result.parts) {
if (part.type === 'text' && 'text' in part) {
const data = JSON.stringify({ type: 'text', content: part.text })
// Subscribe to event stream for real-time deltas
const eventResult = await ocClient.event.subscribe()
const eventStream = eventResult.stream
// Send prompt asynchronously — response comes via events
const { error: asyncError } = await ocClient.session.promptAsync(promptPayload as any)
if (asyncError) {
const detail = formatOpenCodeError(asyncError)
console.error('[AI] OpenCode promptAsync error:', detail)
throw new Error(detail)
}
// Consume event stream, forwarding text deltas to client
let emittedText = false
const sessionId = session.id
const STREAM_TIMEOUT_MS = 180_000
const timeoutPromise = new Promise<{ done: true; value: undefined }>((resolve) =>
setTimeout(() => resolve({ done: true, value: undefined }), STREAM_TIMEOUT_MS),
)
for await (const event of streamWithTimeout(eventStream, timeoutPromise)) {
if (!event || !('type' in event)) continue
const eventType = event.type as string
// Stream text deltas for our session
if (eventType === 'message.part.delta') {
const props = (event as any).properties
if (props?.sessionID === sessionId && props.field === 'text') {
const data = JSON.stringify({ type: 'text', content: props.delta })
controller.enqueue(encoder.encode(`data: ${data}\n\n`))
emittedText = true
}
// Forward reasoning deltas as thinking chunks
if (props?.sessionID === sessionId && props.field === 'reasoning') {
const data = JSON.stringify({ type: 'thinking', content: props.delta })
controller.enqueue(encoder.encode(`data: ${data}\n\n`))
}
continue
}
// Session went idle — response complete
if (eventType === 'session.idle') {
const props = (event as any).properties
if (props?.sessionID === sessionId) break
continue
}
// Session error
if (eventType === 'session.error') {
const props = (event as any).properties
if (props?.sessionID === sessionId || !props?.sessionID) {
const errMsg = formatOpenCodeError(props?.error)
console.error('[AI] OpenCode session error:', errMsg)
const data = JSON.stringify({ type: 'error', content: errMsg })
controller.enqueue(encoder.encode(`data: ${data}\n\n`))
break
}
continue
}
}
clearInterval(pingTimer)
if (!emittedText) {
console.warn('[AI] OpenCode returned no text via streaming events')
const data = JSON.stringify({ type: 'error', content: 'OpenCode returned an empty response. The model may not have generated any output.' })
controller.enqueue(encoder.encode(`data: ${data}\n\n`))
}
controller.enqueue(

View file

@ -5,6 +5,7 @@ import {
buildClaudeAgentEnv,
getClaudeAgentDebugFilePath,
} from '../../utils/resolve-claude-agent-env'
import { formatOpenCodeError } from './chat'
interface GenerateBody {
system: string
@ -141,6 +142,26 @@ function buildOpenCodeReasoning(
return Object.keys(reasoning).length > 0 ? reasoning : undefined
}
/** Timeout for OpenCode prompt calls (3 minutes) */
const OPENCODE_PROMPT_TIMEOUT_MS = 180_000
async function promptWithTimeout(
ocClient: any,
payload: Record<string, unknown>,
timeoutMs = OPENCODE_PROMPT_TIMEOUT_MS,
): Promise<{ data: any; error: any }> {
const result = await Promise.race([
ocClient.session.prompt(payload),
new Promise<{ data: null; error: string }>((resolve) =>
setTimeout(
() => resolve({ data: null, error: `OpenCode prompt timed out after ${timeoutMs / 1000}s` }),
timeoutMs,
),
),
])
return result
}
async function promptOpenCodeWithThinking(
ocClient: any,
basePayload: Record<string, unknown>,
@ -148,17 +169,17 @@ async function promptOpenCodeWithThinking(
): Promise<{ data: any; error: any }> {
const reasoning = buildOpenCodeReasoning(body)
if (!reasoning) {
return await ocClient.session.prompt(basePayload)
return await promptWithTimeout(ocClient, basePayload)
}
const enhanced = { ...basePayload, reasoning }
const firstTry = await ocClient.session.prompt(enhanced)
const firstTry = await promptWithTimeout(ocClient, enhanced)
if (!firstTry.error) {
return firstTry
}
console.warn('[AI] OpenCode reasoning options rejected, retrying without reasoning.')
return await ocClient.session.prompt(basePayload)
return await promptWithTimeout(ocClient, basePayload)
}
/** Generate via OpenCode SDK (connects to a running OpenCode server) */
@ -174,7 +195,8 @@ async function generateViaOpenCode(body: GenerateBody, model?: string): Promise<
title: 'OpenPencil Generate',
})
if (sessionError || !session) {
return { error: 'Failed to create OpenCode session' }
const detail = formatOpenCodeError(sessionError)
return { error: `Failed to create OpenCode session: ${detail}` }
}
// Inject system prompt as context (no AI reply)
@ -189,6 +211,8 @@ async function generateViaOpenCode(body: GenerateBody, model?: string): Promise<
if (model && model.includes('/')) {
const idx = model.indexOf('/')
modelOption = { providerID: model.slice(0, idx), modelID: model.slice(idx + 1) }
} else if (model) {
console.warn(`[AI] OpenCode generate: could not parse model string "${model}", sending without model override`)
}
// Send main prompt and await full response
@ -198,6 +222,8 @@ async function generateViaOpenCode(body: GenerateBody, model?: string): Promise<
parts: [{ type: 'text', text: body.message }],
}
console.log(`[AI] OpenCode generate: model=${model}, parsed=${JSON.stringify(modelOption)}`)
const { data: result, error: promptError } = await promptOpenCodeWithThinking(
ocClient,
promptPayload,
@ -205,7 +231,9 @@ async function generateViaOpenCode(body: GenerateBody, model?: string): Promise<
)
if (promptError) {
return { error: 'OpenCode generation failed' }
const errorDetail = formatOpenCodeError(promptError)
console.error('[AI] OpenCode generate error:', errorDetail)
return { error: errorDetail }
}
// Extract text from response parts
@ -218,6 +246,11 @@ async function generateViaOpenCode(body: GenerateBody, model?: string): Promise<
}
}
if (texts.length === 0) {
console.warn('[AI] OpenCode generate returned no text parts. Response:', JSON.stringify(result).slice(0, 500))
return { error: 'OpenCode returned an empty response. The model may not have generated any output.' }
}
return { text: texts.join('') }
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'

View file

@ -1,4 +1,4 @@
import { fork, execSync, type ChildProcess } from 'node:child_process'
import { spawn, execSync, type ChildProcess } from 'node:child_process'
import { existsSync } from 'node:fs'
import { networkInterfaces } from 'node:os'
import { join, resolve } from 'node:path'
@ -80,13 +80,22 @@ export function startMcpHttpServer(port: number): { running: boolean; port: numb
const serverScript = resolveMcpServerScript()
try {
mcpProcess = fork(serverScript, ['--http', '--port', String(port)], {
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
// Use spawn instead of fork to avoid IPC channel issues on Windows.
// fork() creates an IPC channel that, if unused and disconnected, can
// cause the child process to exit unexpectedly on Windows.
mcpProcess = spawn(process.execPath, [serverScript, '--http', '--port', String(port)], {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env },
windowsHide: true,
})
mcpPort = port
mcpProcess.stdout?.on('data', (data: Buffer) => {
const msg = data.toString().trim()
if (msg) console.log(`[mcp-server] ${msg}`)
})
mcpProcess.stderr?.on('data', (data: Buffer) => {
console.error(`[mcp-server] ${data.toString().trim()}`)
})

View file

@ -1,5 +1,6 @@
import * as fabric from 'fabric'
import type { PenNode, ImageFitMode } from '@/types/pen'
import { buildEllipseArcPath, isArcEllipse } from '@/utils/arc-path'
import type {
PenFill,
PenStroke,
@ -372,14 +373,33 @@ export function createFabricObject(
case 'ellipse': {
const w = sizeToNumber(node.width, 100)
const h = sizeToNumber(node.height, 100)
obj = new fabric.Ellipse({
...baseProps,
rx: w / 2,
ry: h / 2,
fill: resolveFill(node.fill, w, h),
stroke: resolveStrokeColor(node.stroke),
strokeWidth: resolveStrokeWidth(node.stroke),
}) as FabricObjectWithPenId
if (isArcEllipse(node.startAngle, node.sweepAngle, node.innerRadius)) {
const arcD = buildEllipseArcPath(w, h, node.startAngle ?? 0, node.sweepAngle ?? 360, node.innerRadius ?? 0)
obj = new fabric.Path(arcD, {
...baseProps,
fill: resolveFill(node.fill, w, h),
stroke: resolveStrokeColor(node.stroke),
strokeWidth: resolveStrokeWidth(node.stroke),
strokeUniform: true,
fillRule: 'evenodd',
}) as FabricObjectWithPenId
// The arc path is drawn within a 0,0 → w,h coordinate space.
// Override Fabric's auto-computed bounding box to avoid distortion.
;(obj as any).__sourceD = arcD
;(obj as any).__nativeWidth = w
;(obj as any).__nativeHeight = h
;(obj as any).pathOffset = new fabric.Point(w / 2, h / 2)
obj.set({ width: w, height: h, scaleX: 1, scaleY: 1 })
} else {
obj = new fabric.Ellipse({
...baseProps,
rx: w / 2,
ry: h / 2,
fill: resolveFill(node.fill, w, h),
stroke: resolveStrokeColor(node.stroke),
strokeWidth: resolveStrokeWidth(node.stroke),
}) as FabricObjectWithPenId
}
break
}
case 'line': {

View file

@ -1,5 +1,6 @@
import * as fabric from 'fabric'
import type { PenNode } from '@/types/pen'
import { buildEllipseArcPath, isArcEllipse } from '@/utils/arc-path'
import type { FabricObjectWithPenId } from './canvas-object-factory'
import {
resolveFill,
@ -102,13 +103,40 @@ export function syncFabricObject(
case 'ellipse': {
const w = sizeToNumber(node.width, 100)
const h = sizeToNumber(node.height, 100)
obj.set({
rx: w / 2,
ry: h / 2,
fill: resolveFill(node.fill, w, h),
stroke: resolveStrokeColor(node.stroke),
strokeWidth: resolveStrokeWidth(node.stroke),
})
if (isArcEllipse(node.startAngle, node.sweepAngle, node.innerRadius)) {
// Arc ellipse rendered as Fabric.Path — update path data
const arcD = buildEllipseArcPath(w, h, node.startAngle ?? 0, node.sweepAngle ?? 360, node.innerRadius ?? 0)
if (obj instanceof fabric.Path) {
const trackedD = typeof (obj as any).__sourceD === 'string' ? (obj as any).__sourceD.trim() : ''
if (arcD !== trackedD) {
const tmp = new fabric.Path(arcD)
;(obj as any).path = (tmp as any).path
;(obj as any).__sourceD = arcD
;(obj as any).__nativeWidth = w
;(obj as any).__nativeHeight = h
}
}
// Override Fabric's auto-computed bounding box — the arc path is
// drawn within a 0,0 → w,h coordinate space.
;(obj as any).pathOffset = new fabric.Point(w / 2, h / 2)
obj.set({
width: w,
height: h,
scaleX: 1,
scaleY: 1,
fill: resolveFill(node.fill, w, h),
stroke: resolveStrokeColor(node.stroke),
strokeWidth: resolveStrokeWidth(node.stroke),
})
} else {
obj.set({
rx: w / 2,
ry: h / 2,
fill: resolveFill(node.fill, w, h),
stroke: resolveStrokeColor(node.stroke),
strokeWidth: resolveStrokeWidth(node.stroke),
})
}
break
}
case 'line': {

View file

@ -422,35 +422,48 @@ export default function AIChatPanel() {
}
}, [])
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files
if (!files) return
const processImageFiles = useCallback((files: File[]) => {
const maxSize = 5 * 1024 * 1024 // 5MB
const maxCount = 4
const currentCount = useAIStore.getState().pendingAttachments.length
const remaining = maxCount - currentCount
if (remaining <= 0) return
Array.from(files).slice(0, remaining).forEach((file) => {
if (file.size > maxSize) return
if (!file.type.startsWith('image/')) return
const reader = new FileReader()
reader.onload = () => {
const dataUrl = reader.result as string
const base64 = dataUrl.split(',')[1]
if (!base64) return
addPendingAttachment({
id: nanoid(),
name: file.name,
mediaType: file.type,
data: base64,
size: file.size,
})
}
reader.readAsDataURL(file)
})
files.filter((f) => f.type.startsWith('image/') && f.size <= maxSize)
.slice(0, remaining)
.forEach((file) => {
const reader = new FileReader()
reader.onload = () => {
const dataUrl = reader.result as string
const base64 = dataUrl.split(',')[1]
if (!base64) return
addPendingAttachment({
id: nanoid(),
name: file.name || 'pasted-image.png',
mediaType: file.type,
data: base64,
size: file.size,
})
}
reader.readAsDataURL(file)
})
}, [addPendingAttachment])
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files
if (!files) return
processImageFiles(Array.from(files))
// Reset so the same file can be re-selected
e.target.value = ''
}, [addPendingAttachment])
}, [processImageFiles])
const handlePaste = useCallback((e: React.ClipboardEvent) => {
const files = Array.from(e.clipboardData?.files ?? [])
const images = files.filter((f) => f.type.startsWith('image/'))
if (images.length === 0) return
e.preventDefault()
processImageFiles(images)
}, [processImageFiles])
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
@ -597,6 +610,7 @@ export default function AIChatPanel() {
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
onPaste={handlePaste}
placeholder={isStreaming ? t('ai.generating') : t('ai.designWithAgent')}
disabled={isStreaming}
rows={2}

View file

@ -741,6 +741,14 @@ async function main() {
}
}
// Prevent uncaught errors from crashing the MCP server process
process.on('uncaughtException', (err) => {
console.error('MCP server uncaught exception:', err)
})
process.on('unhandledRejection', (err) => {
console.error('MCP server unhandled rejection:', err)
})
main().catch((err) => {
console.error('MCP server failed to start:', err)
process.exit(1)

View file

@ -118,3 +118,4 @@ export const RETRY_TIMEOUT_CONFIG = {
noTextTimeoutMaxMs: 480_000,
firstTextTimeoutMaxMs: 1_200_000,
} as const

View file

@ -7,6 +7,7 @@ import { DESIGN_MODIFIER_PROMPT } from './ai-prompts'
import { executeOrchestration } from './orchestrator'
import { DESIGN_STREAM_TIMEOUTS } from './ai-runtime-config'
import { extractJsonFromResponse } from './design-parser'
import { resolveModelProfile, applyProfileToTimeouts } from './model-profiles'
// ---------------------------------------------------------------------------
// Re-exports for backward compatibility — consumers that import from
@ -115,9 +116,12 @@ export async function generateDesignModification(
let fullResponse = ''
let streamError: string | null = null
const profile = resolveModelProfile(options?.model)
const timeouts = applyProfileToTimeouts({ ...DESIGN_STREAM_TIMEOUTS }, profile)
for await (const chunk of streamChat(DESIGN_MODIFIER_PROMPT, [
{ role: 'user', content: userMessage },
], options?.model, DESIGN_STREAM_TIMEOUTS, options?.provider, abortSignal)) {
], options?.model, timeouts, options?.provider, abortSignal)) {
if (chunk.type === 'thinking') {
// Ignore thinking chunks for modification -- caller already shows progress
} else if (chunk.type === 'text') {
@ -137,5 +141,9 @@ export async function generateDesignModification(
throw new Error(streamError)
}
throw new Error('Failed to parse modified nodes from AI response')
const preview = fullResponse.trim().slice(0, 150)
const hint = fullResponse.trim().length === 0
? 'The model returned an empty response.'
: `Model output: "${preview}${fullResponse.length > 150 ? '…' : ''}"`
throw new Error(`Could not parse design nodes from model response. ${hint}`)
}

View file

@ -37,6 +37,11 @@ export function extractJsonFromResponse(text: string): PenNode[] | null {
return nodes
}
// Fallback: try parsing a single root node with nested children
// (weaker models may output one root object instead of an array)
const singleRoot = tryParseSingleRootNode(text)
if (singleRoot) return singleRoot
// Fallback: try parsing raw text after removing <step> tags.
const stripped = text.replace(/<step[\s\S]*?<\/step>/g, '').trim()
const directNodes = tryParseNodes(stripped)
@ -183,6 +188,28 @@ function parseJsonlToTree(text: string): PenNode[] | null {
return roots.length > 0 ? roots : null
}
/**
* Try to parse a single root PenNode with nested children from raw text.
* Handles the case where weaker models output a single JSON object
* instead of an array or JSONL format.
*/
function tryParseSingleRootNode(text: string): PenNode[] | null {
const first = text.indexOf('{')
const last = text.lastIndexOf('}')
if (first < 0 || last <= first) return null
try {
const obj = JSON.parse(text.slice(first, last + 1)) as Record<string, unknown>
if (
typeof obj.id === 'string' &&
typeof obj.type === 'string' &&
Array.isArray(obj.children)
) {
return [obj as unknown as PenNode]
}
} catch { /* ignore parse errors */ }
return null
}
function tryParseNodes(json: string): PenNode[] | null {
try {
const parsed = JSON.parse(json.trim())

View file

@ -0,0 +1,59 @@
export type DesignType = 'mobile-screen' | 'app-screen' | 'landing-page'
export interface DesignTypePreset {
type: DesignType
width: number
/** Section total height (0 = auto based on section count) */
height: number
/** Explicit rootFrame height (0 = auto) */
rootHeight: number
defaultSections: string[]
/** First preset with any matching pattern wins */
patterns: RegExp[]
}
const PRESETS: DesignTypePreset[] = [
{
type: 'mobile-screen',
width: 375,
height: 812,
rootHeight: 812,
defaultSections: ['Header', 'Main Content', 'Actions'],
patterns: [
/mobile|手机|phone|移动端/i,
/app\s*screen/i,
/(登录|注册|login|sign\s*up)\b/i,
],
},
{
type: 'app-screen',
width: 1200,
height: 800,
rootHeight: 800,
defaultSections: ['Header', 'Main Content', 'Actions'],
patterns: [
/(settings|设置|preference|偏好)/i,
/(profile|个人|account|账户)/i,
/(dashboard|admin|管理|后台|控制台)/i,
/(form|表单|modal|dialog|弹窗)/i,
],
},
{
type: 'landing-page',
width: 1200,
height: 0,
rootHeight: 0,
defaultSections: ['Navigation', 'Hero', 'Core Highlights', 'Feature Showcase', 'CTA', 'Footer'],
patterns: [], // default fallback
},
]
/** Detect design type from prompt text. Returns the first preset with a matching pattern, or the landing-page fallback. */
export function detectDesignType(prompt: string): DesignTypePreset {
for (const preset of PRESETS) {
if (preset.patterns.length === 0) continue
if (preset.patterns.some((p) => p.test(prompt))) return preset
}
// Last preset (landing-page) is the default fallback
return PRESETS[PRESETS.length - 1]
}

View file

@ -0,0 +1,120 @@
/**
* Model capability profiles adapt AI configs per model tier.
*
* Each profile matches a model ID pattern and overrides thinking mode,
* effort, timeouts, and prompt complexity as needed. First match wins.
*/
import type { ThinkingMode, ThinkingEffort } from './ai-runtime-config'
export type ModelTier = 'full' | 'standard' | 'basic'
export interface ModelProfile {
match: string | RegExp
tier: ModelTier
thinkingMode?: ThinkingMode
effort?: ThinkingEffort
timeoutMultiplier?: number
simplifiedPrompt?: boolean
label?: string
}
const MODEL_PROFILES: ModelProfile[] = [
// Full tier — defaults unchanged
{ match: 'claude-opus', tier: 'full', label: 'Claude Opus' },
{ match: 'claude-sonnet', tier: 'full', label: 'Claude Sonnet' },
{ match: 'claude-3-5', tier: 'full', label: 'Claude 3.5' },
{ match: 'claude-3.5', tier: 'full', label: 'Claude 3.5' },
{ match: 'claude-4', tier: 'full', label: 'Claude 4' },
// Standard tier — disable thinking (unsupported or unhelpful)
{ match: 'gpt-4o', tier: 'standard', thinkingMode: 'disabled', label: 'GPT-4o' },
{ match: 'o1', tier: 'standard', thinkingMode: 'disabled', label: 'o1' },
{ match: 'o3', tier: 'standard', thinkingMode: 'disabled', label: 'o3' },
{ match: 'o4', tier: 'standard', thinkingMode: 'disabled', label: 'o4' },
{ match: 'gemini-pro', tier: 'standard', thinkingMode: 'disabled', label: 'Gemini Pro' },
{ match: /^gemini-2/, tier: 'standard', thinkingMode: 'disabled', label: 'Gemini 2' },
{ match: 'deepseek', tier: 'standard', thinkingMode: 'disabled', label: 'DeepSeek' },
// Basic tier — disable thinking, use simplified prompt
{ match: 'claude-haiku', tier: 'basic', thinkingMode: 'disabled', simplifiedPrompt: true, label: 'Claude Haiku' },
{ match: 'gpt-4o-mini', tier: 'basic', thinkingMode: 'disabled', simplifiedPrompt: true, label: 'GPT-4o Mini' },
{ match: 'gpt-4.1-mini', tier: 'basic', thinkingMode: 'disabled', simplifiedPrompt: true, label: 'GPT-4.1 Mini' },
{ match: 'gpt-4.1-nano', tier: 'basic', thinkingMode: 'disabled', simplifiedPrompt: true, label: 'GPT-4.1 Nano' },
{ match: 'minimax', tier: 'basic', thinkingMode: 'disabled', simplifiedPrompt: true, label: 'MiniMax' },
{ match: 'qwen', tier: 'basic', thinkingMode: 'disabled', simplifiedPrompt: true, label: 'Qwen' },
{ match: 'llama', tier: 'basic', thinkingMode: 'disabled', simplifiedPrompt: true, label: 'Llama' },
{ match: 'mistral', tier: 'basic', thinkingMode: 'disabled', simplifiedPrompt: true, label: 'Mistral' },
{ match: 'gemma', tier: 'basic', thinkingMode: 'disabled', simplifiedPrompt: true, label: 'Gemma' },
]
const DEFAULT_PROFILE: ModelProfile = {
match: '',
tier: 'standard',
thinkingMode: 'disabled',
label: 'Unknown model',
}
/**
* Resolve a model profile by ID. Strips `providerID/` prefix, first match wins.
*/
export function resolveModelProfile(modelId?: string): ModelProfile {
if (!modelId) return { ...DEFAULT_PROFILE, tier: 'full', thinkingMode: undefined, label: 'Default (no model)' }
// Strip provider prefix (e.g. "opencode/gpt-4o" → "gpt-4o")
const normalized = modelId.includes('/') ? modelId.slice(modelId.indexOf('/') + 1) : modelId
const lower = normalized.toLowerCase()
for (const profile of MODEL_PROFILES) {
if (typeof profile.match === 'string') {
if (lower.startsWith(profile.match) || lower.includes(profile.match)) {
return profile
}
} else {
if (profile.match.test(lower)) {
return profile
}
}
}
return DEFAULT_PROFILE
}
/**
* Check if a profile requires the simplified sub-agent prompt.
*/
export function needsSimplifiedPrompt(profile: ModelProfile): boolean {
return profile.simplifiedPrompt === true
}
/**
* Apply profile overrides to a timeout config object (mutates a copy).
*/
export function applyProfileToTimeouts<T extends {
hardTimeoutMs: number
noTextTimeoutMs: number
firstTextTimeoutMs?: number
thinkingMode?: ThinkingMode
effort?: ThinkingEffort
}>(base: T, profile: ModelProfile): T {
const result = { ...base }
if (profile.timeoutMultiplier != null && profile.timeoutMultiplier !== 1) {
const m = profile.timeoutMultiplier
result.hardTimeoutMs = Math.round(result.hardTimeoutMs * m)
result.noTextTimeoutMs = Math.round(result.noTextTimeoutMs * m)
if (result.firstTextTimeoutMs != null) {
result.firstTextTimeoutMs = Math.round(result.firstTextTimeoutMs * m)
}
}
if (profile.thinkingMode != null) {
result.thinkingMode = profile.thinkingMode
}
if (profile.effort != null) {
result.effort = profile.effort
}
return result
}

View file

@ -5,7 +5,9 @@ import {
PROMPT_OPTIMIZER_LIMITS,
SUB_AGENT_TIMEOUT_PROFILES,
} from './ai-runtime-config'
import { detectDesignType } from './design-type-presets'
import { getAllPrinciples } from './design-principles'
import { resolveModelProfile, applyProfileToTimeouts } from './model-profiles'
export interface PreparedDesignPrompt {
original: string
@ -17,7 +19,7 @@ export interface PreparedDesignPrompt {
designPrinciples: string
}
export function getSubAgentTimeouts(promptLength: number): {
export function getSubAgentTimeouts(promptLength: number, model?: string): {
hardTimeoutMs: number
noTextTimeoutMs: number
thinkingResetsTimeout: boolean
@ -26,16 +28,18 @@ export function getSubAgentTimeouts(promptLength: number): {
thinkingMode: 'adaptive' | 'disabled' | 'enabled'
effort: 'low' | 'medium' | 'high' | 'max'
} {
let base
if (promptLength < PROMPT_OPTIMIZER_LIMITS.longPromptCharThreshold) {
return { ...SUB_AGENT_TIMEOUT_PROFILES.short }
base = { ...SUB_AGENT_TIMEOUT_PROFILES.short }
} else if (promptLength < PROMPT_TIMEOUT_BUCKETS.mediumPromptMaxChars) {
base = { ...SUB_AGENT_TIMEOUT_PROFILES.medium }
} else {
base = { ...SUB_AGENT_TIMEOUT_PROFILES.long }
}
if (promptLength < PROMPT_TIMEOUT_BUCKETS.mediumPromptMaxChars) {
return { ...SUB_AGENT_TIMEOUT_PROFILES.medium }
}
return { ...SUB_AGENT_TIMEOUT_PROFILES.long }
return applyProfileToTimeouts(base, resolveModelProfile(model))
}
export function getOrchestratorTimeouts(promptLength: number): {
export function getOrchestratorTimeouts(promptLength: number, model?: string): {
hardTimeoutMs: number
noTextTimeoutMs: number
thinkingResetsTimeout: boolean
@ -44,13 +48,15 @@ export function getOrchestratorTimeouts(promptLength: number): {
thinkingMode: 'adaptive' | 'disabled' | 'enabled'
effort: 'low' | 'medium' | 'high' | 'max'
} {
let base
if (promptLength < PROMPT_OPTIMIZER_LIMITS.longPromptCharThreshold) {
return { ...ORCHESTRATOR_TIMEOUT_PROFILES.short }
base = { ...ORCHESTRATOR_TIMEOUT_PROFILES.short }
} else if (promptLength < PROMPT_TIMEOUT_BUCKETS.mediumPromptMaxChars) {
base = { ...ORCHESTRATOR_TIMEOUT_PROFILES.medium }
} else {
base = { ...ORCHESTRATOR_TIMEOUT_PROFILES.long }
}
if (promptLength < PROMPT_TIMEOUT_BUCKETS.mediumPromptMaxChars) {
return { ...ORCHESTRATOR_TIMEOUT_PROFILES.medium }
}
return { ...ORCHESTRATOR_TIMEOUT_PROFILES.long }
return applyProfileToTimeouts(base, resolveModelProfile(model))
}
/**
@ -72,34 +78,26 @@ export function prepareDesignPrompt(prompt: string): PreparedDesignPrompt {
}
export function buildFallbackPlanFromPrompt(prompt: string): OrchestratorPlan {
const labels = extractFallbackSectionLabels(prompt)
const preset = detectDesignType(prompt)
const labels = extractFallbackSectionLabels(prompt, preset.defaultSections)
const sectionCount = Math.max(1, labels.length)
const isMobile = /(?:mobile|手机|phone|app\s*screen|登录|注册|login|signup)/i.test(prompt)
const isAppScreen = /(?:login|signup|register|登录|注册|settings|设置|profile|个人|form|表单|dashboard|modal|dialog)/i.test(prompt)
const width = isMobile ? 375 : 1200
// Mobile app screens: fixed 812px viewport. Desktop landing pages: auto-expand. Desktop app screens: fixed height.
const totalHeight = isMobile
? 812
: isAppScreen
? 800
: sectionCount >= 4 ? 4000 : 800
const totalHeight = preset.height || (sectionCount >= 4 ? 4000 : 800)
const heights = allocateSectionHeights(totalHeight, sectionCount)
return {
rootFrame: {
id: 'page',
name: 'Page',
width,
height: isMobile ? 812 : (isAppScreen ? totalHeight : 0),
width: preset.width,
height: preset.rootHeight || 0,
layout: 'vertical',
fill: [{ type: 'solid', color: '#F8FAFC' }],
},
subtasks: labels.map((label, index) => ({
id: makeSafeSectionId(label, index),
label,
region: { width, height: heights[index] ?? 120 },
region: { width: preset.width, height: heights[index] ?? 120 },
idPrefix: '',
parentFrameId: null,
})),
@ -132,7 +130,7 @@ function truncateByCharCount(text: string, maxChars: number): string {
return `${truncated.trim()}\n\n[truncated]`
}
function extractFallbackSectionLabels(prompt: string): string[] {
function extractFallbackSectionLabels(prompt: string, defaultSections: string[]): string[] {
const lines = prompt.replace(/\r/g, '').split('\n')
const labels: string[] = []
const seen = new Set<string>()
@ -169,24 +167,7 @@ function extractFallbackSectionLabels(prompt: string): string[] {
if (labels.length > 0) return labels
// Detect design type to provide appropriate fallback labels
const isAppScreen = /(?:login|signup|register|登录|注册|settings|设置|profile|个人|form|表单|dashboard|modal|dialog)/i.test(prompt)
if (isAppScreen) {
return [
'Header',
'Main Content',
'Actions',
]
}
return [
'Navigation',
'Hero',
'Core Highlights',
'Feature Showcase',
'CTA',
'Footer',
]
return defaultSections
}
function sanitizePlanSectionLabel(label: string): string {

View file

@ -96,3 +96,48 @@ ${BLOCK}json
${BLOCK}
Start with ${BLOCK}json immediately. No preamble.`
/**
* Simplified sub-agent prompt for weaker models (basic tier).
* Uses nested JSON with children arrays (not flat JSONL with _parent).
* Keeps only essential node types and rules.
*/
export const SUB_AGENT_PROMPT_SIMPLIFIED = `Generate a UI section as a nested JSON tree. Output a ${BLOCK}json block with a single root object containing nested "children" arrays.
TYPES:
frame (width,height,layout,gap,padding,justifyContent,alignItems,cornerRadius,fill,children), rectangle (width,height,cornerRadius,fill), text (content,fontFamily,fontSize,fontWeight,fill,width,textAlign), icon_font (iconFontName,width,height,fill)
SHARED: id, type, name
RULES:
- Root: type="frame", width="fill_container", height="fit_content", layout="vertical".
- Children go in "children" arrays. No x/y on layout children.
- width/height: number | "fill_container" | "fit_content".
- fill: [{"type":"solid","color":"#hex"}].
- Text: never set height. Use width="fill_container" for wrapping text.
- Icons: use icon_font with iconFontName (lucide names: search, bell, user, heart, star, plus, x, check, chevron-right, settings). Sizes: 16/20/24px.
- Buttons: frame with padding=[12,24] containing a text child.
- No emoji characters. No markdown. No explanation.
EXAMPLE:
${BLOCK}json
{
"id": "root",
"type": "frame",
"name": "Hero",
"width": "fill_container",
"height": "fit_content",
"layout": "vertical",
"gap": 24,
"padding": [48, 24],
"fill": [{"type": "solid", "color": "#F8FAFC"}],
"children": [
{"id": "title", "type": "text", "name": "Headline", "content": "Learn Smarter", "fontSize": 48, "fontWeight": 700, "fontFamily": "Space Grotesk", "fill": [{"type": "solid", "color": "#0F172A"}]},
{"id": "desc", "type": "text", "name": "Description", "content": "AI-powered learning", "fontSize": 16, "width": "fill_container", "fill": [{"type": "solid", "color": "#64748B"}]},
{"id": "cta", "type": "frame", "name": "CTA", "padding": [14, 28], "cornerRadius": 10, "justifyContent": "center", "fill": [{"type": "solid", "color": "#2563EB"}], "children": [
{"id": "cta-text", "type": "text", "content": "Get Started", "fontSize": 16, "fontWeight": 600, "fill": [{"type": "solid", "color": "#FFFFFF"}]}
]}
]
}
${BLOCK}
Start with ${BLOCK}json immediately. No preamble.`

View file

@ -18,11 +18,12 @@ import type {
SubAgentResult,
} from './ai-types'
import { streamChat } from './ai-service'
import { SUB_AGENT_PROMPT } from './orchestrator-prompts'
import { SUB_AGENT_PROMPT, SUB_AGENT_PROMPT_SIMPLIFIED } from './orchestrator-prompts'
import {
type PreparedDesignPrompt,
getSubAgentTimeouts,
} from './orchestrator-prompt-optimizer'
import { resolveModelProfile, needsSimplifiedPrompt } from './model-profiles'
import {
expandRootFrameHeight,
extractStreamingNodes,
@ -91,7 +92,7 @@ export async function executeSubAgents(
},
abortSignal?: AbortSignal,
): Promise<SubAgentResult[]> {
const timeoutOptions = getSubAgentTimeouts(preparedPrompt.originalLength)
const timeoutOptions = getSubAgentTimeouts(preparedPrompt.originalLength, request.model)
// Sequential path — each subtask runs one at a time
if (concurrency <= 1) {
@ -193,7 +194,8 @@ export async function executeSubAgents(
// If ALL failed with zero nodes, throw
const totalNodes = collected.reduce((sum, r) => sum + r.nodes.length, 0)
if (totalNodes === 0 && collected.length > 0) {
const firstError = collected.find((r) => r.error)?.error ?? 'All sub-agents failed'
const errors = collected.filter((r) => r.error).map((r) => r.error!)
const firstError = errors[0] ?? 'The model failed to generate any design output.'
throw new Error(firstError)
}
@ -241,10 +243,12 @@ async function executeSubAgent(
request.context?.themes,
)
// Inject design principles into the system prompt (selective, not all at once)
const systemPrompt = preparedPrompt.designPrinciples
? `${SUB_AGENT_PROMPT}\n\n${preparedPrompt.designPrinciples}`
: SUB_AGENT_PROMPT
// Select prompt variant based on model profile
const profile = resolveModelProfile(request.model)
const basePrompt = needsSimplifiedPrompt(profile) ? SUB_AGENT_PROMPT_SIMPLIFIED : SUB_AGENT_PROMPT
const systemPrompt = preparedPrompt.designPrinciples && !needsSimplifiedPrompt(profile)
? `${basePrompt}\n\n${preparedPrompt.designPrinciples}`
: basePrompt
let rawResponse = ''
const nodes: PenNode[] = []
@ -356,11 +360,27 @@ async function executeSubAgent(
if (nodes.length === 0) {
progressEntry.status = 'error'
emitProgress(plan, progress, callbacks)
// Build a diagnostic error with a preview of what the model returned
let errorMsg = 'The model response could not be parsed as design nodes.'
if (rawResponse.trim().length === 0) {
errorMsg += ' The model returned an empty response.'
} else {
// Show a short snippet so the user can diagnose the issue
const preview = rawResponse.trim().slice(0, 150)
const hasJson = rawResponse.includes('{') && rawResponse.includes('"type"')
if (!hasJson) {
errorMsg += ' The response did not contain valid JSON. Model output: "' + preview + (rawResponse.length > 150 ? '…' : '') + '"'
} else {
errorMsg += ' JSON was found but contained no valid PenNode objects (need "id" and "type" fields).'
}
}
return {
subtaskId: subtask.id,
nodes,
rawResponse,
error: 'Subtask completed but returned no parseable PenNode output.',
error: errorMsg,
}
}

View file

@ -440,7 +440,7 @@ async function callOrchestrator(
ORCHESTRATOR_PROMPT,
[{ role: 'user', content: prompt }],
model,
getOrchestratorTimeouts(timeoutHintLength),
getOrchestratorTimeouts(timeoutHintLength, model),
provider,
abortSignal,
)) {
@ -456,7 +456,11 @@ async function callOrchestrator(
const plan = parseOrchestratorResponse(rawResponse)
if (!plan) {
throw new Error('Failed to parse orchestrator plan')
const preview = rawResponse.trim().slice(0, 150)
const hint = rawResponse.trim().length === 0
? 'The model returned an empty response.'
: `Model output: "${preview}${rawResponse.length > 150 ? '…' : ''}"`
throw new Error(`Could not parse design plan from model response. ${hint}`)
}
return plan

View file

@ -3,6 +3,7 @@ import { getActivePageChildren } from '@/stores/document-tree-utils'
import type { PenFill, PenStroke, PenEffect, ShadowEffect } from '@/types/styles'
import { isVariableRef } from '@/variables/resolve-variables'
import { variableNameToCSS, generateCSSVariables } from '@/services/codegen/css-variables-generator'
import { buildEllipseArcPath, isArcEllipse } from '@/utils/arc-path'
/**
* Converts PenDocument nodes to HTML + CSS.
@ -220,6 +221,16 @@ function generateNodeHTML(
}
case 'ellipse': {
if (isArcEllipse(node.startAngle, node.sweepAngle, node.innerRadius)) {
const w = typeof node.width === 'number' ? node.width : 100
const h = typeof node.height === 'number' ? node.height : 100
const d = buildEllipseArcPath(w, h, node.startAngle ?? 0, node.sweepAngle ?? 360, node.innerRadius ?? 0)
const fill = node.fill?.[0]?.type === 'solid' ? varOrLiteral(node.fill[0].color) : '#000'
Object.assign(css, effectsToCSS(node.effects))
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? 'arc')
rules.push({ className, properties: css })
return `${pad}<svg class="${className}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}"><path d="${d}" fill="${fill}" /></svg>`
}
if (typeof node.width === 'number') css.width = `${node.width}px`
if (typeof node.height === 'number') css.height = `${node.height}px`
css['border-radius'] = '50%'

View file

@ -3,6 +3,7 @@ import { getActivePageChildren } from '@/stores/document-tree-utils'
import type { PenFill, PenStroke, PenEffect, ShadowEffect } from '@/types/styles'
import { isVariableRef } from '@/variables/resolve-variables'
import { variableNameToCSS } from '@/services/codegen/css-variables-generator'
import { buildEllipseArcPath, isArcEllipse } from '@/utils/arc-path'
/**
* Converts PenDocument nodes to React + Tailwind code.
@ -253,6 +254,15 @@ function generateNodeJSX(node: PenNode, depth: number): string {
}
case 'ellipse': {
if (isArcEllipse(node.startAngle, node.sweepAngle, node.innerRadius)) {
const w = typeof node.width === 'number' ? node.width : 100
const h = typeof node.height === 'number' ? node.height : 100
const d = buildEllipseArcPath(w, h, node.startAngle ?? 0, node.sweepAngle ?? 360, node.innerRadius ?? 0)
const fill = node.fill?.[0]?.type === 'solid' ? node.fill[0].color : '#000'
classes.push(...effectsToTailwind(node.effects))
const cls = classes.length > 0 ? ` className="${classes.join(' ')}"` : ''
return `${pad}<svg${cls} width="${w}" height="${h}" viewBox="0 0 ${w} ${h}"><path d="${d}" fill="${fill}" /></svg>`
}
classes.push(
'rounded-full',
...sizeToTailwind(node.width, node.height),

View file

@ -3,6 +3,7 @@ import { getActivePageChildren } from '@/stores/document-tree-utils'
import type { PenFill, PenStroke, PenEffect, ShadowEffect } from '@/types/styles'
import { isVariableRef } from '@/variables/resolve-variables'
import { variableNameToCSS } from '@/services/codegen/css-variables-generator'
import { buildEllipseArcPath, isArcEllipse } from '@/utils/arc-path'
/**
* Converts PenDocument nodes to Svelte component code.
@ -148,6 +149,16 @@ function generateNodeMarkup(node: PenNode, depth: number, rules: CSSRule[]): str
}
case 'ellipse': {
if (isArcEllipse(node.startAngle, node.sweepAngle, node.innerRadius)) {
const w = typeof node.width === 'number' ? node.width : 100
const h = typeof node.height === 'number' ? node.height : 100
const d = buildEllipseArcPath(w, h, node.startAngle ?? 0, node.sweepAngle ?? 360, node.innerRadius ?? 0)
const fill = node.fill?.[0]?.type === 'solid' ? varOrLiteral(node.fill[0].color) : '#000'
Object.assign(css, effectsToCSS(node.effects))
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? 'arc')
rules.push({ className, properties: css })
return `${pad}<svg class="${className}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}"><path d="${d}" fill="${fill}" /></svg>`
}
if (typeof node.width === 'number') css.width = `${node.width}px`
if (typeof node.height === 'number') css.height = `${node.height}px`
css['border-radius'] = '50%'

View file

@ -3,6 +3,7 @@ import { getActivePageChildren } from '@/stores/document-tree-utils'
import type { PenFill, PenStroke, PenEffect, ShadowEffect } from '@/types/styles'
import { isVariableRef } from '@/variables/resolve-variables'
import { variableNameToCSS } from '@/services/codegen/css-variables-generator'
import { buildEllipseArcPath, isArcEllipse } from '@/utils/arc-path'
/**
* Converts PenDocument nodes to Vue 3 Single File Component (SFC) code.
@ -154,6 +155,16 @@ function generateNodeTemplate(node: PenNode, depth: number, rules: CSSRule[]): s
}
case 'ellipse': {
if (isArcEllipse(node.startAngle, node.sweepAngle, node.innerRadius)) {
const w = typeof node.width === 'number' ? node.width : 100
const h = typeof node.height === 'number' ? node.height : 100
const d = buildEllipseArcPath(w, h, node.startAngle ?? 0, node.sweepAngle ?? 360, node.innerRadius ?? 0)
const fill = node.fill?.[0]?.type === 'solid' ? varOrLiteral(node.fill[0].color) : '#000'
Object.assign(css, effectsToCSS(node.effects))
const className = nextClassName(node.name?.replace(/\s+/g, '-').toLowerCase() ?? 'arc')
rules.push({ className, properties: css })
return `${pad}<svg class="${className}" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}"><path d="${d}" fill="${fill}" /></svg>`
}
if (typeof node.width === 'number') css.width = `${node.width}px`
if (typeof node.height === 'number') css.height = `${node.height}px`
css['border-radius'] = '50%'

View file

@ -1,4 +1,7 @@
import type { FigmaNodeChange, FigmaMatrix, FigmaImportLayoutMode } from './figma-types'
import type {
FigmaNodeChange, FigmaMatrix, FigmaImportLayoutMode,
FigmaSymbolOverride, FigmaDerivedSymbolDataEntry, FigmaGUID,
} from './figma-types'
import type { PenNode, SizingBehavior, ImageFitMode } from '@/types/pen'
import { mapFigmaFills } from './figma-fill-mapper'
import { mapFigmaStroke } from './figma-stroke-mapper'
@ -10,6 +13,28 @@ import { lookupIconByName } from '@/services/ai/icon-resolver'
import type { TreeNode } from './figma-tree-builder'
import { guidToString } from './figma-tree-builder'
/** Scale tree children's transforms and sizes to fit a different parent size. */
function scaleTreeChildren(children: TreeNode[], sx: number, sy: number): TreeNode[] {
if (Math.abs(sx - 1) < 0.001 && Math.abs(sy - 1) < 0.001) return children
return children.map((child) => {
const figma = { ...child.figma }
if (figma.transform) {
figma.transform = {
...figma.transform,
m02: figma.transform.m02 * sx,
m12: figma.transform.m12 * sy,
}
}
if (figma.size) {
figma.size = { x: figma.size.x * sx, y: figma.size.y * sy }
}
return {
figma,
children: scaleTreeChildren(child.children, sx, sy),
}
})
}
const SKIPPED_TYPES = new Set([
'SLICE', 'CONNECTOR', 'SHAPE_WITH_TEXT', 'STICKY', 'STAMP',
'HIGHLIGHT', 'WASHI_TAPE', 'CODE_BLOCK', 'MEDIA', 'WIDGET',
@ -18,6 +43,8 @@ const SKIPPED_TYPES = new Set([
export interface ConversionContext {
componentMap: Map<string, string>
/** SYMBOL TreeNodes keyed by figma GUID — includes internal canvases for instance inlining */
symbolTree: Map<string, TreeNode>
warnings: string[]
generateId: () => string
blobs: (Uint8Array | string)[]
@ -41,20 +68,41 @@ function resolveHeight(figma: FigmaNodeChange, parentStackMode: string | undefin
function extractPosition(figma: FigmaNodeChange): { x: number; y: number } {
if (figma.transform) {
return {
x: Math.round(figma.transform.m02),
y: Math.round(figma.transform.m12),
x: Math.round(figma.transform.m02 * 100) / 100,
y: Math.round(figma.transform.m12 * 100) / 100,
}
}
return { x: 0, y: 0 }
}
function normalizeAngle(deg: number): number {
let a = deg % 360
if (a < 0) a += 360
return Math.round(a * 100) / 100
}
function extractRotation(transform?: FigmaMatrix): number | undefined {
if (!transform) return undefined
const angle = Math.atan2(transform.m10, transform.m00) * (180 / Math.PI)
// Use abs(m00) to ignore horizontal flip (which is handled separately as flipX)
const angle = Math.atan2(transform.m10, Math.abs(transform.m00)) * (180 / Math.PI)
const rounded = Math.round(angle)
return rounded !== 0 ? rounded : undefined
}
function extractFlip(transform?: FigmaMatrix): { flipX?: boolean; flipY?: boolean } {
if (!transform) return {}
const result: { flipX?: boolean; flipY?: boolean } = {}
// Determinant sign of the 2x2 rotation/scale sub-matrix detects reflection
// m00*m11 - m01*m10 < 0 means a single-axis flip
const det = transform.m00 * transform.m11 - transform.m01 * transform.m10
if (det < -0.001) {
// Check which axis is flipped by looking at the scale signs
if (transform.m00 < 0) result.flipX = true
else result.flipY = true
}
return result
}
function mapCornerRadius(
figma: FigmaNodeChange
): number | [number, number, number, number] | undefined {
@ -77,8 +125,9 @@ function mapCornerRadius(
function commonProps(
figma: FigmaNodeChange,
id: string,
): { id: string; name?: string; x: number; y: number; rotation?: number; opacity?: number; locked?: boolean } {
): { id: string; name?: string; x: number; y: number; rotation?: number; opacity?: number; locked?: boolean; flipX?: boolean; flipY?: boolean } {
const { x, y } = extractPosition(figma)
const flip = extractFlip(figma.transform)
return {
id,
name: figma.name || undefined,
@ -87,6 +136,7 @@ function commonProps(
rotation: extractRotation(figma.transform),
opacity: figma.opacity !== undefined && figma.opacity < 1 ? figma.opacity : undefined,
locked: figma.locked || undefined,
...flip,
}
}
@ -323,6 +373,24 @@ function convertInstance(
: undefined
if (!componentPenId) {
// Instance's own tree node may have 0 children (Figma instances inherit from master).
// Try to inline the master SYMBOL's children so the visual content is preserved.
if (componentGuid && treeNode.children.length === 0) {
const symbolNode = ctx.symbolTree.get(guidToString(componentGuid))
if (symbolNode && symbolNode.children.length > 0) {
const children = applyInstanceOverrides(
symbolNode,
figma.symbolData?.symbolOverrides,
figma.derivedSymbolData,
figma.size,
)
return convertFrame(
{ figma: treeNode.figma, children },
parentStackMode,
ctx,
)
}
}
return convertFrame(treeNode, parentStackMode, ctx)
}
@ -334,6 +402,113 @@ function convertInstance(
}
}
/**
* Apply INSTANCE overrides (fills, arcData) and derived data (sizes, transforms)
* to SYMBOL children when inlining them into an instance.
*/
function applyInstanceOverrides(
symbolNode: TreeNode,
overrides: FigmaSymbolOverride[] | undefined,
derived: FigmaDerivedSymbolDataEntry[] | undefined,
instanceSize: { x: number; y: number } | undefined,
): TreeNode[] {
// If no derived data, fall back to simple scaling
if (!derived || derived.length === 0) {
if (instanceSize && symbolNode.figma.size) {
const sx = instanceSize.x / symbolNode.figma.size.x
const sy = instanceSize.y / symbolNode.figma.size.y
return scaleTreeChildren(symbolNode.children, sx, sy)
}
return symbolNode.children
}
// Build override map keyed by guidPath string
const overrideMap = new Map<string, FigmaSymbolOverride>()
if (overrides) {
for (const ov of overrides) {
if (ov.guidPath?.guids?.length) {
overrideMap.set(guidPathKey(ov.guidPath.guids), ov)
}
}
}
// Build derived map keyed by guidPath string
const derivedMap = new Map<string, FigmaDerivedSymbolDataEntry>()
for (const d of derived) {
if (d.guidPath?.guids?.length) {
derivedMap.set(guidPathKey(d.guidPath.guids), d)
}
}
// Flatten SYMBOL tree in pre-order DFS with children sorted by ascending GUID localID.
// derivedSymbolData entries follow creation order (ascending GUID), not the tree's
// z-order (descending position), so we must match that order.
const flatSymbol: TreeNode[] = []
function flattenDFS(node: TreeNode) {
flatSymbol.push(node)
const sorted = [...node.children].sort((a, b) => {
const aId = a.figma.guid?.localID ?? 0
const bId = b.figma.guid?.localID ?? 0
return aId - bId
})
for (const c of sorted) flattenDFS(c)
}
flattenDFS(symbolNode)
// Map each SYMBOL node's GUID → guidPath key (from derived data, matched by index)
const nodeGuidToPathKey = new Map<string, string>()
for (let i = 0; i < Math.min(flatSymbol.length, derived.length); i++) {
const node = flatSymbol[i]
const d = derived[i]
if (node.figma.guid && d.guidPath?.guids?.length) {
nodeGuidToPathKey.set(
guidToString(node.figma.guid),
guidPathKey(d.guidPath.guids),
)
}
}
// Recursively apply overrides and derived data to each node
function applyToNode(node: TreeNode): TreeNode {
const nodeKey = node.figma.guid ? guidToString(node.figma.guid) : ''
const pathKey = nodeGuidToPathKey.get(nodeKey)
if (!pathKey) {
return { figma: { ...node.figma }, children: node.children.map(applyToNode) }
}
const figma = { ...node.figma }
// Apply derived data (pre-computed sizes and transforms for this instance)
const d = derivedMap.get(pathKey)
if (d) {
if (d.size) figma.size = d.size
if (d.transform) figma.transform = d.transform
if (d.fontSize !== undefined) figma.fontSize = d.fontSize
if (d.derivedTextData) figma.textData = d.derivedTextData
}
// Apply overrides (fills, arcData, text props customized by this instance)
const ov = overrideMap.get(pathKey)
if (ov) {
if (ov.fillPaints) figma.fillPaints = ov.fillPaints
if (ov.arcData) figma.arcData = ov.arcData
if (ov.textData) figma.textData = ov.textData
if (ov.fontSize !== undefined) figma.fontSize = ov.fontSize
if (ov.fontName) figma.fontName = ov.fontName
if (ov.lineHeight) figma.lineHeight = ov.lineHeight
if (ov.letterSpacing) figma.letterSpacing = ov.letterSpacing
}
return { figma, children: node.children.map(applyToNode) }
}
return symbolNode.children.map(applyToNode)
}
function guidPathKey(guids: FigmaGUID[]): string {
return guids.map((g) => guidToString(g)).join('/')
}
function convertRectangle(
treeNode: TreeNode,
parentStackMode: string | undefined,
@ -388,17 +563,69 @@ function convertEllipse(
}
}
// Convert Figma arcData (radians) to PenNode arc properties (degrees)
const arc = figma.arcData
const arcProps = arc ? mapFigmaArcData(arc) : {}
const props = commonProps(figma, id)
// For arc ellipses, absorb flipX/flipY into the arc angles instead of
// relying on canvas-level flip (SVG path flip doesn't work well in Fabric.js).
// Also fix the position: when m00=-1 the x in transform is the right edge.
if (arcProps.sweepAngle !== undefined || arcProps.startAngle !== undefined || arcProps.innerRadius !== undefined) {
const start = arcProps.startAngle ?? 0
const sweep = arcProps.sweepAngle ?? 360
if (props.flipX) {
arcProps.startAngle = normalizeAngle(180 - start - sweep)
arcProps.sweepAngle = sweep
const w = figma.size?.x ?? 0
props.x = Math.round((props.x - w) * 100) / 100
delete props.flipX
}
if (props.flipY) {
arcProps.startAngle = normalizeAngle(360 - start - sweep)
arcProps.sweepAngle = sweep
const h = figma.size?.y ?? 0
props.y = Math.round((props.y - h) * 100) / 100
delete props.flipY
}
}
return {
type: 'ellipse',
...commonProps(figma, id),
...props,
width: resolveWidth(figma, parentStackMode, ctx),
height: resolveHeight(figma, parentStackMode, ctx),
...arcProps,
fill: mapFigmaFills(figma.fillPaints),
stroke: mapFigmaStroke(figma),
effects: mapFigmaEffects(figma.effects),
}
}
/** Convert Figma arcData (radians, endAngle) to PenNode arc props (degrees, sweepAngle). */
function mapFigmaArcData(arc: { startingAngle?: number; endingAngle?: number; innerRadius?: number }): {
startAngle?: number
sweepAngle?: number
innerRadius?: number
} {
const startRad = arc.startingAngle ?? 0
const endRad = arc.endingAngle ?? Math.PI * 2
const inner = arc.innerRadius ?? 0
let sweepRad = endRad - startRad
while (sweepRad < 0) sweepRad += Math.PI * 2
const startDeg = (startRad * 180) / Math.PI
const sweepDeg = (sweepRad * 180) / Math.PI
// Only emit props that differ from the full-circle defaults
const result: { startAngle?: number; sweepAngle?: number; innerRadius?: number } = {}
if (Math.abs(startDeg) > 0.1) result.startAngle = Math.round(startDeg * 100) / 100
if (Math.abs(sweepDeg - 360) > 0.1) result.sweepAngle = Math.round(sweepDeg * 100) / 100
if (inner > 0.001) result.innerRadius = Math.round(inner * 1000) / 1000
return result
}
function convertLine(
treeNode: TreeNode,
ctx: ConversionContext,

View file

@ -7,6 +7,7 @@ import {
buildTree,
buildTreeForClipboard,
collectComponents,
collectSymbolTree,
} from './figma-tree-builder'
import {
type ConversionContext,
@ -48,11 +49,16 @@ export function figmaToPenDocument(
}
const componentMap = new Map<string, string>()
const symbolTree = new Map<string, TreeNode>()
let idCounter = 1
collectComponents(page, componentMap, () => `fig_${idCounter++}`)
// Collect SYMBOL tree nodes from ALL canvases (including Figma's internal canvas
// where master components live) so INSTANCE nodes can inline their content.
collectSymbolTree(tree, symbolTree)
const ctx: ConversionContext = {
componentMap,
symbolTree,
warnings,
generateId: () => `fig_${idCounter++}`,
blobs: decoded.blobs,
@ -112,11 +118,18 @@ export function figmaAllPagesToPenDocument(
}
const componentMap = new Map<string, string>()
const symbolTree = new Map<string, TreeNode>()
let idCounter = 1
const genId = () => `fig_${idCounter++}`
for (const page of allCanvases) {
// Only collect components from user-visible pages so that SYMBOL masters
// living on Figma's internal canvas don't get registered. When an INSTANCE
// references a SYMBOL that isn't in componentMap, convertInstance will
// inline the master's children via symbolTree instead of emitting a
// dangling ref node.
for (const page of pages) {
collectComponents(page, componentMap, genId)
}
collectSymbolTree(tree, symbolTree)
const penPages: PenPage[] = []
@ -124,6 +137,7 @@ export function figmaAllPagesToPenDocument(
const page = pages[i]
const ctx: ConversionContext = {
componentMap,
symbolTree,
warnings,
generateId: genId,
blobs: decoded.blobs,
@ -204,14 +218,19 @@ export function figmaNodeChangesToPenNodes(
}
const componentMap = new Map<string, string>()
const symbolTree = new Map<string, TreeNode>()
let idCounter = 1
const genId = () => `fig_${idCounter++}`
for (const node of topNodes) {
collectComponents(node, componentMap, genId)
}
// For clipboard, also scan all available nodes for symbols
if (tree) collectSymbolTree(tree, symbolTree)
for (const node of topNodes) collectSymbolTree(node, symbolTree)
const ctx: ConversionContext = {
componentMap,
symbolTree,
warnings,
generateId: genId,
blobs: decoded.blobs,

View file

@ -153,6 +153,11 @@ function mapLineHeight(node: FigmaNodeChange): number | undefined {
if (node.lineHeight.units === 'PIXELS' && node.lineHeight.value) {
return node.lineHeight.value
}
// Percentage line height: e.g. 150 → 1.5x multiplier → convert to px
if (node.lineHeight.units === 'PERCENT' && node.lineHeight.value) {
const fontSize = node.fontSize ?? 14
return Math.round(fontSize * node.lineHeight.value / 100)
}
return undefined
}
@ -161,6 +166,11 @@ function mapLetterSpacing(node: FigmaNodeChange): number | undefined {
if (node.letterSpacing.units === 'PIXELS' && node.letterSpacing.value) {
return node.letterSpacing.value
}
// Percentage letter spacing: relative to font size
if (node.letterSpacing.units === 'PERCENT' && node.letterSpacing.value) {
const fontSize = node.fontSize ?? 14
return Math.round(fontSize * node.letterSpacing.value / 100 * 100) / 100
}
return undefined
}

View file

@ -122,3 +122,16 @@ export function collectComponents(
collectComponents(child, map, genId)
}
}
/** Collect SYMBOL TreeNodes keyed by figma GUID from all canvases (including internal). */
export function collectSymbolTree(
root: TreeNode,
map: Map<string, TreeNode>,
): void {
if (root.figma.type === 'SYMBOL' && root.figma.guid) {
map.set(guidToString(root.figma.guid), root)
}
for (const child of root.children) {
collectSymbolTree(child, map)
}
}

View file

@ -114,6 +114,32 @@ export interface FigmaArcData {
innerRadius?: number
}
export interface FigmaGuidPath {
guids: FigmaGUID[]
}
/** Per-child override stored on an INSTANCE's symbolData. */
export interface FigmaSymbolOverride {
guidPath?: FigmaGuidPath
fillPaints?: FigmaPaint[]
arcData?: FigmaArcData
// Text overrides
textData?: FigmaTextData
fontSize?: number
fontName?: FigmaFontName
lineHeight?: FigmaNumber
letterSpacing?: FigmaNumber
}
/** Pre-computed size/transform for each node inside an INSTANCE. */
export interface FigmaDerivedSymbolDataEntry {
guidPath?: FigmaGuidPath
size?: FigmaVector
transform?: FigmaMatrix
fontSize?: number
derivedTextData?: FigmaTextData
}
export type FigmaNodeType =
| 'NONE'
| 'DOCUMENT'
@ -229,9 +255,13 @@ export interface FigmaNodeChange {
arcData?: FigmaArcData
// Component/Instance
symbolData?: { symbolID?: FigmaGUID }
symbolData?: {
symbolID?: FigmaGUID
symbolOverrides?: FigmaSymbolOverride[]
}
overriddenSymbolID?: FigmaGUID
componentKey?: string
derivedSymbolData?: FigmaDerivedSymbolDataEntry[]
}
export interface FigmaDecodedFile {

View file

@ -76,7 +76,7 @@ function r(n: number): string {
}
function joinParts(parts: string[]): string | null {
return parts.length > 1 ? parts.join(' ') : null
return parts.length > 0 ? parts.join(' ') : null
}
/**

View file

@ -116,6 +116,28 @@ code {
.syn-number { color: #f78c6c; }
.syn-bracket { color: #89ddff; }
/* Thin scrollbars — standard property (Firefox, Edge, Chrome 121+) */
* {
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
/* Thin scrollbars — WebKit fallback (older Chrome/Electron) */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--muted-foreground);
}
/* Electron window drag regions */
.app-region-drag { -webkit-app-region: drag; }
.app-region-no-drag { -webkit-app-region: no-drag; }

100
src/utils/arc-path.ts Normal file
View file

@ -0,0 +1,100 @@
/**
* Build an SVG path `d` string for an ellipse arc (pie slice, donut segment, or ring).
*
* @param w - Bounding box width
* @param h - Bounding box height
* @param startDeg - Start angle in degrees (0 = right / 3 o'clock, clockwise)
* @param sweepDeg - Sweep angle in degrees (extent of the arc)
* @param inner - Inner radius ratio 0..1 (0 = pie, >0 = donut)
*/
export function buildEllipseArcPath(
w: number,
h: number,
startDeg: number,
sweepDeg: number,
inner: number,
): string {
const startRad = (startDeg * Math.PI) / 180
const sweepRad = (sweepDeg * Math.PI) / 180
const endRad = startRad + sweepRad
const rx = w / 2
const ry = h / 2
const cx = rx
const cy = ry
// Outer arc endpoints
const ox1 = cx + rx * Math.cos(startRad)
const oy1 = cy + ry * Math.sin(startRad)
const ox2 = cx + rx * Math.cos(endRad)
const oy2 = cy + ry * Math.sin(endRad)
const large = sweepRad > Math.PI ? 1 : 0
// Near-full circle (>=~359.9°): split into two semicircular arcs
if (sweepRad > Math.PI * 2 - 0.02) {
const midRad = startRad + Math.PI
const omx = cx + rx * Math.cos(midRad)
const omy = cy + ry * Math.sin(midRad)
if (inner <= 0.001) {
return [
`M${f(ox1)} ${f(oy1)}`,
`A${f(rx)} ${f(ry)} 0 1 1 ${f(omx)} ${f(omy)}`,
`A${f(rx)} ${f(ry)} 0 1 1 ${f(ox1)} ${f(oy1)}`,
'Z',
].join(' ')
}
const irx = rx * inner
const iry = ry * inner
const ix1 = cx + irx * Math.cos(startRad)
const iy1 = cy + iry * Math.sin(startRad)
const imx = cx + irx * Math.cos(midRad)
const imy = cy + iry * Math.sin(midRad)
return [
`M${f(ox1)} ${f(oy1)}`,
`A${f(rx)} ${f(ry)} 0 1 1 ${f(omx)} ${f(omy)}`,
`A${f(rx)} ${f(ry)} 0 1 1 ${f(ox1)} ${f(oy1)}`,
`L${f(ix1)} ${f(iy1)}`,
`A${f(irx)} ${f(iry)} 0 1 0 ${f(imx)} ${f(imy)}`,
`A${f(irx)} ${f(iry)} 0 1 0 ${f(ix1)} ${f(iy1)}`,
'Z',
].join(' ')
}
if (inner <= 0.001) {
// Pie slice: center → outer start → arc → close
return `M${f(cx)} ${f(cy)} L${f(ox1)} ${f(oy1)} A${f(rx)} ${f(ry)} 0 ${large} 1 ${f(ox2)} ${f(oy2)} Z`
}
// Donut slice: outer arc → line to inner → inner arc (reversed) → close
const irx = rx * inner
const iry = ry * inner
const ix1 = cx + irx * Math.cos(startRad)
const iy1 = cy + iry * Math.sin(startRad)
const ix2 = cx + irx * Math.cos(endRad)
const iy2 = cy + iry * Math.sin(endRad)
return [
`M${f(ox1)} ${f(oy1)}`,
`A${f(rx)} ${f(ry)} 0 ${large} 1 ${f(ox2)} ${f(oy2)}`,
`L${f(ix2)} ${f(iy2)}`,
`A${f(irx)} ${f(iry)} 0 ${large} 0 ${f(ix1)} ${f(iy1)}`,
'Z',
].join(' ')
}
/** True when the arc parameters describe something other than a plain full ellipse. */
export function isArcEllipse(
_startAngle?: number,
sweepAngle?: number,
innerRadius?: number,
): boolean {
const sweep = sweepAngle ?? 360
const inner = innerRadius ?? 0
return sweep < 359.9 || inner > 0.001
}
function f(n: number): string {
return Math.abs(n) < 0.005 ? '0' : parseFloat(n.toFixed(2)).toString()
}