@things-factory/board-import 10.0.0-beta.64

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.
Files changed (70) hide show
  1. package/config/config.development.js +44 -0
  2. package/config/config.production.js +18 -0
  3. package/dist-server/index.d.ts +7 -0
  4. package/dist-server/index.js +11 -0
  5. package/dist-server/index.js.map +1 -0
  6. package/dist-server/service/adapters/dxf-adapter.d.ts +8 -0
  7. package/dist-server/service/adapters/dxf-adapter.js +206 -0
  8. package/dist-server/service/adapters/dxf-adapter.js.map +1 -0
  9. package/dist-server/service/ai/types.d.ts +93 -0
  10. package/dist-server/service/ai/types.js +169 -0
  11. package/dist-server/service/ai/types.js.map +1 -0
  12. package/dist-server/service/converters/generic-to.d.ts +16 -0
  13. package/dist-server/service/converters/generic-to.js +78 -0
  14. package/dist-server/service/converters/generic-to.js.map +1 -0
  15. package/dist-server/service/import-session/import-session-resolver.d.ts +18 -0
  16. package/dist-server/service/import-session/import-session-resolver.js +135 -0
  17. package/dist-server/service/import-session/import-session-resolver.js.map +1 -0
  18. package/dist-server/service/import-session/import-session.d.ts +28 -0
  19. package/dist-server/service/import-session/import-session.js +152 -0
  20. package/dist-server/service/import-session/import-session.js.map +1 -0
  21. package/dist-server/service/import-session/import-worker.d.ts +1 -0
  22. package/dist-server/service/import-session/import-worker.js +79 -0
  23. package/dist-server/service/import-session/import-worker.js.map +1 -0
  24. package/dist-server/service/import-session/index.d.ts +6 -0
  25. package/dist-server/service/import-session/index.js +12 -0
  26. package/dist-server/service/import-session/index.js.map +1 -0
  27. package/dist-server/service/index.d.ts +22 -0
  28. package/dist-server/service/index.js +47 -0
  29. package/dist-server/service/index.js.map +1 -0
  30. package/dist-server/service/pipeline/index.d.ts +33 -0
  31. package/dist-server/service/pipeline/index.js +58 -0
  32. package/dist-server/service/pipeline/index.js.map +1 -0
  33. package/dist-server/service/pipeline/stage2-mapping.d.ts +14 -0
  34. package/dist-server/service/pipeline/stage2-mapping.js +173 -0
  35. package/dist-server/service/pipeline/stage2-mapping.js.map +1 -0
  36. package/dist-server/service/pipeline/stage3-board.d.ts +52 -0
  37. package/dist-server/service/pipeline/stage3-board.js +53 -0
  38. package/dist-server/service/pipeline/stage3-board.js.map +1 -0
  39. package/dist-server/service/pipeline/stage4-binding.d.ts +9 -0
  40. package/dist-server/service/pipeline/stage4-binding.js +28 -0
  41. package/dist-server/service/pipeline/stage4-binding.js.map +1 -0
  42. package/dist-server/service/registry/index.d.ts +27 -0
  43. package/dist-server/service/registry/index.js +108 -0
  44. package/dist-server/service/registry/index.js.map +1 -0
  45. package/dist-server/service/types/index.d.ts +117 -0
  46. package/dist-server/service/types/index.js +3 -0
  47. package/dist-server/service/types/index.js.map +1 -0
  48. package/dist-server/service/types/registry.d.ts +175 -0
  49. package/dist-server/service/types/registry.js +3 -0
  50. package/dist-server/service/types/registry.js.map +1 -0
  51. package/dist-server/tsconfig.tsbuildinfo +1 -0
  52. package/package.json +34 -0
  53. package/server/index.ts +7 -0
  54. package/server/service/adapters/dxf-adapter.ts +217 -0
  55. package/server/service/ai/types.ts +284 -0
  56. package/server/service/converters/generic-to.ts +98 -0
  57. package/server/service/import-session/import-session-resolver.ts +113 -0
  58. package/server/service/import-session/import-session.ts +141 -0
  59. package/server/service/import-session/import-worker.ts +88 -0
  60. package/server/service/import-session/index.ts +8 -0
  61. package/server/service/index.ts +96 -0
  62. package/server/service/pipeline/index.ts +89 -0
  63. package/server/service/pipeline/stage2-mapping.ts +195 -0
  64. package/server/service/pipeline/stage3-board.ts +114 -0
  65. package/server/service/pipeline/stage4-binding.ts +31 -0
  66. package/server/service/registry/index.ts +127 -0
  67. package/server/service/types/index.ts +133 -0
  68. package/server/service/types/registry.ts +186 -0
  69. package/things-factory.config.js +1 -0
  70. package/tsconfig.json +9 -0
