@nine-lab/nine-fab 0.1.6 → 0.1.8

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/nine-fab.js CHANGED
@@ -6,150 +6,155 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
6
6
  var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
7
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
8
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
9
- var _connectorUrl, _tipPopup, _routes, _NineChat_instances, init_fn, render_fn, handleFabCommand_fn, _toggleCollapseHandler, _menuClickHandler;
9
+ var _service, _routes, _NineChat_instances, initInteractions_fn, initActions_fn, render_fn;
10
10
  import { trace, nine } from "@nine-lab/nine-util";
11
- import "@nine-lab/nine-ai";
11
+ class NineFabService {
12
+ constructor(connectorUrl) {
13
+ this.connectorUrl = connectorUrl;
14
+ }
15
+ /**
16
+ * AI 소스 생성 실행
17
+ * @param {string} command - 사용자 입력 명령
18
+ * @param {Array} targets - 체크된 생성 대상 (MyBatis, Service 등)
19
+ * @param {Array} currentRoutes - 현재 프로젝트 경로 정보
20
+ */
21
+ async generate(command, targets, currentRoutes) {
22
+ try {
23
+ trace.log(`🚀 Fab 공정 시작: "${command}" [대상: ${targets.join(", ")}]`);
24
+ const response = await fetch(`${this.connectorUrl}/api/fab/generate`, {
25
+ method: "POST",
26
+ headers: { "Content-Type": "application/json" },
27
+ body: JSON.stringify({
28
+ command,
29
+ targets,
30
+ projectType: "spring-react",
31
+ currentRoutes
32
+ })
33
+ });
34
+ const result = await response.json();
35
+ if (!result.success) {
36
+ throw new Error(result.error || "소스 생성 중 서버 오류가 발생했습니다.");
37
+ }
38
+ return result;
39
+ } catch (error) {
40
+ trace.error("❌ Fab Service Error:", error);
41
+ throw error;
42
+ }
43
+ }
44
+ /**
45
+ * 초기 경로 정보 로드
46
+ */
47
+ async fetchRoutes(routeUrl) {
48
+ if (!routeUrl) return [];
49
+ try {
50
+ const res = await fetch(routeUrl);
51
+ return await res.json();
52
+ } catch (err) {
53
+ trace.error("Route load fail", err);
54
+ return [];
55
+ }
56
+ }
57
+ }
12
58
  class NineChat extends HTMLElement {
13
59
  constructor() {
14
60
  super();
15
61
  __privateAdd(this, _NineChat_instances);
16
- __privateAdd(this, _connectorUrl, "");
17
- __privateAdd(this, _tipPopup, null);
62
+ __privateAdd(this, _service, null);
18
63
  __privateAdd(this, _routes, []);
19
- __privateAdd(this, _toggleCollapseHandler, () => {
20
- this.classList.toggle("collapse");
21
- });
22
- __privateAdd(this, _menuClickHandler, (e) => {
23
- this.shadowRoot.querySelectorAll(".menu-icon").forEach((el) => el.classList.remove("active"));
24
- const clickedIcon = e.target.closest(".menu-icon");
25
- if (clickedIcon) clickedIcon.classList.add("active");
26
- this.settings.classList.toggle("expand", !!e.target.closest(".menu-setting"));
27
- });
28
64
  this.attachShadow({ mode: "open" });
29
65
  }
30
66
  async connectedCallback() {
31
- __privateSet(this, _connectorUrl, this.getAttribute("connector-url") || "http://localhost:3002");
67
+ const connectorUrl = this.getAttribute("connector-url") || "http://localhost:3002";
68
+ __privateSet(this, _service, new NineFabService(connectorUrl));
32
69
  __privateMethod(this, _NineChat_instances, render_fn).call(this);
33
- await __privateMethod(this, _NineChat_instances, init_fn).call(this);
70
+ __privateMethod(this, _NineChat_instances, initInteractions_fn).call(this);
71
+ __privateMethod(this, _NineChat_instances, initActions_fn).call(this);
72
+ __privateSet(this, _routes, await __privateGet(this, _service).fetchRoutes(this.getAttribute("route-url")));
34
73
  }
35
74
  }
36
- _connectorUrl = new WeakMap();
37
- _tipPopup = new WeakMap();
75
+ _service = new WeakMap();
38
76
  _routes = new WeakMap();
39
77
  _NineChat_instances = new WeakSet();
