@things-factory/board-import 10.0.0-beta.71 → 10.0.0-beta.73
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/index.d.ts +1 -1
- package/dist-server/index.js +1 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/adapters/dxf-adapter.d.ts +1 -1
- package/dist-server/service/adapters/dxf-adapter.js +18 -4
- package/dist-server/service/adapters/dxf-adapter.js.map +1 -1
- package/dist-server/service/adapters/image-adapter.d.ts +32 -0
- package/dist-server/service/adapters/image-adapter.js +420 -0
- package/dist-server/service/adapters/image-adapter.js.map +1 -0
- package/dist-server/service/ai/types.d.ts +96 -4
- package/dist-server/service/ai/types.js +221 -12
- package/dist-server/service/ai/types.js.map +1 -1
- package/dist-server/service/converters/generic-to.d.ts +17 -8
- package/dist-server/service/converters/generic-to.js +44 -44
- package/dist-server/service/converters/generic-to.js.map +1 -1
- package/dist-server/service/import-session/import-actions.d.ts +79 -0
- package/dist-server/service/import-session/import-actions.js +98 -0
- package/dist-server/service/import-session/import-actions.js.map +1 -0
- package/dist-server/service/import-session/import-session-resolver.d.ts +22 -1
- package/dist-server/service/import-session/import-session-resolver.js +166 -38
- package/dist-server/service/import-session/import-session-resolver.js.map +1 -1
- package/dist-server/service/import-session/import-worker.js +55 -13
- package/dist-server/service/import-session/import-worker.js.map +1 -1
- package/dist-server/service/import-session/index.d.ts +5 -3
- package/dist-server/service/import-session/index.js +16 -9
- package/dist-server/service/import-session/index.js.map +1 -1
- package/dist-server/service/import-session/materialize-from-session.d.ts +66 -0
- package/dist-server/service/import-session/materialize-from-session.js +104 -0
- package/dist-server/service/import-session/materialize-from-session.js.map +1 -0
- package/dist-server/service/import-session/suggest-board-name.d.ts +80 -0
- package/dist-server/service/import-session/suggest-board-name.js +195 -0
- package/dist-server/service/import-session/suggest-board-name.js.map +1 -0
- package/dist-server/service/import-tools.d.ts +10 -0
- package/dist-server/service/import-tools.js +227 -0
- package/dist-server/service/import-tools.js.map +1 -0
- package/dist-server/service/index.d.ts +22 -19
- package/dist-server/service/index.js +46 -36
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/pipeline/index.d.ts +42 -9
- package/dist-server/service/pipeline/index.js +92 -23
- package/dist-server/service/pipeline/index.js.map +1 -1
- package/dist-server/service/pipeline/stage2-mapping.d.ts +20 -5
- package/dist-server/service/pipeline/stage2-mapping.js +84 -26
- package/dist-server/service/pipeline/stage2-mapping.js.map +1 -1
- package/dist-server/service/pipeline/stage3-board.d.ts +2 -2
- package/dist-server/service/pipeline/stage3-board.js +65 -10
- package/dist-server/service/pipeline/stage3-board.js.map +1 -1
- package/dist-server/service/pipeline/stage4-binding.d.ts +1 -1
- package/dist-server/service/pipeline/stage4-binding.js +2 -2
- package/dist-server/service/pipeline/stage4-binding.js.map +1 -1
- package/dist-server/service/registry/index.d.ts +1 -1
- package/dist-server/service/registry/index.js.map +1 -1
- package/dist-server/service/types/index.d.ts +35 -0
- package/dist-server/service/types/index.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/server/index.ts +1 -1
- package/server/service/adapters/dxf-adapter.ts +17 -4
- package/server/service/adapters/image-adapter.test.ts +545 -0
- package/server/service/adapters/image-adapter.ts +464 -0
- package/server/service/ai/types.ts +354 -19
- package/server/service/converters/generic-to.test.ts +91 -0
- package/server/service/converters/generic-to.ts +66 -49
- package/server/service/import-session/import-actions.test.ts +185 -0
- package/server/service/import-session/import-actions.ts +164 -0
- package/server/service/import-session/import-session-resolver.ts +171 -39
- package/server/service/import-session/import-worker.ts +56 -12
- package/server/service/import-session/index.ts +19 -3
- package/server/service/import-session/materialize-from-session.test.ts +274 -0
- package/server/service/import-session/materialize-from-session.ts +158 -0
- package/server/service/import-session/suggest-board-name.test.ts +271 -0
- package/server/service/import-session/suggest-board-name.ts +279 -0
- package/server/service/import-tools.test.ts +137 -0
- package/server/service/import-tools.ts +255 -0
- package/server/service/index.ts +35 -18
- package/server/service/pipeline/index.ts +118 -23
- package/server/service/pipeline/stage2-mapping.test.ts +204 -0
- package/server/service/pipeline/stage2-mapping.ts +102 -27
- package/server/service/pipeline/stage3-board.test.ts +133 -0
- package/server/service/pipeline/stage3-board.ts +76 -11
- package/server/service/pipeline/stage4-binding.ts +2 -2
- package/server/service/registry/index.ts +1 -1
- package/server/service/types/index.ts +36 -0
|
@@ -0,0 +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"]}
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* Phase 1-2 결정적 파이프라인은 AI 미사용. 위 등록은 Phase 3+ 메서드 활성화용.
|
|
18
18
|
*/
|
|
19
19
|
import { type AIClient as BaseAIClient } from '@things-factory/ai-client-base';
|
|
20
|
-
import type { ComponentCategory, ImportRule } from '../types/registry
|
|
21
|
-
import type { UniversalEntity } from '../types/index
|
|
20
|
+
import type { ComponentCategory, ImportRule } from '../types/registry';
|
|
21
|
+
import type { UniversalEntity } from '../types/index';
|
|
22
22
|
export interface BoardImportAI {
|
|
23
23
|
/** 식별자 — 보통 base.id 와 동일 (provider:model) */
|
|
24
24
|
readonly id: string;
|
|
@@ -34,9 +34,16 @@ export interface BoardImportAI {
|
|
|
34
34
|
classifyEntities?(labeled: LabeledEntity[], unlabeled: UniversalEntity[], options?: ClassificationOptions): Promise<EntityClassification[]>;
|
|
35
35
|
/**
|
|
36
36
|
* 도면 이미지(PNG/JPG/PDF 페이지) 에서 객체를 검출하여 UniversalEntity 로 변환.
|
|
37
|
+
*
|
|
38
|
+
* 1-pass structured response 를 권장 — view type 자동 분류 + entity 추출을 단일 vision
|
|
39
|
+
* 호출로 처리해 토큰 비용을 절반으로 줄인다. 응답 형태:
|
|
40
|
+
* - **`ImageAnalysis`** 객체: `{ viewType, confidence, entities, reasoning? }`
|
|
41
|
+
* - 또는 **`UniversalEntity[]`** 직접 (구버전 호환).
|
|
42
|
+
* ImageAdapter 는 양쪽을 모두 정규화 처리.
|
|
43
|
+
*
|
|
37
44
|
* Phase 3 (이미지 어댑터) 에서 사용.
|
|
38
45
|
*/
|
|
39
|
-
parseFromImage?(image: Buffer | Uint8Array, options?: ImageParseOptions): Promise<UniversalEntity[]>;
|
|
46
|
+
parseFromImage?(image: Buffer | Uint8Array, options?: ImageParseOptions): Promise<ImageAnalysis | UniversalEntity[]>;
|
|
40
47
|
/**
|
|
41
48
|
* 자연어 매핑 정의 ("STK는 stocker, BUF는 buffer") → ImportRule[] 로 변환.
|
|
42
49
|
*/
|
|
@@ -77,17 +84,102 @@ export interface ImageParseOptions {
|
|
|
77
84
|
/** 자연어 컨텍스트. 예: "factory floor plan with AGV paths" */
|
|
78
85
|
context?: string;
|
|
79
86
|
categories?: ComponentCategory[];
|
|
87
|
+
/**
|
|
88
|
+
* 사용자 자유서술 hint — 도면 설명 / 강조 / 카테고리 가이드 등.
|
|
89
|
+
* 1-pass VLM 프롬프트에 합류해 view type 분류 + entity 추출 양쪽에 영향.
|
|
90
|
+
*/
|
|
91
|
+
userPrompt?: string;
|
|
92
|
+
/** 이미지 dimension — 픽셀 좌표계 명시용 (top-down 모드에서). */
|
|
93
|
+
imageWidth?: number;
|
|
94
|
+
imageHeight?: number;
|
|
95
|
+
/**
|
|
96
|
+
* View type 강제. 미지정 시 VLM 이 자동 분류.
|
|
97
|
+
* - 'top-down': 픽셀 좌표 그대로
|
|
98
|
+
* - 'perspective-3d' / 'photo': floor plan 좌표 추정 (0..1000 단위)
|
|
99
|
+
*/
|
|
100
|
+
viewType?: 'top-down' | 'perspective-3d' | 'photo';
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 1-pass VLM 응답의 구조화된 결과 — **다중 시안 (variants)** 포함.
|
|
104
|
+
*
|
|
105
|
+
* 같은 도면을 AI 가 3 가지 다른 관점으로 해석. 사용자가 review 에서 선택.
|
|
106
|
+
*
|
|
107
|
+
* 시안 축 (프롬프트가 제시) 예시:
|
|
108
|
+
* - 장비(equipment) 중심 / 운송(transport) 중심 / 영역(region) 중심
|
|
109
|
+
* - 보수적(conservative) / 균형(balanced) / 공격적(aggressive)
|
|
110
|
+
*
|
|
111
|
+
* 각 시안은 독립적인 entities + subjectName/subjectDescription + importStrategy.
|
|
112
|
+
* viewType / confidence / reasoning 은 시안 무관 공통 (도면 자체의 특성).
|
|
113
|
+
*/
|
|
114
|
+
export interface ImageAnalysis {
|
|
115
|
+
/** 자동 분류된 view type — 시안 무관 공통. */
|
|
116
|
+
viewType: 'top-down' | 'perspective-3d' | 'photo';
|
|
117
|
+
/** 분류 신뢰도 (0..1). LLM 자기보고. */
|
|
118
|
+
confidence: number;
|
|
119
|
+
/** 분류 근거 한 줄 (왜 이 viewType 인가). */
|
|
120
|
+
reasoning?: string;
|
|
121
|
+
/** 시안 풀세트. 보통 3 개 — variants[0] 가 default. */
|
|
122
|
+
variants: ImageAnalysisVariant[];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 한 시안의 풀데이터 — entities + 정체성 + 의도.
|
|
126
|
+
* 각 시안은 같은 도면이지만 다른 해석.
|
|
127
|
+
*/
|
|
128
|
+
export interface ImageAnalysisVariant {
|
|
129
|
+
/** URL 친화적 short id. */
|
|
130
|
+
id: string;
|
|
131
|
+
/** 사용자에게 보여줄 한글 라벨. 예: "장비 중심" */
|
|
132
|
+
label: string;
|
|
133
|
+
/** 짧은 한 줄 설명 — 이 시안의 강조점. */
|
|
134
|
+
description?: string;
|
|
135
|
+
/** 추출된 entity 목록. 좌표 의미는 ImageAnalysis.viewType 에 따름. */
|
|
136
|
+
entities: UniversalEntity[];
|
|
137
|
+
/**
|
|
138
|
+
* 보드 정체성 — 도면이 무엇을 나타내는가의 짧은 제목 (≤30자). 보드 name 자동 추천 기반.
|
|
139
|
+
* 처리 과정이 아니라 **컨텐츠 정체성** 만.
|
|
140
|
+
*/
|
|
141
|
+
subjectName?: string;
|
|
142
|
+
/**
|
|
143
|
+
* 보드 정체성 — 도면이 나타내는 시설/배치/구성 1-2 문장. 보드 description 자동 추천 기반.
|
|
144
|
+
* 처리 과정 X (importStrategy 와 분리).
|
|
145
|
+
*/
|
|
146
|
+
subjectDescription?: string;
|
|
147
|
+
/**
|
|
148
|
+
* AI 의 의도 narrative — "왜 이 시안에서 이렇게 해석했나". 3-5 문장.
|
|
149
|
+
* **transient — wizard review 카드 전시용. 영속 description 에는 사용 X.**
|
|
150
|
+
*/
|
|
151
|
+
importStrategy?: string;
|
|
80
152
|
}
|
|
81
153
|
export declare class DefaultBoardImportAI implements BoardImportAI {
|
|
82
154
|
private base;
|
|
83
155
|
readonly id: string;
|
|
156
|
+
private static readonly VARIANT_AXES;
|
|
84
157
|
constructor(base: BaseAIClient);
|
|
85
158
|
inferConventions(entities: UniversalEntity[], options?: ConventionInferenceOptions): Promise<RuleSuggestion[]>;
|
|
86
159
|
classifyEntities(labeled: LabeledEntity[], unlabeled: UniversalEntity[], options?: ClassificationOptions): Promise<EntityClassification[]>;
|
|
87
|
-
parseFromImage(image: Buffer | Uint8Array, options?: ImageParseOptions): Promise<
|
|
160
|
+
parseFromImage(image: Buffer | Uint8Array, options?: ImageParseOptions): Promise<ImageAnalysis>;
|
|
161
|
+
private _callOneVariant;
|
|
88
162
|
parseNaturalLanguageMapping(text: string, availableTypes: string[]): Promise<ImportRule[]>;
|
|
89
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* VLM 의 JSON 응답을 ImageAnalysis 형태로 정규화 — 다중 시안 (variants) 지원.
|
|
166
|
+
*
|
|
167
|
+
* 허용 입력 형태:
|
|
168
|
+
* - `{ viewType, confidence, reasoning?, variants: [...] }` — **최신 권장** (multi-variant)
|
|
169
|
+
* - `{ viewType, ..., entities }` — 구버전 single-variant (자동으로 variants:[default] 으로 감쌈)
|
|
170
|
+
* - `[ entity, ... ]` — 가장 구버전 (entities 만)
|
|
171
|
+
*
|
|
172
|
+
* 결손 / 비정상 viewType 은 'top-down' fallback. forcedViewType 이 주어지면 그 값 강제.
|
|
173
|
+
*/
|
|
174
|
+
export declare function normalizeImageAnalysisResponse(rawText: string, forcedViewType?: 'top-down' | 'perspective-3d' | 'photo'): ImageAnalysis;
|
|
90
175
|
export declare function setAIClient(client: BoardImportAI | undefined): void;
|
|
176
|
+
/**
|
|
177
|
+
* 등록된 BoardImportAI 가 있으면 그걸 반환.
|
|
178
|
+
* 없으면 ai-client-base 의 default AIClient (config.aiClient) 를 `DefaultBoardImportAI` 로
|
|
179
|
+
* 감싸서 반환 — board-ai 와 동일한 1회 설정만으로 image import 도 동작하게 한다.
|
|
180
|
+
*
|
|
181
|
+
* default base 가 바뀌면 (테스트 등) 캐시를 무효화하고 다시 감싼다.
|
|
182
|
+
*/
|
|
91
183
|
export declare function getAIClient(): BoardImportAI | undefined;
|
|
92
184
|
/** Deprecated alias — 기존 코드 호환용. 새로 작성 시 BoardImportAI 사용. */
|
|
93
185
|
export type AIClient = BoardImportAI;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DefaultBoardImportAI = void 0;
|
|
4
|
+
exports.normalizeImageAnalysisResponse = normalizeImageAnalysisResponse;
|
|
4
5
|
exports.setAIClient = setAIClient;
|
|
5
6
|
exports.getAIClient = getAIClient;
|
|
6
7
|
/**
|
|
@@ -22,8 +23,24 @@ exports.getAIClient = getAIClient;
|
|
|
22
23
|
* Phase 1-2 결정적 파이프라인은 AI 미사용. 위 등록은 Phase 3+ 메서드 활성화용.
|
|
23
24
|
*/
|
|
24
25
|
const ai_client_base_1 = require("@things-factory/ai-client-base");
|
|
25
|
-
// ── 기본 구현 — base AIClient 위에 LLM 프롬프트로 ────────────────────
|
|
26
26
|
class DefaultBoardImportAI {
|
|
27
|
+
static { this.VARIANT_AXES = [
|
|
28
|
+
{
|
|
29
|
+
id: 'equipment-focus',
|
|
30
|
+
label: '장비 중심',
|
|
31
|
+
instruction: '장비/설비(machine/stocker/facility) 식별 우선. 이동체/경로는 보조.'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'transport-focus',
|
|
35
|
+
label: '운송 중심',
|
|
36
|
+
instruction: '차량/경로(AGV/forklift/conveyor) 식별 우선. 정적 장비는 영역으로 묶음.'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'region-focus',
|
|
40
|
+
label: '구역 중심',
|
|
41
|
+
instruction: '영역/구획(zone/wall/structure) 식별 우선. 개별 장비는 generic placeholder.'
|
|
42
|
+
}
|
|
43
|
+
]; }
|
|
27
44
|
constructor(base) {
|
|
28
45
|
this.base = base;
|
|
29
46
|
this.id = base.id;
|
|
@@ -95,17 +112,87 @@ ${JSON.stringify(targets, null, 2)}`;
|
|
|
95
112
|
if (!this.base.vision) {
|
|
96
113
|
throw new Error(`AI client (${this.base.id}) does not support vision input`);
|
|
97
114
|
}
|
|
98
|
-
const
|
|
115
|
+
const results = await Promise.allSettled(DefaultBoardImportAI.VARIANT_AXES.map(axis => this._callOneVariant(image, axis, options)));
|
|
116
|
+
const succeeded = results
|
|
117
|
+
.filter((r) => r.status === 'fulfilled')
|
|
118
|
+
.map(r => r.value);
|
|
119
|
+
if (succeeded.length === 0) {
|
|
120
|
+
const errors = results.map(r => r.reason?.message ?? String(r.reason));
|
|
121
|
+
throw new Error(`All variant calls failed: ${errors.join('; ')}`);
|
|
122
|
+
}
|
|
123
|
+
const forcedVT = options.viewType;
|
|
124
|
+
const viewType = forcedVT ?? consensusViewType(succeeded.map(r => r.viewType));
|
|
125
|
+
const confidence = succeeded.reduce((s, r) => s + r.confidence, 0) / succeeded.length;
|
|
126
|
+
const reasoning = succeeded.find(r => r.reasoning)?.reasoning;
|
|
127
|
+
return { viewType, confidence, reasoning, variants: succeeded.map(r => r.variant) };
|
|
128
|
+
}
|
|
129
|
+
async _callOneVariant(image, axis, options) {
|
|
130
|
+
const w = options.imageWidth;
|
|
131
|
+
const h = options.imageHeight;
|
|
132
|
+
const userHint = options.userPrompt?.trim();
|
|
133
|
+
const forcedViewType = options.viewType;
|
|
134
|
+
const dimsLine = Number.isFinite(w) && Number.isFinite(h)
|
|
135
|
+
? `\nImage dimensions: ${w} x ${h} pixels.`
|
|
136
|
+
: '';
|
|
137
|
+
const viewTypeInstruction = forcedViewType
|
|
138
|
+
? `\nThe view type is GIVEN: "${forcedViewType}". Do not classify; use it as-is.`
|
|
139
|
+
: `\nFirst, classify the view type:
|
|
140
|
+
- "top-down": orthographic floor plan / 2D engineering drawing seen from directly above
|
|
141
|
+
- "perspective-3d": 3D rendering / isometric / bird's-eye view with visible depth
|
|
142
|
+
- "photo": photograph of a physical space`;
|
|
143
|
+
const coordRule = `\nCoordinate convention by viewType:
|
|
144
|
+
- "top-down": INTEGER PIXEL coordinates (origin top-left, x →, y ↓)${Number.isFinite(w) && Number.isFinite(h) ? ` in [0, ${w}] x [0, ${h}]` : ''}. Each entity ≥ 16x16 pixels.
|
|
145
|
+
- "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).
|
|
146
|
+
NEVER use normalized 0..1 coordinates.`;
|
|
147
|
+
const prompt = `Analyze this drawing image and provide ONE interpretation with focus axis: "${axis.id}".
|
|
148
|
+
Focus: ${axis.instruction}${userHint ? '\n\nUser hints (highest priority): ' + userHint : ''}${options.context ? '\nContext: ' + options.context : ''}${options.categories ? '\nPossible categories: ' + options.categories.join(', ') : ''}${dimsLine}${viewTypeInstruction}${coordRule}
|
|
149
|
+
|
|
150
|
+
Return STRICT JSON (no markdown, no commentary) with this shape:
|
|
151
|
+
{
|
|
152
|
+
"viewType": "top-down" | "perspective-3d" | "photo",
|
|
153
|
+
"confidence": 0..1,
|
|
154
|
+
"reasoning": "brief one-sentence explanation of view type classification",
|
|
155
|
+
"variant": {
|
|
156
|
+
"id": "${axis.id}",
|
|
157
|
+
"label": "한글 라벨 (예: ${axis.label})",
|
|
158
|
+
"description": "한 줄 — 이 시안의 강조점",
|
|
159
|
+
"subjectName": "≤30 char short title — what the depicted facility is",
|
|
160
|
+
"subjectDescription": "1-2 sentences in Korean — what is depicted (NOT how you analyzed)",
|
|
161
|
+
"importStrategy": "3-5 sentence narrative explaining YOUR INTENT for this variant",
|
|
162
|
+
"entities": [
|
|
163
|
+
{
|
|
164
|
+
"uid": "string",
|
|
165
|
+
"kind": "shape" | "path" | "text",
|
|
166
|
+
"position": { "x": number, "y": number },
|
|
167
|
+
"size": { "width": number, "height": number },
|
|
168
|
+
"bbox": { "min": {"x": number, "y": number}, "max": {"x": number, "y": number} },
|
|
169
|
+
"attribs": {
|
|
170
|
+
"name": "string?",
|
|
171
|
+
"semanticType": "string?",
|
|
172
|
+
"text": "string?",
|
|
173
|
+
"layer": "string?",
|
|
174
|
+
"categoryHint": "container | vehicle | path | region | equipment | io | marker | structure"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
categoryHint should be one of the listed categories; omit only if highly uncertain.
|
|
182
|
+
Provide 10-30 distinct entities clearly identifiable under the "${axis.id}" axis.
|
|
99
183
|
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
184
|
+
attribs.name — IMPORTANT for component identity / data binding:
|
|
185
|
+
- 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.
|
|
186
|
+
- DO NOT invent labels not in the drawing — leave attribs.name empty if uncertain.
|
|
187
|
+
- Keep alphanumeric + hyphens / underscores. No spaces. ≤24 chars. UNIQUE within the variant.
|
|
188
|
+
|
|
189
|
+
subjectName + subjectDescription — describe WHAT the board depicts (its identity), NOT what you did.
|
|
190
|
+
These become the board's persistent name + description if the user picks this variant.
|
|
191
|
+
|
|
192
|
+
importStrategy — Korean 3-5 sentences: what this variant emphasizes, how you decided categories, areas of uncertainty.
|
|
193
|
+
Used in the wizard review card only; transient — separate from board description.`;
|
|
194
|
+
const raw = await this.base.vision(image, prompt, { maxTokens: 4096 });
|
|
195
|
+
return parseSingleVariantResponse(raw, axis.id, axis.label, forcedViewType);
|
|
109
196
|
}
|
|
110
197
|
async parseNaturalLanguageMapping(text, availableTypes) {
|
|
111
198
|
const prompt = `Convert the following user description into ImportRule[] entries for the board-import pipeline.
|
|
@@ -126,6 +213,110 @@ Return JSON array of:
|
|
|
126
213
|
exports.DefaultBoardImportAI = DefaultBoardImportAI;
|
|
127
214
|
// ── 유틸 ─────────────────────────────────────────────────────────────
|
|
128
215
|
const SYSTEM_HINT = 'You are a CAD/IFC drawing analysis assistant. Output strict JSON only — no commentary, no markdown.';
|
|
216
|
+
const VIEW_TYPES = ['top-down', 'perspective-3d', 'photo'];
|
|
217
|
+
function parseSingleVariantResponse(rawText, axisId, axisLabel, forcedViewType) {
|
|
218
|
+
const parsed = (0, ai_client_base_1.parseJSONResponse)(rawText);
|
|
219
|
+
const trimmed = (s) => (typeof s === 'string' && s.trim() ? s.trim() : undefined);
|
|
220
|
+
const rawVT = parsed?.viewType;
|
|
221
|
+
const viewType = forcedViewType ??
|
|
222
|
+
(VIEW_TYPES.includes(rawVT)
|
|
223
|
+
? rawVT
|
|
224
|
+
: 'top-down');
|
|
225
|
+
const confidence = typeof parsed?.confidence === 'number' && parsed.confidence >= 0 && parsed.confidence <= 1
|
|
226
|
+
? parsed.confidence
|
|
227
|
+
: 0.5;
|
|
228
|
+
const v = parsed?.variant ?? {};
|
|
229
|
+
const variant = {
|
|
230
|
+
id: typeof v.id === 'string' && v.id ? v.id : axisId,
|
|
231
|
+
label: typeof v.label === 'string' && v.label ? v.label : axisLabel,
|
|
232
|
+
description: trimmed(v.description),
|
|
233
|
+
entities: Array.isArray(v.entities) ? v.entities : [],
|
|
234
|
+
subjectName: trimmed(v.subjectName),
|
|
235
|
+
subjectDescription: trimmed(v.subjectDescription),
|
|
236
|
+
importStrategy: trimmed(v.importStrategy)
|
|
237
|
+
};
|
|
238
|
+
return { viewType, confidence, reasoning: trimmed(parsed?.reasoning), variant };
|
|
239
|
+
}
|
|
240
|
+
function consensusViewType(viewTypes) {
|
|
241
|
+
const counts = new Map();
|
|
242
|
+
for (const vt of viewTypes)
|
|
243
|
+
counts.set(vt, (counts.get(vt) ?? 0) + 1);
|
|
244
|
+
let best = 'top-down';
|
|
245
|
+
let max = 0;
|
|
246
|
+
for (const [vt, count] of counts) {
|
|
247
|
+
if (count > max) {
|
|
248
|
+
max = count;
|
|
249
|
+
best = vt;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return best;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* VLM 의 JSON 응답을 ImageAnalysis 형태로 정규화 — 다중 시안 (variants) 지원.
|
|
256
|
+
*
|
|
257
|
+
* 허용 입력 형태:
|
|
258
|
+
* - `{ viewType, confidence, reasoning?, variants: [...] }` — **최신 권장** (multi-variant)
|
|
259
|
+
* - `{ viewType, ..., entities }` — 구버전 single-variant (자동으로 variants:[default] 으로 감쌈)
|
|
260
|
+
* - `[ entity, ... ]` — 가장 구버전 (entities 만)
|
|
261
|
+
*
|
|
262
|
+
* 결손 / 비정상 viewType 은 'top-down' fallback. forcedViewType 이 주어지면 그 값 강제.
|
|
263
|
+
*/
|
|
264
|
+
function normalizeImageAnalysisResponse(rawText, forcedViewType) {
|
|
265
|
+
const parsed = (0, ai_client_base_1.parseJSONResponse)(rawText);
|
|
266
|
+
const trimmed = (s) => (typeof s === 'string' && s.trim() ? s.trim() : undefined);
|
|
267
|
+
if (Array.isArray(parsed)) {
|
|
268
|
+
return {
|
|
269
|
+
viewType: forcedViewType ?? 'top-down',
|
|
270
|
+
confidence: 0.5,
|
|
271
|
+
variants: [{ id: 'default', label: 'default', entities: parsed }]
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
275
|
+
return {
|
|
276
|
+
viewType: forcedViewType ?? 'top-down',
|
|
277
|
+
confidence: 0,
|
|
278
|
+
variants: [{ id: 'default', label: 'default', entities: [] }]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const rawVT = parsed.viewType;
|
|
282
|
+
const viewType = forcedViewType ??
|
|
283
|
+
(VIEW_TYPES.includes(rawVT) ? rawVT : 'top-down');
|
|
284
|
+
const confidence = typeof parsed.confidence === 'number' && parsed.confidence >= 0 && parsed.confidence <= 1
|
|
285
|
+
? parsed.confidence
|
|
286
|
+
: 0.5;
|
|
287
|
+
// variants 배열 정규화 — 최신 권장 형태
|
|
288
|
+
let variants;
|
|
289
|
+
if (Array.isArray(parsed.variants) && parsed.variants.length > 0) {
|
|
290
|
+
variants = parsed.variants.map((v, i) => ({
|
|
291
|
+
id: typeof v?.id === 'string' && v.id ? v.id : `variant-${i + 1}`,
|
|
292
|
+
label: typeof v?.label === 'string' && v.label ? v.label : `Variant ${i + 1}`,
|
|
293
|
+
description: trimmed(v?.description),
|
|
294
|
+
entities: Array.isArray(v?.entities) ? v.entities : [],
|
|
295
|
+
subjectName: trimmed(v?.subjectName),
|
|
296
|
+
subjectDescription: trimmed(v?.subjectDescription),
|
|
297
|
+
importStrategy: trimmed(v?.importStrategy)
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
// 구버전 호환 — top-level entities + subjectName 등을 단일 variant 로 감쌈
|
|
302
|
+
variants = [
|
|
303
|
+
{
|
|
304
|
+
id: 'default',
|
|
305
|
+
label: 'default',
|
|
306
|
+
entities: Array.isArray(parsed.entities) ? parsed.entities : [],
|
|
307
|
+
subjectName: trimmed(parsed.subjectName),
|
|
308
|
+
subjectDescription: trimmed(parsed.subjectDescription),
|
|
309
|
+
importStrategy: trimmed(parsed.importStrategy)
|
|
310
|
+
}
|
|
311
|
+
];
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
viewType,
|
|
315
|
+
confidence,
|
|
316
|
+
reasoning: trimmed(parsed.reasoning),
|
|
317
|
+
variants
|
|
318
|
+
};
|
|
319
|
+
}
|
|
129
320
|
/**
|
|
130
321
|
* LLM 응답의 regex pattern 문자열 → 실제 RegExp.
|
|
131
322
|
* 보통 "^STK_" 같은 source string 으로 응답.
|
|
@@ -160,10 +351,28 @@ function hydrateRuleRegexes(rule) {
|
|
|
160
351
|
}
|
|
161
352
|
// ── Provider 등록 ──────────────────────────────────────────────────
|
|
162
353
|
let currentClient;
|
|
354
|
+
let defaultBackedClient;
|
|
355
|
+
let defaultBackedFor;
|
|
163
356
|
function setAIClient(client) {
|
|
164
357
|
currentClient = client;
|
|
165
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* 등록된 BoardImportAI 가 있으면 그걸 반환.
|
|
361
|
+
* 없으면 ai-client-base 의 default AIClient (config.aiClient) 를 `DefaultBoardImportAI` 로
|
|
362
|
+
* 감싸서 반환 — board-ai 와 동일한 1회 설정만으로 image import 도 동작하게 한다.
|
|
363
|
+
*
|
|
364
|
+
* default base 가 바뀌면 (테스트 등) 캐시를 무효화하고 다시 감싼다.
|
|
365
|
+
*/
|
|
166
366
|
function getAIClient() {
|
|
167
|
-
|
|
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;
|
|
168
377
|
}
|
|
169
378
|
//# 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 { 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"]}
|
|
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"]}
|