@nine-lab/nine-fab 0.1.7 → 0.1.9

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,158 +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
+ 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
+ }
11
58
  class NineChat extends HTMLElement {
12
59
  constructor() {
13
60
  super();
14
61
  __privateAdd(this, _NineChat_instances);
15
- __privateAdd(this, _connectorUrl, "");
16
- __privateAdd(this, _tipPopup, null);
62
+ __privateAdd(this, _service, null);
17
63
  __privateAdd(this, _routes, []);
18
- __privateAdd(this, _toggleCollapseHandler, () => {
19
- this.classList.toggle("collapse");
20
- });
21
- __privateAdd(this, _menuClickHandler, (e) => {
22
- this.shadowRoot.querySelectorAll(".menu-icon").forEach((el) => el.classList.remove("active"));
23
- const clickedIcon = e.target.closest(".menu-icon");
24
- if (clickedIcon) clickedIcon.classList.add("active");
25
- if (this.$settings) {
26
- this.$settings.classList.toggle("expand", !!e.target.closest(".menu-setting"));
27
- }
28
- });
29
64
  this.attachShadow({ mode: "open" });
30
65
  }
31
66
  async connectedCallback() {
32
- __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));
33
69
  __privateMethod(this, _NineChat_instances, render_fn).call(this);
34
- 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")));
35
73
  }
36
74
  }
37
- _connectorUrl = new WeakMap();
38
- _tipPopup = new WeakMap();
75
+ _service = new WeakMap();
39
76
  _routes = new WeakMap();
40
77
  _NineChat_instances = new WeakSet();
41
- init_fn = async function() {
42
- this.$textarea = this.shadowRoot.querySelector("#q");
43
- this.$settings = this.shadowRoot.querySelector("nine-ai-settings");
44
- this.shadowRoot.querySelector(".expand-icon").addEventListener("click", __privateGet(this, _toggleCollapseHandler));
45
- this.shadowRoot.querySelector(".collapse-icon").addEventListener("click", __privateGet(this, _toggleCollapseHandler));
46
- this.shadowRoot.querySelectorAll(".menu-icon").forEach((el) => el.addEventListener("click", __privateGet(this, _menuClickHandler)));
47
- this.$textarea.addEventListener("keypress", (e) => {
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) => {
48
99
  if (e.key === "Enter" && !e.shiftKey) {
49
100
  e.preventDefault();
50
101
  const command = e.target.value.trim();
51
- if (command) {
52
- __privateMethod(this, _NineChat_instances, handleFabCommand_fn).call(this, command);
53
- e.target.value = "";
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();
54
114
  }
55
115
  }
56
116
  });
57
- const routeUrl = this.getAttribute("route-url");
58
- if (routeUrl) {
59
- try {
60
- const res = await fetch(routeUrl);
61
- __privateSet(this, _routes, await res.json());
62
- trace.log("✅ 현재 프로젝트 경로 분석 완료", __privateGet(this, _routes));
63
- } catch (err) {
64
- trace.error("❌ 경로 로드 실패:", err.message);
65
- }
66
- }
67
117
  };
68
118
  render_fn = function() {
69
119
  const placeholder = this.getAttribute("placeholder") || "나에게 무엇이든 물어봐...";
70
- const customPath = this.getAttribute("css-path");
71
- const customImport = customPath ? `@import "${customPath}";` : "";
72
- const version = "0.1.6";
120
+ const customImport = this.getAttribute("css-path") ? `@import "${this.getAttribute("css-path")}";` : "";
73
121
  this.shadowRoot.innerHTML = `
74
122
  <style>
75
- @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${version}/dist/css/nine-fab.css";
123
+ @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${"0.1.8"}/dist/css/nine-fab.css";
76
124
  ${customImport}
77
125
  </style>
78
-
79
126
  <div class="wrapper">
80
- <nine-ai-settings></nine-ai-settings>
81
-
82
- <div class="container">
83
- <div class="head">
84
- <div class="logo"><span></span></div>
127
+ <nine-ai-settings></nine-ai-settings>
128
+ <div class="container">
129
+ <div class="head">
130
+ <div class="logo"><span></span></div>
85
131
  <div id="status-tag" style="font-size:10px; color:#ff5722; padding:5px;">Fab Engine Ready</div>
86
- </div>
87
- <nx-ai-chat></nx-ai-chat>
88
- <div class="foot">
89
- <div class="apply-src">
90
- <div>
91
- <input type="checkbox" id="mybatis" name="gen_target" value="MyBatis" checked />
92
- <label for="mybatis">MyBatis</label>
93
- </div>
94
- <div>
95
- <input type="checkbox" id="service" name="gen_target" value="Service" checked />
96
- <label for="service">Service</label>
97
- </div>
98
- <div>
99
- <input type="checkbox" id="controller" name="gen_target" value="Controller" checked />
100
- <label for="controller">Controller</label>
101
- </div>
102
- <div>
103
- <input type="checkbox" id="javascript" name="gen_target" value="JavaScript" checked />
104
- <label for="javascript">JavaScript</label>
105
- </div>
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>
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>
118
154
  `;
119
155
  };
