@nine-lab/nine-mu 0.1.279 → 0.1.280

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nine-lab/nine-mu",
3
- "version": "0.1.279",
3
+ "version": "0.1.280",
4
4
  "description": "AI-Driven Full-Stack Code Fabrication Engine",
5
5
  "type": "module",
6
6
  "main": "./dist/nine-mu.umd.js",
@@ -4,8 +4,8 @@ import '@nine-lab/nine-util';
4
4
 
5
5
  export class NineDiffPopup extends HTMLElement {
6
6
  #dialog = null;
7
- #diffView = null; // Container 대신 직접 에디터 뷰를 참조
8
- #asisBackup = "";
7
+ #tabContainer = null;
8
+ #filesMap = new Map(); // 각 탭(파일)별 원본 및 에디터 뷰 인스턴스, 메타데이터 관리를 위한 맵
9
9
  #host;
10
10
 
11
11
  constructor() {
@@ -14,107 +14,167 @@ export class NineDiffPopup extends HTMLElement {
14
14
  }
15
15
 
16
16
  connectedCallback() {
17
-
18
17
  this.#host = this.getRootNode().host;
19
-
20
- //console.log("찾은 호스트:", this.#host);
21
-
22
- this.#render();
18
+ this.#renderScaffolding();
23
19
  }
24
20
 
25
- #render() {
21
+ /**
22
+ * 💡 기본 다이얼로그 및 레이아웃 스케폴딩 렌더링
23
+ */
24
+ #renderScaffolding() {
26
25
  const customImport = this.getAttribute("css-path") ? `@import "${this.getAttribute("css-path")}";` : "";
26
+ const appVersion = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : 'latest';
27
27
 
28
28
  this.shadowRoot.innerHTML = `
29
- <style>
30
- @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-mu@${__APP_VERSION__}/dist/css/nine-mu.css";
29
+ <style>
30
+ @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-mu@${appVersion}/dist/css/nine-mu.css";
31
31
  ${customImport}
32
+
33
+ .main-layout {
34
+ display: flex;
35
+ flex-direction: column;
36
+ height: 80vh;
37
+ width: 85vw;
38
+ min-width: 800px;
39
+ }
40
+ .tab-area {
41
+ flex: 1;
42
+ overflow: hidden;
43
+ display: flex;
44
+ flex-direction: column;
45
+ }
46
+ nine-tab {
47
+ flex: 1;
48
+ display: flex;
49
+ flex-direction: column;
50
+ height: 100%;
51
+ }
52
+ .diff-wrapper {
53
+ height: calc(80vh - 120px); /* 푸터와 탭 헤더 높이를 제외한 에디터 영역 확보 */
54
+ width: 100%;
55
+ }
56
+ .footer {
57
+ display: flex;
58
+ justify-content: flex-end;
59
+ gap: 10px;
60
+ padding: 15px 0 0 0;
61
+ border-top: 1px solid #e5e7eb;
62
+ }
32
63
  </style>
33
64
 
34
65
  <nine-dialog>
35
66
  <div class="main-layout">
36
- <div class="diff-area">
37
- <nine-diff></nine-diff>
67
+ <div class="tab-area">
68
+ <nine-tab theme="theme-3"></nine-tab>
38
69
  </div>
39
70
  <div class="footer">
40
71
  <button class="btn btn-cancel">취소</button>
41
- <button class="btn btn-confirm">저장</button>
72
+ <button class="btn btn-confirm">모두 저장</button>
42
73
  </div>
43
74
  </div>
44
75
  </nine-dialog>
45
76
  `;
46
77
 
47
78
  this.#dialog = this.shadowRoot.querySelector('nine-dialog');
48
- this.#diffView = this.shadowRoot.querySelector('nine-diff');
79
+ this.#tabContainer = this.shadowRoot.querySelector('nine-tab');
49
80
 
50
- this.shadowRoot.querySelector('.btn-confirm').onclick = () => this.#handleConfirm();
81
+ this.shadowRoot.querySelector('.btn-confirm').onclick = () => this.#handleConfirmAll();
51
82
  this.shadowRoot.querySelector('.btn-cancel').onclick = () => this.#handleCancel();
52
83
  }
53
84
 
54
85
  popup() {
55
- // 초기화
56
86
  this.#dialog.showModal();
57
-
58
87
  return this;
59
88
  }
60
89
 
61
90
  /**
62
- * @param {string|object} asis - 소스 텍스트 또는 url:/file: 경로
63
- * @param {string|object} tobe - 소스 텍스트 또는 url:/file: 경로
64
- * @param {string} lang - 언어 설정
91
+ * 💡 [핵심 고도화] 여러 파일 리스트를 받아 동적으로 탭을 생성하고 데이터를 주입하는 함수
92
+ * @param {Array} fileList - [{ layer: "MyBatis", full_path: "...", asis_source: "...", source: "..." }]
65
93
  */
66
- async data(asis, tobe, lang = "javascript") {
67
- // 1. 데이터를 읽어오는 공통 로직 (내부 헬퍼)
68
- const loadSource = async (src) => {
69
- if (typeof src === 'string' && (src.startsWith('url:') || src.startsWith('file:'))) {
70
- const targetUrl = src.replace(/^(url:|file:)/, '');
71
- try {
72
- trace.log(`📡 리모트 로드: ${targetUrl}`);
73
- const res = await fetch(targetUrl);
74
- if (!res.ok) throw new Error(`Status: ${res.status}`);
75
- return await res.text();
76
- } catch (e) {
77
- trace.error(`로드 실패 [${targetUrl}]:`, e);
78
- return `// 로드 실패: ${targetUrl}\n${e.message}`;
79
- }
80
- }
81
- return src;
94
+ async data(fileList) {
95
+ if (!Array.isArray(fileList) || fileList.length === 0) return this;
96
+
97
+ // 기존에 빌드된 탭이 있다면 초기화
98
+ this.#tabContainer.innerHTML = "";
99
+ this.#filesMap.clear();
100
+
101
+ // 확장자 기반 주석/언어(Language) 유추 헬퍼
102
+ const detectLanguage = (path) => {
103
+ const lower = (path || "").toLowerCase();
104
+ if (lower.endsWith('.xml')) return 'xml';
105
+ if (lower.endsWith('.java')) return 'java';
106
+ if (lower.endsWith('.json')) return 'json';
107
+ return 'javascript';
82
108
  };
83
109
 
84
- // 2. asis, tobe 비동기로 병렬 로드
85
- const [finalAsis, finalTobe] = await Promise.all([
86
- loadSource(asis),
87
- loadSource(tobe)
88
- ]);
89
-
90
- this.#asisBackup = finalAsis;
91
-
92
- // 3. 에디터 준비 시 데이터 주입
93
- this.#diffView.addEventListener('ready', () => {
94
- const asisStr = typeof finalAsis === 'object' ? JSON.stringify(finalAsis, null, 2) : finalAsis;
95
- const tobeStr = typeof finalTobe === 'object' ? JSON.stringify(finalTobe, null, 2) : finalTobe;
110
+ // 파일별로 nine-tab-page nine-diff 엘리먼트 동적 빌드
111
+ fileList.forEach((file, index) => {
112
+ const { layer, full_path, asis_source, source } = file;
113
+ const lang = detectLanguage(full_path);
114
+ const caption = `${layer} (${full_path.split('/').pop()})`; // 탭 캡션 명세 (레이어명 + 파일명)
115
+
116
+ // 1. 탭 페이지 템플릿 마크업 생성
117
+ const tabPage = document.createElement('nine-tab-page');
118
+ tabPage.setAttribute('caption', caption);
119
+
120
+ const diffWrapper = document.createElement('div');
121
+ diffWrapper.className = 'diff-wrapper';
122
+
123
+ const diffView = document.createElement('nine-diff');
124
+ diffWrapper.appendChild(diffView);
125
+ tabPage.appendChild(diffWrapper);
126
+ this.#tabContainer.appendChild(tabPage);
127
+
128
+ // 2. 파일별 변경 상태 관리를 위해 맵(Map)에 정보 보존
129
+ const fileKey = `file_tab_${index}`;
130
+ this.#filesMap.set(fileKey, {
131
+ layer,
132
+ fullPath: full_path,
133
+ asisSource: asis_source,
134
+ diffView: diffView
135
+ });
96
136
 
97
- this.#diffView.initialize(asisStr, tobeStr, lang);
98
- }, { once: true });
137
+ // 3. 각 Web Component 에디터가 준비 완료(ready)되면 순수 소스 매핑 및 초기화
138
+ diffView.addEventListener('ready', () => {
139
+ diffView.initialize(asis_source || "", source || "", lang);
140
+ }, { once: true });
141
+ });
99
142
 
