openpencil/server/utils/codex-client.ts

252 lines
6.2 KiB
TypeScript
Raw Normal View History

import { spawn } from 'node:child_process'
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
type ThinkingMode = 'adaptive' | 'disabled' | 'enabled'
type ThinkingEffort = 'low' | 'medium' | 'high' | 'max'
interface CodexExecOptions {
model?: string
systemPrompt?: string
thinkingMode?: ThinkingMode
thinkingBudgetTokens?: number
effort?: ThinkingEffort
timeoutMs?: number
V0.1.0 (#13) * feat(editor): implement Figma import functionality - Added a new FigmaImportDialog component for importing .fig files. - Integrated file processing and conversion logic to handle Figma files. - Enhanced the editor layout to include a button for opening the Figma import dialog. - Introduced page tabs for better navigation between document pages. - Updated various components to utilize active page children for rendering and interactions. - Added new dependencies: fzstd and kiwi-schema for improved functionality. * fix(ai): harden Claude agent env variable resolution - Filter empty strings and object values from settings.json env - Validate ANTHROPIC_CUSTOM_HEADERS is valid JSON before passing - Remove incorrect ANTHROPIC_CUSTOM_HEADERS auto-generation from auth token * refactor(ai): remove silent fallback logic, surface errors in AI panel - Remove direct-stream fallback in generateDesign, delegate to orchestrator - Remove orchestrator planner fallback plan (buildFallbackPlanFromPrompt) - Remove sub-agent retry logic, minimal mode retry, and placeholder insertion - Throw on sub-agent error instead of silently degrading - Call updateLastMessage in catch block so errors display immediately - Clean up unused imports, functions, and console.log/warn statements * fix(ai): add 30s API connect timeout for faster error detection - Set maxRetries: 0 on Anthropic SDK to prevent retry delays on 429 - Add AbortController-based 30s timeout for initial API connection - If the provider doesn't respond within 30s, abort and send SSE error - Refactor streamViaAnthropicSDK with shared send() helper * fix(editor): improve Figma import visual fidelity - Extract image files from .fig ZIP archive and resolve by SHA-1 hash - Map Figma textCase property (UPPER/LOWER/TITLE) to text content - Convert ellipse nodes with image fills to image nodes - Decode Figma vector paths and match icons via Lucide icon registry - Fix z-order: move background rectangles behind content layers - Set clipContent=true for Figma frames (default clip behavior) - Unwrap single-artboard pages to use artboard as root frame * fix(canvas): preserve clipPath during async image load and invalidate cache - Copy clipPath from placeholder rect to FabricImage in onload callback - Set obj.dirty=true after applying/removing clipPath so Fabric.js re-renders with the updated clip region instead of using stale cache * fix(ai): use page-aware empty frame detection and place new designs as siblings - Fix isCanvasOnlyEmptyFrame() to use getActivePageChildren() instead of document.children which is empty after page migration - When canvas is empty, replace default frame (existing behavior restored) - When canvas has content, add generated frame as top-level sibling positioned to the right with 100px gap - Track generationRootFrameId so height adjustment targets correct frame - Add WIDTH SELECTION rule: app screens use 375x812 mobile dimensions - Include selected node dimensions in AI context string * feat(electron): implement application menu with native actions - Added a native application menu for macOS and Windows, including File, Edit, View, and Window menus. - Integrated menu actions for creating, opening, saving files, and editing operations like undo/redo. - Enhanced the editor layout to handle Electron menu actions through a custom hook. - Updated icon components in the toolbar and top bar for consistent styling. - Refactored padding and layout components for improved structure and readability. * fix(canvas): use page-aware children for selection depth resolution getSelectableNodeIds() used doc.children which is empty under the pages architecture. Switch to getActivePageChildren() so canvas selection correctly syncs to the layers panel. * fix(panels): adjust property panel sections for image nodes Hide fill/stroke sections (not applicable to images) and show corner radius section for image elements. * feat(canvas): implement image corner radius via clipPath FabricImage does not support rx/ry natively. Use a rounded-rect clipPath instead, with objectCaching disabled to ensure immediate re-render on radius changes. * fix(canvas): use page-aware forcePageResync for canvas re-sync The old pattern { ...doc, children: [...doc.children] } only touched root-level children which are empty under pages architecture. Extract forcePageResync() into canvas-sync-utils and replace all 7 occurrences across canvas-object-modified, drag-into-layout, and layout-reorder. * fix(canvas): preserve image corner radius clipPath during drag Three issues caused corner radius loss on image drag: 1. Scale baking in object:modified changed FabricImage width from natural to display dimensions, creating a clipPath size mismatch. Exclude images from scale baking (like paths/polygons). 2. object:moving unconditionally cleared all clipPaths to avoid stale frame clipping. Now only clears absolutePositioned (frame) clips, preserving object-level clips like image corner radius. 3. Post-drag re-sync overwrote the corner radius clipPath with the parent frame clip. Now skips frame clip when the object already has its own non-absolutePositioned clipPath. * feat(ai): enhance chat functionality with image attachments support - Added support for image attachments in chat messages, allowing users to send images alongside text. - Implemented temporary file handling for attachments, ensuring they are accessible to the AI processing. - Updated chat message structure to include attachments, and modified the prompt building logic to reference these images. - Enhanced the chat panel UI to allow users to select and preview images before sending. - Introduced a new state management for pending attachments in the AI store. - Refactored related components to accommodate the new attachment feature, improving user experience and interaction. * feat(multi-page): introduce multi-page architecture and enhance agent settings UI - Added multi-page architecture to the document structure, allowing for page CRUD actions such as add, remove, rename, reorder, and duplicate. - Implemented `activePageId` state management in the canvas store for better page handling. - Enhanced the top bar with an `AgentStatusButton` to display connected AI providers and their statuses. - Updated the agent settings dialog to improve the UI for managing AI integrations, including better layout and connection indicators. - Refactored related components for improved usability and visual consistency. * docs(README): add contributors section with dynamic contributor image * feat(ai): improve chat streaming and error handling - Added functionality to hide the checklist when streaming stops with no completed items. - Implemented abort controller management to handle user-initiated stops during chat generation. - Enhanced error handling to prevent displaying errors for user-initiated stops while preserving partial content. - Updated orchestration and design generation functions to support abort signals, improving responsiveness during streaming. * fix(canvas): align z-order with layer panel convention (top = front) Establish consistent z-order: children[0] = top of layer panel = frontmost on canvas. This matches the standard design tool convention (Figma, Sketch). - Reverse flattenNodes iteration so children[0] is added last to canvas - Add z-order reconciliation via moveObjectTo after each sync pass - Figma import: sort children descending so front items are at children[0] - addNode defaults to prepend (index 0) so new items appear at top - duplicateNode inserts clone above original (at idx instead of idx+1) - drag-reparent inserts at index 0 (frontmost) instead of childCount - Async image loading preserves z-order via insertAt instead of add - Add FigmaImportLayoutMode toggle (preserve vs openpencil auto-layout) * chore: resolve .gitignore merge conflict * fix(canvas): clamp corner radius to half the element height * chore: update version to 0.1.0 and modify mac artifact naming in electron-builder configuration --------- Co-authored-by: Fini <fini.yang@gmail.com>
2026-03-01 01:33:52 +00:00
/** Paths to temporary image files to reference in the prompt */
imageFiles?: string[]
}
interface CodexCliResult {
text?: string
error?: string
}
const DEFAULT_CODEX_TIMEOUT_MS = 15 * 60 * 1000
export async function runCodexExec(
userPrompt: string,
options: CodexExecOptions = {},
): Promise<CodexCliResult> {
const tempDir = await mkdtemp(join(tmpdir(), 'openpencil-codex-'))
const outputPath = join(tempDir, 'last-message.txt')
V0.1.0 (#13) * feat(editor): implement Figma import functionality - Added a new FigmaImportDialog component for importing .fig files. - Integrated file processing and conversion logic to handle Figma files. - Enhanced the editor layout to include a button for opening the Figma import dialog. - Introduced page tabs for better navigation between document pages. - Updated various components to utilize active page children for rendering and interactions. - Added new dependencies: fzstd and kiwi-schema for improved functionality. * fix(ai): harden Claude agent env variable resolution - Filter empty strings and object values from settings.json env - Validate ANTHROPIC_CUSTOM_HEADERS is valid JSON before passing - Remove incorrect ANTHROPIC_CUSTOM_HEADERS auto-generation from auth token * refactor(ai): remove silent fallback logic, surface errors in AI panel - Remove direct-stream fallback in generateDesign, delegate to orchestrator - Remove orchestrator planner fallback plan (buildFallbackPlanFromPrompt) - Remove sub-agent retry logic, minimal mode retry, and placeholder insertion - Throw on sub-agent error instead of silently degrading - Call updateLastMessage in catch block so errors display immediately - Clean up unused imports, functions, and console.log/warn statements * fix(ai): add 30s API connect timeout for faster error detection - Set maxRetries: 0 on Anthropic SDK to prevent retry delays on 429 - Add AbortController-based 30s timeout for initial API connection - If the provider doesn't respond within 30s, abort and send SSE error - Refactor streamViaAnthropicSDK with shared send() helper * fix(editor): improve Figma import visual fidelity - Extract image files from .fig ZIP archive and resolve by SHA-1 hash - Map Figma textCase property (UPPER/LOWER/TITLE) to text content - Convert ellipse nodes with image fills to image nodes - Decode Figma vector paths and match icons via Lucide icon registry - Fix z-order: move background rectangles behind content layers - Set clipContent=true for Figma frames (default clip behavior) - Unwrap single-artboard pages to use artboard as root frame * fix(canvas): preserve clipPath during async image load and invalidate cache - Copy clipPath from placeholder rect to FabricImage in onload callback - Set obj.dirty=true after applying/removing clipPath so Fabric.js re-renders with the updated clip region instead of using stale cache * fix(ai): use page-aware empty frame detection and place new designs as siblings - Fix isCanvasOnlyEmptyFrame() to use getActivePageChildren() instead of document.children which is empty after page migration - When canvas is empty, replace default frame (existing behavior restored) - When canvas has content, add generated frame as top-level sibling positioned to the right with 100px gap - Track generationRootFrameId so height adjustment targets correct frame - Add WIDTH SELECTION rule: app screens use 375x812 mobile dimensions - Include selected node dimensions in AI context string * feat(electron): implement application menu with native actions - Added a native application menu for macOS and Windows, including File, Edit, View, and Window menus. - Integrated menu actions for creating, opening, saving files, and editing operations like undo/redo. - Enhanced the editor layout to handle Electron menu actions through a custom hook. - Updated icon components in the toolbar and top bar for consistent styling. - Refactored padding and layout components for improved structure and readability. * fix(canvas): use page-aware children for selection depth resolution getSelectableNodeIds() used doc.children which is empty under the pages architecture. Switch to getActivePageChildren() so canvas selection correctly syncs to the layers panel. * fix(panels): adjust property panel sections for image nodes Hide fill/stroke sections (not applicable to images) and show corner radius section for image elements. * feat(canvas): implement image corner radius via clipPath FabricImage does not support rx/ry natively. Use a rounded-rect clipPath instead, with objectCaching disabled to ensure immediate re-render on radius changes. * fix(canvas): use page-aware forcePageResync for canvas re-sync The old pattern { ...doc, children: [...doc.children] } only touched root-level children which are empty under pages architecture. Extract forcePageResync() into canvas-sync-utils and replace all 7 occurrences across canvas-object-modified, drag-into-layout, and layout-reorder. * fix(canvas): preserve image corner radius clipPath during drag Three issues caused corner radius loss on image drag: 1. Scale baking in object:modified changed FabricImage width from natural to display dimensions, creating a clipPath size mismatch. Exclude images from scale baking (like paths/polygons). 2. object:moving unconditionally cleared all clipPaths to avoid stale frame clipping. Now only clears absolutePositioned (frame) clips, preserving object-level clips like image corner radius. 3. Post-drag re-sync overwrote the corner radius clipPath with the parent frame clip. Now skips frame clip when the object already has its own non-absolutePositioned clipPath. * feat(ai): enhance chat functionality with image attachments support - Added support for image attachments in chat messages, allowing users to send images alongside text. - Implemented temporary file handling for attachments, ensuring they are accessible to the AI processing. - Updated chat message structure to include attachments, and modified the prompt building logic to reference these images. - Enhanced the chat panel UI to allow users to select and preview images before sending. - Introduced a new state management for pending attachments in the AI store. - Refactored related components to accommodate the new attachment feature, improving user experience and interaction. * feat(multi-page): introduce multi-page architecture and enhance agent settings UI - Added multi-page architecture to the document structure, allowing for page CRUD actions such as add, remove, rename, reorder, and duplicate. - Implemented `activePageId` state management in the canvas store for better page handling. - Enhanced the top bar with an `AgentStatusButton` to display connected AI providers and their statuses. - Updated the agent settings dialog to improve the UI for managing AI integrations, including better layout and connection indicators. - Refactored related components for improved usability and visual consistency. * docs(README): add contributors section with dynamic contributor image * feat(ai): improve chat streaming and error handling - Added functionality to hide the checklist when streaming stops with no completed items. - Implemented abort controller management to handle user-initiated stops during chat generation. - Enhanced error handling to prevent displaying errors for user-initiated stops while preserving partial content. - Updated orchestration and design generation functions to support abort signals, improving responsiveness during streaming. * fix(canvas): align z-order with layer panel convention (top = front) Establish consistent z-order: children[0] = top of layer panel = frontmost on canvas. This matches the standard design tool convention (Figma, Sketch). - Reverse flattenNodes iteration so children[0] is added last to canvas - Add z-order reconciliation via moveObjectTo after each sync pass - Figma import: sort children descending so front items are at children[0] - addNode defaults to prepend (index 0) so new items appear at top - duplicateNode inserts clone above original (at idx instead of idx+1) - drag-reparent inserts at index 0 (frontmost) instead of childCount - Async image loading preserves z-order via insertAt instead of add - Add FigmaImportLayoutMode toggle (preserve vs openpencil auto-layout) * chore: resolve .gitignore merge conflict * fix(canvas): clamp corner radius to half the element height * chore: update version to 0.1.0 and modify mac artifact naming in electron-builder configuration --------- Co-authored-by: Fini <fini.yang@gmail.com>
2026-03-01 01:33:52 +00:00
const prompt = buildPrompt(options.systemPrompt, userPrompt, options.imageFiles)
const codexEffort = resolveCodexEffort(options.thinkingMode, options.effort)
const args = [
'exec',
'--json',
'--skip-git-repo-check',
'--sandbox',
'read-only',
'--output-last-message',
outputPath,
]
if (options.model) {
args.push('--model', options.model)
}
if (codexEffort) {
args.push('--config', `model_reasoning_effort="${codexEffort}"`)
}
args.push(prompt)
try {
const runResult = await executeCodexCommand(
args,
options.timeoutMs ?? DEFAULT_CODEX_TIMEOUT_MS,
)
const finalText = await readFile(outputPath, 'utf-8').catch(() => '')
const normalizedText = finalText.trim() || runResult.text.trim()
if (normalizedText) {
return { text: normalizedText }
}
if (runResult.errors.length > 0) {
return { error: runResult.errors.join('; ') }
}
return { error: 'Codex returned no output.' }
} catch (error) {
return { error: error instanceof Error ? error.message : 'Codex execution failed' }
} finally {
await rm(tempDir, { recursive: true, force: true }).catch(() => {})
}
}
V0.1.0 (#13) * feat(editor): implement Figma import functionality - Added a new FigmaImportDialog component for importing .fig files. - Integrated file processing and conversion logic to handle Figma files. - Enhanced the editor layout to include a button for opening the Figma import dialog. - Introduced page tabs for better navigation between document pages. - Updated various components to utilize active page children for rendering and interactions. - Added new dependencies: fzstd and kiwi-schema for improved functionality. * fix(ai): harden Claude agent env variable resolution - Filter empty strings and object values from settings.json env - Validate ANTHROPIC_CUSTOM_HEADERS is valid JSON before passing - Remove incorrect ANTHROPIC_CUSTOM_HEADERS auto-generation from auth token * refactor(ai): remove silent fallback logic, surface errors in AI panel - Remove direct-stream fallback in generateDesign, delegate to orchestrator - Remove orchestrator planner fallback plan (buildFallbackPlanFromPrompt) - Remove sub-agent retry logic, minimal mode retry, and placeholder insertion - Throw on sub-agent error instead of silently degrading - Call updateLastMessage in catch block so errors display immediately - Clean up unused imports, functions, and console.log/warn statements * fix(ai): add 30s API connect timeout for faster error detection - Set maxRetries: 0 on Anthropic SDK to prevent retry delays on 429 - Add AbortController-based 30s timeout for initial API connection - If the provider doesn't respond within 30s, abort and send SSE error - Refactor streamViaAnthropicSDK with shared send() helper * fix(editor): improve Figma import visual fidelity - Extract image files from .fig ZIP archive and resolve by SHA-1 hash - Map Figma textCase property (UPPER/LOWER/TITLE) to text content - Convert ellipse nodes with image fills to image nodes - Decode Figma vector paths and match icons via Lucide icon registry - Fix z-order: move background rectangles behind content layers - Set clipContent=true for Figma frames (default clip behavior) - Unwrap single-artboard pages to use artboard as root frame * fix(canvas): preserve clipPath during async image load and invalidate cache - Copy clipPath from placeholder rect to FabricImage in onload callback - Set obj.dirty=true after applying/removing clipPath so Fabric.js re-renders with the updated clip region instead of using stale cache * fix(ai): use page-aware empty frame detection and place new designs as siblings - Fix isCanvasOnlyEmptyFrame() to use getActivePageChildren() instead of document.children which is empty after page migration - When canvas is empty, replace default frame (existing behavior restored) - When canvas has content, add generated frame as top-level sibling positioned to the right with 100px gap - Track generationRootFrameId so height adjustment targets correct frame - Add WIDTH SELECTION rule: app screens use 375x812 mobile dimensions - Include selected node dimensions in AI context string * feat(electron): implement application menu with native actions - Added a native application menu for macOS and Windows, including File, Edit, View, and Window menus. - Integrated menu actions for creating, opening, saving files, and editing operations like undo/redo. - Enhanced the editor layout to handle Electron menu actions through a custom hook. - Updated icon components in the toolbar and top bar for consistent styling. - Refactored padding and layout components for improved structure and readability. * fix(canvas): use page-aware children for selection depth resolution getSelectableNodeIds() used doc.children which is empty under the pages architecture. Switch to getActivePageChildren() so canvas selection correctly syncs to the layers panel. * fix(panels): adjust property panel sections for image nodes Hide fill/stroke sections (not applicable to images) and show corner radius section for image elements. * feat(canvas): implement image corner radius via clipPath FabricImage does not support rx/ry natively. Use a rounded-rect clipPath instead, with objectCaching disabled to ensure immediate re-render on radius changes. * fix(canvas): use page-aware forcePageResync for canvas re-sync The old pattern { ...doc, children: [...doc.children] } only touched root-level children which are empty under pages architecture. Extract forcePageResync() into canvas-sync-utils and replace all 7 occurrences across canvas-object-modified, drag-into-layout, and layout-reorder. * fix(canvas): preserve image corner radius clipPath during drag Three issues caused corner radius loss on image drag: 1. Scale baking in object:modified changed FabricImage width from natural to display dimensions, creating a clipPath size mismatch. Exclude images from scale baking (like paths/polygons). 2. object:moving unconditionally cleared all clipPaths to avoid stale frame clipping. Now only clears absolutePositioned (frame) clips, preserving object-level clips like image corner radius. 3. Post-drag re-sync overwrote the corner radius clipPath with the parent frame clip. Now skips frame clip when the object already has its own non-absolutePositioned clipPath. * feat(ai): enhance chat functionality with image attachments support - Added support for image attachments in chat messages, allowing users to send images alongside text. - Implemented temporary file handling for attachments, ensuring they are accessible to the AI processing. - Updated chat message structure to include attachments, and modified the prompt building logic to reference these images. - Enhanced the chat panel UI to allow users to select and preview images before sending. - Introduced a new state management for pending attachments in the AI store. - Refactored related components to accommodate the new attachment feature, improving user experience and interaction. * feat(multi-page): introduce multi-page architecture and enhance agent settings UI - Added multi-page architecture to the document structure, allowing for page CRUD actions such as add, remove, rename, reorder, and duplicate. - Implemented `activePageId` state management in the canvas store for better page handling. - Enhanced the top bar with an `AgentStatusButton` to display connected AI providers and their statuses. - Updated the agent settings dialog to improve the UI for managing AI integrations, including better layout and connection indicators. - Refactored related components for improved usability and visual consistency. * docs(README): add contributors section with dynamic contributor image * feat(ai): improve chat streaming and error handling - Added functionality to hide the checklist when streaming stops with no completed items. - Implemented abort controller management to handle user-initiated stops during chat generation. - Enhanced error handling to prevent displaying errors for user-initiated stops while preserving partial content. - Updated orchestration and design generation functions to support abort signals, improving responsiveness during streaming. * fix(canvas): align z-order with layer panel convention (top = front) Establish consistent z-order: children[0] = top of layer panel = frontmost on canvas. This matches the standard design tool convention (Figma, Sketch). - Reverse flattenNodes iteration so children[0] is added last to canvas - Add z-order reconciliation via moveObjectTo after each sync pass - Figma import: sort children descending so front items are at children[0] - addNode defaults to prepend (index 0) so new items appear at top - duplicateNode inserts clone above original (at idx instead of idx+1) - drag-reparent inserts at index 0 (frontmost) instead of childCount - Async image loading preserves z-order via insertAt instead of add - Add FigmaImportLayoutMode toggle (preserve vs openpencil auto-layout) * chore: resolve .gitignore merge conflict * fix(canvas): clamp corner radius to half the element height * chore: update version to 0.1.0 and modify mac artifact naming in electron-builder configuration --------- Co-authored-by: Fini <fini.yang@gmail.com>
2026-03-01 01:33:52 +00:00
function buildPrompt(systemPrompt: string | undefined, userPrompt: string, imageFiles?: string[]): string {
const userText = userPrompt.trim()
V0.1.0 (#13) * feat(editor): implement Figma import functionality - Added a new FigmaImportDialog component for importing .fig files. - Integrated file processing and conversion logic to handle Figma files. - Enhanced the editor layout to include a button for opening the Figma import dialog. - Introduced page tabs for better navigation between document pages. - Updated various components to utilize active page children for rendering and interactions. - Added new dependencies: fzstd and kiwi-schema for improved functionality. * fix(ai): harden Claude agent env variable resolution - Filter empty strings and object values from settings.json env - Validate ANTHROPIC_CUSTOM_HEADERS is valid JSON before passing - Remove incorrect ANTHROPIC_CUSTOM_HEADERS auto-generation from auth token * refactor(ai): remove silent fallback logic, surface errors in AI panel - Remove direct-stream fallback in generateDesign, delegate to orchestrator - Remove orchestrator planner fallback plan (buildFallbackPlanFromPrompt) - Remove sub-agent retry logic, minimal mode retry, and placeholder insertion - Throw on sub-agent error instead of silently degrading - Call updateLastMessage in catch block so errors display immediately - Clean up unused imports, functions, and console.log/warn statements * fix(ai): add 30s API connect timeout for faster error detection - Set maxRetries: 0 on Anthropic SDK to prevent retry delays on 429 - Add AbortController-based 30s timeout for initial API connection - If the provider doesn't respond within 30s, abort and send SSE error - Refactor streamViaAnthropicSDK with shared send() helper * fix(editor): improve Figma import visual fidelity - Extract image files from .fig ZIP archive and resolve by SHA-1 hash - Map Figma textCase property (UPPER/LOWER/TITLE) to text content - Convert ellipse nodes with image fills to image nodes - Decode Figma vector paths and match icons via Lucide icon registry - Fix z-order: move background rectangles behind content layers - Set clipContent=true for Figma frames (default clip behavior) - Unwrap single-artboard pages to use artboard as root frame * fix(canvas): preserve clipPath during async image load and invalidate cache - Copy clipPath from placeholder rect to FabricImage in onload callback - Set obj.dirty=true after applying/removing clipPath so Fabric.js re-renders with the updated clip region instead of using stale cache * fix(ai): use page-aware empty frame detection and place new designs as siblings - Fix isCanvasOnlyEmptyFrame() to use getActivePageChildren() instead of document.children which is empty after page migration - When canvas is empty, replace default frame (existing behavior restored) - When canvas has content, add generated frame as top-level sibling positioned to the right with 100px gap - Track generationRootFrameId so height adjustment targets correct frame - Add WIDTH SELECTION rule: app screens use 375x812 mobile dimensions - Include selected node dimensions in AI context string * feat(electron): implement application menu with native actions - Added a native application menu for macOS and Windows, including File, Edit, View, and Window menus. - Integrated menu actions for creating, opening, saving files, and editing operations like undo/redo. - Enhanced the editor layout to handle Electron menu actions through a custom hook. - Updated icon components in the toolbar and top bar for consistent styling. - Refactored padding and layout components for improved structure and readability. * fix(canvas): use page-aware children for selection depth resolution getSelectableNodeIds() used doc.children which is empty under the pages architecture. Switch to getActivePageChildren() so canvas selection correctly syncs to the layers panel. * fix(panels): adjust property panel sections for image nodes Hide fill/stroke sections (not applicable to images) and show corner radius section for image elements. * feat(canvas): implement image corner radius via clipPath FabricImage does not support rx/ry natively. Use a rounded-rect clipPath instead, with objectCaching disabled to ensure immediate re-render on radius changes. * fix(canvas): use page-aware forcePageResync for canvas re-sync The old pattern { ...doc, children: [...doc.children] } only touched root-level children which are empty under pages architecture. Extract forcePageResync() into canvas-sync-utils and replace all 7 occurrences across canvas-object-modified, drag-into-layout, and layout-reorder. * fix(canvas): preserve image corner radius clipPath during drag Three issues caused corner radius loss on image drag: 1. Scale baking in object:modified changed FabricImage width from natural to display dimensions, creating a clipPath size mismatch. Exclude images from scale baking (like paths/polygons). 2. object:moving unconditionally cleared all clipPaths to avoid stale frame clipping. Now only clears absolutePositioned (frame) clips, preserving object-level clips like image corner radius. 3. Post-drag re-sync overwrote the corner radius clipPath with the parent frame clip. Now skips frame clip when the object already has its own non-absolutePositioned clipPath. * feat(ai): enhance chat functionality with image attachments support - Added support for image attachments in chat messages, allowing users to send images alongside text. - Implemented temporary file handling for attachments, ensuring they are accessible to the AI processing. - Updated chat message structure to include attachments, and modified the prompt building logic to reference these images. - Enhanced the chat panel UI to allow users to select and preview images before sending. - Introduced a new state management for pending attachments in the AI store. - Refactored related components to accommodate the new attachment feature, improving user experience and interaction. * feat(multi-page): introduce multi-page architecture and enhance agent settings UI - Added multi-page architecture to the document structure, allowing for page CRUD actions such as add, remove, rename, reorder, and duplicate. - Implemented `activePageId` state management in the canvas store for better page handling. - Enhanced the top bar with an `AgentStatusButton` to display connected AI providers and their statuses. - Updated the agent settings dialog to improve the UI for managing AI integrations, including better layout and connection indicators. - Refactored related components for improved usability and visual consistency. * docs(README): add contributors section with dynamic contributor image * feat(ai): improve chat streaming and error handling - Added functionality to hide the checklist when streaming stops with no completed items. - Implemented abort controller management to handle user-initiated stops during chat generation. - Enhanced error handling to prevent displaying errors for user-initiated stops while preserving partial content. - Updated orchestration and design generation functions to support abort signals, improving responsiveness during streaming. * fix(canvas): align z-order with layer panel convention (top = front) Establish consistent z-order: children[0] = top of layer panel = frontmost on canvas. This matches the standard design tool convention (Figma, Sketch). - Reverse flattenNodes iteration so children[0] is added last to canvas - Add z-order reconciliation via moveObjectTo after each sync pass - Figma import: sort children descending so front items are at children[0] - addNode defaults to prepend (index 0) so new items appear at top - duplicateNode inserts clone above original (at idx instead of idx+1) - drag-reparent inserts at index 0 (frontmost) instead of childCount - Async image loading preserves z-order via insertAt instead of add - Add FigmaImportLayoutMode toggle (preserve vs openpencil auto-layout) * chore: resolve .gitignore merge conflict * fix(canvas): clamp corner radius to half the element height * chore: update version to 0.1.0 and modify mac artifact naming in electron-builder configuration --------- Co-authored-by: Fini <fini.yang@gmail.com>
2026-03-01 01:33:52 +00:00
const imageSection = imageFiles && imageFiles.length > 0
? '\n' + imageFiles.map((f) => `[Attached image: ${f} — read this file to see the image]`).join('\n')
: ''
if (!systemPrompt?.trim()) {
V0.1.0 (#13) * feat(editor): implement Figma import functionality - Added a new FigmaImportDialog component for importing .fig files. - Integrated file processing and conversion logic to handle Figma files. - Enhanced the editor layout to include a button for opening the Figma import dialog. - Introduced page tabs for better navigation between document pages. - Updated various components to utilize active page children for rendering and interactions. - Added new dependencies: fzstd and kiwi-schema for improved functionality. * fix(ai): harden Claude agent env variable resolution - Filter empty strings and object values from settings.json env - Validate ANTHROPIC_CUSTOM_HEADERS is valid JSON before passing - Remove incorrect ANTHROPIC_CUSTOM_HEADERS auto-generation from auth token * refactor(ai): remove silent fallback logic, surface errors in AI panel - Remove direct-stream fallback in generateDesign, delegate to orchestrator - Remove orchestrator planner fallback plan (buildFallbackPlanFromPrompt) - Remove sub-agent retry logic, minimal mode retry, and placeholder insertion - Throw on sub-agent error instead of silently degrading - Call updateLastMessage in catch block so errors display immediately - Clean up unused imports, functions, and console.log/warn statements * fix(ai): add 30s API connect timeout for faster error detection - Set maxRetries: 0 on Anthropic SDK to prevent retry delays on 429 - Add AbortController-based 30s timeout for initial API connection - If the provider doesn't respond within 30s, abort and send SSE error - Refactor streamViaAnthropicSDK with shared send() helper * fix(editor): improve Figma import visual fidelity - Extract image files from .fig ZIP archive and resolve by SHA-1 hash - Map Figma textCase property (UPPER/LOWER/TITLE) to text content - Convert ellipse nodes with image fills to image nodes - Decode Figma vector paths and match icons via Lucide icon registry - Fix z-order: move background rectangles behind content layers - Set clipContent=true for Figma frames (default clip behavior) - Unwrap single-artboard pages to use artboard as root frame * fix(canvas): preserve clipPath during async image load and invalidate cache - Copy clipPath from placeholder rect to FabricImage in onload callback - Set obj.dirty=true after applying/removing clipPath so Fabric.js re-renders with the updated clip region instead of using stale cache * fix(ai): use page-aware empty frame detection and place new designs as siblings - Fix isCanvasOnlyEmptyFrame() to use getActivePageChildren() instead of document.children which is empty after page migration - When canvas is empty, replace default frame (existing behavior restored) - When canvas has content, add generated frame as top-level sibling positioned to the right with 100px gap - Track generationRootFrameId so height adjustment targets correct frame - Add WIDTH SELECTION rule: app screens use 375x812 mobile dimensions - Include selected node dimensions in AI context string * feat(electron): implement application menu with native actions - Added a native application menu for macOS and Windows, including File, Edit, View, and Window menus. - Integrated menu actions for creating, opening, saving files, and editing operations like undo/redo. - Enhanced the editor layout to handle Electron menu actions through a custom hook. - Updated icon components in the toolbar and top bar for consistent styling. - Refactored padding and layout components for improved structure and readability. * fix(canvas): use page-aware children for selection depth resolution getSelectableNodeIds() used doc.children which is empty under the pages architecture. Switch to getActivePageChildren() so canvas selection correctly syncs to the layers panel. * fix(panels): adjust property panel sections for image nodes Hide fill/stroke sections (not applicable to images) and show corner radius section for image elements. * feat(canvas): implement image corner radius via clipPath FabricImage does not support rx/ry natively. Use a rounded-rect clipPath instead, with objectCaching disabled to ensure immediate re-render on radius changes. * fix(canvas): use page-aware forcePageResync for canvas re-sync The old pattern { ...doc, children: [...doc.children] } only touched root-level children which are empty under pages architecture. Extract forcePageResync() into canvas-sync-utils and replace all 7 occurrences across canvas-object-modified, drag-into-layout, and layout-reorder. * fix(canvas): preserve image corner radius clipPath during drag Three issues caused corner radius loss on image drag: 1. Scale baking in object:modified changed FabricImage width from natural to display dimensions, creating a clipPath size mismatch. Exclude images from scale baking (like paths/polygons). 2. object:moving unconditionally cleared all clipPaths to avoid stale frame clipping. Now only clears absolutePositioned (frame) clips, preserving object-level clips like image corner radius. 3. Post-drag re-sync overwrote the corner radius clipPath with the parent frame clip. Now skips frame clip when the object already has its own non-absolutePositioned clipPath. * feat(ai): enhance chat functionality with image attachments support - Added support for image attachments in chat messages, allowing users to send images alongside text. - Implemented temporary file handling for attachments, ensuring they are accessible to the AI processing. - Updated chat message structure to include attachments, and modified the prompt building logic to reference these images. - Enhanced the chat panel UI to allow users to select and preview images before sending. - Introduced a new state management for pending attachments in the AI store. - Refactored related components to accommodate the new attachment feature, improving user experience and interaction. * feat(multi-page): introduce multi-page architecture and enhance agent settings UI - Added multi-page architecture to the document structure, allowing for page CRUD actions such as add, remove, rename, reorder, and duplicate. - Implemented `activePageId` state management in the canvas store for better page handling. - Enhanced the top bar with an `AgentStatusButton` to display connected AI providers and their statuses. - Updated the agent settings dialog to improve the UI for managing AI integrations, including better layout and connection indicators. - Refactored related components for improved usability and visual consistency. * docs(README): add contributors section with dynamic contributor image * feat(ai): improve chat streaming and error handling - Added functionality to hide the checklist when streaming stops with no completed items. - Implemented abort controller management to handle user-initiated stops during chat generation. - Enhanced error handling to prevent displaying errors for user-initiated stops while preserving partial content. - Updated orchestration and design generation functions to support abort signals, improving responsiveness during streaming. * fix(canvas): align z-order with layer panel convention (top = front) Establish consistent z-order: children[0] = top of layer panel = frontmost on canvas. This matches the standard design tool convention (Figma, Sketch). - Reverse flattenNodes iteration so children[0] is added last to canvas - Add z-order reconciliation via moveObjectTo after each sync pass - Figma import: sort children descending so front items are at children[0] - addNode defaults to prepend (index 0) so new items appear at top - duplicateNode inserts clone above original (at idx instead of idx+1) - drag-reparent inserts at index 0 (frontmost) instead of childCount - Async image loading preserves z-order via insertAt instead of add - Add FigmaImportLayoutMode toggle (preserve vs openpencil auto-layout) * chore: resolve .gitignore merge conflict * fix(canvas): clamp corner radius to half the element height * chore: update version to 0.1.0 and modify mac artifact naming in electron-builder configuration --------- Co-authored-by: Fini <fini.yang@gmail.com>
2026-03-01 01:33:52 +00:00
return userText + imageSection
}
return [
'SYSTEM INSTRUCTIONS:',
systemPrompt.trim(),
'',
'USER REQUEST:',
V0.1.0 (#13) * feat(editor): implement Figma import functionality - Added a new FigmaImportDialog component for importing .fig files. - Integrated file processing and conversion logic to handle Figma files. - Enhanced the editor layout to include a button for opening the Figma import dialog. - Introduced page tabs for better navigation between document pages. - Updated various components to utilize active page children for rendering and interactions. - Added new dependencies: fzstd and kiwi-schema for improved functionality. * fix(ai): harden Claude agent env variable resolution - Filter empty strings and object values from settings.json env - Validate ANTHROPIC_CUSTOM_HEADERS is valid JSON before passing - Remove incorrect ANTHROPIC_CUSTOM_HEADERS auto-generation from auth token * refactor(ai): remove silent fallback logic, surface errors in AI panel - Remove direct-stream fallback in generateDesign, delegate to orchestrator - Remove orchestrator planner fallback plan (buildFallbackPlanFromPrompt) - Remove sub-agent retry logic, minimal mode retry, and placeholder insertion - Throw on sub-agent error instead of silently degrading - Call updateLastMessage in catch block so errors display immediately - Clean up unused imports, functions, and console.log/warn statements * fix(ai): add 30s API connect timeout for faster error detection - Set maxRetries: 0 on Anthropic SDK to prevent retry delays on 429 - Add AbortController-based 30s timeout for initial API connection - If the provider doesn't respond within 30s, abort and send SSE error - Refactor streamViaAnthropicSDK with shared send() helper * fix(editor): improve Figma import visual fidelity - Extract image files from .fig ZIP archive and resolve by SHA-1 hash - Map Figma textCase property (UPPER/LOWER/TITLE) to text content - Convert ellipse nodes with image fills to image nodes - Decode Figma vector paths and match icons via Lucide icon registry - Fix z-order: move background rectangles behind content layers - Set clipContent=true for Figma frames (default clip behavior) - Unwrap single-artboard pages to use artboard as root frame * fix(canvas): preserve clipPath during async image load and invalidate cache - Copy clipPath from placeholder rect to FabricImage in onload callback - Set obj.dirty=true after applying/removing clipPath so Fabric.js re-renders with the updated clip region instead of using stale cache * fix(ai): use page-aware empty frame detection and place new designs as siblings - Fix isCanvasOnlyEmptyFrame() to use getActivePageChildren() instead of document.children which is empty after page migration - When canvas is empty, replace default frame (existing behavior restored) - When canvas has content, add generated frame as top-level sibling positioned to the right with 100px gap - Track generationRootFrameId so height adjustment targets correct frame - Add WIDTH SELECTION rule: app screens use 375x812 mobile dimensions - Include selected node dimensions in AI context string * feat(electron): implement application menu with native actions - Added a native application menu for macOS and Windows, including File, Edit, View, and Window menus. - Integrated menu actions for creating, opening, saving files, and editing operations like undo/redo. - Enhanced the editor layout to handle Electron menu actions through a custom hook. - Updated icon components in the toolbar and top bar for consistent styling. - Refactored padding and layout components for improved structure and readability. * fix(canvas): use page-aware children for selection depth resolution getSelectableNodeIds() used doc.children which is empty under the pages architecture. Switch to getActivePageChildren() so canvas selection correctly syncs to the layers panel. * fix(panels): adjust property panel sections for image nodes Hide fill/stroke sections (not applicable to images) and show corner radius section for image elements. * feat(canvas): implement image corner radius via clipPath FabricImage does not support rx/ry natively. Use a rounded-rect clipPath instead, with objectCaching disabled to ensure immediate re-render on radius changes. * fix(canvas): use page-aware forcePageResync for canvas re-sync The old pattern { ...doc, children: [...doc.children] } only touched root-level children which are empty under pages architecture. Extract forcePageResync() into canvas-sync-utils and replace all 7 occurrences across canvas-object-modified, drag-into-layout, and layout-reorder. * fix(canvas): preserve image corner radius clipPath during drag Three issues caused corner radius loss on image drag: 1. Scale baking in object:modified changed FabricImage width from natural to display dimensions, creating a clipPath size mismatch. Exclude images from scale baking (like paths/polygons). 2. object:moving unconditionally cleared all clipPaths to avoid stale frame clipping. Now only clears absolutePositioned (frame) clips, preserving object-level clips like image corner radius. 3. Post-drag re-sync overwrote the corner radius clipPath with the parent frame clip. Now skips frame clip when the object already has its own non-absolutePositioned clipPath. * feat(ai): enhance chat functionality with image attachments support - Added support for image attachments in chat messages, allowing users to send images alongside text. - Implemented temporary file handling for attachments, ensuring they are accessible to the AI processing. - Updated chat message structure to include attachments, and modified the prompt building logic to reference these images. - Enhanced the chat panel UI to allow users to select and preview images before sending. - Introduced a new state management for pending attachments in the AI store. - Refactored related components to accommodate the new attachment feature, improving user experience and interaction. * feat(multi-page): introduce multi-page architecture and enhance agent settings UI - Added multi-page architecture to the document structure, allowing for page CRUD actions such as add, remove, rename, reorder, and duplicate. - Implemented `activePageId` state management in the canvas store for better page handling. - Enhanced the top bar with an `AgentStatusButton` to display connected AI providers and their statuses. - Updated the agent settings dialog to improve the UI for managing AI integrations, including better layout and connection indicators. - Refactored related components for improved usability and visual consistency. * docs(README): add contributors section with dynamic contributor image * feat(ai): improve chat streaming and error handling - Added functionality to hide the checklist when streaming stops with no completed items. - Implemented abort controller management to handle user-initiated stops during chat generation. - Enhanced error handling to prevent displaying errors for user-initiated stops while preserving partial content. - Updated orchestration and design generation functions to support abort signals, improving responsiveness during streaming. * fix(canvas): align z-order with layer panel convention (top = front) Establish consistent z-order: children[0] = top of layer panel = frontmost on canvas. This matches the standard design tool convention (Figma, Sketch). - Reverse flattenNodes iteration so children[0] is added last to canvas - Add z-order reconciliation via moveObjectTo after each sync pass - Figma import: sort children descending so front items are at children[0] - addNode defaults to prepend (index 0) so new items appear at top - duplicateNode inserts clone above original (at idx instead of idx+1) - drag-reparent inserts at index 0 (frontmost) instead of childCount - Async image loading preserves z-order via insertAt instead of add - Add FigmaImportLayoutMode toggle (preserve vs openpencil auto-layout) * chore: resolve .gitignore merge conflict * fix(canvas): clamp corner radius to half the element height * chore: update version to 0.1.0 and modify mac artifact naming in electron-builder configuration --------- Co-authored-by: Fini <fini.yang@gmail.com>
2026-03-01 01:33:52 +00:00
userText + imageSection,
].join('\n')
}
function resolveCodexEffort(
thinkingMode: ThinkingMode | undefined,
effort: ThinkingEffort | undefined,
): 'low' | 'medium' | 'high' | undefined {
if (thinkingMode === 'disabled') {
return 'low'
}
if (effort === 'max') {
return 'high'
}
if (effort === 'low' || effort === 'medium' || effort === 'high') {
return effort
}
if (thinkingMode === 'enabled') {
return 'medium'
}
return undefined
}
async function executeCodexCommand(
args: string[],
timeoutMs: number,
): Promise<{ text: string; errors: string[] }> {
return await new Promise((resolve, reject) => {
const child = spawn('codex', args, {
env: process.env,
stdio: ['ignore', 'pipe', 'pipe'],
})
let stdoutBuffer = ''
let stderrBuffer = ''
let textAccumulator = ''
const errors: string[] = []
const flushStdoutLine = (line: string) => {
const event = parseCodexJsonLine(line)
if (!event) return
if (event.text) {
textAccumulator += event.text
}
if (event.error) {
errors.push(event.error)
}
}
const timer = setTimeout(() => {
child.kill('SIGTERM')
reject(new Error(`Codex request timed out after ${Math.round(timeoutMs / 1000)}s.`))
}, timeoutMs)
child.stdout.on('data', (chunk: Buffer) => {
stdoutBuffer += chunk.toString('utf-8')
let idx = stdoutBuffer.indexOf('\n')
while (idx >= 0) {
const line = stdoutBuffer.slice(0, idx).trim()
stdoutBuffer = stdoutBuffer.slice(idx + 1)
if (line) flushStdoutLine(line)
idx = stdoutBuffer.indexOf('\n')
}
})
child.stderr.on('data', (chunk: Buffer) => {
stderrBuffer += chunk.toString('utf-8')
})
child.on('error', (err) => {
clearTimeout(timer)
reject(err)
})
child.on('close', (code) => {
clearTimeout(timer)
const tail = stdoutBuffer.trim()
if (tail) {
flushStdoutLine(tail)
}
if (code === 0) {
resolve({ text: textAccumulator, errors })
return
}
const stderrError = extractCodexCliError(stderrBuffer)
const fallback = errors[errors.length - 1]
reject(
new Error(
stderrError
|| fallback
|| `Codex exited with code ${code ?? 'unknown'}.`,
),
)
})
})
}
function parseCodexJsonLine(
line: string,
): { text?: string; error?: string } | null {
let parsed: Record<string, unknown>
try {
parsed = JSON.parse(line) as Record<string, unknown>
} catch {
return null
}
const type = typeof parsed.type === 'string' ? parsed.type : ''
if (type === 'error') {
const message = getStringField(parsed, ['message'])
return { error: message || 'Codex returned an unknown error.' }
}
// Common Codex JSONL stream events include deltas in "delta" or "text".
const text =
getStringField(parsed, ['delta'])
|| getStringField(parsed, ['text'])
|| getStringField(parsed, ['content'])
if (!text) return null
return { text }
}
function getStringField(
obj: Record<string, unknown>,
keys: string[],
): string | null {
for (const key of keys) {
const val = obj[key]
if (typeof val === 'string' && val.length > 0) {
return val
}
}
return null
}
function extractCodexCliError(stderr: string): string | null {
const trimmed = stderr.trim()
if (!trimmed) return null
const lines = trimmed.split('\n').map((line) => line.trim()).filter(Boolean)
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i]
if (line.toLowerCase().startsWith('error:')) {
return line.replace(/^error:\s*/i, '').trim()
}
}
return lines[lines.length - 1] ?? null
}