diff --git a/.mcp.json b/.mcp.json index feef2d2..2dc5dad 100644 --- a/.mcp.json +++ b/.mcp.json @@ -7,7 +7,7 @@ "npx", "-y", "figma-developer-mcp", - "--figma-api-key=figd_R6ASvFG2IHcHs35_XFPJh0sTkvp4RxWyEhMhT9vv", + "--figma-api-key=figd_s23TfSDL0hS97DIialy0R2P6QsoZQHfuGx1l_t-k", "--stdio" ] } diff --git a/docs/bg-texture-only.png b/docs/bg-texture-only.png new file mode 100644 index 0000000..eb000f7 Binary files /dev/null and b/docs/bg-texture-only.png differ diff --git a/docs/figma-analysis/1-1_d1.json b/docs/figma-analysis/1-1_d1.json new file mode 100644 index 0000000..e761fb7 --- /dev/null +++ b/docs/figma-analysis/1-1_d1.json @@ -0,0 +1 @@ +{"status":429,"err":"Rate limit exceeded"} \ No newline at end of file diff --git a/docs/figma-analysis/1-1_raw.json b/docs/figma-analysis/1-1_raw.json new file mode 100644 index 0000000..e761fb7 --- /dev/null +++ b/docs/figma-analysis/1-1_raw.json @@ -0,0 +1 @@ +{"status":429,"err":"Rate limit exceeded"} \ No newline at end of file diff --git a/docs/figma-analysis/1-1_미래_raw.json b/docs/figma-analysis/1-1_미래_raw.json new file mode 100644 index 0000000..e761fb7 --- /dev/null +++ b/docs/figma-analysis/1-1_미래_raw.json @@ -0,0 +1 @@ +{"status":429,"err":"Rate limit exceeded"} \ No newline at end of file diff --git a/docs/figma-screenshots/1-1_미래.png b/docs/figma-screenshots/1-1_미래.png new file mode 100644 index 0000000..e1fd6f5 Binary files /dev/null and b/docs/figma-screenshots/1-1_미래.png differ diff --git a/docs/figma-screenshots/2-2장_자세히보기.png b/docs/figma-screenshots/2-2장_자세히보기.png new file mode 100644 index 0000000..f4a9604 Binary files /dev/null and b/docs/figma-screenshots/2-2장_자세히보기.png differ diff --git a/docs/figma-screenshots/가치.png b/docs/figma-screenshots/가치.png new file mode 100644 index 0000000..487368b Binary files /dev/null and b/docs/figma-screenshots/가치.png differ diff --git a/docs/figma-screenshots/기술.png b/docs/figma-screenshots/기술.png new file mode 100644 index 0000000..a9d2223 Binary files /dev/null and b/docs/figma-screenshots/기술.png differ diff --git a/docs/test-bg-layer.png b/docs/test-bg-layer.png new file mode 100644 index 0000000..e90738e Binary files /dev/null and b/docs/test-bg-layer.png differ diff --git a/docs/test-gemini-infographic.png b/docs/test-gemini-infographic.png new file mode 100644 index 0000000..abbd701 Binary files /dev/null and b/docs/test-gemini-infographic.png differ diff --git a/docs/test-layered-slide.html b/docs/test-layered-slide.html new file mode 100644 index 0000000..9efc38c --- /dev/null +++ b/docs/test-layered-slide.html @@ -0,0 +1,206 @@ + + + + +DX와 BIM의 개념적 구분과 재정립 + + + +
+ + +
+ background +
+ + +
DX와 BIM의 개념적 구분과 재정립
+ + +
+
📋
+
용어 혼용
+
DX와 BIM 개념이
명확히 정립되지 않은 채
혼용되어 사용
+
+ +
+
🏛️
+
정책 사례
+
건설기술진흥 기본계획
BIM 도입 = 디지털화
로 표현
+
+ +
+
📐
+
BIM
+
3D 모델 기반
정보 통합·관리 도구
핵심 인프라 기술
+
+ +
+
🔄
+
DX
+
디지털 기술 기반
산업 패러다임 전환
업무방식·가치 구조 변혁
+
+ +
+
🔗
+
기술 융합
+
GIS + BIM + DT
기술 융합으로만
DX 실현 가능
+
+ + +
+
DX와 BIM의
관계
+
개념적 구분과 재정립
+
+ + +
+
상위 개념
+
산업 패러다임 전환
프로세스 혁신
+
+ +
+
핵심 기초 기술
+
건설정보 통합 관리
디지털 협업 인프라
+
+ + +
+ BIM은 건설산업의 디지털전환(DX)을 수행하는 과정에서 가장 기초가 되는 일부분이다 +
+ +
+ + diff --git a/docs/test-layered-v2.html b/docs/test-layered-v2.html new file mode 100644 index 0000000..9c26029 --- /dev/null +++ b/docs/test-layered-v2.html @@ -0,0 +1,222 @@ + + + + +DX와 BIM의 개념적 구분과 재정립 + + + +
+ + +
+ +
+ + + + + + + + + + + + +
DX와 BIM의 개념적 구분과 재정립
+ + +
+
📋
+
+
용어 혼용
+
DX와 BIM 개념이
정립되지 않은 채
혼용되어 사용
+
+
+ +
+
🏛️
+
+
정책 사례
+
건설기술진흥 기본계획
BIM 도입 = 디지털화
+
+
+ +
+
📐
+
+
BIM
+
3D 모델 기반
정보 통합·관리 도구
+
+
+ +
+
🔄
+
+
DX
+
디지털 기술 기반
산업 패러다임 전환
업무방식·가치 구조 변혁
+
+
+ +
+
🔗
+
+
기술 융합
+
GIS + BIM + DT
기술 융합으로만
DX 실현 가능
+
+
+ + +
+
+
DX와 BIM의
관계
+
개념적 구분과 재정립
+
+
+ + +
+
+ +
+
상위 개념
+
산업 패러다임 전환
프로세스 혁신
+
+
+
+ +
+
+
+
핵심 기초 기술
+
건설정보 통합 관리
디지털 협업 인프라
+
+ +
+
+ + +
+ BIM은 건설산업의 디지털전환(DX)을 수행하는 과정에서 가장 기초가 되는 일부분이다 +
+ +
+ + diff --git a/docs/test-relation.html b/docs/test-relation.html new file mode 100644 index 0000000..90313ba --- /dev/null +++ b/docs/test-relation.html @@ -0,0 +1,4 @@ + + + +건설산업 DXGISBIMDigital Twin \ No newline at end of file diff --git a/docs/test-relation.svg b/docs/test-relation.svg new file mode 100644 index 0000000..90313ba --- /dev/null +++ b/docs/test-relation.svg @@ -0,0 +1,4 @@ + + + +건설산업 DXGISBIMDigital Twin \ No newline at end of file diff --git a/docs/test-simple.html b/docs/test-simple.html new file mode 100644 index 0000000..9e169a5 --- /dev/null +++ b/docs/test-simple.html @@ -0,0 +1,4 @@ + + + +StartStep 10102Step 2ProgressCompleteStep 303 \ No newline at end of file diff --git a/docs/test-simple.svg b/docs/test-simple.svg new file mode 100644 index 0000000..9e169a5 --- /dev/null +++ b/docs/test-simple.svg @@ -0,0 +1,4 @@ + + + +StartStep 10102Step 2ProgressCompleteStep 303 \ No newline at end of file diff --git a/docs/test-slide-output.html b/docs/test-slide-output.html new file mode 100644 index 0000000..c914615 --- /dev/null +++ b/docs/test-slide-output.html @@ -0,0 +1,402 @@ + + + + +DX와 BIM의 개념적 구분과 재정립 + + + + + +
+ +
DX와 BIM의 개념적 구분과 재정립
+ + + +
+ +
+
건설산업의 디지털 전환 논의에서 DX와 BIM이 개념적으로 명확히 정립되지 않은 채 혼용되어 사용되고 있으며, BIM 기술의 도입을 DX의 완성으로 오인하거나 DX를 BIM 기술 도입 수준으로 한정하는 인식이 확산되고 있다.
+ +
+ + +
+ +
+ +
+ +
+ +
제7차 건설기술진흥 기본계획
+ 국토교통부, 2023 +
추진방향: 디지털 전환을 통한 스마트 건설 확산 +추진과제: BIM 도입으로 건설산업 디지털화
+ +
+ +
+ +
스마트 건설 활성화 방안
+ 국토교통부, 2022 +
추진과제: 건설산업 디지털화 +세부내용: BIM 전면 도입 및 제도 정비, BIM 전문인력 양성
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
BIM
+ 디지털 전환 핵심 기술 +
시설물 생애주기 정보를 3D 모델 기반으로 통합·관리하는 인프라 기술
+
건설산업 BIM 기본지침, 국토교통부, 2020
+
+ +
+ +
건설산업
+ 종합산업 +
다양한 시설물을 광범위한 기술을 통합·융합하여 만들어내는 종합산업
+ +
+ +
+ +
DX
+ 산업 패러다임 변화 +
디지털 기술 기반으로 업무방식과 가치 창출 구조를 전환하는 과정 및 결과
+
IBM, 2011 / Agile Elephant, 2015
+
+ +
+ + +
+ + + +
+ + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b12dabc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1054 @@ +{ + "name": "design_agent", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "design_agent", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@antv/infographic": "^0.2.16", + "infographic-cli": "^0.5.1" + } + }, + "node_modules/@antv/event-emitter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@antv/event-emitter/-/event-emitter-0.1.3.tgz", + "integrity": "sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg==", + "license": "MIT" + }, + "node_modules/@antv/expr": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@antv/expr/-/expr-1.0.2.tgz", + "integrity": "sha512-vrfdmPHkTuiS5voVutKl2l06w1ihBh9A8SFdQPEE+2KMVpkymzGOF1eWpfkbGZ7tiFE15GodVdhhHomD/hdIwg==", + "license": "MIT" + }, + "node_modules/@antv/graphlib": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@antv/graphlib/-/graphlib-2.0.4.tgz", + "integrity": "sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3" + } + }, + "node_modules/@antv/hierarchy": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@antv/hierarchy/-/hierarchy-0.7.1.tgz", + "integrity": "sha512-7r22r+HxfcRZp79ZjGmsn97zgC1Iajrv0Mm9DIgx3lPfk+Kme2MG/+EKdZj1iEBsN0rJRzjWVPGL5YrBdVHchw==", + "license": "MIT" + }, + "node_modules/@antv/infographic": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/@antv/infographic/-/infographic-0.2.16.tgz", + "integrity": "sha512-PmrQ4Wb1MOlr4wxYzBfWhtKKWwXMS6r/JNEjoH6ZYzjchZII9IAw3K1w1S/ZfuUsS7X9rFt7xbY8/tzqHtaJ6A==", + "license": "MIT", + "workspaces": [ + "dev", + "site" + ], + "dependencies": { + "@antv/hierarchy": "^0.7.0", + "@antv/layout": "^2.0.0-beta.0", + "culori": "^4.0.2", + "d3": "^7.9.0", + "eventemitter3": "^5.0.1", + "flru": "^1.0.2", + "linkedom": "^0.18.12", + "lodash-es": "^4.17.21", + "measury": "^0.1.5", + "postcss": "^8.5.6", + "roughjs": "^4.6.6", + "round-polygon": "^0.6.7", + "tinycolor2": "^1.6.0" + } + }, + "node_modules/@antv/layout": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@antv/layout/-/layout-2.0.0.tgz", + "integrity": "sha512-aCZ3UdNc40SfT7meFV7QTADY2HCnc0DShVw56CJNTI6oExUIVU736grPuL5Dhb8/JrVaU4Y83QPN/P7KafBzlw==", + "license": "MIT", + "dependencies": { + "@antv/event-emitter": "^0.1.3", + "@antv/expr": "^1.0.2", + "@antv/graphlib": "^2.0.0", + "@antv/util": "^3.3.2", + "comlink": "^4.4.1", + "d3-force": "^3.0.0", + "d3-force-3d": "^3.0.5", + "d3-octree": "^1.0.2", + "d3-quadtree": "^3.0.1", + "dagre": "^0.8.5", + "ml-matrix": "^6.10.4", + "tslib": "^2.8.1" + } + }, + "node_modules/@antv/util": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/@antv/util/-/util-3.3.11.tgz", + "integrity": "sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "gl-matrix": "^3.3.0", + "tslib": "^2.3.1" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" + }, + "node_modules/culori": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/culori/-/culori-4.0.2.tgz", + "integrity": "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "license": "MIT" + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz", + "integrity": "sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==", + "license": "MIT", + "dependencies": { + "graphlib": "^2.1.8", + "lodash": "^4.17.15" + } + }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/flru": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flru/-/flru-1.0.2.tgz", + "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/gl-matrix": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", + "integrity": "sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==", + "license": "MIT" + }, + "node_modules/graphlib": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.8.tgz", + "integrity": "sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/infographic-cli": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/infographic-cli/-/infographic-cli-0.5.1.tgz", + "integrity": "sha512-1XmFoghkc99XfM5VgDg0wmC80xbPBlcczFlV5AeYsaOIyul/dAzopSdx/tzs/QxJIobCw3kCFuPpnDseMtVt5w==", + "license": "MIT", + "dependencies": { + "@antv/infographic": "^0.2.11", + "chalk": "^5.4.1", + "commander": "^12.1.0" + }, + "bin": { + "ifgc": "dist/cli.js", + "infographic": "dist/cli.js" + }, + "engines": { + "node": "^18.19 || >=20.0" + } + }, + "node_modules/infographic-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-any-array": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.1.tgz", + "integrity": "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==", + "license": "MIT" + }, + "node_modules/linkedom": { + "version": "0.18.12", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.12.tgz", + "integrity": "sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==", + "license": "ISC", + "dependencies": { + "css-select": "^5.1.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^10.0.0", + "uhyphen": "^0.2.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": ">= 2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/measury": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/measury/-/measury-0.1.5.tgz", + "integrity": "sha512-YS9nhEbFECQzM9V7kyYylASFwpMyajeRvVnpmfRnhZZwbl9AfpBW+C/o7YwmIQLjK8XFzPHt0OGR7GwqoDuWjA==", + "license": "MIT" + }, + "node_modules/ml-array-max": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/ml-array-max/-/ml-array-max-1.2.4.tgz", + "integrity": "sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0" + } + }, + "node_modules/ml-array-min": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/ml-array-min/-/ml-array-min-1.2.3.tgz", + "integrity": "sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0" + } + }, + "node_modules/ml-array-rescale": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ml-array-rescale/-/ml-array-rescale-1.3.7.tgz", + "integrity": "sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.0", + "ml-array-max": "^1.2.4", + "ml-array-min": "^1.2.3" + } + }, + "node_modules/ml-matrix": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/ml-matrix/-/ml-matrix-6.12.1.tgz", + "integrity": "sha512-TJ+8eOFdp+INvzR4zAuwBQJznDUfktMtOB6g/hUcGh3rcyjxbz4Te57Pgri8Q9bhSQ7Zys4IYOGhFdnlgeB6Lw==", + "license": "MIT", + "dependencies": { + "is-any-array": "^2.0.1", + "ml-array-rescale": "^1.3.7" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/round-polygon": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/round-polygon/-/round-polygon-0.6.7.tgz", + "integrity": "sha512-h5rNwOEP6+EHeRuZAGI2/HsNdl6UCKIFvQXzQfv7nTICZ4/sIURe51vOmQJgk/I6KW5LjQ87HJFoASKjaIEySQ==", + "license": "MIT" + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e8d8c6b --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "design_agent", + "version": "1.0.0", + "description": "콘텐츠를 시각적으로 구조화된 슬라이드 HTML로 변환하는 독립 에이전트.", + "main": "index.js", + "directories": { + "doc": "docs", + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/keimin86/design_agent.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "bugs": { + "url": "https://github.com/keimin86/design_agent/issues" + }, + "homepage": "https://github.com/keimin86/design_agent#readme", + "dependencies": { + "@antv/infographic": "^0.2.16", + "infographic-cli": "^0.5.1" + } +} diff --git a/src/design_director.py b/src/design_director.py index a536ec4..e6075be 100644 --- a/src/design_director.py +++ b/src/design_director.py @@ -81,11 +81,28 @@ def _load_catalog() -> str: DIRECTOR_PROMPT = """당신은 디자인 팀장이다. 실장이 분석한 꼭지 목록을 받아 레이아웃을 설계한다. ## 역할 +- 실장의 info_structure(정보 구조)와 각 꼭지의 role(flow/reference)을 **반드시 존중**한다 - 각 꼭지에 적합한 블록을 매핑한다 - 전체 공간을 배분하고 겹침을 방지한다 - 각 블록의 글자 수 가이드를 결정한다 - **텍스트는 절대 정리하지 않는다** (텍스트 편집자가 별도로 한다) +## 정보 구조 기반 배치 (가장 중요한 규칙) +실장이 각 꼭지에 role을 부여했다. 이 role에 따라 배치 영역이 결정된다: +- **role: "flow"** (본문 흐름) → 좌측 또는 메인 영역에 배치. 위→아래 순서대로. +- **role: "reference"** (참조 정보) → 우측 사이드 영역에 독립 배치. 본문 흐름과 분리. +- **detail_target: true** (상세 내용) → 본문에 넣지 않는다. popup/자세히보기로 분리. + +배치 예시: +- 본문 흐름(flow) 꼭지 3개 + 참조(reference) 꼭지 1개 → 좌측에 flow 3개, 우측에 reference 1개 +- 모든 꼭지가 flow → 단일 컬럼 또는 균등 분할 +- detail_target 꼭지 → 해당 블록에 연결된 별도 영역 (현재 블록 없으면 생략) + +## 중복 방지 규칙 +- 같은 내용이 두 개 블록에 나오면 안 된다 +- 예: 용어 정의가 카드에도 있고 비교 블록에도 있으면 → 하나만 선택 +- 블록 타입이 다르더라도 같은 내용이면 중복 + ## {catalog} ## 이미지 처리 규칙 @@ -99,12 +116,13 @@ DIRECTOR_PROMPT = """당신은 디자인 팀장이다. 실장이 분석한 꼭 - 공간에 안 들어가면 → 요약 요청 또는 페이지 분리 ## 자세히보기 규칙 -- 너무 구체적/세부적인 내용은 details-block으로 설계 -- 슬라이드 표면: 요약만, 펼치면: 전체 상세 +- detail_target: true인 꼭지는 본문에 넣지 않는다 +- 관련된 블록 근처에 popup/링크로 연결 ## 공간 배분 규칙 - CSS grid-template-areas 형식으로 배치 - 영역명: header, left, right, center, main, footer 등 +- flow 꼭지는 좌측/메인, reference 꼭지는 우측/사이드 - 꼭지끼리 겹치지 않도록 설계 - 각 블록에 대략적 크기 감(small/medium/large) 제시 @@ -155,14 +173,18 @@ async def create_layout_concept( catalog_text = _load_catalog() - # 꼭지 요약 + # 꼭지 요약 (role과 detail_target 포함) topics_summary = [] for t in analysis.get("topics", []): + role = t.get("role", "flow") line = ( f"꼭지 {t['id']}: {t['title']} " - f"[{t.get('layer', '?')}, 강조:{t.get('emphasis', False)}, " + f"[{t.get('layer', '?')}, ROLE:{role}, " + f"강조:{t.get('emphasis', False)}, " f"방향:{t.get('direction', '?')}, 유형:{t.get('content_type', 'text')}]" ) + if t.get("detail_target"): + line += " → ★자세히보기 대상 (본문에 넣지 마라)" if t.get("image_info"): line += f" 이미지:{t['image_info']}" if t.get("table_info"): @@ -173,14 +195,21 @@ async def create_layout_concept( system = DIRECTOR_PROMPT.replace("{catalog}", catalog_text) + info_structure = analysis.get("info_structure", "정보 구조 미분석") + user_prompt = ( f"## 실장 분석 결과\n" f"제목: {analysis.get('title', '')}\n" f"페이지 수: {analysis.get('total_pages', 1)}\n" + f"정보 구조: {info_structure}\n\n" f"꼭지 목록:\n" + "\n".join(topics_summary) + f"\n\n## 원본 콘텐츠 (분량 참고)\n{content[:2000]}\n\n" f"## 요청\n" - f"위 꼭지를 어떤 블록으로, 어디에, 몇 페이지로 배치할지 설계해줘.\n" + f"위 꼭지를 어떤 블록으로, 어디에 배치할지 설계해줘.\n" + f"반드시 각 꼭지의 ROLE(flow/reference)에 따라 영역을 배정해라.\n" + f"flow → 좌측/메인, reference → 우측/사이드.\n" + f"detail_target → 본문에 넣지 마라.\n" + f"같은 내용이 두 블록에 중복되면 안 된다.\n" f"텍스트는 채우지 마. 구조만 JSON으로." ) diff --git a/src/kei_client.py b/src/kei_client.py index b0c59ab..29ca6bb 100644 --- a/src/kei_client.py +++ b/src/kei_client.py @@ -1,7 +1,7 @@ """DA-12: 1단계 — Kei 실장 (꼭지 추출 + 분석). -본문에서 핵심 꼭지를 추출하고, 각 꼭지의 레이어/강조/배치 방향을 분석한다. -이미지/표/상세 콘텐츠도 판단한다. +1차: Kei API를 통해 Kei persona가 사고하여 꼭지를 추출한다. +fallback: Kei API 실패 시 Anthropic API 직접 호출. """ from __future__ import annotations @@ -11,103 +11,191 @@ import re from typing import Any import anthropic +import httpx from src.config import settings logger = logging.getLogger(__name__) -CLASSIFICATION_PROMPT = """당신은 콘텐츠를 분석하여 슬라이드 구조를 설계하는 실장이다. - -## 역할 -본문에서 핵심 꼭지를 추출하고, 각 꼭지의 성격을 분석하여 슬라이드 구조를 설계한다. - -## 꼭지 추출 규칙 -- 본문에서 2~5개의 핵심 꼭지(파트)를 추출한다 -- 1페이지 적정 꼭지 수: 5개 -- 꼭지가 5개를 넘고 중요도가 동등하면 → 2페이지로 분리 (의미 기반 분할) -- 5개인데 내용이 많으면 → 세부 내용은 "자세히보기" 대상으로 표시 - -## 각 꼭지 분석 항목 -1. **레이어 수준**: 도입(문제 제기, 배경) / 핵심(핵심 내용, 정의) / 보조(사례, 근거) / 결론(요약, 핵심 메시지) -2. **강조**: 눈에 띄게 해야 하는 꼭지 표시 (true/false) -3. **배치 방향**: 세로로 긴 내용(vertical) / 가로로 나열(horizontal) / 유연(flexible) -4. **콘텐츠 유형**: text(텍스트) / image(이미지) / table(표) / mixed(혼합) -5. **이미지 정보** (이미지가 있는 경우): - - 핵심인지 보조인지 (core/supplementary) - - 텍스트 포함 여부 (도표/차트는 true) -6. **표 정보** (표가 있는 경우): - - 대략적 행/열 수 - - 전체 표시 가능한지 판단 -7. **자세히보기 대상**: 너무 구체적/세부적인 내용은 detail_target: true - -## 출력 형식 (반드시 JSON만. 설명 없이.) -```json -{ - "title": "슬라이드 제목", - "total_pages": 1, - "topics": [ - { - "id": 1, - "title": "꼭지 제목", - "summary": "꼭지 내용 요약 (1~2줄)", - "layer": "intro|core|supporting|conclusion", - "emphasis": true, - "direction": "vertical|horizontal|flexible", - "content_type": "text|image|table|mixed", - "image_info": {"role": "core|supplementary", "has_text": true}, - "table_info": {"rows": 5, "cols": 3, "fits_page": true}, - "detail_target": false, - "page": 1 - } - ] -} -```""" +KEI_PROMPT = ( + "다음 콘텐츠를 슬라이드로 정리하려고 해.\n\n" + "## 1단계: 정보 구조 파악 (꼭지 추출 전에 먼저 수행)\n" + "- 이 콘텐츠가 하나의 흐름으로 읽히는가, 아니면 '본문 흐름'과 '참조 정보'로 분리되는 구조인가?\n" + "- 독립적으로 참조되는 정보(용어 정의, 부록, 별도 설명)가 있는가?\n" + "- 상세히 다뤄야 하지만 본문 흐름을 끊는 내용(비교표, 상세 데이터)이 있는가?\n" + "- 정보 구조를 info_structure 필드에 기술해줘.\n\n" + "## 2단계: 꼭지 추출\n" + "- 1단계에서 파악한 정보 구조를 바탕으로 꼭지를 나눠줘\n" + "- 원본의 논리 흐름과 정보를 빠뜨리지 마라\n" + "- 원본에 있는 내용을 임의로 제거하거나 다른 의미로 바꾸지 마라\n" + "- 슬라이드에 맞게 정리하되, 원본이 말하려는 흐름은 유지\n" + "- 각 꼭지의 레이어(도입/핵심/보조/결론), 강조 여부, 배치 방향을 판단해줘\n" + "- 참조 정보는 role: 'reference'로, 본문 흐름은 role: 'flow'로 표시\n" + "- 본문 흐름을 끊는 상세 내용은 detail_target: true로 표시\n" + "- 이미지/표가 있으면 그것도 판단해줘\n" + "- 1페이지 적정 꼭지: 5개. 초과 시 2페이지 분리.\n\n" + "## 출력 형식 (JSON만)\n" + "```json\n" + '{"title": "제목", "total_pages": 1, ' + '"info_structure": "이 콘텐츠의 정보 구조 설명 (본문 흐름 vs 참조 분리 등)", ' + '"topics": [' + '{"id": 1, "title": "꼭지 제목", "summary": "요약", ' + '"layer": "intro|core|supporting|conclusion", ' + '"role": "flow|reference", ' + '"emphasis": true, "direction": "vertical|horizontal|flexible", ' + '"content_type": "text|image|table|mixed", ' + '"detail_target": false, "page": 1}]}\n' + "```\n\n" + "## 콘텐츠:\n" +) async def classify_content(content: str) -> dict[str, Any] | None: - """1단계: 본문에서 꼭지를 추출하고 분석한다. + """1단계: Kei API를 통해 꼭지를 추출하고 분석한다. - Args: - content: 원본 텍스트 콘텐츠 - - Returns: - 분류 결과 JSON. 실패 시 None. + 1차: Kei API (persona + RAG + 사고) + fallback: Anthropic API 직접 호출 """ - if not settings.anthropic_api_key: - logger.warning("ANTHROPIC_API_KEY 미설정. 수동 분류 모드.") - return None + # 1차: Kei API + result = await _call_kei_api(content) + if result: + logger.info( + f"[Kei API] 꼭지 추출 완료: {result.get('title', '')}, " + f"{len(result.get('topics', []))}개 꼭지" + ) + return result + + # fallback: Anthropic 직접 + logger.warning("Kei API 실패. Anthropic 직접 호출로 fallback.") + result = await _call_anthropic_direct(content) + if result: + logger.info( + f"[Anthropic] 꼭지 추출 완료: {result.get('title', '')}, " + f"{len(result.get('topics', []))}개 꼭지" + ) + return result + + return None + + +async def _call_kei_api(content: str) -> dict[str, Any] | None: + """Kei API를 통해 꼭지 추출. SSE 스트리밍 응답을 파싱.""" + kei_url = getattr(settings, "kei_api_url", "http://localhost:8000") try: - client = anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key) - - response = await client.messages.create( - model="claude-sonnet-4-20250514", - max_tokens=2048, - system=CLASSIFICATION_PROMPT, - messages=[ - { - "role": "user", - "content": f"다음 콘텐츠를 분석하여 꼭지를 추출하고 구조를 설계해줘:\n\n{content}", - } - ], - ) - - result_text = response.content[0].text - analysis = _parse_json(result_text) - - if analysis and "topics" in analysis: - logger.info( - f"꼭지 추출 완료: {analysis.get('title', 'untitled')}, " - f"{len(analysis['topics'])}개 꼭지, " - f"{analysis.get('total_pages', 1)}페이지" + async with httpx.AsyncClient(timeout=None) as client: + response = await client.post( + f"{kei_url}/api/message", + json={ + "message": KEI_PROMPT + content, + "session_id": "design-agent", + "mode": "chat", + }, + timeout=None, ) - return analysis - else: - logger.warning(f"분류 JSON 파싱 실패. 응답: {result_text[:200]}") + + if response.status_code != 200: + logger.warning(f"Kei API HTTP {response.status_code}") + return None + + # SSE 응답에서 토큰 수집 + full_text = _extract_sse_text(response.text) + + if not full_text: + logger.warning("Kei API 응답에서 텍스트 추출 실패") + return None + + # JSON 추출 + result = _parse_json(full_text) + if result and "topics" in result: + return result + + logger.warning(f"Kei API JSON 파싱 실패. 텍스트: {full_text[:200]}") return None except Exception as e: - logger.warning(f"실장 분류 호출 실패: {e}") + logger.warning(f"Kei API 호출 실패: {e}") + return None + + +def _extract_sse_text(raw: str) -> str: + """SSE 응답에서 토큰 텍스트를 수집한다. CRLF/LF 모두 처리.""" + tokens = [] + # CRLF 또는 LF로 이벤트 분리 + events = re.split(r'\r?\n\r?\n', raw) + + for event in events: + if not event.strip(): + continue + + event_type = "" + event_data = "" + + for line in event.split('\n'): + line = line.strip('\r') + if line.startswith('event:'): + event_type = line[6:].strip() + elif line.startswith('data:'): + event_data = line[5:].strip() + + if not event_data: + continue + + if event_type == 'token': + try: + token = json.loads(event_data) + if isinstance(token, str): + tokens.append(token) + except json.JSONDecodeError: + tokens.append(event_data) + elif event_type == 'done': + break + + return "".join(tokens) + + +async def _call_anthropic_direct(content: str) -> dict[str, Any] | None: + """Anthropic API 직접 호출 (Kei API fallback).""" + if not settings.anthropic_api_key: + return None + + system_prompt = ( + "당신은 콘텐츠를 분석하여 슬라이드 구조를 설계하는 실장이다.\n\n" + "## 핵심 원칙\n" + "- 원본의 논리 흐름과 정보를 빠뜨리지 마라\n" + "- 원본에 있는 내용을 임의로 제거하거나 다른 의미로 바꾸지 마라\n" + "- 슬라이드에 맞게 정리하되, 원본이 말하려는 흐름은 유지\n\n" + "## 꼭지 추출 규칙\n" + "- 본문에서 2~5개의 핵심 꼭지를 추출한다\n" + "- 1페이지 적정 꼭지 수: 5개\n" + "- 초과 시 2페이지 분리\n\n" + "## 출력 형식 (JSON만. 설명 없이.)\n" + '{"title": "제목", "total_pages": 1, "topics": [' + '{"id": 1, "title": "꼭지 제목", "summary": "요약", ' + '"layer": "intro|core|supporting|conclusion", ' + '"emphasis": true, "direction": "vertical|horizontal|flexible", ' + '"content_type": "text", "detail_target": false, "page": 1}]}' + ) + + try: + client = anthropic.AsyncAnthropic(api_key=settings.anthropic_api_key) + response = await client.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=2048, + system=system_prompt, + messages=[{"role": "user", "content": f"다음 콘텐츠의 꼭지를 추출해줘:\n\n{content}"}], + ) + + result_text = response.content[0].text + result = _parse_json(result_text) + + if result and "topics" in result: + return result + + return None + + except Exception as e: + logger.warning(f"Anthropic 직접 호출 실패: {e}") return None @@ -129,7 +217,7 @@ def _parse_json(text: str) -> dict[str, Any] | None: def manual_classify(content: str) -> dict[str, Any]: - """실장 분류 실패 시 기본 구조를 반환하는 fallback.""" + """분류 실패 시 기본 구조 fallback.""" return { "title": "슬라이드", "total_pages": 1, diff --git a/src/pipeline.py b/src/pipeline.py index 4007c19..2b8cad3 100644 --- a/src/pipeline.py +++ b/src/pipeline.py @@ -10,6 +10,7 @@ from __future__ import annotations import json import logging +import re from typing import Any, AsyncIterator import anthropic diff --git a/templates/catalog.yaml b/templates/catalog.yaml new file mode 100644 index 0000000..3964452 --- /dev/null +++ b/templates/catalog.yaml @@ -0,0 +1,260 @@ +# Design Agent 블록 카탈로그 +# 디자인 팀장이 콘텐츠에 적합한 블록을 선택할 때 참조하는 메뉴판 +# +# 규칙: +# - when: 이 블록을 선택해야 하는 상황 +# - not_for: 이 블록을 선택하면 안 되는 상황 (유사 블록과 구분 핵심) +# - slots.required: 반드시 채워야 하는 슬롯 +# - slots.optional: 있으면 좋지만 없어도 됨 +# - character_limits: 팀장의 글자 수 가이드 참고용 (하드코딩 기준 아님, 판단의 참고치) + +version: "1.0" + +blocks: + + # ────────────────────────────────────── + # 1. 강조 인용 (quote-block) + # ────────────────────────────────────── + - id: quote-block + name: 강조 인용 + template: blocks/quote-block.html + visual: "좌측 컬러 라인(빨간색) + 연한 배경 + 인용 텍스트. 출처 표기 가능." + when: > + 문제 제기, 핵심 주장, 정의 강조할 때. + 원문에서 따온 중요한 문장을 부각시킬 때. + 슬라이드 상단에 배치하여 이슈를 먼저 던질 때. + not_for: > + 일반 설명문 (일반 텍스트는 card-grid이나 본문 영역 사용). + 사례 나열 (출처가 있는 사례는 example-card 사용). + 결론 요약 (하단 결론은 conclusion-bar 사용). + slots: + required: + - quote_text + optional: + - source + character_limits: + quote_text: 150 + source: 50 + + # ────────────────────────────────────── + # 2. 카드 그리드 (card-grid) + # ────────────────────────────────────── + - id: card-grid + name: 카드 그리드 + template: blocks/card-grid.html + visual: "2~4열 카드 나란히. 각 카드 상단에 컬러 액센트 라인. 아이콘/제목/카테고리/설명/출처." + when: > + 용어를 여러 개 정의할 때 (2~4개). + 개념/기능/특성을 나란히 비교 없이 나열할 때. + 각 항목에 제목+설명+출처가 있을 때. + not_for: > + A vs B 직접 비교 (comparison 사용). + 출처가 있는 정책 사례 인용 (example-card 사용, 향후 추가). + 기능 목록이 아이콘 중심일 때 (icon-list 사용, 향후 추가). + 순서가 있는 항목 (process 사용). + slots: + required: + - "cards[]" + optional: [] + card_fields: + required: + - title + - description + optional: + - icon + - category + - color + - source + character_limits: + title: 20 + category: 15 + description: 80 + source: 40 + + # ────────────────────────────────────── + # 3. 비교 (comparison) + # ────────────────────────────────────── + - id: comparison + name: 2단 비교 + template: blocks/comparison.html + visual: "좌우 2단 병렬. 중앙 세로 구분선. 좌측은 파란 액센트, 우측은 빨간 액센트." + when: > + A vs B 직접 비교할 때. + 장단점, Before/After, 현재/미래를 대비할 때. + 두 개념의 차이를 명확히 보여줄 때. + not_for: > + 3개 이상 항목 비교 (comparison-table 사용). + 비교가 아닌 단순 나열 (card-grid 사용). + 수치 비교 (big-number 사용, 향후 추가). + slots: + required: + - left_title + - left_content + - right_title + - right_content + optional: + - left_subtitle + - right_subtitle + character_limits: + left_title: 15 + right_title: 15 + left_content: 200 + right_content: 200 + left_subtitle: 30 + right_subtitle: 30 + + # ────────────────────────────────────── + # 4. 관계도 (relationship) + # ────────────────────────────────────── + - id: relationship + name: 관계도 (벤 다이어그램) + template: blocks/relationship.html + visual: "큰 원(상위 개념) 안에 작은 원 2~3개(하위 요소). 하단에 관계 설명." + when: > + 상위-하위 관계, 포함 관계를 시각화할 때. + 기술 융합/통합 구조를 보여줄 때 (예: DX = GIS + BIM + DT). + 2~3개 요소가 하나의 큰 개념에 속함을 보여줄 때. + not_for: > + 순서가 있는 흐름 (process 사용). + 대등한 비교 (comparison 사용). + 4개 이상 요소 나열 (card-grid 사용). + slots: + required: + - center_label + - "items[]" + optional: + - center_sub + - description + item_fields: + required: + - label + optional: + - color + character_limits: + center_label: 15 + center_sub: 20 + item_label: 10 + description: 100 + + # ────────────────────────────────────── + # 5. 프로세스 (process) + # ────────────────────────────────────── + - id: process + name: 단계 흐름 + template: blocks/process.html + visual: "가로 방향 단계. 각 단계는 번호 원형 + 제목 + 설명. 단계 사이에 → 화살표." + when: > + 순서가 있는 절차를 보여줄 때 (1→2→3→4). + 워크플로우, 실행 단계, 파이프라인. + 시간 순서가 아닌 논리적 순서. + not_for: > + 시간 기반 순서 (timeline 사용, 향후 추가). + 순서 없는 항목 나열 (card-grid 사용). + 2개 비교 (comparison 사용). + slots: + required: + - "steps[]" + optional: [] + step_fields: + required: + - title + optional: + - number + - description + character_limits: + title: 15 + description: 60 + + # ────────────────────────────────────── + # 6. 결론 바 (conclusion-bar) + # ────────────────────────────────────── + - id: conclusion-bar + name: 결론 바 + template: blocks/conclusion-bar.html + visual: "전체 너비 진한 배경(primary색). 중앙 정렬 텍스트. 라벨 + 핵심 한 줄." + when: > + 슬라이드 하단에 핵심 메시지를 한 줄로 마무리할 때. + 전체 내용의 결론, 요약, 시사점을 강조할 때. + 항상 슬라이드 맨 아래에 배치. + not_for: > + 본문 중간의 강조 (quote-block 사용). + 여러 줄의 요약 (quote-block 또는 card-grid 사용). + slots: + required: + - conclusion_text + optional: + - label + character_limits: + conclusion_text: 80 + label: 10 + + # ────────────────────────────────────── + # 7. 비교 테이블 (comparison-table) + # ────────────────────────────────────── + - id: comparison-table + name: 비교 테이블 + template: blocks/comparison-table.html + visual: "표 형태. 진한 배경 헤더 행. 짝수 행 배경 교차. 첫 열 굵은 글씨." + when: > + 3개 이상 항목을 여러 기준으로 비교할 때. + 다차원 비교, 기능 매트릭스, 스펙 비교. + 행/열이 5개 이상인 구조화된 데이터. + not_for: > + 2개 항목 비교 (comparison 사용). + 정의/설명 나열 (card-grid 사용). + 순서가 있는 데이터 (process 또는 timeline 사용). + slots: + required: + - "headers[]" + - "rows[][]" + optional: [] + character_limits: + header: 15 + cell: 40 + +# ────────────────────────────────────── +# 레이아웃 옵션 +# ────────────────────────────────────── +layouts: + + - id: "65-35" + name: "6.5:3.5 좌우 분할" + grid_columns: "6.5fr 3.5fr" + when: "좌측 메인 콘텐츠(인용+사례+이미지) + 우측 보조(정의/용어)" + + - id: "50-50" + name: "5:5 균등 분할" + grid_columns: "1fr 1fr" + when: "대등한 비교, 병렬 콘텐츠" + + - id: "single" + name: "단일 컬럼" + grid_columns: "1fr" + when: "프로세스 흐름, 타임라인, 단순 구조, 블록이 1~2개일 때" + + - id: "35-65" + name: "3.5:6.5 좌우 분할" + grid_columns: "3.5fr 6.5fr" + when: "좌측 요약/네비게이션 + 우측 메인 콘텐츠" + + - id: "40-60" + name: "4:6 좌우 분할" + grid_columns: "4fr 6fr" + when: "좌측 설명/정의 + 우측 시각화(관계도/이미지)" + + - id: "60-40" + name: "6:4 좌우 분할" + grid_columns: "6fr 4fr" + when: "좌측 메인 텍스트 + 우측 보조 정보" + +# ────────────────────────────────────── +# 향후 추가 예정 블록 (Figma 추출 후) +# ────────────────────────────────────── +# - example-card: 출처+불릿 사례 카드 +# - image-block: 이미지 블록 (full/side/thumb) +# - image-gallery: 이미지 갤러리 (2col/3col/2x2) +# - timeline: 타임라인 (vertical/horizontal) +# - big-number: 핵심 지표 (큰 숫자) +# - icon-list: 아이콘 리스트 +# - section-title: 섹션 타이틀 (영문+한글) +# - details-block: 자세히보기 (
/)