@nine-lab/nine-fab 0.1.6 → 0.1.7

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
@@ -8,7 +8,6 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
8
8
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
9
9
  var _connectorUrl, _tipPopup, _routes, _NineChat_instances, init_fn, render_fn, handleFabCommand_fn, _toggleCollapseHandler, _menuClickHandler;
10
10
  import { trace, nine } from "@nine-lab/nine-util";
11
- import "@nine-lab/nine-ai";
12
11
  class NineChat extends HTMLElement {
13
12
  constructor() {
14
13
  super();
@@ -23,7 +22,9 @@ class NineChat extends HTMLElement {
23
22
  this.shadowRoot.querySelectorAll(".menu-icon").forEach((el) => el.classList.remove("active"));
24
23
  const clickedIcon = e.target.closest(".menu-icon");
25
24
  if (clickedIcon) clickedIcon.classList.add("active");
26
- this.settings.classList.toggle("expand", !!e.target.closest(".menu-setting"));
25
+ if (this.$settings) {
26
+ this.$settings.classList.toggle("expand", !!e.target.closest(".menu-setting"));
27
+ }
27
28
  });
28
29
  this.attachShadow({ mode: "open" });
29
30
  }
@@ -38,9 +39,21 @@ _tipPopup = new WeakMap();
38
39
  _routes = new WeakMap();
39
40
  _NineChat_instances = new WeakSet();