100
143
  return this;
101
144
  }
102
145
 
146
+ /**
147
+ * 💡 [일괄 처리 갱신] 오픈된 모든 탭의 수정한 수정본 코드를 취합하여 백엔드로 API 일괄 전송
148
+ */
149
+ async #handleConfirmAll() {
150
+ const payloadList = [];
151
+
152
+ // 맵을 순회하며 변경 완료된 오른쪽 창(tobe)의 소스코드를 추출
153
+ for (const [key, fileObj] of this.#filesMap.entries()) {
154
+ const { diffView, fullPath, asisSource, layer } = fileObj;
103
155
 
104
- #handleConfirm() {
105
- // 에디터에서 직접 콘텐츠 추출
106
- const content = this.#diffView ? this.#diffView.getContents() : this.#asisBackup;
156
+ // 사용자가 수정한 최종 TO-BE 코드를 추출 (에디터가 없거나 로드 전이면 원본 유지)
157
+ const currentContent = diffView ? diffView.getContents() : asisSource;
107
158
 
108
- const params = {
109
- packageName: this.#host.getAttribute('package-name'),
110
- contents: content,
159
+ payloadList.push({
160
+ layer,
161
+ fullPath: fullPath,
162
+ packageName: this.#host?.getAttribute('package-name') || "com.ninelab.ai",
163
+ contents: currentContent
164
+ });
111
165
  }
112
166
 
113
- api.post(`/nine-mu/source/generateJsonFile`, params).then(res => {
114
- nine.alert("소스를 변경하였습니다.").then(res => {
115
- this.#dialog.close();
116
- });
117
- });
167
+ try {
168
+ // 변경 사항을 일괄 전송하는 백엔드 규격 API 호출
169
+ // (컨트롤러 측 구조에 맞춰 묶음 발송 처리 하거나 루프를 활용하세요)
170
+ await api.post(`/nine-mu/source/generateJsonFileBatch`, { fileList: payloadList });
171
+
172
+ await nine.alert("요청하신 모든 파일의 소스 변경 사항이 성공적으로 반영되었습니다.");
173
+ this.#dialog.close();
174
+ } catch (error) {
175
+ trace.error("일괄 파일 저장 처리 중 실패:", error);
176
+ nine.alert("소스 저장 중 오류가 발생했습니다.");
177
+ }
118
178
  }
