diff --git a/DSL_MANUAL.md b/DSL_MANUAL.md index 99eae2b1..8fca8e94 100644 --- a/DSL_MANUAL.md +++ b/DSL_MANUAL.md @@ -1,4 +1,4 @@ -# FlowMind DSL Syntax Guide (V2) +# OpenFlowKit DSL Syntax Guide (V2) FlowMind uses a concise, human-readable Domain Specific Language (DSL) to define diagrams textually. V2 adds support for explicit IDs, styling attributes, and groups. diff --git a/README.md b/README.md index 64ca18fb..e699e634 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@

OpenFlowKit

-

The open-source diagramming workspace engineers actually want to use.

+

The open-source diagramming studio builders actually want to use.

-

Create flows from templates, code, structured imports, or AI. Refine them visually, keep them local-first, and export without giving up diagram-as-code or developer workflows.

+

Create flows from templates, code, structured imports, or AI. Refine them visually, keep them local-first, and export — including as a cinematic animated video that no other open-source tool offers.


@@ -41,33 +41,49 @@
- - + + OpenFlowKit on Product Hunt + + +
+
+OpenFlowKit in action + +

- - - + +
🏠 Workspace Home
Create · open · import
No forced blank file
🧑‍💻 Code → Diagram
GitHub · SQL · Terraform
K8s · OpenAPI
🤖 AI Generation
9 providers · BYOK
Streaming diff preview
🧑‍💻 Code → Diagram
SQL · Terraform · K8s
OpenAPI · Source code
🤖 AI Generation
9 providers · BYOK
Direct-to-canvas output
`{}` Diagram as Code
Bidirectional live sync
Git-friendly DSL
🧩 Asset Libraries
Developer · AWS · Azure
GCP · CNCF · Icons
🎬 Cinematic Export
Animated video & GIF
No upload required
+ +
- - OpenFlowKit on Product Hunt - +--- - +## See it in action + + + + + + + +
+ OpenFlowKit demo 1 + + OpenFlowKit demo 2 + + OpenFlowKit demo 3 +