120
- handleFabCommand_fn = async function(command) {
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);
123
- try {
124
- statusEl.textContent = "⚙️ 공정 분석 중...";
125
- trace.log(`🚀 Fab 명령 실행: "${command}" [대상: ${targets.join(", ")}]`);
126
- const response = await fetch(`${__privateGet(this, _connectorUrl)}/api/fab/generate`, {
127
- method: "POST",
128
- headers: { "Content-Type": "application/json" },
129
- body: JSON.stringify({
130
- command,
131
- targets,
132
- // MyBatis, Service 등 선택된 항목 전송
133
- projectType: "spring-react",
134
- currentRoutes: __privateGet(this, _routes)
135
- })
136
- });
137
- const result = await response.json();
138
- if (!result.success) throw new Error(result.error || "생성 실패");
139
- trace.log("✅ 소스 생성 완료", result.files);
140
- statusEl.textContent = "✅ 파일 주입 완료";
141
- nine.alert("소스가 성공적으로 생성되었습니다.").rgb();
142
- this.dispatchEvent(new CustomEvent("nine-fab-completed", {
143
- detail: result,
144
- bubbles: true,
145
- composed: true
146
- }));
147
- } catch (error) {
148
- trace.error("Fab 공정 실패:", error);
149
- statusEl.textContent = "❌ 에러 발생";
150
- nine.alert(error.message).rgb().shake();
151
- }
152
- };
153
- _toggleCollapseHandler = new WeakMap();
154
- _menuClickHandler = new WeakMap();
155
- trace.init("nine-fab", "#FF5722");
156
- if (!customElements.get("nine-chat")) {
157
- customElements.define("nine-chat", NineChat);
158
- }
159
156
  const NineFab = {
160
- version: "0.1.6",
157
+ version: "0.1.8",
161
158
  init: (config) => {
162
159
  trace.log("🛠️ Nine-Fab Engine initialized", config);
163
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';\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":"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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")):"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"}})});
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.8/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.8",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';\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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"}
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.7",
3
+ "version": "0.1.9",
4
4
  "description": "AI-Driven Full-Stack Code Fabrication Engine",
5
5
  "type": "module",
6
6
  "main": "./dist/nine-fab.umd.js",
@@ -1,174 +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
- trace.init("nine-fab", "#FF5722");
11
- }
12
-
13
8
  constructor() {
14
9
  super();
15
10
  this.attachShadow({ mode: 'open' });
16
11
  }
17
12
 
18
13
  async connectedCallback() {
19
- this.#connectorUrl = this.getAttribute('connector-url') || 'http://localhost:3002';
14
+ // 서비스 초기화
15
+ const connectorUrl = this.getAttribute('connector-url') || 'http://localhost:3002';
16
+ this.#service = new NineFabService(connectorUrl);
17
+
20
18
  this.#render();
21
- await this.#init();
19
+ this.#initInteractions(); // UI 토글 로직
20
+ this.#initActions(); // 엔터 시 실행 로직
21
+
22
+ // 경로 데이터 로드
23
+ this.#routes = await this.#service.fetchRoutes(this.getAttribute('route-url'));
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
+ // --- [그룹 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
+ }
28
44
 
