* feat(code): link code to canvas layers and underline design issues
Code in the Code tab and layers on the canvas were unrelated: finding the
element behind a layer, or the layer behind an element, meant reading names.
Generated Design JSX and Tailwind JSX report the layer behind each element
in the order elements open, and edited Design JSX keeps the source line of
every element through the sandbox and renderer, so hovering an element
highlights its layer, Cmd/Ctrl-click brings it into view without changing
the selection the code shows, and errors and warnings from the design check
are underlined on the property that causes them.
* feat(code): explain the Code tab when nothing is selected
With no selection the editor showed a starter frame that read like a real
layer. The tab now says it shows the selected layers' code and offers Write
JSX, which opens the editor focused on the starter template.
* refactor(code): group code-to-layer linking into its own domain
Layer link types, issue mapping, and the hover and reveal behavior move
from the Code panel and a component file into src/app/code/layers, with
useCodeLayers as the panel's entry point, so app code no longer imports
types from components.
* fix(code): underline off-scale gaps after the spacing rule renamed its property
* feat(code): mark the layer of the element around the cursor
Hover highlighting and ⌘-click reveal replaced by one model: the element
around the cursor marks its opening and closing tag names and outlines its
layer on the canvas while the editor has focus. ⌘-click also collided
with CodeMirror's add-a-cursor gesture. Read-only Tailwind JSX now takes a
cursor so it links the same way.
Leaving the editor now ends a live Design JSX edit as one undo step.
Before, canvas edits made after typing never reached the code until the
tab was reopened, and their undo entries landed before the edit's.
* feat(code): sync the Code tab and the canvas both ways by patching
Canvas edits now patch the Design JSX a person wrote instead of waiting
for them to leave the editor: each linked element remembers the layer as
Design JSX last wrote it, and a canvas change rewrites only the attributes,
text and child elements that differ from that base, as CodeMirror changes
that keep the cursor, comments, formatting and history. Attributes written
as expressions are never overwritten; the code marks them when the canvas
now differs. Untouched code is regenerated with a minimal text change.
Code edits update layers in place: the new render is reconciled into the
existing layers (reconcileRenderedLayers), which keep their ids, so links,
selection and canvas edits survive typing. Each edit is one coalesced undo
step, replacing the restore-and-rerender preview and the commit on blur.
* feat(code): patch reordered layers and aliased properties in edited code
Reordering layers on the canvas now moves their elements in code a person
wrote: each child element and the blank lines and comments above it form a
block kept as written, and the children are written again in the new
order, staying linked. Children that cannot move safely, such as a loop
between them, keep their order and are marked.
Properties accepted under several names now come from one alias table in
the Design JSX schema, which the renderer resolves through and the patcher
and issue underlines use, so a canvas change to `w` patches `width` where
the person wrote that, instead of adding a second attribute.
* feat(code): keep the cursor in moved code and patch values written in style
A reorder rewrites the children span in one change, which collapsed a
cursor or out-of-sync marker inside a moved element to the span's edge.
The patch now carries where each block moved and places selections and
markers inside it at their new position.
Properties the renderer also reads from style={{ … }} come from a table in
the Design JSX schema instead of a hand-written list, keeping the rule
that an attribute under any of its names wins. The patcher uses it to
update a value written in style where it is, as a number or a px string
as written; values the renderer cannot read, such as '50%', are marked.
The layer patcher is split by concern: syntax helpers, attribute and
style patches, child patches, out-of-sync state and transaction assembly.
* feat(code): show the code's layer on the canvas as a tinted box
The layer of the element around the cursor used the canvas hover slot, so
moving the pointer over the canvas replaced it and the two read the same.
It now has its own shared editor state, codeFocusNodeId, drawn as the hover
outline over a light tint in every pane: hover stays an outline and the
selection keeps its handles, without borrowing the dashed outlines that
already mean component sets, drag parents and ghosts.
* fix(code): write added and removed layers when a reorder cannot move the code
When children could not be moved, such as two written on one line, the
patch marked the order and returned before adding or removing elements,
so a layer created in the same change never reached the code. It now
marks the order and still writes additions and removals.
* refactor(design-jsx): format the rebased layer description and stroke aliases
* feat(code): mount the layer-linked code editor through useCodeMirror
Master moved the code editor onto the shared useCodeMirror composable.
Its layer links, issue underlines, canvas patches, minimal text updates,
autofocus and read-only cursor now sit on that composable instead of a
hand-mounted view.
534 lines
16 KiB
TypeScript
534 lines
16 KiB
TypeScript
import type { Fill, Stroke } from '@open-pencil/scene-graph'
|
|
import type { Color } from '@open-pencil/scene-graph/primitives'
|
|
|
|
export const IS_BROWSER = typeof window !== 'undefined'
|
|
export const IS_TAURI = IS_BROWSER && '__TAURI_INTERNALS__' in window
|
|
|
|
export const BLACK: Color = { r: 0, g: 0, b: 0, a: 1 }
|
|
export const TRANSPARENT: Color = { r: 0, g: 0, b: 0, a: 0 }
|
|
export const DEFAULT_SHADOW_COLOR: Color = { r: 0, g: 0, b: 0, a: 0.25 }
|
|
export const SELECTION_COLOR = { r: 0.23, g: 0.51, b: 0.96, a: 1 } satisfies Color
|
|
export const COMPONENT_COLOR = { r: 0.592, g: 0.278, b: 1, a: 1 } satisfies Color
|
|
export const SNAP_COLOR = { r: 1.0, g: 0.0, b: 0.56, a: 1 } satisfies Color
|
|
export const MEASUREMENT_COLOR = { r: 0.949, g: 0.282, b: 0.133, a: 1 } satisfies Color
|
|
export const MEASUREMENT_PILL_PADDING_X = 5
|
|
export const MEASUREMENT_PILL_HEIGHT = 18
|
|
export const MEASUREMENT_PILL_RADIUS = 3
|
|
export const MEASUREMENT_TEXT_BASELINE = 4
|
|
export const CANVAS_BG_COLOR = { r: 0.96, g: 0.96, b: 0.96, a: 1 } satisfies Color
|
|
export const CANVAS_BG_COLOR_DARK = { r: 0.173, g: 0.173, b: 0.173, a: 1 } satisfies Color // #2c2c2c, Figma-ish dark canvas
|
|
|
|
/**
|
|
* Returns the canvas background to initialize new pages with. Defers
|
|
* to the OS `prefers-color-scheme` so users on a dark desktop don't
|
|
* get a white flash every time they open a document.
|
|
*
|
|
* NOTE: this is deliberately the runtime/new-page path only. The
|
|
* `.fig` serialization path continues to write the static light
|
|
* `CANVAS_BG_COLOR` so files stay portable — a dark-theme user saving
|
|
* a file must not force darkness on recipients.
|
|
*/
|
|
export function getDefaultCanvasBgColor(): Color {
|
|
if (IS_BROWSER) {
|
|
const params = new URLSearchParams(window.location.search)
|
|
if ('env' in import.meta && import.meta.env.DEV && params.has('test')) {
|
|
return CANVAS_BG_COLOR
|
|
}
|
|
}
|
|
|
|
if (
|
|
IS_BROWSER &&
|
|
typeof window.matchMedia === 'function' &&
|
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
) {
|
|
return CANVAS_BG_COLOR_DARK
|
|
}
|
|
return CANVAS_BG_COLOR
|
|
}
|
|
|
|
export const SNAP_THRESHOLD_SCREEN_PX = 5
|
|
|
|
export const RULER_SIZE = 20
|
|
export const RULER_BG_COLOR = { r: 0.14, g: 0.14, b: 0.14, a: 1 } satisfies Color
|
|
export const RULER_TICK_COLOR = { r: 0.4, g: 0.4, b: 0.4, a: 1 } satisfies Color
|
|
export const RULER_TEXT_COLOR = { r: 0.55, g: 0.55, b: 0.55, a: 1 } satisfies Color
|
|
export const RULER_BADGE_HEIGHT = 14
|
|
export const RULER_BADGE_PADDING = 3
|
|
export const RULER_BADGE_RADIUS = 2
|
|
export const RULER_BADGE_EXCLUSION = 30
|
|
export const RULER_TEXT_BASELINE = 0.65
|
|
export const RULER_MAJOR_TICK = 0.5
|
|
export const RULER_MINOR_TICK = 0.25
|
|
export const RULER_HIGHLIGHT_ALPHA = 0.3
|
|
|
|
export const PEN_HANDLE_RADIUS = 2.5
|
|
export const PEN_VERTEX_RADIUS = 3
|
|
export const PEN_CLOSE_RADIUS_BOOST = 2
|
|
export const PEN_PATH_STROKE_WIDTH = 2
|
|
export const PARENT_OUTLINE_ALPHA = 0.5
|
|
export const PARENT_OUTLINE_DASH = 4
|
|
export const DEFAULT_FONT_FAMILY = 'Inter'
|
|
export const DEFAULT_FONT_SIZE = 14
|
|
export const DEFAULT_STROKE_MITER_LIMIT = 4
|
|
export const LABEL_FONT_SIZE = 11
|
|
export const SIZE_FONT_SIZE = 10
|
|
|
|
export const HANDLE_HALF_SIZE = 3
|
|
|
|
export const LABEL_OFFSET_Y = 8
|
|
export const SIZE_PILL_PADDING_X = 6
|
|
export const SIZE_PILL_PADDING_Y = 6
|
|
export const SIZE_PILL_HEIGHT = 18
|
|
export const SIZE_PILL_RADIUS = 4
|
|
export const SIZE_PILL_TEXT_OFFSET_Y = 13
|
|
|
|
export const MARQUEE_FILL_ALPHA = 0.08
|
|
export const SELECTION_DASH_ALPHA = 0.6
|
|
/** Tint inside the layer of the code element around the cursor, under its hover outline. */
|
|
export const CODE_FOCUS_FILL_ALPHA = 0.1
|
|
export const DROP_HIGHLIGHT_ALPHA = 0.8
|
|
export const DROP_HIGHLIGHT_STROKE = 2
|
|
|
|
export const LAYOUT_INDICATOR_STROKE = 2
|
|
export const AUTO_LAYOUT_HOVER_TICK_LENGTH = 12
|
|
export const AUTO_LAYOUT_HOVER_STRIPE_GAP = 8
|
|
export const AUTO_LAYOUT_HOVER_STRIPE_WIDTH = 1
|
|
export const AUTO_LAYOUT_HOVER_STROKE_WIDTH = 1.5
|
|
export const AUTO_LAYOUT_HOVER_CHILD_DASH = 4
|
|
export const AUTO_LAYOUT_HOVER_VALUE_OFFSET = 18
|
|
export const AUTO_LAYOUT_HOVER_VALUE_PILL_HEIGHT = 22
|
|
export const AUTO_LAYOUT_HOVER_VALUE_PILL_RADIUS = 5
|
|
export const AUTO_LAYOUT_HOVER_VALUE_PILL_PADDING_X = 5
|
|
export const AUTO_LAYOUT_HOVER_GAP_REGION_TOLERANCE = 12
|
|
export const AUTO_LAYOUT_HOVER_TICK_HIT_TOLERANCE = 8
|
|
export const AUTO_LAYOUT_HOVER_PADDING_REGION_TOLERANCE = 20
|
|
export const AUTO_LAYOUT_PADDING_EDITOR_OFFSET_X = 18
|
|
export const AUTO_LAYOUT_PADDING_EDITOR_OFFSET_Y = 36
|
|
export const AUTO_LAYOUT_HOVER_BLUE = { r: 0.28, g: 0.64, b: 1, a: 0.82 } satisfies Color
|
|
export const AUTO_LAYOUT_HOVER_BLUE_FILL = { r: 0.28, g: 0.64, b: 1, a: 0.1 } satisfies Color
|
|
export const AUTO_LAYOUT_HOVER_MAGENTA = { r: 1, g: 0.32, b: 0.68, a: 0.78 } satisfies Color
|
|
export const AUTO_LAYOUT_HOVER_MAGENTA_FILL = { r: 1, g: 0.32, b: 0.68, a: 0.1 } satisfies Color
|
|
|
|
export const SECTION_CORNER_RADIUS = 5
|
|
export const SECTION_TITLE_HEIGHT = 24
|
|
export const SECTION_TITLE_PADDING_X = 6
|
|
export const SECTION_TITLE_RADIUS = 5
|
|
export const SECTION_TITLE_FONT_SIZE = 11
|
|
export const SECTION_TITLE_GAP = 6
|
|
export const SECTION_HOVER_STROKE_WIDTH = 2
|
|
|
|
export const COMPONENT_SET_DASH = 6
|
|
export const COMPONENT_SET_DASH_GAP = 4
|
|
export const COMPONENT_SET_BORDER_WIDTH = 1.5
|
|
export const COMPONENT_LABEL_FONT_SIZE = 11
|
|
export const COMPONENT_LABEL_GAP = 6
|
|
export const COMPONENT_LABEL_ICON_SIZE = 10
|
|
export const COMPONENT_LABEL_ICON_GAP = 4
|
|
|
|
export const RULER_TARGET_PIXEL_SPACING = 100
|
|
export const RULER_MAJOR_TOLERANCE = 0.01
|
|
|
|
export const FLASH_COLOR = SELECTION_COLOR
|
|
export const FLASH_ATTACK_MS = 200
|
|
export const FLASH_HOLD_MS = 400
|
|
export const FLASH_RELEASE_MS = 300
|
|
export const FLASH_STROKE_WIDTH = 2
|
|
export const FLASH_PADDING = 5
|
|
export const FLASH_OVERSHOOT = 30
|
|
export const FLASH_RADIUS = 4
|
|
|
|
export const ISSUE_ERROR_COLOR = { r: 0.949, g: 0.282, b: 0.133, a: 1 } satisfies Color // #F24822
|
|
export const ISSUE_WARNING_COLOR = { r: 1, g: 0.702, b: 0.078, a: 1 } satisfies Color // #FFB314
|
|
export const ISSUE_INFO_COLOR = { r: 0.55, g: 0.55, b: 0.55, a: 1 } satisfies Color
|
|
export const ISSUE_MARKER_HEIGHT = 16
|
|
export const ISSUE_MARKER_PADDING_X = 5
|
|
/** Gap between a layer's corner and its marker; clears the 8px selection handle. */
|
|
export const ISSUE_MARKER_OFFSET = 4
|
|
/** Markers closer than this merge into one. */
|
|
export const ISSUE_MARKER_GAP = 2
|
|
export const ISSUE_MARKER_VIEWPORT_INSET = 4
|
|
/** Layers smaller than this on screen pass their marker to an ancestor. */
|
|
export const ISSUE_MARKER_MIN_TARGET = 24
|
|
export const ISSUE_MARKER_MAX_COUNT = 99
|
|
/** Edge pins closer than this along the viewport edge merge into one. */
|
|
export const ISSUE_EDGE_MERGE_GAP = 24
|
|
/** Length of the chevron an edge pin points with, beyond its ring. */
|
|
export const ISSUE_EDGE_ARROW = 5
|
|
export const ISSUE_MARKER_RING_WIDTH = 1.5
|
|
export const ISSUE_HIGHLIGHT_STROKE_WIDTH = 1.5
|
|
export const ISSUE_HIGHLIGHT_FILL_ALPHA = 0.08
|
|
|
|
export const AI_ACTIVE_COLOR = { r: 0.26, g: 0.52, b: 0.96 }
|
|
export const AI_DONE_COLOR = { r: 0.16, g: 0.73, b: 0.36 }
|
|
export const AI_PULSE_PERIOD_MS = 1500
|
|
export const AI_DONE_DURATION_MS = 800
|
|
|
|
export const TEXT_SELECTION_COLOR = { r: 0.26, g: 0.52, b: 0.96, a: 0.3 }
|
|
export const TEXT_CARET_COLOR = BLACK
|
|
export const TEXT_CARET_WIDTH = 1
|
|
|
|
export type ACPAgentID = 'claude-code' | 'codex' | 'gemini-cli'
|
|
|
|
export interface ACPAgentDef {
|
|
id: ACPAgentID
|
|
name: string
|
|
command: string
|
|
args: string[]
|
|
installCommand?: string
|
|
}
|
|
|
|
export const ACP_AGENTS: ACPAgentDef[] = [
|
|
{
|
|
id: 'claude-code',
|
|
name: 'Claude Code',
|
|
command: 'claude-agent-acp',
|
|
args: [],
|
|
installCommand: 'npm i -g @agentclientprotocol/claude-agent-acp'
|
|
},
|
|
{
|
|
id: 'codex',
|
|
name: 'Codex',
|
|
command: 'codex-acp',
|
|
args: [],
|
|
installCommand: 'npm i -g @zed-industries/codex-acp'
|
|
},
|
|
{
|
|
id: 'gemini-cli',
|
|
name: 'Gemini CLI',
|
|
command: 'gemini',
|
|
args: ['--acp'],
|
|
installCommand: 'npm i -g @google/gemini-cli'
|
|
}
|
|
]
|
|
|
|
export type AIProviderID =
|
|
| 'openrouter'
|
|
| 'anthropic'
|
|
| 'openai'
|
|
| 'google'
|
|
| 'deepseek'
|
|
| 'openai-compatible'
|
|
| 'zai'
|
|
| 'minimax'
|
|
| 'anthropic-compatible'
|
|
| 'harness:pi'
|
|
| `acp:${ACPAgentID}`
|
|
|
|
export interface ModelOption {
|
|
id: string
|
|
name: string
|
|
tag?: string
|
|
capabilities?: readonly ('tools' | 'vision')[]
|
|
recommendedMaxOutputTokens?: number
|
|
releaseDate?: string
|
|
status?: 'active' | 'beta' | 'deprecated'
|
|
}
|
|
|
|
export interface AIProviderDef {
|
|
id: AIProviderID
|
|
name: string
|
|
keyPlaceholder: string
|
|
keyURL: string
|
|
models: ModelOption[]
|
|
defaultModel: string
|
|
supportsCustomBaseURL?: boolean
|
|
supportsCustomModel?: boolean
|
|
}
|
|
|
|
export const HARNESS_PROVIDER_ID = 'harness:pi' as const
|
|
|
|
export const AI_PROVIDERS: AIProviderDef[] = [
|
|
{
|
|
id: HARNESS_PROVIDER_ID,
|
|
name: 'Pi',
|
|
keyPlaceholder: 'Provider API key',
|
|
keyURL: '',
|
|
defaultModel: '',
|
|
supportsCustomModel: true,
|
|
models: []
|
|
},
|
|
{
|
|
id: 'openrouter',
|
|
name: 'OpenRouter',
|
|
keyPlaceholder: 'sk-or-…',
|
|
keyURL: 'https://openrouter.ai/keys',
|
|
defaultModel: 'anthropic/claude-sonnet-5',
|
|
supportsCustomModel: true,
|
|
models: [
|
|
{
|
|
id: 'anthropic/claude-sonnet-5',
|
|
name: 'Claude Sonnet 5',
|
|
tag: 'Best for design',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{
|
|
id: 'anthropic/claude-opus-5',
|
|
name: 'Claude Opus 5',
|
|
tag: 'Smartest',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{
|
|
id: 'anthropic/claude-fable-5.1',
|
|
name: 'Claude Fable 5.1',
|
|
tag: 'Latest Anthropic',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{
|
|
id: 'openai/gpt-5.6',
|
|
name: 'GPT-5.6',
|
|
tag: 'Latest OpenAI',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{
|
|
id: 'google/gemini-3.8-flash',
|
|
name: 'Gemini 3.8 Flash',
|
|
tag: 'Fast',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{ id: 'z-ai/glm-5.3', name: 'GLM-5.3', capabilities: ['tools'] },
|
|
{
|
|
id: 'deepseek/deepseek-v4-pro',
|
|
name: 'DeepSeek V4 Pro',
|
|
tag: 'Reasoning',
|
|
capabilities: ['tools']
|
|
},
|
|
{
|
|
id: 'moonshotai/kimi-k3',
|
|
name: 'Kimi K3',
|
|
tag: 'Vision + code',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{ id: 'qwen/qwen3-coder:free', name: 'Qwen3 Coder', tag: 'Free' },
|
|
{ id: 'openai/gpt-oss-120b:free', name: 'GPT-OSS 120B', tag: 'Free' }
|
|
]
|
|
},
|
|
{
|
|
id: 'anthropic',
|
|
name: 'Anthropic',
|
|
keyPlaceholder: 'sk-ant-…',
|
|
keyURL: 'https://console.anthropic.com/settings/keys',
|
|
defaultModel: 'claude-sonnet-5',
|
|
models: [
|
|
{
|
|
id: 'claude-sonnet-5',
|
|
name: 'Claude Sonnet 5',
|
|
tag: 'Best for design',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{
|
|
id: 'claude-opus-5',
|
|
name: 'Claude Opus 5',
|
|
tag: 'Smartest',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{
|
|
id: 'claude-fable-5-1',
|
|
name: 'Claude Fable 5.1',
|
|
tag: 'Latest',
|
|
capabilities: ['tools', 'vision']
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'openai',
|
|
name: 'OpenAI',
|
|
keyPlaceholder: 'sk-…',
|
|
keyURL: 'https://platform.openai.com/api-keys',
|
|
defaultModel: 'gpt-5.6',
|
|
models: [
|
|
{ id: 'gpt-5.6', name: 'GPT-5.6', tag: 'Best', capabilities: ['tools', 'vision'] },
|
|
{ id: 'gpt-5.5', name: 'GPT-5.5', capabilities: ['tools', 'vision'] },
|
|
{ id: 'gpt-5.4-mini', name: 'GPT-5.4 mini', tag: 'Fast', capabilities: ['tools', 'vision'] },
|
|
{ id: 'gpt-5.4-nano', name: 'GPT-5.4 nano', tag: 'Cheap', capabilities: ['tools', 'vision'] }
|
|
]
|
|
},
|
|
{
|
|
id: 'google',
|
|
name: 'Google AI',
|
|
keyPlaceholder: 'AIza…',
|
|
keyURL: 'https://aistudio.google.com/apikey',
|
|
defaultModel: 'gemini-3.8-flash',
|
|
models: [
|
|
{
|
|
id: 'gemini-3.8-flash',
|
|
name: 'Gemini 3.8 Flash',
|
|
tag: 'Latest',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{ id: 'gemini-3.7-flash', name: 'Gemini 3.7 Flash', capabilities: ['tools', 'vision'] },
|
|
{
|
|
id: 'gemini-flash-latest',
|
|
name: 'Gemini Flash Latest',
|
|
tag: 'Alias',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{
|
|
id: 'gemini-3.1-pro-preview',
|
|
name: 'Gemini 3.1 Pro',
|
|
tag: 'Pro',
|
|
capabilities: ['tools', 'vision']
|
|
}
|
|
]
|
|
},
|
|
{
|
|
id: 'deepseek',
|
|
name: 'DeepSeek',
|
|
keyPlaceholder: 'sk-…',
|
|
keyURL: 'https://platform.deepseek.com/api_keys',
|
|
defaultModel: 'deepseek-v4-flash',
|
|
models: [
|
|
{ id: 'deepseek-v4-flash', name: 'DeepSeek V4 Flash', tag: 'Fast' },
|
|
{ id: 'deepseek-v4-pro', name: 'DeepSeek V4 Pro', tag: 'Reasoning' }
|
|
]
|
|
},
|
|
{
|
|
id: 'zai',
|
|
name: 'Z.ai',
|
|
keyPlaceholder: 'API key',
|
|
keyURL: 'https://docs.z.ai/devpack/quick-start',
|
|
defaultModel: 'glm-5.3',
|
|
models: [
|
|
{ id: 'glm-5.3', name: 'GLM-5.3', tag: 'Best' },
|
|
{ id: 'glm-5.3-flash', name: 'GLM-5.3-Flash', tag: 'Fast' },
|
|
{ id: 'glm-5.2', name: 'GLM-5.2' },
|
|
{
|
|
id: 'glm-5v-turbo',
|
|
name: 'GLM-5V-Turbo',
|
|
tag: 'Vision',
|
|
capabilities: ['tools', 'vision']
|
|
},
|
|
{ id: 'glm-5.1', name: 'GLM-5.1' },
|
|
{ id: 'glm-5', name: 'GLM-5' },
|
|
{ id: 'glm-5-code', name: 'GLM-5-Code' },
|
|
{ id: 'glm-4.7', name: 'GLM-4.7' },
|
|
{ id: 'glm-4.7-flashx', name: 'GLM-4.7-FlashX' },
|
|
{ id: 'glm-4.6', name: 'GLM-4.6' },
|
|
{ id: 'glm-4.5', name: 'GLM-4.5' },
|
|
{ id: 'glm-4.5-x', name: 'GLM-4.5-X' },
|
|
{ id: 'glm-4.5-air', name: 'GLM-4.5-Air' },
|
|
{ id: 'glm-4.5-airx', name: 'GLM-4.5-AirX' },
|
|
{ id: 'glm-4-32b-0414-128k', name: 'GLM-4-32B-0414-128K' },
|
|
{ id: 'glm-4.7-flash', name: 'GLM-4.7-Flash', tag: 'Free' },
|
|
{ id: 'glm-4.5-flash', name: 'GLM-4.5-Flash', tag: 'Free' }
|
|
]
|
|
},
|
|
{
|
|
id: 'minimax',
|
|
name: 'MiniMax',
|
|
keyPlaceholder: 'API key',
|
|
keyURL: 'https://platform.minimax.io/user-center/basic-information/interface-key',
|
|
defaultModel: 'MiniMax-M3',
|
|
models: [
|
|
{ id: 'MiniMax-M3', name: 'MiniMax-M3', tag: 'Best' },
|
|
{ id: 'MiniMax-M2.7', name: 'MiniMax-M2.7' },
|
|
{ id: 'MiniMax-M2.7-highspeed', name: 'MiniMax-M2.7-highspeed', tag: 'Fast' },
|
|
{ id: 'MiniMax-M2.5', name: 'MiniMax-M2.5' },
|
|
{ id: 'MiniMax-M2.5-highspeed', name: 'MiniMax-M2.5 Highspeed', tag: 'Fast' },
|
|
{ id: 'MiniMax-M2.1', name: 'MiniMax-M2.1' },
|
|
{ id: 'MiniMax-M2.1-highspeed', name: 'MiniMax-M2.1 Highspeed', tag: 'Fast' },
|
|
{ id: 'MiniMax-M2', name: 'MiniMax-M2' }
|
|
]
|
|
},
|
|
{
|
|
id: 'openai-compatible',
|
|
name: 'OpenAI-compatible',
|
|
keyPlaceholder: 'API key',
|
|
keyURL: '',
|
|
defaultModel: '',
|
|
models: [],
|
|
supportsCustomBaseURL: true,
|
|
supportsCustomModel: true
|
|
},
|
|
{
|
|
id: 'anthropic-compatible',
|
|
name: 'Anthropic-compatible',
|
|
keyPlaceholder: 'API key',
|
|
keyURL: '',
|
|
defaultModel: '',
|
|
models: [],
|
|
supportsCustomBaseURL: true,
|
|
supportsCustomModel: true
|
|
}
|
|
]
|
|
|
|
export const DEFAULT_AI_PROVIDER: AIProviderID = 'openai-compatible'
|
|
export const DEFAULT_AI_MODEL =
|
|
AI_PROVIDERS.find((provider) => provider.id === DEFAULT_AI_PROVIDER)?.defaultModel ?? ''
|
|
|
|
export const AUTOMATION_HTTP_PORT = 7600
|
|
|
|
export const GOOGLE_FONTS_API_KEY = 'AIzaSyD1tYDR_dUEiV-Tw1vksEhZbUytgKW5pc8'
|
|
|
|
export const CJK_FALLBACK_FAMILIES_MACOS = [
|
|
'PingFang SC',
|
|
'PingFang TC',
|
|
'Hiragino Sans',
|
|
'Apple SD Gothic Neo',
|
|
'Heiti SC',
|
|
'Heiti TC'
|
|
]
|
|
|
|
export const CJK_FALLBACK_FAMILIES_WINDOWS = [
|
|
'Microsoft YaHei',
|
|
'Microsoft YaHei UI',
|
|
'Microsoft JhengHei',
|
|
'Yu Gothic',
|
|
'Malgun Gothic',
|
|
'SimHei',
|
|
'SimSun'
|
|
]
|
|
|
|
export const CJK_FALLBACK_FAMILIES_LINUX = [
|
|
'Noto Sans CJK SC',
|
|
'Noto Sans CJK TC',
|
|
'Noto Sans CJK JP',
|
|
'Noto Sans CJK KR',
|
|
'WenQuanYi Micro Hei',
|
|
'Droid Sans Fallback'
|
|
]
|
|
|
|
export const CJK_GOOGLE_FONTS = ['Noto Sans SC', 'Noto Sans TC', 'Noto Sans JP', 'Noto Sans KR']
|
|
|
|
export const DEFAULT_SHAPE_FILL: Fill = {
|
|
type: 'SOLID',
|
|
color: { r: 0.83, g: 0.83, b: 0.83, a: 1 },
|
|
opacity: 1,
|
|
visible: true
|
|
}
|
|
|
|
export const DEFAULT_FRAME_FILL: Fill = {
|
|
type: 'SOLID',
|
|
color: { r: 1, g: 1, b: 1, a: 1 },
|
|
opacity: 1,
|
|
visible: true
|
|
}
|
|
|
|
export const SECTION_DEFAULT_FILL: Fill = {
|
|
type: 'SOLID',
|
|
color: { r: 0.37, g: 0.37, b: 0.37, a: 1 },
|
|
opacity: 1,
|
|
visible: true
|
|
}
|
|
|
|
export const SECTION_DEFAULT_STROKE: Stroke = {
|
|
type: 'SOLID',
|
|
color: { r: 0.55, g: 0.55, b: 0.55, a: 1 },
|
|
weight: 1,
|
|
opacity: 1,
|
|
visible: true,
|
|
align: 'INSIDE'
|
|
}
|
|
|
|
export const ZOOM_DIVISOR = 50
|
|
export const ZOOM_SCALE_MIN = 0.75
|
|
export const ZOOM_SCALE_MAX = 1.25
|
|
|
|
export const PEN_CLOSE_THRESHOLD = 8
|
|
export const ROTATION_SNAP_DEGREES = 15
|
|
export const CORNER_ROTATE_ZONE = 16
|
|
export const HANDLE_HIT_RADIUS = 6
|
|
export const ROTATION_HANDLE_DISTANCE = 24
|
|
export const DEFAULT_TEXT_WIDTH = 200
|
|
export const DEFAULT_TEXT_HEIGHT = 24
|
|
export const AUTO_LAYOUT_BREAK_THRESHOLD = 8
|