@@ -95,12 +111,11 @@ OpenFlowKit is the **only MIT-licensed tool** that combines a real workspace hom | ------------------------------ | :---------: | :--------: | :-----: | :-----: | :--------: | | Visual canvas editor | ✅ | ✅ | ✅ | ❌ | ✅ | | Bidirectional diagram-as-code | ✅ | ❌ | ❌ | ✅ | ❌ | -| AI generation (9 providers) | ✅ | ❌ | ❌ | ❌ | Limited | -| GitHub repo → diagram | ✅ | ❌ | ❌ | ❌ | ❌ | -| SQL → ERD (native parser) | ✅ | ❌ | ❌ | ❌ | ❌ | -| Terraform / K8s import | ✅ | ❌ | ❌ | ❌ | ❌ | +| AI generation (9 providers) `Beta` | ✅ | ❌ | ❌ | ❌ | Limited | +| SQL → ERD (native parser) | ✅ | ❌ | ❌ | ❌ | ❌ | +| Terraform / K8s import `Beta` | ✅ | ❌ | ❌ | ❌ | ❌ | | AWS / Azure / GCP / CNCF icons | ✅ | ❌ | ✅ | Partial | ✅ | -| Real-time collaboration (P2P) | ✅ | ✅ | ❌ | ❌ | ✅ (cloud) | +| Real-time collaboration (P2P) `Beta` | ✅ | ✅ | ❌ | ❌ | ✅ (cloud) | | Cinematic animated export | ✅ | ❌ | ❌ | ❌ | ❌ | | Figma export (editable SVG) | ✅ | ❌ | ❌ | ❌ | ❌ | | No account required | ✅ | ✅ | ✅ | ✅ | ❌ | @@ -147,23 +162,18 @@ spec: **AI-powered imports (API key required):** -``` -github.com/vercel/next.js → architecture diagram -``` - -→ Fetches a prioritized slice of the GitHub repository in-browser, analyzes the codebase with AI, then generates an editable architecture diagram. Quality depends on repository size, file coverage, and model choice. +Paste source code, infrastructure, or API specs and hit generate — the diagram lands directly on your canvas. AWS, Azure, GCP, and CNCF icons are automatically applied when the AI detects cloud services in your input. -| Source | Engine | API key? | -| ------------------------- | -------------------------- | :------: | -| GitHub repo URL | AI-assisted import | Yes | -| SQL DDL | **Native parser** | **No** | -| Terraform `.tfstate` | **Native parser** | **No** | -| Terraform HCL | AI-assisted | Yes | -| Kubernetes YAML / Helm | **Native parser** | **No** | -| OpenAPI / Swagger YAML/JSON | **Native parser** | **No** | -| OpenAPI source text → richer flow | AI-assisted | Yes | -| Source code (single file) | AI-assisted · 9 languages | Yes | -| Mermaid | **Native parser** | **No** | +| Source | Engine | API key? | +| ----------------------------------- | ------------------------- | :------: | +| SQL DDL | **Native parser** | **No** | +| Terraform `.tfstate` | **Native parser** | **No** | +| Terraform HCL | AI-assisted | Yes | +| Kubernetes YAML / Helm | **Native parser** | **No** | +| OpenAPI / Swagger YAML/JSON | **Native parser** | **No** | +| OpenAPI source text → richer flow | AI-assisted | Yes | +| Source code (single file) | AI-assisted · 9 languages | Yes | +| Mermaid | **Native parser** | **No** | --- @@ -249,17 +259,29 @@ Large diagrams also get better organization with multi-page documents, layers, s --- +## 🎬 Cinematic Export — a USP no other open-source tool has + +Turn any diagram into an animated video walkthrough — node by node, edge by edge — entirely in your browser. No upload, no server, no third-party tool. + +Designed for architecture reviews, onboarding docs, and demos where a static image doesn't cut it. + +> **No other open-source diagramming tool does this.** + +Export as **WebM**, control animation speed, and share a link or embed it anywhere. + +--- + ## Export everywhere Build your diagram once. Take it anywhere. +- **🎬 Cinematic MP4 / GIF** — animated walkthrough, browser-only, no upload required - **PNG / SVG** — transparent background, pixel-perfect at any resolution - **PDF** — print-ready, vector-crisp - **Mermaid** — paste directly into GitHub READMEs, Notion, Confluence, Linear - **PlantUML** — for enterprise toolchains and legacy integrations - **Figma** — full editable SVG import with preserved layers - **JSON** — complete round-trip import/export, no data loss -- **🎬 Cinematic MP4** — an animated walkthrough of your diagram, node by node, edge by edge. Designed for demos, presentations, and architecture reviews. No other open-source diagramming tool does this. --- @@ -288,6 +310,8 @@ Plus: smart alignment guides, snap-to-grid, multi-select, pages, layers, section Current roadmap focus: +- **GIF export for cinematic animations** — WebM is shipping now; GIF export coming next so diagrams can be embedded anywhere without conversion +- **GitHub repo → diagram** — currently in beta internally; will ship when output quality is consistent across real-world codebases - better layers and page workflows for larger technical diagrams - stronger code and structured-import diagram quality - smarter auto-layout defaults with less cleanup after import diff --git a/docs-site/src/content/docs/choose-export-format.md b/docs-site/src/content/docs/choose-export-format.md index 99a20111..527290d3 100644 --- a/docs-site/src/content/docs/choose-export-format.md +++ b/docs-site/src/content/docs/choose-export-format.md @@ -15,7 +15,7 @@ OpenFlowKit supports both visual exports and editable handoff formats. The right | Markdown or repo-friendly text | Mermaid | | docs, slides, or tickets | PNG, JPG, or SVG | | design-tool handoff | Figma | -| short playback artifact | GIF or video | +| short playback artifact | cinematic video | | live viewer or room access | Share / Embed | ## Rules of thumb diff --git a/docs-site/src/content/docs/exporting.md b/docs-site/src/content/docs/exporting.md index 88ed055a..87274c32 100644 --- a/docs-site/src/content/docs/exporting.md +++ b/docs-site/src/content/docs/exporting.md @@ -36,9 +36,9 @@ This is the most faithful archival format for re-import into OpenFlowKit. Use it If you are unsure what to save as the master file, choose JSON. -### Cinematic video and GIF +### Cinematic video -Use cinematic exports when the diagram is meant to communicate change over time rather than a single static state. These are useful for demos, changelogs, social posts, and process walkthroughs. +Use cinematic video export when the diagram is meant to communicate change over time rather than a single static state. This is useful for demos, changelogs, social posts, and process walkthroughs. These exports are presentation-oriented. Keep JSON as the editable master if you may need to revise the underlying diagram later. diff --git a/docs-site/src/content/docs/tr/choose-export-format.md b/docs-site/src/content/docs/tr/choose-export-format.md index 7884d50d..c020c2e4 100644 --- a/docs-site/src/content/docs/tr/choose-export-format.md +++ b/docs-site/src/content/docs/tr/choose-export-format.md @@ -15,7 +15,7 @@ OpenFlowKit hem görsel hem de düzenlenebilir teslim formatlarını destekler. | Markdown veya repo dostu metin | Mermaid | | dokümanlar ve sunumlar | PNG, JPG veya SVG | | tasarım aracı aktarımı | Figma | -| kısa oynatma çıktısı | GIF veya video | +| kısa oynatma çıktısı | sinematik video | | canlı izleyici veya oda erişimi | Share / Embed | ## Varsayılan öneri diff --git a/e2e/workflows.spec.ts b/e2e/workflows.spec.ts index 80fee724..443972a6 100644 --- a/e2e/workflows.spec.ts +++ b/e2e/workflows.spec.ts @@ -268,7 +268,7 @@ test('can navigate nodes with keyboard', async ({ page }) => { await createNewFlow(page); await page.getByTestId('toolbar-add-toggle').click(); - await page.getByText('Sticky Note').click(); + await page.getByRole('button', { name: /^(Note|Sticky Note)$/ }).click(); await page.keyboard.press('Tab'); await page.keyboard.press('Tab'); diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index 2a425cc5..064ff06d 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -67,7 +67,10 @@ "distributeVertically": "Vertikal verteilen", "group": "Gruppe", "upload": "Hochladen", - "rename": "Umbenennen" + "rename": "Umbenennen", + "export": "Exportieren", + "exportDiagram": "Diagramm exportieren", + "exportAs": "Exportieren als" }, "nav": { "home": "Startseite", @@ -124,12 +127,12 @@ "openflowdsl": "Als OpenFlow DSL exportieren", "figma": "Nach Figma exportieren", "hintTransparent4K": "Transparent (4K)", - "hintWhiteBg4K": "White Background (4K)", + "hintWhiteBg4K": "Weißer Hintergrund (4K)", "jsonLabel": "JSON File", - "hintDownload": "Download", + "hintDownload": "Herunterladen", "openflowdslLabel": "{{appName}} DSL", "hintClipboard": "Kopieren", - "figmaEditable": "Figma Editable", + "figmaEditable": "Figma-bearbeitbar", "exportDiagram": "Export Diagram", "exportAs": "Export As", "actionCopy": "Kopieren", @@ -144,27 +147,28 @@ "shareEmbed": "Teilen & Einbetten", "hintShareViewer": "Einladungslink für diesen Raum", "hintShareEmbed": "Nur-Lese-Link für Viewer", - "readmeEmbed": "README Embed", - "hintReadmeEmbed": "Copy Markdown snippet", - "sectionImage": "Image", + "readmeEmbed": "README-Einbettung", + "hintReadmeEmbed": "Markdown-Snippet kopieren", + "sectionImage": "Bild", "sectionVideo": "Video", "sectionCode": "Code", "shareSection": "Canvas teilen", - "revealVideo": "Reveal Video", - "hintRevealVideo": "Nodes fade in sequentially (WebM/MP4)", - "revealGif": "Reveal GIF", - "hintRevealGif": "Animated reveal for docs/social", - "cinematicVideo": "Cinematic Build Video", + "revealVideo": "Video anzeigen", + "hintRevealVideo": "Knoten werden nacheinander eingeblendet (WebM/MP4)", + "revealGif": "GIF anzeigen", + "hintRevealGif": "Animierte Aufdeckung für Doku/Social", + "cinematicVideo": "Cinematic-Build-Video", "hintCinematicVideo": "Dark launch-style build", - "cinematicGif": "Cinematic Build GIF", - "hintCinematicGif": "Dark social-ready loop", - "actionDownload": "Download", + "cinematicGif": "Cinematic-Build-GIF", + "hintCinematicGif": "Dunkle Social-Schleife", + "actionDownload": "Herunterladen", "hintDocument": "Dokument", "hintEditableSvg": "Bearbeitbares SVG", "chooseFormat": "Format wählen", "speed": "Geschwindigkeit", "resolution": "Auflösung", - "transparentBackground": "Transparenter Hintergrund" + "transparentBackground": "Transparenter Hintergrund", + "headingFormat": "Exportformat" }, "import": { "title": "Importieren", @@ -267,26 +271,26 @@ "hint": "Dies kann nicht rückgängig gemacht werden, es sei denn, Sie haben ein exportiertes Backup oder eine weitere Kopie.", "closeDialog": "Dialog zum Löschen des Flows schließen" }, + "homeEmptyTitle": "Erstellen Sie Ihren ersten Flow", + "homeEmptySubtitle": "Entwerfen Sie sofort Enterprise-Architekturen. Beginnen Sie mit einer leeren Leinwand, beschreiben Sie Ihre Infrastruktur mit unserem KI-Builder oder verwenden Sie eine maßgeschneiderte Vorlage.", + "homeBlankCanvas": "Leere Leinwand", + "homeFlowpilotAI": "Flowpilot KI", + "homeTemplates": "Vorlagen", + "homeImportFile": "Oder eine bestehende Datei importieren", + "suggestionAI": "Flowpilot KI", "suggestionBlank": "Leere Leinwand", "suggestionBlankDesc": "Direkt in den Editor einsteigen", - "suggestionAI": "Flowpilot KI", "suggestionAIDesc": "Mit einer Eingabe beginnen", "suggestionImport": "Importieren", "suggestionImportDesc": "Bestehende Arbeit einbringen", "suggestionTemplates": "Vorlagen", "suggestionTemplatesDesc": "Mit einem bewährten Muster beginnen", - "continueTitle": "Mit einer kürzlichen Aktion fortfahren", - "homeEmptyTitle": "Erstellen Sie Ihren ersten Flow", - "homeEmptySubtitle": "Entwerfen Sie sofort Enterprise-Architekturen. Beginnen Sie mit einer leeren Leinwand, beschreiben Sie Ihre Infrastruktur mit unserem KI-Builder oder verwenden Sie eine maßgeschneiderte Vorlage.", - "homeBlankCanvas": "Leere Leinwand", - "homeFlowpilotAI": "Flowpilot KI", - "homeTemplates": "Vorlagen", - "homeImportFile": "Oder eine bestehende Datei importieren" + "continueTitle": "Mit einer kürzlichen Aktion fortfahren" }, "settings": { "title": "Einstellungen", "general": "Allgemein", - "canvas": "Canvas", + "canvas": "Leinwand", "shortcuts": "Tastaturkürzel", "theme": "Design", "language": "Sprache", @@ -298,14 +302,14 @@ "about": "Über", "brand": "Markeneinstellungen", "privacy": "Datenschutz", - "themeLight": "Light", - "themeDark": "Dark", + "themeLight": "Hell", + "themeDark": "Dunkel", "themeSystem": "System" }, "shortcuts": { "title": "Tastaturkürzel", "general": "Allgemein", - "canvas": "Canvas", + "canvas": "Leinwand", "nodes": "Knoten", "essentials": "Grundlagen", "manipulation": "Bearbeitung", @@ -313,28 +317,28 @@ "help": "Hilfe" }, "properties": { - "title": "Properties", - "shape": "Shape", - "color": "Color", - "icon": "Icon", - "rotation": "Rotation", - "transparency": "Transparency", - "imageSettings": "Image Settings", - "bulkShape": "Bulk Shape", - "bulkColor": "Bulk Color", - "bulkIcon": "Bulk Icon", - "labelTransform": "Label Transform", - "findReplace": "Find & Replace", - "findLabel": "Find", - "findPlaceholder": "Find text", - "replaceLabel": "Replace", - "replacePlaceholder": "Replace with", - "prefixOptional": "Prefix (optional)", + "title": "Eigenschaften", + "shape": "Form", + "color": "Farbe", + "icon": "Symbol", + "rotation": "Drehung", + "transparency": "Transparenz", + "imageSettings": "Bildeinstellungen", + "bulkShape": "Form (Mehrfachauswahl)", + "bulkColor": "Farbe (Mehrfachauswahl)", + "bulkIcon": "Symbol (Mehrfachauswahl)", + "labelTransform": "Beschriftungstransformation", + "findReplace": "Suchen & Ersetzen", + "findLabel": "Suchen", + "findPlaceholder": "Text suchen", + "replaceLabel": "Ersetzen", + "replacePlaceholder": "Ersetzen durch", + "prefixOptional": "Präfix (optional)", "suffixOptional": "Suffix (optional)", - "useRegex": "Use Regex", - "applyToSelectedNodes": "Apply to selected nodes", - "selectFieldToApply": "Select a field to apply", - "previewSummary": "Preview summary", + "useRegex": "Regex verwenden", + "applyToSelectedNodes": "Auf ausgewählte Knoten anwenden", + "selectFieldToApply": "Feld zum Anwenden auswählen", + "previewSummary": "Vorschau-Zusammenfassung", "selectionSummary": "Auswahlübersicht" }, "saveStatus": { @@ -346,8 +350,14 @@ "model": "Modell", "apiKey": "API-Schlüssel", "placeholder": "Flussdiagramm beschreiben...", - "settingsSubtitle": "Configure your preferred AI provider, model, and API key below.", - "generateWithFlowpilot": "Mit Flowpilot generieren" + "settingsSubtitle": "Konfigurieren Sie unten Ihren bevorzugten KI-Anbieter, das Modell und den API-Schlüssel.", + "generateWithFlowpilot": "Mit Flowpilot generieren", + "howToGetKey": "So erhalten Sie Ihren API-Schlüssel", + "openConsole": "Konsole öffnen", + "pasteKeyStep": "Fügen Sie ihn oben ein — er wird niemals mit uns geteilt", + "customEndpointTitle": "Was ist ein benutzerdefinierter Endpunkt?", + "customModelHint": "Geben Sie die genaue Modell-ID für Ihren Endpunkt ein", + "privacyTitle": "Datenschutz & Verschlüsselung" }, "playback": { "title": "Wiedergabe", @@ -615,6 +625,13 @@ "edgeSyntax": "Verwenden Sie Architektur-Kantenpfeile `-->`, `<--` oder `<-->` sowie Seitenqualifikatoren wie `api:R --> L:db`.", "nodeSyntax": "Verwenden Sie gültige Knotendeklarationen: `service id(icon)[Label]`, `group id[Label]`, `junction id[Label]`.", "fallback": "Deaktivieren Sie den Architektur-Strict-Mode, um automatische Wiederherstellung zuzulassen, oder beheben Sie die Diagnosen und versuchen Sie es erneut." + }, + "jumpToLine": "Springe zu Zeile {{line}}", + "diagnosticsGroup": { + "syntax": "Syntax-Probleme", + "identity": "Bezeichner-Probleme", + "recovery": "Wiederherstellungswarnungen", + "general": "Diagnosen" } }, "layout": { @@ -757,9 +774,7 @@ "infra": "Infra", "openapi": "OpenAPI", "code": "Code", - "codebase": "Repository", - "mindmap": "Mindmap", - "markdown": "Markdown" + "codebase": "Repository" }, "title": "Aus Daten importieren", "description": "Importiere Code, Infrastruktur, SQL oder API-Spezifikationen in dein Diagramm.", @@ -838,7 +853,9 @@ "edgeBundling": "Geschwisterkanten bündeln", "edgeBundlingDesc": "Parallele Verbindungen auf gemeinsamen Spuren halten", "architectureStrictMode": "Architektur-Strict-Modus", - "architectureStrictModeDesc": "Mermaid-Import blockieren, wenn Architekturdiagnosen Wiederherstellungs-/Validierungsprobleme enthalten" + "architectureStrictModeDesc": "Mermaid-Import blockieren, wenn Architekturdiagnosen Wiederherstellungs-/Validierungsprobleme enthalten", + "miniMap": "Minimap", + "miniMapDesc": "Minimap unten rechts anzeigen" }, "ai": { "provider": "Anbieter", @@ -912,132 +929,227 @@ }, "models": { "gemini": { - "gemini-2": { - "5-flash-lite": { - "label": "Gemini 2.5 Flash Lite" - }, - "5-flash": { - "label": "Gemini 2.5 Flash" - }, - "5-pro": { - "label": "Gemini 2.5 Pro" - } - }, "gemini-3-flash": { - "label": "Gemini 3 Flash" + "label": "Gemini 3 Flash", + "hint": "Frontier speed + intelligence", + "category": "Legacy", + "badge": "New" }, "gemini-3-pro": { - "label": "Gemini 3 Pro" + "label": "Gemini 3 Pro", + "hint": "Most powerful · Multimodal", + "category": "Legacy", + "badge": "New" + }, + "gemini-2.5-flash-lite": { + "label": "2.5 Flash Lite", + "hint": "Fastest · Free tier default", + "category": "Speed", + "badge": "Default" + }, + "gemini-2.5-flash": { + "label": "2.5 Flash", + "hint": "Best price/performance balance", + "category": "Speed" + }, + "gemini-2.5-pro": { + "label": "2.5 Pro", + "hint": "Best reasoning · Complex diagrams", + "category": "Reasoning" } }, "openai": { "gpt-5-mini": { - "label": "GPT-5 Mini" - }, - "gpt-5": { - "2": { - "label": "GPT-5.2" - }, - "label": "GPT-5" + "label": "GPT-5 Mini", + "hint": "Fast · Cost-efficient", + "category": "Speed", + "badge": "Default" }, "o4-mini": { - "label": "O4-Mini" + "label": "O4-Mini", + "hint": "Advanced reasoning · Fast", + "category": "Reasoning", + "badge": "Reasoning" }, "o3": { - "label": "O3" + "label": "O3", + "hint": "Deep reasoning · Complex tasks", + "category": "Reasoning", + "badge": "Reasoning" + }, + "gpt-5": { + "label": "GPT-5", + "hint": "Flagship model · Most capable", + "category": "Flagship" + }, + "gpt-5.2": { + "label": "GPT-5.2", + "hint": "Latest update · Improved reasoning", + "category": "Reasoning", + "badge": "New" } }, "claude": { "claude-haiku-4-5": { - "label": "Claude Haiku 4.5" + "label": "Claude Haiku 4.5", + "hint": "Fastest · Most affordable", + "category": "Speed" }, "claude-sonnet-4-5": { - "label": "Claude Sonnet 4.5" + "label": "Claude Sonnet 4.5", + "hint": "Balanced intelligence & speed", + "category": "Flagship" }, "claude-sonnet-4-6": { - "label": "Claude Sonnet 4.6" + "label": "Claude Sonnet 4.6", + "hint": "Latest Sonnet · Best coding", + "category": "Flagship", + "badge": "Default" }, "claude-opus-4-6": { - "label": "Claude Opus 4.6" + "label": "Claude Opus 4.6", + "hint": "Most intelligent · 1M token context", + "category": "Reasoning", + "badge": "Flagship" } }, "groq": { "meta-llama/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Free tier · Very fast", + "category": "Speed", + "badge": "Free" }, "meta-llama/llama-4-maverick-17b-128e-instruct": { - "label": "Llama 4 Maverick" + "label": "Llama 4 Maverick", + "hint": "More capable · Free tier", + "category": "Speed", + "badge": "Free" }, "qwen/qwen3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" }, - "llama-3": { - "3-70b-versatile": { - "label": "Llama 3.3 70B" - } + "llama-3.3-70b-versatile": { + "label": "Llama 3.3 70B Versatile", + "hint": "Versatile model", + "category": "Performance", + "badge": "Performance" } }, "nvidia": { "meta/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Efficient · Multi-modal", + "category": "Speed" }, "nvidia/nemotron-nano-12b-v2-vl": { - "label": "Nemotron Nano 12B" + "label": "Nemotron Nano 12B", + "hint": "Lightweight · Vision-language · Fast", + "category": "Speed" }, "deepseek/deepseek-v3-2": { - "label": "DeepSeek V3-2" + "label": "DeepSeek V3-2", + "hint": "Latest · GPT-5 comparable", + "category": "Flagship", + "badge": "New" }, "qwen/qwq-32b": { - "label": "QwQ 32B" + "label": "QwQ 32B", + "hint": "Strong reasoning model", + "category": "Reasoning" }, "moonshotai/kimi-k2-thinking": { - "label": "Kimi K2 Thinking" + "label": "Kimi K2 Thinking", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "cerebras": { "gpt-oss-120b": { - "label": "GPT OSS 120B" + "label": "GPT OSS 120B", + "hint": "120B params · Fast on WSE-3", + "category": "Speed", + "badge": "Default" }, "qwen-3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "2,403 tok/s · Industry fastest", + "category": "Speed", + "badge": "🚀 Fastest" }, "qwen-3-235b-a22b": { - "label": "Qwen 3 235B" + "label": "Qwen 3 235B", + "hint": "Flagship · Best quality", + "category": "Flagship", + "badge": "Flagship" }, - "zai-glm-4": { - "7": { - "label": "Zai GLM 4.7" - } + "zai-glm-4.7": { + "label": "Zai-GLM 4.7", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "mistral": { "mistral-small-latest": { - "label": "Mistral Small" + "label": "Mistral Small", + "hint": "Fast · Cost-efficient · 32k context", + "category": "Speed", + "badge": "Free" }, "mistral-medium-latest": { - "label": "Mistral Medium" + "label": "Mistral Medium", + "hint": "Balanced quality-cost · Best default", + "category": "Flagship", + "badge": "Default" }, "mistral-large-latest": { - "label": "Mistral Large" + "label": "Mistral Large", + "hint": "Most capable · 128k context · Flagship", + "category": "Flagship", + "badge": "Flagship" }, "codestral-latest": { - "label": "Codestral" + "label": "Codestral", + "hint": "Code-optimized · 256k context", + "category": "Coding", + "badge": "Code" }, "pixtral-large-latest": { - "label": "Pixtral Large" + "label": "Pixtral Large", + "hint": "Vision + reasoning · Multimodal", + "category": "Multimodal", + "badge": "Vision" + } + }, + "custom": { + "custom": { + "label": "Custom Model", + "hint": "Enter your model ID below", + "category": "Custom" } } }, "customEndpoints": { "ollama": { - "name": "Ollama" + "name": "Ollama", + "hint": "Local · Free" }, "lmStudio": { - "name": "LM Studio" + "name": "LM Studio", + "hint": "Local · Free" }, "together": { - "name": "Together AI" + "name": "Together AI", + "hint": "Cloud · Fast" } + }, + "byok": { + "dataPrivacy": "Your data never passes through our servers", + "control": "Full control over cost and rate limits", + "flexibility": "Switch providers anytime without re-linking", + "cuttingEdge": "Access cutting-edge models as soon as they launch" } }, "brand": { @@ -1078,19 +1190,7 @@ "analytics": { "enableTitle": "Anonyme Start-Analysen", "enableDescription": "Anonyme grundlegende Nutzungsdaten teilen (optional)" - }, - "search": "Suchen...", - "appearance": "Erscheinungsbild", - "account": "Konto", - "about": "Über", - "language": "Sprache", - "theme": "Design", - "fontSize": "Schriftgröße", - "fontFamily": "Schriftart", - "save": "Speichern", - "cancel": "Abbrechen", - "reset": "Zurücksetzen", - "confirm": "Bestätigen" + } }, "customNodes": { "browserContent": "Browser-Inhalt", @@ -1278,19 +1378,45 @@ "linkCopied": "Zusammenarbeitslink wurde kopiert.", "copyManual": "Der Zugriff auf die Zwischenablage ist blockiert. Kopieren Sie den Link manuell aus dem Freigabedialog." }, - "embed": "Einbetten", - "embedCode": "Einbettungscode", - "export": "Exportieren", - "settings": "Freigabeeinstellungen", - "anyoneWithLink": "Jeder mit Link", - "viewOnly": "Nur ansehen", - "canEdit": "Kann bearbeiten", - "disable": "Freigabe deaktivieren", - "enable": "Freigabe aktivieren", - "invite": "Einladen", - "pending": "Ausstehend", - "revoke": "Widerrufen", - "expired": "Abgelaufen" + "roomLink": "Zusammenarbeitslink", + "permissionsNote": "Jeder mit dem Link kann beitreten.", + "permissionsNoteSecondary": "Berechtigungskontrollen und dauerhafte Backend-Synchronisierung sind noch nicht konfiguriert.", + "viewerCount": { + "one": "1 Betrachter in dieser Sitzung.", + "many": "{{count}} Betrachter in dieser Sitzung." + }, + "mode": { + "realtime": { + "title": "Echtzeit-Synchronisierung aktiv", + "body": "Peers, die diesen Link öffnen, können Live-Updates und Cursor über den aktuellen Peer-Transport sehen." + }, + "waiting": { + "title": "Verbindung zur Echtzeit-Synchronisierung", + "body": "Diese Leinwand versucht noch, eine Live-Peer-Synchronisierung herzustellen. Falls dies nicht gelingt, bleibt die Sitzung lokal in diesem Browser." + }, + "fallback": { + "title": "Nur-lokale Zusammenarbeit", + "body": "Ohne ein Backend-Relay oder unterstützten Peer-Transport bietet diese Sitzung keine dauerhafte Multi-User-Live-Synchronisierung außerhalb der aktuellen Browser-Laufzeit." + } + }, + "cache": { + "syncing": { + "title": "Lokaler Raumcache wird synchronisiert", + "body": "Dieser Browser stellt noch den in IndexedDB zwischengespeicherten Raumzustand wieder her, bevor die Peer-Synchronisierung vollständig abgeschlossen ist." + }, + "hydrated": { + "title": "Aus lokalem Cache wiederhergestellt", + "body": "Dieser Raum hatte bereits lokal zwischengespeicherten Zustand in diesem Browser, sodass die Leinwand wiederhergestellt werden konnte, bevor Peers sich wieder verbunden haben." + }, + "ready": { + "title": "Lokaler Cache bereit", + "body": "Dieser Browser kann eine lokale IndexedDB-Kopie des Raums für Neuladen und Offline-Wiederherstellung auf diesem Gerät speichern." + }, + "unavailable": { + "title": "Kein lokaler Raumcache", + "body": "Diese Zusammenarbeitssitzung verwendet derzeit keine browserseitige IndexedDB-Raumpersistenz." + } + } }, "connectionPanel": { "architecture": "Architektur", @@ -1299,68 +1425,19 @@ "route": "Route", "appearance": "Darstellung", "condition": "Bedingung", - "deleteConnection": "Verbindung löschen" + "deleteConnection": "Verbindung löschen", + "style": "Stil", + "actions": "Aktionen" }, "sidebar": { - "close": "Seitenleiste schließen", - "searchPlaceholder": "Suchen...", - "noResults": "Keine Ergebnisse", - "nodes": "Knoten", - "components": "Komponenten", - "snippets": "Snippets", - "addons": "Add-ons" + "close": "Seitenleiste schließen" }, "contextMenu": { "label": "Canvas-Kontextmenü" }, - "canvas": { - "addNodeShortcut": "Knoten hinzufügen", - "aiChatPlaceholder": "Etwas erstellen...", - "nodes": "Knoten", - "connections": "Verbindungen", - "elements": "Elemente", - "selection": "Auswahl", - "noSelection": "Keine Auswahl", - "alignNodes": "Knoten ausrichten", - "distributeNodes": "Knoten verteilen", - "alignLeft": "Links ausrichten", - "alignCenter": "Zentriert ausrichten", - "alignRight": "Rechts ausrichten", - "alignTop": "Oben ausrichten", - "alignMiddle": "Mitte ausrichten", - "alignBottom": "Unten ausrichten", - "distributeHorizontally": "Horizontal verteilen", - "distributeVertically": "Vertikal verteilen", - "zoomToFit": "Einpassen zoom", - "zoomToSelection": "Auf Auswahl zoomen", - "locked": "Gesperrt", - "unlock": "Entsperren", - "lock": "Sperren", - "bringToFront": "Nach vorne bringen", - "sendToBack": "Nach hinten senden", - "group": "Gruppieren", - "ungroup": "Gruppierung aufheben" - }, - "mindmap": { - "addChild": "Unterthema hinzufügen", - "addSibling": "Geschwisterthema hinzufügen", - "addParent": "Übergeordnetes Thema hinzufügen", - "delete": "Löschen", - "insertAfter": "Danach einfügen", - "insertBefore": "Davor einfügen" - }, + "canvas": {}, + "mindmap": {}, "aiModel": { - "title": "KI-Modell", - "provider": "Anbieter", - "model": "Modell", - "temperature": "Temperatur", - "maxTokens": "Maximale Token", - "buttons": { - "retry": "Erneut versuchen", - "stop": "Stopp", - "clear": "Löschen" - }, - "thinking": "Denkt nach...", - "error": "KI-Fehler" + "buttons": {} } } diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index f8d8f511..3a8c5f91 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -156,7 +156,7 @@ "hintPlaybackGif": "Short loop for docs/social", "hintRevealVideo": "Nodes fade in sequentially (WebM/MP4)", "hintRevealGif": "Animated reveal for docs/social", - "hintCinematicVideo": "Dark launch-style build", + "hintCinematicVideo": "Presentation-ready animated export", "hintCinematicGif": "Dark social-ready loop", "hintShareViewer": "Invite link for this room", "hintShareEmbed": "Read-only viewer link", @@ -284,28 +284,28 @@ "hint": "Names are local to this browser profile unless you export or sync them elsewhere.", "closeDialog": "Close rename flow dialog" }, - "continueTitle": "Continue with a recent action", "homeEmptyTitle": "Create your first flow", "homeEmptySubtitle": "Design enterprise-grade architectures instantly. Start from a blank canvas, describe your infrastructure with our AI builder, or use a tailored template.", "homeBlankCanvas": "Blank Canvas", "homeFlowpilotAI": "Flowpilot AI", "homeTemplates": "Templates", "homeImportFile": "Or import an existing file", + "deleteFlow": { + "title": "Delete flow", + "description": "This removes the local autosaved flow from this device.", + "confirmation": "Delete \"{{name}}\"?", + "hint": "This cannot be undone unless you have an exported backup or another copy.", + "closeDialog": "Close delete flow dialog" + }, + "suggestionAI": "Flowpilot AI", "suggestionBlank": "Blank canvas", "suggestionBlankDesc": "Jump straight into the editor", - "suggestionAI": "Flowpilot AI", "suggestionAIDesc": "Start from a prompt", "suggestionImport": "Import", "suggestionImportDesc": "Bring in existing work", "suggestionTemplates": "Templates", "suggestionTemplatesDesc": "Start from a proven pattern", - "deleteFlow": { - "title": "Delete flow", - "description": "This removes the local autosaved flow from this device.", - "confirmation": "Delete \"{{name}}\"?", - "hint": "This cannot be undone unless you have an exported backup or another copy.", - "closeDialog": "Close delete flow dialog" - } + "continueTitle": "Continue with a recent action" }, "settings": { "title": "Settings", @@ -1302,7 +1302,7 @@ "diagrams": "Diagrams", "assets": "Assets", "node": "Shape", - "stickyNote": "Sticky Note", + "stickyNote": "Note", "section": "Section", "text": "Text", "other": "Other", diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json index df095cd6..6ed7325d 100644 --- a/public/locales/es/translation.json +++ b/public/locales/es/translation.json @@ -67,7 +67,10 @@ "distributeVertically": "Distribuir verticalmente", "group": "Grupo", "upload": "Subir", - "rename": "Renombrar" + "rename": "Renombrar", + "export": "Exportar", + "exportDiagram": "Exportar diagrama", + "exportAs": "Exportar como" }, "nav": { "home": "Inicio", @@ -123,13 +126,13 @@ "plantuml": "Exportar como PlantUML", "openflowdsl": "Exportar como OpenFlow DSL", "figma": "Exportar a Figma", - "hintTransparent4K": "Transparent (4K)", - "hintWhiteBg4K": "White Background (4K)", + "hintTransparent4K": "Transparente (4K)", + "hintWhiteBg4K": "Fondo blanco (4K)", "jsonLabel": "JSON File", - "hintDownload": "Download", + "hintDownload": "Descargar", "openflowdslLabel": "{{appName}} DSL", "hintClipboard": "Copiar", - "figmaEditable": "Figma Editable", + "figmaEditable": "Figma editable", "exportDiagram": "Export Diagram", "exportAs": "Export As", "actionCopy": "Copiar", @@ -144,27 +147,28 @@ "shareEmbed": "Compartir e incrustar", "hintShareViewer": "Enlace de invitación para esta sala", "hintShareEmbed": "Enlace de visor de solo lectura", - "readmeEmbed": "README Embed", - "hintReadmeEmbed": "Copy Markdown snippet", - "sectionImage": "Image", + "readmeEmbed": "Incrustar README", + "hintReadmeEmbed": "Copiar fragmento Markdown", + "sectionImage": "Imagen", "sectionVideo": "Video", - "sectionCode": "Code", + "sectionCode": "Código", "shareSection": "Compartir lienzo", - "revealVideo": "Reveal Video", - "hintRevealVideo": "Nodes fade in sequentially (WebM/MP4)", - "revealGif": "Reveal GIF", - "hintRevealGif": "Animated reveal for docs/social", - "cinematicVideo": "Cinematic Build Video", + "revealVideo": "Video de revelación", + "hintRevealVideo": "Los nodos aparecen secuencialmente (WebM/MP4)", + "revealGif": "GIF de revelación", + "hintRevealGif": "Revelación animada para docs/redes", + "cinematicVideo": "Video cinematográfico", "hintCinematicVideo": "Dark launch-style build", - "cinematicGif": "Cinematic Build GIF", - "hintCinematicGif": "Dark social-ready loop", - "actionDownload": "Download", + "cinematicGif": "GIF cinematográfico", + "hintCinematicGif": "Bucle oscuro para redes", + "actionDownload": "Descargar", "hintDocument": "Documento", "hintEditableSvg": "SVG editable", "chooseFormat": "Elegir formato", "speed": "Velocidad", "resolution": "Resolución", - "transparentBackground": "Fondo transparente" + "transparentBackground": "Fondo transparente", + "headingFormat": "Formato de exportación" }, "import": { "title": "Importar", @@ -267,21 +271,21 @@ "hint": "Esto no se puede deshacer a menos que tengas una copia exportada u otra copia.", "closeDialog": "Cerrar el diálogo de eliminar flujo" }, + "homeEmptyTitle": "Crea tu primer flujo", + "homeEmptySubtitle": "Diseña arquitecturas de nivel empresarial al instante. Comienza con un lienzo en blanco, describe tu infraestructura con nuestro constructor de IA, o usa una plantilla adaptada.", + "homeBlankCanvas": "Lienzo en blanco", + "homeFlowpilotAI": "Flowpilot IA", + "homeTemplates": "Plantillas", + "homeImportFile": "O importar un archivo existente", + "suggestionAI": "Flowpilot IA", "suggestionBlank": "Lienzo en blanco", "suggestionBlankDesc": "Saltar directamente al editor", - "suggestionAI": "Flowpilot IA", "suggestionAIDesc": "Empezar desde un mensaje", "suggestionImport": "Importar", "suggestionImportDesc": "Traer trabajo existente", "suggestionTemplates": "Plantillas", "suggestionTemplatesDesc": "Empezar desde un patrón probado", - "continueTitle": "Continuar con una acción reciente", - "homeEmptyTitle": "Crea tu primer flujo", - "homeEmptySubtitle": "Diseña arquitecturas de nivel empresarial al instante. Comienza con un lienzo en blanco, describe tu infraestructura con nuestro constructor de IA, o usa una plantilla adaptada.", - "homeBlankCanvas": "Lienzo en blanco", - "homeFlowpilotAI": "Flowpilot IA", - "homeTemplates": "Plantillas", - "homeImportFile": "O importar un archivo existente" + "continueTitle": "Continuar con una acción reciente" }, "settings": { "title": "Configuración", @@ -298,9 +302,9 @@ "about": "Acerca de", "brand": "Configuración de marca", "privacy": "Privacidad", - "themeLight": "Light", - "themeDark": "Dark", - "themeSystem": "System" + "themeLight": "Claro", + "themeDark": "Oscuro", + "themeSystem": "Sistema" }, "shortcuts": { "title": "Atajos de teclado", @@ -313,28 +317,28 @@ "help": "Ayuda" }, "properties": { - "title": "Properties", - "shape": "Shape", + "title": "Propiedades", + "shape": "Forma", "color": "Color", - "icon": "Icon", - "rotation": "Rotation", - "transparency": "Transparency", - "imageSettings": "Image Settings", - "bulkShape": "Bulk Shape", - "bulkColor": "Bulk Color", - "bulkIcon": "Bulk Icon", - "labelTransform": "Label Transform", - "findReplace": "Find & Replace", - "findLabel": "Find", - "findPlaceholder": "Find text", - "replaceLabel": "Replace", - "replacePlaceholder": "Replace with", - "prefixOptional": "Prefix (optional)", - "suffixOptional": "Suffix (optional)", - "useRegex": "Use Regex", - "applyToSelectedNodes": "Apply to selected nodes", - "selectFieldToApply": "Select a field to apply", - "previewSummary": "Preview summary", + "icon": "Ícono", + "rotation": "Rotación", + "transparency": "Transparencia", + "imageSettings": "Configuración de imagen", + "bulkShape": "Forma (masiva)", + "bulkColor": "Color (masivo)", + "bulkIcon": "Ícono (masivo)", + "labelTransform": "Transformación de etiqueta", + "findReplace": "Buscar y reemplazar", + "findLabel": "Buscar", + "findPlaceholder": "Buscar texto", + "replaceLabel": "Reemplazar", + "replacePlaceholder": "Reemplazar con", + "prefixOptional": "Prefijo (opcional)", + "suffixOptional": "Sufijo (opcional)", + "useRegex": "Usar expresión regular", + "applyToSelectedNodes": "Aplicar a nodos seleccionados", + "selectFieldToApply": "Seleccionar campo para aplicar", + "previewSummary": "Resumen de vista previa", "selectionSummary": "Resumen de selección" }, "saveStatus": { @@ -346,8 +350,14 @@ "model": "Modelo", "apiKey": "Clave API", "placeholder": "Describe tu diagrama...", - "settingsSubtitle": "Configure your preferred AI provider, model, and API key below.", - "generateWithFlowpilot": "Generar con Flowpilot" + "settingsSubtitle": "Configura tu proveedor de IA, modelo y clave API preferidos a continuación.", + "generateWithFlowpilot": "Generar con Flowpilot", + "howToGetKey": "Cómo obtener tu clave API", + "openConsole": "Abrir consola", + "pasteKeyStep": "Pégala en el campo de arriba; nunca se comparte con nosotros", + "customEndpointTitle": "¿Qué es un endpoint personalizado?", + "customModelHint": "Introduce el ID exacto del modelo para tu endpoint", + "privacyTitle": "Privacidad y cifrado" }, "playback": { "title": "Reproducción", @@ -615,6 +625,13 @@ "edgeSyntax": "Usa flechas de arista de arquitectura `-->`, `<--` o `<-->` y calificadores de lado como `api:R --> L:db`.", "nodeSyntax": "Usa declaraciones de nodo válidas: `service id(icon)[Label]`, `group id[Label]`, `junction id[Label]`.", "fallback": "Desactiva el modo estricto de arquitectura para permitir la recuperación automática, o corrige los diagnósticos y vuelve a intentarlo." + }, + "jumpToLine": "Ir a la línea {{line}}", + "diagnosticsGroup": { + "syntax": "Problemas de sintaxis", + "identity": "Problemas de identificador", + "recovery": "Advertencias de recuperación", + "general": "Diagnósticos" } }, "layout": { @@ -757,9 +774,7 @@ "infra": "Infra", "openapi": "OpenAPI", "code": "Code", - "codebase": "Repositorio", - "mindmap": "Mapa mental", - "markdown": "Markdown" + "codebase": "Repositorio" }, "title": "Importar desde datos", "description": "Importa código, infraestructura, SQL o especificaciones de API en tu diagrama.", @@ -838,7 +853,9 @@ "edgeBundling": "Agrupar bordes hermanos", "edgeBundlingDesc": "Mantener conexiones paralelas en carriles compartidos", "architectureStrictMode": "Modo Estricto de Arquitectura", - "architectureStrictModeDesc": "Bloquear importación de Mermaid cuando los diagnósticos de arquitectura incluyan problemas de recuperación/validación" + "architectureStrictModeDesc": "Bloquear importación de Mermaid cuando los diagnósticos de arquitectura incluyan problemas de recuperación/validación", + "miniMap": "Minimapa", + "miniMapDesc": "Mostrar minimapa en la esquina inferior derecha" }, "ai": { "provider": "Proveedor", @@ -912,132 +929,227 @@ }, "models": { "gemini": { - "gemini-2": { - "5-flash-lite": { - "label": "Gemini 2.5 Flash Lite" - }, - "5-flash": { - "label": "Gemini 2.5 Flash" - }, - "5-pro": { - "label": "Gemini 2.5 Pro" - } - }, "gemini-3-flash": { - "label": "Gemini 3 Flash" + "label": "Gemini 3 Flash", + "hint": "Frontier speed + intelligence", + "category": "Legacy", + "badge": "New" }, "gemini-3-pro": { - "label": "Gemini 3 Pro" + "label": "Gemini 3 Pro", + "hint": "Most powerful · Multimodal", + "category": "Legacy", + "badge": "New" + }, + "gemini-2.5-flash-lite": { + "label": "2.5 Flash Lite", + "hint": "Fastest · Free tier default", + "category": "Speed", + "badge": "Default" + }, + "gemini-2.5-flash": { + "label": "2.5 Flash", + "hint": "Best price/performance balance", + "category": "Speed" + }, + "gemini-2.5-pro": { + "label": "2.5 Pro", + "hint": "Best reasoning · Complex diagrams", + "category": "Reasoning" } }, "openai": { "gpt-5-mini": { - "label": "GPT-5 Mini" - }, - "gpt-5": { - "2": { - "label": "GPT-5.2" - }, - "label": "GPT-5" + "label": "GPT-5 Mini", + "hint": "Fast · Cost-efficient", + "category": "Speed", + "badge": "Default" }, "o4-mini": { - "label": "O4-Mini" + "label": "O4-Mini", + "hint": "Advanced reasoning · Fast", + "category": "Reasoning", + "badge": "Reasoning" }, "o3": { - "label": "O3" + "label": "O3", + "hint": "Deep reasoning · Complex tasks", + "category": "Reasoning", + "badge": "Reasoning" + }, + "gpt-5": { + "label": "GPT-5", + "hint": "Flagship model · Most capable", + "category": "Flagship" + }, + "gpt-5.2": { + "label": "GPT-5.2", + "hint": "Latest update · Improved reasoning", + "category": "Reasoning", + "badge": "New" } }, "claude": { "claude-haiku-4-5": { - "label": "Claude Haiku 4.5" + "label": "Claude Haiku 4.5", + "hint": "Fastest · Most affordable", + "category": "Speed" }, "claude-sonnet-4-5": { - "label": "Claude Sonnet 4.5" + "label": "Claude Sonnet 4.5", + "hint": "Balanced intelligence & speed", + "category": "Flagship" }, "claude-sonnet-4-6": { - "label": "Claude Sonnet 4.6" + "label": "Claude Sonnet 4.6", + "hint": "Latest Sonnet · Best coding", + "category": "Flagship", + "badge": "Default" }, "claude-opus-4-6": { - "label": "Claude Opus 4.6" + "label": "Claude Opus 4.6", + "hint": "Most intelligent · 1M token context", + "category": "Reasoning", + "badge": "Flagship" } }, "groq": { "meta-llama/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Free tier · Very fast", + "category": "Speed", + "badge": "Free" }, "meta-llama/llama-4-maverick-17b-128e-instruct": { - "label": "Llama 4 Maverick" + "label": "Llama 4 Maverick", + "hint": "More capable · Free tier", + "category": "Speed", + "badge": "Free" }, "qwen/qwen3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" }, - "llama-3": { - "3-70b-versatile": { - "label": "Llama 3.3 70B" - } + "llama-3.3-70b-versatile": { + "label": "Llama 3.3 70B Versatile", + "hint": "Versatile model", + "category": "Performance", + "badge": "Performance" } }, "nvidia": { "meta/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Efficient · Multi-modal", + "category": "Speed" }, "nvidia/nemotron-nano-12b-v2-vl": { - "label": "Nemotron Nano 12B" + "label": "Nemotron Nano 12B", + "hint": "Lightweight · Vision-language · Fast", + "category": "Speed" }, "deepseek/deepseek-v3-2": { - "label": "DeepSeek V3-2" + "label": "DeepSeek V3-2", + "hint": "Latest · GPT-5 comparable", + "category": "Flagship", + "badge": "New" }, "qwen/qwq-32b": { - "label": "QwQ 32B" + "label": "QwQ 32B", + "hint": "Strong reasoning model", + "category": "Reasoning" }, "moonshotai/kimi-k2-thinking": { - "label": "Kimi K2 Thinking" + "label": "Kimi K2 Thinking", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "cerebras": { "gpt-oss-120b": { - "label": "GPT OSS 120B" + "label": "GPT OSS 120B", + "hint": "120B params · Fast on WSE-3", + "category": "Speed", + "badge": "Default" }, "qwen-3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "2,403 tok/s · Industry fastest", + "category": "Speed", + "badge": "🚀 Fastest" }, "qwen-3-235b-a22b": { - "label": "Qwen 3 235B" + "label": "Qwen 3 235B", + "hint": "Flagship · Best quality", + "category": "Flagship", + "badge": "Flagship" }, - "zai-glm-4": { - "7": { - "label": "Zai GLM 4.7" - } + "zai-glm-4.7": { + "label": "Zai-GLM 4.7", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "mistral": { "mistral-small-latest": { - "label": "Mistral Small" + "label": "Mistral Small", + "hint": "Fast · Cost-efficient · 32k context", + "category": "Speed", + "badge": "Free" }, "mistral-medium-latest": { - "label": "Mistral Medium" + "label": "Mistral Medium", + "hint": "Balanced quality-cost · Best default", + "category": "Flagship", + "badge": "Default" }, "mistral-large-latest": { - "label": "Mistral Large" + "label": "Mistral Large", + "hint": "Most capable · 128k context · Flagship", + "category": "Flagship", + "badge": "Flagship" }, "codestral-latest": { - "label": "Codestral" + "label": "Codestral", + "hint": "Code-optimized · 256k context", + "category": "Coding", + "badge": "Code" }, "pixtral-large-latest": { - "label": "Pixtral Large" + "label": "Pixtral Large", + "hint": "Vision + reasoning · Multimodal", + "category": "Multimodal", + "badge": "Vision" + } + }, + "custom": { + "custom": { + "label": "Custom Model", + "hint": "Enter your model ID below", + "category": "Custom" } } }, "customEndpoints": { "ollama": { - "name": "Ollama" + "name": "Ollama", + "hint": "Local · Free" }, "lmStudio": { - "name": "LM Studio" + "name": "LM Studio", + "hint": "Local · Free" }, "together": { - "name": "Together AI" + "name": "Together AI", + "hint": "Cloud · Fast" } + }, + "byok": { + "dataPrivacy": "Your data never passes through our servers", + "control": "Full control over cost and rate limits", + "flexibility": "Switch providers anytime without re-linking", + "cuttingEdge": "Access cutting-edge models as soon as they launch" } }, "brand": { @@ -1078,19 +1190,7 @@ "analytics": { "enableTitle": "Analíticas de inicio anónimas", "enableDescription": "Compartir datos básicos de uso de forma anónima (opcional)" - }, - "search": "Buscar...", - "appearance": "Apariencia", - "account": "Cuenta", - "about": "Acerca de", - "language": "Idioma", - "theme": "Tema", - "fontSize": "Tamaño de fuente", - "fontFamily": "Familia de fuente", - "save": "Guardar", - "cancel": "Cancelar", - "reset": "Restablecer", - "confirm": "Confirmar" + } }, "customNodes": { "browserContent": "Contenido del navegador", @@ -1278,19 +1378,45 @@ "linkCopied": "Se copió el enlace de colaboración.", "copyManual": "El acceso al portapapeles está bloqueado. Copia el enlace manualmente desde el diálogo de compartir." }, - "embed": "Insertar", - "embedCode": "Código de inserción", - "export": "Exportar", - "settings": "Configuración de compartir", - "anyoneWithLink": "Cualquiera con el enlace", - "viewOnly": "Solo lectura", - "canEdit": "Puede editar", - "disable": "Desactivar compartir", - "enable": "Activar compartir", - "invite": "Invitar", - "pending": "Pendiente", - "revoke": "Revocar", - "expired": "Expirado" + "roomLink": "Enlace de colaboración", + "permissionsNote": "Cualquiera con el enlace puede unirse.", + "permissionsNoteSecondary": "Los controles de permisos y la sincronización backend duradera aún no están configurados.", + "viewerCount": { + "one": "1 espectador en esta sesión.", + "many": "{{count}} espectadores en esta sesión." + }, + "mode": { + "realtime": { + "title": "Sincronización en tiempo real activa", + "body": "Los pares que abran este enlace pueden ver actualizaciones en vivo y cursores a través del transporte par actual." + }, + "waiting": { + "title": "Conectando a la sincronización en tiempo real", + "body": "Este lienzo aún está intentando establecer la sincronización par en vivo. Si no puede, la sesión se mantendrá solo local en este navegador." + }, + "fallback": { + "title": "Colaboración solo local", + "body": "Sin un relay backend o transporte par compatible, esta sesión no proporciona sincronización en vivo multiusuario duradera fuera del runtime del navegador actual." + } + }, + "cache": { + "syncing": { + "title": "Sincronizando caché local de la sala", + "body": "Este navegador aún está restaurando el estado de la sala cacheado en IndexedDB antes de que la sincronización par se establezca completamente." + }, + "hydrated": { + "title": "Recuperado del caché local", + "body": "Esta sala ya tenía estado cacheado localmente en este navegador, por lo que el lienzo pudo restaurarse antes de que los pares se reconectaran." + }, + "ready": { + "title": "Caché local listo", + "body": "Este navegador puede mantener una copia local IndexedDB de la sala para recarga y recuperación sin conexión en este dispositivo." + }, + "unavailable": { + "title": "Sin caché local de sala", + "body": "Esta sesión de colaboración no está usando la persistencia IndexedDB de sala del navegador actualmente." + } + } }, "connectionPanel": { "architecture": "Arquitectura", @@ -1299,68 +1425,19 @@ "route": "Ruta", "appearance": "Apariencia", "condition": "Condición", - "deleteConnection": "Eliminar conexión" + "deleteConnection": "Eliminar conexión", + "style": "Estilo", + "actions": "Acciones" }, "sidebar": { - "close": "Cerrar barra lateral", - "searchPlaceholder": "Buscar...", - "noResults": "Sin resultados", - "nodes": "Nodos", - "components": "Componentes", - "snippets": "Fragmentos", - "addons": "Complementos" + "close": "Cerrar barra lateral" }, "contextMenu": { "label": "Menú contextual del lienzo" }, - "canvas": { - "addNodeShortcut": "Agregar nodo", - "aiChatPlaceholder": "Crear algo...", - "nodes": "Nodos", - "connections": "Conexiones", - "elements": "Elementos", - "selection": "Selección", - "noSelection": "Sin selección", - "alignNodes": "Alinear nodos", - "distributeNodes": "Distribuir nodos", - "alignLeft": "Alinear a la izquierda", - "alignCenter": "Alinear al centro", - "alignRight": "Alinear a la derecha", - "alignTop": "Alinear arriba", - "alignMiddle": "Alinear al medio", - "alignBottom": "Alinear abajo", - "distributeHorizontally": "Distribuir horizontalmente", - "distributeVertically": "Distribuir verticalmente", - "zoomToFit": "Ajustar zoom", - "zoomToSelection": "Zoom a selección", - "locked": "Bloqueado", - "unlock": "Desbloquear", - "lock": "Bloquear", - "bringToFront": "Traer al frente", - "sendToBack": "Enviar atrás", - "group": "Agrupar", - "ungroup": "Desagrupar" - }, - "mindmap": { - "addChild": "Agregar subtema", - "addSibling": "Agregar tema hermano", - "addParent": "Agregar tema padre", - "delete": "Eliminar", - "insertAfter": "Insertar después", - "insertBefore": "Insertar antes" - }, + "canvas": {}, + "mindmap": {}, "aiModel": { - "title": "Modelo de IA", - "provider": "Proveedor", - "model": "Modelo", - "temperature": "Temperatura", - "maxTokens": "Máximo de tokens", - "buttons": { - "retry": "Reintentar", - "stop": "Detener", - "clear": "Limpiar" - }, - "thinking": "Pensando...", - "error": "Error de IA" + "buttons": {} } } diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 8f10646d..4720325f 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -67,7 +67,10 @@ "distributeVertically": "Distribuer verticalement", "group": "Groupe", "upload": "Téléverser", - "rename": "Renommer" + "rename": "Renommer", + "export": "Exporter", + "exportDiagram": "Exporter le diagramme", + "exportAs": "Exporter en tant que" }, "nav": { "home": "Accueil", @@ -124,12 +127,12 @@ "openflowdsl": "Exporter en OpenFlow DSL", "figma": "Exporter vers Figma", "hintTransparent4K": "Transparent (4K)", - "hintWhiteBg4K": "White Background (4K)", + "hintWhiteBg4K": "Fond blanc (4K)", "jsonLabel": "JSON File", - "hintDownload": "Download", + "hintDownload": "Télécharger", "openflowdslLabel": "{{appName}} DSL", "hintClipboard": "Copier", - "figmaEditable": "Figma Editable", + "figmaEditable": "Figma éditable", "exportDiagram": "Export Diagram", "exportAs": "Export As", "actionCopy": "Copier", @@ -144,27 +147,28 @@ "shareEmbed": "Partager et intégrer", "hintShareViewer": "Lien d’invitation pour cette salle", "hintShareEmbed": "Lien de visualisation en lecture seule", - "readmeEmbed": "README Embed", - "hintReadmeEmbed": "Copy Markdown snippet", + "readmeEmbed": "Intégration README", + "hintReadmeEmbed": "Copier un extrait Markdown", "sectionImage": "Image", - "sectionVideo": "Video", + "sectionVideo": "Vidéo", "sectionCode": "Code", "shareSection": "Partager le canevas", - "revealVideo": "Reveal Video", - "hintRevealVideo": "Nodes fade in sequentially (WebM/MP4)", - "revealGif": "Reveal GIF", - "hintRevealGif": "Animated reveal for docs/social", - "cinematicVideo": "Cinematic Build Video", + "revealVideo": "Vidéo de révélation", + "hintRevealVideo": "Les nœuds apparaissent séquentiellement (WebM/MP4)", + "revealGif": "GIF de révélation", + "hintRevealGif": "Révélation animée pour docs/réseaux", + "cinematicVideo": "Vidéo cinématique", "hintCinematicVideo": "Dark launch-style build", - "cinematicGif": "Cinematic Build GIF", - "hintCinematicGif": "Dark social-ready loop", - "actionDownload": "Download", + "cinematicGif": "GIF cinématique", + "hintCinematicGif": "Boucle sombre pour réseaux", + "actionDownload": "Télécharger", "hintDocument": "Document", "hintEditableSvg": "SVG modifiable", "chooseFormat": "Choisir un format", "speed": "Vitesse", "resolution": "Résolution", - "transparentBackground": "Arrière-plan transparent" + "transparentBackground": "Arrière-plan transparent", + "headingFormat": "Format d'exportation" }, "import": { "title": "Importer", @@ -267,21 +271,21 @@ "hint": "Cette action est irréversible sauf si vous avez un export de sauvegarde ou une autre copie.", "closeDialog": "Fermer la boîte de dialogue de suppression du flux" }, - "suggestionBlank": "Canevas vierge", - "suggestionBlankDesc": "aller directement dans l'éditeur", - "suggestionAI": "Flowpilot IA", - "suggestionAIDesc": "Commencer à partir d'une invite", - "suggestionImport": "Importer", - "suggestionImportDesc": "Importer un travail existant", - "suggestionTemplates": "Modèles", - "suggestionTemplatesDesc": "Commencer à partir d'un modèle éprouvé", - "continueTitle": "Continuer avec une action récente", "homeEmptyTitle": "Créez votre premier flux", "homeEmptySubtitle": "Concevez des architectures de qualité entreprise instantanément. Commencez avec un canevas vierge, décrivez votre infrastructure avec notre constructeur IA, ou utilisez un modèle adapté.", "homeBlankCanvas": "Canevas vierge", "homeFlowpilotAI": "Flowpilot IA", "homeTemplates": "Modèles", - "homeImportFile": "Ou importer un fichier existant" + "homeImportFile": "Ou importer un fichier existant", + "suggestionAI": "Flowpilot IA", + "suggestionBlank": "Canevas vierge", + "suggestionBlankDesc": "Accéder directement à l'éditeur", + "suggestionAIDesc": "Commencer avec une invite", + "suggestionImport": "Importer", + "suggestionImportDesc": "Importer un travail existant", + "suggestionTemplates": "Modèles", + "suggestionTemplatesDesc": "Commencer avec un modèle éprouvé", + "continueTitle": "Continuer avec une action récente" }, "settings": { "title": "Paramètres", @@ -298,9 +302,9 @@ "about": "À propos", "brand": "Paramètres de marque", "privacy": "Confidentialité", - "themeLight": "Light", - "themeDark": "Dark", - "themeSystem": "System" + "themeLight": "Clair", + "themeDark": "Sombre", + "themeSystem": "Système" }, "shortcuts": { "title": "Raccourcis clavier", @@ -313,28 +317,28 @@ "help": "Aide" }, "properties": { - "title": "Properties", - "shape": "Shape", - "color": "Color", - "icon": "Icon", + "title": "Propriétés", + "shape": "Forme", + "color": "Couleur", + "icon": "Icône", "rotation": "Rotation", - "transparency": "Transparency", - "imageSettings": "Image Settings", - "bulkShape": "Bulk Shape", - "bulkColor": "Bulk Color", - "bulkIcon": "Bulk Icon", - "labelTransform": "Label Transform", - "findReplace": "Find & Replace", - "findLabel": "Find", - "findPlaceholder": "Find text", - "replaceLabel": "Replace", - "replacePlaceholder": "Replace with", - "prefixOptional": "Prefix (optional)", - "suffixOptional": "Suffix (optional)", - "useRegex": "Use Regex", - "applyToSelectedNodes": "Apply to selected nodes", - "selectFieldToApply": "Select a field to apply", - "previewSummary": "Preview summary", + "transparency": "Transparence", + "imageSettings": "Paramètres d'image", + "bulkShape": "Forme (groupée)", + "bulkColor": "Couleur (groupée)", + "bulkIcon": "Icône (groupée)", + "labelTransform": "Transformation de l'étiquette", + "findReplace": "Rechercher & Remplacer", + "findLabel": "Rechercher", + "findPlaceholder": "Rechercher du texte", + "replaceLabel": "Remplacer", + "replacePlaceholder": "Remplacer par", + "prefixOptional": "Préfixe (optionnel)", + "suffixOptional": "Suffixe (optionnel)", + "useRegex": "Utiliser une expression régulière", + "applyToSelectedNodes": "Appliquer aux nœuds sélectionnés", + "selectFieldToApply": "Sélectionner un champ à appliquer", + "previewSummary": "Résumé de l'aperçu", "selectionSummary": "Résumé de la sélection" }, "saveStatus": { @@ -346,8 +350,14 @@ "model": "Modèle", "apiKey": "Clé API", "placeholder": "Décrivez votre diagramme...", - "settingsSubtitle": "Configure your preferred AI provider, model, and API key below.", - "generateWithFlowpilot": "Générer avec Flowpilot" + "settingsSubtitle": "Configurez votre fournisseur IA, modèle et clé API préférés ci-dessous.", + "generateWithFlowpilot": "Générer avec Flowpilot", + "howToGetKey": "Comment obtenir votre clé API", + "openConsole": "Ouvrir la console", + "pasteKeyStep": "Collez-la dans le champ ci-dessus — elle n'est jamais partagée avec nous", + "customEndpointTitle": "Qu'est-ce qu'un point de terminaison personnalisé ?", + "customModelHint": "Entrez l'ID exact du modèle pour votre point de terminaison", + "privacyTitle": "Confidentialité et chiffrement" }, "playback": { "title": "Lecture", @@ -615,6 +625,13 @@ "edgeSyntax": "Utilisez les flèches d’arête d’architecture `-->`, `<--` ou `<-->` et des qualificateurs de côté comme `api:R --> L:db`.", "nodeSyntax": "Utilisez des déclarations de nœud valides : `service id(icon)[Label]`, `group id[Label]`, `junction id[Label]`.", "fallback": "Désactivez le mode strict d’architecture pour autoriser la récupération automatique, ou corrigez les diagnostics puis réessayez." + }, + "jumpToLine": "Aller à la ligne {{line}}", + "diagnosticsGroup": { + "syntax": "Problèmes de syntaxe", + "identity": "Problèmes d'identifiant", + "recovery": "Avertissements de récupération", + "general": "Diagnostics" } }, "layout": { @@ -757,9 +774,7 @@ "infra": "Infra", "openapi": "OpenAPI", "code": "Code", - "codebase": "Dépôt", - "mindmap": "Carte mentale", - "markdown": "Markdown" + "codebase": "Dépôt" }, "title": "Importer depuis des données", "description": "Importez du code, de l'infrastructure, du SQL ou des spécifications d'API dans votre diagramme.", @@ -838,7 +853,9 @@ "edgeBundling": "Regrouper les bords siblings", "edgeBundlingDesc": "Garder les connexions parallèles sur des voies partagées", "architectureStrictMode": "Mode Strict Architecture", - "architectureStrictModeDesc": "Bloquer l'import Mermaid lorsque les diagnostics d'architecture incluent des problèmes de récupération/validation" + "architectureStrictModeDesc": "Bloquer l'import Mermaid lorsque les diagnostics d'architecture incluent des problèmes de récupération/validation", + "miniMap": "Mini carte", + "miniMapDesc": "Afficher la mini carte en bas à droite" }, "ai": { "provider": "Fournisseur", @@ -912,132 +929,227 @@ }, "models": { "gemini": { - "gemini-2": { - "5-flash-lite": { - "label": "Gemini 2.5 Flash Lite" - }, - "5-flash": { - "label": "Gemini 2.5 Flash" - }, - "5-pro": { - "label": "Gemini 2.5 Pro" - } - }, "gemini-3-flash": { - "label": "Gemini 3 Flash" + "label": "Gemini 3 Flash", + "hint": "Frontier speed + intelligence", + "category": "Legacy", + "badge": "New" }, "gemini-3-pro": { - "label": "Gemini 3 Pro" + "label": "Gemini 3 Pro", + "hint": "Most powerful · Multimodal", + "category": "Legacy", + "badge": "New" + }, + "gemini-2.5-flash-lite": { + "label": "2.5 Flash Lite", + "hint": "Fastest · Free tier default", + "category": "Speed", + "badge": "Default" + }, + "gemini-2.5-flash": { + "label": "2.5 Flash", + "hint": "Best price/performance balance", + "category": "Speed" + }, + "gemini-2.5-pro": { + "label": "2.5 Pro", + "hint": "Best reasoning · Complex diagrams", + "category": "Reasoning" } }, "openai": { "gpt-5-mini": { - "label": "GPT-5 Mini" - }, - "gpt-5": { - "2": { - "label": "GPT-5.2" - }, - "label": "GPT-5" + "label": "GPT-5 Mini", + "hint": "Fast · Cost-efficient", + "category": "Speed", + "badge": "Default" }, "o4-mini": { - "label": "O4-Mini" + "label": "O4-Mini", + "hint": "Advanced reasoning · Fast", + "category": "Reasoning", + "badge": "Reasoning" }, "o3": { - "label": "O3" + "label": "O3", + "hint": "Deep reasoning · Complex tasks", + "category": "Reasoning", + "badge": "Reasoning" + }, + "gpt-5": { + "label": "GPT-5", + "hint": "Flagship model · Most capable", + "category": "Flagship" + }, + "gpt-5.2": { + "label": "GPT-5.2", + "hint": "Latest update · Improved reasoning", + "category": "Reasoning", + "badge": "New" } }, "claude": { "claude-haiku-4-5": { - "label": "Claude Haiku 4.5" + "label": "Claude Haiku 4.5", + "hint": "Fastest · Most affordable", + "category": "Speed" }, "claude-sonnet-4-5": { - "label": "Claude Sonnet 4.5" + "label": "Claude Sonnet 4.5", + "hint": "Balanced intelligence & speed", + "category": "Flagship" }, "claude-sonnet-4-6": { - "label": "Claude Sonnet 4.6" + "label": "Claude Sonnet 4.6", + "hint": "Latest Sonnet · Best coding", + "category": "Flagship", + "badge": "Default" }, "claude-opus-4-6": { - "label": "Claude Opus 4.6" + "label": "Claude Opus 4.6", + "hint": "Most intelligent · 1M token context", + "category": "Reasoning", + "badge": "Flagship" } }, "groq": { "meta-llama/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Free tier · Very fast", + "category": "Speed", + "badge": "Free" }, "meta-llama/llama-4-maverick-17b-128e-instruct": { - "label": "Llama 4 Maverick" + "label": "Llama 4 Maverick", + "hint": "More capable · Free tier", + "category": "Speed", + "badge": "Free" }, "qwen/qwen3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" }, - "llama-3": { - "3-70b-versatile": { - "label": "Llama 3.3 70B" - } + "llama-3.3-70b-versatile": { + "label": "Llama 3.3 70B Versatile", + "hint": "Versatile model", + "category": "Performance", + "badge": "Performance" } }, "nvidia": { "meta/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Efficient · Multi-modal", + "category": "Speed" }, "nvidia/nemotron-nano-12b-v2-vl": { - "label": "Nemotron Nano 12B" + "label": "Nemotron Nano 12B", + "hint": "Lightweight · Vision-language · Fast", + "category": "Speed" }, "deepseek/deepseek-v3-2": { - "label": "DeepSeek V3-2" + "label": "DeepSeek V3-2", + "hint": "Latest · GPT-5 comparable", + "category": "Flagship", + "badge": "New" }, "qwen/qwq-32b": { - "label": "QwQ 32B" + "label": "QwQ 32B", + "hint": "Strong reasoning model", + "category": "Reasoning" }, "moonshotai/kimi-k2-thinking": { - "label": "Kimi K2 Thinking" + "label": "Kimi K2 Thinking", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "cerebras": { "gpt-oss-120b": { - "label": "GPT OSS 120B" + "label": "GPT OSS 120B", + "hint": "120B params · Fast on WSE-3", + "category": "Speed", + "badge": "Default" }, "qwen-3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "2,403 tok/s · Industry fastest", + "category": "Speed", + "badge": "🚀 Fastest" }, "qwen-3-235b-a22b": { - "label": "Qwen 3 235B" + "label": "Qwen 3 235B", + "hint": "Flagship · Best quality", + "category": "Flagship", + "badge": "Flagship" }, - "zai-glm-4": { - "7": { - "label": "Zai GLM 4.7" - } + "zai-glm-4.7": { + "label": "Zai-GLM 4.7", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "mistral": { "mistral-small-latest": { - "label": "Mistral Small" + "label": "Mistral Small", + "hint": "Fast · Cost-efficient · 32k context", + "category": "Speed", + "badge": "Free" }, "mistral-medium-latest": { - "label": "Mistral Medium" + "label": "Mistral Medium", + "hint": "Balanced quality-cost · Best default", + "category": "Flagship", + "badge": "Default" }, "mistral-large-latest": { - "label": "Mistral Large" + "label": "Mistral Large", + "hint": "Most capable · 128k context · Flagship", + "category": "Flagship", + "badge": "Flagship" }, "codestral-latest": { - "label": "Codestral" + "label": "Codestral", + "hint": "Code-optimized · 256k context", + "category": "Coding", + "badge": "Code" }, "pixtral-large-latest": { - "label": "Pixtral Large" + "label": "Pixtral Large", + "hint": "Vision + reasoning · Multimodal", + "category": "Multimodal", + "badge": "Vision" + } + }, + "custom": { + "custom": { + "label": "Custom Model", + "hint": "Enter your model ID below", + "category": "Custom" } } }, "customEndpoints": { "ollama": { - "name": "Ollama" + "name": "Ollama", + "hint": "Local · Free" }, "lmStudio": { - "name": "LM Studio" + "name": "LM Studio", + "hint": "Local · Free" }, "together": { - "name": "Together AI" + "name": "Together AI", + "hint": "Cloud · Fast" } + }, + "byok": { + "dataPrivacy": "Your data never passes through our servers", + "control": "Full control over cost and rate limits", + "flexibility": "Switch providers anytime without re-linking", + "cuttingEdge": "Access cutting-edge models as soon as they launch" } }, "brand": { @@ -1078,19 +1190,7 @@ "analytics": { "enableTitle": "Analyses de lancement anonymes", "enableDescription": "Partager des données d’usage de base de manière anonyme (optionnel)" - }, - "search": "Rechercher...", - "appearance": "Apparence", - "account": "Compte", - "about": "À propos", - "language": "Langue", - "theme": "Thème", - "fontSize": "Taille de police", - "fontFamily": "Police", - "save": "Enregistrer", - "cancel": "Annuler", - "reset": "Réinitialiser", - "confirm": "Confirmer" + } }, "customNodes": { "browserContent": "Contenu du navigateur", @@ -1278,19 +1378,45 @@ "linkCopied": "Le lien de collaboration a été copié.", "copyManual": "L’accès au presse-papiers est bloqué. Copiez le lien manuellement depuis la boîte de dialogue de partage." }, - "embed": "Intégrer", - "embedCode": "Code d'intégration", - "export": "Exporter", - "settings": "Paramètres de partage", - "anyoneWithLink": "Toute personne avec le lien", - "viewOnly": "Lecture seule", - "canEdit": "Peut modifier", - "disable": "Désactiver le partage", - "enable": "Activer le partage", - "invite": "Inviter", - "pending": "En attente", - "revoke": "Révoquer", - "expired": "Expiré" + "roomLink": "Lien de collaboration", + "permissionsNote": "Toute personne disposant du lien peut rejoindre.", + "permissionsNoteSecondary": "Les contrôles d'autorisation et la synchronisation backend durable ne sont pas encore configurés.", + "viewerCount": { + "one": "1 spectateur dans cette session.", + "many": "{{count}} spectateurs dans cette session." + }, + "mode": { + "realtime": { + "title": "Synchronisation en temps réel active", + "body": "Les pairs qui ouvrent ce lien peuvent voir les mises à jour en direct et les curseurs via le transport pair actuel." + }, + "waiting": { + "title": "Connexion à la synchronisation en temps réel", + "body": "Ce caneaux essaie toujours d'établir une synchronisation pair-à-pair en direct. En cas d'échec, la session restera locale dans ce navigateur." + }, + "fallback": { + "title": "Collaboration locale uniquement", + "body": "Sans relais backend ni transport pair supporté, cette session ne fournit pas de synchronisation en direct multi-utilisateurs durable en dehors de l'exécution du navigateur actuel." + } + }, + "cache": { + "syncing": { + "title": "Synchronisation du cache local de la salle", + "body": "Ce navigateur restaure encore l'état de la salle mis en cache dans IndexedDB avant que la synchronisation pair ne soit pleinement établie." + }, + "hydrated": { + "title": "Récupéré depuis le cache local", + "body": "Cette salle avait déjà un état mis en cache localement dans ce navigateur, donc le canevas a pu être restauré avant que les pairs ne se reconnectent." + }, + "ready": { + "title": "Cache local prêt", + "body": "Ce navigateur peut conserver une copie locale IndexedDB de la salle pour le rechargement et la récupération hors ligne sur cet appareil." + }, + "unavailable": { + "title": "Pas de cache local de salle", + "body": "Cette session de collaboration n'utilise pas la persistance IndexedDB de la salle dans le navigateur actuellement." + } + } }, "connectionPanel": { "architecture": "Architecture", @@ -1299,68 +1425,19 @@ "route": "Trajet", "appearance": "Apparence", "condition": "Condition", - "deleteConnection": "Supprimer la connexion" + "deleteConnection": "Supprimer la connexion", + "style": "Style", + "actions": "Actions" }, "sidebar": { - "close": "Fermer la barre latérale", - "searchPlaceholder": "Rechercher...", - "noResults": "Aucun résultat", - "nodes": "Nœuds", - "components": "Composants", - "snippets": "Extraits", - "addons": "Add-ons" + "close": "Fermer la barre latérale" }, "contextMenu": { "label": "Menu contextuel du canevas" }, - "canvas": { - "addNodeShortcut": "Ajouter un nœud", - "aiChatPlaceholder": "Créer quelque chose...", - "nodes": "Nœuds", - "connections": "Connexions", - "elements": "Éléments", - "selection": "Sélection", - "noSelection": "Aucune sélection", - "alignNodes": "Aligner les nœuds", - "distributeNodes": "Distribuer les nœuds", - "alignLeft": "Aligner à gauche", - "alignCenter": "Aligner au centre", - "alignRight": "Aligner à droite", - "alignTop": "Aligner en haut", - "alignMiddle": "Aligner au milieu", - "alignBottom": "Aligner en bas", - "distributeHorizontally": "Distribuer horizontalement", - "distributeVertically": "Distribuer verticalement", - "zoomToFit": "Zoom pour ajuster", - "zoomToSelection": "Zoom sur la sélection", - "locked": "Verrouillé", - "unlock": "Déverrouiller", - "lock": "Verrouiller", - "bringToFront": "Mettre au premier plan", - "sendToBack": "Envoyer à l'arrière", - "group": "Grouper", - "ungroup": "Dissocier" - }, - "mindmap": { - "addChild": "Ajouter un sous-sujet", - "addSibling": "Ajouter un sujet frère", - "addParent": "Ajouter un sujet parent", - "delete": "Supprimer", - "insertAfter": "Insérer après", - "insertBefore": "Insérer avant" - }, + "canvas": {}, + "mindmap": {}, "aiModel": { - "title": "Modèle IA", - "provider": "Fournisseur", - "model": "Modèle", - "temperature": "Température", - "maxTokens": "Jetons max", - "buttons": { - "retry": "Réessayer", - "stop": "Arrêter", - "clear": "Effacer" - }, - "thinking": "Réflexion...", - "error": "Erreur IA" + "buttons": {} } } diff --git a/public/locales/ja/translation.json b/public/locales/ja/translation.json index c7986127..bc01444f 100644 --- a/public/locales/ja/translation.json +++ b/public/locales/ja/translation.json @@ -67,7 +67,10 @@ "distributeVertically": "垂直方向に分布", "group": "グループ", "upload": "アップロード", - "rename": "名前を変更" + "rename": "名前を変更", + "export": "エクスポート", + "exportDiagram": "図をエクスポート", + "exportAs": "名前を付けてエクスポート" }, "nav": { "home": "ホーム", @@ -123,13 +126,13 @@ "plantuml": "PlantUMLとしてエクスポート", "openflowdsl": "OpenFlow DSLとしてエクスポート", "figma": "Figmaにエクスポート", - "hintTransparent4K": "Transparent (4K)", - "hintWhiteBg4K": "White Background (4K)", + "hintTransparent4K": "透明 (4K)", + "hintWhiteBg4K": "白背景 (4K)", "jsonLabel": "JSON File", - "hintDownload": "Download", + "hintDownload": "ダウンロード", "openflowdslLabel": "{{appName}} DSL", "hintClipboard": "コピー", - "figmaEditable": "Figma Editable", + "figmaEditable": "Figma 編集可能", "exportDiagram": "Export Diagram", "exportAs": "Export As", "actionCopy": "コピー", @@ -144,27 +147,28 @@ "shareEmbed": "共有と埋め込み", "hintShareViewer": "このルームへの招待リンク", "hintShareEmbed": "読み取り専用ビューアーリンク", - "readmeEmbed": "README Embed", - "hintReadmeEmbed": "Copy Markdown snippet", - "sectionImage": "Image", - "sectionVideo": "Video", - "sectionCode": "Code", + "readmeEmbed": "README 埋め込み", + "hintReadmeEmbed": "Markdown スニペットをコピー", + "sectionImage": "画像", + "sectionVideo": "動画", + "sectionCode": "コード", "shareSection": "キャンバスを共有", - "revealVideo": "Reveal Video", - "hintRevealVideo": "Nodes fade in sequentially (WebM/MP4)", - "revealGif": "Reveal GIF", - "hintRevealGif": "Animated reveal for docs/social", - "cinematicVideo": "Cinematic Build Video", + "revealVideo": "リビール動画", + "hintRevealVideo": "ノードが順番にフェードイン (WebM/MP4)", + "revealGif": "リビール GIF", + "hintRevealGif": "ドキュメント/SNS向けアニメーション表示", + "cinematicVideo": "シネマティブuild動画", "hintCinematicVideo": "Dark launch-style build", - "cinematicGif": "Cinematic Build GIF", - "hintCinematicGif": "Dark social-ready loop", - "actionDownload": "Download", + "cinematicGif": "シネマティブuild GIF", + "hintCinematicGif": "ダークソーシャルループ", + "actionDownload": "ダウンロード", "hintDocument": "ドキュメント", "hintEditableSvg": "編集可能な SVG", "chooseFormat": "形式を選択", "speed": "速度", "resolution": "解像度", - "transparentBackground": "透明な背景" + "transparentBackground": "透明な背景", + "headingFormat": "エクスポート形式" }, "import": { "title": "インポート", @@ -267,21 +271,21 @@ "hint": "エクスポートしたバックアップまたは別のコピーがない限り、この操作は元に戻せません。", "closeDialog": "フロー削除ダイアログを閉じる" }, + "homeEmptyTitle": "最初のフローを作成", + "homeEmptySubtitle": "エンタープライズグレードのアーキテクチャを即座に設計。空白のキャンバスから始めるか、AIビルダーでインフラを描述するか、カスタマイズテンプレートを使用してください。", + "homeBlankCanvas": "空白のキャンバス", + "homeFlowpilotAI": "フローパイロット人工知能", + "homeTemplates": "テンプレート", + "homeImportFile": "または既存のファイルをインポート", + "suggestionAI": "フローパイロット人工知能", "suggestionBlank": "空白のキャンバス", "suggestionBlankDesc": "エディタに直接ジャンプ", - "suggestionAI": "フローパイロット人工知能", "suggestionAIDesc": "プロンプトから始める", "suggestionImport": "インポート", "suggestionImportDesc": "既存の作品を持ち込む", "suggestionTemplates": "テンプレート", "suggestionTemplatesDesc": "実証済みのパターンから始める", - "continueTitle": "最近の操作を続ける", - "homeEmptyTitle": "最初のフローを作成", - "homeEmptySubtitle": "エンタープライズグレードのアーキテクチャを即座に設計。空白のキャンバスから始めるか、AIビルダーでインフラを描述するか、カスタマイズテンプレートを使用してください。", - "homeBlankCanvas": "空白のキャンバス", - "homeFlowpilotAI": "フローパイロット人工知能", - "homeTemplates": "テンプレート", - "homeImportFile": "または既存のファイルをインポート" + "continueTitle": "最近の操作を続ける" }, "settings": { "title": "設定", @@ -313,28 +317,28 @@ "help": "ヘルプ" }, "properties": { - "title": "Properties", - "shape": "Shape", - "color": "Color", - "icon": "Icon", - "rotation": "Rotation", - "transparency": "Transparency", - "imageSettings": "Image Settings", - "bulkShape": "Bulk Shape", - "bulkColor": "Bulk Color", - "bulkIcon": "Bulk Icon", - "labelTransform": "Label Transform", - "findReplace": "Find & Replace", - "findLabel": "Find", - "findPlaceholder": "Find text", - "replaceLabel": "Replace", - "replacePlaceholder": "Replace with", - "prefixOptional": "Prefix (optional)", - "suffixOptional": "Suffix (optional)", - "useRegex": "Use Regex", - "applyToSelectedNodes": "Apply to selected nodes", - "selectFieldToApply": "Select a field to apply", - "previewSummary": "Preview summary", + "title": "プロパティ", + "shape": "形状", + "color": "色", + "icon": "アイコン", + "rotation": "回転", + "transparency": "透明度", + "imageSettings": "画像設定", + "bulkShape": "一括形状", + "bulkColor": "一括色", + "bulkIcon": "一括アイコン", + "labelTransform": "ラベル変換", + "findReplace": "検索と置換", + "findLabel": "検索", + "findPlaceholder": "テキストを検索", + "replaceLabel": "置換", + "replacePlaceholder": "置換内容", + "prefixOptional": "プレフィックス(任意)", + "suffixOptional": "サフィックス(任意)", + "useRegex": "正規表現を使用", + "applyToSelectedNodes": "選択したノードに適用", + "selectFieldToApply": "適用するフィールドを選択", + "previewSummary": "プレビュー概要", "selectionSummary": "選択の概要" }, "saveStatus": { @@ -346,8 +350,14 @@ "model": "モデル", "apiKey": "APIキー", "placeholder": "フローチャートを説明してください...", - "settingsSubtitle": "Configure your preferred AI provider, model, and API key below.", - "generateWithFlowpilot": "Flowpilotで生成" + "settingsSubtitle": "以下で希望の AI プロバイダー、モデル、API キーを設定してください。", + "generateWithFlowpilot": "Flowpilotで生成", + "howToGetKey": "APIキーの取得方法", + "openConsole": "コンソールを開く", + "pasteKeyStep": "上の入力欄に貼り付けてください。こちらには共有されません", + "customEndpointTitle": "カスタムエンドポイントとは?", + "customModelHint": "このエンドポイントで使う正確なモデル ID を入力してください", + "privacyTitle": "プライバシーと暗号化" }, "playback": { "title": "再生", @@ -615,6 +625,13 @@ "edgeSyntax": "アーキテクチャのエッジ矢印 `-->`、`<--`、`<-->` と、`api:R --> L:db` のような側面修飾子を使用してください。", "nodeSyntax": "有効なノード宣言を使用してください: `service id(icon)[Label]`、`group id[Label]`、`junction id[Label]`。", "fallback": "自動回復を許可するにはアーキテクチャ厳格モードをオフにするか、診断を修正して再試行してください。" + }, + "jumpToLine": "{{line}}行目に移動", + "diagnosticsGroup": { + "syntax": "構文の問題", + "identity": "識別子の問題", + "recovery": "リカバリー警告", + "general": "診断" } }, "layout": { @@ -757,9 +774,7 @@ "infra": "Infra", "openapi": "OpenAPI", "code": "Code", - "codebase": "リポジトリ", - "mindmap": "マインドマップ", - "markdown": "Markdown" + "codebase": "リポジトリ" }, "title": "データからインポート", "description": "コード、インフラ、SQL、または API 仕様を図に取り込みます。", @@ -838,7 +853,9 @@ "edgeBundling": "兄弟エッジをバンドル", "edgeBundlingDesc": "並行接続を共有レーンに保つ", "architectureStrictMode": "アーキテクチャStrictモード", - "architectureStrictModeDesc": "アーキテクチャ診断が回復/検証の問題を含む場合、Mermaidインポートをブロック" + "architectureStrictModeDesc": "アーキテクチャ診断が回復/検証の問題を含む場合、Mermaidインポートをブロック", + "miniMap": "ミニマップ", + "miniMapDesc": "右下にミニマップを表示" }, "ai": { "provider": "プロバイダー", @@ -912,132 +929,227 @@ }, "models": { "gemini": { - "gemini-2": { - "5-flash-lite": { - "label": "Gemini 2.5 Flash Lite" - }, - "5-flash": { - "label": "Gemini 2.5 Flash" - }, - "5-pro": { - "label": "Gemini 2.5 Pro" - } - }, "gemini-3-flash": { - "label": "Gemini 3 Flash" + "label": "Gemini 3 Flash", + "hint": "Frontier speed + intelligence", + "category": "Legacy", + "badge": "New" }, "gemini-3-pro": { - "label": "Gemini 3 Pro" + "label": "Gemini 3 Pro", + "hint": "Most powerful · Multimodal", + "category": "Legacy", + "badge": "New" + }, + "gemini-2.5-flash-lite": { + "label": "2.5 Flash Lite", + "hint": "Fastest · Free tier default", + "category": "Speed", + "badge": "Default" + }, + "gemini-2.5-flash": { + "label": "2.5 Flash", + "hint": "Best price/performance balance", + "category": "Speed" + }, + "gemini-2.5-pro": { + "label": "2.5 Pro", + "hint": "Best reasoning · Complex diagrams", + "category": "Reasoning" } }, "openai": { "gpt-5-mini": { - "label": "GPT-5 Mini" - }, - "gpt-5": { - "2": { - "label": "GPT-5.2" - }, - "label": "GPT-5" + "label": "GPT-5 Mini", + "hint": "Fast · Cost-efficient", + "category": "Speed", + "badge": "Default" }, "o4-mini": { - "label": "O4-Mini" + "label": "O4-Mini", + "hint": "Advanced reasoning · Fast", + "category": "Reasoning", + "badge": "Reasoning" }, "o3": { - "label": "O3" + "label": "O3", + "hint": "Deep reasoning · Complex tasks", + "category": "Reasoning", + "badge": "Reasoning" + }, + "gpt-5": { + "label": "GPT-5", + "hint": "Flagship model · Most capable", + "category": "Flagship" + }, + "gpt-5.2": { + "label": "GPT-5.2", + "hint": "Latest update · Improved reasoning", + "category": "Reasoning", + "badge": "New" } }, "claude": { "claude-haiku-4-5": { - "label": "Claude Haiku 4.5" + "label": "Claude Haiku 4.5", + "hint": "Fastest · Most affordable", + "category": "Speed" }, "claude-sonnet-4-5": { - "label": "Claude Sonnet 4.5" + "label": "Claude Sonnet 4.5", + "hint": "Balanced intelligence & speed", + "category": "Flagship" }, "claude-sonnet-4-6": { - "label": "Claude Sonnet 4.6" + "label": "Claude Sonnet 4.6", + "hint": "Latest Sonnet · Best coding", + "category": "Flagship", + "badge": "Default" }, "claude-opus-4-6": { - "label": "Claude Opus 4.6" + "label": "Claude Opus 4.6", + "hint": "Most intelligent · 1M token context", + "category": "Reasoning", + "badge": "Flagship" } }, "groq": { "meta-llama/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Free tier · Very fast", + "category": "Speed", + "badge": "Free" }, "meta-llama/llama-4-maverick-17b-128e-instruct": { - "label": "Llama 4 Maverick" + "label": "Llama 4 Maverick", + "hint": "More capable · Free tier", + "category": "Speed", + "badge": "Free" }, "qwen/qwen3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" }, - "llama-3": { - "3-70b-versatile": { - "label": "Llama 3.3 70B" - } + "llama-3.3-70b-versatile": { + "label": "Llama 3.3 70B Versatile", + "hint": "Versatile model", + "category": "Performance", + "badge": "Performance" } }, "nvidia": { "meta/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Efficient · Multi-modal", + "category": "Speed" }, "nvidia/nemotron-nano-12b-v2-vl": { - "label": "Nemotron Nano 12B" + "label": "Nemotron Nano 12B", + "hint": "Lightweight · Vision-language · Fast", + "category": "Speed" }, "deepseek/deepseek-v3-2": { - "label": "DeepSeek V3-2" + "label": "DeepSeek V3-2", + "hint": "Latest · GPT-5 comparable", + "category": "Flagship", + "badge": "New" }, "qwen/qwq-32b": { - "label": "QwQ 32B" + "label": "QwQ 32B", + "hint": "Strong reasoning model", + "category": "Reasoning" }, "moonshotai/kimi-k2-thinking": { - "label": "Kimi K2 Thinking" + "label": "Kimi K2 Thinking", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "cerebras": { "gpt-oss-120b": { - "label": "GPT OSS 120B" + "label": "GPT OSS 120B", + "hint": "120B params · Fast on WSE-3", + "category": "Speed", + "badge": "Default" }, "qwen-3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "2,403 tok/s · Industry fastest", + "category": "Speed", + "badge": "🚀 Fastest" }, "qwen-3-235b-a22b": { - "label": "Qwen 3 235B" + "label": "Qwen 3 235B", + "hint": "Flagship · Best quality", + "category": "Flagship", + "badge": "Flagship" }, - "zai-glm-4": { - "7": { - "label": "Zai GLM 4.7" - } + "zai-glm-4.7": { + "label": "Zai-GLM 4.7", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "mistral": { "mistral-small-latest": { - "label": "Mistral Small" + "label": "Mistral Small", + "hint": "Fast · Cost-efficient · 32k context", + "category": "Speed", + "badge": "Free" }, "mistral-medium-latest": { - "label": "Mistral Medium" + "label": "Mistral Medium", + "hint": "Balanced quality-cost · Best default", + "category": "Flagship", + "badge": "Default" }, "mistral-large-latest": { - "label": "Mistral Large" + "label": "Mistral Large", + "hint": "Most capable · 128k context · Flagship", + "category": "Flagship", + "badge": "Flagship" }, "codestral-latest": { - "label": "Codestral" + "label": "Codestral", + "hint": "Code-optimized · 256k context", + "category": "Coding", + "badge": "Code" }, "pixtral-large-latest": { - "label": "Pixtral Large" + "label": "Pixtral Large", + "hint": "Vision + reasoning · Multimodal", + "category": "Multimodal", + "badge": "Vision" + } + }, + "custom": { + "custom": { + "label": "Custom Model", + "hint": "Enter your model ID below", + "category": "Custom" } } }, "customEndpoints": { "ollama": { - "name": "Ollama" + "name": "Ollama", + "hint": "Local · Free" }, "lmStudio": { - "name": "LM Studio" + "name": "LM Studio", + "hint": "Local · Free" }, "together": { - "name": "Together AI" + "name": "Together AI", + "hint": "Cloud · Fast" } + }, + "byok": { + "dataPrivacy": "Your data never passes through our servers", + "control": "Full control over cost and rate limits", + "flexibility": "Switch providers anytime without re-linking", + "cuttingEdge": "Access cutting-edge models as soon as they launch" } }, "brand": { @@ -1078,19 +1190,7 @@ "analytics": { "enableTitle": "匿名の起動分析", "enableDescription": "匿名の基本的な利用データを共有します(任意)" - }, - "search": "検索...", - "appearance": "外観", - "account": "アカウント", - "about": "概要", - "language": "言語", - "theme": "テーマ", - "fontSize": "フォントサイズ", - "fontFamily": "フォント", - "save": "保存", - "cancel": "キャンセル", - "reset": "リセット", - "confirm": "確認" + } }, "customNodes": { "browserContent": "ブラウザコンテンツ", @@ -1278,19 +1378,45 @@ "linkCopied": "共同編集リンクをコピーしました。", "copyManual": "クリップボードへのアクセスがブロックされています。共有ダイアログからリンクを手動でコピーしてください。" }, - "embed": "埋め込み", - "embedCode": "埋め込みコード", - "export": "エクスポート", - "settings": "共有設定", - "anyoneWithLink": "リンクを持つ任何人", - "viewOnly": "表示のみ", - "canEdit": "編集可能", - "disable": "共有を無効化", - "enable": "共有を有効化", - "invite": "招待", - "pending": "保留中", - "revoke": "取り消し", - "expired": "期限切れ" + "roomLink": "コラボレーションリンク", + "permissionsNote": "リンクを知っている人は誰でも参加できます。", + "permissionsNoteSecondary": "権限管理と永続的なバックエンド同期はまだ設定されていません。", + "viewerCount": { + "one": "このセッションに 1 人の閲覧者。", + "many": "このセッションに {{count}} 人の閲覧者。" + }, + "mode": { + "realtime": { + "title": "リアルタイム同期が有効", + "body": "このリンクを開いたピアは、現在のピアトランスポートを通じてライブアップデートとカーソルを確認できます。" + }, + "waiting": { + "title": "リアルタイム同期に接続中", + "body": "このキャンバスはライブピア同期の確立を試みています。失敗した場合、セッションはこのブラウザでローカルのみのままになります。" + }, + "fallback": { + "title": "ローカルのみのコラボレーション", + "body": "バックエンドリレーまたはサポートされるピアトランスポートがない場合、このセッションは現在のブラウザランタイム外で永続的なマルチユーザーライブ同期を提供しません。" + } + }, + "cache": { + "syncing": { + "title": "ルームのローカルキャッシュを同期中", + "body": "このブラウザは、ピア同期が完全に確立する前にIndexedDBにキャッシュされたルーム状態をまだ復元しています。" + }, + "hydrated": { + "title": "ローカルキャッシュから復元しました", + "body": "このルームにはこのブラウザにローカルにキャッシュされた状態があったため、ピアが再接続する前にキャンバスを復元できました。" + }, + "ready": { + "title": "ローカルキャッシュ準備完了", + "body": "このブラウザは、このデバイスでのリロードとオフライン回復のためにルームのローカルIndexedDBコピーを保持できます。" + }, + "unavailable": { + "title": "ローカルルームキャッシュなし", + "body": "このコラボレーションセッションは現在、ブラウザIndexedDBルーム永続化を使用していません。" + } + } }, "connectionPanel": { "architecture": "アーキテクチャ", @@ -1299,68 +1425,19 @@ "route": "ルート", "appearance": "外観", "condition": "条件", - "deleteConnection": "接続を削除" + "deleteConnection": "接続を削除", + "style": "スタイル", + "actions": "アクション" }, "sidebar": { - "close": "サイドバーを閉じる", - "searchPlaceholder": "検索...", - "noResults": "結果なし", - "nodes": "ノード", - "components": "コンポーネント", - "snippets": "スニペット", - "addons": "アドオン" + "close": "サイドバーを閉じる" }, "contextMenu": { "label": "キャンバスコンテキストメニュー" }, - "canvas": { - "addNodeShortcut": "ノードを追加", - "aiChatPlaceholder": "何かを作成...", - "nodes": "ノード", - "connections": "接続", - "elements": "要素", - "selection": "選択", - "noSelection": "選択なし", - "alignNodes": "ノードを整列", - "distributeNodes": "ノードを分布", - "alignLeft": "左揃え", - "alignCenter": "中央揃え", - "alignRight": "右揃え", - "alignTop": "上揃え", - "alignMiddle": "中間揃え", - "alignBottom": "下揃え", - "distributeHorizontally": "水平に分布", - "distributeVertically": "垂直に分布", - "zoomToFit": "フィットにズーム", - "zoomToSelection": "選択にズーム", - "locked": "ロック済み", - "unlock": "ロック解除", - "lock": "ロック", - "bringToFront": "最前面へ", - "sendToBack": "最背面へ", - "group": "グループ化", - "ungroup": "グループ解除" - }, - "mindmap": { - "addChild": "サブトピックを追加", - "addSibling": "兄弟トピックを追加", - "addParent": "親トピックを追加", - "delete": "削除", - "insertAfter": "後に挿入", - "insertBefore": "前に挿入" - }, + "canvas": {}, + "mindmap": {}, "aiModel": { - "title": "AIモデル", - "provider": "プロバイダー", - "model": "モデル", - "temperature": "温度", - "maxTokens": "最大トークン数", - "buttons": { - "retry": "再試行", - "stop": "停止", - "clear": "クリア" - }, - "thinking": "思考中...", - "error": "AIエラー" + "buttons": {} } } diff --git a/public/locales/tr/translation.json b/public/locales/tr/translation.json index e63099a4..45a5ee57 100644 --- a/public/locales/tr/translation.json +++ b/public/locales/tr/translation.json @@ -167,7 +167,8 @@ "chooseFormat": "Biçim seç", "speed": "Hız", "resolution": "Çözünürlük", - "transparentBackground": "Şeffaf arka plan" + "transparentBackground": "Şeffaf arka plan", + "headingFormat": "Dışa Aktarma Biçimi" }, "import": { "title": "İçe Aktar", @@ -290,21 +291,21 @@ "hint": "Dışa aktarılmış bir yedeklemeniz veya başka bir kopyanız yoksa bu işlem geri alınamaz.", "closeDialog": "Akış silme iletişim kutusunu kapat" }, + "homeEmptyTitle": "İlk akışınızı oluşturun", + "homeEmptySubtitle": "Kurumsal düzey mimariileri anında tasarlayın. Boş bir tuvalden başlayın, altyapınızı AI builderımızla tanımlayın veya hazır bir şablon kullanın.", + "homeBlankCanvas": "Boş Tuval", + "homeFlowpilotAI": "Flowpilot Yapay Zeka", + "homeTemplates": "Şablonlar", + "homeImportFile": "Veya mevcut bir dosyayı içe aktarın", + "suggestionAI": "Flowpilot Yapay Zeka", "suggestionBlank": "Boş tuval", "suggestionBlankDesc": "Doğrudan editöre geç", - "suggestionAI": "Flowpilot Yapay Zeka", "suggestionAIDesc": "Bir prompt ile başla", "suggestionImport": "İçe Aktar", "suggestionImportDesc": "Mevcut çalışmanı getir", "suggestionTemplates": "Şablonlar", "suggestionTemplatesDesc": "Kanıtlanmış bir kalıptan başla", - "continueTitle": "Son eylemle devam et", - "homeEmptyTitle": "İlk akışınızı oluşturun", - "homeEmptySubtitle": "Kurumsal düzey mimariileri anında tasarlayın. Boş bir tuvalden başlayın, altyapınızı AI builderımızla tanımlayın veya hazır bir şablon kullanın.", - "homeBlankCanvas": "Boş Tuval", - "homeFlowpilotAI": "Flowpilot Yapay Zeka", - "homeTemplates": "Şablonlar", - "homeImportFile": "Veya mevcut bir dosyayı içe aktarın" + "continueTitle": "Son eylemle devam et" }, "settings": { "title": "Ayarlar", @@ -644,6 +645,13 @@ "edgeSyntax": "`-->`, `<--` veya `<-->` mimari kenar oklarını ve `api:R --> L:db` gibi yan niteleyicileri kullanın.", "nodeSyntax": "Geçerli düğüm tanımlarını kullanın: `service id(icon)[Label]`, `group id[Label]`, `junction id[Label]`.", "fallback": "Otomatik toparlamaya izin vermek için Mimari Katı Modu kapatın veya tanıları düzeltip yeniden deneyin." + }, + "jumpToLine": "{{line}} satırına git", + "diagnosticsGroup": { + "syntax": "Sözdizimi sorunları", + "identity": "Tanımlayıcı sorunları", + "recovery": "Kurtarma uyarıları", + "general": "Tanılama" } }, "layout": { @@ -786,9 +794,7 @@ "infra": "Altyapı", "openapi": "OpenAPI", "code": "Kod", - "codebase": "Depo", - "mindmap": "Zihin Haritası", - "markdown": "Markdown" + "codebase": "Depo" }, "title": "Veriden içe aktar", "description": "Kod, altyapı, SQL veya API özelliklerini diyagramınıza içe aktarın.", @@ -867,7 +873,9 @@ "edgeBundling": "Kardeş Kenarları Grupla", "edgeBundlingDesc": "Paralel bağlantıları paylaşılan şeritlerde tut", "architectureStrictMode": "Mimari Katı Modu", - "architectureStrictModeDesc": "Mimari tanılamalar kurtarma/doğrulama sorunları içerdiğinde Mermaid içe aktarmayı engelle" + "architectureStrictModeDesc": "Mimari tanılamalar kurtarma/doğrulama sorunları içerdiğinde Mermaid içe aktarmayı engelle", + "miniMap": "Mini Harita", + "miniMapDesc": "Sağ altta mini haritayı göster" }, "ai": { "provider": "Sağlayıcı", @@ -947,17 +955,6 @@ "hint": "En güçlü · Çok modlu", "category": "Eski", "badge": "Yeni" - }, - "gemini-2": { - "5-flash-lite": { - "label": "Gemini 2.5 Flash Lite" - }, - "5-flash": { - "label": "Gemini 2.5 Flash" - }, - "5-pro": { - "label": "Gemini 2.5 Pro" - } } }, "openai": { @@ -967,14 +964,6 @@ "category": "Hız", "badge": "Varsayılan" }, - "gpt-5": { - "2": { - "label": "GPT-5.2" - }, - "label": "GPT-5", - "hint": "Amiral gemisi · En yetenekli", - "category": "Amiral Gemisi" - }, "gpt-5.2": { "label": "GPT-5.2", "hint": "Son güncelleme · Geliştirilmiş akıl yürütme", @@ -992,6 +981,11 @@ "hint": "Derin akıl yürütme · Karmaşık görevler", "category": "Akıl Yürütme", "badge": "Akıl Yürütme" + }, + "gpt-5": { + "label": "GPT-5", + "hint": "Flagship model · Most capable", + "category": "Flagship" } }, "claude": { @@ -1041,11 +1035,6 @@ "hint": "Çok yönlü model", "category": "Performans", "badge": "Performans" - }, - "llama-3": { - "3-70b-versatile": { - "label": "Llama 3.3 70B" - } } }, "nvidia": { @@ -1099,11 +1088,6 @@ "label": "Zai-GLM 4.7", "hint": "Gelişmiş akıl yürütme · Araç kullanımı", "category": "Akıl Yürütme" - }, - "zai-glm-4": { - "7": { - "label": "Zai GLM 4.7" - } } }, "mistral": { @@ -1226,19 +1210,7 @@ "analytics": { "enableTitle": "Anonim başlatma analitiği", "enableDescription": "Anonim temel kullanım verilerini paylaş (isteğe bağlı)" - }, - "search": "Ara...", - "appearance": "Görünüm", - "account": "Hesap", - "about": "Hakkında", - "language": "Dil", - "theme": "Tema", - "fontSize": "Yazı boyutu", - "fontFamily": "Yazı tipi", - "save": "Kaydet", - "cancel": "İptal", - "reset": "Sıfırla", - "confirm": "Onayla" + } }, "customNodes": { "browserContent": "Tarayıcı İçeriği", @@ -1406,19 +1378,45 @@ "linkCopied": "İş birliği bağlantısı kopyalandı.", "copyManual": "Pano erişimi engellendi. Bağlantıyı paylaşım diyaloğundan manuel olarak kopyalayın." }, - "embed": "Göm", - "embedCode": "Gömme kodu", - "export": "Dışa aktar", - "settings": "Paylaşım ayarları", - "anyoneWithLink": "Bağlantısı olan herkes", - "viewOnly": "Yalnızca görüntüleme", - "canEdit": "Düzenleyebilir", - "disable": "Paylaşımı kapat", - "enable": "Paylaşımı aç", - "invite": "Davet et", - "pending": "Beklemede", - "revoke": "İptal et", - "expired": "Süresi doldu" + "roomLink": "İşbirliği Bağlantısı", + "permissionsNote": "Bağlantıya sahip herkes katılabilir.", + "permissionsNoteSecondary": "İzin kontrolleri ve kalıcı backend senkronizasyonu henüz yapılandırılmadı.", + "viewerCount": { + "one": "Bu oturumda 1 izleyici.", + "many": "Bu oturumda {{count}} izleyici." + }, + "mode": { + "realtime": { + "title": "Eşzamanlı senkronizasyon etkin", + "body": "Bu bağlantıyı açan akranlar, mevcut aktarım üzerinden canlı güncellemeleri ve imleçleri görebilir." + }, + "waiting": { + "title": "Eşzamanlı senkronizasyona bağlanılıyor", + "body": "Bu tuval hâlâ canlı eş akran senkronizasyonu kurmaya çalışıyor. Başarısız olursa oturum bu tarayıcıda yerel olarak kalır." + }, + "fallback": { + "title": "Yalnızca yerel işbirliği", + "body": "Bir arka uç aktarıcı veya desteklenen eş aktarımı olmadan bu oturum, mevcut tarayıcı çalışma zamanı dışında kalıcı çoklu kullanıcı canlı senkronizasyonu sağlamaz." + } + }, + "cache": { + "syncing": { + "title": "Yerel oda önbelleği senkronize ediliyor", + "body": "Bu tarayıcı, eş senkronizasyon tamamen yerleşmeden önce IndexedDB önb belleğe alınmış oda durumunu geri yüklemeye devam ediyor." + }, + "hydrated": { + "title": "Yerel önbellekten kurtarıldı", + "body": "Bu odada bu tarayıcıda zaten yerel olarak önbelleğe alınmış bir durum vardı, bu nedenle eşler yeniden bağlanmadan önce tuval geri yüklenebildi." + }, + "ready": { + "title": "Yerel önbellek hazır", + "body": "Bu tarayıcı, yeniden yükleme ve çevrimdışı kurtarma için odanın yerel bir IndexedDB kopyasını tutabilir." + }, + "unavailable": { + "title": "Yerel oda önbelleği yok", + "body": "Bu işbirliği oturumu şu anda tarayıcı IndexedDB oda kalıcılığını kullanmıyor." + } + } }, "connectionPanel": { "architecture": "Mimari", @@ -1427,68 +1425,19 @@ "route": "Yol", "appearance": "Görünüm", "condition": "Koşul", - "deleteConnection": "Bağlantıyı sil" + "deleteConnection": "Bağlantıyı sil", + "style": "Stil", + "actions": "İşlemler" }, "sidebar": { - "close": "Kenar çubuğunu kapat", - "searchPlaceholder": "Ara...", - "noResults": "Sonuç yok", - "nodes": "Düğümler", - "components": "Bileşenler", - "snippets": "Parçacıklar", - "addons": "Eklentiler" + "close": "Kenar çubuğunu kapat" }, "contextMenu": { "label": "Tuval Bağlam Menüsü" }, - "canvas": { - "addNodeShortcut": "Düğüm ekle", - "aiChatPlaceholder": "Bir şeyler oluştur...", - "nodes": "Düğümler", - "connections": "Bağlantılar", - "elements": "Öğeler", - "selection": "Seçim", - "noSelection": "Seçim yok", - "alignNodes": "Düğümleri hizala", - "distributeNodes": "Düğümleri dağıt", - "alignLeft": "Sola hizala", - "alignCenter": "Merkeze hizala", - "alignRight": "Sağa hizala", - "alignTop": "Üste hizala", - "alignMiddle": "Ortaya hizala", - "alignBottom": "Alta hizala", - "distributeHorizontally": "Yatay dağıt", - "distributeVertically": "Dikey dağıt", - "zoomToFit": "Sığdırmak için yakınlaştır", - "zoomToSelection": "Seçime yakınlaştır", - "locked": "Kilitli", - "unlock": "Kilidi aç", - "lock": "Kilitle", - "bringToFront": "Öne getir", - "sendToBack": "Arkaya gönder", - "group": "Grupla", - "ungroup": "Grubu kaldır" - }, - "mindmap": { - "addChild": "Alt konu ekle", - "addSibling": "Kardeş konu ekle", - "addParent": "Üst konu ekle", - "delete": "Sil", - "insertAfter": "Sonra ekle", - "insertBefore": "Önce ekle" - }, + "canvas": {}, + "mindmap": {}, "aiModel": { - "title": "AI Modeli", - "provider": "Sağlayıcı", - "model": "Model", - "temperature": "Sıcaklık", - "maxTokens": "Maksimum belirteç", - "buttons": { - "retry": "Tekrar dene", - "stop": "Durdur", - "clear": "Temizle" - }, - "thinking": "Düşünüyor...", - "error": "AI hatası" + "buttons": {} } } diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index eee38fe7..b60cec1a 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -67,7 +67,10 @@ "distributeVertically": "垂直分布", "group": "分组", "upload": "上传", - "rename": "重命名" + "rename": "重命名", + "export": "导出", + "exportDiagram": "导出图表", + "exportAs": "导出为" }, "nav": { "home": "首页", @@ -123,13 +126,13 @@ "plantuml": "导出为 PlantUML", "openflowdsl": "导出为 OpenFlow DSL", "figma": "导出到 Figma", - "hintTransparent4K": "Transparent (4K)", - "hintWhiteBg4K": "White Background (4K)", + "hintTransparent4K": "透明 (4K)", + "hintWhiteBg4K": "白色背景 (4K)", "jsonLabel": "JSON File", - "hintDownload": "Download", + "hintDownload": "下载", "openflowdslLabel": "{{appName}} DSL", "hintClipboard": "复制", - "figmaEditable": "Figma Editable", + "figmaEditable": "Figma 可编辑", "exportDiagram": "Export Diagram", "exportAs": "Export As", "actionCopy": "复制", @@ -144,27 +147,28 @@ "shareEmbed": "分享与嵌入", "hintShareViewer": "此房间的邀请链接", "hintShareEmbed": "只读查看链接", - "readmeEmbed": "README Embed", - "hintReadmeEmbed": "Copy Markdown snippet", - "sectionImage": "Image", + "readmeEmbed": "README 嵌入", + "hintReadmeEmbed": "复制 Markdown 代码片段", + "sectionImage": "图片", "sectionVideo": "视频", "sectionCode": "代码", "shareSection": "分享画布", - "revealVideo": "Reveal Video", - "hintRevealVideo": "Nodes fade in sequentially (WebM/MP4)", - "revealGif": "Reveal GIF", - "hintRevealGif": "Animated reveal for docs/social", - "cinematicVideo": "Cinematic Build Video", + "revealVideo": "渐显视频", + "hintRevealVideo": "节点依次淡入 (WebM/MP4)", + "revealGif": "渐显 GIF", + "hintRevealGif": "适用于文档/社交媒体的动画渐显", + "cinematicVideo": "电影级构建视频", "hintCinematicVideo": "Dark launch-style build", - "cinematicGif": "Cinematic Build GIF", - "hintCinematicGif": "Dark social-ready loop", - "actionDownload": "Download", + "cinematicGif": "电影级构建 GIF", + "hintCinematicGif": "适用于社交媒体的深色循环", + "actionDownload": "下载", "hintDocument": "文档", "hintEditableSvg": "可编辑 SVG", "chooseFormat": "选择格式", "speed": "速度", "resolution": "分辨率", - "transparentBackground": "透明背景" + "transparentBackground": "透明背景", + "headingFormat": "导出格式" }, "import": { "title": "导入", @@ -267,21 +271,21 @@ "hint": "除非您有导出的备份或另一份副本,否则此操作无法撤销。", "closeDialog": "关闭删除流程对话框" }, + "homeEmptyTitle": "创建您的第一个流程", + "homeEmptySubtitle": "即时设计企业级架构。从空白画布开始,用我们的AI构建器描述您的基础设施,或使用定制模板。", + "homeBlankCanvas": "空白画布", + "homeFlowpilotAI": "流程领航人工智能", + "homeTemplates": "模板", + "homeImportFile": "或导入现有文件", + "suggestionAI": "Flowpilot 人工智能", "suggestionBlank": "空白画布", "suggestionBlankDesc": "直接进入编辑器", - "suggestionAI": "流程领航人工智能", "suggestionAIDesc": "从提示开始", "suggestionImport": "导入", "suggestionImportDesc": "导入现有工作", "suggestionTemplates": "模板", "suggestionTemplatesDesc": "从经过验证的模式开始", - "continueTitle": "继续最近的操作", - "homeEmptyTitle": "创建您的第一个流程", - "homeEmptySubtitle": "即时设计企业级架构。从空白画布开始,用我们的AI构建器描述您的基础设施,或使用定制模板。", - "homeBlankCanvas": "空白画布", - "homeFlowpilotAI": "流程领航人工智能", - "homeTemplates": "模板", - "homeImportFile": "或导入现有文件" + "continueTitle": "继续最近的操作" }, "settings": { "title": "设置", @@ -298,9 +302,9 @@ "about": "关于", "brand": "品牌设置", "privacy": "隐私", - "themeLight": "Light", - "themeDark": "Dark", - "themeSystem": "System" + "themeLight": "浅色", + "themeDark": "深色", + "themeSystem": "跟随系统" }, "shortcuts": { "title": "键盘快捷键", @@ -313,28 +317,28 @@ "help": "帮助" }, "properties": { - "title": "Properties", - "shape": "Shape", - "color": "Color", - "icon": "Icon", - "rotation": "Rotation", - "transparency": "Transparency", - "imageSettings": "Image Settings", - "bulkShape": "Bulk Shape", - "bulkColor": "Bulk Color", - "bulkIcon": "Bulk Icon", - "labelTransform": "Label Transform", - "findReplace": "Find & Replace", - "findLabel": "Find", - "findPlaceholder": "Find text", - "replaceLabel": "Replace", - "replacePlaceholder": "Replace with", - "prefixOptional": "Prefix (optional)", - "suffixOptional": "Suffix (optional)", - "useRegex": "Use Regex", - "applyToSelectedNodes": "Apply to selected nodes", - "selectFieldToApply": "Select a field to apply", - "previewSummary": "Preview summary", + "title": "属性", + "shape": "形状", + "color": "颜色", + "icon": "图标", + "rotation": "旋转", + "transparency": "透明度", + "imageSettings": "图片设置", + "bulkShape": "批量形状", + "bulkColor": "批量颜色", + "bulkIcon": "批量图标", + "labelTransform": "标签变换", + "findReplace": "查找和替换", + "findLabel": "查找", + "findPlaceholder": "查找文本", + "replaceLabel": "替换", + "replacePlaceholder": "替换为", + "prefixOptional": "前缀(可选)", + "suffixOptional": "后缀(可选)", + "useRegex": "使用正则表达式", + "applyToSelectedNodes": "应用于所选节点", + "selectFieldToApply": "选择要应用的字段", + "previewSummary": "预览摘要", "selectionSummary": "选择摘要" }, "saveStatus": { @@ -346,8 +350,14 @@ "model": "模型", "apiKey": "API 密钥", "placeholder": "描述你的流程图...", - "settingsSubtitle": "Configure your preferred AI provider, model, and API key below.", - "generateWithFlowpilot": "使用 Flowpilot 生成" + "settingsSubtitle": "在下方配置您首选的 AI 提供商、模型和 API 密钥。", + "generateWithFlowpilot": "使用 Flowpilot 生成", + "howToGetKey": "如何获取 API 密钥", + "openConsole": "打开控制台", + "pasteKeyStep": "将其粘贴到上方字段中,我们绝不会获取此信息", + "customEndpointTitle": "什么是自定义端点?", + "customModelHint": "输入此端点对应的准确模型 ID", + "privacyTitle": "隐私与加密" }, "playback": { "title": "回放", @@ -615,6 +625,13 @@ "edgeSyntax": "使用架构边箭头 `-->`、`<--` 或 `<-->`,以及类似 `api:R --> L:db` 的侧向限定符。", "nodeSyntax": "使用有效的节点声明:`service id(icon)[Label]`、`group id[Label]`、`junction id[Label]`。", "fallback": "关闭架构严格模式以允许自动恢复,或修复诊断后重试。" + }, + "jumpToLine": "跳转到第 {{line}} 行", + "diagnosticsGroup": { + "syntax": "语法问题", + "identity": "标识符问题", + "recovery": "恢复警告", + "general": "诊断信息" } }, "layout": { @@ -757,9 +774,7 @@ "infra": "Infra", "openapi": "OpenAPI", "code": "Code", - "codebase": "仓库", - "mindmap": "思维导图", - "markdown": "Markdown" + "codebase": "仓库" }, "title": "从数据导入", "description": "将代码、基础设施、SQL 或 API 规格导入到你的图表中。", @@ -838,7 +853,9 @@ "edgeBundling": "捆绑兄弟边", "edgeBundlingDesc": "将并行连接保持在共享车道上", "architectureStrictMode": "架构严格模式", - "architectureStrictModeDesc": "当架构诊断包含恢复/验证问题时阻止Mermaid导入" + "architectureStrictModeDesc": "当架构诊断包含恢复/验证问题时阻止Mermaid导入", + "miniMap": "小地图", + "miniMapDesc": "在右下角显示小地图" }, "ai": { "provider": "提供商", @@ -912,132 +929,227 @@ }, "models": { "gemini": { - "gemini-2": { - "5-flash-lite": { - "label": "Gemini 2.5 Flash Lite" - }, - "5-flash": { - "label": "Gemini 2.5 Flash" - }, - "5-pro": { - "label": "Gemini 2.5 Pro" - } - }, "gemini-3-flash": { - "label": "Gemini 3 Flash" + "label": "Gemini 3 Flash", + "hint": "Frontier speed + intelligence", + "category": "Legacy", + "badge": "New" }, "gemini-3-pro": { - "label": "Gemini 3 Pro" + "label": "Gemini 3 Pro", + "hint": "Most powerful · Multimodal", + "category": "Legacy", + "badge": "New" + }, + "gemini-2.5-flash-lite": { + "label": "2.5 Flash Lite", + "hint": "Fastest · Free tier default", + "category": "Speed", + "badge": "Default" + }, + "gemini-2.5-flash": { + "label": "2.5 Flash", + "hint": "Best price/performance balance", + "category": "Speed" + }, + "gemini-2.5-pro": { + "label": "2.5 Pro", + "hint": "Best reasoning · Complex diagrams", + "category": "Reasoning" } }, "openai": { "gpt-5-mini": { - "label": "GPT-5 Mini" - }, - "gpt-5": { - "2": { - "label": "GPT-5.2" - }, - "label": "GPT-5" + "label": "GPT-5 Mini", + "hint": "Fast · Cost-efficient", + "category": "Speed", + "badge": "Default" }, "o4-mini": { - "label": "O4-Mini" + "label": "O4-Mini", + "hint": "Advanced reasoning · Fast", + "category": "Reasoning", + "badge": "Reasoning" }, "o3": { - "label": "O3" + "label": "O3", + "hint": "Deep reasoning · Complex tasks", + "category": "Reasoning", + "badge": "Reasoning" + }, + "gpt-5": { + "label": "GPT-5", + "hint": "Flagship model · Most capable", + "category": "Flagship" + }, + "gpt-5.2": { + "label": "GPT-5.2", + "hint": "Latest update · Improved reasoning", + "category": "Reasoning", + "badge": "New" } }, "claude": { "claude-haiku-4-5": { - "label": "Claude Haiku 4.5" + "label": "Claude Haiku 4.5", + "hint": "Fastest · Most affordable", + "category": "Speed" }, "claude-sonnet-4-5": { - "label": "Claude Sonnet 4.5" + "label": "Claude Sonnet 4.5", + "hint": "Balanced intelligence & speed", + "category": "Flagship" }, "claude-sonnet-4-6": { - "label": "Claude Sonnet 4.6" + "label": "Claude Sonnet 4.6", + "hint": "Latest Sonnet · Best coding", + "category": "Flagship", + "badge": "Default" }, "claude-opus-4-6": { - "label": "Claude Opus 4.6" + "label": "Claude Opus 4.6", + "hint": "Most intelligent · 1M token context", + "category": "Reasoning", + "badge": "Flagship" } }, "groq": { "meta-llama/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Free tier · Very fast", + "category": "Speed", + "badge": "Free" }, "meta-llama/llama-4-maverick-17b-128e-instruct": { - "label": "Llama 4 Maverick" + "label": "Llama 4 Maverick", + "hint": "More capable · Free tier", + "category": "Speed", + "badge": "Free" }, "qwen/qwen3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" }, - "llama-3": { - "3-70b-versatile": { - "label": "Llama 3.3 70B" - } + "llama-3.3-70b-versatile": { + "label": "Llama 3.3 70B Versatile", + "hint": "Versatile model", + "category": "Performance", + "badge": "Performance" } }, "nvidia": { "meta/llama-4-scout-17b-16e-instruct": { - "label": "Llama 4 Scout" + "label": "Llama 4 Scout", + "hint": "Efficient · Multi-modal", + "category": "Speed" }, "nvidia/nemotron-nano-12b-v2-vl": { - "label": "Nemotron Nano 12B" + "label": "Nemotron Nano 12B", + "hint": "Lightweight · Vision-language · Fast", + "category": "Speed" }, "deepseek/deepseek-v3-2": { - "label": "DeepSeek V3-2" + "label": "DeepSeek V3-2", + "hint": "Latest · GPT-5 comparable", + "category": "Flagship", + "badge": "New" }, "qwen/qwq-32b": { - "label": "QwQ 32B" + "label": "QwQ 32B", + "hint": "Strong reasoning model", + "category": "Reasoning" }, "moonshotai/kimi-k2-thinking": { - "label": "Kimi K2 Thinking" + "label": "Kimi K2 Thinking", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "cerebras": { "gpt-oss-120b": { - "label": "GPT OSS 120B" + "label": "GPT OSS 120B", + "hint": "120B params · Fast on WSE-3", + "category": "Speed", + "badge": "Default" }, "qwen-3-32b": { - "label": "Qwen 3 32B" + "label": "Qwen 3 32B", + "hint": "2,403 tok/s · Industry fastest", + "category": "Speed", + "badge": "🚀 Fastest" }, "qwen-3-235b-a22b": { - "label": "Qwen 3 235B" + "label": "Qwen 3 235B", + "hint": "Flagship · Best quality", + "category": "Flagship", + "badge": "Flagship" }, - "zai-glm-4": { - "7": { - "label": "Zai GLM 4.7" - } + "zai-glm-4.7": { + "label": "Zai-GLM 4.7", + "hint": "Advanced reasoning · Tool use", + "category": "Reasoning" } }, "mistral": { "mistral-small-latest": { - "label": "Mistral Small" + "label": "Mistral Small", + "hint": "Fast · Cost-efficient · 32k context", + "category": "Speed", + "badge": "Free" }, "mistral-medium-latest": { - "label": "Mistral Medium" + "label": "Mistral Medium", + "hint": "Balanced quality-cost · Best default", + "category": "Flagship", + "badge": "Default" }, "mistral-large-latest": { - "label": "Mistral Large" + "label": "Mistral Large", + "hint": "Most capable · 128k context · Flagship", + "category": "Flagship", + "badge": "Flagship" }, "codestral-latest": { - "label": "Codestral" + "label": "Codestral", + "hint": "Code-optimized · 256k context", + "category": "Coding", + "badge": "Code" }, "pixtral-large-latest": { - "label": "Pixtral Large" + "label": "Pixtral Large", + "hint": "Vision + reasoning · Multimodal", + "category": "Multimodal", + "badge": "Vision" + } + }, + "custom": { + "custom": { + "label": "Custom Model", + "hint": "Enter your model ID below", + "category": "Custom" } } }, "customEndpoints": { "ollama": { - "name": "Ollama" + "name": "Ollama", + "hint": "Local · Free" }, "lmStudio": { - "name": "LM Studio" + "name": "LM Studio", + "hint": "Local · Free" }, "together": { - "name": "Together AI" + "name": "Together AI", + "hint": "Cloud · Fast" } + }, + "byok": { + "dataPrivacy": "Your data never passes through our servers", + "control": "Full control over cost and rate limits", + "flexibility": "Switch providers anytime without re-linking", + "cuttingEdge": "Access cutting-edge models as soon as they launch" } }, "brand": { @@ -1078,19 +1190,7 @@ "analytics": { "enableTitle": "匿名启动分析", "enableDescription": "共享匿名的基础使用数据(可选)" - }, - "search": "搜索...", - "appearance": "外观", - "account": "账户", - "about": "关于", - "language": "语言", - "theme": "主题", - "fontSize": "字体大小", - "fontFamily": "字体", - "save": "保存", - "cancel": "取消", - "reset": "重置", - "confirm": "确认" + } }, "customNodes": { "browserContent": "浏览器内容", @@ -1278,19 +1378,45 @@ "linkCopied": "协作链接已复制。", "copyManual": "剪贴板访问被阻止。请从分享对话框中手动复制链接。" }, - "embed": "嵌入", - "embedCode": "嵌入代码", - "export": "导出", - "settings": "分享设置", - "anyoneWithLink": "拥有链接的任何人", - "viewOnly": "仅查看", - "canEdit": "可以编辑", - "disable": "关闭分享", - "enable": "开启分享", - "invite": "邀请", - "pending": "待处理", - "revoke": "撤销", - "expired": "已过期" + "roomLink": "协作链接", + "permissionsNote": "任何拥有此链接的人都可以加入。", + "permissionsNoteSecondary": "权限控制和持久后端同步尚未配置。", + "viewerCount": { + "one": "此会话中有 1 位查看者。", + "many": "此会话中有 {{count}} 位查看者。" + }, + "mode": { + "realtime": { + "title": "实时同步已激活", + "body": "打开此链接的对等方可以通过当前对等传输看到实时更新和光标。" + }, + "waiting": { + "title": "正在连接实时同步", + "body": "此画布仍在尝试建立实时对等同步。如果无法建立,会话将在此浏览器中保持仅本地模式。" + }, + "fallback": { + "title": "仅本地协作", + "body": "没有后端中继或受支持的对等传输,此会话无法在当前浏览器运行时之外提供持久的多用户实时同步。" + } + }, + "cache": { + "syncing": { + "title": "本地房间缓存同步中", + "body": "此浏览器仍在恢复 IndexedDB 缓存的房间状态,然后再完全建立对等同步。" + }, + "hydrated": { + "title": "已从本地缓存恢复", + "body": "此房间在此浏览器中已有本地缓存状态,因此画布可以在对等方重新连接之前恢复。" + }, + "ready": { + "title": "本地缓存就绪", + "body": "此浏览器可以保存房间的本地 IndexedDB 副本,用于此设备上的重新加载和离线恢复。" + }, + "unavailable": { + "title": "无本地房间缓存", + "body": "此协作会话当前未使用浏览器 IndexedDB 房间持久化。" + } + } }, "connectionPanel": { "architecture": "架构", @@ -1299,68 +1425,19 @@ "route": "路由", "appearance": "外观", "condition": "条件", - "deleteConnection": "删除连接" + "deleteConnection": "删除连接", + "style": "样式", + "actions": "操作" }, "sidebar": { - "close": "关闭侧边栏", - "searchPlaceholder": "搜索...", - "noResults": "无结果", - "nodes": "节点", - "components": "组件", - "snippets": "代码片段", - "addons": "插件" + "close": "关闭侧边栏" }, "contextMenu": { "label": "画布右键菜单" }, - "canvas": { - "addNodeShortcut": "添加节点", - "aiChatPlaceholder": "创建一些内容...", - "nodes": "节点", - "connections": "连接", - "elements": "元素", - "selection": "选择", - "noSelection": "无选择", - "alignNodes": "对齐节点", - "distributeNodes": "分布节点", - "alignLeft": "左对齐", - "alignCenter": "居中对齐", - "alignRight": "右对齐", - "alignTop": "顶部对齐", - "alignMiddle": "中间对齐", - "alignBottom": "底部对齐", - "distributeHorizontally": "水平分布", - "distributeVertically": "垂直分布", - "zoomToFit": "缩放以适应", - "zoomToSelection": "缩放到选择", - "locked": "已锁定", - "unlock": "解锁", - "lock": "锁定", - "bringToFront": "带到前面", - "sendToBack": "发送到后面", - "group": "分组", - "ungroup": "取消分组" - }, - "mindmap": { - "addChild": "添加子主题", - "addSibling": "添加同级主题", - "addParent": "添加父主题", - "delete": "删除", - "insertAfter": "在后面插入", - "insertBefore": "在前面插入" - }, + "canvas": {}, + "mindmap": {}, "aiModel": { - "title": "AI模型", - "provider": "提供商", - "model": "模型", - "temperature": "温度", - "maxTokens": "最大令牌数", - "buttons": { - "retry": "重试", - "stop": "停止", - "clear": "清除" - }, - "thinking": "思考中...", - "error": "AI错误" + "buttons": {} } } diff --git a/readme-media/GIF-OFK-F.gif b/readme-media/GIF-OFK-F.gif new file mode 100644 index 00000000..faeea0cd Binary files /dev/null and b/readme-media/GIF-OFK-F.gif differ diff --git a/readme-media/Sample -GIF-1.gif b/readme-media/Sample -GIF-1.gif new file mode 100644 index 00000000..26b269fe Binary files /dev/null and b/readme-media/Sample -GIF-1.gif differ diff --git a/readme-media/Sample -GIF-2.gif b/readme-media/Sample -GIF-2.gif new file mode 100644 index 00000000..19384f9a Binary files /dev/null and b/readme-media/Sample -GIF-2.gif differ diff --git a/readme-media/Sample -GIF-3.gif b/readme-media/Sample -GIF-3.gif new file mode 100644 index 00000000..05e83c15 Binary files /dev/null and b/readme-media/Sample -GIF-3.gif differ diff --git a/src/components/ArchitectureRulesPanel.tsx b/src/components/ArchitectureRulesPanel.tsx new file mode 100644 index 00000000..a28f6e8f --- /dev/null +++ b/src/components/ArchitectureRulesPanel.tsx @@ -0,0 +1,26 @@ +import React, { lazy, Suspense } from 'react'; +import { SidebarBody, SidebarHeader, SidebarShell } from './SidebarShell'; + +const LazyLintRulesPanel = lazy(async () => { + const module = await import('./architecture-lint/LintRulesPanel'); + return { default: module.LintRulesPanel }; +}); + +interface ArchitectureRulesPanelProps { + onClose: () => void; +} + +export function ArchitectureRulesPanel({ + onClose, +}: ArchitectureRulesPanelProps): React.ReactElement { + return ( + + + + + + + + + ); +} diff --git a/src/components/CinematicExportOverlay.tsx b/src/components/CinematicExportOverlay.tsx new file mode 100644 index 00000000..1a0e3cf5 --- /dev/null +++ b/src/components/CinematicExportOverlay.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { Loader2, X } from 'lucide-react'; +import { + useCinematicExportActions, + useCinematicExportJobState, +} from '@/context/CinematicExportContext'; + +function getFrameCopy(completedFrames: number, totalFrames: number): string | null { + if (totalFrames > 0) { + return `${completedFrames}/${totalFrames}`; + } + + return null; +} + +export function CinematicExportOverlay(): React.ReactElement | null { + const { canCancel, completedFrames, progressPercent, request, stageLabel, status, totalFrames } = + useCinematicExportJobState(); + const { cancelExport } = useCinematicExportActions(); + + if (status === 'idle') { + return null; + } + + const resolvedStageLabel = stageLabel || 'Preparing cinematic export…'; + const frameCopy = getFrameCopy(completedFrames, totalFrames); + const resolutionLabel = request?.resolution?.toUpperCase() ?? 'VIDEO'; + const progressWidth = `${Math.max(5, progressPercent)}%`; + + return ( +
+
+ {/* Underlay Progress Overlay */} +
+ +
+ +
+ +
+ + {resolvedStageLabel} + + + {progressPercent}% + +
+ + {(frameCopy || resolutionLabel) && ( + <> +
+ +
+ {frameCopy && {frameCopy}} + {resolutionLabel && ( + {resolutionLabel} + )} +
+ + )} + + +
+
+ ); +} diff --git a/src/components/CommandBar.tsx b/src/components/CommandBar.tsx index e215e4a9..e4b460c6 100644 --- a/src/components/CommandBar.tsx +++ b/src/components/CommandBar.tsx @@ -56,6 +56,7 @@ function OpenCommandBarContent({ onOpenStudioAI, onOpenStudioOpenFlow, onOpenStudioMermaid, + onOpenArchitectureRules, initialView = 'root', onAddAnnotation: _onAddAnnotation, onAddSection: _onAddSection, @@ -133,6 +134,7 @@ function OpenCommandBarContent({ onOpenStudioAI, onOpenStudioOpenFlow, onOpenStudioMermaid, + onOpenArchitectureRules, hasImport, }); diff --git a/src/components/ExportMenu.tsx b/src/components/ExportMenu.tsx index 51115095..f4f0eddb 100644 --- a/src/components/ExportMenu.tsx +++ b/src/components/ExportMenu.tsx @@ -1,13 +1,17 @@ import React, { Suspense, lazy, useState } from 'react'; import { Download } from 'lucide-react'; import { useTranslation } from 'react-i18next'; +import { + createDefaultCinematicExportRequest, + type CinematicExportRequest, + type CinematicExportResolution, + type CinematicExportSpeed, + type CinematicThemeMode, +} from '@/services/export/cinematicExport'; import { Tooltip } from './Tooltip'; import { Button } from './ui/Button'; import { useExportMenu } from './useExportMenu'; -type CinematicSpeed = 'slow' | 'normal' | 'fast'; -type CinematicResolution = '720p' | '1080p' | '4k'; - const LazyExportMenuPanel = lazy(async () => { const module = await import('./ExportMenuPanel'); return { default: module.ExportMenuPanel }; @@ -19,7 +23,7 @@ interface ExportMenuProps { onExportSVG: () => void; onCopySVG: () => void; onExportPDF: () => void; - onExportCinematic: (format: 'cinematic-video' | 'cinematic-gif') => void; + onExportCinematic: (request: CinematicExportRequest) => void; onExportJSON: () => void; onCopyJSON: () => void; onExportMermaid: () => void; @@ -31,12 +35,11 @@ interface ExportMenuProps { onExportFigma: () => void; onDownloadFigma: () => void; onShare: () => void; - cinematicSpeed?: CinematicSpeed; - onCinematicSpeedChange?: (speed: CinematicSpeed) => void; - cinematicResolution?: CinematicResolution; - onCinematicResolutionChange?: (res: CinematicResolution) => void; - cinematicTransparent?: boolean; - onCinematicTransparentChange?: (transparent: boolean) => void; + cinematicSpeed?: CinematicExportSpeed; + onCinematicSpeedChange?: (speed: CinematicExportSpeed) => void; + cinematicResolution?: CinematicExportResolution; + onCinematicResolutionChange?: (res: CinematicExportResolution) => void; + cinematicThemeMode: CinematicThemeMode; } export const ExportMenu: React.FC = ({ @@ -61,21 +64,26 @@ export const ExportMenu: React.FC = ({ onCinematicSpeedChange, cinematicResolution, onCinematicResolutionChange, - cinematicTransparent, - onCinematicTransparentChange, + cinematicThemeMode, }) => { const { t } = useTranslation(); const exportLabel = t('export.title', 'Export'); - const [cinematicSpeedState, setCinematicSpeedState] = useState('normal'); + const defaultRequest = createDefaultCinematicExportRequest(cinematicThemeMode); + const [cinematicSpeedState, setCinematicSpeedState] = useState( + defaultRequest.speed + ); const [cinematicResolutionState, setCinematicResolutionState] = - useState('1080p'); - const [cinematicTransparentState, setCinematicTransparentState] = useState(false); + useState(defaultRequest.resolution); const effectiveSpeed = cinematicSpeed ?? cinematicSpeedState; const effectiveSpeedChange = onCinematicSpeedChange ?? setCinematicSpeedState; const effectiveResolution = cinematicResolution ?? cinematicResolutionState; const effectiveResolutionChange = onCinematicResolutionChange ?? setCinematicResolutionState; - const effectiveTransparent = cinematicTransparent ?? cinematicTransparentState; - const effectiveTransparentChange = onCinematicTransparentChange ?? setCinematicTransparentState; + const cinematicExportRequest: CinematicExportRequest = { + format: 'cinematic-video', + speed: effectiveSpeed, + resolution: effectiveResolution, + themeMode: cinematicThemeMode, + }; const { isOpen, menuRef, @@ -88,6 +96,7 @@ export const ExportMenu: React.FC = ({ onCopySVG, onExportPDF, onExportCinematic, + getCinematicExportRequest: () => cinematicExportRequest, onExportJSON, onCopyJSON, onExportMermaid, @@ -124,8 +133,6 @@ export const ExportMenu: React.FC = ({ onCinematicSpeedChange={effectiveSpeedChange} cinematicResolution={effectiveResolution} onCinematicResolutionChange={effectiveResolutionChange} - cinematicTransparent={effectiveTransparent} - onCinematicTransparentChange={effectiveTransparentChange} /> )} diff --git a/src/components/ExportMenuPanel.test.tsx b/src/components/ExportMenuPanel.test.tsx index 933dcfac..081bf795 100644 --- a/src/components/ExportMenuPanel.test.tsx +++ b/src/components/ExportMenuPanel.test.tsx @@ -61,18 +61,15 @@ describe('ExportMenuPanel', () => { render(); fireEvent.click(screen.getByRole('tab', { name: /Video/i })); - fireEvent.click(within(screen.getByTestId('export-format-select')).getByRole('button', { name: /Cinematic Build Video/i })); - expect(within(screen.getByTestId('export-format-select')).getByRole('button', { name: /Cinematic Build Video/i })).toBeTruthy(); - expect(within(screen.getByRole('listbox')).getByRole('button', { name: /Cinematic Build Video/i })).toBeTruthy(); - expect(within(screen.getByRole('listbox')).getByRole('button', { name: /Cinematic Build GIF/i })).toBeTruthy(); - expect(within(screen.getByRole('listbox')).queryByRole('button', { name: /Playback Video/i })).toBeNull(); + expect(screen.getByTestId('export-format-summary')).toBeTruthy(); + expect(screen.getByText(/Cinematic Build Video/i)).toBeTruthy(); + expect(screen.queryByTestId('export-format-select')).toBeNull(); }); it('renders video controls before the download action and uses shared control interactions', () => { const onCinematicSpeedChange = vi.fn(); const onCinematicResolutionChange = vi.fn(); - const onCinematicTransparentChange = vi.fn(); render( { onCinematicSpeedChange={onCinematicSpeedChange} cinematicResolution="1080p" onCinematicResolutionChange={onCinematicResolutionChange} - cinematicTransparent={false} - onCinematicTransparentChange={onCinematicTransparentChange} /> ); fireEvent.click(screen.getByRole('tab', { name: /Video/i })); - - const panelText = screen.getByText(/Transparent background/i); const downloadButton = screen.getByTestId('export-action-cinematic-video-download'); - - expect( - panelText.compareDocumentPosition(downloadButton) & Node.DOCUMENT_POSITION_FOLLOWING - ).toBeTruthy(); + expect(downloadButton).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: '2×' })); fireEvent.click(screen.getByRole('button', { name: '4K' })); - fireEvent.click(screen.getByRole('switch')); expect(onCinematicSpeedChange).toHaveBeenCalledWith('fast'); expect(onCinematicResolutionChange).toHaveBeenCalledWith('4k'); - expect(onCinematicTransparentChange).toHaveBeenCalledWith(true); }); }); diff --git a/src/components/ExportMenuPanel.tsx b/src/components/ExportMenuPanel.tsx index 8d634b4d..35ea77d7 100644 --- a/src/components/ExportMenuPanel.tsx +++ b/src/components/ExportMenuPanel.tsx @@ -15,26 +15,25 @@ import { } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { APP_NAME } from '@/lib/brand'; +import { + type CinematicExportResolution, + type CinematicExportSpeed, +} from '@/services/export/cinematicExport'; import { SegmentedChoice } from './properties/SegmentedChoice'; import { Button } from './ui/Button'; import { Select, type SelectOption } from './ui/Select'; import { SegmentedTabs } from './ui/SegmentedTabs'; -import { Switch } from './ui/Switch'; interface ExportMenuPanelProps { onSelect: (key: string, action: ExportActionKey) => void; - cinematicSpeed?: CinematicSpeed; - onCinematicSpeedChange?: (speed: CinematicSpeed) => void; - cinematicResolution?: CinematicResolution; - onCinematicResolutionChange?: (res: CinematicResolution) => void; - cinematicTransparent?: boolean; - onCinematicTransparentChange?: (transparent: boolean) => void; + cinematicSpeed?: CinematicExportSpeed; + onCinematicSpeedChange?: (speed: CinematicExportSpeed) => void; + cinematicResolution?: CinematicExportResolution; + onCinematicResolutionChange?: (res: CinematicExportResolution) => void; } type ExportCategoryKey = 'image' | 'video' | 'code'; type ExportActionKey = 'download' | 'copy'; -type CinematicSpeed = 'slow' | 'normal' | 'fast'; -type CinematicResolution = '720p' | '1080p' | '4k'; interface ExportOption { key: string; @@ -115,8 +114,6 @@ export function ExportMenuPanel({ onCinematicSpeedChange, cinematicResolution = '1080p', onCinematicResolutionChange, - cinematicTransparent = false, - onCinematicTransparentChange, }: ExportMenuPanelProps): React.ReactElement { const { t } = useTranslation(); @@ -163,14 +160,7 @@ export function ExportMenuPanel({ { key: 'cinematic-video', label: t('export.cinematicVideo', 'Cinematic Build Video'), - hint: t('export.hintCinematicVideo', 'Polished animated build'), - Icon: Film, - actions: ['download'], - }, - { - key: 'cinematic-gif', - label: t('export.cinematicGif', 'Cinematic Build GIF'), - hint: t('export.hintCinematicGif', 'Animated loop for sharing'), + hint: t('export.hintCinematicVideo', 'Presentation-ready animated export'), Icon: Film, actions: ['download'], }, @@ -250,6 +240,7 @@ export function ExportMenuPanel({ const selectedItem = activeSection.items.find((item) => item.key === selectedKeys[activeSectionKey]) ?? activeSection.items[0]; + const shouldShowFormatSelect = activeSection.items.length > 1; const selectOptions: SelectOption[] = activeSection.items.map((item) => ({ value: item.key, label: item.label, @@ -297,14 +288,24 @@ export function ExportMenuPanel({

-
- +
+ ) : ( +
+

{selectedItem.label}

+

{selectedItem.hint}

+
+ )} {activeSectionKey === 'video' && onCinematicSpeedChange && (
@@ -313,7 +314,7 @@ export function ExportMenuPanel({

onCinematicSpeedChange(speed as CinematicSpeed)} + onSelect={(speed) => onCinematicSpeedChange(speed as CinematicExportSpeed)} items={CINEMATIC_SPEED_ITEMS} columns={3} size="sm" @@ -328,7 +329,7 @@ export function ExportMenuPanel({

onCinematicResolutionChange(res as CinematicResolution)} + onSelect={(res) => onCinematicResolutionChange(res as CinematicExportResolution)} items={CINEMATIC_RESOLUTION_ITEMS} columns={3} size="sm" @@ -336,19 +337,6 @@ export function ExportMenuPanel({
)} - {activeSectionKey === 'video' && onCinematicTransparentChange && ( -
-

- {t('export.transparentBackground', 'Transparent background')} -

- -
- )} -
1 ? 'grid-cols-2' : 'grid-cols-1'}`} > diff --git a/src/components/FlowEditor.tsx b/src/components/FlowEditor.tsx index e26c62d8..886e617c 100644 --- a/src/components/FlowEditor.tsx +++ b/src/components/FlowEditor.tsx @@ -1,6 +1,7 @@ import React from 'react'; import '@xyflow/react/dist/style.css'; import { FlowCanvas } from './FlowCanvas'; +import { CinematicExportOverlay } from './CinematicExportOverlay'; import { FlowEditorChrome } from './flow-editor/FlowEditorChrome'; import { useFlowEditorScreenModel } from './flow-editor/useFlowEditorScreenModel'; import { ArchitectureLintProvider } from '@/context/ArchitectureLintContext'; @@ -8,9 +9,7 @@ import { useCinematicExportState } from '@/context/CinematicExportContext'; import { DiagramDiffProvider } from '@/context/DiagramDiffContext'; import { ShareEmbedModal } from '@/components/ShareEmbedModal'; import { ImportRecoveryDialog } from '@/components/ImportRecoveryDialog'; - -const CINEMATIC_EXPORT_BACKGROUND = - 'radial-gradient(circle at top, rgba(59,130,246,0.14), transparent 42%), linear-gradient(180deg, #f8fbff 0%, #eef5ff 52%, #f8fafc 100%)'; +import { resolveCinematicExportTheme } from '@/services/export/cinematicExportTheme'; interface FlowEditorProps { onGoHome: () => void; @@ -43,6 +42,7 @@ export function FlowEditor({ onGoHome }: FlowEditorProps) { flowEditorController, t, } = useFlowEditorScreenModel({ onGoHome }); + const cinematicExportTheme = resolveCinematicExportTheme(cinematicExportState.backgroundMode); return ( + ({ }, })); +vi.mock('./ArchitectureRulesPanel', () => ({ + ArchitectureRulesPanel: () =>
, +})); + const baseProps = { commandBar: { isOpen: false, @@ -68,6 +72,7 @@ const baseProps = { onOpenStudioOpenFlow: vi.fn(), onOpenStudioMermaid: vi.fn(), onOpenStudioPlayback: vi.fn(), + onOpenArchitectureRules: vi.fn(), initialView: 'root' as const, onAddAnnotation: vi.fn(), onAddSection: vi.fn(), @@ -138,6 +143,7 @@ const baseProps = { lastAIError: null, onClearAIError: vi.fn(), chatMessages: [], + assistantThread: [], onClearChat: vi.fn(), activeTab: 'ai' as const, onTabChange: vi.fn(), @@ -160,6 +166,10 @@ const baseProps = { onPlaybackSpeedChange: vi.fn(), }, }, + architectureRules: { + isOpen: false, + onClose: vi.fn(), + }, isHistoryOpen: false, }; @@ -226,6 +236,19 @@ describe('FlowEditorPanels', () => { expect(screen.queryByTestId('properties-panel')).toBeNull(); }); + it('shows the architecture rules panel in architecture rules mode', async () => { + render( + + ); + + expect(await screen.findByTestId('architecture-rules-panel')).not.toBeNull(); + expect(screen.queryByTestId('studio-panel')).toBeNull(); + }); + it('shows the properties panel for bulk selection even without a focused primary node', async () => { render( { return { default: module.StudioPanel }; }); +const LazyArchitectureRulesPanel = lazy(async () => { + const module = await import('./ArchitectureRulesPanel'); + return { default: module.ArchitectureRulesPanel }; +}); + export interface CommandBarPanelProps { isOpen: boolean; onClose: () => void; @@ -131,6 +137,7 @@ export interface CommandBarPanelProps { onOpenStudioOpenFlow: () => void; onOpenStudioMermaid: () => void; onOpenStudioPlayback: () => void; + onOpenArchitectureRules: () => void; initialView: CommandBarView; onAddAnnotation: () => void; onAddSection: () => void; @@ -231,6 +238,7 @@ export interface StudioRailProps { selectedNodeCount: number; onViewProperties: () => void; chatMessages: ChatMessage[]; + assistantThread: AssistantThreadItem[]; onClearChat: () => void; activeTab: StudioTab; onTabChange: (tab: StudioTab) => void; @@ -258,6 +266,10 @@ export interface FlowEditorPanelsProps { snapshots: SnapshotsPanelProps; properties: PropertiesRailProps; studio: StudioRailProps; + architectureRules: { + isOpen: boolean; + onClose: () => void; + }; isHistoryOpen: boolean; editorMode: FlowEditorMode; } @@ -267,6 +279,7 @@ export function FlowEditorPanels({ snapshots, properties, studio, + architectureRules, isHistoryOpen, editorMode, }: FlowEditorPanelsProps): React.ReactElement { @@ -303,6 +316,7 @@ export function FlowEditorPanels({ onOpenStudioOpenFlow={commandBar.onOpenStudioOpenFlow} onOpenStudioMermaid={commandBar.onOpenStudioMermaid} onOpenStudioPlayback={commandBar.onOpenStudioPlayback} + onOpenArchitectureRules={commandBar.onOpenArchitectureRules} initialView={commandBar.initialView} onAddAnnotation={commandBar.onAddAnnotation} onAddSection={commandBar.onAddSection} @@ -399,6 +413,7 @@ export function FlowEditorPanels({ selectedNodeCount={studio.selectedNodeCount} onViewProperties={studio.onViewProperties} chatMessages={studio.chatMessages} + assistantThread={studio.assistantThread} onClearChat={studio.onClearChat} activeTab={studio.activeTab} onTabChange={studio.onTabChange} @@ -413,6 +428,27 @@ export function FlowEditorPanels({ ) : null} + {architectureRules.isOpen ? ( + + + + } + > + + }> + + + + + ) : null} + {showPropertiesRail ? ( ({ })); describe('HomePage integration flows', () => { + function setEmptyHomeState(): void { + useFlowStore.setState({ + documents: [], + activeDocumentId: '', + tabs: [], + activeTabId: null, + nodes: [], + edges: [], + }); + } + beforeEach(() => { localStorage.clear(); localStorage.setItem(WELCOME_MODAL_ENABLED_STORAGE_KEY, 'false'); @@ -97,17 +108,10 @@ describe('HomePage integration flows', () => { expect(screen.queryByRole('button', { name: /Product Discovery Workshop Map/i })).toBeNull(); }); - it('exposes template and flowpilot entry points in the empty dashboard state', async () => { + it('exposes template and flowpilot entry points in the empty dashboard state', async () => { const onLaunchWithTemplates = vi.fn(); const onLaunchWithAI = vi.fn(); - useFlowStore.setState({ - documents: [], - activeDocumentId: '', - tabs: [], - activeTabId: null, - nodes: [], - edges: [], - }); + setEmptyHomeState(); await renderHomePage({ onLaunchWithTemplates, onLaunchWithAI }); @@ -118,25 +122,16 @@ describe('HomePage integration flows', () => { expect(onLaunchWithAI).toHaveBeenCalledTimes(1); }); - it('shows recent onboarding action suggestions in the empty dashboard', async () => { - useFlowStore.setState({ - documents: [], - activeDocumentId: '', - tabs: [], - activeTabId: null, - nodes: [], - edges: [], - }); + it('keeps the empty dashboard focused on the primary actions', async () => { + setEmptyHomeState(); recordOnboardingEvent('welcome_prompt_selected', { source: 'welcome-modal' }); recordOnboardingEvent('welcome_template_selected', { source: 'welcome-modal' }); await renderHomePage(); - expect(screen.getByText('Continue with a recent action')).toBeTruthy(); - const suggestionPanel = screen.getByTestId('home-recent-actions'); - - expect(within(suggestionPanel).getByRole('button', { name: /Flowpilot AI/i })).toBeTruthy(); - expect(within(suggestionPanel).getByRole('button', { name: /Templates/i })).toBeTruthy(); + expect(screen.queryByText('Continue with a recent action')).toBeNull(); + expect(screen.getByTestId('home-generate-with-ai')).toBeTruthy(); + expect(screen.getByTestId('home-open-templates')).toBeTruthy(); }); it('opens persisted flows from the dashboard list', async () => { @@ -333,6 +328,6 @@ describe('HomePage integration flows', () => { expect(nodes).toHaveLength(0); expect(edges).toHaveLength(0); expect(screen.queryByText('Solo Flow')).toBeNull(); - expect(screen.getByTestId('home-create-new')).toBeTruthy(); + expect(screen.getByTestId('home-create-new-main')).toBeTruthy(); }); }); diff --git a/src/components/IconAssetNodeBody.tsx b/src/components/IconAssetNodeBody.tsx index 96a33d87..5e122be0 100644 --- a/src/components/IconAssetNodeBody.tsx +++ b/src/components/IconAssetNodeBody.tsx @@ -3,6 +3,7 @@ import { NodeChrome } from './NodeChrome'; import { InlineTextEditSurface } from './InlineTextEditSurface'; import MemoizedMarkdown from './MemoizedMarkdown'; import { NamedIcon } from './IconMap'; +import { getIconAssetNodeMinSize } from './nodeHelpers'; import { getTransformDiagnosticsAttrs } from './transformDiagnostics'; interface InlineEditLike { @@ -33,6 +34,9 @@ function toCssSize(value: number | string | undefined): string | undefined { return typeof value === 'number' ? `${value}px` : value; } +const ICON_FRAME_SIZE = 72; +const ICON_HANDLE_STYLE_EXTRAS = { left: { top: 42 }, right: { top: 42 } }; + /** Renders the compact icon-first presentation used for architecture asset nodes. */ export function IconAssetNodeBody({ nodeId, @@ -47,70 +51,65 @@ export function IconAssetNodeBody({ isActiveSelected, labelEdit, }: IconAssetNodeBodyProps): React.ReactElement { - const iconFrameSize = 72; - const iconHandleStyleExtras = { left: { top: 42 }, right: { top: 42 } }; - const minWidth = hasLabel ? 116 : 96; - const minHeight = hasLabel ? 118 : 88; + const { minWidth, minHeight } = getIconAssetNodeMinSize(hasLabel); return ( - <> - +
-
- {resolvedAssetIconUrl ? ( - {typeof - ) : activeIconKey ? ( -
- -
- ) : null} -
- {hasLabel ? ( - } - onBeginEdit={labelEdit.beginEdit} - onDraftChange={labelEdit.setDraft} - onCommit={labelEdit.commit} - onKeyDown={labelEdit.handleKeyDown} - className="block max-w-full break-words px-1 text-center text-sm font-semibold leading-tight markdown-content [&_p]:m-0" - style={{ color: 'var(--brand-text)' }} - inputMode="multiline" - inputClassName="text-center" - isSelected={isActiveSelected} + {resolvedAssetIconUrl ? ( + {typeof + ) : activeIconKey ? ( +
+ +
) : null}
- - + {hasLabel ? ( + } + onBeginEdit={labelEdit.beginEdit} + onDraftChange={labelEdit.setDraft} + onCommit={labelEdit.commit} + onKeyDown={labelEdit.handleKeyDown} + className="block max-w-full break-words px-1 text-center text-sm font-semibold leading-tight markdown-content [&_p]:m-0" + style={{ color: 'var(--brand-text)' }} + inputMode="multiline" + inputClassName="text-center" + isSelected={isActiveSelected} + /> + ) : null} +
+
); } diff --git a/src/components/SidebarShell.tsx b/src/components/SidebarShell.tsx index 6a4ff4ed..4c700a82 100644 --- a/src/components/SidebarShell.tsx +++ b/src/components/SidebarShell.tsx @@ -6,6 +6,7 @@ interface SidebarTabItem { id: string; label: string; icon?: React.ReactNode; + badge?: string; } interface SidebarShellProps { @@ -107,7 +108,7 @@ export function SidebarSegmentedTabs({ }: SidebarSegmentedTabsProps): React.ReactElement { return (
- {tabs.map(({ id, label, icon }) => ( + {tabs.map(({ id, label, icon, badge }) => ( ))}
diff --git a/src/components/StudioAIPanel.test.tsx b/src/components/StudioAIPanel.test.tsx index bff0226d..28b3954f 100644 --- a/src/components/StudioAIPanel.test.tsx +++ b/src/components/StudioAIPanel.test.tsx @@ -57,6 +57,7 @@ describe('StudioAIPanel', () => { lastError={null} onClearError={vi.fn()} chatMessages={[]} + assistantThread={[]} onClearChat={vi.fn()} nodeCount={0} selectedNodeCount={0} @@ -91,6 +92,7 @@ describe('StudioAIPanel', () => { lastError={null} onClearError={vi.fn()} chatMessages={[]} + assistantThread={[]} onClearChat={vi.fn()} nodeCount={3} selectedNodeCount={0} @@ -129,6 +131,7 @@ describe('StudioAIPanel', () => { lastError="The provider rejected this request." onClearError={onClearError} chatMessages={[]} + assistantThread={[]} onClearChat={vi.fn()} nodeCount={1} selectedNodeCount={0} @@ -163,6 +166,7 @@ describe('StudioAIPanel', () => { lastError="The provider rejected this request." onClearError={onClearError} chatMessages={[]} + assistantThread={[]} onClearChat={vi.fn()} nodeCount={1} selectedNodeCount={0} @@ -204,6 +208,7 @@ describe('StudioAIPanel', () => { lastError="Failed to fetch" onClearError={vi.fn()} chatMessages={[]} + assistantThread={[]} onClearChat={vi.fn()} nodeCount={1} selectedNodeCount={0} @@ -240,6 +245,7 @@ describe('StudioAIPanel', () => { lastError={null} onClearError={vi.fn()} chatMessages={[]} + assistantThread={[]} onClearChat={vi.fn()} nodeCount={0} selectedNodeCount={0} @@ -274,6 +280,7 @@ describe('StudioAIPanel', () => { lastError={null} onClearError={vi.fn()} chatMessages={[]} + assistantThread={[]} onClearChat={vi.fn()} nodeCount={3} selectedNodeCount={0} @@ -317,6 +324,7 @@ describe('StudioAIPanel', () => { lastError={null} onClearError={vi.fn()} chatMessages={[]} + assistantThread={[]} onClearChat={vi.fn()} nodeCount={0} selectedNodeCount={0} @@ -334,4 +342,55 @@ describe('StudioAIPanel', () => { expect(screen.getByText('Platform: aws')).toBeInTheDocument(); expect(screen.getByText('4 native sections')).toBeInTheDocument(); }); + + it('renders assistant plan cards from the richer thread model', () => { + render( + + ); + + expect(screen.getByText('Plan')).toBeInTheDocument(); + expect(screen.getByText('Mode: plan')).toBeInTheDocument(); + expect(screen.getByText('1. Inspect the current diagram')).toBeInTheDocument(); + }); }); diff --git a/src/components/StudioAIPanel.tsx b/src/components/StudioAIPanel.tsx index 3292cbf5..03c98ff8 100644 --- a/src/components/StudioAIPanel.tsx +++ b/src/components/StudioAIPanel.tsx @@ -6,6 +6,7 @@ import { import { useTranslation } from 'react-i18next'; import { IS_BEVELED } from '@/lib/brand'; import type { ChatMessage } from '@/services/aiService'; +import type { AssistantThreadItem } from '@/services/flowpilot/types'; import type { ImportDiff } from '@/hooks/useAIGeneration'; import type { AIReadinessState } from '@/hooks/ai-generation/readiness'; import { useAIViewState } from './command-bar/useAIViewState'; @@ -38,6 +39,7 @@ interface StudioAIPanelProps { lastError: string | null; onClearError: () => void; chatMessages: ChatMessage[]; + assistantThread: AssistantThreadItem[]; onClearChat: () => void; nodeCount?: number; selectedNodeCount?: number; @@ -124,6 +126,7 @@ export function StudioAIPanel({ lastError, onClearError, chatMessages, + assistantThread, onClearChat, nodeCount = 0, selectedNodeCount = 0, @@ -151,7 +154,7 @@ export function StudioAIPanel({ isGenerating, onAIGenerate, onClose: () => undefined, - chatMessageCount: chatMessages.length, + chatMessageCount: assistantThread.length, }); useEffect(() => { @@ -161,7 +164,7 @@ export function StudioAIPanel({ } }, [initialPrompt, onInitialPromptConsumed, setPrompt]); - const hasHistory = chatMessages.length > 0; + const hasHistory = assistantThread.length > 0; const isCanvasEmpty = nodeCount === 0; const effectiveGenerationMode: AIGenerationMode = nodeCount === 0 ? 'create' : generationMode; const examplePrompts = isCanvasEmpty ? EMPTY_CANVAS_EXAMPLES : ITERATION_EXAMPLES; @@ -206,9 +209,10 @@ export function StudioAIPanel({ t={t} /> ) : null} - + {item.assetMatches.slice(0, 4).map((match) => ( + + {match.label} + + ))} +
+ ); +} + +function renderThreadPreview(item: AssistantThreadItem): ReactElement | null { + if (!item.previewTitle) { + return null; + } + + return ( +
+
{item.previewTitle}
+ {item.previewDetail ? ( +
+ {item.previewDetail} +
+ ) : null} + {item.previewStats && item.previewStats.length > 0 ? ( +
+ {item.previewStats.map((stat) => ( + + {stat} + + ))} +
+ ) : null} +
+ ); +} + +function renderPlanContent(item: AssistantThreadItem): ReactElement { + return ( +
+
+ + Plan +
+
{item.plan?.reasoningSummary}
+
+ + Mode: {item.plan?.mode.replaceAll('_', ' ')} + + + Confidence: {Math.round((item.plan?.confidence ?? 0) * 100)}% + +
+
+ {item.plan?.steps.map((step, index) => ( +
{index + 1}. {step}
+ ))} +
+
+ ); +} + +function renderThreadContent(item: AssistantThreadItem): ReactElement { + if (item.role !== 'user' && item.type === 'assistant_plan' && item.plan) { + return renderPlanContent(item); + } + + return
{item.content}
; +} + +function getStreamingStatusCopy( + streamingText: string | null, + retryCount: number, + chatMessageCount: number, + t: TranslateFn +): string { + if (retryCount > 0) { + return t('commandBar.aiStudio.retrying', { + retryCount, + defaultValue: 'Retrying ({{retryCount}} of 3)...', + }); + } + + if (chatMessageCount > 0) { + return 'Inspecting the canvas, grounding assets, and preparing the next step.'; + } + + if (streamingText) { + return 'Understanding the request and drafting the response.'; + } + + return 'Understanding the request and deciding whether to answer in chat or prepare a canvas preview.'; +} + +function renderThreadItem(item: AssistantThreadItem): ReactElement { + const isUser = item.role === 'user'; + + return ( +
+
+ {renderThreadContent(item)} + {isUser ? null : renderThreadAssetMatches(item)} + {isUser ? null : renderThreadPreview(item)} +
+
+ ); +} + export function ChatHistoryView({ hasHistory, chatMessages, + assistantThread, isGenerating, streamingText, retryCount, @@ -161,40 +293,20 @@ export function ChatHistoryView({ ref={scrollRef} className="min-h-0 flex-1 space-y-4 overflow-y-auto px-1 py-4 custom-scrollbar" > - {chatMessages.map((msg, idx) => ( -
-
- {msg.parts.map((part, index) => ( -
- {part.text} -
- ))} -
-
- ))} + {assistantThread.map((item) => renderThreadItem(item))} {isGenerating ? (
+
+ + {streamingText ? 'Drafting response' : 'Thinking'} +
+
+ {getStreamingStatusCopy(streamingText, retryCount, chatMessages.length, t)} +
{streamingText ? ( - {streamingText} - ) : ( - - - {retryCount > 0 - ? t('commandBar.aiStudio.retrying', { - retryCount, - defaultValue: 'Retrying ({{retryCount}} of 3)...', - }) - : t('commandBar.aiStudio.generating', 'Generating...')} - - )} +
{streamingText}
+ ) : null}
) : null} @@ -473,7 +585,7 @@ export function ComposerSection({
) : null} -
+