@@ -0,0 +1,44 @@
1
+ module.exports = {
2
+ /*
3
+ Board Import Configuration
4
+
5
+ Settings for drawing → board conversion pipeline (DXF / IFC / image).
6
+ Override in your application's config to tune performance limits.
7
+ */
8
+ boardImport: {
9
+ /*
10
+ Maximum entities parsed from a single drawing. Performance guard.
11
+ Drawings with more entities are truncated; warning is recorded in
12
+ MappingResult.warnings.
13
+ */
14
+ maxEntities: 100000,
15
+
16
+ /*
17
+ Maximum concurrent ImportSession workers within a single process.
18
+ Higher = faster batch import, more memory.
19
+
20
+ Recommended: number of CPU cores.
21
+ */
22
+ maxConcurrentJobs: 4,
23
+
24
+ /*
25
+ Default scope(s) to use when ImportSession options.scopes is empty.
26
+ Application typically registers ImportRules under a domain scope (e.g. 'fmsim').
27
+
28
+ Example:
29
+ defaultScopes: ['fmsim']
30
+ */
31
+ // defaultScopes: undefined,
32
+
33
+ /*
34
+ Default geometry handling for assembled BoardModel.
35
+ These are applied when the corresponding ImportSession option is undefined.
36
+ */
37
+ defaults: {
38
+ normalizeOrigin: true, // shift bbox so minX,minY = 0
39
+ flipY: true, // CAD Y-up → scene Y-down
40
+ scale: 1, // 1mm = 1 scene unit (CAD standard)
41
+ applyBinding: true // run Stage 4 (subscription auto-mapping)
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,18 @@
1
+ module.exports = {
2
+ /*
3
+ Board Import Configuration — Production
4
+
5
+ See config.development.js for the full reference. Production typically tunes
6
+ `maxConcurrentJobs` based on host capacity.
7
+ */
8
+ boardImport: {
9
+ maxEntities: 100000,
10
+ maxConcurrentJobs: 4,
11
+ defaults: {
12
+ normalizeOrigin: true,
13
+ flipY: true,
14
+ scale: 1,
15
+ applyBinding: true
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @things-factory/board-import — 도면(CAD/이미지/IFC) → Board Model 백엔드 코어.
3
+ *
4
+ * Phase 1: DXF 결정적 파이프라인 (AI 미사용).
5
+ * Phase 2+: GraphQL/REST 엔드포인트, AI 통합.
6
+ */
7
+ export * from './service/index.js';
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ /**
5
+ * @things-factory/board-import — 도면(CAD/이미지/IFC) → Board Model 백엔드 코어.
6
+ *
7
+ * Phase 1: DXF 결정적 파이프라인 (AI 미사용).
8
+ * Phase 2+: GraphQL/REST 엔드포인트, AI 통합.
9
+ */
10
+ tslib_1.__exportStar(require("./service/index.js"), exports);
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../server/index.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,6DAAkC","sourcesContent":["/**\n * @things-factory/board-import — 도면(CAD/이미지/IFC) → Board Model 백엔드 코어.\n *\n * Phase 1: DXF 결정적 파이프라인 (AI 미사용).\n * Phase 2+: GraphQL/REST 엔드포인트, AI 통합.\n */\nexport * from './service/index.js'\n"]}
@@ -0,0 +1,8 @@
1
+ import type { AdapterCapabilities, AdapterInput, FormatAdapter, ParseOptions, ParseResult } from '../types/index.js';
2
+ export declare class DxfAdapter implements FormatAdapter {
3
+ readonly format = "dxf";
4
+ readonly supportedExtensions: string[];
5
+ readonly capabilities: AdapterCapabilities;
6
+ detect(file: AdapterInput, filename?: string): Promise<boolean>;
7
+ parse(file: AdapterInput, options?: ParseOptions): Promise<ParseResult>;
8
+ }
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DxfAdapter = void 0;
4
+ const tslib_1 = require("tslib");
5
+ /**
6
+ * DXF Format Adapter — DXF 텍스트 파일을 UniversalEntity 목록으로 정규화.
7
+ *
8
+ * 백엔드 환경(Node.js)에서 동작. 입력은 string / Buffer / ArrayBuffer / Uint8Array 모두 허용.
9
+ *
10
+ * 구현 범위 (Phase 1):
11
+ * - INSERT (블록 참조) → kind='shape', attribs.name 추출
12
+ * - LWPOLYLINE / POLYLINE → kind='path'
13
+ * - CIRCLE → kind='shape', attribs.shape='circle'
14
+ * - LINE → kind='path' (단일 선분)
15
+ * - TEXT / MTEXT → kind='text'
16
+ * - 레이어, 좌표, 회전 추출
17
+ * - $INSUNITS 단위 추출
18
+ *
19
+ * 보류 (Phase 2+):
20
+ * - HATCH, SPLINE, ELLIPSE 의 정밀 변환
21
+ * - 블록 정의 안의 entities 깊이 펼치기
22
+ * - 외부 참조 (XREF)
23
+ */
24
+ const dxf_parser_1 = tslib_1.__importDefault(require("dxf-parser"));
25
+ const DXF_INSUNITS_TO_UNITS = {
26
+ 0: 'unknown',
27
+ 1: 'inch',
28
+ 4: 'mm',
29
+ 5: 'cm',
30
+ 6: 'm'
31
+ };
32
+ class DxfAdapter {
33
+ constructor() {
34
+ this.format = 'dxf';
35
+ this.supportedExtensions = ['.dxf'];
36
+ this.capabilities = {
37
+ has2D: true,
38
+ has3D: false,
39
+ hasSemanticLabels: false,
40
+ hasTextLabels: true,
41
+ hasAttributes: true
42
+ };
43
+ }
44
+ async detect(file, filename) {
45
+ if (filename && filename.toLowerCase().endsWith('.dxf'))
46
+ return true;
47
+ const head = readHead(file, 256);
48
+ return /\b0\s*\n\s*SECTION\b/.test(head) || /^\s*999/.test(head);
49
+ }
50
+ async parse(file, options = {}) {
51
+ const text = readText(file);
52
+ const parser = new dxf_parser_1.default();
53
+ const dxf = parser.parseSync(text);
54
+ if (!dxf)
55
+ throw new Error('DXF 파싱 실패');
56
+ const entities = [];
57
+ let uidCounter = 0;
58
+ const nextUid = () => `dxf-${++uidCounter}`;
59
+ const excludeLayers = new Set((options.excludeLayers || []).map(l => l.toLowerCase()));
60
+ const maxEntities = options.maxEntities ?? 100000;
61
+ for (const e of dxf.entities || []) {
62
+ if (entities.length >= maxEntities)
63
+ break;
64
+ const layer = e.layer || '';
65
+ if (excludeLayers.has(layer.toLowerCase()))
66
+ continue;
67
+ const ue = entityToUniversal(e, layer, nextUid());
68
+ if (ue)
69
+ entities.push(ue);
70
+ }
71
+ const bbox = computeBoundingBox(entities);
72
+ const units = DXF_INSUNITS_TO_UNITS[dxf.header?.$INSUNITS ?? 0];
73
+ return {
74
+ entities,
75
+ metadata: {
76
+ format: 'dxf',
77
+ formatVersion: dxf.header?.$ACADVER,
78
+ ...(dxf.header || {})
79
+ },
80
+ bbox,
81
+ units
82
+ };
83
+ }
84
+ }
85
+ exports.DxfAdapter = DxfAdapter;
86
+ // ── DXF entity → UniversalEntity ─────────────────────────────────────
87
+ function entityToUniversal(e, layer, uid) {
88
+ const type = e.type;
89
+ switch (type) {
90
+ case 'INSERT': {
91
+ const x = e.position?.x ?? 0;
92
+ const y = e.position?.y ?? 0;
93
+ const width = (e.xScale ?? 1) * 1;
94
+ const height = (e.yScale ?? 1) * 1;
95
+ return {
96
+ uid,
97
+ kind: 'shape',
98
+ position: { x, y },
99
+ size: { width, height },
100
+ rotation: e.rotation ?? 0,
101
+ bbox: { min: { x, y }, max: { x: x + width, y: y + height } },
102
+ attribs: {
103
+ name: e.name,
104
+ layer,
105
+ shape: 'rect'
106
+ }
107
+ };
108
+ }
109
+ case 'CIRCLE': {
110
+ const cx = e.center?.x ?? 0;
111
+ const cy = e.center?.y ?? 0;
112
+ const r = e.radius ?? 0;
113
+ return {
114
+ uid,
115
+ kind: 'shape',
116
+ position: { x: cx - r, y: cy - r },
117
+ size: { width: 2 * r, height: 2 * r },
118
+ bbox: { min: { x: cx - r, y: cy - r }, max: { x: cx + r, y: cy + r } },
119
+ attribs: { layer, shape: 'circle' }
120
+ };
121
+ }
122
+ case 'LWPOLYLINE':
123
+ case 'POLYLINE': {
124
+ const verts = (e.vertices || []).map((v) => ({ x: v.x, y: v.y }));
125
+ if (verts.length === 0)
126
+ return null;
127
+ const xs = verts.map(v => v.x);
128
+ const ys = verts.map(v => v.y);
129
+ const min = { x: Math.min(...xs), y: Math.min(...ys) };
130
+ const max = { x: Math.max(...xs), y: Math.max(...ys) };
131
+ return {
132
+ uid,
133
+ kind: 'path',
134
+ position: { x: min.x, y: min.y },
135
+ size: { width: max.x - min.x, height: max.y - min.y },
136
+ bbox: { min, max },
137
+ attribs: { layer, shape: 'polyline', vertices: verts, closed: !!e.shape }
138
+ };
139
+ }
140
+ case 'LINE': {
141
+ const x1 = e.vertices?.[0]?.x ?? 0;
142
+ const y1 = e.vertices?.[0]?.y ?? 0;
143
+ const x2 = e.vertices?.[1]?.x ?? 0;
144
+ const y2 = e.vertices?.[1]?.y ?? 0;
145
+ const min = { x: Math.min(x1, x2), y: Math.min(y1, y2) };
146
+ const max = { x: Math.max(x1, x2), y: Math.max(y1, y2) };
147
+ return {
148
+ uid,
149
+ kind: 'path',
150
+ position: { x: min.x, y: min.y },
151
+ size: { width: max.x - min.x, height: max.y - min.y },
152
+ bbox: { min, max },
153
+ attribs: { layer, shape: 'polyline', vertices: [{ x: x1, y: y1 }, { x: x2, y: y2 }] }
154
+ };
155
+ }
156
+ case 'TEXT':
157
+ case 'MTEXT': {
158
+ const x = e.position?.x ?? e.startPoint?.x ?? 0;
159
+ const y = e.position?.y ?? e.startPoint?.y ?? 0;
160
+ const text = e.text ?? e.string ?? '';
161
+ const h = e.textHeight ?? e.height ?? 1;
162
+ return {
163
+ uid,
164
+ kind: 'text',
165
+ position: { x, y },
166
+ bbox: { min: { x, y }, max: { x: x + h * (text.length || 1), y: y + h } },
167
+ attribs: { layer, text }
168
+ };
169
+ }
170
+ default:
171
+ return null;
172
+ }
173
+ }
174
+ // ── 유틸 ─────────────────────────────────────────────────────────────
175
+ function computeBoundingBox(entities) {
176
+ if (entities.length === 0)
177
+ return { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } };
178
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
179
+ for (const e of entities) {
180
+ if (e.bbox.min.x < minX)
181
+ minX = e.bbox.min.x;
182
+ if (e.bbox.min.y < minY)
183
+ minY = e.bbox.min.y;
184
+ if (e.bbox.max.x > maxX)
185
+ maxX = e.bbox.max.x;
186
+ if (e.bbox.max.y > maxY)
187
+ maxY = e.bbox.max.y;
188
+ }
189
+ return { min: { x: minX, y: minY }, max: { x: maxX, y: maxY } };
190
+ }
191
+ function readText(file) {
192
+ if (typeof file === 'string')
193
+ return file;
194
+ if (Buffer.isBuffer(file))
195
+ return file.toString('utf-8');
196
+ if (file instanceof Uint8Array)
197
+ return Buffer.from(file).toString('utf-8');
198
+ if (file instanceof ArrayBuffer)
199
+ return Buffer.from(new Uint8Array(file)).toString('utf-8');
200
+ throw new Error('Unsupported DXF input type');
201
+ }
202
+ function readHead(file, maxBytes) {
203
+ const text = readText(file);
204
+ return text.substring(0, maxBytes);
205
+ }
206
+ //# sourceMappingURL=dxf-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dxf-adapter.js","sourceRoot":"","sources":["../../../server/service/adapters/dxf-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * DXF Format Adapter — DXF 텍스트 파일을 UniversalEntity 목록으로 정규화.\n *\n * 백엔드 환경(Node.js)에서 동작. 입력은 string / Buffer / ArrayBuffer / Uint8Array 모두 허용.\n *\n * 구현 범위 (Phase 1):\n * - INSERT (블록 참조) → kind='shape', attribs.name 추출\n * - LWPOLYLINE / POLYLINE → kind='path'\n * - CIRCLE → kind='shape', attribs.shape='circle'\n * - LINE → kind='path' (단일 선분)\n * - TEXT / MTEXT → kind='text'\n * - 레이어, 좌표, 회전 추출\n * - $INSUNITS 단위 추출\n *\n * 보류 (Phase 2+):\n * - HATCH, SPLINE, ELLIPSE 의 정밀 변환\n * - 블록 정의 안의 entities 깊이 펼치기\n * - 외부 참조 (XREF)\n */\nimport DxfParser from 'dxf-parser'\n\nimport type {\n AdapterCapabilities,\n AdapterInput,\n BoundingBox,\n FormatAdapter,\n ParseOptions,\n ParseResult,\n UniversalEntity\n} from '../types/index.js'\n\nconst DXF_INSUNITS_TO_UNITS: Record<number, ParseResult['units']> = {\n 0: 'unknown',\n 1: 'inch',\n 4: 'mm',\n 5: 'cm',\n 6: 'm'\n}\n\nexport class DxfAdapter implements FormatAdapter {\n readonly format = 'dxf'\n readonly supportedExtensions = ['.dxf']\n readonly capabilities: AdapterCapabilities = {\n has2D: true,\n has3D: false,\n hasSemanticLabels: false,\n hasTextLabels: true,\n hasAttributes: true\n }\n\n async detect(file: AdapterInput, filename?: string): Promise<boolean> {\n if (filename && filename.toLowerCase().endsWith('.dxf')) return true\n const head = readHead(file, 256)\n return /\\b0\\s*\\n\\s*SECTION\\b/.test(head) || /^\\s*999/.test(head)\n }\n\n async parse(file: AdapterInput, options: ParseOptions = {}): Promise<ParseResult> {\n const text = readText(file)\n const parser = new DxfParser()\n const dxf = parser.parseSync(text)\n\n if (!dxf) throw new Error('DXF 파싱 실패')\n\n const entities: UniversalEntity[] = []\n let uidCounter = 0\n const nextUid = () => `dxf-${++uidCounter}`\n\n const excludeLayers = new Set((options.excludeLayers || []).map(l => l.toLowerCase()))\n const maxEntities = options.maxEntities ?? 100000\n\n for (const e of dxf.entities || []) {\n if (entities.length >= maxEntities) break\n\n const layer = (e as any).layer || ''\n if (excludeLayers.has(layer.toLowerCase())) continue\n\n const ue = entityToUniversal(e, layer, nextUid())\n if (ue) entities.push(ue)\n }\n\n const bbox = computeBoundingBox(entities)\n const units = DXF_INSUNITS_TO_UNITS[(dxf.header as any)?.$INSUNITS ?? 0]\n\n return {\n entities,\n metadata: {\n format: 'dxf',\n formatVersion: (dxf.header as any)?.$ACADVER,\n ...((dxf.header as any) || {})\n },\n bbox,\n units\n }\n }\n}\n\n// ── DXF entity → UniversalEntity ─────────────────────────────────────\n\nfunction entityToUniversal(e: any, layer: string, uid: string): UniversalEntity | null {\n const type = e.type as string\n\n switch (type) {\n case 'INSERT': {\n const x = e.position?.x ?? 0\n const y = e.position?.y ?? 0\n const width = (e.xScale ?? 1) * 1\n const height = (e.yScale ?? 1) * 1\n return {\n uid,\n kind: 'shape',\n position: { x, y },\n size: { width, height },\n rotation: e.rotation ?? 0,\n bbox: { min: { x, y }, max: { x: x + width, y: y + height } },\n attribs: {\n name: e.name,\n layer,\n shape: 'rect'\n }\n }\n }\n\n case 'CIRCLE': {\n const cx = e.center?.x ?? 0\n const cy = e.center?.y ?? 0\n const r = e.radius ?? 0\n return {\n uid,\n kind: 'shape',\n position: { x: cx - r, y: cy - r },\n size: { width: 2 * r, height: 2 * r },\n bbox: { min: { x: cx - r, y: cy - r }, max: { x: cx + r, y: cy + r } },\n attribs: { layer, shape: 'circle' }\n }\n }\n\n case 'LWPOLYLINE':\n case 'POLYLINE': {\n const verts: { x: number; y: number }[] = (e.vertices || []).map((v: any) => ({ x: v.x, y: v.y }))\n if (verts.length === 0) return null\n const xs = verts.map(v => v.x)\n const ys = verts.map(v => v.y)\n const min = { x: Math.min(...xs), y: Math.min(...ys) }\n const max = { x: Math.max(...xs), y: Math.max(...ys) }\n return {\n uid,\n kind: 'path',\n position: { x: min.x, y: min.y },\n size: { width: max.x - min.x, height: max.y - min.y },\n bbox: { min, max },\n attribs: { layer, shape: 'polyline', vertices: verts, closed: !!e.shape }\n }\n }\n\n case 'LINE': {\n const x1 = e.vertices?.[0]?.x ?? 0\n const y1 = e.vertices?.[0]?.y ?? 0\n const x2 = e.vertices?.[1]?.x ?? 0\n const y2 = e.vertices?.[1]?.y ?? 0\n const min = { x: Math.min(x1, x2), y: Math.min(y1, y2) }\n const max = { x: Math.max(x1, x2), y: Math.max(y1, y2) }\n return {\n uid,\n kind: 'path',\n position: { x: min.x, y: min.y },\n size: { width: max.x - min.x, height: max.y - min.y },\n bbox: { min, max },\n attribs: { layer, shape: 'polyline', vertices: [{ x: x1, y: y1 }, { x: x2, y: y2 }] }\n }\n }\n\n case 'TEXT':\n case 'MTEXT': {\n const x = e.position?.x ?? e.startPoint?.x ?? 0\n const y = e.position?.y ?? e.startPoint?.y ?? 0\n const text = e.text ?? e.string ?? ''\n const h = e.textHeight ?? e.height ?? 1\n return {\n uid,\n kind: 'text',\n position: { x, y },\n bbox: { min: { x, y }, max: { x: x + h * (text.length || 1), y: y + h } },\n attribs: { layer, text }\n }\n }\n\n default:\n return null\n }\n}\n\n// ── 유틸 ─────────────────────────────────────────────────────────────\n\nfunction computeBoundingBox(entities: UniversalEntity[]): BoundingBox {\n if (entities.length === 0) return { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n for (const e of entities) {\n if (e.bbox.min.x < minX) minX = e.bbox.min.x\n if (e.bbox.min.y < minY) minY = e.bbox.min.y\n if (e.bbox.max.x > maxX) maxX = e.bbox.max.x\n if (e.bbox.max.y > maxY) maxY = e.bbox.max.y\n }\n return { min: { x: minX, y: minY }, max: { x: maxX, y: maxY } }\n}\n\nfunction readText(file: AdapterInput): string {\n if (typeof file === 'string') return file\n if (Buffer.isBuffer(file)) return file.toString('utf-8')\n if (file instanceof Uint8Array) return Buffer.from(file).toString('utf-8')\n if (file instanceof ArrayBuffer) return Buffer.from(new Uint8Array(file)).toString('utf-8')\n throw new Error('Unsupported DXF input type')\n}\n\nfunction readHead(file: AdapterInput, maxBytes: number): string {\n const text = readText(file)\n return text.substring(0, maxBytes)\n}\n"]}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * BoardImportAI — board-import 의 **도메인 AI 인터페이스**.
3
+ *
4
+ * Provider 추상화 (chat/JSON/vision) 는 `@things-factory/ai-client-base` 의 `AIClient` 가 담당.
5
+ * 이 모듈은 board-import 의 도메인 메서드 (컨벤션 추론, Few-shot 분류, 도면 이미지 파싱,
6
+ * 자연어 → ImportRule) 만 정의하고, 기본 구현은 base AIClient 위에 LLM 프롬프트로 만든다.
7
+ *
8
+ * 사용 예:
9
+ * ```ts
10
+ * import { createAIClient } from '@things-factory/ai-client-base'
11
+ * import { setAIClient, DefaultBoardImportAI } from '@things-factory/board-import'
12
+ *
13
+ * const base = createAIClient({ provider: 'anthropic', apiKey: '...' })
14
+ * setAIClient(new DefaultBoardImportAI(base))
15
+ * ```
16
+ *
17
+ * Phase 1-2 결정적 파이프라인은 AI 미사용. 위 등록은 Phase 3+ 메서드 활성화용.
18
+ */
19
+ import { type AIClient as BaseAIClient } from '@things-factory/ai-client-base';
20
+ import type { ComponentCategory, ImportRule } from '../types/registry.js';
21
+ import type { UniversalEntity } from '../types/index.js';
22
+ export interface BoardImportAI {
23
+ /** 식별자 — 보통 base.id 와 동일 (provider:model) */
24
+ readonly id: string;
25
+ /**
26
+ * 미매칭 entity 묶음을 보고 컨벤션 규칙을 추론.
27
+ * 예: "STK_001, STK_002 가 stocker 인 것 같습니다" → 정규식 규칙 제안
28
+ */
29
+ inferConventions?(entities: UniversalEntity[], options?: ConventionInferenceOptions): Promise<RuleSuggestion[]>;
30
+ /**
31
+ * 사용자가 일부 entity 를 라벨링했을 때, 나머지 미라벨 entity 의 카테고리·type 을 추론.
32
+ * Few-shot 분류.
33
+ */
34
+ classifyEntities?(labeled: LabeledEntity[], unlabeled: UniversalEntity[], options?: ClassificationOptions): Promise<EntityClassification[]>;
35
+ /**
36
+ * 도면 이미지(PNG/JPG/PDF 페이지) 에서 객체를 검출하여 UniversalEntity 로 변환.
37
+ * Phase 3 (이미지 어댑터) 에서 사용.
38
+ */
39
+ parseFromImage?(image: Buffer | Uint8Array, options?: ImageParseOptions): Promise<UniversalEntity[]>;
40
+ /**
41
+ * 자연어 매핑 정의 ("STK는 stocker, BUF는 buffer") → ImportRule[] 로 변환.
42
+ */
43
+ parseNaturalLanguageMapping?(text: string, availableTypes: string[]): Promise<ImportRule[]>;
44
+ }
45
+ export interface ConventionInferenceOptions {
46
+ /** 알려진 도메인 type 후보 (LLM 이 이 안에서 고르도록) */
47
+ knownTypes?: string[];
48
+ /** 카테고리 후보 */
49
+ categories?: ComponentCategory[];
50
+ /** 최대 제안 개수 */
51
+ maxSuggestions?: number;
52
+ }
53
+ export interface RuleSuggestion {
54
+ /** 자동 생성된 ImportRule 후보 */
55
+ rule: ImportRule;
56
+ /** 0..1 신뢰도 */
57
+ confidence: number;
58
+ /** 추론 근거 */
59
+ reasoning?: string;
60
+ }
61
+ export interface ClassificationOptions {
62
+ knownTypes?: string[];
63
+ categories?: ComponentCategory[];
64
+ }
65
+ export interface LabeledEntity {
66
+ entity: UniversalEntity;
67
+ type: string;
68
+ category?: ComponentCategory;
69
+ }
70
+ export interface EntityClassification {
71
+ entityUid: string;
72
+ type: string;
73
+ category: ComponentCategory;
74
+ confidence: number;
75
+ }
76
+ export interface ImageParseOptions {
77
+ /** 자연어 컨텍스트. 예: "factory floor plan with AGV paths" */
78
+ context?: string;
79
+ categories?: ComponentCategory[];
80
+ }
81
+ export declare class DefaultBoardImportAI implements BoardImportAI {
82
+ private base;
83
+ readonly id: string;
84
+ constructor(base: BaseAIClient);
85
+ inferConventions(entities: UniversalEntity[], options?: ConventionInferenceOptions): Promise<RuleSuggestion[]>;
86
+ classifyEntities(labeled: LabeledEntity[], unlabeled: UniversalEntity[], options?: ClassificationOptions): Promise<EntityClassification[]>;
87
+ parseFromImage(image: Buffer | Uint8Array, options?: ImageParseOptions): Promise<UniversalEntity[]>;
88
+ parseNaturalLanguageMapping(text: string, availableTypes: string[]): Promise<ImportRule[]>;
89
+ }
90
+ export declare function setAIClient(client: BoardImportAI | undefined): void;
91
+ export declare function getAIClient(): BoardImportAI | undefined;
92
+ /** Deprecated alias — 기존 코드 호환용. 새로 작성 시 BoardImportAI 사용. */
93
+ export type AIClient = BoardImportAI;
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultBoardImportAI = void 0;
4
+ exports.setAIClient = setAIClient;
5
+ exports.getAIClient = getAIClient;
6
+ /**
7
+ * BoardImportAI — board-import 의 **도메인 AI 인터페이스**.
8
+ *
9
+ * Provider 추상화 (chat/JSON/vision) 는 `@things-factory/ai-client-base` 의 `AIClient` 가 담당.
10
+ * 이 모듈은 board-import 의 도메인 메서드 (컨벤션 추론, Few-shot 분류, 도면 이미지 파싱,
11
+ * 자연어 → ImportRule) 만 정의하고, 기본 구현은 base AIClient 위에 LLM 프롬프트로 만든다.
12
+ *
13
+ * 사용 예:
14
+ * ```ts
15
+ * import { createAIClient } from '@things-factory/ai-client-base'
16
+ * import { setAIClient, DefaultBoardImportAI } from '@things-factory/board-import'
17
+ *
18
+ * const base = createAIClient({ provider: 'anthropic', apiKey: '...' })
19
+ * setAIClient(new DefaultBoardImportAI(base))
20
+ * ```
21
+ *
22
+ * Phase 1-2 결정적 파이프라인은 AI 미사용. 위 등록은 Phase 3+ 메서드 활성화용.
23
+ */
24
+ const ai_client_base_1 = require("@things-factory/ai-client-base");
25
+ // ── 기본 구현 — base AIClient 위에 LLM 프롬프트로 ────────────────────
26
+ class DefaultBoardImportAI {
27
+ constructor(base) {
28
+ this.base = base;
29
+ this.id = base.id;
30
+ }
31
+ async inferConventions(entities, options = {}) {
32
+ const sample = entities.slice(0, 100).map(e => ({
33
+ uid: e.uid,
34
+ name: e.attribs.name,
35
+ layer: e.attribs.layer,
36
+ kind: e.kind,
37
+ shape: e.attribs.shape
38
+ }));
39
+ const knownTypes = options.knownTypes ?? [];
40
+ const categories = options.categories ?? [];
41
+ const maxSuggestions = options.maxSuggestions ?? 5;
42
+ const prompt = `You analyze CAD/IFC drawing entities and infer naming/layer conventions for an import pipeline.
43
+
44
+ Unmatched entities (sample):
45
+ ${JSON.stringify(sample, null, 2)}
46
+
47
+ Available component types: ${knownTypes.length ? knownTypes.join(', ') : '(any)'}
48
+ Available categories: ${categories.length ? categories.join(', ') : '(any)'}
49
+
50
+ Suggest up to ${maxSuggestions} convention rules. For each, return:
51
+ - rule.componentType: chosen type
52
+ - rule.category: one of available categories (or 'unknown')
53
+ - rule.match.cad.blockNamePattern: regex pattern source string (e.g. "^STK_")
54
+ - rule.match.cad.layer: optional regex source string
55
+ - rule.build.idStrategy: 'fromAttribute' or 'auto'
56
+ - confidence: 0..1
57
+ - reasoning: short explanation`;
58
+ const raw = await this.base.generateJSON(prompt, { systemPrompt: SYSTEM_HINT });
59
+ return Array.isArray(raw) ? raw.map(r => ({ ...r, rule: hydrateRuleRegexes(r.rule) })) : [];
60
+ }
61
+ async classifyEntities(labeled, unlabeled, options = {}) {
62
+ const examples = labeled.map(l => ({
63
+ name: l.entity.attribs.name,
64
+ layer: l.entity.attribs.layer,
65
+ kind: l.entity.kind,
66
+ type: l.type,
67
+ category: l.category
68
+ }));
69
+ const targets = unlabeled.map(e => ({
70
+ uid: e.uid,
71
+ name: e.attribs.name,
72
+ layer: e.attribs.layer,
73
+ kind: e.kind
74
+ }));
75
+ const prompt = `Few-shot entity classification.
76
+
77
+ Labeled examples:
78
+ ${JSON.stringify(examples, null, 2)}
79
+
80
+ Available types: ${(options.knownTypes ?? []).join(', ') || '(any)'}
81
+ Available categories: ${(options.categories ?? []).join(', ') || '(any)'}
82
+
83
+ Classify these unlabeled entities. Return JSON array of:
84
+ - entityUid
85
+ - type
86
+ - category
87
+ - confidence: 0..1
88
+
89
+ Targets:
90
+ ${JSON.stringify(targets, null, 2)}`;
91
+ const raw = await this.base.generateJSON(prompt, { systemPrompt: SYSTEM_HINT });
92
+ return Array.isArray(raw) ? raw : [];
93
+ }
94
+ async parseFromImage(image, options = {}) {
95
+ if (!this.base.vision) {
96
+ throw new Error(`AI client (${this.base.id}) does not support vision input`);
97
+ }
98
+ const prompt = `Detect distinct objects in this drawing image.${options.context ? '\nContext: ' + options.context : ''}${options.categories ? '\nPossible categories: ' + options.categories.join(', ') : ''}
99
+
100
+ Return JSON array of UniversalEntity-like objects:
101
+ - uid: string
102
+ - kind: 'shape' | 'path' | 'text'
103
+ - position: {x, y}
104
+ - size: {width, height}
105
+ - bbox: {min:{x,y}, max:{x,y}}
106
+ - attribs: {name?, semanticType?, text?, layer?}`;
107
+ const text = await this.base.vision(image, prompt);
108
+ return (0, ai_client_base_1.parseJSONResponse)(text);
109
+ }
110
+ async parseNaturalLanguageMapping(text, availableTypes) {
111
+ const prompt = `Convert the following user description into ImportRule[] entries for the board-import pipeline.
112
+
113
+ Description: "${text}"
114
+ Available component types: ${availableTypes.join(', ')}
115
+
116
+ Return JSON array of:
117
+ - componentType
118
+ - category (one of: container, vehicle, path, region, equipment, io, marker, structure, unknown)
119
+ - match.cad.blockNamePattern: regex source string (e.g. "^STK_")
120
+ - match.cad.layer: optional regex source string
121
+ - build.idStrategy: 'fromAttribute' or 'auto'`;
122
+ const raw = await this.base.generateJSON(prompt, { systemPrompt: SYSTEM_HINT });
123
+ return Array.isArray(raw) ? raw.map(hydrateRuleRegexes) : [];
124
+ }
125
+ }
126
+ exports.DefaultBoardImportAI = DefaultBoardImportAI;
127
+ // ── 유틸 ─────────────────────────────────────────────────────────────
128
+ const SYSTEM_HINT = 'You are a CAD/IFC drawing analysis assistant. Output strict JSON only — no commentary, no markdown.';
129
+ /**
130
+ * LLM 응답의 regex pattern 문자열 → 실제 RegExp.
131
+ * 보통 "^STK_" 같은 source string 으로 응답.
132
+ */
133
+ function hydrateRuleRegexes(rule) {
134
+ if (!rule || !rule.match)
135
+ return rule;
136
+ const cad = rule.match.cad;
137
+ if (cad) {
138
+ const newCad = { ...cad };
139
+ if (typeof newCad.blockNamePattern === 'string') {
140
+ try {
141
+ newCad.blockNamePattern = new RegExp(newCad.blockNamePattern);
142
+ }
143
+ catch {
144
+ // invalid regex — 패턴 무시
145
+ delete newCad.blockNamePattern;
146
+ }
147
+ }
148
+ if (typeof newCad.layer === 'string') {
149
+ const m = newCad.layer.match(/^\/(.+)\/([a-z]*)$/);
150
+ if (m) {
151
+ try {
152
+ newCad.layer = new RegExp(m[1], m[2]);
153
+ }
154
+ catch { /* keep as string */ }
155
+ }
156
+ }
157
+ rule.match = { ...rule.match, cad: newCad };
158
+ }
159
+ return rule;
160
+ }
161
+ // ── Provider 등록 ──────────────────────────────────────────────────
162
+ let currentClient;
163
+ function setAIClient(client) {
164
+ currentClient = client;
165
+ }
166
+ function getAIClient() {
167
+ return currentClient;
168
+ }
169
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../server/service/ai/types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * BoardImportAI — board-import 의 **도메인 AI 인터페이스**.\n *\n * Provider 추상화 (chat/JSON/vision) 는 `@things-factory/ai-client-base` 의 `AIClient` 가 담당.\n * 이 모듈은 board-import 의 도메인 메서드 (컨벤션 추론, Few-shot 분류, 도면 이미지 파싱,\n * 자연어 → ImportRule) 만 정의하고, 기본 구현은 base AIClient 위에 LLM 프롬프트로 만든다.\n *\n * 사용 예:\n * ```ts\n * import { createAIClient } from '@things-factory/ai-client-base'\n * import { setAIClient, DefaultBoardImportAI } from '@things-factory/board-import'\n *\n * const base = createAIClient({ provider: 'anthropic', apiKey: '...' })\n * setAIClient(new DefaultBoardImportAI(base))\n * ```\n *\n * Phase 1-2 결정적 파이프라인은 AI 미사용. 위 등록은 Phase 3+ 메서드 활성화용.\n */\nimport { parseJSONResponse, type AIClient as BaseAIClient } from '@things-factory/ai-client-base'\nimport type { ComponentCategory, ImportRule } from '../types/registry.js'\nimport type { UniversalEntity } from '../types/index.js'\n\nexport interface BoardImportAI {\n /** 식별자 — 보통 base.id 와 동일 (provider:model) */\n readonly id: string\n\n /**\n * 미매칭 entity 묶음을 보고 컨벤션 규칙을 추론.\n * 예: \"STK_001, STK_002 가 stocker 인 것 같습니다\" → 정규식 규칙 제안\n */\n inferConventions?(\n entities: UniversalEntity[],\n options?: ConventionInferenceOptions\n ): Promise<RuleSuggestion[]>\n\n /**\n * 사용자가 일부 entity 를 라벨링했을 때, 나머지 미라벨 entity 의 카테고리·type 을 추론.\n * Few-shot 분류.\n */\n classifyEntities?(\n labeled: LabeledEntity[],\n unlabeled: UniversalEntity[],\n options?: ClassificationOptions\n ): Promise<EntityClassification[]>\n\n /**\n * 도면 이미지(PNG/JPG/PDF 페이지) 에서 객체를 검출하여 UniversalEntity 로 변환.\n * Phase 3 (이미지 어댑터) 에서 사용.\n */\n parseFromImage?(\n image: Buffer | Uint8Array,\n options?: ImageParseOptions\n ): Promise<UniversalEntity[]>\n\n /**\n * 자연어 매핑 정의 (\"STK는 stocker, BUF는 buffer\") → ImportRule[] 로 변환.\n */\n parseNaturalLanguageMapping?(\n text: string,\n availableTypes: string[]\n ): Promise<ImportRule[]>\n}\n\n// ── 옵션 / 결과 타입 ────────────────────────────────────────────────\n\nexport interface ConventionInferenceOptions {\n /** 알려진 도메인 type 후보 (LLM 이 이 안에서 고르도록) */\n knownTypes?: string[]\n /** 카테고리 후보 */\n categories?: ComponentCategory[]\n /** 최대 제안 개수 */\n maxSuggestions?: number\n}\n\nexport interface RuleSuggestion {\n /** 자동 생성된 ImportRule 후보 */\n rule: ImportRule\n /** 0..1 신뢰도 */\n confidence: number\n /** 추론 근거 */\n reasoning?: string\n}\n\nexport interface ClassificationOptions {\n knownTypes?: string[]\n categories?: ComponentCategory[]\n}\n\nexport interface LabeledEntity {\n entity: UniversalEntity\n type: string\n category?: ComponentCategory\n}\n\nexport interface EntityClassification {\n entityUid: string\n type: string\n category: ComponentCategory\n confidence: number\n}\n\nexport interface ImageParseOptions {\n /** 자연어 컨텍스트. 예: \"factory floor plan with AGV paths\" */\n context?: string\n categories?: ComponentCategory[]\n}\n\n// ── 기본 구현 — base AIClient 위에 LLM 프롬프트로 ────────────────────\n\nexport class DefaultBoardImportAI implements BoardImportAI {\n readonly id: string\n constructor(private base: BaseAIClient) {\n this.id = base.id\n }\n\n async inferConventions(\n entities: UniversalEntity[],\n options: ConventionInferenceOptions = {}\n ): Promise<RuleSuggestion[]> {\n const sample = entities.slice(0, 100).map(e => ({\n uid: e.uid,\n name: e.attribs.name,\n layer: e.attribs.layer,\n kind: e.kind,\n shape: e.attribs.shape\n }))\n const knownTypes = options.knownTypes ?? []\n const categories = options.categories ?? []\n const maxSuggestions = options.maxSuggestions ?? 5\n\n const prompt = `You analyze CAD/IFC drawing entities and infer naming/layer conventions for an import pipeline.\n\nUnmatched entities (sample):\n${JSON.stringify(sample, null, 2)}\n\nAvailable component types: ${knownTypes.length ? knownTypes.join(', ') : '(any)'}\nAvailable categories: ${categories.length ? categories.join(', ') : '(any)'}\n\nSuggest up to ${maxSuggestions} convention rules. For each, return:\n- rule.componentType: chosen type\n- rule.category: one of available categories (or 'unknown')\n- rule.match.cad.blockNamePattern: regex pattern source string (e.g. \"^STK_\")\n- rule.match.cad.layer: optional regex source string\n- rule.build.idStrategy: 'fromAttribute' or 'auto'\n- confidence: 0..1\n- reasoning: short explanation`\n\n const raw = await this.base.generateJSON<any[]>(prompt, { systemPrompt: SYSTEM_HINT })\n return Array.isArray(raw) ? raw.map(r => ({ ...r, rule: hydrateRuleRegexes(r.rule) })) : []\n }\n\n async classifyEntities(\n labeled: LabeledEntity[],\n unlabeled: UniversalEntity[],\n options: ClassificationOptions = {}\n ): Promise<EntityClassification[]> {\n const examples = labeled.map(l => ({\n name: l.entity.attribs.name,\n layer: l.entity.attribs.layer,\n kind: l.entity.kind,\n type: l.type,\n category: l.category\n }))\n const targets = unlabeled.map(e => ({\n uid: e.uid,\n name: e.attribs.name,\n layer: e.attribs.layer,\n kind: e.kind\n }))\n\n const prompt = `Few-shot entity classification.\n\nLabeled examples:\n${JSON.stringify(examples, null, 2)}\n\nAvailable types: ${(options.knownTypes ?? []).join(', ') || '(any)'}\nAvailable categories: ${(options.categories ?? []).join(', ') || '(any)'}\n\nClassify these unlabeled entities. Return JSON array of:\n- entityUid\n- type\n- category\n- confidence: 0..1\n\nTargets:\n${JSON.stringify(targets, null, 2)}`\n\n const raw = await this.base.generateJSON<EntityClassification[]>(prompt, { systemPrompt: SYSTEM_HINT })\n return Array.isArray(raw) ? raw : []\n }\n\n async parseFromImage(\n image: Buffer | Uint8Array,\n options: ImageParseOptions = {}\n ): Promise<UniversalEntity[]> {\n if (!this.base.vision) {\n throw new Error(`AI client (${this.base.id}) does not support vision input`)\n }\n const prompt = `Detect distinct objects in this drawing image.${\n options.context ? '\\nContext: ' + options.context : ''\n }${\n options.categories ? '\\nPossible categories: ' + options.categories.join(', ') : ''\n }\n\nReturn JSON array of UniversalEntity-like objects:\n- uid: string\n- kind: 'shape' | 'path' | 'text'\n- position: {x, y}\n- size: {width, height}\n- bbox: {min:{x,y}, max:{x,y}}\n- attribs: {name?, semanticType?, text?, layer?}`\n\n const text = await this.base.vision(image as Buffer, prompt)\n return parseJSONResponse<UniversalEntity[]>(text)\n }\n\n async parseNaturalLanguageMapping(\n text: string,\n availableTypes: string[]\n ): Promise<ImportRule[]> {\n const prompt = `Convert the following user description into ImportRule[] entries for the board-import pipeline.\n\nDescription: \"${text}\"\nAvailable component types: ${availableTypes.join(', ')}\n\nReturn JSON array of:\n- componentType\n- category (one of: container, vehicle, path, region, equipment, io, marker, structure, unknown)\n- match.cad.blockNamePattern: regex source string (e.g. \"^STK_\")\n- match.cad.layer: optional regex source string\n- build.idStrategy: 'fromAttribute' or 'auto'`\n\n const raw = await this.base.generateJSON<any[]>(prompt, { systemPrompt: SYSTEM_HINT })\n return Array.isArray(raw) ? raw.map(hydrateRuleRegexes) : []\n }\n}\n\n// ── 유틸 ─────────────────────────────────────────────────────────────\n\nconst SYSTEM_HINT =\n 'You are a CAD/IFC drawing analysis assistant. Output strict JSON only — no commentary, no markdown.'\n\n/**\n * LLM 응답의 regex pattern 문자열 → 실제 RegExp.\n * 보통 \"^STK_\" 같은 source string 으로 응답.\n */\nfunction hydrateRuleRegexes<T extends { match?: any }>(rule: T): T {\n if (!rule || !rule.match) return rule\n const cad = rule.match.cad\n if (cad) {\n const newCad = { ...cad }\n if (typeof newCad.blockNamePattern === 'string') {\n try {\n newCad.blockNamePattern = new RegExp(newCad.blockNamePattern)\n } catch {\n // invalid regex — 패턴 무시\n delete newCad.blockNamePattern\n }\n }\n if (typeof newCad.layer === 'string') {\n const m = newCad.layer.match(/^\\/(.+)\\/([a-z]*)$/)\n if (m) {\n try { newCad.layer = new RegExp(m[1], m[2]) } catch { /* keep as string */ }\n }\n }\n rule.match = { ...rule.match, cad: newCad }\n }\n return rule\n}\n\n// ── Provider 등록 ──────────────────────────────────────────────────\n\nlet currentClient: BoardImportAI | undefined\n\nexport function setAIClient(client: BoardImportAI | undefined): void {\n currentClient = client\n}\n\nexport function getAIClient(): BoardImportAI | undefined {\n return currentClient\n}\n\n/** Deprecated alias — 기존 코드 호환용. 새로 작성 시 BoardImportAI 사용. */\nexport type AIClient = BoardImportAI\n"]}
@@ -0,0 +1,16 @@
1
+ import type { BoardComponent } from '../pipeline/stage3-board.js';
2
+ export interface ConvertOptions {
3
+ scopes?: string[];
4
+ /** 새 컴포넌트의 ID. 미지정 시 ImportRule 의 idStrategy 적용 또는 자동 생성 */
5
+ id?: string;
6
+ }
7
+ /**
8
+ * GenericObject 를 지정된 도메인 type 으로 변환한다.
9
+ * 기존 좌표/크기/회전/source 보존.
10
+ */
11
+ export declare function convertGenericTo(generic: BoardComponent, toType: string, options?: ConvertOptions): BoardComponent;
12
+ /**
13
+ * 변환된 컴포넌트를 다시 GenericObject 로 되돌린다.
14
+ * convertedFrom 정보가 있어야 가능.
15
+ */
16
+ export declare function revertToGeneric(component: BoardComponent, options?: ConvertOptions): BoardComponent;