@things-factory/board-ai 10.1.24 → 10.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLAN-styling-tools.md +1 -1
- package/dist-client/server/service/assistant.d.ts +5 -4
- package/dist-client/server/service/assistant.js +35 -35
- package/dist-client/server/service/assistant.js.map +1 -1
- package/dist-client/server/service/styling/clear-tools.d.ts +2 -2
- package/dist-client/server/service/styling/clear-tools.js.map +1 -1
- package/dist-client/server/service/styling/copy-tools.d.ts +3 -3
- package/dist-client/server/service/styling/copy-tools.js +1 -1
- package/dist-client/server/service/styling/copy-tools.js.map +1 -1
- package/dist-client/server/service/styling/effect-tools.d.ts +2 -2
- package/dist-client/server/service/styling/effect-tools.js.map +1 -1
- package/dist-client/server/service/styling/fill-tools.d.ts +3 -3
- package/dist-client/server/service/styling/fill-tools.js +1 -1
- package/dist-client/server/service/styling/fill-tools.js.map +1 -1
- package/dist-client/server/service/styling/material-tools.d.ts +2 -2
- package/dist-client/server/service/styling/material-tools.js.map +1 -1
- package/dist-client/server/service/styling/registry.d.ts +6 -6
- package/dist-client/server/service/styling/registry.js +2 -2
- package/dist-client/server/service/styling/registry.js.map +1 -1
- package/dist-client/server/service/styling/stroke-tools.d.ts +2 -2
- package/dist-client/server/service/styling/stroke-tools.js.map +1 -1
- package/dist-client/server/service/styling/text-tools.d.ts +2 -2
- package/dist-client/server/service/styling/text-tools.js.map +1 -1
- package/dist-client/server/service/types.d.ts +6 -148
- package/dist-client/server/service/types.js.map +1 -1
- package/dist-client/server/service/validation/board-model-schema.d.ts +262 -262
- package/dist-client/server/service/validation/board-model-schema.js +4 -4
- package/dist-client/server/service/validation/board-model-schema.js.map +1 -1
- package/dist-client/server/service/validation/tool-validation.js +5 -5
- package/dist-client/server/service/validation/tool-validation.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.d.ts +2 -2
- package/dist-server/index.js +2 -2
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/assistant.d.ts +5 -4
- package/dist-server/service/assistant.js +37 -37
- package/dist-server/service/assistant.js.map +1 -1
- package/dist-server/service/board-ai-resolver.js +2 -2
- package/dist-server/service/board-ai-resolver.js.map +1 -1
- package/dist-server/service/index.d.ts +0 -1
- package/dist-server/service/index.js +0 -1
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/patch-entry/board-patch-subscription.js +1 -1
- package/dist-server/service/patch-entry/board-patch-subscription.js.map +1 -1
- package/dist-server/service/patch-entry/patch-entry.d.ts +1 -1
- package/dist-server/service/patch-entry/patch-entry.js +1 -1
- package/dist-server/service/patch-entry/patch-entry.js.map +1 -1
- package/dist-server/service/styling/clear-tools.d.ts +2 -2
- package/dist-server/service/styling/clear-tools.js.map +1 -1
- package/dist-server/service/styling/copy-tools.d.ts +3 -3
- package/dist-server/service/styling/copy-tools.js +1 -1
- package/dist-server/service/styling/copy-tools.js.map +1 -1
- package/dist-server/service/styling/effect-tools.d.ts +2 -2
- package/dist-server/service/styling/effect-tools.js.map +1 -1
- package/dist-server/service/styling/fill-tools.d.ts +3 -3
- package/dist-server/service/styling/fill-tools.js +1 -1
- package/dist-server/service/styling/fill-tools.js.map +1 -1
- package/dist-server/service/styling/material-tools.d.ts +2 -2
- package/dist-server/service/styling/material-tools.js.map +1 -1
- package/dist-server/service/styling/registry.d.ts +6 -6
- package/dist-server/service/styling/registry.js +2 -2
- package/dist-server/service/styling/registry.js.map +1 -1
- package/dist-server/service/styling/stroke-tools.d.ts +2 -2
- package/dist-server/service/styling/stroke-tools.js.map +1 -1
- package/dist-server/service/styling/text-tools.d.ts +2 -2
- package/dist-server/service/styling/text-tools.js.map +1 -1
- package/dist-server/service/types.d.ts +6 -148
- package/dist-server/service/types.js.map +1 -1
- package/dist-server/service/validation/board-model-schema.d.ts +262 -262
- package/dist-server/service/validation/board-model-schema.js +4 -4
- package/dist-server/service/validation/board-model-schema.js.map +1 -1
- package/dist-server/service/validation/tool-validation.js +5 -5
- package/dist-server/service/validation/tool-validation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/logs/.5e5d741d8b7784a2fbad65eedc0fd46946aaf6f2-audit.json +5 -0
- package/logs/connections-2026-09-17-16.log +0 -0
- package/package.json +8 -7
- package/server/index.ts +2 -2
- package/server/service/assistant-integration.test.ts +44 -44
- package/server/service/assistant-llm-smoke.test.ts +5 -5
- package/server/service/assistant.test.ts +86 -86
- package/server/service/assistant.ts +41 -43
- package/server/service/board-ai-resolver.ts +2 -2
- package/server/service/index.ts +0 -1
- package/server/service/patch-entry/board-patch-subscription.ts +1 -1
- package/server/service/patch-entry/patch-entry.ts +2 -2
- package/server/service/styling/clear-tools.ts +2 -2
- package/server/service/styling/copy-tools.ts +3 -3
- package/server/service/styling/effect-tools.ts +2 -2
- package/server/service/styling/fill-tools.test.ts +1 -1
- package/server/service/styling/fill-tools.ts +4 -4
- package/server/service/styling/material-tools.ts +2 -2
- package/server/service/styling/registry.ts +6 -6
- package/server/service/styling/stroke-tools.test.ts +1 -1
- package/server/service/styling/stroke-tools.ts +3 -3
- package/server/service/styling/text-tools.ts +2 -2
- package/server/service/tool-exposure.test.ts +7 -7
- package/server/service/types.ts +9 -110
- package/server/service/validation/board-model-schema.test.ts +3 -3
- package/server/service/validation/board-model-schema.ts +4 -4
- package/server/service/validation/tool-validation.ts +5 -5
- package/dist-server/service/apply-patch.d.ts +0 -47
- package/dist-server/service/apply-patch.js +0 -369
- package/dist-server/service/apply-patch.js.map +0 -1
- package/server/service/apply-patch-drift.test.ts +0 -831
- package/server/service/apply-patch.test.ts +0 -757
- package/server/service/apply-patch.ts +0 -411
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* preset 적용 전 / 데이터 바인딩 적용 전 깨끗한 상태로 돌리기 위한 helper.
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
6
|
+
import type { SceneEditOp } from '@operato/scene-ops';
|
|
7
7
|
export interface ClearStyleInput {
|
|
8
8
|
refids: number[];
|
|
9
9
|
aspects: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font' | 'opacity'>;
|
|
10
10
|
}
|
|
11
11
|
export interface OpsResult {
|
|
12
|
-
ops:
|
|
12
|
+
ops: SceneEditOp[];
|
|
13
13
|
errors: string[];
|
|
14
14
|
}
|
|
15
15
|
export declare function buildClearStyleOps(input: ClearStyleInput): OpsResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clear-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/clear-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * clearStyle — 특정 styling aspect 들을 초기화 (제거).\n *\n * preset 적용 전 / 데이터 바인딩 적용 전 깨끗한 상태로 돌리기 위한 helper.\n */\nimport type {
|
|
1
|
+
{"version":3,"file":"clear-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/clear-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * clearStyle — 특정 styling aspect 들을 초기화 (제거).\n *\n * preset 적용 전 / 데이터 바인딩 적용 전 깨끗한 상태로 돌리기 위한 helper.\n */\nimport type { SceneEditOp } from '@operato/scene-ops'\n\nexport interface ClearStyleInput {\n refids: number[]\n aspects: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font' | 'opacity'>\n}\n\nexport interface OpsResult {\n ops: SceneEditOp[]\n errors: string[]\n}\n\nconst ASPECT_TO_PATCH: Record<string, any> = {\n fill: { fillStyle: { type: 'none' } },\n // stroke: strokeStyle 는 색상 문자열 (top-level), 굵기는 lineWidth (top-level).\n // 굵기 0 으로 윤곽선 시각 제거 (색은 유지). 완전 제거는 strokeStyle: null 동시 set.\n stroke: { lineWidth: 0 },\n shadow: { shadow: { blur: 0, color: 'transparent' } },\n material: { material3d: { preset: 'default' } },\n // font: null = delete 시맨틱 (apply-patch.ts:233-243). 핵심 폰트 키 모두 제거 →\n // component 가 기본 폰트 동작으로 복귀. 보존이 의도라면 font aspect 미선택.\n font: {\n fontSize: null,\n fontFamily: null,\n fontColor: null,\n fontWeight: null,\n fontStyle: null\n },\n opacity: { alpha: 1 }\n}\n\nexport function buildClearStyleOps(input: ClearStyleInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }\n\n if (!Array.isArray(input.aspects) || input.aspects.length === 0) {\n return { ops: [], errors: ['aspects must be a non-empty array'] }\n }\n const valid = ['fill', 'stroke', 'shadow', 'material', 'font', 'opacity']\n const unknown = input.aspects.filter(a => !valid.includes(a))\n if (unknown.length > 0) {\n return {\n ops: [],\n errors: [`Unknown aspect(s): ${unknown.join(', ')}. Valid: ${valid.join('/')}`]\n }\n }\n\n // 모든 aspect 의 patch 를 하나로 병합 (한 refid 당 하나의 modify op)\n const merged: any = {}\n for (const a of input.aspects) {\n Object.assign(merged, ASPECT_TO_PATCH[a])\n }\n if (Object.keys(merged).length === 0) {\n return { ops: [], errors: ['No effective patch — try other aspects'] }\n }\n\n return {\n ops: refids.map(refid => ({ op: 'modify', refid, patch: merged })),\n errors: []\n }\n}\n"]}
|
|
@@ -9,18 +9,18 @@
|
|
|
9
9
|
* 입력 — sourceRefid 의 실제 styling 값을 미리 알 필요 없음. server 가 components
|
|
10
10
|
* 에서 source 찾아 styling 추출 후 target 들에 patch 생성.
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { SceneEditOp } from '@operato/scene-ops';
|
|
13
13
|
export interface CopyStyleInput {
|
|
14
14
|
sourceRefid: number;
|
|
15
15
|
targetRefids: number[];
|
|
16
16
|
aspects?: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font' | 'opacity' | 'visibility'>;
|
|
17
17
|
}
|
|
18
18
|
export interface OpsResult {
|
|
19
|
-
ops:
|
|
19
|
+
ops: SceneEditOp[];
|
|
20
20
|
errors: string[];
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
-
* input + components 배열 →
|
|
23
|
+
* input + components 배열 → SceneEditOp[].
|
|
24
24
|
*
|
|
25
25
|
* components 는 board 의 모든 컴포넌트 (caller 가 전달). source 찾아 styling 추출.
|
|
26
26
|
*/
|
|
@@ -26,7 +26,7 @@ const DEFAULT_ASPECTS = [
|
|
|
26
26
|
// visibility 는 default 에서 제외 — 의도 없이 hidden 복사 위험
|
|
27
27
|
];
|
|
28
28
|
/**
|
|
29
|
-
* input + components 배열 →
|
|
29
|
+
* input + components 배열 → SceneEditOp[].
|
|
30
30
|
*
|
|
31
31
|
* components 는 board 의 모든 컴포넌트 (caller 가 전달). source 찾아 styling 추출.
|
|
32
32
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/copy-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * copyStyle — 한 컴포넌트의 styling 을 다른 컴포넌트들에 복사.\n *\n * 자주 쓰는 패턴: \"MOTOR1 처럼 다른 모터들도 같게 만들어줘\"\n * AI: getComponentByRefid(MOTOR1) 또는 getComponentStyle(MOTOR1) → copyStyle(...)\n *\n * aspects 인자로 어느 styling 영역만 복사할지 선택 가능 — 부분 통일 (예: 색만 통일).\n *\n * 입력 — sourceRefid 의 실제 styling 값을 미리 알 필요 없음. server 가 components\n * 에서 source 찾아 styling 추출 후 target 들에 patch 생성.\n */\nimport type {
|
|
1
|
+
{"version":3,"file":"copy-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/copy-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * copyStyle — 한 컴포넌트의 styling 을 다른 컴포넌트들에 복사.\n *\n * 자주 쓰는 패턴: \"MOTOR1 처럼 다른 모터들도 같게 만들어줘\"\n * AI: getComponentByRefid(MOTOR1) 또는 getComponentStyle(MOTOR1) → copyStyle(...)\n *\n * aspects 인자로 어느 styling 영역만 복사할지 선택 가능 — 부분 통일 (예: 색만 통일).\n *\n * 입력 — sourceRefid 의 실제 styling 값을 미리 알 필요 없음. server 가 components\n * 에서 source 찾아 styling 추출 후 target 들에 patch 생성.\n */\nimport type { SceneEditOp } from '@operato/scene-ops'\n\nexport interface CopyStyleInput {\n sourceRefid: number\n targetRefids: number[]\n aspects?: Array<'fill' | 'stroke' | 'shadow' | 'material' | 'font' | 'opacity' | 'visibility'>\n}\n\nexport interface OpsResult {\n ops: SceneEditOp[]\n errors: string[]\n}\n\nconst ASPECT_FIELDS: Record<string, string[]> = {\n fill: ['fillStyle'],\n stroke: ['strokeStyle'],\n shadow: ['shadow'],\n material: ['material3d'],\n font: [\n 'fontFamily',\n 'fontSize',\n 'fontWeight',\n 'fontStyle',\n 'fontColor',\n 'textAlign',\n 'textBaseline',\n 'lineHeight'\n ],\n opacity: ['alpha'],\n visibility: ['hidden']\n}\n\nconst DEFAULT_ASPECTS: Array<keyof typeof ASPECT_FIELDS> = [\n 'fill',\n 'stroke',\n 'shadow',\n 'material',\n 'font',\n 'opacity'\n // visibility 는 default 에서 제외 — 의도 없이 hidden 복사 위험\n]\n\n/**\n * input + components 배열 → SceneEditOp[].\n *\n * components 는 board 의 모든 컴포넌트 (caller 가 전달). source 찾아 styling 추출.\n */\nexport function buildCopyStyleOps(\n input: CopyStyleInput,\n components: ReadonlyArray<any>\n): OpsResult {\n if (typeof input.sourceRefid !== 'number') {\n return { ops: [], errors: ['sourceRefid (number) required'] }\n }\n if (!Array.isArray(input.targetRefids) || input.targetRefids.length === 0) {\n return { ops: [], errors: ['targetRefids must be a non-empty array'] }\n }\n const targets = input.targetRefids.filter(\n n => typeof n === 'number' && Number.isFinite(n) && n !== input.sourceRefid\n )\n if (targets.length === 0) {\n return { ops: [], errors: ['No valid target refids (excluding source)'] }\n }\n\n const source = components.find(\n c => c && typeof c.refid === 'number' && c.refid === input.sourceRefid\n )\n if (!source) {\n return {\n ops: [],\n errors: [`Source component refid=${input.sourceRefid} not found`]\n }\n }\n\n const aspects = input.aspects && input.aspects.length > 0 ? input.aspects : DEFAULT_ASPECTS\n const validAspects = Object.keys(ASPECT_FIELDS)\n const unknown = aspects.filter(a => !validAspects.includes(a))\n if (unknown.length > 0) {\n return {\n ops: [],\n errors: [`Unknown aspect(s): ${unknown.join(', ')}. Valid: ${validAspects.join('/')}`]\n }\n }\n\n // source 에서 aspect 별 필드 추출 → patch 합치기\n const patch: any = {}\n for (const aspect of aspects) {\n const fields = ASPECT_FIELDS[aspect]\n for (const field of fields) {\n if (source[field] !== undefined) {\n patch[field] = source[field]\n }\n }\n }\n\n if (Object.keys(patch).length === 0) {\n return { ops: [], errors: ['Source has no styling for the requested aspects'] }\n }\n\n return {\n ops: targets.map(refid => ({ op: 'modify', refid, patch })),\n errors: []\n }\n}\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* setOpacity: state.alpha (컴포넌트 전체 알파, 0..1)
|
|
6
6
|
* setVisible: state.hidden 토글 (true → 숨김)
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
8
|
+
import type { SceneEditOp } from '@operato/scene-ops';
|
|
9
9
|
export interface SetShadowInput {
|
|
10
10
|
refids: number[];
|
|
11
11
|
/** synthetic — false 면 blur=0 + color=transparent 매핑 (drawer 가 안 그림). 모델에 enabled 필드 없음. */
|
|
@@ -29,7 +29,7 @@ export interface SetVisibleInput {
|
|
|
29
29
|
visible: boolean;
|
|
30
30
|
}
|
|
31
31
|
export interface OpsResult {
|
|
32
|
-
ops:
|
|
32
|
+
ops: SceneEditOp[];
|
|
33
33
|
errors: string[];
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/effect-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setShadow / setOpacity / setVisible — 효과 / 가시성 / 투명도.\n *\n * setShadow: state.shadow (color/blur/offsetX/offsetY)\n * setOpacity: state.alpha (컴포넌트 전체 알파, 0..1)\n * setVisible: state.hidden 토글 (true → 숨김)\n */\nimport type {
|
|
1
|
+
{"version":3,"file":"effect-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/effect-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setShadow / setOpacity / setVisible — 효과 / 가시성 / 투명도.\n *\n * setShadow: state.shadow (color/blur/offsetX/offsetY)\n * setOpacity: state.alpha (컴포넌트 전체 알파, 0..1)\n * setVisible: state.hidden 토글 (true → 숨김)\n */\nimport type { SceneEditOp } from '@operato/scene-ops'\nimport { normalizeColor, validateColor } from './color-utils'\n\nexport interface SetShadowInput {\n refids: number[]\n /** synthetic — false 면 blur=0 + color=transparent 매핑 (drawer 가 안 그림). 모델에 enabled 필드 없음. */\n enabled?: boolean\n /** state.shadow.color */\n color?: string\n /** Blur radius (CSS box-shadow 의 blur-radius, Canvas 의 shadowBlur). state.shadow.blur. */\n blur?: number\n /** Horizontal offset (CSS box-shadow 의 offset-x, Canvas 의 shadowOffsetX). state.shadow.offsetX. */\n offsetX?: number\n /** Vertical offset (CSS box-shadow 의 offset-y, Canvas 의 shadowOffsetY). state.shadow.offsetY. */\n offsetY?: number\n}\n\nexport interface SetOpacityInput {\n refids: number[]\n /** state.alpha 의 canonical 이름. 0..1. */\n alpha: number\n}\n\nexport interface SetVisibleInput {\n refids: number[]\n visible: boolean\n}\n\nexport interface OpsResult {\n ops: SceneEditOp[]\n errors: string[]\n}\n\n/**\n * setShadow — CSS box-shadow / Canvas API 표준 이름 (color/offsetX/offsetY/blur) 그대로\n * state.shadow 에 set. things-scene drawer 는 옛 이름 (left/top/blurSize) 도\n * backwards-compat 으로 읽지만 (effect/shadow.ts:36-47), 새 모델은 canonical 만 사용.\n *\n * synthetic enabled=false → blur 0 + transparent 매핑 (drawer 가 안 그림).\n * 모델에 enabled 필드는 없음.\n */\nexport function buildSetShadowOps(input: SetShadowInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }\n\n const noOp =\n input.enabled === undefined &&\n input.color === undefined &&\n input.blur === undefined &&\n input.offsetX === undefined &&\n input.offsetY === undefined\n if (noOp) return { ops: [], errors: ['No shadow changes specified'] }\n\n if (input.enabled === false) {\n // synthetic — false 면 blur 0 + transparent 으로 변환 (drawer 가 안 그림)\n const patch = { shadow: { blur: 0, color: 'transparent' } }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n }\n\n if (input.color !== undefined) {\n const v = validateColor(input.color)\n if (!v.valid) return { ops: [], errors: [v.error ?? 'Invalid shadow color'] }\n }\n if (input.blur !== undefined && (input.blur < 0 || !Number.isFinite(input.blur))) {\n return { ops: [], errors: [`Invalid blur: ${input.blur}`] }\n }\n if (input.offsetX !== undefined && !Number.isFinite(input.offsetX)) {\n return { ops: [], errors: [`Invalid offsetX: ${input.offsetX}`] }\n }\n if (input.offsetY !== undefined && !Number.isFinite(input.offsetY)) {\n return { ops: [], errors: [`Invalid offsetY: ${input.offsetY}`] }\n }\n\n // CSS / Canvas 표준 canonical 이름 — rename 없음\n const sh: Record<string, number | string> = {}\n // validateColor 가 통과한 색만 도달 — normalizeColor 가 여기서 null 반환 안 함\n if (input.color !== undefined) sh.color = normalizeColor(input.color)!\n if (input.blur !== undefined) sh.blur = input.blur\n if (input.offsetX !== undefined) sh.offsetX = input.offsetX\n if (input.offsetY !== undefined) sh.offsetY = input.offsetY\n\n const patch = { shadow: sh }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n\nexport function buildSetOpacityOps(input: SetOpacityInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n if (typeof input.alpha !== 'number' || input.alpha < 0 || input.alpha > 1) {\n return { ops: [], errors: [`alpha must be 0..1: ${input.alpha}`] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }\n\n // canonical 이름 그대로\n const patch = { alpha: input.alpha }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n\nexport function buildSetVisibleOps(input: SetVisibleInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n if (typeof input.visible !== 'boolean') {\n return { ops: [], errors: ['visible must be a boolean'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }\n\n // hidden 은 visible 의 inverse\n const patch = { hidden: !input.visible }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n"]}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture
|
|
18
18
|
* 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제는 자체 해결.
|
|
19
19
|
*/
|
|
20
|
-
import type {
|
|
20
|
+
import type { SceneEditOp } from '@operato/scene-ops';
|
|
21
21
|
export interface SetFillInput {
|
|
22
22
|
refids: number[];
|
|
23
23
|
mode?: 'none' | 'solid' | 'gradient' | 'pattern';
|
|
@@ -46,11 +46,11 @@ export interface SetFillInput {
|
|
|
46
46
|
opacity?: number;
|
|
47
47
|
}
|
|
48
48
|
export interface SetFillResult {
|
|
49
|
-
ops:
|
|
49
|
+
ops: SceneEditOp[];
|
|
50
50
|
errors: string[];
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* setFill 입력 검증 +
|
|
53
|
+
* setFill 입력 검증 + SceneEditOp[] 생성.
|
|
54
54
|
*
|
|
55
55
|
* 기본 정책:
|
|
56
56
|
* - refids 비어있거나 mode 도 색도 없으면 에러
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fill-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/fill-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setFill — 컴포넌트 배경 (state.fillStyle) 변경.\n *\n * 4 modes 지원: none / solid / gradient / pattern.\n *\n * 출력 정책 — refid 당 1 op:\n * - solid: fillStyle = '#rrggbb' or 'rgba(...)' (string)\n * - gradient: fillStyle = { type:'gradient', gradientType, colorStops, rotation, ... }\n * - pattern: fillStyle = { type:'pattern', image, ... }\n * - none: fillStyle = { type:'none' }\n * - mode 미지정 + color 만: 부분 update (string 또는 object 병합)\n *\n * things-scene 실제 데이터 형식 (drawer/fill.ts 검증):\n * - gradient: { type, gradientType, colorStops: [{position, color}], rotation (라디안) }\n * - rotation 은 라디안 — `Math.cos(fRotation)` 직접 사용\n *\n * 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture\n * 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제는 자체 해결.\n */\nimport type { BoardEditOp } from '../types'\nimport { parseColor, normalizeColor, validateColor } from './color-utils'\n\nexport interface SetFillInput {\n refids: number[]\n mode?: 'none' | 'solid' | 'gradient' | 'pattern'\n // solid\n color?: string\n // gradient\n gradientType?: 'linear' | 'radial'\n /** colorStops — things-scene 의 canonical 필드명 그대로. position 0..1, color hex/rgb/named. */\n colorStops?: Array<{ color: string; position: number }>\n /** 회전 — 입력은 degrees (사용자/AI 친화), 내부 변환 후 things-scene 의 라디안으로 저장. */\n rotation?: number\n // pattern (drawer/fill.ts:95~ 검증)\n image?: string\n /** pattern: 영역에 맞춰 비율 조정 (true) vs 원본 크기 사용 (false). */\n fitPattern?: boolean\n /** pattern: 반복 안 함 (true) — 1회만 그림. */\n noRepeat?: boolean\n /** pattern: 정렬. fitPattern=false 일 때만 적용. */\n align?:\n | 'center'\n | 'left-top'\n | 'left-bottom'\n | 'right-top'\n | 'right-bottom'\n | 'left'\n | 'right'\n | 'top'\n | 'bottom'\n /** pattern: 영역 내 오프셋 (fitPattern=false 일 때). */\n offsetX?: number\n offsetY?: number\n /** pattern: 패턴 크기 (기본은 이미지 원본 크기). */\n patternWidth?: number\n patternHeight?: number\n // 공통\n opacity?: number // 0..1\n}\n\nexport interface SetFillResult {\n ops: BoardEditOp[]\n errors: string[] // refid 별 에러 (성공한 것은 op 만 발사)\n}\n\n/**\n * setFill 입력 검증 + BoardEditOp[] 생성.\n *\n * 기본 정책:\n * - refids 비어있거나 mode 도 색도 없으면 에러\n * - 색상 입력은 normalizeColor 통과해야 적용\n * - mode='gradient' 인데 stops 없으면 자동 생성 (color → 어두운 변형)\n */\nexport function buildSetFillOps(input: SetFillInput): SetFillResult {\n const errors: string[] = []\n const ops: BoardEditOp[] = []\n\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array of numbers'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) {\n return { ops: [], errors: ['No valid numeric refids in input'] }\n }\n\n // mode 미지정 + 어떤 styling 인자도 없으면 무의미\n if (\n !input.mode &&\n !input.color &&\n !input.colorStops &&\n !input.image &&\n typeof input.opacity !== 'number'\n ) {\n return {\n ops: [],\n errors: ['No fill changes specified. Provide mode and/or color/opacity/etc.']\n }\n }\n\n // 색상 검증\n if (input.color !== undefined) {\n const v = validateColor(input.color)\n if (!v.valid) {\n errors.push(v.error ?? `Invalid color: ${input.color}`)\n return { ops: [], errors }\n }\n }\n\n // gradient stops 검증\n if (input.colorStops) {\n for (const s of input.colorStops) {\n if (typeof s.position !== 'number' || s.position < 0 || s.position > 1) {\n errors.push(`Gradient stop position out of [0,1]: ${s.position}`)\n return { ops: [], errors }\n }\n const v = validateColor(s.color)\n if (!v.valid) {\n errors.push(`Invalid gradient stop color: ${s.color}`)\n return { ops: [], errors }\n }\n }\n }\n\n // opacity 검증\n if (typeof input.opacity === 'number') {\n if (input.opacity < 0 || input.opacity > 1) {\n errors.push(`Opacity out of [0,1]: ${input.opacity}`)\n return { ops: [], errors }\n }\n }\n\n // pattern — image 필수\n if (input.mode === 'pattern' && !input.image) {\n errors.push(`Mode 'pattern' requires 'image' (URL or asset ref)`)\n return { ops: [], errors }\n }\n\n // 각 refid 별 patch 생성\n const patch = buildFillPatch(input)\n if (!patch) {\n return { ops: [], errors: ['Failed to build fill patch'] }\n }\n\n // 단일 op — apply-patch 의 deep merge 가 자연스럽게 처리:\n // - 새 fillStyle 이 string (solid) → 기존 object 통째 replace (typeof 매칭 X)\n // - 새 fillStyle 이 object → 같은 key (colorStops 배열 등) 는 array replace 됨\n // - 기존 부가 필드 (rotation 등) 가 새 patch 에 없으면 유지 — partial update 의도\n // 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture trigger 해\n // 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제 자체가 해결됐음.\n for (const refid of refids) {\n ops.push({ op: 'modify', refid, patch })\n }\n return { ops, errors }\n}\n\n/**\n * input → fillStyle patch 객체 생성.\n *\n * Mode 명시 시: fillStyle 통째 교체 → patch = { fillStyle: {...새 객체} }\n * Mode 미지정 시: 기존 fillStyle 위에 색/opacity 만 병합 → patch = { fillStyle: { ...덮어쓸 필드 } }\n * (apply-patch 의 deep-merge 가 처리. fillStyle 이 객체라면.)\n *\n * mode='none' 의 경우 fillStyle 을 falsy 로 — 빈 객체 또는 type:'none' 명시.\n */\nfunction buildFillPatch(input: SetFillInput): { fillStyle: any } | null {\n const { mode } = input\n\n if (mode === 'none') {\n // fillStyle 제거 의도 — null 시맨틱 활용 (apply-patch 가 키 자체를 delete).\n // drawer/fill.ts: fillStyle 없으면 falsy → 채우지 않음.\n return { fillStyle: null as any }\n }\n\n if (mode === 'solid') {\n // things-scene drawer/fill.ts:17 — fillStyle 이 string 이면 그대로 색상.\n // 객체 형식 {type:'solid', color} 은 인터페이스에만 있는 stale.\n // opacity 는 색상에 alpha 채널로 embedding (rgba).\n const color = input.color ? normalizeColor(input.color) : undefined\n if (!color) return null\n let fillStyle: string = color\n if (typeof input.opacity === 'number' && input.opacity < 1) {\n fillStyle = withAlpha(color, input.opacity)\n }\n return { fillStyle: fillStyle as any }\n }\n\n if (mode === 'gradient') {\n let stops = input.colorStops\n // stops 미지정 시 — color 와 자동 생성 어두운 변형 (간단한 fallback)\n if ((!stops || stops.length === 0) && input.color) {\n const hex = normalizeColor(input.color)\n if (hex) {\n stops = [\n { color: hex, position: 0 },\n { color: darken(hex, 0.4), position: 1 }\n ]\n }\n }\n if (!stops || stops.length < 2) {\n // gradient 인데 stops 부족 — 호출자가 명시 안 하면 만들어 주지 않음\n return null\n }\n // things-scene 의 실제 필드: colorStops [{position, color}]\n const colorStops = stops\n .map(s => ({ position: s.position, color: normalizeColor(s.color)! }))\n .filter(s => !!s.color)\n if (colorStops.length < 2) return null\n // rotation: 항상 명시 — drawer/fill.ts 가 default 없이 fillStyle.rotation 을 직접\n // Math.cos / Math.sin 에 넘기므로 undefined 면 NaN → createLinearGradient 크래시.\n // AI/사용자는 degrees 로 표현, 내부에서 라디안 변환. 미지정 시 0 default.\n const rotationRad =\n typeof input.rotation === 'number' ? (input.rotation * Math.PI) / 180 : 0\n const fs: any = {\n type: 'gradient',\n gradientType: input.gradientType ?? 'linear',\n colorStops,\n rotation: rotationRad\n }\n if (typeof input.opacity === 'number') fs.opacity = input.opacity\n return { fillStyle: fs }\n }\n\n if (mode === 'pattern') {\n if (!input.image) return null\n // things-scene 의 실제 pattern fillStyle (drawer/fill.ts:95~)\n const fs: any = { type: 'pattern', image: input.image }\n if (input.color) {\n const c = normalizeColor(input.color)\n if (c) fs.color = c // 패턴 뒤 배경색 (drawer 가 사용)\n }\n if (input.fitPattern !== undefined) fs.fitPattern = input.fitPattern\n if (input.noRepeat !== undefined) fs.noRepeat = input.noRepeat\n if (input.align) fs.align = input.align\n if (typeof input.offsetX === 'number') fs.offsetX = input.offsetX\n if (typeof input.offsetY === 'number') fs.offsetY = input.offsetY\n if (typeof input.patternWidth === 'number') fs.width = input.patternWidth\n if (typeof input.patternHeight === 'number') fs.height = input.patternHeight\n if (typeof input.opacity === 'number') fs.opacity = input.opacity\n return { fillStyle: fs }\n }\n\n // mode 미지정 — 부분 update.\n // things-scene 의 fillStyle 은 string (solid) 또는 object (gradient/pattern).\n // 부분 update 에서 색만 들어오면 solid string 으로 통째 set (가장 흔한 케이스).\n // (gradient 의 부분 update 는 mode='gradient' 명시 사용 — 모드 명시 시 full replace.)\n if (input.color) {\n const c = normalizeColor(input.color)\n if (!c) return null\n const fillStyle =\n typeof input.opacity === 'number' && input.opacity < 1 ? withAlpha(c, input.opacity) : c\n return { fillStyle: fillStyle as any }\n }\n // color 없이 opacity 만 — 부분 update 어려움 (string 인지 object 인지 모름).\n // 명확한 의도가 있으면 mode 명시 권장. 일단 무동작 (null).\n return null\n}\n\n/**\n * hex / 명명 색 + alpha (0..1) → rgba(r,g,b,a) 문자열.\n * Canvas API 의 fillStyle 은 rgba string 그대로 사용 가능.\n */\nfunction withAlpha(hexOrName: string, alpha: number): string {\n // transparent\n if (hexOrName === 'transparent') return 'rgba(0,0,0,0)'\n // hex 예상 (#rrggbb)\n if (hexOrName.startsWith('#') && hexOrName.length === 7) {\n const r = parseInt(hexOrName.slice(1, 3), 16)\n const g = parseInt(hexOrName.slice(3, 5), 16)\n const b = parseInt(hexOrName.slice(5, 7), 16)\n return `rgba(${r},${g},${b},${alpha})`\n }\n // 다른 형식이면 색을 그대로 두고 alpha 무시 (nullable 안전)\n return hexOrName\n}\n\n/**\n * 단순 hex 색상 어둡게 — gradient 자동 생성 시 fallback 두 번째 stop 용.\n * lightness 기반 정확도 < HSL 변환이 정확하지만 의존성 줄이려 RGB linear blend 로 대체.\n */\nfunction darken(hex: string, amount: number): string {\n const p = parseColor(hex)\n if (!p || p.hex === 'transparent') return hex\n const h = p.hex.slice(1) // remove #\n const r = parseInt(h.slice(0, 2), 16)\n const g = parseInt(h.slice(2, 4), 16)\n const b = parseInt(h.slice(4, 6), 16)\n const factor = 1 - Math.max(0, Math.min(1, amount))\n const r2 = Math.round(r * factor)\n const g2 = Math.round(g * factor)\n const b2 = Math.round(b * factor)\n const toHex = (n: number) => n.toString(16).padStart(2, '0')\n return `#${toHex(r2)}${toHex(g2)}${toHex(b2)}`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fill-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/fill-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setFill — 컴포넌트 배경 (state.fillStyle) 변경.\n *\n * 4 modes 지원: none / solid / gradient / pattern.\n *\n * 출력 정책 — refid 당 1 op:\n * - solid: fillStyle = '#rrggbb' or 'rgba(...)' (string)\n * - gradient: fillStyle = { type:'gradient', gradientType, colorStops, rotation, ... }\n * - pattern: fillStyle = { type:'pattern', image, ... }\n * - none: fillStyle = { type:'none' }\n * - mode 미지정 + color 만: 부분 update (string 또는 object 병합)\n *\n * things-scene 실제 데이터 형식 (drawer/fill.ts 검증):\n * - gradient: { type, gradientType, colorStops: [{position, color}], rotation (라디안) }\n * - rotation 은 라디안 — `Math.cos(fRotation)` 직접 사용\n *\n * 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture\n * 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제는 자체 해결.\n */\nimport type { SceneEditOp } from '@operato/scene-ops'\nimport { parseColor, normalizeColor, validateColor } from './color-utils'\n\nexport interface SetFillInput {\n refids: number[]\n mode?: 'none' | 'solid' | 'gradient' | 'pattern'\n // solid\n color?: string\n // gradient\n gradientType?: 'linear' | 'radial'\n /** colorStops — things-scene 의 canonical 필드명 그대로. position 0..1, color hex/rgb/named. */\n colorStops?: Array<{ color: string; position: number }>\n /** 회전 — 입력은 degrees (사용자/AI 친화), 내부 변환 후 things-scene 의 라디안으로 저장. */\n rotation?: number\n // pattern (drawer/fill.ts:95~ 검증)\n image?: string\n /** pattern: 영역에 맞춰 비율 조정 (true) vs 원본 크기 사용 (false). */\n fitPattern?: boolean\n /** pattern: 반복 안 함 (true) — 1회만 그림. */\n noRepeat?: boolean\n /** pattern: 정렬. fitPattern=false 일 때만 적용. */\n align?:\n | 'center'\n | 'left-top'\n | 'left-bottom'\n | 'right-top'\n | 'right-bottom'\n | 'left'\n | 'right'\n | 'top'\n | 'bottom'\n /** pattern: 영역 내 오프셋 (fitPattern=false 일 때). */\n offsetX?: number\n offsetY?: number\n /** pattern: 패턴 크기 (기본은 이미지 원본 크기). */\n patternWidth?: number\n patternHeight?: number\n // 공통\n opacity?: number // 0..1\n}\n\nexport interface SetFillResult {\n ops: SceneEditOp[]\n errors: string[] // refid 별 에러 (성공한 것은 op 만 발사)\n}\n\n/**\n * setFill 입력 검증 + SceneEditOp[] 생성.\n *\n * 기본 정책:\n * - refids 비어있거나 mode 도 색도 없으면 에러\n * - 색상 입력은 normalizeColor 통과해야 적용\n * - mode='gradient' 인데 stops 없으면 자동 생성 (color → 어두운 변형)\n */\nexport function buildSetFillOps(input: SetFillInput): SetFillResult {\n const errors: string[] = []\n const ops: SceneEditOp[] = []\n\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array of numbers'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) {\n return { ops: [], errors: ['No valid numeric refids in input'] }\n }\n\n // mode 미지정 + 어떤 styling 인자도 없으면 무의미\n if (\n !input.mode &&\n !input.color &&\n !input.colorStops &&\n !input.image &&\n typeof input.opacity !== 'number'\n ) {\n return {\n ops: [],\n errors: ['No fill changes specified. Provide mode and/or color/opacity/etc.']\n }\n }\n\n // 색상 검증\n if (input.color !== undefined) {\n const v = validateColor(input.color)\n if (!v.valid) {\n errors.push(v.error ?? `Invalid color: ${input.color}`)\n return { ops: [], errors }\n }\n }\n\n // gradient stops 검증\n if (input.colorStops) {\n for (const s of input.colorStops) {\n if (typeof s.position !== 'number' || s.position < 0 || s.position > 1) {\n errors.push(`Gradient stop position out of [0,1]: ${s.position}`)\n return { ops: [], errors }\n }\n const v = validateColor(s.color)\n if (!v.valid) {\n errors.push(`Invalid gradient stop color: ${s.color}`)\n return { ops: [], errors }\n }\n }\n }\n\n // opacity 검증\n if (typeof input.opacity === 'number') {\n if (input.opacity < 0 || input.opacity > 1) {\n errors.push(`Opacity out of [0,1]: ${input.opacity}`)\n return { ops: [], errors }\n }\n }\n\n // pattern — image 필수\n if (input.mode === 'pattern' && !input.image) {\n errors.push(`Mode 'pattern' requires 'image' (URL or asset ref)`)\n return { ops: [], errors }\n }\n\n // 각 refid 별 patch 생성\n const patch = buildFillPatch(input)\n if (!patch) {\n return { ops: [], errors: ['Failed to build fill patch'] }\n }\n\n // 단일 op — apply-patch 의 deep merge 가 자연스럽게 처리:\n // - 새 fillStyle 이 string (solid) → 기존 object 통째 replace (typeof 매칭 X)\n // - 새 fillStyle 이 object → 같은 key (colorStops 배열 등) 는 array replace 됨\n // - 기존 부가 필드 (rotation 등) 가 새 patch 에 없으면 유지 — partial update 의도\n // 이전 2-op (null + new) 방식은 중간 null 상태가 onchange / 3D texture trigger 해\n // 크래시 유발 → 제거. 정확한 colorStops 출력으로 stale 필드 문제 자체가 해결됐음.\n for (const refid of refids) {\n ops.push({ op: 'modify', refid, patch })\n }\n return { ops, errors }\n}\n\n/**\n * input → fillStyle patch 객체 생성.\n *\n * Mode 명시 시: fillStyle 통째 교체 → patch = { fillStyle: {...새 객체} }\n * Mode 미지정 시: 기존 fillStyle 위에 색/opacity 만 병합 → patch = { fillStyle: { ...덮어쓸 필드 } }\n * (apply-patch 의 deep-merge 가 처리. fillStyle 이 객체라면.)\n *\n * mode='none' 의 경우 fillStyle 을 falsy 로 — 빈 객체 또는 type:'none' 명시.\n */\nfunction buildFillPatch(input: SetFillInput): { fillStyle: any } | null {\n const { mode } = input\n\n if (mode === 'none') {\n // fillStyle 제거 의도 — null 시맨틱 활용 (apply-patch 가 키 자체를 delete).\n // drawer/fill.ts: fillStyle 없으면 falsy → 채우지 않음.\n return { fillStyle: null as any }\n }\n\n if (mode === 'solid') {\n // things-scene drawer/fill.ts:17 — fillStyle 이 string 이면 그대로 색상.\n // 객체 형식 {type:'solid', color} 은 인터페이스에만 있는 stale.\n // opacity 는 색상에 alpha 채널로 embedding (rgba).\n const color = input.color ? normalizeColor(input.color) : undefined\n if (!color) return null\n let fillStyle: string = color\n if (typeof input.opacity === 'number' && input.opacity < 1) {\n fillStyle = withAlpha(color, input.opacity)\n }\n return { fillStyle: fillStyle as any }\n }\n\n if (mode === 'gradient') {\n let stops = input.colorStops\n // stops 미지정 시 — color 와 자동 생성 어두운 변형 (간단한 fallback)\n if ((!stops || stops.length === 0) && input.color) {\n const hex = normalizeColor(input.color)\n if (hex) {\n stops = [\n { color: hex, position: 0 },\n { color: darken(hex, 0.4), position: 1 }\n ]\n }\n }\n if (!stops || stops.length < 2) {\n // gradient 인데 stops 부족 — 호출자가 명시 안 하면 만들어 주지 않음\n return null\n }\n // things-scene 의 실제 필드: colorStops [{position, color}]\n const colorStops = stops\n .map(s => ({ position: s.position, color: normalizeColor(s.color)! }))\n .filter(s => !!s.color)\n if (colorStops.length < 2) return null\n // rotation: 항상 명시 — drawer/fill.ts 가 default 없이 fillStyle.rotation 을 직접\n // Math.cos / Math.sin 에 넘기므로 undefined 면 NaN → createLinearGradient 크래시.\n // AI/사용자는 degrees 로 표현, 내부에서 라디안 변환. 미지정 시 0 default.\n const rotationRad =\n typeof input.rotation === 'number' ? (input.rotation * Math.PI) / 180 : 0\n const fs: any = {\n type: 'gradient',\n gradientType: input.gradientType ?? 'linear',\n colorStops,\n rotation: rotationRad\n }\n if (typeof input.opacity === 'number') fs.opacity = input.opacity\n return { fillStyle: fs }\n }\n\n if (mode === 'pattern') {\n if (!input.image) return null\n // things-scene 의 실제 pattern fillStyle (drawer/fill.ts:95~)\n const fs: any = { type: 'pattern', image: input.image }\n if (input.color) {\n const c = normalizeColor(input.color)\n if (c) fs.color = c // 패턴 뒤 배경색 (drawer 가 사용)\n }\n if (input.fitPattern !== undefined) fs.fitPattern = input.fitPattern\n if (input.noRepeat !== undefined) fs.noRepeat = input.noRepeat\n if (input.align) fs.align = input.align\n if (typeof input.offsetX === 'number') fs.offsetX = input.offsetX\n if (typeof input.offsetY === 'number') fs.offsetY = input.offsetY\n if (typeof input.patternWidth === 'number') fs.width = input.patternWidth\n if (typeof input.patternHeight === 'number') fs.height = input.patternHeight\n if (typeof input.opacity === 'number') fs.opacity = input.opacity\n return { fillStyle: fs }\n }\n\n // mode 미지정 — 부분 update.\n // things-scene 의 fillStyle 은 string (solid) 또는 object (gradient/pattern).\n // 부분 update 에서 색만 들어오면 solid string 으로 통째 set (가장 흔한 케이스).\n // (gradient 의 부분 update 는 mode='gradient' 명시 사용 — 모드 명시 시 full replace.)\n if (input.color) {\n const c = normalizeColor(input.color)\n if (!c) return null\n const fillStyle =\n typeof input.opacity === 'number' && input.opacity < 1 ? withAlpha(c, input.opacity) : c\n return { fillStyle: fillStyle as any }\n }\n // color 없이 opacity 만 — 부분 update 어려움 (string 인지 object 인지 모름).\n // 명확한 의도가 있으면 mode 명시 권장. 일단 무동작 (null).\n return null\n}\n\n/**\n * hex / 명명 색 + alpha (0..1) → rgba(r,g,b,a) 문자열.\n * Canvas API 의 fillStyle 은 rgba string 그대로 사용 가능.\n */\nfunction withAlpha(hexOrName: string, alpha: number): string {\n // transparent\n if (hexOrName === 'transparent') return 'rgba(0,0,0,0)'\n // hex 예상 (#rrggbb)\n if (hexOrName.startsWith('#') && hexOrName.length === 7) {\n const r = parseInt(hexOrName.slice(1, 3), 16)\n const g = parseInt(hexOrName.slice(3, 5), 16)\n const b = parseInt(hexOrName.slice(5, 7), 16)\n return `rgba(${r},${g},${b},${alpha})`\n }\n // 다른 형식이면 색을 그대로 두고 alpha 무시 (nullable 안전)\n return hexOrName\n}\n\n/**\n * 단순 hex 색상 어둡게 — gradient 자동 생성 시 fallback 두 번째 stop 용.\n * lightness 기반 정확도 < HSL 변환이 정확하지만 의존성 줄이려 RGB linear blend 로 대체.\n */\nfunction darken(hex: string, amount: number): string {\n const p = parseColor(hex)\n if (!p || p.hex === 'transparent') return hex\n const h = p.hex.slice(1) // remove #\n const r = parseInt(h.slice(0, 2), 16)\n const g = parseInt(h.slice(2, 4), 16)\n const b = parseInt(h.slice(4, 6), 16)\n const factor = 1 - Math.max(0, Math.min(1, amount))\n const r2 = Math.round(r * factor)\n const g2 = Math.round(g * factor)\n const b2 = Math.round(b * factor)\n const toHex = (n: number) => n.toString(16).padStart(2, '0')\n return `#${toHex(r2)}${toHex(g2)}${toHex(b2)}`\n}\n"]}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Material3D (`things-scene/src/threed/material-3d.ts`) 와 1:1 매핑.
|
|
5
5
|
* preset 만 줘도 변경 가능 (resolveMaterial3d 가 default 위 override 적용).
|
|
6
6
|
*/
|
|
7
|
-
import type {
|
|
7
|
+
import type { SceneEditOp } from '@operato/scene-ops';
|
|
8
8
|
declare const MATERIAL_PRESETS: readonly ["default", "metal", "glass", "plastic", "wood", "ceramic", "rubber", "custom"];
|
|
9
9
|
declare const SIDE_VALUES: readonly ["double", "front", "back"];
|
|
10
10
|
export interface SetMaterialInput {
|
|
@@ -24,7 +24,7 @@ export interface SetShadowPropertiesInput {
|
|
|
24
24
|
receiveShadow?: boolean;
|
|
25
25
|
}
|
|
26
26
|
export interface OpsResult {
|
|
27
|
-
ops:
|
|
27
|
+
ops: SceneEditOp[];
|
|
28
28
|
errors: string[];
|
|
29
29
|
}
|
|
30
30
|
export declare function buildSetMaterialOps(input: SetMaterialInput): OpsResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"material-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/material-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setMaterial / setShadowProperties — 3D PBR 재질 + 그림자 동작.\n *\n * Material3D (`things-scene/src/threed/material-3d.ts`) 와 1:1 매핑.\n * preset 만 줘도 변경 가능 (resolveMaterial3d 가 default 위 override 적용).\n */\nimport type {
|
|
1
|
+
{"version":3,"file":"material-tools.js","sourceRoot":"","sources":["../../../../server/service/styling/material-tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * setMaterial / setShadowProperties — 3D PBR 재질 + 그림자 동작.\n *\n * Material3D (`things-scene/src/threed/material-3d.ts`) 와 1:1 매핑.\n * preset 만 줘도 변경 가능 (resolveMaterial3d 가 default 위 override 적용).\n */\nimport type { SceneEditOp } from '@operato/scene-ops'\nimport { normalizeColor, validateColor } from './color-utils'\n\nconst MATERIAL_PRESETS = ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom'] as const\nconst SIDE_VALUES = ['double', 'front', 'back'] as const\n\nexport interface SetMaterialInput {\n refids: number[]\n preset?: typeof MATERIAL_PRESETS[number]\n metalness?: number // 0..1\n roughness?: number // 0..1\n emissive?: string\n emissiveIntensity?: number // 0..5\n opacity?: number // 0..1 (3D-only)\n envMapIntensity?: number // 0..3\n side?: typeof SIDE_VALUES[number]\n}\n\nexport interface SetShadowPropertiesInput {\n refids: number[]\n castShadow?: boolean\n receiveShadow?: boolean\n}\n\nexport interface OpsResult {\n ops: SceneEditOp[]\n errors: string[]\n}\n\nexport function buildSetMaterialOps(input: SetMaterialInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }\n\n const noOp = !(\n input.preset ||\n input.metalness !== undefined ||\n input.roughness !== undefined ||\n input.emissive !== undefined ||\n input.emissiveIntensity !== undefined ||\n input.opacity !== undefined ||\n input.envMapIntensity !== undefined ||\n input.side !== undefined\n )\n if (noOp) return { ops: [], errors: ['No material changes specified'] }\n\n if (input.preset && !MATERIAL_PRESETS.includes(input.preset)) {\n return {\n ops: [],\n errors: [`Unknown preset: '${input.preset}'. Valid: ${MATERIAL_PRESETS.join('/')}`]\n }\n }\n if (input.metalness !== undefined && (input.metalness < 0 || input.metalness > 1)) {\n return { ops: [], errors: [`metalness out of [0,1]: ${input.metalness}`] }\n }\n if (input.roughness !== undefined && (input.roughness < 0 || input.roughness > 1)) {\n return { ops: [], errors: [`roughness out of [0,1]: ${input.roughness}`] }\n }\n if (input.opacity !== undefined && (input.opacity < 0 || input.opacity > 1)) {\n return { ops: [], errors: [`opacity out of [0,1]: ${input.opacity}`] }\n }\n if (input.emissiveIntensity !== undefined && (input.emissiveIntensity < 0 || input.emissiveIntensity > 5)) {\n return { ops: [], errors: [`emissiveIntensity out of [0,5]: ${input.emissiveIntensity}`] }\n }\n if (input.envMapIntensity !== undefined && (input.envMapIntensity < 0 || input.envMapIntensity > 3)) {\n return { ops: [], errors: [`envMapIntensity out of [0,3]: ${input.envMapIntensity}`] }\n }\n if (input.emissive !== undefined) {\n const v = validateColor(input.emissive)\n if (!v.valid) return { ops: [], errors: [v.error ?? 'Invalid emissive color'] }\n }\n if (input.side && !SIDE_VALUES.includes(input.side)) {\n return { ops: [], errors: [`Unknown side: '${input.side}'. Valid: ${SIDE_VALUES.join('/')}`] }\n }\n\n const m: any = {}\n if (input.preset) m.preset = input.preset\n if (input.metalness !== undefined) m.metalness = input.metalness\n if (input.roughness !== undefined) m.roughness = input.roughness\n if (input.emissive !== undefined) m.emissive = normalizeColor(input.emissive)\n if (input.emissiveIntensity !== undefined) m.emissiveIntensity = input.emissiveIntensity\n if (input.opacity !== undefined) m.opacity = input.opacity\n if (input.envMapIntensity !== undefined) m.envMapIntensity = input.envMapIntensity\n if (input.side) m.side = input.side\n\n const patch = { material3d: m }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n\nexport function buildSetShadowPropertiesOps(input: SetShadowPropertiesInput): OpsResult {\n if (!Array.isArray(input.refids) || input.refids.length === 0) {\n return { ops: [], errors: ['refids must be a non-empty array'] }\n }\n const refids = input.refids.filter(n => typeof n === 'number' && Number.isFinite(n))\n if (refids.length === 0) return { ops: [], errors: ['No valid numeric refids'] }\n\n const noOp = input.castShadow === undefined && input.receiveShadow === undefined\n if (noOp) return { ops: [], errors: ['Specify castShadow and/or receiveShadow'] }\n\n const m: any = {}\n if (input.castShadow !== undefined) m.castShadow = input.castShadow\n if (input.receiveShadow !== undefined) m.receiveShadow = input.receiveShadow\n\n const patch = { material3d: m }\n return { ops: refids.map(refid => ({ op: 'modify', refid, patch })), errors: [] }\n}\n"]}
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* STYLING_TOOLS 는 초기엔 비어있고, 한 tool 씩 점진 이전한다. 각 단계마다
|
|
13
13
|
* 기존 hardcoded 정의/dispatch 와 byte-단위 동등성을 단위 테스트가 잠금.
|
|
14
14
|
*
|
|
15
|
-
* 비-styling write tool (addComponent /
|
|
15
|
+
* 비-styling write tool (addComponent / modifyScene / alignComponents 등) 은
|
|
16
16
|
* 본 registry 의 영역이 아니다 — 의미·구조가 styling 패턴과 달라 별도 dispatch.
|
|
17
17
|
*/
|
|
18
|
-
import type {
|
|
18
|
+
import type { SceneEditOp } from '@operato/scene-ops';
|
|
19
19
|
export interface OpsResult {
|
|
20
|
-
ops:
|
|
20
|
+
ops: SceneEditOp[];
|
|
21
21
|
errors: string[];
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
@@ -77,15 +77,15 @@ export declare function buildStylingToolDefinitions(registry?: ReadonlyArray<Sty
|
|
|
77
77
|
parameters: any;
|
|
78
78
|
}>;
|
|
79
79
|
/**
|
|
80
|
-
* AIToolCall name 에 매칭되는 styling tool 의 builder 호출 →
|
|
80
|
+
* AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → SceneEditOp[] | null.
|
|
81
81
|
*
|
|
82
82
|
* 반환:
|
|
83
83
|
* - 매칭 spec 없음 → null (caller 가 다른 카테고리 dispatch 시도)
|
|
84
84
|
* - 빈 ops (validation 실패 등) → null (assistant.ts 의 기존 동작과 동일)
|
|
85
|
-
* - 정상 →
|
|
85
|
+
* - 정상 → SceneEditOp[]
|
|
86
86
|
*
|
|
87
87
|
* components 인자는 copyStyle 같은 source-lookup 필요 tool 에만 의미 있음.
|
|
88
88
|
*/
|
|
89
|
-
export declare function dispatchStylingTool(name: string, args: any, components?: ReadonlyArray<any>, registry?: ReadonlyArray<StylingToolSpec>):
|
|
89
|
+
export declare function dispatchStylingTool(name: string, args: any, components?: ReadonlyArray<any>, registry?: ReadonlyArray<StylingToolSpec>): SceneEditOp[] | null;
|
|
90
90
|
/** name 이 styling tool 로 등록되어 있는가. */
|
|
91
91
|
export declare function isStylingTool(name: string, registry?: ReadonlyArray<StylingToolSpec>): boolean;
|
|
@@ -322,12 +322,12 @@ export function buildStylingToolDefinitions(registry = STYLING_TOOLS) {
|
|
|
322
322
|
}));
|
|
323
323
|
}
|
|
324
324
|
/**
|
|
325
|
-
* AIToolCall name 에 매칭되는 styling tool 의 builder 호출 →
|
|
325
|
+
* AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → SceneEditOp[] | null.
|
|
326
326
|
*
|
|
327
327
|
* 반환:
|
|
328
328
|
* - 매칭 spec 없음 → null (caller 가 다른 카테고리 dispatch 시도)
|
|
329
329
|
* - 빈 ops (validation 실패 등) → null (assistant.ts 의 기존 동작과 동일)
|
|
330
|
-
* - 정상 →
|
|
330
|
+
* - 정상 → SceneEditOp[]
|
|
331
331
|
*
|
|
332
332
|
* components 인자는 copyStyle 같은 source-lookup 필요 tool 에만 의미 있음.
|
|
333
333
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../server/service/styling/registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Styling tool registry — 선언형 단일 진실 (single source of truth).\n *\n * 동기:\n * 기존 styling tool 추가 시 4-5곳 (WRITE_TOOL_NAMES set / tools 배열 정의 /\n * dispatch case / validation case / system prompt 안내) 에 같은 정보가 산재해\n * 동기화 누락 위험. 신규 tool 마다 4곳 boilerplate.\n *\n * 본 registry 가 한 곳에 모음 — 위 모든 사용처가 STYLING_TOOLS 에서 자동 도출.\n *\n * 마이그레이션:\n * STYLING_TOOLS 는 초기엔 비어있고, 한 tool 씩 점진 이전한다. 각 단계마다\n * 기존 hardcoded 정의/dispatch 와 byte-단위 동등성을 단위 테스트가 잠금.\n *\n * 비-styling write tool (addComponent / modifyBoard / alignComponents 등) 은\n * 본 registry 의 영역이 아니다 — 의미·구조가 styling 패턴과 달라 별도 dispatch.\n */\nimport type { BoardEditOp } from '../types'\nimport { buildSetFillOps } from './fill-tools'\nimport { buildSetStrokeOps } from './stroke-tools'\nimport { buildSetTextOps, buildSetFontOps } from './text-tools'\nimport { buildSetShadowOps, buildSetOpacityOps, buildSetVisibleOps } from './effect-tools'\nimport { buildSetMaterialOps, buildSetShadowPropertiesOps } from './material-tools'\nimport { buildClearStyleOps } from './clear-tools'\nimport { buildCopyStyleOps } from './copy-tools'\n\nexport interface OpsResult {\n ops: BoardEditOp[]\n errors: string[]\n}\n\n/**\n * 한 styling tool 의 모든 메타데이터 + 빌더.\n *\n * description 과 themeNote 는 별도 — 자동 합성될 때 description 본문은 영문/기능\n * 위주로 짧게, themeNote 는 한국어 ⚠ 정책 단문. 분리해 두면 미래에 i18n / 정책\n * 일괄 변경 시 검색-치환이 쉽다.\n */\nexport interface StylingToolSpec {\n /** AIToolCall.name 과 매칭. unique. */\n name: string\n /** 기능 설명 (영문 위주). */\n description: string\n /**\n * 선택 — ⚠ 정책 / 테마 가이드 단문. 빌드 시 description 끝에 자동 부착.\n * 예: '⚠ 테마 요청 시 width/dash 건드리지 말 것.'\n */\n themeNote?: string\n /** JSON Schema for parameters — LLM tool schema 에 그대로 노출. */\n schema: any\n /**\n * AIToolCall.arguments → OpsResult 변환.\n * components 는 copyStyle 처럼 source lookup 필요한 tool 만 사용. 다른 tool 은\n * 두 번째 인자 무시하면 됨.\n */\n builder: (args: any, components: ReadonlyArray<any>) => OpsResult\n}\n\n/**\n * 등록된 styling tool 들. 한 tool 씩 점진 이전 중.\n *\n * 마이그레이션 순서: setFill → setStroke → setText/setFont → setShadow /\n * setOpacity / setVisible → setMaterial / setShadowProperties → clearStyle\n * → copyStyle. 각 이전 commit 은 별도 — bisect 로 회귀 추적 용이.\n *\n * spec 작성 규약:\n * - description 은 영문 / 기능 설명\n * - themeNote 는 ⚠ 시작 한국어 정책 단문 (테마 가이드 / 호출 금지 등)\n * - schema 는 LLM 에 그대로 노출되는 JSON Schema\n * - builder 는 builder 함수 직접 사용 (signature 호환). components 인자\n * 안 쓰는 tool 도 그대로 통과.\n */\nexport const STYLING_TOOLS: StylingToolSpec[] = [\n {\n name: 'setFill',\n description:\n \"Set a component's BACKGROUND/FILL (fillStyle). Use this for fill changes — handles 4 modes (none/solid/gradient/pattern/image) safely with mode-aware updates. Prefer over modifyComponentByRefid for fill-only changes.\\n\\nMode rules:\\n - mode='solid': requires `color`\\n - mode='gradient': provide `colorStops` (array of {color,position}) or fall back from `color` (auto-generates 2 stops)\\n - mode='pattern' or 'image': requires `image` (URL or asset ref)\\n - mode='none': removes fill\\n - omitting `mode`: partial update (only color/opacity) — preserves existing mode and other fields.\",\n schema: {\n type: 'object',\n properties: {\n refids: {\n type: 'array',\n items: { type: 'number' },\n description: 'Components to update. things-scene auto-assigned numeric handles.'\n },\n mode: {\n type: 'string',\n enum: ['none', 'solid', 'gradient', 'pattern', 'image'],\n description: 'Fill mode dispatcher (synthetic — not stored on the model). Omit for partial update of color/opacity within the existing mode.'\n },\n color: {\n type: 'string',\n description: \"Color (hex '#ff0000' / rgb / named 'red' / semantic 'warm-red'). Required for mode='solid'. Output: top-level fillStyle string when mode=solid; nested fillStyle.color when mode=pattern (background tint).\"\n },\n gradientType: {\n type: 'string',\n enum: ['linear', 'radial'],\n description: 'Gradient kind (when mode=gradient). Default linear. Stored at fillStyle.gradientType.'\n },\n colorStops: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n color: { type: 'string' },\n position: { type: 'number', description: '0..1' }\n },\n required: ['color', 'position']\n },\n description: 'Gradient color stops (when mode=gradient). At least 2. Stored at fillStyle.colorStops (canonical things-scene field name).'\n },\n rotation: {\n type: 'number',\n description: 'Gradient rotation in degrees (when mode=gradient). Default 0. Internally converted to radians and stored at fillStyle.rotation.'\n },\n image: {\n type: 'string',\n description: 'Image URL or asset reference (when mode=pattern or mode=image). Stored at fillStyle.image.'\n },\n opacity: {\n type: 'number',\n description: 'Fill alpha 0..1. For mode=solid, encoded as rgba alpha channel in the fillStyle string. For mode=gradient/pattern, stored at fillStyle.opacity.'\n }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetFillOps(args)\n },\n {\n name: 'setStroke',\n description:\n \"Set component STROKE / BORDER. Canonical things-scene fields (all top-level on state): strokeStyle (color string), lineWidth (number), lineDash (preset name string), lineCap, lineJoin. dash 는 CSS-친화 alias 도 매핑 — 'dashed'/'dotted'/'dashdot' → things-scene preset. 'solid'/'dash'/'square-dot'/'round-dot'/'dash-dot'/'long-dash'/'long-dash-dot'/'long-dash-dot-dot' 도 가능.\",\n themeNote:\n '⚠ 테마 요청 (북유럽/사이버펑크/미니멀 등) 시 lineWidth/dash/lineCap/lineJoin 은 건드리지 말 것 — 선의 두께·점선 패턴은 디자인 의도를 가진 *구조* 다. strokeStyle (색상) 만 테마 팔레트에 맞춰 변경. 사용자가 명시적으로 두께/점선 변경을 요청한 경우에만 lineWidth/dash 사용.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n enabled: {\n type: 'boolean',\n description: 'Synthetic helper — false maps to lineWidth=0 (drawer skips). Not stored on the model itself.'\n },\n strokeStyle: {\n type: 'string',\n description: 'Stroke color (CSS color string — hex / rgb / named / semantic alias). Stored at top-level state.strokeStyle.'\n },\n lineWidth: {\n type: 'number',\n description: 'Stroke width in pixels (0 = no stroke). Stored at top-level state.lineWidth.'\n },\n dash: {\n description:\n \"Dash pattern. CSS-친화 alias 자동 매핑: 'dashed'→'dash', 'dotted'→'square-dot', 'dashdot'→'dash-dot'. things-scene preset 직접 사용도 OK: 'solid' / 'round-dot' / 'square-dot' / 'dash' / 'dash-dot' / 'long-dash' / 'long-dash-dot' / 'long-dash-dot-dot'. Stored at top-level state.lineDash.\"\n },\n lineCap: {\n type: 'string',\n enum: ['butt', 'round', 'square'],\n description: 'Line cap style. Stored at top-level state.lineCap.'\n },\n lineJoin: {\n type: 'string',\n enum: ['miter', 'round', 'bevel'],\n description: 'Line join style. Stored at top-level state.lineJoin.'\n }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetStrokeOps(args)\n },\n {\n name: 'setText',\n description: 'Change component TEXT CONTENT (state.text). For font styling use setFont separately.',\n themeNote:\n '⚠ 텍스트는 *콘텐츠* 이지 *스타일* 이 아니다 — 테마 요청 (북유럽/사이버펑크/미니멀 등) 시 호출 금지. 사용자가 텍스트 변경을 명시한 경우에만 사용.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n text: { type: 'string' }\n },\n required: ['refids', 'text']\n },\n builder: (args, _components) => buildSetTextOps(args)\n },\n {\n name: 'setFont',\n description:\n 'Set FONT/TEXT STYLE (family / size / weight / style / color / align / baseline / lineHeight). For text content use setText.',\n themeNote:\n '⚠ 테마 요청 (북유럽/사이버펑크 등) 시 fontSize / fontFamily 는 건드리지 말 것 — 정보 위계와 레이아웃에 영향. fontColor 만 테마 팔레트에 맞춰 변경. 사용자가 명시적으로 폰트 크기/패밀리 변경을 요청한 경우에만 사용.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n fontFamily: { type: 'string' },\n fontSize: { type: 'number' },\n fontWeight: {\n description: \"'normal' | 'bold' | 'lighter' | 'bolder' | 100..900\"\n },\n fontStyle: { type: 'string', enum: ['normal', 'italic', 'oblique'] },\n fontColor: { type: 'string' },\n textAlign: {\n type: 'string',\n enum: ['left', 'center', 'right', 'start', 'end', 'justify']\n },\n textBaseline: {\n type: 'string',\n enum: ['top', 'middle', 'bottom', 'hanging', 'alphabetic', 'ideographic']\n },\n lineHeight: { type: 'number' }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetFontOps(args)\n },\n {\n name: 'setShadow',\n description:\n 'Set component SHADOW. Standard CSS box-shadow / Canvas shadow* fields (nested under state.shadow): color, blur, offsetX, offsetY. enabled=false maps to blur=0 + transparent (synthetic helper).',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n enabled: {\n type: 'boolean',\n description: 'Synthetic helper — false maps to blur=0 + color=transparent. Not stored as a model field.'\n },\n color: {\n type: 'string',\n description: 'Shadow color (CSS color string). Stored at state.shadow.color.'\n },\n blur: {\n type: 'number',\n description: 'Shadow blur radius in pixels (matches CSS box-shadow blur-radius / Canvas shadowBlur). Stored at state.shadow.blur.'\n },\n offsetX: {\n type: 'number',\n description: 'Shadow horizontal offset in pixels (matches CSS box-shadow offset-x / Canvas shadowOffsetX). Stored at state.shadow.offsetX.'\n },\n offsetY: {\n type: 'number',\n description: 'Shadow vertical offset in pixels (matches CSS box-shadow offset-y / Canvas shadowOffsetY). Stored at state.shadow.offsetY.'\n }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetShadowOps(args)\n },\n {\n name: 'setOpacity',\n description: 'Set component overall ALPHA (canonical things-scene field state.alpha, 0..1).',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n alpha: {\n type: 'number',\n description: 'Component overall opacity 0..1 (1 = opaque, 0 = invisible). Stored at top-level state.alpha.'\n }\n },\n required: ['refids', 'alpha']\n },\n builder: (args, _components) => buildSetOpacityOps(args)\n },\n {\n name: 'setVisible',\n description: 'Show/hide components (state.hidden = !visible).',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n visible: { type: 'boolean' }\n },\n required: ['refids', 'visible']\n },\n builder: (args, _components) => buildSetVisibleOps(args)\n },\n {\n name: 'setMaterial',\n description:\n 'Set 3D PBR MATERIAL (preset / metalness / roughness / emissive / opacity / envMapIntensity / side). Preset is semantic shortcut: metal/glass/plastic/wood/ceramic/rubber/default. preset alone resets, preset + properties applies overrides on top.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n preset: {\n type: 'string',\n enum: ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom']\n },\n metalness: { type: 'number', description: '0..1' },\n roughness: { type: 'number', description: '0..1' },\n emissive: { type: 'string', description: 'glow color (hex / named)' },\n emissiveIntensity: { type: 'number', description: '0..5' },\n opacity: { type: 'number', description: '0..1 (3D-only, separate from component alpha)' },\n envMapIntensity: { type: 'number', description: '0..3' },\n side: { type: 'string', enum: ['double', 'front', 'back'] }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetMaterialOps(args)\n },\n {\n name: 'setShadowProperties',\n description: 'Set 3D shadow behavior — castShadow / receiveShadow flags on material3d.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n castShadow: { type: 'boolean' },\n receiveShadow: { type: 'boolean' }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetShadowPropertiesOps(args)\n },\n {\n name: 'clearStyle',\n description:\n 'Reset specific styling aspects to default — useful before applying preset/binding. Aspects: fill/stroke/shadow/material/font/opacity.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n aspects: {\n type: 'array',\n items: {\n type: 'string',\n enum: ['fill', 'stroke', 'shadow', 'material', 'font', 'opacity']\n }\n }\n },\n required: ['refids', 'aspects']\n },\n builder: (args, _components) => buildClearStyleOps(args)\n },\n {\n name: 'copyStyle',\n description:\n \"Copy STYLING from one component to others. Use for 'make these components look like that one' patterns. aspects can limit which styling areas to copy (default: all except visibility). Source's styling is read server-side — you don't need getComponentStyle first.\",\n schema: {\n type: 'object',\n properties: {\n sourceRefid: { type: 'number', description: 'Component to copy from' },\n targetRefids: {\n type: 'array',\n items: { type: 'number' },\n description: 'Components to copy to (source excluded automatically)'\n },\n aspects: {\n type: 'array',\n items: {\n type: 'string',\n enum: ['fill', 'stroke', 'shadow', 'material', 'font', 'opacity', 'visibility']\n },\n description:\n 'Which styling aspects to copy. Default: fill/stroke/shadow/material/font/opacity (visibility excluded by default).'\n }\n },\n required: ['sourceRefid', 'targetRefids']\n },\n // copyStyle 은 source lookup 위해 components 배열 필요\n builder: (args, components) => buildCopyStyleOps(args, components as any[])\n }\n]\n\n// ── Helpers — 사용처에서 호출 ────────────────────────────────────\n\n/**\n * registry 에 등록된 모든 styling tool 이름 set.\n * WRITE_TOOL_NAMES 합산용 — assistant.ts 가 isWriteTool 판정 시 styling 영역\n * 부분을 본 set 에 위임.\n */\nexport function getStylingToolNames(\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): Set<string> {\n return new Set(registry.map(s => s.name))\n}\n\n/**\n * LLM 에 노출할 tool 정의 배열.\n * buildBoardEditTools 가 styling 부분을 본 함수 결과로 채운다.\n */\nexport function buildStylingToolDefinitions(\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): Array<{ name: string; description: string; parameters: any }> {\n return registry.map(spec => ({\n name: spec.name,\n description: spec.themeNote\n ? `${spec.description} ${spec.themeNote}`\n : spec.description,\n parameters: spec.schema\n }))\n}\n\n/**\n * AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → BoardEditOp[] | null.\n *\n * 반환:\n * - 매칭 spec 없음 → null (caller 가 다른 카테고리 dispatch 시도)\n * - 빈 ops (validation 실패 등) → null (assistant.ts 의 기존 동작과 동일)\n * - 정상 → BoardEditOp[]\n *\n * components 인자는 copyStyle 같은 source-lookup 필요 tool 에만 의미 있음.\n */\nexport function dispatchStylingTool(\n name: string,\n args: any,\n components: ReadonlyArray<any> = [],\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): BoardEditOp[] | null {\n const spec = registry.find(s => s.name === name)\n if (!spec) return null\n const result = spec.builder(args, components)\n return result.ops.length > 0 ? result.ops : null\n}\n\n/** name 이 styling tool 로 등록되어 있는가. */\nexport function isStylingTool(\n name: string,\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): boolean {\n return registry.some(s => s.name === name)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../../server/service/styling/registry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Styling tool registry — 선언형 단일 진실 (single source of truth).\n *\n * 동기:\n * 기존 styling tool 추가 시 4-5곳 (WRITE_TOOL_NAMES set / tools 배열 정의 /\n * dispatch case / validation case / system prompt 안내) 에 같은 정보가 산재해\n * 동기화 누락 위험. 신규 tool 마다 4곳 boilerplate.\n *\n * 본 registry 가 한 곳에 모음 — 위 모든 사용처가 STYLING_TOOLS 에서 자동 도출.\n *\n * 마이그레이션:\n * STYLING_TOOLS 는 초기엔 비어있고, 한 tool 씩 점진 이전한다. 각 단계마다\n * 기존 hardcoded 정의/dispatch 와 byte-단위 동등성을 단위 테스트가 잠금.\n *\n * 비-styling write tool (addComponent / modifyScene / alignComponents 등) 은\n * 본 registry 의 영역이 아니다 — 의미·구조가 styling 패턴과 달라 별도 dispatch.\n */\nimport type { SceneEditOp } from '@operato/scene-ops'\nimport { buildSetFillOps } from './fill-tools'\nimport { buildSetStrokeOps } from './stroke-tools'\nimport { buildSetTextOps, buildSetFontOps } from './text-tools'\nimport { buildSetShadowOps, buildSetOpacityOps, buildSetVisibleOps } from './effect-tools'\nimport { buildSetMaterialOps, buildSetShadowPropertiesOps } from './material-tools'\nimport { buildClearStyleOps } from './clear-tools'\nimport { buildCopyStyleOps } from './copy-tools'\n\nexport interface OpsResult {\n ops: SceneEditOp[]\n errors: string[]\n}\n\n/**\n * 한 styling tool 의 모든 메타데이터 + 빌더.\n *\n * description 과 themeNote 는 별도 — 자동 합성될 때 description 본문은 영문/기능\n * 위주로 짧게, themeNote 는 한국어 ⚠ 정책 단문. 분리해 두면 미래에 i18n / 정책\n * 일괄 변경 시 검색-치환이 쉽다.\n */\nexport interface StylingToolSpec {\n /** AIToolCall.name 과 매칭. unique. */\n name: string\n /** 기능 설명 (영문 위주). */\n description: string\n /**\n * 선택 — ⚠ 정책 / 테마 가이드 단문. 빌드 시 description 끝에 자동 부착.\n * 예: '⚠ 테마 요청 시 width/dash 건드리지 말 것.'\n */\n themeNote?: string\n /** JSON Schema for parameters — LLM tool schema 에 그대로 노출. */\n schema: any\n /**\n * AIToolCall.arguments → OpsResult 변환.\n * components 는 copyStyle 처럼 source lookup 필요한 tool 만 사용. 다른 tool 은\n * 두 번째 인자 무시하면 됨.\n */\n builder: (args: any, components: ReadonlyArray<any>) => OpsResult\n}\n\n/**\n * 등록된 styling tool 들. 한 tool 씩 점진 이전 중.\n *\n * 마이그레이션 순서: setFill → setStroke → setText/setFont → setShadow /\n * setOpacity / setVisible → setMaterial / setShadowProperties → clearStyle\n * → copyStyle. 각 이전 commit 은 별도 — bisect 로 회귀 추적 용이.\n *\n * spec 작성 규약:\n * - description 은 영문 / 기능 설명\n * - themeNote 는 ⚠ 시작 한국어 정책 단문 (테마 가이드 / 호출 금지 등)\n * - schema 는 LLM 에 그대로 노출되는 JSON Schema\n * - builder 는 builder 함수 직접 사용 (signature 호환). components 인자\n * 안 쓰는 tool 도 그대로 통과.\n */\nexport const STYLING_TOOLS: StylingToolSpec[] = [\n {\n name: 'setFill',\n description:\n \"Set a component's BACKGROUND/FILL (fillStyle). Use this for fill changes — handles 4 modes (none/solid/gradient/pattern/image) safely with mode-aware updates. Prefer over modifyComponentByRefid for fill-only changes.\\n\\nMode rules:\\n - mode='solid': requires `color`\\n - mode='gradient': provide `colorStops` (array of {color,position}) or fall back from `color` (auto-generates 2 stops)\\n - mode='pattern' or 'image': requires `image` (URL or asset ref)\\n - mode='none': removes fill\\n - omitting `mode`: partial update (only color/opacity) — preserves existing mode and other fields.\",\n schema: {\n type: 'object',\n properties: {\n refids: {\n type: 'array',\n items: { type: 'number' },\n description: 'Components to update. things-scene auto-assigned numeric handles.'\n },\n mode: {\n type: 'string',\n enum: ['none', 'solid', 'gradient', 'pattern', 'image'],\n description: 'Fill mode dispatcher (synthetic — not stored on the model). Omit for partial update of color/opacity within the existing mode.'\n },\n color: {\n type: 'string',\n description: \"Color (hex '#ff0000' / rgb / named 'red' / semantic 'warm-red'). Required for mode='solid'. Output: top-level fillStyle string when mode=solid; nested fillStyle.color when mode=pattern (background tint).\"\n },\n gradientType: {\n type: 'string',\n enum: ['linear', 'radial'],\n description: 'Gradient kind (when mode=gradient). Default linear. Stored at fillStyle.gradientType.'\n },\n colorStops: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n color: { type: 'string' },\n position: { type: 'number', description: '0..1' }\n },\n required: ['color', 'position']\n },\n description: 'Gradient color stops (when mode=gradient). At least 2. Stored at fillStyle.colorStops (canonical things-scene field name).'\n },\n rotation: {\n type: 'number',\n description: 'Gradient rotation in degrees (when mode=gradient). Default 0. Internally converted to radians and stored at fillStyle.rotation.'\n },\n image: {\n type: 'string',\n description: 'Image URL or asset reference (when mode=pattern or mode=image). Stored at fillStyle.image.'\n },\n opacity: {\n type: 'number',\n description: 'Fill alpha 0..1. For mode=solid, encoded as rgba alpha channel in the fillStyle string. For mode=gradient/pattern, stored at fillStyle.opacity.'\n }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetFillOps(args)\n },\n {\n name: 'setStroke',\n description:\n \"Set component STROKE / BORDER. Canonical things-scene fields (all top-level on state): strokeStyle (color string), lineWidth (number), lineDash (preset name string), lineCap, lineJoin. dash 는 CSS-친화 alias 도 매핑 — 'dashed'/'dotted'/'dashdot' → things-scene preset. 'solid'/'dash'/'square-dot'/'round-dot'/'dash-dot'/'long-dash'/'long-dash-dot'/'long-dash-dot-dot' 도 가능.\",\n themeNote:\n '⚠ 테마 요청 (북유럽/사이버펑크/미니멀 등) 시 lineWidth/dash/lineCap/lineJoin 은 건드리지 말 것 — 선의 두께·점선 패턴은 디자인 의도를 가진 *구조* 다. strokeStyle (색상) 만 테마 팔레트에 맞춰 변경. 사용자가 명시적으로 두께/점선 변경을 요청한 경우에만 lineWidth/dash 사용.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n enabled: {\n type: 'boolean',\n description: 'Synthetic helper — false maps to lineWidth=0 (drawer skips). Not stored on the model itself.'\n },\n strokeStyle: {\n type: 'string',\n description: 'Stroke color (CSS color string — hex / rgb / named / semantic alias). Stored at top-level state.strokeStyle.'\n },\n lineWidth: {\n type: 'number',\n description: 'Stroke width in pixels (0 = no stroke). Stored at top-level state.lineWidth.'\n },\n dash: {\n description:\n \"Dash pattern. CSS-친화 alias 자동 매핑: 'dashed'→'dash', 'dotted'→'square-dot', 'dashdot'→'dash-dot'. things-scene preset 직접 사용도 OK: 'solid' / 'round-dot' / 'square-dot' / 'dash' / 'dash-dot' / 'long-dash' / 'long-dash-dot' / 'long-dash-dot-dot'. Stored at top-level state.lineDash.\"\n },\n lineCap: {\n type: 'string',\n enum: ['butt', 'round', 'square'],\n description: 'Line cap style. Stored at top-level state.lineCap.'\n },\n lineJoin: {\n type: 'string',\n enum: ['miter', 'round', 'bevel'],\n description: 'Line join style. Stored at top-level state.lineJoin.'\n }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetStrokeOps(args)\n },\n {\n name: 'setText',\n description: 'Change component TEXT CONTENT (state.text). For font styling use setFont separately.',\n themeNote:\n '⚠ 텍스트는 *콘텐츠* 이지 *스타일* 이 아니다 — 테마 요청 (북유럽/사이버펑크/미니멀 등) 시 호출 금지. 사용자가 텍스트 변경을 명시한 경우에만 사용.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n text: { type: 'string' }\n },\n required: ['refids', 'text']\n },\n builder: (args, _components) => buildSetTextOps(args)\n },\n {\n name: 'setFont',\n description:\n 'Set FONT/TEXT STYLE (family / size / weight / style / color / align / baseline / lineHeight). For text content use setText.',\n themeNote:\n '⚠ 테마 요청 (북유럽/사이버펑크 등) 시 fontSize / fontFamily 는 건드리지 말 것 — 정보 위계와 레이아웃에 영향. fontColor 만 테마 팔레트에 맞춰 변경. 사용자가 명시적으로 폰트 크기/패밀리 변경을 요청한 경우에만 사용.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n fontFamily: { type: 'string' },\n fontSize: { type: 'number' },\n fontWeight: {\n description: \"'normal' | 'bold' | 'lighter' | 'bolder' | 100..900\"\n },\n fontStyle: { type: 'string', enum: ['normal', 'italic', 'oblique'] },\n fontColor: { type: 'string' },\n textAlign: {\n type: 'string',\n enum: ['left', 'center', 'right', 'start', 'end', 'justify']\n },\n textBaseline: {\n type: 'string',\n enum: ['top', 'middle', 'bottom', 'hanging', 'alphabetic', 'ideographic']\n },\n lineHeight: { type: 'number' }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetFontOps(args)\n },\n {\n name: 'setShadow',\n description:\n 'Set component SHADOW. Standard CSS box-shadow / Canvas shadow* fields (nested under state.shadow): color, blur, offsetX, offsetY. enabled=false maps to blur=0 + transparent (synthetic helper).',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n enabled: {\n type: 'boolean',\n description: 'Synthetic helper — false maps to blur=0 + color=transparent. Not stored as a model field.'\n },\n color: {\n type: 'string',\n description: 'Shadow color (CSS color string). Stored at state.shadow.color.'\n },\n blur: {\n type: 'number',\n description: 'Shadow blur radius in pixels (matches CSS box-shadow blur-radius / Canvas shadowBlur). Stored at state.shadow.blur.'\n },\n offsetX: {\n type: 'number',\n description: 'Shadow horizontal offset in pixels (matches CSS box-shadow offset-x / Canvas shadowOffsetX). Stored at state.shadow.offsetX.'\n },\n offsetY: {\n type: 'number',\n description: 'Shadow vertical offset in pixels (matches CSS box-shadow offset-y / Canvas shadowOffsetY). Stored at state.shadow.offsetY.'\n }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetShadowOps(args)\n },\n {\n name: 'setOpacity',\n description: 'Set component overall ALPHA (canonical things-scene field state.alpha, 0..1).',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n alpha: {\n type: 'number',\n description: 'Component overall opacity 0..1 (1 = opaque, 0 = invisible). Stored at top-level state.alpha.'\n }\n },\n required: ['refids', 'alpha']\n },\n builder: (args, _components) => buildSetOpacityOps(args)\n },\n {\n name: 'setVisible',\n description: 'Show/hide components (state.hidden = !visible).',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n visible: { type: 'boolean' }\n },\n required: ['refids', 'visible']\n },\n builder: (args, _components) => buildSetVisibleOps(args)\n },\n {\n name: 'setMaterial',\n description:\n 'Set 3D PBR MATERIAL (preset / metalness / roughness / emissive / opacity / envMapIntensity / side). Preset is semantic shortcut: metal/glass/plastic/wood/ceramic/rubber/default. preset alone resets, preset + properties applies overrides on top.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n preset: {\n type: 'string',\n enum: ['default', 'metal', 'glass', 'plastic', 'wood', 'ceramic', 'rubber', 'custom']\n },\n metalness: { type: 'number', description: '0..1' },\n roughness: { type: 'number', description: '0..1' },\n emissive: { type: 'string', description: 'glow color (hex / named)' },\n emissiveIntensity: { type: 'number', description: '0..5' },\n opacity: { type: 'number', description: '0..1 (3D-only, separate from component alpha)' },\n envMapIntensity: { type: 'number', description: '0..3' },\n side: { type: 'string', enum: ['double', 'front', 'back'] }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetMaterialOps(args)\n },\n {\n name: 'setShadowProperties',\n description: 'Set 3D shadow behavior — castShadow / receiveShadow flags on material3d.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n castShadow: { type: 'boolean' },\n receiveShadow: { type: 'boolean' }\n },\n required: ['refids']\n },\n builder: (args, _components) => buildSetShadowPropertiesOps(args)\n },\n {\n name: 'clearStyle',\n description:\n 'Reset specific styling aspects to default — useful before applying preset/binding. Aspects: fill/stroke/shadow/material/font/opacity.',\n schema: {\n type: 'object',\n properties: {\n refids: { type: 'array', items: { type: 'number' } },\n aspects: {\n type: 'array',\n items: {\n type: 'string',\n enum: ['fill', 'stroke', 'shadow', 'material', 'font', 'opacity']\n }\n }\n },\n required: ['refids', 'aspects']\n },\n builder: (args, _components) => buildClearStyleOps(args)\n },\n {\n name: 'copyStyle',\n description:\n \"Copy STYLING from one component to others. Use for 'make these components look like that one' patterns. aspects can limit which styling areas to copy (default: all except visibility). Source's styling is read server-side — you don't need getComponentStyle first.\",\n schema: {\n type: 'object',\n properties: {\n sourceRefid: { type: 'number', description: 'Component to copy from' },\n targetRefids: {\n type: 'array',\n items: { type: 'number' },\n description: 'Components to copy to (source excluded automatically)'\n },\n aspects: {\n type: 'array',\n items: {\n type: 'string',\n enum: ['fill', 'stroke', 'shadow', 'material', 'font', 'opacity', 'visibility']\n },\n description:\n 'Which styling aspects to copy. Default: fill/stroke/shadow/material/font/opacity (visibility excluded by default).'\n }\n },\n required: ['sourceRefid', 'targetRefids']\n },\n // copyStyle 은 source lookup 위해 components 배열 필요\n builder: (args, components) => buildCopyStyleOps(args, components as any[])\n }\n]\n\n// ── Helpers — 사용처에서 호출 ────────────────────────────────────\n\n/**\n * registry 에 등록된 모든 styling tool 이름 set.\n * WRITE_TOOL_NAMES 합산용 — assistant.ts 가 isWriteTool 판정 시 styling 영역\n * 부분을 본 set 에 위임.\n */\nexport function getStylingToolNames(\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): Set<string> {\n return new Set(registry.map(s => s.name))\n}\n\n/**\n * LLM 에 노출할 tool 정의 배열.\n * buildBoardEditTools 가 styling 부분을 본 함수 결과로 채운다.\n */\nexport function buildStylingToolDefinitions(\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): Array<{ name: string; description: string; parameters: any }> {\n return registry.map(spec => ({\n name: spec.name,\n description: spec.themeNote\n ? `${spec.description} ${spec.themeNote}`\n : spec.description,\n parameters: spec.schema\n }))\n}\n\n/**\n * AIToolCall name 에 매칭되는 styling tool 의 builder 호출 → SceneEditOp[] | null.\n *\n * 반환:\n * - 매칭 spec 없음 → null (caller 가 다른 카테고리 dispatch 시도)\n * - 빈 ops (validation 실패 등) → null (assistant.ts 의 기존 동작과 동일)\n * - 정상 → SceneEditOp[]\n *\n * components 인자는 copyStyle 같은 source-lookup 필요 tool 에만 의미 있음.\n */\nexport function dispatchStylingTool(\n name: string,\n args: any,\n components: ReadonlyArray<any> = [],\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): SceneEditOp[] | null {\n const spec = registry.find(s => s.name === name)\n if (!spec) return null\n const result = spec.builder(args, components)\n return result.ops.length > 0 ? result.ops : null\n}\n\n/** name 이 styling tool 로 등록되어 있는가. */\nexport function isStylingTool(\n name: string,\n registry: ReadonlyArray<StylingToolSpec> = STYLING_TOOLS\n): boolean {\n return registry.some(s => s.name === name)\n}\n"]}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* 입력 단순화 — AI 친화적 alias 도 받아 things-scene preset 으로 매핑:
|
|
15
15
|
* AI 'dashed' → 'dash', 'dotted' → 'square-dot', 'dashdot' → 'dash-dot'.
|
|
16
16
|
*/
|
|
17
|
-
import type {
|
|
17
|
+
import type { SceneEditOp } from '@operato/scene-ops';
|
|
18
18
|
export interface SetStrokeInput {
|
|
19
19
|
refids: number[];
|
|
20
20
|
/** synthetic — false 면 lineWidth=0 으로 매핑 (drawer 가 안 그림). 모델에 enabled 필드는 안 박힘. */
|
|
@@ -29,7 +29,7 @@ export interface SetStrokeInput {
|
|
|
29
29
|
lineJoin?: 'miter' | 'round' | 'bevel';
|
|
30
30
|
}
|
|
31
31
|
export interface SetStrokeResult {
|
|
32
|
-
ops:
|
|
32
|
+
ops: SceneEditOp[];
|
|
33
33
|
errors: string[];
|
|
34
34
|
}
|
|
35
35
|
export declare function buildSetStrokeOps(input: SetStrokeInput): SetStrokeResult;
|