29
- // 아이콘 이벤트 연결
30
- this.shadowRoot.querySelector(".expand-icon").addEventListener("click", this.#toggleCollapseHandler);
31
- this.shadowRoot.querySelector(".collapse-icon").addEventListener("click", this.#toggleCollapseHandler);
32
- this.shadowRoot.querySelectorAll(".menu-icon").forEach(el => el.addEventListener("click", this.#menuClickHandler));
45
+ // --- [그룹 2: Action] 엔터키 입력 시 서비스 호출 ---
46
+ #initActions() {
47
+ const $textarea = this.shadowRoot.querySelector('#q');
48
+ const $status = this.shadowRoot.querySelector('#status-tag');
33
49
 
34
- // [핵심] 텍스트 영역 엔터 이벤트 (Command 전송)
35
- this.$textarea.addEventListener('keypress', (e) => {
50
+ $textarea.addEventListener('keypress', async (e) => {
36
51
  if (e.key === 'Enter' && !e.shiftKey) {
37
52
  e.preventDefault();
38
53
  const command = e.target.value.trim();
39
- if (command) {
40
- this.#handleFabCommand(command);
41
- e.target.value = '';
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();
42
76
  }
43
77
  }
44
78
  });
45
-
46
- // 기존 경로 정보 로드
47
- const routeUrl = this.getAttribute('route-url');
48
- if (routeUrl) {
49
- try {
50
- const res = await fetch(routeUrl);
51
- this.#routes = await res.json();
52
- trace.log("✅ 현재 프로젝트 경로 분석 완료", this.#routes);
53
- } catch (err) {
54
- trace.error("❌ 경로 로드 실패:", err.message);
55
- }
56
- }
57
79
  }
58
80
 
59
81
  #render() {
60
82
  const placeholder = this.getAttribute("placeholder") || "나에게 무엇이든 물어봐...";
61
- const customPath = this.getAttribute("css-path");
62
- const customImport = customPath ? `@import "${customPath}";` : "";
63
- const version = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'latest';
83
+ const customImport = this.getAttribute("css-path") ? `@import "${this.getAttribute("css-path")}";` : "";
64
84
 
65
85
  this.shadowRoot.innerHTML = `
66
86
  <style>
67
- @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${version}/dist/css/nine-fab.css";
87
+ @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-fab@${__APP_VERSION__}/dist/css/nine-fab.css";
68
88
  ${customImport}
69
89
  </style>
70
-
71
90
  <div class="wrapper">
72
- <nine-ai-settings></nine-ai-settings>
73
-
74
- <div class="container">
75
- <div class="head">
76
- <div class="logo"><span></span></div>
91
+ <nine-ai-settings></nine-ai-settings>
92
+ <div class="container">
93
+ <div class="head">
94
+ <div class="logo"><span></span></div>
77
95
  <div id="status-tag" style="font-size:10px; color:#ff5722; padding:5px;">Fab Engine Ready</div>
78
- </div>
79
- <nx-ai-chat></nx-ai-chat>
80
- <div class="foot">
81
- <div class="apply-src">
82
- <div>
83
- <input type="checkbox" id="mybatis" name="gen_target" value="MyBatis" checked />
84
- <label for="mybatis">MyBatis</label>
85
- </div>
86
- <div>
87
- <input type="checkbox" id="service" name="gen_target" value="Service" checked />
88
- <label for="service">Service</label>
89
- </div>
90
- <div>
91
- <input type="checkbox" id="controller" name="gen_target" value="Controller" checked />
92
- <label for="controller">Controller</label>
93
- </div>
94
- <div>
95
- <input type="checkbox" id="javascript" name="gen_target" value="JavaScript" checked />
96
- <label for="javascript">JavaScript</label>
97
- </div>
98
- </div>
99
- <textarea id="q" rows="4" placeholder="${placeholder}"></textarea>
100
- </div>
101
- </div>
102
- <div class="menu">
103
- <div class="collapse-icon"></div>
104
- <div class="menu-icon menu-filter active"></div>
105
- <div class="menu-icon menu-general"></div>
106
- <div class="menu-icon menu-setting"></div>
107
- </div>
108
- </div>
109
- <div class="expand-icon"></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>
110
118
  `;
111
119
  }
112
-
113
- async #handleFabCommand(command) {
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);
119
-
120
- try {
121
- statusEl.textContent = "⚙️ 공정 분석 중...";
122
- trace.log(`🚀 Fab 명령 실행: "${command}" [대상: ${targets.join(', ')}]`);
123
-
124
- const response = await fetch(`${this.#connectorUrl}/api/fab/generate`, {
125
- method: 'POST',
126
- headers: { 'Content-Type': 'application/json' },
127
- body: JSON.stringify({
128
- command,
129
- targets, // MyBatis, Service 등 선택된 항목 전송
130
- projectType: 'spring-react',
131
- currentRoutes: this.#routes
132
- })
133
- });
134
-
135
- const result = await response.json();
136
-
137
- if (!result.success) throw new Error(result.error || "생성 실패");
138
-
139
- trace.log("✅ 소스 생성 완료", result.files);
140
- statusEl.textContent = "✅ 파일 주입 완료";
141
- nine.alert("소스가 성공적으로 생성되었습니다.").rgb();
142
-
143
- this.dispatchEvent(new CustomEvent('nine-fab-completed', {
144
- detail: result,
145
- bubbles: true,
146
- composed: true
147
- }));
148
-
149
- } catch (error) {
150
- trace.error("Fab 공정 실패:", error);
151
- statusEl.textContent = "❌ 에러 발생";
152
- nine.alert(error.message).rgb().shake();
153
- }
154
- }
155
-
156
- #toggleCollapseHandler = () => {
157
- this.classList.toggle("collapse");
158
- };
159
-
160
- #menuClickHandler = (e) => {
161
- this.shadowRoot.querySelectorAll(".menu-icon").forEach(el => el.classList.remove("active"));
162
- const clickedIcon = e.target.closest(".menu-icon");
163
- if (clickedIcon) clickedIcon.classList.add("active");
164
-
165
- // nx-ai-settings 토글 처리
166
- if (this.$settings) {
167
- this.$settings.classList.toggle("expand", !!e.target.closest(".menu-setting"));
168
- }
169
- };
170
- }
171
-
172
- if (!customElements.get('nine-chat')) {
173
- customElements.define('nine-chat', NineChat);
174
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
+ }