40
- init_fn = async function() {
41
- this.shadowRoot.querySelector(".expand-icon").addEventListener("click", __privateGet(this, _toggleCollapseHandler));
42
- this.shadowRoot.querySelector(".collapse-icon").addEventListener("click", __privateGet(this, _toggleCollapseHandler));
43
- this.shadowRoot.querySelectorAll(".menu-icon").forEach((el) => el.addEventListener("click", __privateGet(this, _menuClickHandler)));
44
- const routeUrl = this.getAttribute("route-url");
45
- if (routeUrl) {
46
- try {
47
- const res = await fetch(routeUrl);
48
- __privateSet(this, _routes, await res.json());
49
- trace.log("✅ 현재 프로젝트 경로 분석 완료", __privateGet(this, _routes));
50
- } catch (err) {
51
- trace.error("❌ 경로 로드 실패:", err.message);
78
+ // --- [그룹 1: Interaction] 화면 토글 및 메뉴 클릭 ---
79
+ initInteractions_fn = function() {
80
+ const $settings = this.shadowRoot.querySelector("nine-ai-settings");
81
+ const $menuIcons = this.shadowRoot.querySelectorAll(".menu-icon");
82
+ const toggleUI = () => this.classList.toggle("collapse");
83
+ this.shadowRoot.querySelector(".expand-icon").addEventListener("click", toggleUI);
84
+ this.shadowRoot.querySelector(".collapse-icon").addEventListener("click", toggleUI);
85
+ $menuIcons.forEach((el) => {
86
+ el.addEventListener("click", (e) => {
87
+ $menuIcons.forEach((icon) => icon.classList.remove("active"));
88
+ const clicked = e.target.closest(".menu-icon");
89
+ clicked.classList.add("active");
90
+ $settings.classList.toggle("expand", clicked.classList.contains("menu-setting"));
91
+ });
92
+ });
93
+ };
94
+ // --- [그룹 2: Action] 엔터키 입력 시 서비스 호출 ---
95
+ initActions_fn = function() {
96
+ const $textarea = this.shadowRoot.querySelector("#q");
97
+ const $status = this.shadowRoot.querySelector("#status-tag");
98
+ $textarea.addEventListener("keypress", async (e) => {
99
+ if (e.key === "Enter" && !e.shiftKey) {
100
+ e.preventDefault();
101
+ const command = e.target.value.trim();
102
+ if (!command) return;
103
+ $status.textContent = "⚙️ 공정 분석 중...";
104
+ e.target.value = "";
105
+ const targets = Array.from(this.shadowRoot.querySelectorAll('input[name="gen_target"]:checked')).map((el) => el.value);
106
+ try {
107
+ const result = await __privateGet(this, _service).generate(command, targets, __privateGet(this, _routes));
108
+ $status.textContent = "✅ 완료";
109
+ nine.alert("소스 생성 성공").rgb();
110
+ this.dispatchEvent(new CustomEvent("nine-fab-completed", { detail: result, bubbles: true }));
111
+ } catch (err) {
112
+ $status.textContent = "❌ 실패";
113
+ nine.alert(err.message).rgb().shake();
114
+ }
52
115
  }
53
- }
116
+ });
54
117
  };
55
118
  render_fn = function() {
56
119
  const placeholder = this.getAttribute("placeholder") || "나에게 무엇이든 물어봐...";
57
- const customPath = this.getAttribute("css-path");
58
- const customImport = customPath ? `@import "${customPath}";` : "";
120
+ const customImport = this.getAttribute("css-path") ? `@import "${this.getAttribute("css-path")}";` : "";
59
121
  this.shadowRoot.innerHTML = `
60
122
  <style>
61
- /* Fab 전용 스타일 (nomenu와 차별화 추천) */
62
- @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${"0.1.5"}/dist/css/nine-fab.css";
123
+ @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${"0.1.7"}/dist/css/nine-fab.css";
63
124
  ${customImport}
64
125
  </style>
65
-
66
126
  <div class="wrapper">
67
- <nine-ai-settings></nine-ai-settings>
68
-
69
- <div class="container">
70
- <div class="head">
71
- <div class="logo"><span></span></div>
72
- </div>
73
- <nx-ai-chat></nx-ai-chat>
74
- <div class="foot">
75
- <div class="apply-src">
76
- <div>
77
- <input type="checkbox" id="mybatis" name="mybatis" value="Y" checked />
78
- <label for="mybatis">MyBatis</label>
79
- </div>
80
- <div>
81
- <input type="checkbox" id="service" name="service" value="Y" checked />
82
- <label for="service">Service</label>
83
- </div>
84
- <div>
85
- <input type="checkbox" id="controller" name="controller" value="Y" checked />
86
- <label for="controller">Controller</label>
87
- </div>
88
- <div>
89
- <input type="checkbox" id="javascript" name="javascript" value="Y" checked />
90
- <label for="javascript">JavaScript</label>
91
- </div>
92
- </div>
93
- <textarea name="ask" id="q" name="q" rows="4" placeholder="${placeholder}"></textarea>
94
- </div>
95
- </div>
96
- <div class="menu">
97
- <div class="collapse-icon"></div>
98
- <div class="menu-icon menu-filter active"></div>
99
- <div class="menu-icon menu-general"></div>
100
- <div class="menu-icon menu-setting"></div>
101
- </div>
102
- </div>
103
-
104
- <div class="expand-icon"></div>
127
+ <nine-ai-settings></nine-ai-settings>
128
+ <div class="container">
129
+ <div class="head">
130
+ <div class="logo"><span></span></div>
131
+ <div id="status-tag" style="font-size:10px; color:#ff5722; padding:5px;">Fab Engine Ready</div>
132
+ </div>
133
+ <nx-ai-chat></nx-ai-chat>
134
+ <div class="foot">
135
+ <div class="apply-src">
136
+ ${["MyBatis", "Service", "Controller", "JavaScript"].map((t) => `
137
+ <div>
138
+ <input type="checkbox" id="${t.toLowerCase()}" name="gen_target" value="${t}" checked />
139
+ <label for="${t.toLowerCase()}">${t}</label>
140
+ </div>
141
+ `).join("")}
142
+ </div>
143
+ <textarea id="q" rows="4" placeholder="${placeholder}"></textarea>
144
+ </div>
145
+ </div>
146
+ <div class="menu">
147
+ <div class="collapse-icon"></div>
148
+ <div class="menu-icon menu-filter active"></div>
149
+ <div class="menu-icon menu-general"></div>
150
+ <div class="menu-icon menu-setting"></div>
151
+ </div>
152
+ </div>
153
+ <div class="expand-icon"></div>
105
154
  `;
106
155
  };
107
- handleFabCommand_fn = async function(command) {
108
- var _a, _b;
109
- const statusEl = this.shadowRoot.getElementById("status");
110
- try {
111
- statusEl.textContent = "AI가 설계를 분석 중입니다...";
112
- await ((_a = __privateGet(this, _tipPopup)) == null ? void 0 : _a.popup());
113
- trace.log(`🚀 소스 생성 명령 전송: "${command}"`);
114
- const response = await fetch(`${__privateGet(this, _connectorUrl)}/api/fab/generate`, {
115
- method: "POST",
116
- headers: { "Content-Type": "application/json" },
117
- body: JSON.stringify({
118
- command,
119
- projectType: "spring-react",
120
- // 템플릿 정보
121
- currentRoutes: __privateGet(this, _routes)
122
- // 기존 경로 정보 (참고용)
123
- })
124
- });
125
- const result = await response.json();
126
- if (!result.success) {
127
- throw new Error(result.error || "소스 생성 중 오류가 발생했습니다.");
128
- }
129
- trace.log("✅ 소스 생성 및 파일 주입 완료!", result.files);
130
- statusEl.textContent = "✅ 생성 완료! 잠시 후 화면이 갱신됩니다.";
131
- nine.alert("소스 생성이 완료되었습니다!").rgb();
132
- this.dispatchEvent(new CustomEvent("nine-fab-completed", {
133
- detail: result,
134
- bubbles: true,
135
- composed: true
136
- }));
137
- } catch (error) {
138
- trace.error("Fab 공정 실패:", error);
139
- statusEl.textContent = "❌ 생성 실패";
140
- nine.alert(error.message).rgb().shake();
141
- } finally {
142
- (_b = __privateGet(this, _tipPopup)) == null ? void 0 : _b.close();
143
- }
144
- };
145
- _toggleCollapseHandler = new WeakMap();
146
- _menuClickHandler = new WeakMap();
147
- trace.init("nine-fab", "#FF5722");
148
- if (!customElements.get("nine-chat")) {
149
- customElements.define("nine-chat", NineChat);
150
- }
151
156
  const NineFab = {
152
- version: "0.1.5",
157
+ version: "0.1.7",
153
158
  init: (config) => {
154
159
  trace.log("🛠️ Nine-Fab Engine initialized", config);
155
160
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nine-fab.js","sources":["../src/components/NineChat.js","../src/index.js"],"sourcesContent":["import { nine, trace } from '@nine-lab/nine-util';\nimport { TipPopup } from '@nine-lab/nine-ai';\n\nexport class NineChat extends HTMLElement {\n #connectorUrl = '';\n #tipPopup = null;\n #routes = [];\n\n static {\n // Fab 전용 로깅 컬러로 변경 (색상 차별화)\n trace.init(\"nine-fab\", \"#FF5722\");\n }\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n async connectedCallback() {\n this.#connectorUrl = this.getAttribute('connector-url') || 'http://localhost:3002';\n this.#render();\n await this.#init();\n }\n\n async #init() {\n\n this.shadowRoot.querySelector(\".expand-icon\").addEventListener(\"click\", this.#toggleCollapseHandler);\n this.shadowRoot.querySelector(\".collapse-icon\").addEventListener(\"click\", this.#toggleCollapseHandler);\n\n this.shadowRoot.querySelectorAll(\".menu-icon\").forEach(el => el.addEventListener(\"click\", this.#menuClickHandler));\n\n\n // [중요] Fab 엔진은 route-url을 통해 현재 등록된 전체 메뉴를 파악하여\n // AI가 중복된 경로를 만들지 않게 하거나, 기존 소스를 참고하게 합니다.\n const routeUrl = this.getAttribute('route-url');\n if (routeUrl) {\n try {\n const res = await fetch(routeUrl);\n this.#routes = await res.json();\n trace.log(\"✅ 현재 프로젝트 경로 분석 완료\", this.#routes);\n } catch (err) {\n trace.error(\"❌ 경로 로드 실패:\", err.message);\n }\n }\n }\n\n #render() {\n const placeholder = this.getAttribute(\"placeholder\") || \"나에게 무엇이든 물어봐...\";\n\n const customPath = this.getAttribute(\"css-path\");\n const customImport = customPath ? `@import \"${customPath}\";` : \"\";\n\n this.shadowRoot.innerHTML = `\n <style>\n /* Fab 전용 스타일 (nomenu와 차별화 추천) */\n @import \"https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${__APP_VERSION__}/dist/css/nine-fab.css\";\n ${customImport}\n </style>\n \n <div class=\"wrapper\">\n\t\t\t\t<nine-ai-settings></nine-ai-settings>\n\t\t\t\n\t\t\t\t<div class=\"container\">\n\t\t\t\t\t<div class=\"head\">\n\t\t\t\t\t\t<div class=\"logo\"><span></span></div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<nx-ai-chat></nx-ai-chat>\n\t\t\t\t\t<div class=\"foot\">\n\t\t\t\t\t\t<div class=\"apply-src\">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type=\"checkbox\" id=\"mybatis\" name=\"mybatis\" value=\"Y\" checked />\n\t\t\t\t\t\t\t <label for=\"mybatis\">MyBatis</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type=\"checkbox\" id=\"service\" name=\"service\" value=\"Y\" checked />\n\t\t\t\t\t\t\t <label for=\"service\">Service</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type=\"checkbox\" id=\"controller\" name=\"controller\" value=\"Y\" checked />\n\t\t\t\t\t\t\t <label for=\"controller\">Controller</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type=\"checkbox\" id=\"javascript\" name=\"javascript\" value=\"Y\" checked />\n\t\t\t\t\t\t\t <label for=\"javascript\">JavaScript</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<textarea name=\"ask\" id=\"q\" name=\"q\" rows=\"4\" placeholder=\"${placeholder}\"></textarea>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"menu\">\n\t\t\t\t\t<div class=\"collapse-icon\"></div>\n\t\t\t\t\t<div class=\"menu-icon menu-filter active\"></div>\n\t\t\t\t\t<div class=\"menu-icon menu-general\"></div>\n\t\t\t\t\t<div class=\"menu-icon menu-setting\"></div>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div class=\"expand-icon\"></div>\n `;\n\n /**\n if (customElements.get(\"nine-ai-tip-popup\")) {\n this.#tipPopup = document.createElement('nine-ai-tip-popup');\n this.shadowRoot.appendChild(this.#tipPopup);\n }\n\n const input = this.shadowRoot.querySelector('input');\n input.addEventListener('keypress', (e) => {\n if (e.key === 'Enter') {\n const command = e.target.value.trim();\n if (command) {\n this.#handleFabCommand(command); // 내비게이션 대신 생성 명령 호출\n e.target.value = '';\n }\n }\n }); */\n }\n\n /**\n * [핵심] 소스 코드 생성 및 파일 주입 요청\n */\n async #handleFabCommand(command) {\n const statusEl = this.shadowRoot.getElementById('status');\n try {\n statusEl.textContent = \"AI가 설계를 분석 중입니다...\";\n await this.#tipPopup?.popup();\n\n trace.log(`🚀 소스 생성 명령 전송: \"${command}\"`);\n\n // [변경] nomenu/go -> fab/generate\n const response = await fetch(`${this.#connectorUrl}/api/fab/generate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n command,\n projectType: 'spring-react', // 템플릿 정보\n currentRoutes: this.#routes // 기존 경로 정보 (참고용)\n })\n });\n\n const result = await response.json();\n\n if (!result.success) {\n throw new Error(result.error || \"소스 생성 중 오류가 발생했습니다.\");\n }\n\n trace.log(\"✅ 소스 생성 및 파일 주입 완료!\", result.files);\n statusEl.textContent = \"✅ 생성 완료! 잠시 후 화면이 갱신됩니다.\";\n\n nine.alert(\"소스 생성이 완료되었습니다!\").rgb();\n\n // [알림] 생성 성공 시 프레임워크에 알림을 줘서 메뉴를 새로고침하게 함\n this.dispatchEvent(new CustomEvent('nine-fab-completed', {\n detail: result,\n bubbles: true,\n composed: true\n }));\n\n } catch (error) {\n trace.error(\"Fab 공정 실패:\", error);\n statusEl.textContent = \"❌ 생성 실패\";\n nine.alert(error.message).rgb().shake();\n } finally {\n this.#tipPopup?.close();\n }\n }\n\n #toggleCollapseHandler = () => {\n this.classList.toggle(\"collapse\");\n };\n\n #menuClickHandler = (e) => {\n\n // 모든 `.menu-icon`에서 `active` 클래스 제거\n this.shadowRoot.querySelectorAll(\".menu-icon\").forEach(el => el.classList.remove(\"active\"));\n\n // 클릭한 `.menu-icon`에 `active` 클래스 추가\n const clickedIcon = e.target.closest(\".menu-icon\");\n if (clickedIcon) clickedIcon.classList.add(\"active\");\n\n // `.menu-setting`이 클릭되었는지 확인 후 `nx-ai-settings` 토글\n this.settings.classList.toggle(\"expand\", !!e.target.closest(\".menu-setting\"));\n };\n}\n\n// 1. 웹컴포넌트 등록\nif (!customElements.get('nine-chat')) {\n customElements.define('nine-chat', NineChat);\n}","import { trace } from '@nine-lab/nine-util';\nimport { NineChat } from './components/NineChat.js';\n\n/**\n * Nine-Fab 엔진 메인 클래스\n */\nexport const NineFab = {\n version: typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.1.0',\n init: (config) => {\n trace.log(\"🛠️ Nine-Fab Engine initialized\", config);\n // 향후 커넥터 URL 전역 설정이나 인증 토큰 처리 로직 추가 가능\n }\n};\n\n// 기본 export 및 컴포넌트 export\nexport default NineFab;\nexport { NineChat };"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"nine-fab.js","sources":["../src/services/NineFabService.js","../src/components/NineChat.js","../src/index.js"],"sourcesContent":["import { nine, trace } from '@nine-lab/nine-util';\n\nexport class NineFabService {\n constructor(connectorUrl) {\n this.connectorUrl = connectorUrl;\n }\n\n /**\n * AI 소스 생성 실행\n * @param {string} command - 사용자 입력 명령\n * @param {Array} targets - 체크된 생성 대상 (MyBatis, Service 등)\n * @param {Array} currentRoutes - 현재 프로젝트 경로 정보\n */\n async generate(command, targets, currentRoutes) {\n try {\n trace.log(`🚀 Fab 공정 시작: \"${command}\" [대상: ${targets.join(', ')}]`);\n\n const response = await fetch(`${this.connectorUrl}/api/fab/generate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n command,\n targets,\n projectType: 'spring-react',\n currentRoutes\n })\n });\n\n const result = await response.json();\n\n if (!result.success) {\n throw new Error(result.error || \"소스 생성 중 서버 오류가 발생했습니다.\");\n }\n\n return result;\n } catch (error) {\n trace.error(\"❌ Fab Service Error:\", error);\n throw error;\n }\n }\n\n /**\n * 초기 경로 정보 로드\n */\n async fetchRoutes(routeUrl) {\n if (!routeUrl) return [];\n try {\n const res = await fetch(routeUrl);\n return await res.json();\n } catch (err) {\n trace.error(\"Route load fail\", err);\n return [];\n }\n }\n}","import { nine, trace } from '@nine-lab/nine-util';\nimport { NineFabService } from '../services/NineFabService.js';\n\nexport class NineChat extends HTMLElement {\n #service = null;\n #routes = [];\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n async connectedCallback() {\n // 서비스 초기화\n const connectorUrl = this.getAttribute('connector-url') || 'http://localhost:3002';\n this.#service = new NineFabService(connectorUrl);\n\n this.#render();\n this.#initInteractions(); // UI 토글 로직\n this.#initActions(); // 엔터 시 실행 로직\n\n // 경로 데이터 로드\n this.#routes = await this.#service.fetchRoutes(this.getAttribute('route-url'));\n }\n\n // --- [그룹 1: Interaction] 화면 토글 및 메뉴 클릭 ---\n #initInteractions() {\n const $settings = this.shadowRoot.querySelector('nine-ai-settings');\n const $menuIcons = this.shadowRoot.querySelectorAll(\".menu-icon\");\n\n const toggleUI = () => this.classList.toggle(\"collapse\");\n this.shadowRoot.querySelector(\".expand-icon\").addEventListener(\"click\", toggleUI);\n this.shadowRoot.querySelector(\".collapse-icon\").addEventListener(\"click\", toggleUI);\n\n $menuIcons.forEach(el => {\n el.addEventListener(\"click\", (e) => {\n $menuIcons.forEach(icon => icon.classList.remove(\"active\"));\n const clicked = e.target.closest(\".menu-icon\");\n clicked.classList.add(\"active\");\n $settings.classList.toggle(\"expand\", clicked.classList.contains('menu-setting'));\n });\n });\n }\n\n // --- [그룹 2: Action] 엔터키 입력 시 서비스 호출 ---\n #initActions() {\n const $textarea = this.shadowRoot.querySelector('#q');\n const $status = this.shadowRoot.querySelector('#status-tag');\n\n $textarea.addEventListener('keypress', async (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n const command = e.target.value.trim();\n if (!command) return;\n\n // 1. UI 피드백\n $status.textContent = \"⚙️ 공정 분석 중...\";\n e.target.value = '';\n\n // 2. 체크된 타겟 수집\n const targets = Array.from(this.shadowRoot.querySelectorAll('input[name=\"gen_target\"]:checked'))\n .map(el => el.value);\n\n // 3. 분리된 서비스 호출\n try {\n const result = await this.#service.generate(command, targets, this.#routes);\n\n // 4. 성공 처리\n $status.textContent = \"✅ 완료\";\n nine.alert(\"소스 생성 성공\").rgb();\n this.dispatchEvent(new CustomEvent('nine-fab-completed', { detail: result, bubbles: true }));\n } catch (err) {\n // 5. 실패 처리\n $status.textContent = \"❌ 실패\";\n nine.alert(err.message).rgb().shake();\n }\n }\n });\n }\n\n #render() {\n const placeholder = this.getAttribute(\"placeholder\") || \"나에게 무엇이든 물어봐...\";\n const customImport = this.getAttribute(\"css-path\") ? `@import \"${this.getAttribute(\"css-path\")}\";` : \"\";\n\n this.shadowRoot.innerHTML = `\n <style>\n @import \"https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${__APP_VERSION__}/dist/css/nine-fab.css\";\n ${customImport}\n </style>\n <div class=\"wrapper\">\n <nine-ai-settings></nine-ai-settings>\n <div class=\"container\">\n <div class=\"head\">\n <div class=\"logo\"><span></span></div>\n <div id=\"status-tag\" style=\"font-size:10px; color:#ff5722; padding:5px;\">Fab Engine Ready</div>\n </div>\n <nx-ai-chat></nx-ai-chat>\n <div class=\"foot\">\n <div class=\"apply-src\">\n ${['MyBatis', 'Service', 'Controller', 'JavaScript'].map(t => `\n <div>\n <input type=\"checkbox\" id=\"${t.toLowerCase()}\" name=\"gen_target\" value=\"${t}\" checked />\n <label for=\"${t.toLowerCase()}\">${t}</label>\n </div>\n `).join('')}\n </div>\n <textarea id=\"q\" rows=\"4\" placeholder=\"${placeholder}\"></textarea>\n </div>\n </div>\n <div class=\"menu\">\n <div class=\"collapse-icon\"></div>\n <div class=\"menu-icon menu-filter active\"></div>\n <div class=\"menu-icon menu-general\"></div>\n <div class=\"menu-icon menu-setting\"></div>\n </div>\n </div>\n <div class=\"expand-icon\"></div>\n `;\n }\n}","import { trace } from '@nine-lab/nine-util';\nimport { NineChat } from './components/NineChat.js';\n\n/**\n * Nine-Fab 엔진 메인 클래스\n */\nexport const NineFab = {\n version: typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.1.0',\n init: (config) => {\n trace.log(\"🛠️ Nine-Fab Engine initialized\", config);\n // 향후 커넥터 URL 전역 설정이나 인증 토큰 처리 로직 추가 가능\n }\n};\n\n// 기본 export 및 컴포넌트 export\nexport default NineFab;\nexport { NineChat };"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@nine-lab/nine-util"),require("@nine-lab/nine-ai")):"function"==typeof define&&define.amd?define(["exports","@nine-lab/nine-util","@nine-lab/nine-ai"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).NineFab={},t.NineUtil)}(this,function(t,e){"use strict";var n,i,a,s,c,l,o,r,d=t=>{throw TypeError(t)},h=(t,e,n)=>e.has(t)||d("Cannot "+n),v=(t,e,n)=>(h(t,e,"read from private field"),n?n.call(t):e.get(t)),u=(t,e,n)=>e.has(t)?d("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,n),p=(t,e,n,i)=>(h(t,e,"write to private field"),i?i.call(t,n):e.set(t,n),n),m=(t,e,n)=>(h(t,e,"access private method"),n);class b extends HTMLElement{constructor(){super(),u(this,s),u(this,n,""),u(this,i,null),u(this,a,[]),u(this,o,()=>{this.classList.toggle("collapse")}),u(this,r,t=>{this.shadowRoot.querySelectorAll(".menu-icon").forEach(t=>t.classList.remove("active"));const e=t.target.closest(".menu-icon");e&&e.classList.add("active"),this.settings.classList.toggle("expand",!!t.target.closest(".menu-setting"))}),this.attachShadow({mode:"open"})}async connectedCallback(){p(this,n,this.getAttribute("connector-url")||"http://localhost:3002"),m(this,s,l).call(this),await m(this,s,c).call(this)}}n=new WeakMap,i=new WeakMap,a=new WeakMap,s=new WeakSet,c=async function(){this.shadowRoot.querySelector(".expand-icon").addEventListener("click",v(this,o)),this.shadowRoot.querySelector(".collapse-icon").addEventListener("click",v(this,o)),this.shadowRoot.querySelectorAll(".menu-icon").forEach(t=>t.addEventListener("click",v(this,r)));const t=this.getAttribute("route-url");if(t)try{const n=await fetch(t);p(this,a,await n.json()),e.trace.log(" 현재 프로젝트 경로 분석 완료",v(this,a))}catch(n){e.trace.error("❌ 경로 로드 실패:",n.message)}},l=function(){const t=this.getAttribute("placeholder")||"나에게 무엇이든 물어봐...",e=this.getAttribute("css-path"),n=e?`@import "${e}";`:"";this.shadowRoot.innerHTML=`\n <style>\n /* Fab 전용 스타일 (nomenu와 차별화 추천) */\n @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@0.1.5/dist/css/nine-fab.css";\n ${n}\n </style>\n \n <div class="wrapper">\n\t\t\t\t<nine-ai-settings></nine-ai-settings>\n\t\t\t\n\t\t\t\t<div class="container">\n\t\t\t\t\t<div class="head">\n\t\t\t\t\t\t<div class="logo"><span></span></div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<nx-ai-chat></nx-ai-chat>\n\t\t\t\t\t<div class="foot">\n\t\t\t\t\t\t<div class="apply-src">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type="checkbox" id="mybatis" name="mybatis" value="Y" checked />\n\t\t\t\t\t\t\t <label for="mybatis">MyBatis</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type="checkbox" id="service" name="service" value="Y" checked />\n\t\t\t\t\t\t\t <label for="service">Service</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type="checkbox" id="controller" name="controller" value="Y" checked />\n\t\t\t\t\t\t\t <label for="controller">Controller</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type="checkbox" id="javascript" name="javascript" value="Y" checked />\n\t\t\t\t\t\t\t <label for="javascript">JavaScript</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<textarea name="ask" id="q" name="q" rows="4" placeholder="${t}"></textarea>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class="menu">\n\t\t\t\t\t<div class="collapse-icon"></div>\n\t\t\t\t\t<div class="menu-icon menu-filter active"></div>\n\t\t\t\t\t<div class="menu-icon menu-general"></div>\n\t\t\t\t\t<div class="menu-icon menu-setting"></div>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div class="expand-icon"></div>\n `},o=new WeakMap,r=new WeakMap,e.trace.init("nine-fab","#FF5722"),customElements.get("nine-chat")||customElements.define("nine-chat",b);const f={version:"0.1.5",init:t=>{e.trace.log("🛠️ Nine-Fab Engine initialized",t)}};t.NineChat=b,t.NineFab=f,t.default=f,Object.defineProperties(t,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@nine-lab/nine-util")):"function"==typeof define&&define.amd?define(["exports","@nine-lab/nine-util"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).NineFab={},e.NineUtil)}(this,function(e,t){"use strict";var n,i,a,s,o,r,c=e=>{throw TypeError(e)},l=(e,t,n)=>t.has(e)||c("Cannot "+n),d=(e,t,n)=>(l(e,t,"read from private field"),n?n.call(e):t.get(e)),h=(e,t,n)=>t.has(e)?c("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),u=(e,t,n,i)=>(l(e,t,"write to private field"),i?i.call(e,n):t.set(e,n),n),p=(e,t,n)=>(l(e,t,"access private method"),n);class v{constructor(e){this.connectorUrl=e}async generate(e,n,i){try{t.trace.log(`🚀 Fab 공정 시작: "${e}" [대상: ${n.join(", ")}]`);const a=await fetch(`${this.connectorUrl}/api/fab/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({command:e,targets:n,projectType:"spring-react",currentRoutes:i})}),s=await a.json();if(!s.success)throw new Error(s.error||"소스 생성 중 서버 오류가 발생했습니다.");return s}catch(a){throw t.trace.error("❌ Fab Service Error:",a),a}}async fetchRoutes(e){if(!e)return[];try{const t=await fetch(e);return await t.json()}catch(n){return t.trace.error("Route load fail",n),[]}}}class f extends HTMLElement{constructor(){super(),h(this,a),h(this,n,null),h(this,i,[]),this.attachShadow({mode:"open"})}async connectedCallback(){const e=this.getAttribute("connector-url")||"http://localhost:3002";u(this,n,new v(e)),p(this,a,r).call(this),p(this,a,s).call(this),p(this,a,o).call(this),u(this,i,await d(this,n).fetchRoutes(this.getAttribute("route-url")))}}n=new WeakMap,i=new WeakMap,a=new WeakSet,s=function(){const e=this.shadowRoot.querySelector("nine-ai-settings"),t=this.shadowRoot.querySelectorAll(".menu-icon"),n=()=>this.classList.toggle("collapse");this.shadowRoot.querySelector(".expand-icon").addEventListener("click",n),this.shadowRoot.querySelector(".collapse-icon").addEventListener("click",n),t.forEach(n=>{n.addEventListener("click",n=>{t.forEach(e=>e.classList.remove("active"));const i=n.target.closest(".menu-icon");i.classList.add("active"),e.classList.toggle("expand",i.classList.contains("menu-setting"))})})},o=function(){const e=this.shadowRoot.querySelector("#q"),a=this.shadowRoot.querySelector("#status-tag");e.addEventListener("keypress",async e=>{if("Enter"===e.key&&!e.shiftKey){e.preventDefault();const o=e.target.value.trim();if(!o)return;a.textContent="⚙️ 공정 분석 중...",e.target.value="";const r=Array.from(this.shadowRoot.querySelectorAll('input[name="gen_target"]:checked')).map(e=>e.value);try{const e=await d(this,n).generate(o,r,d(this,i));a.textContent="✅ 완료",t.nine.alert("소스 생성 성공").rgb(),this.dispatchEvent(new CustomEvent("nine-fab-completed",{detail:e,bubbles:!0}))}catch(s){a.textContent="❌ 실패",t.nine.alert(s.message).rgb().shake()}}})},r=function(){const e=this.getAttribute("placeholder")||"나에게 무엇이든 물어봐...",t=this.getAttribute("css-path")?`@import "${this.getAttribute("css-path")}";`:"";this.shadowRoot.innerHTML=`\n <style>\n @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@0.1.7/dist/css/nine-fab.css";\n ${t}\n </style>\n <div class="wrapper">\n <nine-ai-settings></nine-ai-settings>\n <div class="container">\n <div class="head">\n <div class="logo"><span></span></div>\n <div id="status-tag" style="font-size:10px; color:#ff5722; padding:5px;">Fab Engine Ready</div>\n </div>\n <nx-ai-chat></nx-ai-chat>\n <div class="foot">\n <div class="apply-src">\n ${["MyBatis","Service","Controller","JavaScript"].map(e=>`\n <div>\n <input type="checkbox" id="${e.toLowerCase()}" name="gen_target" value="${e}" checked />\n <label for="${e.toLowerCase()}">${e}</label>\n </div>\n `).join("")}\n </div>\n <textarea id="q" rows="4" placeholder="${e}"></textarea>\n </div>\n </div>\n <div class="menu">\n <div class="collapse-icon"></div>\n <div class="menu-icon menu-filter active"></div>\n <div class="menu-icon menu-general"></div>\n <div class="menu-icon menu-setting"></div>\n </div>\n </div>\n <div class="expand-icon"></div>\n `};const g={version:"0.1.7",init:e=>{t.trace.log("🛠️ Nine-Fab Engine initialized",e)}};e.NineChat=f,e.NineFab=g,e.default=g,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
2
2
  //# sourceMappingURL=nine-fab.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nine-fab.umd.js","sources":["../src/components/NineChat.js","../src/index.js"],"sourcesContent":["import { nine, trace } from '@nine-lab/nine-util';\nimport { TipPopup } from '@nine-lab/nine-ai';\n\nexport class NineChat extends HTMLElement {\n #connectorUrl = '';\n #tipPopup = null;\n #routes = [];\n\n static {\n // Fab 전용 로깅 컬러로 변경 (색상 차별화)\n trace.init(\"nine-fab\", \"#FF5722\");\n }\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n async connectedCallback() {\n this.#connectorUrl = this.getAttribute('connector-url') || 'http://localhost:3002';\n this.#render();\n await this.#init();\n }\n\n async #init() {\n\n this.shadowRoot.querySelector(\".expand-icon\").addEventListener(\"click\", this.#toggleCollapseHandler);\n this.shadowRoot.querySelector(\".collapse-icon\").addEventListener(\"click\", this.#toggleCollapseHandler);\n\n this.shadowRoot.querySelectorAll(\".menu-icon\").forEach(el => el.addEventListener(\"click\", this.#menuClickHandler));\n\n\n // [중요] Fab 엔진은 route-url을 통해 현재 등록된 전체 메뉴를 파악하여\n // AI가 중복된 경로를 만들지 않게 하거나, 기존 소스를 참고하게 합니다.\n const routeUrl = this.getAttribute('route-url');\n if (routeUrl) {\n try {\n const res = await fetch(routeUrl);\n this.#routes = await res.json();\n trace.log(\"✅ 현재 프로젝트 경로 분석 완료\", this.#routes);\n } catch (err) {\n trace.error(\"❌ 경로 로드 실패:\", err.message);\n }\n }\n }\n\n #render() {\n const placeholder = this.getAttribute(\"placeholder\") || \"나에게 무엇이든 물어봐...\";\n\n const customPath = this.getAttribute(\"css-path\");\n const customImport = customPath ? `@import \"${customPath}\";` : \"\";\n\n this.shadowRoot.innerHTML = `\n <style>\n /* Fab 전용 스타일 (nomenu와 차별화 추천) */\n @import \"https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${__APP_VERSION__}/dist/css/nine-fab.css\";\n ${customImport}\n </style>\n \n <div class=\"wrapper\">\n\t\t\t\t<nine-ai-settings></nine-ai-settings>\n\t\t\t\n\t\t\t\t<div class=\"container\">\n\t\t\t\t\t<div class=\"head\">\n\t\t\t\t\t\t<div class=\"logo\"><span></span></div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<nx-ai-chat></nx-ai-chat>\n\t\t\t\t\t<div class=\"foot\">\n\t\t\t\t\t\t<div class=\"apply-src\">\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type=\"checkbox\" id=\"mybatis\" name=\"mybatis\" value=\"Y\" checked />\n\t\t\t\t\t\t\t <label for=\"mybatis\">MyBatis</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type=\"checkbox\" id=\"service\" name=\"service\" value=\"Y\" checked />\n\t\t\t\t\t\t\t <label for=\"service\">Service</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type=\"checkbox\" id=\"controller\" name=\"controller\" value=\"Y\" checked />\n\t\t\t\t\t\t\t <label for=\"controller\">Controller</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t <input type=\"checkbox\" id=\"javascript\" name=\"javascript\" value=\"Y\" checked />\n\t\t\t\t\t\t\t <label for=\"javascript\">JavaScript</label>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<textarea name=\"ask\" id=\"q\" name=\"q\" rows=\"4\" placeholder=\"${placeholder}\"></textarea>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"menu\">\n\t\t\t\t\t<div class=\"collapse-icon\"></div>\n\t\t\t\t\t<div class=\"menu-icon menu-filter active\"></div>\n\t\t\t\t\t<div class=\"menu-icon menu-general\"></div>\n\t\t\t\t\t<div class=\"menu-icon menu-setting\"></div>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div class=\"expand-icon\"></div>\n `;\n\n /**\n if (customElements.get(\"nine-ai-tip-popup\")) {\n this.#tipPopup = document.createElement('nine-ai-tip-popup');\n this.shadowRoot.appendChild(this.#tipPopup);\n }\n\n const input = this.shadowRoot.querySelector('input');\n input.addEventListener('keypress', (e) => {\n if (e.key === 'Enter') {\n const command = e.target.value.trim();\n if (command) {\n this.#handleFabCommand(command); // 내비게이션 대신 생성 명령 호출\n e.target.value = '';\n }\n }\n }); */\n }\n\n /**\n * [핵심] 소스 코드 생성 및 파일 주입 요청\n */\n async #handleFabCommand(command) {\n const statusEl = this.shadowRoot.getElementById('status');\n try {\n statusEl.textContent = \"AI가 설계를 분석 중입니다...\";\n await this.#tipPopup?.popup();\n\n trace.log(`🚀 소스 생성 명령 전송: \"${command}\"`);\n\n // [변경] nomenu/go -> fab/generate\n const response = await fetch(`${this.#connectorUrl}/api/fab/generate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n command,\n projectType: 'spring-react', // 템플릿 정보\n currentRoutes: this.#routes // 기존 경로 정보 (참고용)\n })\n });\n\n const result = await response.json();\n\n if (!result.success) {\n throw new Error(result.error || \"소스 생성 중 오류가 발생했습니다.\");\n }\n\n trace.log(\"✅ 소스 생성 및 파일 주입 완료!\", result.files);\n statusEl.textContent = \"✅ 생성 완료! 잠시 후 화면이 갱신됩니다.\";\n\n nine.alert(\"소스 생성이 완료되었습니다!\").rgb();\n\n // [알림] 생성 성공 시 프레임워크에 알림을 줘서 메뉴를 새로고침하게 함\n this.dispatchEvent(new CustomEvent('nine-fab-completed', {\n detail: result,\n bubbles: true,\n composed: true\n }));\n\n } catch (error) {\n trace.error(\"Fab 공정 실패:\", error);\n statusEl.textContent = \"❌ 생성 실패\";\n nine.alert(error.message).rgb().shake();\n } finally {\n this.#tipPopup?.close();\n }\n }\n\n #toggleCollapseHandler = () => {\n this.classList.toggle(\"collapse\");\n };\n\n #menuClickHandler = (e) => {\n\n // 모든 `.menu-icon`에서 `active` 클래스 제거\n this.shadowRoot.querySelectorAll(\".menu-icon\").forEach(el => el.classList.remove(\"active\"));\n\n // 클릭한 `.menu-icon`에 `active` 클래스 추가\n const clickedIcon = e.target.closest(\".menu-icon\");\n if (clickedIcon) clickedIcon.classList.add(\"active\");\n\n // `.menu-setting`이 클릭되었는지 확인 후 `nx-ai-settings` 토글\n this.settings.classList.toggle(\"expand\", !!e.target.closest(\".menu-setting\"));\n };\n}\n\n// 1. 웹컴포넌트 등록\nif (!customElements.get('nine-chat')) {\n customElements.define('nine-chat', NineChat);\n}","import { trace } from '@nine-lab/nine-util';\nimport { NineChat } from './components/NineChat.js';\n\n/**\n * Nine-Fab 엔진 메인 클래스\n */\nexport const NineFab = {\n version: typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.1.0',\n init: (config) => {\n trace.log(\"🛠️ Nine-Fab Engine initialized\", config);\n // 향후 커넥터 URL 전역 설정이나 인증 토큰 처리 로직 추가 가능\n }\n};\n\n// 기본 export 및 컴포넌트 export\nexport default NineFab;\nexport { NineChat };"],"names":["NineChat","HTMLElement","constructor","super","__privateAdd","this","_NineChat_instances","_connectorUrl","_tipPopup","_routes","_toggleCollapseHandler","classList","toggle","_menuClickHandler","e","shadowRoot","querySelectorAll","forEach","el","remove","clickedIcon","target","closest","add","settings","attachShadow","mode","connectedCallback","__privateSet","getAttribute","__privateMethod","render_fn","call","init_fn","WeakMap","WeakSet","async","querySelector","addEventListener","__privateGet","routeUrl","res","fetch","json","trace","log","err","error","message","placeholder","customPath","customImport","innerHTML","init","customElements","get","define","NineFab","version","config"],"mappings":"kuBAGO,MAAMA,UAAiBC,YAU1B,WAAAC,GACIC,QAXDC,EAAAC,KAAAC,GACHF,EAAAC,KAAAE,EAAgB,IAChBH,EAAAC,KAAAG,EAAY,MACZJ,EAAAC,KAAAI,EAAU,IAiKVL,EAAAC,KAAAK,EAAyB,KACrBL,KAAKM,UAAUC,OAAO,cAG1BR,EAAAC,KAAAQ,EAAqBC,IAGjBT,KAAKU,WAAWC,iBAAiB,cAAcC,WAAcC,EAAGP,UAAUQ,OAAO,WAGjF,MAAMC,EAAcN,EAAEO,OAAOC,QAAQ,cACjCF,GAAaA,EAAYT,UAAUY,IAAI,UAG3ClB,KAAKmB,SAASb,UAAUC,OAAO,WAAYE,EAAEO,OAAOC,QAAQ,oBAtK5DjB,KAAKoB,aAAa,CAAEC,KAAM,QAC9B,CAEA,uBAAMC,GACFC,EAAAvB,KAAKE,EAAgBF,KAAKwB,aAAa,kBAAoB,yBAC3DC,EAAAzB,KAAKC,EAAAyB,GAALC,KAAA3B,YACMyB,OAAKxB,EAAA2B,GAALD,KAAA3B,KACV,EAlBAE,EAAA,IAAA2B,QACA1B,EAAA,IAAA0B,QACAzB,EAAA,IAAAyB,QAHG5B,EAAA,IAAA6B,QAqBGF,EAAAG,iBAEF/B,KAAKU,WAAWsB,cAAc,gBAAgBC,iBAAiB,QAASC,OAAK7B,IAC7EL,KAAKU,WAAWsB,cAAc,kBAAkBC,iBAAiB,QAASC,OAAK7B,IAE/EL,KAAKU,WAAWC,iBAAiB,cAAcC,QAAQC,GAAMA,EAAGoB,iBAAiB,QAASC,EAAAlC,KAAKQ,KAK/F,MAAM2B,EAAWnC,KAAKwB,aAAa,aACnC,GAAIW,EACA,IACI,MAAMC,QAAYC,MAAMF,GACxBZ,EAAAvB,KAAKI,QAAgBgC,EAAIE,QACzBC,EAAAA,MAAMC,IAAI,qBAAsBN,EAAAlC,KAAKI,GACzC,OAASqC,GACLF,EAAAA,MAAMG,MAAM,cAAeD,EAAIE,QACnC,CAER,EAEAjB,EAAA,WACI,MAAMkB,EAAc5C,KAAKwB,aAAa,gBAAkB,kBAElDqB,EAAa7C,KAAKwB,aAAa,YAC/BsB,EAAeD,EAAa,YAAYA,MAAiB,GAE/D7C,KAAKU,WAAWqC,UAAY,qMAIlBD,2sCA8BiDF,oXA8B/D,EAmDAvC,EAAA,IAAAwB,QAIArB,EAAA,IAAAqB,QAjKIU,QAAMS,KAAK,WAAY,WAgL1BC,eAAeC,IAAI,cACpBD,eAAeE,OAAO,YAAaxD,GCrLhC,MAAMyD,EAAU,CACnBC,QAAkD,QAClDL,KAAOM,IACHf,QAAMC,IAAI,kCAAmCc"}
1
+ {"version":3,"file":"nine-fab.umd.js","sources":["../src/services/NineFabService.js","../src/components/NineChat.js","../src/index.js"],"sourcesContent":["import { nine, trace } from '@nine-lab/nine-util';\n\nexport class NineFabService {\n constructor(connectorUrl) {\n this.connectorUrl = connectorUrl;\n }\n\n /**\n * AI 소스 생성 실행\n * @param {string} command - 사용자 입력 명령\n * @param {Array} targets - 체크된 생성 대상 (MyBatis, Service 등)\n * @param {Array} currentRoutes - 현재 프로젝트 경로 정보\n */\n async generate(command, targets, currentRoutes) {\n try {\n trace.log(`🚀 Fab 공정 시작: \"${command}\" [대상: ${targets.join(', ')}]`);\n\n const response = await fetch(`${this.connectorUrl}/api/fab/generate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n command,\n targets,\n projectType: 'spring-react',\n currentRoutes\n })\n });\n\n const result = await response.json();\n\n if (!result.success) {\n throw new Error(result.error || \"소스 생성 중 서버 오류가 발생했습니다.\");\n }\n\n return result;\n } catch (error) {\n trace.error(\"❌ Fab Service Error:\", error);\n throw error;\n }\n }\n\n /**\n * 초기 경로 정보 로드\n */\n async fetchRoutes(routeUrl) {\n if (!routeUrl) return [];\n try {\n const res = await fetch(routeUrl);\n return await res.json();\n } catch (err) {\n trace.error(\"Route load fail\", err);\n return [];\n }\n }\n}","import { nine, trace } from '@nine-lab/nine-util';\nimport { NineFabService } from '../services/NineFabService.js';\n\nexport class NineChat extends HTMLElement {\n #service = null;\n #routes = [];\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n async connectedCallback() {\n // 서비스 초기화\n const connectorUrl = this.getAttribute('connector-url') || 'http://localhost:3002';\n this.#service = new NineFabService(connectorUrl);\n\n this.#render();\n this.#initInteractions(); // UI 토글 로직\n this.#initActions(); // 엔터 시 실행 로직\n\n // 경로 데이터 로드\n this.#routes = await this.#service.fetchRoutes(this.getAttribute('route-url'));\n }\n\n // --- [그룹 1: Interaction] 화면 토글 및 메뉴 클릭 ---\n #initInteractions() {\n const $settings = this.shadowRoot.querySelector('nine-ai-settings');\n const $menuIcons = this.shadowRoot.querySelectorAll(\".menu-icon\");\n\n const toggleUI = () => this.classList.toggle(\"collapse\");\n this.shadowRoot.querySelector(\".expand-icon\").addEventListener(\"click\", toggleUI);\n this.shadowRoot.querySelector(\".collapse-icon\").addEventListener(\"click\", toggleUI);\n\n $menuIcons.forEach(el => {\n el.addEventListener(\"click\", (e) => {\n $menuIcons.forEach(icon => icon.classList.remove(\"active\"));\n const clicked = e.target.closest(\".menu-icon\");\n clicked.classList.add(\"active\");\n $settings.classList.toggle(\"expand\", clicked.classList.contains('menu-setting'));\n });\n });\n }\n\n // --- [그룹 2: Action] 엔터키 입력 시 서비스 호출 ---\n #initActions() {\n const $textarea = this.shadowRoot.querySelector('#q');\n const $status = this.shadowRoot.querySelector('#status-tag');\n\n $textarea.addEventListener('keypress', async (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n const command = e.target.value.trim();\n if (!command) return;\n\n // 1. UI 피드백\n $status.textContent = \"⚙️ 공정 분석 중...\";\n e.target.value = '';\n\n // 2. 체크된 타겟 수집\n const targets = Array.from(this.shadowRoot.querySelectorAll('input[name=\"gen_target\"]:checked'))\n .map(el => el.value);\n\n // 3. 분리된 서비스 호출\n try {\n const result = await this.#service.generate(command, targets, this.#routes);\n\n // 4. 성공 처리\n $status.textContent = \"✅ 완료\";\n nine.alert(\"소스 생성 성공\").rgb();\n this.dispatchEvent(new CustomEvent('nine-fab-completed', { detail: result, bubbles: true }));\n } catch (err) {\n // 5. 실패 처리\n $status.textContent = \"❌ 실패\";\n nine.alert(err.message).rgb().shake();\n }\n }\n });\n }\n\n #render() {\n const placeholder = this.getAttribute(\"placeholder\") || \"나에게 무엇이든 물어봐...\";\n const customImport = this.getAttribute(\"css-path\") ? `@import \"${this.getAttribute(\"css-path\")}\";` : \"\";\n\n this.shadowRoot.innerHTML = `\n <style>\n @import \"https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${__APP_VERSION__}/dist/css/nine-fab.css\";\n ${customImport}\n </style>\n <div class=\"wrapper\">\n <nine-ai-settings></nine-ai-settings>\n <div class=\"container\">\n <div class=\"head\">\n <div class=\"logo\"><span></span></div>\n <div id=\"status-tag\" style=\"font-size:10px; color:#ff5722; padding:5px;\">Fab Engine Ready</div>\n </div>\n <nx-ai-chat></nx-ai-chat>\n <div class=\"foot\">\n <div class=\"apply-src\">\n ${['MyBatis', 'Service', 'Controller', 'JavaScript'].map(t => `\n <div>\n <input type=\"checkbox\" id=\"${t.toLowerCase()}\" name=\"gen_target\" value=\"${t}\" checked />\n <label for=\"${t.toLowerCase()}\">${t}</label>\n </div>\n `).join('')}\n </div>\n <textarea id=\"q\" rows=\"4\" placeholder=\"${placeholder}\"></textarea>\n </div>\n </div>\n <div class=\"menu\">\n <div class=\"collapse-icon\"></div>\n <div class=\"menu-icon menu-filter active\"></div>\n <div class=\"menu-icon menu-general\"></div>\n <div class=\"menu-icon menu-setting\"></div>\n </div>\n </div>\n <div class=\"expand-icon\"></div>\n `;\n }\n}","import { trace } from '@nine-lab/nine-util';\nimport { NineChat } from './components/NineChat.js';\n\n/**\n * Nine-Fab 엔진 메인 클래스\n */\nexport const NineFab = {\n version: typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '0.1.0',\n init: (config) => {\n trace.log(\"🛠️ Nine-Fab Engine initialized\", config);\n // 향후 커넥터 URL 전역 설정이나 인증 토큰 처리 로직 추가 가능\n }\n};\n\n// 기본 export 및 컴포넌트 export\nexport default NineFab;\nexport { NineChat };"],"names":["NineFabService","constructor","connectorUrl","this","generate","command","targets","currentRoutes","trace","log","join","response","fetch","method","headers","body","JSON","stringify","projectType","result","json","success","Error","error","fetchRoutes","routeUrl","res","err","NineChat","HTMLElement","super","__privateAdd","_NineChat_instances","_service","_routes","attachShadow","mode","connectedCallback","getAttribute","__privateSet","__privateMethod","render_fn","call","initInteractions_fn","initActions_fn","__privateGet","WeakMap","WeakSet","$settings","shadowRoot","querySelector","$menuIcons","querySelectorAll","toggleUI","classList","toggle","addEventListener","forEach","el","e","icon","remove","clicked","target","closest","add","contains","$textarea","$status","async","key","shiftKey","preventDefault","value","trim","textContent","Array","from","map","nine","alert","rgb","dispatchEvent","CustomEvent","detail","bubbles","message","shake","placeholder","customImport","innerHTML","t","toLowerCase","NineFab","version","init","config"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nine-lab/nine-fab",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "AI-Driven Full-Stack Code Fabrication Engine",
5
5
  "type": "module",
6
6
  "main": "./dist/nine-fab.umd.js",
@@ -1,189 +1,120 @@
1
1
  import { nine, trace } from '@nine-lab/nine-util';
2
- import { TipPopup } from '@nine-lab/nine-ai';
2
+ import { NineFabService } from '../services/NineFabService.js';
3
3
 
4
4
  export class NineChat extends HTMLElement {
5
- #connectorUrl = '';
6
- #tipPopup = null;
5
+ #service = null;
7
6
  #routes = [];
8
7
 
9
- static {
10
- // Fab 전용 로깅 컬러로 변경 (색상 차별화)
11
- trace.init("nine-fab", "#FF5722");
12
- }
13
-
14
8
  constructor() {
15
9
  super();
16
10
  this.attachShadow({ mode: 'open' });
17
11
  }
18
12
 
19
13
  async connectedCallback() {
20
- this.#connectorUrl = this.getAttribute('connector-url') || 'http://localhost:3002';
21
- this.#render();
22
- await this.#init();
23
- }
14
+ // 서비스 초기화
15
+ const connectorUrl = this.getAttribute('connector-url') || 'http://localhost:3002';
16
+ this.#service = new NineFabService(connectorUrl);
24
17
 
25
- async #init() {
18
+ this.#render();
19
+ this.#initInteractions(); // UI 토글 로직
20
+ this.#initActions(); // 엔터 시 실행 로직
26
21
 
27
- this.shadowRoot.querySelector(".expand-icon").addEventListener("click", this.#toggleCollapseHandler);
28
- this.shadowRoot.querySelector(".collapse-icon").addEventListener("click", this.#toggleCollapseHandler);
22
+ // 경로 데이터 로드
23
+ this.#routes = await this.#service.fetchRoutes(this.getAttribute('route-url'));
24
+ }
29
25
 
30
- this.shadowRoot.querySelectorAll(".menu-icon").forEach(el => el.addEventListener("click", this.#menuClickHandler));
26
+ // --- [그룹 1: Interaction] 화면 토글 및 메뉴 클릭 ---
27
+ #initInteractions() {
28
+ const $settings = this.shadowRoot.querySelector('nine-ai-settings');
29
+ const $menuIcons = this.shadowRoot.querySelectorAll(".menu-icon");
30
+
31
+ const toggleUI = () => this.classList.toggle("collapse");
32
+ this.shadowRoot.querySelector(".expand-icon").addEventListener("click", toggleUI);
33
+ this.shadowRoot.querySelector(".collapse-icon").addEventListener("click", toggleUI);
34
+
35
+ $menuIcons.forEach(el => {
36
+ el.addEventListener("click", (e) => {
37
+ $menuIcons.forEach(icon => icon.classList.remove("active"));
38
+ const clicked = e.target.closest(".menu-icon");
39
+ clicked.classList.add("active");
40
+ $settings.classList.toggle("expand", clicked.classList.contains('menu-setting'));
41
+ });
42
+ });
43
+ }
31
44
 
45
+ // --- [그룹 2: Action] 엔터키 입력 시 서비스 호출 ---
46
+ #initActions() {
47
+ const $textarea = this.shadowRoot.querySelector('#q');
48
+ const $status = this.shadowRoot.querySelector('#status-tag');
32
49
 
33
- // [중요] Fab 엔진은 route-url을 통해 현재 등록된 전체 메뉴를 파악하여
34
- // AI가 중복된 경로를 만들지 않게 하거나, 기존 소스를 참고하게 합니다.
35
- const routeUrl = this.getAttribute('route-url');
36
- if (routeUrl) {
37
- try {
38
- const res = await fetch(routeUrl);
39
- this.#routes = await res.json();
40
- trace.log("✅ 현재 프로젝트 경로 분석 완료", this.#routes);
41
- } catch (err) {
42
- trace.error("❌ 경로 로드 실패:", err.message);
50
+ $textarea.addEventListener('keypress', async (e) => {
51
+ if (e.key === 'Enter' && !e.shiftKey) {
52
+ e.preventDefault();
53
+ const command = e.target.value.trim();
54
+ if (!command) return;
55
+
56
+ // 1. UI 피드백
57
+ $status.textContent = "⚙️ 공정 분석 중...";
58
+ e.target.value = '';
59
+
60
+ // 2. 체크된 타겟 수집
61
+ const targets = Array.from(this.shadowRoot.querySelectorAll('input[name="gen_target"]:checked'))
62
+ .map(el => el.value);
63
+
64
+ // 3. 분리된 서비스 호출
65
+ try {
66
+ const result = await this.#service.generate(command, targets, this.#routes);
67
+
68
+ // 4. 성공 처리
69
+ $status.textContent = "✅ 완료";
70
+ nine.alert("소스 생성 성공").rgb();
71
+ this.dispatchEvent(new CustomEvent('nine-fab-completed', { detail: result, bubbles: true }));
72
+ } catch (err) {
73
+ // 5. 실패 처리
74
+ $status.textContent = "❌ 실패";
75
+ nine.alert(err.message).rgb().shake();
76
+ }
43
77
  }
44
- }
78
+ });
45
79
  }
46
80
 
47
81
  #render() {
48
82
  const placeholder = this.getAttribute("placeholder") || "나에게 무엇이든 물어봐...";
49
-
50
- const customPath = this.getAttribute("css-path");
51
- const customImport = customPath ? `@import "${customPath}";` : "";
83
+ const customImport = this.getAttribute("css-path") ? `@import "${this.getAttribute("css-path")}";` : "";
52
84
 
53
85
  this.shadowRoot.innerHTML = `
54
86
  <style>
55
- /* Fab 전용 스타일 (nomenu와 차별화 추천) */
56
87
  @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${__APP_VERSION__}/dist/css/nine-fab.css";
57
88
  ${customImport}
58
89
  </style>
59
-
60
90
  <div class="wrapper">
61
- <nine-ai-settings></nine-ai-settings>
62
-
63
- <div class="container">
64
- <div class="head">
65
- <div class="logo"><span></span></div>
66
- </div>
67
- <nx-ai-chat></nx-ai-chat>
68
- <div class="foot">
69
- <div class="apply-src">
70
- <div>
71
- <input type="checkbox" id="mybatis" name="mybatis" value="Y" checked />
72
- <label for="mybatis">MyBatis</label>
73
- </div>
74
- <div>
75
- <input type="checkbox" id="service" name="service" value="Y" checked />
76
- <label for="service">Service</label>
77
- </div>
78
- <div>
79
- <input type="checkbox" id="controller" name="controller" value="Y" checked />
80
- <label for="controller">Controller</label>
81
- </div>
82
- <div>
83
- <input type="checkbox" id="javascript" name="javascript" value="Y" checked />
84
- <label for="javascript">JavaScript</label>
85
- </div>
86
- </div>
87
- <textarea name="ask" id="q" name="q" rows="4" placeholder="${placeholder}"></textarea>
88
- </div>
89
- </div>
90
- <div class="menu">
91
- <div class="collapse-icon"></div>
92
- <div class="menu-icon menu-filter active"></div>
93
- <div class="menu-icon menu-general"></div>
94
- <div class="menu-icon menu-setting"></div>
95
- </div>
96
- </div>
97
-
98
- <div class="expand-icon"></div>
91
+ <nine-ai-settings></nine-ai-settings>
92
+ <div class="container">
93
+ <div class="head">
94
+ <div class="logo"><span></span></div>
95
+ <div id="status-tag" style="font-size:10px; color:#ff5722; padding:5px;">Fab Engine Ready</div>
96
+ </div>
97
+ <nx-ai-chat></nx-ai-chat>
98
+ <div class="foot">
99
+ <div class="apply-src">
100
+ ${['MyBatis', 'Service', 'Controller', 'JavaScript'].map(t => `
101
+ <div>
102
+ <input type="checkbox" id="${t.toLowerCase()}" name="gen_target" value="${t}" checked />
103
+ <label for="${t.toLowerCase()}">${t}</label>
104
+ </div>
105
+ `).join('')}
106
+ </div>
107
+ <textarea id="q" rows="4" placeholder="${placeholder}"></textarea>
108
+ </div>
109
+ </div>
110
+ <div class="menu">
111
+ <div class="collapse-icon"></div>
112
+ <div class="menu-icon menu-filter active"></div>
113
+ <div class="menu-icon menu-general"></div>
114
+ <div class="menu-icon menu-setting"></div>
115
+ </div>
116
+ </div>
117
+ <div class="expand-icon"></div>
99
118
  `;
100
-
101
- /**
102
- if (customElements.get("nine-ai-tip-popup")) {
103
- this.#tipPopup = document.createElement('nine-ai-tip-popup');
104
- this.shadowRoot.appendChild(this.#tipPopup);
105
- }
106
-
107
- const input = this.shadowRoot.querySelector('input');
108
- input.addEventListener('keypress', (e) => {
109
- if (e.key === 'Enter') {
110
- const command = e.target.value.trim();
111
- if (command) {
112
- this.#handleFabCommand(command); // 내비게이션 대신 생성 명령 호출
113
- e.target.value = '';
114
- }
115
- }
116
- }); */
117
- }
118
-
119
- /**
120
- * [핵심] 소스 코드 생성 및 파일 주입 요청
121
- */
122
- async #handleFabCommand(command) {
123
- const statusEl = this.shadowRoot.getElementById('status');
124
- try {
125
- statusEl.textContent = "AI가 설계를 분석 중입니다...";
126
- await this.#tipPopup?.popup();
127
-
128
- trace.log(`🚀 소스 생성 명령 전송: "${command}"`);
129
-
130
- // [변경] nomenu/go -> fab/generate
131
- const response = await fetch(`${this.#connectorUrl}/api/fab/generate`, {
132
- method: 'POST',
133
- headers: { 'Content-Type': 'application/json' },
134
- body: JSON.stringify({
135
- command,
136
- projectType: 'spring-react', // 템플릿 정보
137
- currentRoutes: this.#routes // 기존 경로 정보 (참고용)
138
- })
139
- });
140
-
141
- const result = await response.json();
142
-
143
- if (!result.success) {
144
- throw new Error(result.error || "소스 생성 중 오류가 발생했습니다.");
145
- }
146
-
147
- trace.log("✅ 소스 생성 및 파일 주입 완료!", result.files);
148
- statusEl.textContent = "✅ 생성 완료! 잠시 후 화면이 갱신됩니다.";
149
-
150
- nine.alert("소스 생성이 완료되었습니다!").rgb();
151
-
152
- // [알림] 생성 성공 시 프레임워크에 알림을 줘서 메뉴를 새로고침하게 함
153
- this.dispatchEvent(new CustomEvent('nine-fab-completed', {
154
- detail: result,
155
- bubbles: true,
156
- composed: true
157
- }));
158
-
159
- } catch (error) {
160
- trace.error("Fab 공정 실패:", error);
161
- statusEl.textContent = "❌ 생성 실패";
162
- nine.alert(error.message).rgb().shake();
163
- } finally {
164
- this.#tipPopup?.close();
165
- }
166
119
  }
167
-
168
- #toggleCollapseHandler = () => {
169
- this.classList.toggle("collapse");
170
- };
171
-
172
- #menuClickHandler = (e) => {
173
-
174
- // 모든 `.menu-icon`에서 `active` 클래스 제거
175
- this.shadowRoot.querySelectorAll(".menu-icon").forEach(el => el.classList.remove("active"));
176
-
177
- // 클릭한 `.menu-icon`에 `active` 클래스 추가
178
- const clickedIcon = e.target.closest(".menu-icon");
179
- if (clickedIcon) clickedIcon.classList.add("active");
180
-
181
- // `.menu-setting`이 클릭되었는지 확인 후 `nx-ai-settings` 토글
182
- this.settings.classList.toggle("expand", !!e.target.closest(".menu-setting"));
183
- };
184
- }
185
-
186
- // 1. 웹컴포넌트 등록
187
- if (!customElements.get('nine-chat')) {
188
- customElements.define('nine-chat', NineChat);
189
120
  }
@@ -0,0 +1,55 @@
1
+ import { nine, trace } from '@nine-lab/nine-util';
2
+
3
+ export class NineFabService {
4
+ constructor(connectorUrl) {
5
+ this.connectorUrl = connectorUrl;
6
+ }
7
+
8
+ /**
9
+ * AI 소스 생성 실행
10
+ * @param {string} command - 사용자 입력 명령
11
+ * @param {Array} targets - 체크된 생성 대상 (MyBatis, Service 등)
12
+ * @param {Array} currentRoutes - 현재 프로젝트 경로 정보
13
+ */
14
+ async generate(command, targets, currentRoutes) {
15
+ try {
16
+ trace.log(`🚀 Fab 공정 시작: "${command}" [대상: ${targets.join(', ')}]`);
17
+
18
+ const response = await fetch(`${this.connectorUrl}/api/fab/generate`, {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify({
22
+ command,
23
+ targets,
24
+ projectType: 'spring-react',
25
+ currentRoutes
26
+ })
27
+ });
28
+
29
+ const result = await response.json();
30
+
31
+ if (!result.success) {
32
+ throw new Error(result.error || "소스 생성 중 서버 오류가 발생했습니다.");
33
+ }
34
+
35
+ return result;
36
+ } catch (error) {
37
+ trace.error("❌ Fab Service Error:", error);
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * 초기 경로 정보 로드
44
+ */
45
+ async fetchRoutes(routeUrl) {
46
+ if (!routeUrl) return [];
47
+ try {
48
+ const res = await fetch(routeUrl);
49
+ return await res.json();
50
+ } catch (err) {
51
+ trace.error("Route load fail", err);
52
+ return [];
53
+ }
54
+ }
55
+ }