119
179
 
120
180
  #handleCancel() {
@@ -0,0 +1,125 @@
1
+ import { trace } from '@nopeer';
2
+ import { api, nine } from '@nine-lab/nine-util';
3
+ import '@nine-lab/nine-util';
4
+
5
+ export class NineDiffPopup extends HTMLElement {
6
+ #dialog = null;
7
+ #diffView = null; // Container 대신 직접 에디터 뷰를 참조
8
+ #asisBackup = "";
9
+ #host;
10
+
11
+ constructor() {
12
+ super();
13
+ this.attachShadow({ mode: 'open' });
14
+ }
15
+
16
+ connectedCallback() {
17
+
18
+ this.#host = this.getRootNode().host;
19
+
20
+ //console.log("찾은 호스트:", this.#host);
21
+
22
+ this.#render();
23
+ }
24
+
25
+ #render() {
26
+ const customImport = this.getAttribute("css-path") ? `@import "${this.getAttribute("css-path")}";` : "";
27
+
28
+ this.shadowRoot.innerHTML = `
29
+ <style>
30
+ @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-mu@${__APP_VERSION__}/dist/css/nine-mu.css";
31
+ ${customImport}
32
+ </style>
33
+
34
+ <nine-dialog>
35
+ <div class="main-layout">
36
+ <div class="diff-area">
37
+ <nine-diff></nine-diff>
38
+ </div>
39
+ <div class="footer">
40
+ <button class="btn btn-cancel">취소</button>
41
+ <button class="btn btn-confirm">저장</button>
42
+ </div>
43
+ </div>
44
+ </nine-dialog>
45
+ `;
46
+
47
+ this.#dialog = this.shadowRoot.querySelector('nine-dialog');
48
+ this.#diffView = this.shadowRoot.querySelector('nine-diff');
49
+
50
+ this.shadowRoot.querySelector('.btn-confirm').onclick = () => this.#handleConfirm();
51
+ this.shadowRoot.querySelector('.btn-cancel').onclick = () => this.#handleCancel();
52
+ }
53
+
54
+ popup() {
55
+ // 초기화
56
+ this.#dialog.showModal();
57
+
58
+ return this;
59
+ }
60
+
61
+ /**
62
+ * @param {string|object} asis - 소스 텍스트 또는 url:/file: 경로
63
+ * @param {string|object} tobe - 소스 텍스트 또는 url:/file: 경로
64
+ * @param {string} lang - 언어 설정
65
+ */
66
+ async data(asis, tobe, lang = "javascript") {
67
+ // 1. 데이터를 읽어오는 공통 로직 (내부 헬퍼)
68
+ const loadSource = async (src) => {
69
+ if (typeof src === 'string' && (src.startsWith('url:') || src.startsWith('file:'))) {
70
+ const targetUrl = src.replace(/^(url:|file:)/, '');
71
+ try {
72
+ trace.log(`📡 리모트 로드: ${targetUrl}`);
73
+ const res = await fetch(targetUrl);
74
+ if (!res.ok) throw new Error(`Status: ${res.status}`);
75
+ return await res.text();
76
+ } catch (e) {
77
+ trace.error(`로드 실패 [${targetUrl}]:`, e);
78
+ return `// 로드 실패: ${targetUrl}\n${e.message}`;
79
+ }
80
+ }
81
+ return src;
82
+ };
83
+
84
+ // 2. asis, tobe 둘 다 비동기로 병렬 로드
85
+ const [finalAsis, finalTobe] = await Promise.all([
86
+ loadSource(asis),
87
+ loadSource(tobe)
88
+ ]);
89
+
90
+ this.#asisBackup = finalAsis;
91
+
92
+ // 3. 에디터 준비 시 데이터 주입
93
+ this.#diffView.addEventListener('ready', () => {
94
+ const asisStr = typeof finalAsis === 'object' ? JSON.stringify(finalAsis, null, 2) : finalAsis;
95
+ const tobeStr = typeof finalTobe === 'object' ? JSON.stringify(finalTobe, null, 2) : finalTobe;
96
+
97
+ this.#diffView.initialize(asisStr, tobeStr, lang);
98
+ }, { once: true });
99
+
100
+ return this;
101
+ }
102
+
103
+
104
+ #handleConfirm() {
105
+ // 에디터에서 직접 콘텐츠 추출
106
+ const content = this.#diffView ? this.#diffView.getContents() : this.#asisBackup;
107
+
108
+ const params = {
109
+ packageName: this.#host.getAttribute('package-name'),
110
+ contents: content,
111
+ }
112
+
113
+ api.post(`/nine-mu/source/generateJsonFile`, params).then(res => {
114
+ nine.alert("소스를 변경하였습니다.").then(res => {
115
+ this.#dialog.close();
116
+ });
117
+ });
118
+ }
119
+
120
+ #handleCancel() {
121
+ this.#dialog.close();
122
+ }
123
+ }
124
+
125
+ customElements.define("nine-diff-popup", NineDiffPopup);
@@ -46,14 +46,14 @@ export class NotificationHandler {
46
46
  switch (logger) {
47
47
  case "generate-source-brain":
48
48
  if (data && data.source) {
49
- trace.log(`[시그널 감지] 설계 완료`, data);
49
+ //trace.log(`[시그널 감지] 설계 완료`, data);
50
50
  this.#generateSource(data);
51
51
  }
52
52
  break;
53
53
 
54
54
  case "modify-source-brain":
55
55
  if (data && data.source) {
56
- trace.log(`[시그널 감지] 설계 완료`, data);
56
+ //trace.log(`[시그널 감지] 설계 완료`, data);
57
57
  this.#modifySource(data);
58
58
  }
59
59
  break;
@@ -62,7 +62,7 @@ export class NotificationHandler {
62
62
  }
63
63
 
64
64
  #modifySource = async (data) => {
65
- console.log(data.source);
65
+ console.log(data);
66
66
  };
67
67
 
68
68
  /**
@@ -83,9 +83,5 @@ export class NotificationHandler {
83
83
  });
84
84
 
85
85
  trace.log(res)
86
-
87
-
88
- // TODO: 추후 다른 거 개발 완료 시, 여기에 실제 로컬 물리 파일 라이터 서비스 연결
89
- // Example: FileWritableService.write(sourceData.path, sourceData.file_name, sourceData.source);
90
86
  }
91
87
  }