40
41
  init_fn = async function() {
42
+ this.$textarea = this.shadowRoot.querySelector("#q");
43
+ this.$settings = this.shadowRoot.querySelector("nine-ai-settings");
41
44
  this.shadowRoot.querySelector(".expand-icon").addEventListener("click", __privateGet(this, _toggleCollapseHandler));
42
45
  this.shadowRoot.querySelector(".collapse-icon").addEventListener("click", __privateGet(this, _toggleCollapseHandler));
43
46
  this.shadowRoot.querySelectorAll(".menu-icon").forEach((el) => el.addEventListener("click", __privateGet(this, _menuClickHandler)));
47
+ this.$textarea.addEventListener("keypress", (e) => {
48
+ if (e.key === "Enter" && !e.shiftKey) {
49
+ e.preventDefault();
50
+ const command = e.target.value.trim();
51
+ if (command) {
52
+ __privateMethod(this, _NineChat_instances, handleFabCommand_fn).call(this, command);
53
+ e.target.value = "";
54
+ }
55
+ }
56
+ });
44
57
  const routeUrl = this.getAttribute("route-url");
45
58
  if (routeUrl) {
46
59
  try {
@@ -56,10 +69,10 @@ render_fn = function() {
56
69
  const placeholder = this.getAttribute("placeholder") || "나에게 무엇이든 물어봐...";
57
70
  const customPath = this.getAttribute("css-path");
58
71
  const customImport = customPath ? `@import "${customPath}";` : "";
72
+ const version = "0.1.6";
59
73
  this.shadowRoot.innerHTML = `
60
74
  <style>
61
- /* Fab 전용 스타일 (nomenu와 차별화 추천) */
62
- @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${"0.1.5"}/dist/css/nine-fab.css";
75
+ @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${version}/dist/css/nine-fab.css";
63
76
  ${customImport}
64
77
  </style>
65
78
 
@@ -69,28 +82,29 @@ render_fn = function() {
69
82
  <div class="container">
70
83
  <div class="head">
71
84
  <div class="logo"><span></span></div>
85
+ <div id="status-tag" style="font-size:10px; color:#ff5722; padding:5px;">Fab Engine Ready</div>
72
86
  </div>
73
87
  <nx-ai-chat></nx-ai-chat>
74
88
  <div class="foot">
75
89
  <div class="apply-src">
76
90
  <div>
77
- <input type="checkbox" id="mybatis" name="mybatis" value="Y" checked />
91
+ <input type="checkbox" id="mybatis" name="gen_target" value="MyBatis" checked />
78
92
  <label for="mybatis">MyBatis</label>
79
93
  </div>
80
94
  <div>
81
- <input type="checkbox" id="service" name="service" value="Y" checked />
95
+ <input type="checkbox" id="service" name="gen_target" value="Service" checked />
82
96
  <label for="service">Service</label>
83
97
  </div>
84
98
  <div>
85
- <input type="checkbox" id="controller" name="controller" value="Y" checked />
99
+ <input type="checkbox" id="controller" name="gen_target" value="Controller" checked />
86
100
  <label for="controller">Controller</label>
87
101
  </div>
88
102
  <div>
89
- <input type="checkbox" id="javascript" name="javascript" value="Y" checked />
103
+ <input type="checkbox" id="javascript" name="gen_target" value="JavaScript" checked />
90
104
  <label for="javascript">JavaScript</label>
91
105
  </div>
92
106
  </div>
93
- <textarea name="ask" id="q" name="q" rows="4" placeholder="${placeholder}"></textarea>
107
+ <textarea id="q" rows="4" placeholder="${placeholder}"></textarea>
94
108
  </div>
95
109
  </div>
96
110
  <div class="menu">
@@ -100,35 +114,31 @@ render_fn = function() {
100
114
  <div class="menu-icon menu-setting"></div>
101
115
  </div>
102
116
  </div>
103
-
104
117
  <div class="expand-icon"></div>
105
118
  `;
106
119
  };
107
120
  handleFabCommand_fn = async function(command) {
108
- var _a, _b;
109
- const statusEl = this.shadowRoot.getElementById("status");
121
+ const statusEl = this.shadowRoot.getElementById("status-tag");
122
+ const targets = Array.from(this.shadowRoot.querySelectorAll('input[name="gen_target"]:checked')).map((el) => el.value);
110
123
  try {
111
- statusEl.textContent = "AI가 설계를 분석 중입니다...";
112
- await ((_a = __privateGet(this, _tipPopup)) == null ? void 0 : _a.popup());
113
- trace.log(`🚀 소스 생성 명령 전송: "${command}"`);
124
+ statusEl.textContent = "⚙️ 공정 분석 중...";
125
+ trace.log(`🚀 Fab 명령 실행: "${command}" [대상: ${targets.join(", ")}]`);
114
126
  const response = await fetch(`${__privateGet(this, _connectorUrl)}/api/fab/generate`, {
115
127
  method: "POST",
116
128
  headers: { "Content-Type": "application/json" },
117
129
  body: JSON.stringify({
118
130
  command,
131
+ targets,
132
+ // MyBatis, Service 등 선택된 항목 전송
119
133
  projectType: "spring-react",
120
- // 템플릿 정보
121
134
  currentRoutes: __privateGet(this, _routes)
122
- // 기존 경로 정보 (참고용)
123
135
  })
124
136
  });
125
137
  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();
138
+ if (!result.success) throw new Error(result.error || "생성 실패");
139
+ trace.log("✅ 소스 생성 완료", result.files);
140
+ statusEl.textContent = "✅ 파일 주입 완료";
141
+ nine.alert("소스가 성공적으로 생성되었습니다.").rgb();
132
142
  this.dispatchEvent(new CustomEvent("nine-fab-completed", {
133
143
  detail: result,
134
144
  bubbles: true,
@@ -136,10 +146,8 @@ handleFabCommand_fn = async function(command) {
136
146
  }));
137
147
  } catch (error) {
138
148
  trace.error("Fab 공정 실패:", error);
139
- statusEl.textContent = "❌ 생성 실패";
149
+ statusEl.textContent = "❌ 에러 발생";
140
150
  nine.alert(error.message).rgb().shake();
141
- } finally {
142
- (_b = __privateGet(this, _tipPopup)) == null ? void 0 : _b.close();
143
151
  }
144
152
  };
145
153
  _toggleCollapseHandler = new WeakMap();
@@ -149,7 +157,7 @@ if (!customElements.get("nine-chat")) {
149
157
  customElements.define("nine-chat", NineChat);
150
158
  }
151
159
  const NineFab = {
152
- version: "0.1.5",
160
+ version: "0.1.6",
153
161
  init: (config) => {
154
162
  trace.log("🛠️ Nine-Fab Engine initialized", config);
155
163
  }
@@ -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/components/NineChat.js","../src/index.js"],"sourcesContent":["import { nine, trace } from '@nine-lab/nine-util';\n// import { TipPopup } from '@nine-lab/nine-ai'; // 필요시 주석 해제\n\nexport class NineChat extends HTMLElement {\n #connectorUrl = '';\n #tipPopup = null;\n #routes = [];\n\n static {\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 // UI 요소 참조\n this.$textarea = this.shadowRoot.querySelector('#q');\n this.$settings = this.shadowRoot.querySelector('nine-ai-settings');\n\n // 아이콘 이벤트 연결\n this.shadowRoot.querySelector(\".expand-icon\").addEventListener(\"click\", this.#toggleCollapseHandler);\n this.shadowRoot.querySelector(\".collapse-icon\").addEventListener(\"click\", this.#toggleCollapseHandler);\n this.shadowRoot.querySelectorAll(\".menu-icon\").forEach(el => el.addEventListener(\"click\", this.#menuClickHandler));\n\n // [핵심] 텍스트 영역 엔터 이벤트 (Command 전송)\n this.$textarea.addEventListener('keypress', (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n const command = e.target.value.trim();\n if (command) {\n this.#handleFabCommand(command);\n e.target.value = '';\n }\n }\n });\n\n // 기존 경로 정보 로드\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 const customPath = this.getAttribute(\"css-path\");\n const customImport = customPath ? `@import \"${customPath}\";` : \"\";\n const version = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'latest';\n\n this.shadowRoot.innerHTML = `\n <style>\n @import \"https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${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 <div id=\"status-tag\" style=\"font-size:10px; color:#ff5722; padding:5px;\">Fab Engine Ready</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=\"gen_target\" value=\"MyBatis\" 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=\"gen_target\" value=\"Service\" 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=\"gen_target\" value=\"Controller\" 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=\"gen_target\" value=\"JavaScript\" 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 id=\"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\t\t\t<div class=\"expand-icon\"></div>\n `;\n }\n\n async #handleFabCommand(command) {\n const statusEl = this.shadowRoot.getElementById('status-tag');\n\n // 체크된 생성 대상 수집\n const targets = Array.from(this.shadowRoot.querySelectorAll('input[name=\"gen_target\"]:checked'))\n .map(el => el.value);\n\n try {\n statusEl.textContent = \"⚙️ 공정 분석 중...\";\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, // MyBatis, Service 등 선택된 항목 전송\n projectType: 'spring-react',\n currentRoutes: this.#routes\n })\n });\n\n const result = await response.json();\n\n if (!result.success) throw new Error(result.error || \"생성 실패\");\n\n trace.log(\"✅ 소스 생성 완료\", result.files);\n statusEl.textContent = \"✅ 파일 주입 완료\";\n nine.alert(\"소스가 성공적으로 생성되었습니다.\").rgb();\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 }\n }\n\n #toggleCollapseHandler = () => {\n this.classList.toggle(\"collapse\");\n };\n\n #menuClickHandler = (e) => {\n this.shadowRoot.querySelectorAll(\".menu-icon\").forEach(el => el.classList.remove(\"active\"));\n const clickedIcon = e.target.closest(\".menu-icon\");\n if (clickedIcon) clickedIcon.classList.add(\"active\");\n\n // nx-ai-settings 토글 처리\n if (this.$settings) {\n this.$settings.classList.toggle(\"expand\", !!e.target.closest(\".menu-setting\"));\n }\n };\n}\n\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":";;;;;;;;;;AAGO,MAAM,iBAAiB,YAAY;AAAA,EAStC,cAAc;AACV,UAAA;AAVD;AACH,sCAAgB;AAChB,kCAAY;AACZ,gCAAU,CAAA;AAqJV,+CAAyB,MAAM;AAC3B,WAAK,UAAU,OAAO,UAAU;AAAA,IACpC;AAEA,0CAAoB,CAAC,MAAM;AACvB,WAAK,WAAW,iBAAiB,YAAY,EAAE,QAAQ,QAAM,GAAG,UAAU,OAAO,QAAQ,CAAC;AAC1F,YAAM,cAAc,EAAE,OAAO,QAAQ,YAAY;AACjD,UAAI,YAAa,aAAY,UAAU,IAAI,QAAQ;AAGnD,UAAI,KAAK,WAAW;AAChB,aAAK,UAAU,UAAU,OAAO,UAAU,CAAC,CAAC,EAAE,OAAO,QAAQ,eAAe,CAAC;AAAA,MACjF;AAAA,IACJ;AA1JI,SAAK,aAAa,EAAE,MAAM,OAAA,CAAQ;AAAA,EACtC;AAAA,EAEA,MAAM,oBAAoB;AACtB,uBAAK,eAAgB,KAAK,aAAa,eAAe,KAAK;AAC3D,0BAAK,gCAAL;AACA,UAAM,sBAAK,8BAAL;AAAA,EACV;AAoJJ;AArKI;AACA;AACA;AAHG;AAoBG,UAAA,iBAAQ;AAEV,OAAK,YAAY,KAAK,WAAW,cAAc,IAAI;AACnD,OAAK,YAAY,KAAK,WAAW,cAAc,kBAAkB;AAGjE,OAAK,WAAW,cAAc,cAAc,EAAE,iBAAiB,SAAS,mBAAK,uBAAsB;AACnG,OAAK,WAAW,cAAc,gBAAgB,EAAE,iBAAiB,SAAS,mBAAK,uBAAsB;AACrG,OAAK,WAAW,iBAAiB,YAAY,EAAE,QAAQ,CAAA,OAAM,GAAG,iBAAiB,SAAS,mBAAK,kBAAiB,CAAC;AAGjH,OAAK,UAAU,iBAAiB,YAAY,CAAC,MAAM;AAC/C,QAAI,EAAE,QAAQ,WAAW,CAAC,EAAE,UAAU;AAClC,QAAE,eAAA;AACF,YAAM,UAAU,EAAE,OAAO,MAAM,KAAA;AAC/B,UAAI,SAAS;AACT,8BAAK,0CAAL,WAAuB;AACvB,UAAE,OAAO,QAAQ;AAAA,MACrB;AAAA,IACJ;AAAA,EACJ,CAAC;AAGD,QAAM,WAAW,KAAK,aAAa,WAAW;AAC9C,MAAI,UAAU;AACV,QAAI;AACA,YAAM,MAAM,MAAM,MAAM,QAAQ;AAChC,yBAAK,SAAU,MAAM,IAAI,KAAA;AACzB,YAAM,IAAI,sBAAsB,mBAAK,QAAO;AAAA,IAChD,SAAS,KAAK;AACV,YAAM,MAAM,eAAe,IAAI,OAAO;AAAA,IAC1C;AAAA,EACJ;AACJ;AAEA,YAAA,WAAU;AACN,QAAM,cAAc,KAAK,aAAa,aAAa,KAAK;AACxD,QAAM,aAAa,KAAK,aAAa,UAAU;AAC/C,QAAM,eAAe,aAAa,YAAY,UAAU,OAAO;AAC/D,QAAM,UAAmD;AAEzD,OAAK,WAAW,YAAY;AAAA;AAAA,2EAEuC,OAAO;AAAA,kBAChE,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CA+BiB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYtD;AAEM,qCAAkB,SAAS;AAC7B,QAAM,WAAW,KAAK,WAAW,eAAe,YAAY;AAG5D,QAAM,UAAU,MAAM,KAAK,KAAK,WAAW,iBAAiB,kCAAkC,CAAC,EAC1F,IAAI,CAAA,OAAM,GAAG,KAAK;AAEvB,MAAI;AACA,aAAS,cAAc;AACvB,UAAM,IAAI,kBAAkB,OAAO,UAAU,QAAQ,KAAK,IAAI,CAAC,GAAG;AAElE,UAAM,WAAW,MAAM,MAAM,GAAG,mBAAK,cAAa,qBAAqB;AAAA,MACnE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAA;AAAA,MAC3B,MAAM,KAAK,UAAU;AAAA,QACjB;AAAA,QACA;AAAA;AAAA,QACA,aAAa;AAAA,QACb,eAAe,mBAAK;AAAA,MAAA,CACvB;AAAA,IAAA,CACJ;AAED,UAAM,SAAS,MAAM,SAAS,KAAA;AAE9B,QAAI,CAAC,OAAO,QAAS,OAAM,IAAI,MAAM,OAAO,SAAS,OAAO;AAE5D,UAAM,IAAI,cAAc,OAAO,KAAK;AACpC,aAAS,cAAc;AACvB,SAAK,MAAM,oBAAoB,EAAE,IAAA;AAEjC,SAAK,cAAc,IAAI,YAAY,sBAAsB;AAAA,MACrD,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,UAAU;AAAA,IAAA,CACb,CAAC;AAAA,EAEN,SAAS,OAAO;AACZ,UAAM,MAAM,cAAc,KAAK;AAC/B,aAAS,cAAc;AACvB,SAAK,MAAM,MAAM,OAAO,EAAE,IAAA,EAAM,MAAA;AAAA,EACpC;AACJ;AAEA;AAIA;AAtJI,MAAM,KAAK,YAAY,SAAS;AAkKxC,IAAI,CAAC,eAAe,IAAI,WAAW,GAAG;AAClC,iBAAe,OAAO,aAAa,QAAQ;AAC/C;ACvKO,MAAM,UAAU;AAAA,EACnB,SAAkD;AAAA,EAClD,MAAM,CAAC,WAAW;AACd,UAAM,IAAI,mCAAmC,MAAM;AAAA,EAEvD;AACJ;"}
@@ -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(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@nine-lab/nine-util")):"function"==typeof define&&define.amd?define(["exports","@nine-lab/nine-util"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).NineFab={},t.NineUtil)}(this,function(t,e){"use strict";var n,i,a,s,o,c,r,l,d,h=t=>{throw TypeError(t)},p=(t,e,n)=>e.has(t)||h("Cannot "+n),v=(t,e,n)=>(p(t,e,"read from private field"),n?n.call(t):e.get(t)),u=(t,e,n)=>e.has(t)?h("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,n),g=(t,e,n,i)=>(p(t,e,"write to private field"),i?i.call(t,n):e.set(t,n),n),f=(t,e,n)=>(p(t,e,"access private method"),n);class m extends HTMLElement{constructor(){super(),u(this,s),u(this,n,""),u(this,i,null),u(this,a,[]),u(this,l,()=>{this.classList.toggle("collapse")}),u(this,d,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&&this.$settings.classList.toggle("expand",!!t.target.closest(".menu-setting"))}),this.attachShadow({mode:"open"})}async connectedCallback(){g(this,n,this.getAttribute("connector-url")||"http://localhost:3002"),f(this,s,c).call(this),await f(this,s,o).call(this)}}n=new WeakMap,i=new WeakMap,a=new WeakMap,s=new WeakSet,o=async function(){this.$textarea=this.shadowRoot.querySelector("#q"),this.$settings=this.shadowRoot.querySelector("nine-ai-settings"),this.shadowRoot.querySelector(".expand-icon").addEventListener("click",v(this,l)),this.shadowRoot.querySelector(".collapse-icon").addEventListener("click",v(this,l)),this.shadowRoot.querySelectorAll(".menu-icon").forEach(t=>t.addEventListener("click",v(this,d))),this.$textarea.addEventListener("keypress",t=>{if("Enter"===t.key&&!t.shiftKey){t.preventDefault();const e=t.target.value.trim();e&&(f(this,s,r).call(this,e),t.target.value="")}});const t=this.getAttribute("route-url");if(t)try{const n=await fetch(t);g(this,a,await n.json()),e.trace.log("✅ 현재 프로젝트 경로 분석 완료",v(this,a))}catch(n){e.trace.error("❌ 경로 로드 실패:",n.message)}},c=function(){const t=this.getAttribute("placeholder")||"나에게 무엇이든 물어봐...",e=this.getAttribute("css-path"),n=e?`@import "${e}";`:"";this.shadowRoot.innerHTML=`\n <style>\n @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@0.1.6/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 <div id="status-tag" style="font-size:10px; color:#ff5722; padding:5px;">Fab Engine Ready</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="gen_target" value="MyBatis" 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="gen_target" value="Service" 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="gen_target" value="Controller" 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="gen_target" value="JavaScript" 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 id="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\t\t\t<div class="expand-icon"></div>\n `},r=async function(t){const i=this.shadowRoot.getElementById("status-tag"),s=Array.from(this.shadowRoot.querySelectorAll('input[name="gen_target"]:checked')).map(t=>t.value);try{i.textContent="⚙️ 공정 분석 중...",e.trace.log(`🚀 Fab 명령 실행: "${t}" [대상: ${s.join(", ")}]`);const o=await fetch(`${v(this,n)}/api/fab/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({command:t,targets:s,projectType:"spring-react",currentRoutes:v(this,a)})}),c=await o.json();if(!c.success)throw new Error(c.error||"생성 실패");e.trace.log("✅ 소스 생성 완료",c.files),i.textContent="✅ 파일 주입 완료",e.nine.alert("소스가 성공적으로 생성되었습니다.").rgb(),this.dispatchEvent(new CustomEvent("nine-fab-completed",{detail:c,bubbles:!0,composed:!0}))}catch(o){e.trace.error("Fab 공정 실패:",o),i.textContent="❌ 에러 발생",e.nine.alert(o.message).rgb().shake()}},l=new WeakMap,d=new WeakMap,e.trace.init("nine-fab","#FF5722"),customElements.get("nine-chat")||customElements.define("nine-chat",m);const b={version:"0.1.6",init:t=>{e.trace.log("🛠️ Nine-Fab Engine initialized",t)}};t.NineChat=m,t.NineFab=b,t.default=b,Object.defineProperties(t,{__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/components/NineChat.js","../src/index.js"],"sourcesContent":["import { nine, trace } from '@nine-lab/nine-util';\n// import { TipPopup } from '@nine-lab/nine-ai'; // 필요시 주석 해제\n\nexport class NineChat extends HTMLElement {\n #connectorUrl = '';\n #tipPopup = null;\n #routes = [];\n\n static {\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 // UI 요소 참조\n this.$textarea = this.shadowRoot.querySelector('#q');\n this.$settings = this.shadowRoot.querySelector('nine-ai-settings');\n\n // 아이콘 이벤트 연결\n this.shadowRoot.querySelector(\".expand-icon\").addEventListener(\"click\", this.#toggleCollapseHandler);\n this.shadowRoot.querySelector(\".collapse-icon\").addEventListener(\"click\", this.#toggleCollapseHandler);\n this.shadowRoot.querySelectorAll(\".menu-icon\").forEach(el => el.addEventListener(\"click\", this.#menuClickHandler));\n\n // [핵심] 텍스트 영역 엔터 이벤트 (Command 전송)\n this.$textarea.addEventListener('keypress', (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n const command = e.target.value.trim();\n if (command) {\n this.#handleFabCommand(command);\n e.target.value = '';\n }\n }\n });\n\n // 기존 경로 정보 로드\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 const customPath = this.getAttribute(\"css-path\");\n const customImport = customPath ? `@import \"${customPath}\";` : \"\";\n const version = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'latest';\n\n this.shadowRoot.innerHTML = `\n <style>\n @import \"https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${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 <div id=\"status-tag\" style=\"font-size:10px; color:#ff5722; padding:5px;\">Fab Engine Ready</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=\"gen_target\" value=\"MyBatis\" 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=\"gen_target\" value=\"Service\" 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=\"gen_target\" value=\"Controller\" 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=\"gen_target\" value=\"JavaScript\" 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 id=\"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\t\t\t<div class=\"expand-icon\"></div>\n `;\n }\n\n async #handleFabCommand(command) {\n const statusEl = this.shadowRoot.getElementById('status-tag');\n\n // 체크된 생성 대상 수집\n const targets = Array.from(this.shadowRoot.querySelectorAll('input[name=\"gen_target\"]:checked'))\n .map(el => el.value);\n\n try {\n statusEl.textContent = \"⚙️ 공정 분석 중...\";\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, // MyBatis, Service 등 선택된 항목 전송\n projectType: 'spring-react',\n currentRoutes: this.#routes\n })\n });\n\n const result = await response.json();\n\n if (!result.success) throw new Error(result.error || \"생성 실패\");\n\n trace.log(\"✅ 소스 생성 완료\", result.files);\n statusEl.textContent = \"✅ 파일 주입 완료\";\n nine.alert(\"소스가 성공적으로 생성되었습니다.\").rgb();\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 }\n }\n\n #toggleCollapseHandler = () => {\n this.classList.toggle(\"collapse\");\n };\n\n #menuClickHandler = (e) => {\n this.shadowRoot.querySelectorAll(\".menu-icon\").forEach(el => el.classList.remove(\"active\"));\n const clickedIcon = e.target.closest(\".menu-icon\");\n if (clickedIcon) clickedIcon.classList.add(\"active\");\n\n // nx-ai-settings 토글 처리\n if (this.$settings) {\n this.$settings.classList.toggle(\"expand\", !!e.target.closest(\".menu-setting\"));\n }\n };\n}\n\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","$textarea","querySelector","addEventListener","__privateGet","key","shiftKey","preventDefault","command","value","trim","routeUrl","res","fetch","json","trace","log","err","error","message","placeholder","customPath","customImport","innerHTML","handleFabCommand_fn","statusEl","getElementById","targets","Array","from","map","textContent","join","response","method","headers","body","JSON","stringify","projectType","currentRoutes","result","success","Error","files","nine","alert","rgb","dispatchEvent","CustomEvent","detail","bubbles","composed","shake","init","customElements","get","define","NineFab","version","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.7",
4
4
  "description": "AI-Driven Full-Stack Code Fabrication Engine",
5
5
  "type": "module",
6
6
  "main": "./dist/nine-fab.umd.js",
@@ -1,5 +1,5 @@
1
1
  import { nine, trace } from '@nine-lab/nine-util';
2
- import { TipPopup } from '@nine-lab/nine-ai';
2
+ // import { TipPopup } from '@nine-lab/nine-ai'; // 필요시 주석 해제
3
3
 
4
4
  export class NineChat extends HTMLElement {
5
5
  #connectorUrl = '';
@@ -7,7 +7,6 @@ export class NineChat extends HTMLElement {
7
7
  #routes = [];
8
8
 
9
9
  static {
10
- // Fab 전용 로깅 컬러로 변경 (색상 차별화)
11
10
  trace.init("nine-fab", "#FF5722");
12
11
  }
13
12
 
@@ -23,15 +22,28 @@ export class NineChat extends HTMLElement {
23
22
  }
24
23
 
25
24
  async #init() {
25
+ // UI 요소 참조
26
+ this.$textarea = this.shadowRoot.querySelector('#q');
27
+ this.$settings = this.shadowRoot.querySelector('nine-ai-settings');
26
28
 
29
+ // 아이콘 이벤트 연결
27
30
  this.shadowRoot.querySelector(".expand-icon").addEventListener("click", this.#toggleCollapseHandler);
28
31
  this.shadowRoot.querySelector(".collapse-icon").addEventListener("click", this.#toggleCollapseHandler);
29
-
30
32
  this.shadowRoot.querySelectorAll(".menu-icon").forEach(el => el.addEventListener("click", this.#menuClickHandler));
31
33
 
34
+ // [핵심] 텍스트 영역 엔터 이벤트 (Command 전송)
35
+ this.$textarea.addEventListener('keypress', (e) => {
36
+ if (e.key === 'Enter' && !e.shiftKey) {
37
+ e.preventDefault();
38
+ const command = e.target.value.trim();
39
+ if (command) {
40
+ this.#handleFabCommand(command);
41
+ e.target.value = '';
42
+ }
43
+ }
44
+ });
32
45
 
33
- // [중요] Fab 엔진은 route-url을 통해 현재 등록된 전체 메뉴를 파악하여
34
- // AI가 중복된 경로를 만들지 않게 하거나, 기존 소스를 참고하게 합니다.
46
+ // 기존 경로 정보 로드
35
47
  const routeUrl = this.getAttribute('route-url');
36
48
  if (routeUrl) {
37
49
  try {
@@ -46,14 +58,13 @@ export class NineChat extends HTMLElement {
46
58
 
47
59
  #render() {
48
60
  const placeholder = this.getAttribute("placeholder") || "나에게 무엇이든 물어봐...";
49
-
50
61
  const customPath = this.getAttribute("css-path");
51
62
  const customImport = customPath ? `@import "${customPath}";` : "";
63
+ const version = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'latest';
52
64
 
53
65
  this.shadowRoot.innerHTML = `
54
66
  <style>
55
- /* Fab 전용 스타일 (nomenu와 차별화 추천) */
56
- @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${__APP_VERSION__}/dist/css/nine-fab.css";
67
+ @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${version}/dist/css/nine-fab.css";
57
68
  ${customImport}
58
69
  </style>
59
70
 
@@ -63,28 +74,29 @@ export class NineChat extends HTMLElement {
63
74
  <div class="container">
64
75
  <div class="head">
65
76
  <div class="logo"><span></span></div>
77
+ <div id="status-tag" style="font-size:10px; color:#ff5722; padding:5px;">Fab Engine Ready</div>
66
78
  </div>
67
79
  <nx-ai-chat></nx-ai-chat>
68
80
  <div class="foot">
69
81
  <div class="apply-src">
70
82
  <div>
71
- <input type="checkbox" id="mybatis" name="mybatis" value="Y" checked />
83
+ <input type="checkbox" id="mybatis" name="gen_target" value="MyBatis" checked />
72
84
  <label for="mybatis">MyBatis</label>
73
85
  </div>
74
86
  <div>
75
- <input type="checkbox" id="service" name="service" value="Y" checked />
87
+ <input type="checkbox" id="service" name="gen_target" value="Service" checked />
76
88
  <label for="service">Service</label>
77
89
  </div>
78
90
  <div>
79
- <input type="checkbox" id="controller" name="controller" value="Y" checked />
91
+ <input type="checkbox" id="controller" name="gen_target" value="Controller" checked />
80
92
  <label for="controller">Controller</label>
81
93
  </div>
82
94
  <div>
83
- <input type="checkbox" id="javascript" name="javascript" value="Y" checked />
95
+ <input type="checkbox" id="javascript" name="gen_target" value="JavaScript" checked />
84
96
  <label for="javascript">JavaScript</label>
85
97
  </div>
86
98
  </div>
87
- <textarea name="ask" id="q" name="q" rows="4" placeholder="${placeholder}"></textarea>
99
+ <textarea id="q" rows="4" placeholder="${placeholder}"></textarea>
88
100
  </div>
89
101
  </div>
90
102
  <div class="menu">
@@ -94,62 +106,40 @@ export class NineChat extends HTMLElement {
94
106
  <div class="menu-icon menu-setting"></div>
95
107
  </div>
96
108
  </div>
97
-
98
109
  <div class="expand-icon"></div>
99
110
  `;
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
111
  }
118
112
 
119
- /**
120
- * [핵심] 소스 코드 생성 및 파일 주입 요청
121
- */
122
113
  async #handleFabCommand(command) {
123
- const statusEl = this.shadowRoot.getElementById('status');
124
- try {
125
- statusEl.textContent = "AI가 설계를 분석 중입니다...";
126
- await this.#tipPopup?.popup();
114
+ const statusEl = this.shadowRoot.getElementById('status-tag');
115
+
116
+ // 체크된 생성 대상 수집
117
+ const targets = Array.from(this.shadowRoot.querySelectorAll('input[name="gen_target"]:checked'))
118
+ .map(el => el.value);
127
119
 
128
- trace.log(`🚀 소스 생성 명령 전송: "${command}"`);
120
+ try {
121
+ statusEl.textContent = "⚙️ 공정 분석 중...";
122
+ trace.log(`🚀 Fab 명령 실행: "${command}" [대상: ${targets.join(', ')}]`);
129
123
 
130
- // [변경] nomenu/go -> fab/generate
131
124
  const response = await fetch(`${this.#connectorUrl}/api/fab/generate`, {
132
125
  method: 'POST',
133
126
  headers: { 'Content-Type': 'application/json' },
134
127
  body: JSON.stringify({
135
128
  command,
136
- projectType: 'spring-react', // 템플릿 정보
137
- currentRoutes: this.#routes // 기존 경로 정보 (참고용)
129
+ targets, // MyBatis, Service 등 선택된 항목 전송
130
+ projectType: 'spring-react',
131
+ currentRoutes: this.#routes
138
132
  })
139
133
  });
140
134
 
141
135
  const result = await response.json();
142
136
 
143
- if (!result.success) {
144
- throw new Error(result.error || "소스 생성 중 오류가 발생했습니다.");
145
- }
146
-
147
- trace.log("✅ 소스 생성 및 파일 주입 완료!", result.files);
148
- statusEl.textContent = "✅ 생성 완료! 잠시 후 화면이 갱신됩니다.";
137
+ if (!result.success) throw new Error(result.error || "생성 실패");
149
138
 
150
- nine.alert("소스 생성이 완료되었습니다!").rgb();
139
+ trace.log("소스 생성 완료", result.files);
140
+ statusEl.textContent = "✅ 파일 주입 완료";
141
+ nine.alert("소스가 성공적으로 생성되었습니다.").rgb();
151
142
 
152
- // [알림] 생성 성공 시 프레임워크에 알림을 줘서 메뉴를 새로고침하게 함
153
143
  this.dispatchEvent(new CustomEvent('nine-fab-completed', {
154
144
  detail: result,
155
145
  bubbles: true,
@@ -158,10 +148,8 @@ export class NineChat extends HTMLElement {
158
148
 
159
149
  } catch (error) {
160
150
  trace.error("Fab 공정 실패:", error);
161
- statusEl.textContent = "❌ 생성 실패";
151
+ statusEl.textContent = "❌ 에러 발생";
162
152
  nine.alert(error.message).rgb().shake();
163
- } finally {
164
- this.#tipPopup?.close();
165
153
  }
166
154
  }
167
155
 
@@ -170,20 +158,17 @@ export class NineChat extends HTMLElement {
170
158
  };
171
159
 
172
160
  #menuClickHandler = (e) => {
173
-
174
- // 모든 `.menu-icon`에서 `active` 클래스 제거
175
161
  this.shadowRoot.querySelectorAll(".menu-icon").forEach(el => el.classList.remove("active"));
176
-
177
- // 클릭한 `.menu-icon`에 `active` 클래스 추가
178
162
  const clickedIcon = e.target.closest(".menu-icon");
179
163
  if (clickedIcon) clickedIcon.classList.add("active");
180
164
 
181
- // `.menu-setting`이 클릭되었는지 확인 후 `nx-ai-settings` 토글
182
- this.settings.classList.toggle("expand", !!e.target.closest(".menu-setting"));
165
+ // nx-ai-settings 토글 처리
166
+ if (this.$settings) {
167
+ this.$settings.classList.toggle("expand", !!e.target.closest(".menu-setting"));
168
+ }
183
169
  };
184
170
  }
185
171
 
186
- // 1. 웹컴포넌트 등록
187
172
  if (!customElements.get('nine-chat')) {
188
173
  customElements.define('nine-chat', NineChat);
189
174
  }