@things-factory/board-import 10.1.150 → 10.1.155

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.
@@ -1,5 +1,20 @@
1
+ /**
2
+ * Image Format Adapter — 이미지 (PNG/JPG/WebP/GIF) → UniversalEntity 목록.
3
+ *
4
+ * VLM (Vision LLM) 의존. board-import 의 BoardImportAI.parseFromImage 가 핵심 작업을
5
+ * 담당하므로 이 어댑터는 그 위에 얇은 wrapper. AI client 가 등록되지 않았거나 vision 미지원
6
+ * provider 면 parse 시점에 명확한 에러로 실패한다 (detect 는 통과 — 외부 입력 식별만).
7
+ *
8
+ * zero-to-twin 메인 트랙 (조감도/사진 업로드 → 보드) 의 첫 어댑터.
9
+ */
10
+ import { type BoardImportAI } from '../ai/types';
1
11
  import type { AdapterCapabilities, AdapterInput, FormatAdapter, ParseOptions, ParseResult } from '../types/index';
2
12
  export interface ImageParseOptions extends ParseOptions {
13
+ /**
14
+ * The AI of the domain the import runs for - asked only when an image is parsed, so a drawing that needs no AI
15
+ * imports in a domain with no AI agent.
16
+ */
17
+ resolveAI?: () => Promise<BoardImportAI>;
3
18
  /** 자연어 컨텍스트 — VLM 에 함께 전달. 예: "factory floor plan with AGV paths and stockers" */
4
19
  context?: string;
5
20
  /** 후보 카테고리 — VLM 이 그 안에서 라벨링하도록 가이드 */
@@ -30,9 +30,9 @@ class ImageAdapter {
30
30
  return detectImageMagic(file);
31
31
  }
32
32
  async parse(file, options = {}) {
33
- const ai = (0, types_1.getAIClient)();
33
+ const ai = (0, types_1.getAIClient)() ?? (await options.resolveAI?.());
34
34
  if (!ai) {
35
- throw new Error('Image import requires a registered BoardImportAI (call setAIClient first).');
35
+ throw new Error('Image import needs the AI of the domain it runs for (resolveAI) or a registered BoardImportAI.');
36
36
  }
37
37
  if (!ai.parseFromImage) {
38
38
  throw new Error('Registered BoardImportAI does not implement parseFromImage (no VLM support).');
@@ -1 +1 @@
1
- {"version":3,"file":"image-adapter.js","sourceRoot":"","sources":["../../../server/service/adapters/image-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Image Format Adapter — 이미지 (PNG/JPG/WebP/GIF) → UniversalEntity 목록.\n *\n * VLM (Vision LLM) 의존. board-import 의 BoardImportAI.parseFromImage 가 핵심 작업을\n * 담당하므로 이 어댑터는 그 위에 얇은 wrapper. AI client 가 등록되지 않았거나 vision 미지원\n * provider 면 parse 시점에 명확한 에러로 실패한다 (detect 는 통과 — 외부 입력 식별만).\n *\n * zero-to-twin 메인 트랙 (조감도/사진 업로드 → 보드) 의 첫 어댑터.\n */\nimport { getAIClient } from '../ai/types'\nimport type {\n AdapterCapabilities,\n AdapterInput,\n BoundingBox,\n FormatAdapter,\n ParseOptions,\n ParseResult,\n UniversalEntity\n} from '../types/index'\n\nconst IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp']\n\nexport interface ImageParseOptions extends ParseOptions {\n /** 자연어 컨텍스트 — VLM 에 함께 전달. 예: \"factory floor plan with AGV paths and stockers\" */\n context?: string\n /** 후보 카테고리 — VLM 이 그 안에서 라벨링하도록 가이드 */\n categories?: string[]\n /**\n * 이미지 dimension — VLM 프롬프트가 픽셀 좌표계를 명시하기 위해 사용. ImageAdapter 가\n * 헤더에서 읽어 자동 주입한다. 호출자가 직접 부를 때도 명시 가능.\n */\n imageWidth?: number\n imageHeight?: number\n /**\n * 사용자 자유서술 hint — 도면 설명, 강조 부분, 카테고리 가이드 등.\n * VLM 의 view type 분류 + entity 추출 양쪽에 추가 컨텍스트로 합류.\n */\n userPrompt?: string\n /**\n * View type 명시 (강제). 미지정 시 ImageAdapter 가 1-pass classify 로 자동 결정.\n * - 'top-down' : orthographic 도면 / 평면도 (픽셀 좌표 그대로)\n * - 'perspective-3d' : 3D 렌더 / 조감도 / 등각투상 (floor plan 추론)\n * - 'photo' : 실사진 (floor plan 추론)\n */\n viewType?: 'top-down' | 'perspective-3d' | 'photo'\n}\n\nexport class ImageAdapter implements FormatAdapter {\n readonly format = 'image'\n readonly supportedExtensions = IMAGE_EXTENSIONS\n readonly capabilities: AdapterCapabilities = {\n has2D: true,\n has3D: false,\n hasSemanticLabels: true, // VLM 이 의미 라벨링\n hasTextLabels: true, // VLM 이 텍스트 인식\n hasAttributes: false // CAD 의 명시적 속성 없음\n }\n\n async detect(file: AdapterInput, filename?: string): Promise<boolean> {\n if (filename && IMAGE_EXTENSIONS.some(ext => filename.toLowerCase().endsWith(ext))) return true\n return detectImageMagic(file)\n }\n\n async parse(file: AdapterInput, options: ImageParseOptions = {}): Promise<ParseResult> {\n const ai = getAIClient()\n if (!ai) {\n throw new Error('Image import requires a registered BoardImportAI (call setAIClient first).')\n }\n if (!ai.parseFromImage) {\n throw new Error('Registered BoardImportAI does not implement parseFromImage (no VLM support).')\n }\n\n const buffer = toBuffer(file)\n if (!buffer) {\n throw new Error('Image input must be a Buffer / ArrayBuffer / Uint8Array.')\n }\n\n // 이미지 dimension 을 미리 알아내야 VLM 에 좌표계를 명시적으로 지시할 수 있다.\n // 미지정 시 LLM 이 0..1 정규화 좌표로 응답해 stage3 가 1px 도형을 그리는 회귀가 발생.\n const dims = readImageDimensions(buffer)\n\n const raw = await ai.parseFromImage(buffer, {\n context: options.context,\n categories: options.categories as any,\n imageWidth: dims?.width,\n imageHeight: dims?.height,\n userPrompt: options.userPrompt,\n viewType: options.viewType\n } as any)\n\n // 응답 흡수 — 3 가지 형태 다 수용:\n // 1) { variants: [...] } — 권장 (multi-variant)\n // 2) { entities: [...], ... } — 구버전 single-variant\n // 3) [ entity, ... ] — 가장 구버전 (entities only)\n const viewType: 'top-down' | 'perspective-3d' | 'photo' = options.viewType\n ? options.viewType\n : raw && typeof raw === 'object' && !Array.isArray(raw)\n ? ((raw as any).viewType ?? 'top-down')\n : 'top-down'\n const confidence: number =\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? Number((raw as any).confidence ?? 0.5)\n : 0.5\n const reasoning: string | undefined =\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? (raw as any).reasoning\n : undefined\n\n const rawVariants: any[] = (() => {\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n if (Array.isArray((raw as any).variants) && (raw as any).variants.length > 0) {\n return (raw as any).variants\n }\n if (Array.isArray((raw as any).entities)) {\n // 구버전 single-variant 응답 — 단일 시안으로 감쌈\n return [\n {\n id: 'default',\n label: 'default',\n entities: (raw as any).entities,\n subjectName: (raw as any).subjectName,\n subjectDescription: (raw as any).subjectDescription,\n importStrategy: (raw as any).importStrategy\n }\n ]\n }\n }\n if (Array.isArray(raw)) {\n return [{ id: 'default', label: 'default', entities: raw as any[] }]\n }\n return []\n })()\n\n // 시안별 entity 정규화 + 좌표 보정 + grid fallback — 시안 무관 동일 적용.\n const usedGridLayout = viewType === 'perspective-3d' || viewType === 'photo'\n const denormDim = viewType === 'top-down' ? dims : { width: 1000, height: 1000 }\n\n const normalizedVariants = rawVariants.map((v: any) => {\n let entities = (Array.isArray(v.entities) ? v.entities : []).map((e: any, i: number) =>\n normalizeEntity(e, i)\n )\n entities = denormalizeIfFractional(entities, denormDim)\n if (usedGridLayout) entities = layoutInGrid(entities)\n const variantBbox = computeOuterBbox(entities)\n return {\n id: typeof v.id === 'string' ? v.id : 'variant',\n label: typeof v.label === 'string' ? v.label : 'variant',\n description: typeof v.description === 'string' ? v.description : undefined,\n entities,\n bbox: variantBbox,\n subjectName: typeof v.subjectName === 'string' ? v.subjectName : undefined,\n subjectDescription:\n typeof v.subjectDescription === 'string' ? v.subjectDescription : undefined,\n importStrategy: typeof v.importStrategy === 'string' ? v.importStrategy : undefined\n }\n })\n\n // default = 첫 variant. 빈 배열일 때 fallback.\n const defaultVariant = normalizedVariants[0]\n const entities = defaultVariant?.entities ?? []\n const subjectName = defaultVariant?.subjectName\n const subjectDescription = defaultVariant?.subjectDescription\n const importStrategy = defaultVariant?.importStrategy\n\n return {\n entities,\n variants: normalizedVariants,\n metadata: {\n format: 'image',\n source: 'vlm',\n aiClientId: ai.id,\n imageWidth: dims?.width,\n imageHeight: dims?.height,\n viewType,\n ...(usedGridLayout ? { layoutMethod: 'grid' as const } : {}),\n viewTypeConfidence: confidence,\n ...(reasoning ? { viewTypeReasoning: reasoning } : {}),\n ...(importStrategy ? { importStrategy } : {}),\n ...(subjectName ? { subjectName } : {}),\n ...(subjectDescription ? { subjectDescription } : {}),\n ...(options.userPrompt ? { userPrompt: options.userPrompt } : {})\n },\n bbox: computeOuterBbox(entities),\n units: 'unknown'\n }\n }\n}\n\n/**\n * PNG / JPEG / WebP / GIF / BMP 헤더에서 width/height 추출. dep 없이 inline 처리.\n * 실패 시 undefined — 호출자가 프롬프트에 dimension 을 포함하지 않게 한다.\n */\nfunction readImageDimensions(buf: Buffer): { width: number; height: number } | undefined {\n if (!buf || buf.length < 26) return undefined\n // PNG: 8-byte signature + IHDR (length:4, type:4, width:4, height:4)\n if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) {\n const width = buf.readUInt32BE(16)\n const height = buf.readUInt32BE(20)\n return Number.isFinite(width) && Number.isFinite(height) ? { width, height } : undefined\n }\n // JPEG: scan for SOFn (Start Of Frame) markers — 0xFFC0..0xFFCF except DHT/JPG/DAC\n if (buf[0] === 0xff && buf[1] === 0xd8) {\n let i = 2\n while (i + 9 < buf.length) {\n if (buf[i] !== 0xff) {\n i++\n continue\n }\n // skip filler 0xFF\n while (i < buf.length && buf[i] === 0xff) i++\n const marker = buf[i]\n i++\n if (marker === 0xd8 || marker === 0xd9) continue // SOI/EOI\n if (i + 1 >= buf.length) return undefined\n const segLen = buf.readUInt16BE(i)\n // SOFn (baseline=C0, progressive=C2, etc) — exclude DHT(C4), JPG(C8), DAC(CC)\n if (\n marker >= 0xc0 &&\n marker <= 0xcf &&\n marker !== 0xc4 &&\n marker !== 0xc8 &&\n marker !== 0xcc\n ) {\n if (i + 7 >= buf.length) return undefined\n const height = buf.readUInt16BE(i + 3)\n const width = buf.readUInt16BE(i + 5)\n return { width, height }\n }\n i += segLen\n }\n return undefined\n }\n // GIF: bytes 6-9 (LE width/height)\n if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x38) {\n return { width: buf.readUInt16LE(6), height: buf.readUInt16LE(8) }\n }\n // BMP: width@18 (LE), height@22 (LE)\n if (buf[0] === 0x42 && buf[1] === 0x4d) {\n return { width: buf.readUInt32LE(18), height: Math.abs(buf.readInt32LE(22)) }\n }\n // WebP (VP8/VP8L/VP8X) — 30+ bytes for dimension info\n if (\n buf[0] === 0x52 &&\n buf[1] === 0x49 &&\n buf[2] === 0x46 &&\n buf[3] === 0x46 &&\n buf[8] === 0x57 &&\n buf[9] === 0x45 &&\n buf[10] === 0x42 &&\n buf[11] === 0x50 &&\n buf.length >= 30\n ) {\n // VP8 lossy: chunk header at offset 12, dims at offset 26 (14-bit each)\n if (buf[12] === 0x56 && buf[13] === 0x50 && buf[14] === 0x38 && buf[15] === 0x20) {\n const width = buf.readUInt16LE(26) & 0x3fff\n const height = buf.readUInt16LE(28) & 0x3fff\n return { width, height }\n }\n // VP8L lossless: dims at offset 21 (14-bit packed)\n if (buf[12] === 0x56 && buf[13] === 0x50 && buf[14] === 0x38 && buf[15] === 0x4c) {\n const b0 = buf[21]\n const b1 = buf[22]\n const b2 = buf[23]\n const b3 = buf[24]\n const width = 1 + (((b1 & 0x3f) << 8) | b0)\n const height = 1 + (((b3 & 0x0f) << 10) | (b2 << 2) | ((b1 & 0xc0) >> 6))\n return { width, height }\n }\n }\n return undefined\n}\n\n/**\n * Perspective view 의 entity 들을 격자로 균등 배치한다.\n *\n * VLM 은 perspective 좌표 → floor plan 좌표 변환이 불가능에 가깝다 (학습 데이터 부재 +\n * 명시적 3D 세계 모델 없음). screen 좌표를 그대로 깔면 \"perspective 가 평평해진\" 결과.\n * 차라리 좌표를 무시하고 격자로 깔고 사용자가 직접 옮기는 게 정직하다.\n *\n * 정책:\n * - 각 entity 의 size 는 보존 (VLM 이 추정한 상대 크기는 비교적 신뢰 가능)\n * - 격자 col 수 = ceil(sqrt(N)), row 수 = ceil(N/cols)\n * - tile 간격 = max(entity 평균 width/height) * 1.4\n * - 보드 우상단부터 row-major 로 배치\n */\nfunction layoutInGrid(entities: UniversalEntity[]): UniversalEntity[] {\n if (entities.length === 0) return entities\n const cols = Math.max(1, Math.ceil(Math.sqrt(entities.length)))\n\n // tile 크기 결정 — entity 평균 size 의 1.4 배. size 미지정이면 80 plan-unit.\n const sizes = entities.filter(e => e.size).map(e => e.size!)\n const avgW = sizes.length ? sizes.reduce((s, v) => s + v.width, 0) / sizes.length : 80\n const avgH = sizes.length ? sizes.reduce((s, v) => s + v.height, 0) / sizes.length : 80\n const tileW = Math.max(80, avgW) * 1.4\n const tileH = Math.max(80, avgH) * 1.4\n\n return entities.map((e, i) => {\n const col = i % cols\n const row = Math.floor(i / cols)\n const x = col * tileW\n const y = row * tileH\n const w = e.size?.width ?? 80\n const h = e.size?.height ?? 80\n return {\n ...e,\n position: { x, y },\n size: { width: w, height: h },\n bbox: { min: { x, y }, max: { x: x + w, y: y + h } }\n }\n })\n}\n\n/**\n * 모든 entity 의 좌표/크기가 [0..2] 범위에 들어가면 normalized 로 간주 (LLM 의 흔한 응답 패턴),\n * image dimension 으로 곱해 픽셀 좌표로 보정. dim 이 없으면 그대로 반환.\n */\nfunction denormalizeIfFractional(\n entities: UniversalEntity[],\n dims: { width: number; height: number } | undefined\n): UniversalEntity[] {\n if (!dims || entities.length === 0) return entities\n const isAllFractional = entities.every(e => {\n const xs = [e.position.x, e.bbox.min.x, e.bbox.max.x, (e.size?.width ?? 0)]\n const ys = [e.position.y, e.bbox.min.y, e.bbox.max.y, (e.size?.height ?? 0)]\n return [...xs, ...ys].every(v => Number.isFinite(v) && v >= 0 && v <= 2)\n })\n if (!isAllFractional) return entities\n const sx = dims.width\n const sy = dims.height\n return entities.map(e => ({\n ...e,\n position: { x: e.position.x * sx, y: e.position.y * sy },\n size: e.size ? { width: e.size.width * sx, height: e.size.height * sy } : undefined,\n bbox: {\n min: { x: e.bbox.min.x * sx, y: e.bbox.min.y * sy },\n max: { x: e.bbox.max.x * sx, y: e.bbox.max.y * sy }\n }\n }))\n}\n\n// ── 헬퍼 ─────────────────────────────────────────────────────────────\n\n/**\n * 매직 바이트로 이미지 형식 감지. filename 이 없거나 확장자가 모호한 경우 fallback.\n * 외부 reference 가 placeholder 로 응답되는 시나리오 (예: GLB top-view fetch 실패) 도\n * 같은 binary 검사로 식별 가능.\n */\nfunction detectImageMagic(file: AdapterInput): boolean {\n const buf = toBuffer(file)\n if (!buf || buf.length < 8) return false\n\n // PNG: 89 50 4E 47 0D 0A 1A 0A\n if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return true\n // JPEG: FF D8 FF\n if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return true\n // GIF: 47 49 46 38\n if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x38) return true\n // BMP: 42 4D\n if (buf[0] === 0x42 && buf[1] === 0x4d) return true\n // WebP: RIFF....WEBP\n if (\n buf[0] === 0x52 &&\n buf[1] === 0x49 &&\n buf[2] === 0x46 &&\n buf[3] === 0x46 &&\n buf.length >= 12 &&\n buf[8] === 0x57 &&\n buf[9] === 0x45 &&\n buf[10] === 0x42 &&\n buf[11] === 0x50\n ) {\n return true\n }\n return false\n}\n\n/**\n * VLM 응답 entity 의 결손 필드 복구. uid 누락 → 인덱스 기반, bbox 누락 → position+size 로 합성,\n * size/position 비숫자 값 → 0 fallback. 후속 stage2 가 안전하게 진행되도록 보호.\n */\nfunction normalizeEntity(e: any, index: number): UniversalEntity {\n const uid = typeof e?.uid === 'string' && e.uid ? e.uid : `img-${index}`\n const kind = e?.kind ?? 'shape'\n const position =\n e?.position && Number.isFinite(e.position.x) && Number.isFinite(e.position.y)\n ? { x: e.position.x, y: e.position.y }\n : { x: 0, y: 0 }\n const size =\n e?.size && Number.isFinite(e.size.width) && Number.isFinite(e.size.height)\n ? { width: e.size.width, height: e.size.height }\n : undefined\n\n let bbox: BoundingBox\n if (\n e?.bbox?.min &&\n e?.bbox?.max &&\n Number.isFinite(e.bbox.min.x) &&\n Number.isFinite(e.bbox.min.y) &&\n Number.isFinite(e.bbox.max.x) &&\n Number.isFinite(e.bbox.max.y)\n ) {\n bbox = { min: { x: e.bbox.min.x, y: e.bbox.min.y }, max: { x: e.bbox.max.x, y: e.bbox.max.y } }\n } else {\n const w = size?.width ?? 0\n const h = size?.height ?? 0\n bbox = {\n min: { x: position.x, y: position.y },\n max: { x: position.x + w, y: position.y + h }\n }\n }\n\n // bbox 가 position+size 와 일관되지 않으면 (VLM 이 lossy 한 bbox 를 줌) 일관성 보장.\n // size 가 있을 때 bbox 는 항상 position..(position+size) 를 cover 해야 stage3-board 의\n // 보드 크기 산정 / flipY 계산이 올바르다. 안 그러면 컴포넌트가 root 외곽으로 spill.\n if (size) {\n if (bbox.min.x > position.x) bbox.min.x = position.x\n if (bbox.min.y > position.y) bbox.min.y = position.y\n if (bbox.max.x < position.x + size.width) bbox.max.x = position.x + size.width\n if (bbox.max.y < position.y + size.height) bbox.max.y = position.y + size.height\n }\n\n return {\n uid,\n kind,\n position,\n size,\n rotation: typeof e?.rotation === 'number' ? e.rotation : undefined,\n bbox,\n attribs: e?.attribs && typeof e.attribs === 'object' ? { ...e.attribs } : {}\n }\n}\n\n/**\n * 모든 entity 의 bbox 를 합쳐 외부 bbox 산출. 빈 입력 / 무효값은 (0,0)-(0,0) fallback.\n */\nfunction computeOuterBbox(entities: UniversalEntity[]): BoundingBox {\n if (entities.length === 0) return { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const e of entities) {\n if (Number.isFinite(e.bbox.min.x) && e.bbox.min.x < minX) minX = e.bbox.min.x\n if (Number.isFinite(e.bbox.min.y) && e.bbox.min.y < minY) minY = e.bbox.min.y\n if (Number.isFinite(e.bbox.max.x) && e.bbox.max.x > maxX) maxX = e.bbox.max.x\n if (Number.isFinite(e.bbox.max.y) && e.bbox.max.y > maxY) maxY = e.bbox.max.y\n }\n if (!Number.isFinite(minX)) {\n return { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }\n }\n return { min: { x: minX, y: minY }, max: { x: maxX, y: maxY } }\n}\n\nfunction toBuffer(file: AdapterInput): Buffer | undefined {\n if (Buffer.isBuffer(file)) return file\n if (file instanceof Uint8Array) return Buffer.from(file)\n if (file instanceof ArrayBuffer) return Buffer.from(new Uint8Array(file))\n if (typeof file === 'string') {\n // 이미지 파싱은 string 입력을 의미 있게 처리할 방법이 없음 (base64 가정은 위험).\n // 명시적으로 binary 만 수용 — string 은 undefined 로 떨어져 parse 단계에서 명확한 에러.\n return undefined\n }\n return undefined\n}\n"]}
1
+ {"version":3,"file":"image-adapter.js","sourceRoot":"","sources":["../../../server/service/adapters/image-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Image Format Adapter — 이미지 (PNG/JPG/WebP/GIF) → UniversalEntity 목록.\n *\n * VLM (Vision LLM) 의존. board-import 의 BoardImportAI.parseFromImage 가 핵심 작업을\n * 담당하므로 이 어댑터는 그 위에 얇은 wrapper. AI client 가 등록되지 않았거나 vision 미지원\n * provider 면 parse 시점에 명확한 에러로 실패한다 (detect 는 통과 — 외부 입력 식별만).\n *\n * zero-to-twin 메인 트랙 (조감도/사진 업로드 → 보드) 의 첫 어댑터.\n */\nimport { getAIClient, type BoardImportAI } from '../ai/types'\nimport type {\n AdapterCapabilities,\n AdapterInput,\n BoundingBox,\n FormatAdapter,\n ParseOptions,\n ParseResult,\n UniversalEntity\n} from '../types/index'\n\nconst IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.bmp']\n\nexport interface ImageParseOptions extends ParseOptions {\n /**\n * The AI of the domain the import runs for - asked only when an image is parsed, so a drawing that needs no AI\n * imports in a domain with no AI agent.\n */\n resolveAI?: () => Promise<BoardImportAI>\n /** 자연어 컨텍스트 — VLM 에 함께 전달. 예: \"factory floor plan with AGV paths and stockers\" */\n context?: string\n /** 후보 카테고리 — VLM 이 그 안에서 라벨링하도록 가이드 */\n categories?: string[]\n /**\n * 이미지 dimension — VLM 프롬프트가 픽셀 좌표계를 명시하기 위해 사용. ImageAdapter 가\n * 헤더에서 읽어 자동 주입한다. 호출자가 직접 부를 때도 명시 가능.\n */\n imageWidth?: number\n imageHeight?: number\n /**\n * 사용자 자유서술 hint — 도면 설명, 강조 부분, 카테고리 가이드 등.\n * VLM 의 view type 분류 + entity 추출 양쪽에 추가 컨텍스트로 합류.\n */\n userPrompt?: string\n /**\n * View type 명시 (강제). 미지정 시 ImageAdapter 가 1-pass classify 로 자동 결정.\n * - 'top-down' : orthographic 도면 / 평면도 (픽셀 좌표 그대로)\n * - 'perspective-3d' : 3D 렌더 / 조감도 / 등각투상 (floor plan 추론)\n * - 'photo' : 실사진 (floor plan 추론)\n */\n viewType?: 'top-down' | 'perspective-3d' | 'photo'\n}\n\nexport class ImageAdapter implements FormatAdapter {\n readonly format = 'image'\n readonly supportedExtensions = IMAGE_EXTENSIONS\n readonly capabilities: AdapterCapabilities = {\n has2D: true,\n has3D: false,\n hasSemanticLabels: true, // VLM 이 의미 라벨링\n hasTextLabels: true, // VLM 이 텍스트 인식\n hasAttributes: false // CAD 의 명시적 속성 없음\n }\n\n async detect(file: AdapterInput, filename?: string): Promise<boolean> {\n if (filename && IMAGE_EXTENSIONS.some(ext => filename.toLowerCase().endsWith(ext))) return true\n return detectImageMagic(file)\n }\n\n async parse(file: AdapterInput, options: ImageParseOptions = {}): Promise<ParseResult> {\n const ai = getAIClient() ?? (await options.resolveAI?.())\n if (!ai) {\n throw new Error('Image import needs the AI of the domain it runs for (resolveAI) or a registered BoardImportAI.')\n }\n if (!ai.parseFromImage) {\n throw new Error('Registered BoardImportAI does not implement parseFromImage (no VLM support).')\n }\n\n const buffer = toBuffer(file)\n if (!buffer) {\n throw new Error('Image input must be a Buffer / ArrayBuffer / Uint8Array.')\n }\n\n // 이미지 dimension 을 미리 알아내야 VLM 에 좌표계를 명시적으로 지시할 수 있다.\n // 미지정 시 LLM 이 0..1 정규화 좌표로 응답해 stage3 가 1px 도형을 그리는 회귀가 발생.\n const dims = readImageDimensions(buffer)\n\n const raw = await ai.parseFromImage(buffer, {\n context: options.context,\n categories: options.categories as any,\n imageWidth: dims?.width,\n imageHeight: dims?.height,\n userPrompt: options.userPrompt,\n viewType: options.viewType\n } as any)\n\n // 응답 흡수 — 3 가지 형태 다 수용:\n // 1) { variants: [...] } — 권장 (multi-variant)\n // 2) { entities: [...], ... } — 구버전 single-variant\n // 3) [ entity, ... ] — 가장 구버전 (entities only)\n const viewType: 'top-down' | 'perspective-3d' | 'photo' = options.viewType\n ? options.viewType\n : raw && typeof raw === 'object' && !Array.isArray(raw)\n ? ((raw as any).viewType ?? 'top-down')\n : 'top-down'\n const confidence: number =\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? Number((raw as any).confidence ?? 0.5)\n : 0.5\n const reasoning: string | undefined =\n raw && typeof raw === 'object' && !Array.isArray(raw)\n ? (raw as any).reasoning\n : undefined\n\n const rawVariants: any[] = (() => {\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n if (Array.isArray((raw as any).variants) && (raw as any).variants.length > 0) {\n return (raw as any).variants\n }\n if (Array.isArray((raw as any).entities)) {\n // 구버전 single-variant 응답 — 단일 시안으로 감쌈\n return [\n {\n id: 'default',\n label: 'default',\n entities: (raw as any).entities,\n subjectName: (raw as any).subjectName,\n subjectDescription: (raw as any).subjectDescription,\n importStrategy: (raw as any).importStrategy\n }\n ]\n }\n }\n if (Array.isArray(raw)) {\n return [{ id: 'default', label: 'default', entities: raw as any[] }]\n }\n return []\n })()\n\n // 시안별 entity 정규화 + 좌표 보정 + grid fallback — 시안 무관 동일 적용.\n const usedGridLayout = viewType === 'perspective-3d' || viewType === 'photo'\n const denormDim = viewType === 'top-down' ? dims : { width: 1000, height: 1000 }\n\n const normalizedVariants = rawVariants.map((v: any) => {\n let entities = (Array.isArray(v.entities) ? v.entities : []).map((e: any, i: number) =>\n normalizeEntity(e, i)\n )\n entities = denormalizeIfFractional(entities, denormDim)\n if (usedGridLayout) entities = layoutInGrid(entities)\n const variantBbox = computeOuterBbox(entities)\n return {\n id: typeof v.id === 'string' ? v.id : 'variant',\n label: typeof v.label === 'string' ? v.label : 'variant',\n description: typeof v.description === 'string' ? v.description : undefined,\n entities,\n bbox: variantBbox,\n subjectName: typeof v.subjectName === 'string' ? v.subjectName : undefined,\n subjectDescription:\n typeof v.subjectDescription === 'string' ? v.subjectDescription : undefined,\n importStrategy: typeof v.importStrategy === 'string' ? v.importStrategy : undefined\n }\n })\n\n // default = 첫 variant. 빈 배열일 때 fallback.\n const defaultVariant = normalizedVariants[0]\n const entities = defaultVariant?.entities ?? []\n const subjectName = defaultVariant?.subjectName\n const subjectDescription = defaultVariant?.subjectDescription\n const importStrategy = defaultVariant?.importStrategy\n\n return {\n entities,\n variants: normalizedVariants,\n metadata: {\n format: 'image',\n source: 'vlm',\n aiClientId: ai.id,\n imageWidth: dims?.width,\n imageHeight: dims?.height,\n viewType,\n ...(usedGridLayout ? { layoutMethod: 'grid' as const } : {}),\n viewTypeConfidence: confidence,\n ...(reasoning ? { viewTypeReasoning: reasoning } : {}),\n ...(importStrategy ? { importStrategy } : {}),\n ...(subjectName ? { subjectName } : {}),\n ...(subjectDescription ? { subjectDescription } : {}),\n ...(options.userPrompt ? { userPrompt: options.userPrompt } : {})\n },\n bbox: computeOuterBbox(entities),\n units: 'unknown'\n }\n }\n}\n\n/**\n * PNG / JPEG / WebP / GIF / BMP 헤더에서 width/height 추출. dep 없이 inline 처리.\n * 실패 시 undefined — 호출자가 프롬프트에 dimension 을 포함하지 않게 한다.\n */\nfunction readImageDimensions(buf: Buffer): { width: number; height: number } | undefined {\n if (!buf || buf.length < 26) return undefined\n // PNG: 8-byte signature + IHDR (length:4, type:4, width:4, height:4)\n if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) {\n const width = buf.readUInt32BE(16)\n const height = buf.readUInt32BE(20)\n return Number.isFinite(width) && Number.isFinite(height) ? { width, height } : undefined\n }\n // JPEG: scan for SOFn (Start Of Frame) markers — 0xFFC0..0xFFCF except DHT/JPG/DAC\n if (buf[0] === 0xff && buf[1] === 0xd8) {\n let i = 2\n while (i + 9 < buf.length) {\n if (buf[i] !== 0xff) {\n i++\n continue\n }\n // skip filler 0xFF\n while (i < buf.length && buf[i] === 0xff) i++\n const marker = buf[i]\n i++\n if (marker === 0xd8 || marker === 0xd9) continue // SOI/EOI\n if (i + 1 >= buf.length) return undefined\n const segLen = buf.readUInt16BE(i)\n // SOFn (baseline=C0, progressive=C2, etc) — exclude DHT(C4), JPG(C8), DAC(CC)\n if (\n marker >= 0xc0 &&\n marker <= 0xcf &&\n marker !== 0xc4 &&\n marker !== 0xc8 &&\n marker !== 0xcc\n ) {\n if (i + 7 >= buf.length) return undefined\n const height = buf.readUInt16BE(i + 3)\n const width = buf.readUInt16BE(i + 5)\n return { width, height }\n }\n i += segLen\n }\n return undefined\n }\n // GIF: bytes 6-9 (LE width/height)\n if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x38) {\n return { width: buf.readUInt16LE(6), height: buf.readUInt16LE(8) }\n }\n // BMP: width@18 (LE), height@22 (LE)\n if (buf[0] === 0x42 && buf[1] === 0x4d) {\n return { width: buf.readUInt32LE(18), height: Math.abs(buf.readInt32LE(22)) }\n }\n // WebP (VP8/VP8L/VP8X) — 30+ bytes for dimension info\n if (\n buf[0] === 0x52 &&\n buf[1] === 0x49 &&\n buf[2] === 0x46 &&\n buf[3] === 0x46 &&\n buf[8] === 0x57 &&\n buf[9] === 0x45 &&\n buf[10] === 0x42 &&\n buf[11] === 0x50 &&\n buf.length >= 30\n ) {\n // VP8 lossy: chunk header at offset 12, dims at offset 26 (14-bit each)\n if (buf[12] === 0x56 && buf[13] === 0x50 && buf[14] === 0x38 && buf[15] === 0x20) {\n const width = buf.readUInt16LE(26) & 0x3fff\n const height = buf.readUInt16LE(28) & 0x3fff\n return { width, height }\n }\n // VP8L lossless: dims at offset 21 (14-bit packed)\n if (buf[12] === 0x56 && buf[13] === 0x50 && buf[14] === 0x38 && buf[15] === 0x4c) {\n const b0 = buf[21]\n const b1 = buf[22]\n const b2 = buf[23]\n const b3 = buf[24]\n const width = 1 + (((b1 & 0x3f) << 8) | b0)\n const height = 1 + (((b3 & 0x0f) << 10) | (b2 << 2) | ((b1 & 0xc0) >> 6))\n return { width, height }\n }\n }\n return undefined\n}\n\n/**\n * Perspective view 의 entity 들을 격자로 균등 배치한다.\n *\n * VLM 은 perspective 좌표 → floor plan 좌표 변환이 불가능에 가깝다 (학습 데이터 부재 +\n * 명시적 3D 세계 모델 없음). screen 좌표를 그대로 깔면 \"perspective 가 평평해진\" 결과.\n * 차라리 좌표를 무시하고 격자로 깔고 사용자가 직접 옮기는 게 정직하다.\n *\n * 정책:\n * - 각 entity 의 size 는 보존 (VLM 이 추정한 상대 크기는 비교적 신뢰 가능)\n * - 격자 col 수 = ceil(sqrt(N)), row 수 = ceil(N/cols)\n * - tile 간격 = max(entity 평균 width/height) * 1.4\n * - 보드 우상단부터 row-major 로 배치\n */\nfunction layoutInGrid(entities: UniversalEntity[]): UniversalEntity[] {\n if (entities.length === 0) return entities\n const cols = Math.max(1, Math.ceil(Math.sqrt(entities.length)))\n\n // tile 크기 결정 — entity 평균 size 의 1.4 배. size 미지정이면 80 plan-unit.\n const sizes = entities.filter(e => e.size).map(e => e.size!)\n const avgW = sizes.length ? sizes.reduce((s, v) => s + v.width, 0) / sizes.length : 80\n const avgH = sizes.length ? sizes.reduce((s, v) => s + v.height, 0) / sizes.length : 80\n const tileW = Math.max(80, avgW) * 1.4\n const tileH = Math.max(80, avgH) * 1.4\n\n return entities.map((e, i) => {\n const col = i % cols\n const row = Math.floor(i / cols)\n const x = col * tileW\n const y = row * tileH\n const w = e.size?.width ?? 80\n const h = e.size?.height ?? 80\n return {\n ...e,\n position: { x, y },\n size: { width: w, height: h },\n bbox: { min: { x, y }, max: { x: x + w, y: y + h } }\n }\n })\n}\n\n/**\n * 모든 entity 의 좌표/크기가 [0..2] 범위에 들어가면 normalized 로 간주 (LLM 의 흔한 응답 패턴),\n * image dimension 으로 곱해 픽셀 좌표로 보정. dim 이 없으면 그대로 반환.\n */\nfunction denormalizeIfFractional(\n entities: UniversalEntity[],\n dims: { width: number; height: number } | undefined\n): UniversalEntity[] {\n if (!dims || entities.length === 0) return entities\n const isAllFractional = entities.every(e => {\n const xs = [e.position.x, e.bbox.min.x, e.bbox.max.x, (e.size?.width ?? 0)]\n const ys = [e.position.y, e.bbox.min.y, e.bbox.max.y, (e.size?.height ?? 0)]\n return [...xs, ...ys].every(v => Number.isFinite(v) && v >= 0 && v <= 2)\n })\n if (!isAllFractional) return entities\n const sx = dims.width\n const sy = dims.height\n return entities.map(e => ({\n ...e,\n position: { x: e.position.x * sx, y: e.position.y * sy },\n size: e.size ? { width: e.size.width * sx, height: e.size.height * sy } : undefined,\n bbox: {\n min: { x: e.bbox.min.x * sx, y: e.bbox.min.y * sy },\n max: { x: e.bbox.max.x * sx, y: e.bbox.max.y * sy }\n }\n }))\n}\n\n// ── 헬퍼 ─────────────────────────────────────────────────────────────\n\n/**\n * 매직 바이트로 이미지 형식 감지. filename 이 없거나 확장자가 모호한 경우 fallback.\n * 외부 reference 가 placeholder 로 응답되는 시나리오 (예: GLB top-view fetch 실패) 도\n * 같은 binary 검사로 식별 가능.\n */\nfunction detectImageMagic(file: AdapterInput): boolean {\n const buf = toBuffer(file)\n if (!buf || buf.length < 8) return false\n\n // PNG: 89 50 4E 47 0D 0A 1A 0A\n if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return true\n // JPEG: FF D8 FF\n if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return true\n // GIF: 47 49 46 38\n if (buf[0] === 0x47 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x38) return true\n // BMP: 42 4D\n if (buf[0] === 0x42 && buf[1] === 0x4d) return true\n // WebP: RIFF....WEBP\n if (\n buf[0] === 0x52 &&\n buf[1] === 0x49 &&\n buf[2] === 0x46 &&\n buf[3] === 0x46 &&\n buf.length >= 12 &&\n buf[8] === 0x57 &&\n buf[9] === 0x45 &&\n buf[10] === 0x42 &&\n buf[11] === 0x50\n ) {\n return true\n }\n return false\n}\n\n/**\n * VLM 응답 entity 의 결손 필드 복구. uid 누락 → 인덱스 기반, bbox 누락 → position+size 로 합성,\n * size/position 비숫자 값 → 0 fallback. 후속 stage2 가 안전하게 진행되도록 보호.\n */\nfunction normalizeEntity(e: any, index: number): UniversalEntity {\n const uid = typeof e?.uid === 'string' && e.uid ? e.uid : `img-${index}`\n const kind = e?.kind ?? 'shape'\n const position =\n e?.position && Number.isFinite(e.position.x) && Number.isFinite(e.position.y)\n ? { x: e.position.x, y: e.position.y }\n : { x: 0, y: 0 }\n const size =\n e?.size && Number.isFinite(e.size.width) && Number.isFinite(e.size.height)\n ? { width: e.size.width, height: e.size.height }\n : undefined\n\n let bbox: BoundingBox\n if (\n e?.bbox?.min &&\n e?.bbox?.max &&\n Number.isFinite(e.bbox.min.x) &&\n Number.isFinite(e.bbox.min.y) &&\n Number.isFinite(e.bbox.max.x) &&\n Number.isFinite(e.bbox.max.y)\n ) {\n bbox = { min: { x: e.bbox.min.x, y: e.bbox.min.y }, max: { x: e.bbox.max.x, y: e.bbox.max.y } }\n } else {\n const w = size?.width ?? 0\n const h = size?.height ?? 0\n bbox = {\n min: { x: position.x, y: position.y },\n max: { x: position.x + w, y: position.y + h }\n }\n }\n\n // bbox 가 position+size 와 일관되지 않으면 (VLM 이 lossy 한 bbox 를 줌) 일관성 보장.\n // size 가 있을 때 bbox 는 항상 position..(position+size) 를 cover 해야 stage3-board 의\n // 보드 크기 산정 / flipY 계산이 올바르다. 안 그러면 컴포넌트가 root 외곽으로 spill.\n if (size) {\n if (bbox.min.x > position.x) bbox.min.x = position.x\n if (bbox.min.y > position.y) bbox.min.y = position.y\n if (bbox.max.x < position.x + size.width) bbox.max.x = position.x + size.width\n if (bbox.max.y < position.y + size.height) bbox.max.y = position.y + size.height\n }\n\n return {\n uid,\n kind,\n position,\n size,\n rotation: typeof e?.rotation === 'number' ? e.rotation : undefined,\n bbox,\n attribs: e?.attribs && typeof e.attribs === 'object' ? { ...e.attribs } : {}\n }\n}\n\n/**\n * 모든 entity 의 bbox 를 합쳐 외부 bbox 산출. 빈 입력 / 무효값은 (0,0)-(0,0) fallback.\n */\nfunction computeOuterBbox(entities: UniversalEntity[]): BoundingBox {\n if (entities.length === 0) return { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const e of entities) {\n if (Number.isFinite(e.bbox.min.x) && e.bbox.min.x < minX) minX = e.bbox.min.x\n if (Number.isFinite(e.bbox.min.y) && e.bbox.min.y < minY) minY = e.bbox.min.y\n if (Number.isFinite(e.bbox.max.x) && e.bbox.max.x > maxX) maxX = e.bbox.max.x\n if (Number.isFinite(e.bbox.max.y) && e.bbox.max.y > maxY) maxY = e.bbox.max.y\n }\n if (!Number.isFinite(minX)) {\n return { min: { x: 0, y: 0 }, max: { x: 0, y: 0 } }\n }\n return { min: { x: minX, y: minY }, max: { x: maxX, y: maxY } }\n}\n\nfunction toBuffer(file: AdapterInput): Buffer | undefined {\n if (Buffer.isBuffer(file)) return file\n if (file instanceof Uint8Array) return Buffer.from(file)\n if (file instanceof ArrayBuffer) return Buffer.from(new Uint8Array(file))\n if (typeof file === 'string') {\n // 이미지 파싱은 string 입력을 의미 있게 처리할 방법이 없음 (base64 가정은 위험).\n // 명시적으로 binary 만 수용 — string 은 undefined 로 떨어져 parse 단계에서 명확한 에러.\n return undefined\n }\n return undefined\n}\n"]}
@@ -7,10 +7,10 @@
7
7
  *
8
8
  * 사용 예:
9
9
  * ```ts
10
- * import { createAIClient } from '@things-factory/ai-client-base'
10
+ * import { aiClientFor } from '@things-factory/ai-client-base'
11
11
  * import { setAIClient, DefaultBoardImportAI } from '@things-factory/board-import'
12
12
  *
13
- * const base = createAIClient({ provider: 'anthropic', apiKey: '...' })
13
+ * const base = await aiClientFor({ domain, user }) // the domain's AI agent
14
14
  * setAIClient(new DefaultBoardImportAI(base))
15
15
  * ```
16
16
  *
@@ -172,14 +172,20 @@ export declare class DefaultBoardImportAI implements BoardImportAI {
172
172
  * 결손 / 비정상 viewType 은 'top-down' fallback. forcedViewType 이 주어지면 그 값 강제.
173
173
  */
174
174
  export declare function normalizeImageAnalysisResponse(rawText: string, forcedViewType?: 'top-down' | 'perspective-3d' | 'photo'): ImageAnalysis;
175
+ /** A BoardImportAI that answers every import, whatever the domain - for tests and for a host that brings its own. */
175
176
  export declare function setAIClient(client: BoardImportAI | undefined): void;
177
+ export declare function getAIClient(): BoardImportAI | undefined;
176
178
  /**
177
- * 등록된 BoardImportAI 가 있으면 그걸 반환.
178
- * 없으면 ai-client-base 의 default AIClient (config.aiClient) 를 `DefaultBoardImportAI` 로
179
- * 감싸서 반환 — board-ai 와 동일한 1회 설정만으로 image import 도 동작하게 한다.
180
- *
181
- * default base 가 바뀌면 (테스트 등) 캐시를 무효화하고 다시 감싼다.
179
+ * The BoardImportAI of a domain's AI agent (design/ai/agent-settings.md). An import runs in the background, after the
180
+ * request is gone, so the job asks with the domain and the person it stored.
182
181
  */
183
- export declare function getAIClient(): BoardImportAI | undefined;
182
+ export declare function boardImportAIFor(request: {
183
+ domain: {
184
+ id: string;
185
+ };
186
+ user?: {
187
+ id: string;
188
+ } | null;
189
+ }): Promise<BoardImportAI>;
184
190
  /** Deprecated alias — 기존 코드 호환용. 새로 작성 시 BoardImportAI 사용. */
185
191
  export type AIClient = BoardImportAI;
@@ -4,6 +4,7 @@ exports.DefaultBoardImportAI = void 0;
4
4
  exports.normalizeImageAnalysisResponse = normalizeImageAnalysisResponse;
5
5
  exports.setAIClient = setAIClient;
6
6
  exports.getAIClient = getAIClient;
7
+ exports.boardImportAIFor = boardImportAIFor;
7
8
  /**
8
9
  * BoardImportAI — board-import 의 **도메인 AI 인터페이스**.
9
10
  *
@@ -13,10 +14,10 @@ exports.getAIClient = getAIClient;
13
14
  *
14
15
  * 사용 예:
15
16
  * ```ts
16
- * import { createAIClient } from '@things-factory/ai-client-base'
17
+ * import { aiClientFor } from '@things-factory/ai-client-base'
17
18
  * import { setAIClient, DefaultBoardImportAI } from '@things-factory/board-import'
18
19
  *
19
- * const base = createAIClient({ provider: 'anthropic', apiKey: '...' })
20
+ * const base = await aiClientFor({ domain, user }) // the domain's AI agent
20
21
  * setAIClient(new DefaultBoardImportAI(base))
21
22
  * ```
22
23
  *
@@ -351,28 +352,18 @@ function hydrateRuleRegexes(rule) {
351
352
  }
352
353
  // ── Provider 등록 ──────────────────────────────────────────────────
353
354
  let currentClient;
354
- let defaultBackedClient;
355
- let defaultBackedFor;
355
+ /** A BoardImportAI that answers every import, whatever the domain - for tests and for a host that brings its own. */
356
356
  function setAIClient(client) {
357
357
  currentClient = client;
358
358
  }
359
+ function getAIClient() {
360
+ return currentClient;
361
+ }
359
362
  /**
360
- * 등록된 BoardImportAI 가 있으면 그걸 반환.
361
- * 없으면 ai-client-base 의 default AIClient (config.aiClient) 를 `DefaultBoardImportAI` 로
362
- * 감싸서 반환 — board-ai 와 동일한 1회 설정만으로 image import 도 동작하게 한다.
363
- *
364
- * default base 가 바뀌면 (테스트 등) 캐시를 무효화하고 다시 감싼다.
363
+ * The BoardImportAI of a domain's AI agent (design/ai/agent-settings.md). An import runs in the background, after the
364
+ * request is gone, so the job asks with the domain and the person it stored.
365
365
  */
366
- function getAIClient() {
367
- if (currentClient)
368
- return currentClient;
369
- const base = (0, ai_client_base_1.getDefaultAIClient)();
370
- if (!base)
371
- return undefined;
372
- if (defaultBackedFor !== base) {
373
- defaultBackedFor = base;
374
- defaultBackedClient = new DefaultBoardImportAI(base);
375
- }
376
- return defaultBackedClient;
366
+ async function boardImportAIFor(request) {
367
+ return currentClient ?? new DefaultBoardImportAI(await (0, ai_client_base_1.aiClientFor)(request));
377
368
  }
378
369
  //# sourceMappingURL=types.js.map
@@ -1 +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 {\n getDefaultAIClient,\n parseJSONResponse,\n type AIClient as BaseAIClient\n} from '@things-factory/ai-client-base'\nimport type { ComponentCategory, ImportRule } from '../types/registry'\nimport type { UniversalEntity } from '../types/index'\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 *\n * 1-pass structured response 를 권장 — view type 자동 분류 + entity 추출을 단일 vision\n * 호출로 처리해 토큰 비용을 절반으로 줄인다. 응답 형태:\n * - **`ImageAnalysis`** 객체: `{ viewType, confidence, entities, reasoning? }`\n * - 또는 **`UniversalEntity[]`** 직접 (구버전 호환).\n * ImageAdapter 는 양쪽을 모두 정규화 처리.\n *\n * Phase 3 (이미지 어댑터) 에서 사용.\n */\n parseFromImage?(\n image: Buffer | Uint8Array,\n options?: ImageParseOptions\n ): Promise<ImageAnalysis | 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 * 사용자 자유서술 hint — 도면 설명 / 강조 / 카테고리 가이드 등.\n * 1-pass VLM 프롬프트에 합류해 view type 분류 + entity 추출 양쪽에 영향.\n */\n userPrompt?: string\n /** 이미지 dimension — 픽셀 좌표계 명시용 (top-down 모드에서). */\n imageWidth?: number\n imageHeight?: number\n /**\n * View type 강제. 미지정 시 VLM 이 자동 분류.\n * - 'top-down': 픽셀 좌표 그대로\n * - 'perspective-3d' / 'photo': floor plan 좌표 추정 (0..1000 단위)\n */\n viewType?: 'top-down' | 'perspective-3d' | 'photo'\n}\n\n/**\n * 1-pass VLM 응답의 구조화된 결과 — **다중 시안 (variants)** 포함.\n *\n * 같은 도면을 AI 가 3 가지 다른 관점으로 해석. 사용자가 review 에서 선택.\n *\n * 시안 축 (프롬프트가 제시) 예시:\n * - 장비(equipment) 중심 / 운송(transport) 중심 / 영역(region) 중심\n * - 보수적(conservative) / 균형(balanced) / 공격적(aggressive)\n *\n * 각 시안은 독립적인 entities + subjectName/subjectDescription + importStrategy.\n * viewType / confidence / reasoning 은 시안 무관 공통 (도면 자체의 특성).\n */\nexport interface ImageAnalysis {\n /** 자동 분류된 view type — 시안 무관 공통. */\n viewType: 'top-down' | 'perspective-3d' | 'photo'\n /** 분류 신뢰도 (0..1). LLM 자기보고. */\n confidence: number\n /** 분류 근거 한 줄 (왜 이 viewType 인가). */\n reasoning?: string\n /** 시안 풀세트. 보통 3 개 — variants[0] 가 default. */\n variants: ImageAnalysisVariant[]\n}\n\n/**\n * 한 시안의 풀데이터 — entities + 정체성 + 의도.\n * 각 시안은 같은 도면이지만 다른 해석.\n */\nexport interface ImageAnalysisVariant {\n /** URL 친화적 short id. */\n id: string\n /** 사용자에게 보여줄 한글 라벨. 예: \"장비 중심\" */\n label: string\n /** 짧은 한 줄 설명 — 이 시안의 강조점. */\n description?: string\n /** 추출된 entity 목록. 좌표 의미는 ImageAnalysis.viewType 에 따름. */\n entities: UniversalEntity[]\n /**\n * 보드 정체성 — 도면이 무엇을 나타내는가의 짧은 제목 (≤30자). 보드 name 자동 추천 기반.\n * 처리 과정이 아니라 **컨텐츠 정체성** 만.\n */\n subjectName?: string\n /**\n * 보드 정체성 — 도면이 나타내는 시설/배치/구성 1-2 문장. 보드 description 자동 추천 기반.\n * 처리 과정 X (importStrategy 와 분리).\n */\n subjectDescription?: string\n /**\n * AI 의 의도 narrative — \"왜 이 시안에서 이렇게 해석했나\". 3-5 문장.\n * **transient — wizard review 카드 전시용. 영속 description 에는 사용 X.**\n */\n importStrategy?: string\n}\n\n// ── 기본 구현 — base AIClient 위에 LLM 프롬프트로 ────────────────────\n\ninterface SingleVariantResult {\n viewType: 'top-down' | 'perspective-3d' | 'photo'\n confidence: number\n reasoning?: string\n variant: ImageAnalysisVariant\n}\n\nexport class DefaultBoardImportAI implements BoardImportAI {\n readonly id: string\n\n private static readonly VARIANT_AXES = [\n {\n id: 'equipment-focus',\n label: '장비 중심',\n instruction: '장비/설비(machine/stocker/facility) 식별 우선. 이동체/경로는 보조.'\n },\n {\n id: 'transport-focus',\n label: '운송 중심',\n instruction: '차량/경로(AGV/forklift/conveyor) 식별 우선. 정적 장비는 영역으로 묶음.'\n },\n {\n id: 'region-focus',\n label: '구역 중심',\n instruction: '영역/구획(zone/wall/structure) 식별 우선. 개별 장비는 generic placeholder.'\n }\n ] as const\n\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<ImageAnalysis> {\n if (!this.base.vision) {\n throw new Error(`AI client (${this.base.id}) does not support vision input`)\n }\n\n const results = await Promise.allSettled(\n DefaultBoardImportAI.VARIANT_AXES.map(axis => this._callOneVariant(image, axis, options))\n )\n\n const succeeded = results\n .filter((r): r is PromiseFulfilledResult<SingleVariantResult> => r.status === 'fulfilled')\n .map(r => r.value)\n\n if (succeeded.length === 0) {\n const errors = (results as PromiseRejectedResult[]).map(r => r.reason?.message ?? String(r.reason))\n throw new Error(`All variant calls failed: ${errors.join('; ')}`)\n }\n\n const forcedVT = options.viewType\n const viewType = forcedVT ?? consensusViewType(succeeded.map(r => r.viewType))\n const confidence = succeeded.reduce((s, r) => s + r.confidence, 0) / succeeded.length\n const reasoning = succeeded.find(r => r.reasoning)?.reasoning\n\n return { viewType, confidence, reasoning, variants: succeeded.map(r => r.variant) }\n }\n\n private async _callOneVariant(\n image: Buffer | Uint8Array,\n axis: { id: string; label: string; instruction: string },\n options: ImageParseOptions\n ): Promise<SingleVariantResult> {\n const w = options.imageWidth\n const h = options.imageHeight\n const userHint = options.userPrompt?.trim()\n const forcedViewType = options.viewType\n\n const dimsLine =\n Number.isFinite(w) && Number.isFinite(h)\n ? `\\nImage dimensions: ${w} x ${h} pixels.`\n : ''\n\n const viewTypeInstruction = forcedViewType\n ? `\\nThe view type is GIVEN: \"${forcedViewType}\". Do not classify; use it as-is.`\n : `\\nFirst, classify the view type:\n - \"top-down\": orthographic floor plan / 2D engineering drawing seen from directly above\n - \"perspective-3d\": 3D rendering / isometric / bird's-eye view with visible depth\n - \"photo\": photograph of a physical space`\n\n const coordRule = `\\nCoordinate convention by viewType:\n - \"top-down\": INTEGER PIXEL coordinates (origin top-left, x →, y ↓)${\n Number.isFinite(w) && Number.isFinite(h) ? ` in [0, ${w}] x [0, ${h}]` : ''\n }. Each entity ≥ 16x16 pixels.\n - \"perspective-3d\" / \"photo\": INFER the FLOOR PLAN layout (where each object would be on a flat floor seen from above, NOT screen positions). Use arbitrary plan units in [0, 1000] x [0, 1000]. Object size in plan units (e.g. 50x80 for a stocker).\nNEVER use normalized 0..1 coordinates.`\n\n const prompt = `Analyze this drawing image and provide ONE interpretation with focus axis: \"${axis.id}\".\nFocus: ${axis.instruction}${\n userHint ? '\\n\\nUser hints (highest priority): ' + userHint : ''\n }${options.context ? '\\nContext: ' + options.context : ''}${\n options.categories ? '\\nPossible categories: ' + options.categories.join(', ') : ''\n }${dimsLine}${viewTypeInstruction}${coordRule}\n\nReturn STRICT JSON (no markdown, no commentary) with this shape:\n{\n \"viewType\": \"top-down\" | \"perspective-3d\" | \"photo\",\n \"confidence\": 0..1,\n \"reasoning\": \"brief one-sentence explanation of view type classification\",\n \"variant\": {\n \"id\": \"${axis.id}\",\n \"label\": \"한글 라벨 (예: ${axis.label})\",\n \"description\": \"한 줄 — 이 시안의 강조점\",\n \"subjectName\": \"≤30 char short title — what the depicted facility is\",\n \"subjectDescription\": \"1-2 sentences in Korean — what is depicted (NOT how you analyzed)\",\n \"importStrategy\": \"3-5 sentence narrative explaining YOUR INTENT for this variant\",\n \"entities\": [\n {\n \"uid\": \"string\",\n \"kind\": \"shape\" | \"path\" | \"text\",\n \"position\": { \"x\": number, \"y\": number },\n \"size\": { \"width\": number, \"height\": number },\n \"bbox\": { \"min\": {\"x\": number, \"y\": number}, \"max\": {\"x\": number, \"y\": number} },\n \"attribs\": {\n \"name\": \"string?\",\n \"semanticType\": \"string?\",\n \"text\": \"string?\",\n \"layer\": \"string?\",\n \"categoryHint\": \"container | vehicle | path | region | equipment | io | marker | structure\"\n }\n }\n ]\n }\n}\n\ncategoryHint should be one of the listed categories; omit only if highly uncertain.\nProvide 10-30 distinct entities clearly identifiable under the \"${axis.id}\" axis.\n\nattribs.name — IMPORTANT for component identity / data binding:\n - If you see a TEXT LABEL on/near the entity in the drawing (e.g. \"STK_001\", \"AGV-3\", \"CV1\"), use that EXACTLY as the name.\n - DO NOT invent labels not in the drawing — leave attribs.name empty if uncertain.\n - Keep alphanumeric + hyphens / underscores. No spaces. ≤24 chars. UNIQUE within the variant.\n\nsubjectName + subjectDescription — describe WHAT the board depicts (its identity), NOT what you did.\n These become the board's persistent name + description if the user picks this variant.\n\nimportStrategy — Korean 3-5 sentences: what this variant emphasizes, how you decided categories, areas of uncertainty.\nUsed in the wizard review card only; transient — separate from board description.`\n\n const raw = await this.base.vision!(image as Buffer, prompt, { maxTokens: 4096 })\n return parseSingleVariantResponse(raw, axis.id, axis.label, forcedViewType)\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\nconst VIEW_TYPES = ['top-down', 'perspective-3d', 'photo'] as const\n\nfunction parseSingleVariantResponse(\n rawText: string,\n axisId: string,\n axisLabel: string,\n forcedViewType?: 'top-down' | 'perspective-3d' | 'photo'\n): SingleVariantResult {\n const parsed = parseJSONResponse<any>(rawText)\n const trimmed = (s: any) => (typeof s === 'string' && s.trim() ? s.trim() : undefined)\n\n const rawVT = parsed?.viewType\n const viewType: 'top-down' | 'perspective-3d' | 'photo' =\n forcedViewType ??\n ((VIEW_TYPES as readonly string[]).includes(rawVT)\n ? (rawVT as 'top-down' | 'perspective-3d' | 'photo')\n : 'top-down')\n const confidence =\n typeof parsed?.confidence === 'number' && parsed.confidence >= 0 && parsed.confidence <= 1\n ? parsed.confidence\n : 0.5\n\n const v = parsed?.variant ?? {}\n const variant: ImageAnalysisVariant = {\n id: typeof v.id === 'string' && v.id ? v.id : axisId,\n label: typeof v.label === 'string' && v.label ? v.label : axisLabel,\n description: trimmed(v.description),\n entities: Array.isArray(v.entities) ? v.entities : [],\n subjectName: trimmed(v.subjectName),\n subjectDescription: trimmed(v.subjectDescription),\n importStrategy: trimmed(v.importStrategy)\n }\n\n return { viewType, confidence, reasoning: trimmed(parsed?.reasoning), variant }\n}\n\nfunction consensusViewType(\n viewTypes: Array<'top-down' | 'perspective-3d' | 'photo'>\n): 'top-down' | 'perspective-3d' | 'photo' {\n const counts = new Map<string, number>()\n for (const vt of viewTypes) counts.set(vt, (counts.get(vt) ?? 0) + 1)\n let best: 'top-down' | 'perspective-3d' | 'photo' = 'top-down'\n let max = 0\n for (const [vt, count] of counts) {\n if (count > max) {\n max = count\n best = vt as 'top-down' | 'perspective-3d' | 'photo'\n }\n }\n return best\n}\n\n/**\n * VLM 의 JSON 응답을 ImageAnalysis 형태로 정규화 — 다중 시안 (variants) 지원.\n *\n * 허용 입력 형태:\n * - `{ viewType, confidence, reasoning?, variants: [...] }` — **최신 권장** (multi-variant)\n * - `{ viewType, ..., entities }` — 구버전 single-variant (자동으로 variants:[default] 으로 감쌈)\n * - `[ entity, ... ]` — 가장 구버전 (entities 만)\n *\n * 결손 / 비정상 viewType 은 'top-down' fallback. forcedViewType 이 주어지면 그 값 강제.\n */\nexport function normalizeImageAnalysisResponse(\n rawText: string,\n forcedViewType?: 'top-down' | 'perspective-3d' | 'photo'\n): ImageAnalysis {\n const parsed = parseJSONResponse<any>(rawText)\n const trimmed = (s: any) => (typeof s === 'string' && s.trim() ? s.trim() : undefined)\n\n if (Array.isArray(parsed)) {\n return {\n viewType: forcedViewType ?? 'top-down',\n confidence: 0.5,\n variants: [{ id: 'default', label: 'default', entities: parsed }]\n }\n }\n if (!parsed || typeof parsed !== 'object') {\n return {\n viewType: forcedViewType ?? 'top-down',\n confidence: 0,\n variants: [{ id: 'default', label: 'default', entities: [] }]\n }\n }\n const rawVT = parsed.viewType\n const viewType =\n forcedViewType ??\n ((VIEW_TYPES as readonly string[]).includes(rawVT) ? rawVT : 'top-down')\n const confidence =\n typeof parsed.confidence === 'number' && parsed.confidence >= 0 && parsed.confidence <= 1\n ? parsed.confidence\n : 0.5\n\n // variants 배열 정규화 — 최신 권장 형태\n let variants: ImageAnalysisVariant[]\n if (Array.isArray(parsed.variants) && parsed.variants.length > 0) {\n variants = parsed.variants.map((v: any, i: number) => ({\n id: typeof v?.id === 'string' && v.id ? v.id : `variant-${i + 1}`,\n label: typeof v?.label === 'string' && v.label ? v.label : `Variant ${i + 1}`,\n description: trimmed(v?.description),\n entities: Array.isArray(v?.entities) ? v.entities : [],\n subjectName: trimmed(v?.subjectName),\n subjectDescription: trimmed(v?.subjectDescription),\n importStrategy: trimmed(v?.importStrategy)\n }))\n } else {\n // 구버전 호환 — top-level entities + subjectName 등을 단일 variant 로 감쌈\n variants = [\n {\n id: 'default',\n label: 'default',\n entities: Array.isArray(parsed.entities) ? parsed.entities : [],\n subjectName: trimmed(parsed.subjectName),\n subjectDescription: trimmed(parsed.subjectDescription),\n importStrategy: trimmed(parsed.importStrategy)\n }\n ]\n }\n\n return {\n viewType,\n confidence,\n reasoning: trimmed(parsed.reasoning),\n variants\n }\n}\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\nlet defaultBackedClient: BoardImportAI | undefined\nlet defaultBackedFor: BaseAIClient | undefined\n\nexport function setAIClient(client: BoardImportAI | undefined): void {\n currentClient = client\n}\n\n/**\n * 등록된 BoardImportAI 가 있으면 그걸 반환.\n * 없으면 ai-client-base 의 default AIClient (config.aiClient) 를 `DefaultBoardImportAI` 로\n * 감싸서 반환 — board-ai 와 동일한 1회 설정만으로 image import 도 동작하게 한다.\n *\n * default base 가 바뀌면 (테스트 등) 캐시를 무효화하고 다시 감싼다.\n */\nexport function getAIClient(): BoardImportAI | undefined {\n if (currentClient) return currentClient\n const base = getDefaultAIClient()\n if (!base) return undefined\n if (defaultBackedFor !== base) {\n defaultBackedFor = base\n defaultBackedClient = new DefaultBoardImportAI(base)\n }\n return defaultBackedClient\n}\n\n/** Deprecated alias — 기존 코드 호환용. 새로 작성 시 BoardImportAI 사용. */\nexport type AIClient = BoardImportAI\n"]}
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 { aiClientFor } from '@things-factory/ai-client-base'\n * import { setAIClient, DefaultBoardImportAI } from '@things-factory/board-import'\n *\n * const base = await aiClientFor({ domain, user }) // the domain's AI agent\n * setAIClient(new DefaultBoardImportAI(base))\n * ```\n *\n * Phase 1-2 결정적 파이프라인은 AI 미사용. 위 등록은 Phase 3+ 메서드 활성화용.\n */\nimport {\n aiClientFor,\n parseJSONResponse,\n type AIClient as BaseAIClient\n} from '@things-factory/ai-client-base'\nimport type { ComponentCategory, ImportRule } from '../types/registry'\nimport type { UniversalEntity } from '../types/index'\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 *\n * 1-pass structured response 를 권장 — view type 자동 분류 + entity 추출을 단일 vision\n * 호출로 처리해 토큰 비용을 절반으로 줄인다. 응답 형태:\n * - **`ImageAnalysis`** 객체: `{ viewType, confidence, entities, reasoning? }`\n * - 또는 **`UniversalEntity[]`** 직접 (구버전 호환).\n * ImageAdapter 는 양쪽을 모두 정규화 처리.\n *\n * Phase 3 (이미지 어댑터) 에서 사용.\n */\n parseFromImage?(\n image: Buffer | Uint8Array,\n options?: ImageParseOptions\n ): Promise<ImageAnalysis | 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 * 사용자 자유서술 hint — 도면 설명 / 강조 / 카테고리 가이드 등.\n * 1-pass VLM 프롬프트에 합류해 view type 분류 + entity 추출 양쪽에 영향.\n */\n userPrompt?: string\n /** 이미지 dimension — 픽셀 좌표계 명시용 (top-down 모드에서). */\n imageWidth?: number\n imageHeight?: number\n /**\n * View type 강제. 미지정 시 VLM 이 자동 분류.\n * - 'top-down': 픽셀 좌표 그대로\n * - 'perspective-3d' / 'photo': floor plan 좌표 추정 (0..1000 단위)\n */\n viewType?: 'top-down' | 'perspective-3d' | 'photo'\n}\n\n/**\n * 1-pass VLM 응답의 구조화된 결과 — **다중 시안 (variants)** 포함.\n *\n * 같은 도면을 AI 가 3 가지 다른 관점으로 해석. 사용자가 review 에서 선택.\n *\n * 시안 축 (프롬프트가 제시) 예시:\n * - 장비(equipment) 중심 / 운송(transport) 중심 / 영역(region) 중심\n * - 보수적(conservative) / 균형(balanced) / 공격적(aggressive)\n *\n * 각 시안은 독립적인 entities + subjectName/subjectDescription + importStrategy.\n * viewType / confidence / reasoning 은 시안 무관 공통 (도면 자체의 특성).\n */\nexport interface ImageAnalysis {\n /** 자동 분류된 view type — 시안 무관 공통. */\n viewType: 'top-down' | 'perspective-3d' | 'photo'\n /** 분류 신뢰도 (0..1). LLM 자기보고. */\n confidence: number\n /** 분류 근거 한 줄 (왜 이 viewType 인가). */\n reasoning?: string\n /** 시안 풀세트. 보통 3 개 — variants[0] 가 default. */\n variants: ImageAnalysisVariant[]\n}\n\n/**\n * 한 시안의 풀데이터 — entities + 정체성 + 의도.\n * 각 시안은 같은 도면이지만 다른 해석.\n */\nexport interface ImageAnalysisVariant {\n /** URL 친화적 short id. */\n id: string\n /** 사용자에게 보여줄 한글 라벨. 예: \"장비 중심\" */\n label: string\n /** 짧은 한 줄 설명 — 이 시안의 강조점. */\n description?: string\n /** 추출된 entity 목록. 좌표 의미는 ImageAnalysis.viewType 에 따름. */\n entities: UniversalEntity[]\n /**\n * 보드 정체성 — 도면이 무엇을 나타내는가의 짧은 제목 (≤30자). 보드 name 자동 추천 기반.\n * 처리 과정이 아니라 **컨텐츠 정체성** 만.\n */\n subjectName?: string\n /**\n * 보드 정체성 — 도면이 나타내는 시설/배치/구성 1-2 문장. 보드 description 자동 추천 기반.\n * 처리 과정 X (importStrategy 와 분리).\n */\n subjectDescription?: string\n /**\n * AI 의 의도 narrative — \"왜 이 시안에서 이렇게 해석했나\". 3-5 문장.\n * **transient — wizard review 카드 전시용. 영속 description 에는 사용 X.**\n */\n importStrategy?: string\n}\n\n// ── 기본 구현 — base AIClient 위에 LLM 프롬프트로 ────────────────────\n\ninterface SingleVariantResult {\n viewType: 'top-down' | 'perspective-3d' | 'photo'\n confidence: number\n reasoning?: string\n variant: ImageAnalysisVariant\n}\n\nexport class DefaultBoardImportAI implements BoardImportAI {\n readonly id: string\n\n private static readonly VARIANT_AXES = [\n {\n id: 'equipment-focus',\n label: '장비 중심',\n instruction: '장비/설비(machine/stocker/facility) 식별 우선. 이동체/경로는 보조.'\n },\n {\n id: 'transport-focus',\n label: '운송 중심',\n instruction: '차량/경로(AGV/forklift/conveyor) 식별 우선. 정적 장비는 영역으로 묶음.'\n },\n {\n id: 'region-focus',\n label: '구역 중심',\n instruction: '영역/구획(zone/wall/structure) 식별 우선. 개별 장비는 generic placeholder.'\n }\n ] as const\n\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<ImageAnalysis> {\n if (!this.base.vision) {\n throw new Error(`AI client (${this.base.id}) does not support vision input`)\n }\n\n const results = await Promise.allSettled(\n DefaultBoardImportAI.VARIANT_AXES.map(axis => this._callOneVariant(image, axis, options))\n )\n\n const succeeded = results\n .filter((r): r is PromiseFulfilledResult<SingleVariantResult> => r.status === 'fulfilled')\n .map(r => r.value)\n\n if (succeeded.length === 0) {\n const errors = (results as PromiseRejectedResult[]).map(r => r.reason?.message ?? String(r.reason))\n throw new Error(`All variant calls failed: ${errors.join('; ')}`)\n }\n\n const forcedVT = options.viewType\n const viewType = forcedVT ?? consensusViewType(succeeded.map(r => r.viewType))\n const confidence = succeeded.reduce((s, r) => s + r.confidence, 0) / succeeded.length\n const reasoning = succeeded.find(r => r.reasoning)?.reasoning\n\n return { viewType, confidence, reasoning, variants: succeeded.map(r => r.variant) }\n }\n\n private async _callOneVariant(\n image: Buffer | Uint8Array,\n axis: { id: string; label: string; instruction: string },\n options: ImageParseOptions\n ): Promise<SingleVariantResult> {\n const w = options.imageWidth\n const h = options.imageHeight\n const userHint = options.userPrompt?.trim()\n const forcedViewType = options.viewType\n\n const dimsLine =\n Number.isFinite(w) && Number.isFinite(h)\n ? `\\nImage dimensions: ${w} x ${h} pixels.`\n : ''\n\n const viewTypeInstruction = forcedViewType\n ? `\\nThe view type is GIVEN: \"${forcedViewType}\". Do not classify; use it as-is.`\n : `\\nFirst, classify the view type:\n - \"top-down\": orthographic floor plan / 2D engineering drawing seen from directly above\n - \"perspective-3d\": 3D rendering / isometric / bird's-eye view with visible depth\n - \"photo\": photograph of a physical space`\n\n const coordRule = `\\nCoordinate convention by viewType:\n - \"top-down\": INTEGER PIXEL coordinates (origin top-left, x →, y ↓)${\n Number.isFinite(w) && Number.isFinite(h) ? ` in [0, ${w}] x [0, ${h}]` : ''\n }. Each entity ≥ 16x16 pixels.\n - \"perspective-3d\" / \"photo\": INFER the FLOOR PLAN layout (where each object would be on a flat floor seen from above, NOT screen positions). Use arbitrary plan units in [0, 1000] x [0, 1000]. Object size in plan units (e.g. 50x80 for a stocker).\nNEVER use normalized 0..1 coordinates.`\n\n const prompt = `Analyze this drawing image and provide ONE interpretation with focus axis: \"${axis.id}\".\nFocus: ${axis.instruction}${\n userHint ? '\\n\\nUser hints (highest priority): ' + userHint : ''\n }${options.context ? '\\nContext: ' + options.context : ''}${\n options.categories ? '\\nPossible categories: ' + options.categories.join(', ') : ''\n }${dimsLine}${viewTypeInstruction}${coordRule}\n\nReturn STRICT JSON (no markdown, no commentary) with this shape:\n{\n \"viewType\": \"top-down\" | \"perspective-3d\" | \"photo\",\n \"confidence\": 0..1,\n \"reasoning\": \"brief one-sentence explanation of view type classification\",\n \"variant\": {\n \"id\": \"${axis.id}\",\n \"label\": \"한글 라벨 (예: ${axis.label})\",\n \"description\": \"한 줄 — 이 시안의 강조점\",\n \"subjectName\": \"≤30 char short title — what the depicted facility is\",\n \"subjectDescription\": \"1-2 sentences in Korean — what is depicted (NOT how you analyzed)\",\n \"importStrategy\": \"3-5 sentence narrative explaining YOUR INTENT for this variant\",\n \"entities\": [\n {\n \"uid\": \"string\",\n \"kind\": \"shape\" | \"path\" | \"text\",\n \"position\": { \"x\": number, \"y\": number },\n \"size\": { \"width\": number, \"height\": number },\n \"bbox\": { \"min\": {\"x\": number, \"y\": number}, \"max\": {\"x\": number, \"y\": number} },\n \"attribs\": {\n \"name\": \"string?\",\n \"semanticType\": \"string?\",\n \"text\": \"string?\",\n \"layer\": \"string?\",\n \"categoryHint\": \"container | vehicle | path | region | equipment | io | marker | structure\"\n }\n }\n ]\n }\n}\n\ncategoryHint should be one of the listed categories; omit only if highly uncertain.\nProvide 10-30 distinct entities clearly identifiable under the \"${axis.id}\" axis.\n\nattribs.name — IMPORTANT for component identity / data binding:\n - If you see a TEXT LABEL on/near the entity in the drawing (e.g. \"STK_001\", \"AGV-3\", \"CV1\"), use that EXACTLY as the name.\n - DO NOT invent labels not in the drawing — leave attribs.name empty if uncertain.\n - Keep alphanumeric + hyphens / underscores. No spaces. ≤24 chars. UNIQUE within the variant.\n\nsubjectName + subjectDescription — describe WHAT the board depicts (its identity), NOT what you did.\n These become the board's persistent name + description if the user picks this variant.\n\nimportStrategy — Korean 3-5 sentences: what this variant emphasizes, how you decided categories, areas of uncertainty.\nUsed in the wizard review card only; transient — separate from board description.`\n\n const raw = await this.base.vision!(image as Buffer, prompt, { maxTokens: 4096 })\n return parseSingleVariantResponse(raw, axis.id, axis.label, forcedViewType)\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\nconst VIEW_TYPES = ['top-down', 'perspective-3d', 'photo'] as const\n\nfunction parseSingleVariantResponse(\n rawText: string,\n axisId: string,\n axisLabel: string,\n forcedViewType?: 'top-down' | 'perspective-3d' | 'photo'\n): SingleVariantResult {\n const parsed = parseJSONResponse<any>(rawText)\n const trimmed = (s: any) => (typeof s === 'string' && s.trim() ? s.trim() : undefined)\n\n const rawVT = parsed?.viewType\n const viewType: 'top-down' | 'perspective-3d' | 'photo' =\n forcedViewType ??\n ((VIEW_TYPES as readonly string[]).includes(rawVT)\n ? (rawVT as 'top-down' | 'perspective-3d' | 'photo')\n : 'top-down')\n const confidence =\n typeof parsed?.confidence === 'number' && parsed.confidence >= 0 && parsed.confidence <= 1\n ? parsed.confidence\n : 0.5\n\n const v = parsed?.variant ?? {}\n const variant: ImageAnalysisVariant = {\n id: typeof v.id === 'string' && v.id ? v.id : axisId,\n label: typeof v.label === 'string' && v.label ? v.label : axisLabel,\n description: trimmed(v.description),\n entities: Array.isArray(v.entities) ? v.entities : [],\n subjectName: trimmed(v.subjectName),\n subjectDescription: trimmed(v.subjectDescription),\n importStrategy: trimmed(v.importStrategy)\n }\n\n return { viewType, confidence, reasoning: trimmed(parsed?.reasoning), variant }\n}\n\nfunction consensusViewType(\n viewTypes: Array<'top-down' | 'perspective-3d' | 'photo'>\n): 'top-down' | 'perspective-3d' | 'photo' {\n const counts = new Map<string, number>()\n for (const vt of viewTypes) counts.set(vt, (counts.get(vt) ?? 0) + 1)\n let best: 'top-down' | 'perspective-3d' | 'photo' = 'top-down'\n let max = 0\n for (const [vt, count] of counts) {\n if (count > max) {\n max = count\n best = vt as 'top-down' | 'perspective-3d' | 'photo'\n }\n }\n return best\n}\n\n/**\n * VLM 의 JSON 응답을 ImageAnalysis 형태로 정규화 — 다중 시안 (variants) 지원.\n *\n * 허용 입력 형태:\n * - `{ viewType, confidence, reasoning?, variants: [...] }` — **최신 권장** (multi-variant)\n * - `{ viewType, ..., entities }` — 구버전 single-variant (자동으로 variants:[default] 으로 감쌈)\n * - `[ entity, ... ]` — 가장 구버전 (entities 만)\n *\n * 결손 / 비정상 viewType 은 'top-down' fallback. forcedViewType 이 주어지면 그 값 강제.\n */\nexport function normalizeImageAnalysisResponse(\n rawText: string,\n forcedViewType?: 'top-down' | 'perspective-3d' | 'photo'\n): ImageAnalysis {\n const parsed = parseJSONResponse<any>(rawText)\n const trimmed = (s: any) => (typeof s === 'string' && s.trim() ? s.trim() : undefined)\n\n if (Array.isArray(parsed)) {\n return {\n viewType: forcedViewType ?? 'top-down',\n confidence: 0.5,\n variants: [{ id: 'default', label: 'default', entities: parsed }]\n }\n }\n if (!parsed || typeof parsed !== 'object') {\n return {\n viewType: forcedViewType ?? 'top-down',\n confidence: 0,\n variants: [{ id: 'default', label: 'default', entities: [] }]\n }\n }\n const rawVT = parsed.viewType\n const viewType =\n forcedViewType ??\n ((VIEW_TYPES as readonly string[]).includes(rawVT) ? rawVT : 'top-down')\n const confidence =\n typeof parsed.confidence === 'number' && parsed.confidence >= 0 && parsed.confidence <= 1\n ? parsed.confidence\n : 0.5\n\n // variants 배열 정규화 — 최신 권장 형태\n let variants: ImageAnalysisVariant[]\n if (Array.isArray(parsed.variants) && parsed.variants.length > 0) {\n variants = parsed.variants.map((v: any, i: number) => ({\n id: typeof v?.id === 'string' && v.id ? v.id : `variant-${i + 1}`,\n label: typeof v?.label === 'string' && v.label ? v.label : `Variant ${i + 1}`,\n description: trimmed(v?.description),\n entities: Array.isArray(v?.entities) ? v.entities : [],\n subjectName: trimmed(v?.subjectName),\n subjectDescription: trimmed(v?.subjectDescription),\n importStrategy: trimmed(v?.importStrategy)\n }))\n } else {\n // 구버전 호환 — top-level entities + subjectName 등을 단일 variant 로 감쌈\n variants = [\n {\n id: 'default',\n label: 'default',\n entities: Array.isArray(parsed.entities) ? parsed.entities : [],\n subjectName: trimmed(parsed.subjectName),\n subjectDescription: trimmed(parsed.subjectDescription),\n importStrategy: trimmed(parsed.importStrategy)\n }\n ]\n }\n\n return {\n viewType,\n confidence,\n reasoning: trimmed(parsed.reasoning),\n variants\n }\n}\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\n/** A BoardImportAI that answers every import, whatever the domain - for tests and for a host that brings its own. */\nexport function setAIClient(client: BoardImportAI | undefined): void {\n currentClient = client\n}\n\nexport function getAIClient(): BoardImportAI | undefined {\n return currentClient\n}\n\n/**\n * The BoardImportAI of a domain's AI agent (design/ai/agent-settings.md). An import runs in the background, after the\n * request is gone, so the job asks with the domain and the person it stored.\n */\nexport async function boardImportAIFor(request: { domain: { id: string }; user?: { id: string } | null }): Promise<BoardImportAI> {\n return currentClient ?? new DefaultBoardImportAI(await aiClientFor(request as any))\n}\n\n/** Deprecated alias — 기존 코드 호환용. 새로 작성 시 BoardImportAI 사용. */\nexport type AIClient = BoardImportAI\n"]}
@@ -56,6 +56,8 @@ export interface ExtractionContext {
56
56
  units: number;
57
57
  unit?: string;
58
58
  };
59
+ /** The AI client of the domain the extraction runs for (aiClientFor). A recognizer that asks a model needs it. */
60
+ aiClient?: import('@things-factory/ai-client-base').AIClient;
59
61
  [k: string]: any;
60
62
  }
61
63
  export interface GeometryExtractor {
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../../../server/service/extraction/contracts.ts"],"names":[],"mappings":"","sourcesContent":["/*\n * 하이브리드 추출 contracts (Phase Board-AI §3.8) — 이미지/도면 → 레이아웃 추출을\n * *역할분담* 으로 분해하기 위한 provider-중립 인터페이스.\n *\n * 위치/기하 ← GeometryExtractor (벡터 파싱 > CV > VLM폴백)\n * 식별/라벨 ← LabelExtractor (OCR: GCP/Textract/Tesseract)\n * 의미/타입 ← Recognizer (VLM — region 인식 전담, 좌표 아님)\n * 스케일 ← ScaleCalibrator (치수/스케일바/알려진 크기)\n * 정리 ← LayoutRefiner (정렬/스냅/등간격/겹침해소)\n *\n * 구현체는 스캐폴딩 단계에선 stub. 본격 테스트 단계에서 소스 적합 impl 을 채운다.\n */\nimport type { UniversalEntity, Vec2, BoundingBox, AdapterInput, ParseResult } from '../types/index'\n\n/** 입력 소스 분류 — 추출 경로 선택 기준. */\nexport type SourceKind =\n | 'vector' // DXF/IFC/SVG/PDF-vector — 정밀 기하 직접 파싱\n | 'raster-topdown' // 평면도 비트맵 — CV + OCR + VLM 하이브리드\n | 'photo' // 사진/3D 렌더 — VLM 러프 폴백\n | 'unknown'\n\n/** 검출된 저수준 기하 프리미티브 (픽셀/소스 좌표계). */\nexport interface GeometricPrimitive {\n kind: 'line' | 'rect' | 'region' | 'point'\n bbox: BoundingBox\n /** line 일 때 양 끝점. */\n segment?: { a: Vec2; b: Vec2 }\n /** 검출 신뢰도 0..1 (있으면). */\n score?: number\n /**\n * 이미 완전한 엔티티를 아는 추출기(벡터: DXF/IFC/SVG)가 원 엔티티를 그대로 실어 보낸다.\n * 있으면 fuse 가 무손실로 사용(attribs/rotation/vertices 보존). CV 추출기는 비움 → fuse 가\n * bbox 로 bare 엔티티 생성 후 Recognizer 가 의미 부여.\n */\n entity?: UniversalEntity\n /** 추출기별 원시 메타. */\n meta?: Record<string, any>\n}\n\n/** OCR 라벨 — 텍스트 + 위치 (식별/앵커/스케일 단서). */\nexport interface Label {\n text: string\n bbox: BoundingBox\n score?: number\n}\n\n/** 한 region(프리미티브/crop)에 대한 의미 인식 결과. */\nexport interface RecognitionResult {\n /** 실제 보드 컴포넌트 type (카탈로그 그라운딩). 불명 시 undefined. */\n componentType?: string\n confidence: number\n attribs?: Record<string, any>\n}\n\n/** 스케일 캘리브레이션 결과 — 픽셀 → board 단위 변환. */\nexport interface ScaleResult {\n unitsPerPixel: number\n origin: Vec2\n units?: ParseResult['units']\n}\n\n/** 추출 컨텍스트 — 이미지 dims, 사용자 힌트, 카탈로그 등 (확장 여지). */\nexport interface ExtractionContext {\n imageWidth?: number\n imageHeight?: number\n userPrompt?: string\n /** 카탈로그 그라운딩용 컴포넌트 type 목록 (Recognizer 가 제약). */\n catalog?: string[]\n /**\n * 명시 스케일 기준 — 알려진 픽셀 span = 알려진 실제 길이 (예: UI 2점 캘리브레이션,\n * 스케일바). ScaleCalibrator 가 최우선 사용. unitsPerPixel = units / pixels.\n */\n scaleReference?: { pixels: number; units: number; unit?: string }\n [k: string]: any\n}\n\n// ── contracts ────────────────────────────────────────────────────────────────\n\nexport interface GeometryExtractor {\n readonly id: string\n extract(input: AdapterInput, ctx?: ExtractionContext): Promise<GeometricPrimitive[]>\n}\n\nexport interface LabelExtractor {\n readonly id: string\n extract(input: AdapterInput, ctx?: ExtractionContext): Promise<Label[]>\n}\n\nexport interface Recognizer {\n readonly id: string\n /** region(프리미티브 + ctx)이 어떤 컴포넌트인지 — VLM 인식 전담(좌표 아님). */\n recognize(region: GeometricPrimitive, ctx?: ExtractionContext): Promise<RecognitionResult>\n}\n\nexport interface ScaleCalibrator {\n readonly id: string\n calibrate(labels: Label[], primitives: GeometricPrimitive[], ctx?: ExtractionContext): Promise<ScaleResult>\n}\n\nexport interface LayoutRefiner {\n readonly id: string\n refine(entities: UniversalEntity[], ctx?: ExtractionContext): UniversalEntity[]\n}\n\n/** 한 시안(해석) — 공유 기하 위에 의미/그룹핑/전략만 달리한 결과. */\nexport interface VariantDraft {\n /** URL 친화 short id (예: 'equipment-focus'). */\n id: string\n /** 사용자 표시 라벨 (예: '장비 중심'). */\n label: string\n description?: string\n entities: UniversalEntity[]\n subjectName?: string\n subjectDescription?: string\n importStrategy?: string\n}\n\n/**\n * 시안 전략 — *정확한 공유 기하+라벨* 을 받아 N개 해석 시안을 만든다.\n * (UX: 사용자가 3안 중 선택하는 흐름 보존. 기하는 공유라 셋 다 레이아웃 정상, 해석만 다름.)\n * 미지정 시 파이프라인이 단일 default 시안 1개를 만든다.\n */\nexport interface VariantStrategy {\n readonly id: string\n interpret(\n primitives: GeometricPrimitive[],\n labels: Label[],\n set: ExtractorSet,\n ctx?: ExtractionContext\n ): Promise<VariantDraft[]>\n}\n\n/** 한 소스경로의 추출기 묶음 (geometry 필수, 나머지는 경로별 선택). */\nexport interface ExtractorSet {\n geometry: GeometryExtractor\n labels?: LabelExtractor\n recognizer?: Recognizer\n scale?: ScaleCalibrator\n refiner?: LayoutRefiner\n /** 다중 시안 생성 전략 (3안 선택 UX). 미지정 시 단일 default 시안. */\n variants?: VariantStrategy\n}\n"]}
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../../../server/service/extraction/contracts.ts"],"names":[],"mappings":"","sourcesContent":["/*\n * 하이브리드 추출 contracts (Phase Board-AI §3.8) — 이미지/도면 → 레이아웃 추출을\n * *역할분담* 으로 분해하기 위한 provider-중립 인터페이스.\n *\n * 위치/기하 ← GeometryExtractor (벡터 파싱 > CV > VLM폴백)\n * 식별/라벨 ← LabelExtractor (OCR: GCP/Textract/Tesseract)\n * 의미/타입 ← Recognizer (VLM — region 인식 전담, 좌표 아님)\n * 스케일 ← ScaleCalibrator (치수/스케일바/알려진 크기)\n * 정리 ← LayoutRefiner (정렬/스냅/등간격/겹침해소)\n *\n * 구현체는 스캐폴딩 단계에선 stub. 본격 테스트 단계에서 소스 적합 impl 을 채운다.\n */\nimport type { UniversalEntity, Vec2, BoundingBox, AdapterInput, ParseResult } from '../types/index'\n\n/** 입력 소스 분류 — 추출 경로 선택 기준. */\nexport type SourceKind =\n | 'vector' // DXF/IFC/SVG/PDF-vector — 정밀 기하 직접 파싱\n | 'raster-topdown' // 평면도 비트맵 — CV + OCR + VLM 하이브리드\n | 'photo' // 사진/3D 렌더 — VLM 러프 폴백\n | 'unknown'\n\n/** 검출된 저수준 기하 프리미티브 (픽셀/소스 좌표계). */\nexport interface GeometricPrimitive {\n kind: 'line' | 'rect' | 'region' | 'point'\n bbox: BoundingBox\n /** line 일 때 양 끝점. */\n segment?: { a: Vec2; b: Vec2 }\n /** 검출 신뢰도 0..1 (있으면). */\n score?: number\n /**\n * 이미 완전한 엔티티를 아는 추출기(벡터: DXF/IFC/SVG)가 원 엔티티를 그대로 실어 보낸다.\n * 있으면 fuse 가 무손실로 사용(attribs/rotation/vertices 보존). CV 추출기는 비움 → fuse 가\n * bbox 로 bare 엔티티 생성 후 Recognizer 가 의미 부여.\n */\n entity?: UniversalEntity\n /** 추출기별 원시 메타. */\n meta?: Record<string, any>\n}\n\n/** OCR 라벨 — 텍스트 + 위치 (식별/앵커/스케일 단서). */\nexport interface Label {\n text: string\n bbox: BoundingBox\n score?: number\n}\n\n/** 한 region(프리미티브/crop)에 대한 의미 인식 결과. */\nexport interface RecognitionResult {\n /** 실제 보드 컴포넌트 type (카탈로그 그라운딩). 불명 시 undefined. */\n componentType?: string\n confidence: number\n attribs?: Record<string, any>\n}\n\n/** 스케일 캘리브레이션 결과 — 픽셀 → board 단위 변환. */\nexport interface ScaleResult {\n unitsPerPixel: number\n origin: Vec2\n units?: ParseResult['units']\n}\n\n/** 추출 컨텍스트 — 이미지 dims, 사용자 힌트, 카탈로그 등 (확장 여지). */\nexport interface ExtractionContext {\n imageWidth?: number\n imageHeight?: number\n userPrompt?: string\n /** 카탈로그 그라운딩용 컴포넌트 type 목록 (Recognizer 가 제약). */\n catalog?: string[]\n /**\n * 명시 스케일 기준 — 알려진 픽셀 span = 알려진 실제 길이 (예: UI 2점 캘리브레이션,\n * 스케일바). ScaleCalibrator 가 최우선 사용. unitsPerPixel = units / pixels.\n */\n scaleReference?: { pixels: number; units: number; unit?: string }\n /** The AI client of the domain the extraction runs for (aiClientFor). A recognizer that asks a model needs it. */\n aiClient?: import('@things-factory/ai-client-base').AIClient\n [k: string]: any\n}\n\n// ── contracts ────────────────────────────────────────────────────────────────\n\nexport interface GeometryExtractor {\n readonly id: string\n extract(input: AdapterInput, ctx?: ExtractionContext): Promise<GeometricPrimitive[]>\n}\n\nexport interface LabelExtractor {\n readonly id: string\n extract(input: AdapterInput, ctx?: ExtractionContext): Promise<Label[]>\n}\n\nexport interface Recognizer {\n readonly id: string\n /** region(프리미티브 + ctx)이 어떤 컴포넌트인지 — VLM 인식 전담(좌표 아님). */\n recognize(region: GeometricPrimitive, ctx?: ExtractionContext): Promise<RecognitionResult>\n}\n\nexport interface ScaleCalibrator {\n readonly id: string\n calibrate(labels: Label[], primitives: GeometricPrimitive[], ctx?: ExtractionContext): Promise<ScaleResult>\n}\n\nexport interface LayoutRefiner {\n readonly id: string\n refine(entities: UniversalEntity[], ctx?: ExtractionContext): UniversalEntity[]\n}\n\n/** 한 시안(해석) — 공유 기하 위에 의미/그룹핑/전략만 달리한 결과. */\nexport interface VariantDraft {\n /** URL 친화 short id (예: 'equipment-focus'). */\n id: string\n /** 사용자 표시 라벨 (예: '장비 중심'). */\n label: string\n description?: string\n entities: UniversalEntity[]\n subjectName?: string\n subjectDescription?: string\n importStrategy?: string\n}\n\n/**\n * 시안 전략 — *정확한 공유 기하+라벨* 을 받아 N개 해석 시안을 만든다.\n * (UX: 사용자가 3안 중 선택하는 흐름 보존. 기하는 공유라 셋 다 레이아웃 정상, 해석만 다름.)\n * 미지정 시 파이프라인이 단일 default 시안 1개를 만든다.\n */\nexport interface VariantStrategy {\n readonly id: string\n interpret(\n primitives: GeometricPrimitive[],\n labels: Label[],\n set: ExtractorSet,\n ctx?: ExtractionContext\n ): Promise<VariantDraft[]>\n}\n\n/** 한 소스경로의 추출기 묶음 (geometry 필수, 나머지는 경로별 선택). */\nexport interface ExtractorSet {\n geometry: GeometryExtractor\n labels?: LabelExtractor\n recognizer?: Recognizer\n scale?: ScaleCalibrator\n refiner?: LayoutRefiner\n /** 다중 시안 생성 전략 (3안 선택 UX). 미지정 시 단일 default 시안. */\n variants?: VariantStrategy\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { type AIClient } from '@things-factory/ai-client-base';
1
+ import type { AIClient } from '@things-factory/ai-client-base';
2
2
  import type { Recognizer, RecognitionResult, GeometricPrimitive, ExtractionContext } from './contracts';
3
3
  interface RawRecognition {
4
4
  componentType?: string | null;
@@ -3,16 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VlmRecognizer = void 0;
4
4
  exports.buildPrompt = buildPrompt;
5
5
  exports.normalize = normalize;
6
- /*
7
- * VlmRecognizer — region 이 *어떤 컴포넌트인지* 식별 (Phase Board-AI §3.8 의 Recognizer).
8
- *
9
- * 좌표/레이아웃은 절대 추정하지 않는다(그건 Geometry/CV 의 몫). VLM/LLM 의 강점인
10
- * *의미 인식* 만: 주어진 신호(라벨·모양·크기·레이어) + 카탈로그 → componentType.
11
- * 카탈로그(ctx.catalog) 가 있으면 그 안의 값만 허용(그라운딩).
12
- *
13
- * provider 는 ai-client-base 의 AIClient. 생성자 주입 가능(테스트), 미주입 시 기본 클라이언트.
14
- */
15
- const ai_client_base_1 = require("@things-factory/ai-client-base");
16
6
  const SYSTEM = '당신은 시설 도면의 한 영역(region)이 어떤 보드 컴포넌트 type 인지 판정하는 분류기다. ' +
17
7
  '좌표/위치/크기를 추정하지 말고, 주어진 신호(라벨/모양/크기/레이어)와 카탈로그만으로 *type* 만 판정한다. ' +
18
8
  'STRICT JSON 만 반환(마크다운/주석 금지).';
@@ -22,9 +12,9 @@ class VlmRecognizer {
22
12
  this.id = 'vlm-recognizer';
23
13
  }
24
14
  async recognize(region, ctx) {
25
- const client = this.client ?? (0, ai_client_base_1.getDefaultAIClient)();
15
+ const client = this.client ?? ctx?.aiClient;
26
16
  if (!client) {
27
- throw new Error('[board-import/extraction] VlmRecognizer 는 AIClient 가 필요합니다 (setDefaultAIClient / config.aiClient).');
17
+ throw new Error('[board-import/extraction] VlmRecognizer needs the AI client of the domain it runs for (ctx.aiClient).');
28
18
  }
29
19
  const raw = await client.generateJSON(buildPrompt(region, ctx), {
30
20
  systemPrompt: SYSTEM,