@nine-lab/nine-mu 0.1.332 → 0.1.334

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.332",
3
+ "version": "0.1.334",
4
4
  "description": "AI-Driven Full-Stack Code Fabrication Engine",
5
5
  "type": "module",
6
6
  "main": "./dist/nine-mu.umd.js",
@@ -940,4 +940,82 @@
940
940
  /* JS로 내용이 채워지므로 별도의 CSS 애니메이션은 필요 없음 */
941
941
  }
942
942
  }
943
+ }
944
+
945
+ :host(nine-exception-hook) {
946
+ display: block;
947
+ width: 100%;
948
+ height: 100%;
949
+
950
+ .nine-error-container {
951
+ padding: 30px;
952
+ margin: 20px;
953
+ background-color: #FFF5F5;
954
+ border: 1px solid #FEB2B2;
955
+ border-radius: 8px;
956
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
957
+ }
958
+
959
+ .nine-error-title {
960
+ color: #C53030;
961
+ margin-top: 0;
962
+ font-size: 18px;
963
+ font-weight: bold;
964
+ }
965
+
966
+ .nine-error-label {
967
+ margin: 10px 0 5px 0;
968
+ font-weight: bold;
969
+ color: #4A5568;
970
+ }
971
+
972
+ .nine-error-log {
973
+ background-color: #1A202C;
974
+ color: #EDF2F7;
975
+ padding: 15px;
976
+ border-radius: 6px;
977
+ overflow-x: auto;
978
+ white-space: pre-wrap;
979
+ font-family: 'Courier New', Courier, monospace;
980
+ font-size: 14px;
981
+ }
982
+
983
+ /* 💡 버튼 커스텀 디자인 세트 */
984
+ .nine-ai-btn {
985
+ background-color: #3182CE;
986
+ color: white;
987
+ padding: 10px 20px;
988
+ font-size: 14px;
989
+ font-weight: bold;
990
+ border: none;
991
+ border-radius: 6px;
992
+ cursor: pointer;
993
+ transition: background-color 0.2s ease;
994
+ white-space: nowrap;
995
+ }
996
+
997
+ .nine-ai-btn:hover {
998
+ background-color: #2B6CB0;
999
+ }
1000
+
1001
+ .nine-ai-btn:disabled {
1002
+ background-color: #A0AEC0;
1003
+ cursor: not-allowed;
1004
+ }
1005
+
1006
+ .nine-ai-btn.loading {
1007
+ background-color: #4A5568;
1008
+ animation: pulse 1.5s infinite ease-in-out;
1009
+ }
1010
+
1011
+ .nine-error-footer {
1012
+ font-size: 14px;
1013
+ color: #718096;
1014
+ }
1015
+
1016
+ @keyframes pulse {
1017
+ 0% { opacity: 0.6; }
1018
+ 50% { opacity: 1; }
1019
+ 100% { opacity: 0.6; }
1020
+ }
943
1021
  }
@@ -367,7 +367,7 @@ class ChatMessageBody extends HTMLElement
367
367
 
368
368
  #renderer = () => {
369
369
 
370
- const customImport = nine.cssPath ? `@import "${nine.cssPath}/ninePanel.css";` : "";
370
+ const customImport = nine.cssPath ? `@import "${nine.cssPath}/nine-mu.css";` : "";
371
371
 
372
372
  this.shadowRoot.innerHTML = `
373
373
  <style>
@@ -131,7 +131,8 @@ export class NineChat extends HTMLElement {
131
131
 
132
132
  #render() {
133
133
  const placeholder = this.getAttribute("placeholder") || "나에게 무엇이든 물어봐...";
134
- const customImport = this.getAttribute("css-path") ? `@import "${this.getAttribute("css-path")}";` : "";
134
+
135
+ const customImport = nine.cssPath ? `@import "${nine.cssPath}/nine-mu.css";` : "";
135
136
 
136
137
  this.shadowRoot.innerHTML = `
137
138
  <style>
@@ -196,7 +196,7 @@ export class NineDiff extends HTMLElement {
196
196
 
197
197
  connectedCallback() {
198
198
 
199
- const customImport = this.getAttribute("css-path") ? `@import "${this.getAttribute("css-path")}";` : "";
199
+ const customImport = nine.cssPath ? `@import "${nine.cssPath}/nine-mu.css";` : "";
200
200
 
201
201
  this.shadowRoot.innerHTML = `
202
202
  <style>
@@ -161,7 +161,7 @@ export class NineChat extends HTMLElement {
161
161
 
162
162
  #render() {
163
163
  const placeholder = this.getAttribute("placeholder") || "나에게 무엇이든 물어봐...";
164
- const customImport = this.getAttribute("css-path") ? `@import "${this.getAttribute("css-path")}";` : "";
164
+ const customImport = nine.cssPath ? `@import "${nine.cssPath}/nine-mu.css";` : "";
165
165
 
166
166
  this.shadowRoot.innerHTML = `
167
167
  <style>
@@ -0,0 +1,160 @@
1
+ // src/components/exception/ExceptionHook.js
2
+ export class ExceptionHook extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ this.lastPath = window.location.pathname;
6
+ this.hasError = false;
7
+ this.cssPath = "/css/nine-exception-hook.css";
8
+
9
+ // 💡 1. Shadow DOM(open 모드) 활성화 및 슬롯 공간 준비
10
+ this.attachShadow({ mode: 'open' });
11
+
12
+ // 기본 구조 선언: 자식 엘리먼트들이 위치할 <slot>을 배치합니다.
13
+
14
+ }
15
+
16
+ connectedCallback() {
17
+
18
+ this.#renderer();
19
+
20
+ // Shadow DOM 내부에 캡슐화된 스타일시트 링크 주입
21
+ this.#injectShadowStylesheet();
22
+
23
+ window.addEventListener('error', this.#handleRuntimeError.bind(this), true);
24
+ window.addEventListener('unhandledrejection', this.#handlePromiseError.bind(this), true);
25
+ window.addEventListener('popstate', this.#handleLocationChange.bind(this));
26
+ document.addEventListener('click', this.#handleLocationChange.bind(this), true);
27
+ }
28
+
29
+ disconnectedCallback() {
30
+ window.removeEventListener('error', this.#handleRuntimeError);
31
+ window.removeEventListener('unhandledrejection', this.#handlePromiseError);
32
+ window.removeEventListener('popstate', this.#handleLocationChange);
33
+ document.removeEventListener('click', this.#handleLocationChange, true);
34
+ }
35
+
36
+ #renderer = () => {
37
+ const customImport = nine.cssPath ? `@import "${nine.cssPath}/nine-mu.css";` : "";
38
+
39
+ this.shadowRoot.innerHTML = `
40
+ <style>
41
+ @import "https://cdn.jsdelivr.net/npm/@nine-lab/nine-mu@${__APP_VERSION__}/dist/css/nine-mu.css";
42
+ ${customImport}
43
+ </style>
44
+
45
+ <div class="nine-hook-core">
46
+ <slot></slot>
47
+ </div>
48
+ `;
49
+ }
50
+
51
+ // 💡 2. Shadow Root 내부 전용 스타일 링크 주입 함수
52
+ #injectShadowStylesheet() {
53
+ if (this.shadowRoot.querySelector(`link[href="${this.cssPath}"]`)) return;
54
+
55
+ const link = document.createElement('link');
56
+ link.rel = 'stylesheet';
57
+ link.href = this.cssPath;
58
+ // head가 아닌 shadowRoot에 집어넣어 글로벌 오염을 완벽히 격리합니다.
59
+ this.shadowRoot.appendChild(link);
60
+ }
61
+
62
+ #handleRuntimeError(event) {
63
+ // Shadow DOM 외부의 자식 영역(Light DOM) 검증을 위해 여전히 this.contains 활용
64
+ if (this.contains(event.target) || event.error) {
65
+ this.#renderError(event.error?.message || event.message, event.error?.stack);
66
+ }
67
+ }
68
+
69
+
70
+ #handlePromiseError(event) {
71
+ this.#renderError(event.reason?.message || String(event.reason), event.reason?.stack);
72
+ }
73
+
74
+ #handleLocationChange() {
75
+ setTimeout(() => {
76
+ if (this.hasError && this.lastPath !== window.location.pathname) {
77
+ console.log("✨ [nine-exception-hook] Shadow DOM 내부 예외 청소 및 정상 복구");
78
+ this.lastPath = window.location.pathname;
79
+ this.hasError = false;
80
+
81
+ // 💡 3. 복구 시 Shadow Root 내부의 에러 컨테이너만 타겟팅해서 제거
82
+ const errorBox = this.shadowRoot.querySelector('.nine-error-container');
83
+ if (errorBox) errorBox.remove();
84
+
85
+ // 숨겨두었던 원래 본문 슬롯 구역을 다시 노출
86
+ const coreContainer = this.shadowRoot.querySelector('.nine-hook-core');
87
+ if (coreContainer) coreContainer.style.display = '';
88
+ }
89
+ }, 10);
90
+ }
91
+
92
+ async #sendToAiAction(errorMessage, errorStack) {
93
+ // 💡 4. DOM 쿼리 탐색을 모두 this가 아닌 this.shadowRoot로 전환
94
+ const btn = this.shadowRoot.querySelector('.nine-ai-btn');
95
+ const footer = this.shadowRoot.querySelector('.nine-error-footer');
96
+ if (!btn) return;
97
+
98
+ btn.disabled = true;
99
+ btn.innerText = "⚡ AI 관제탑에 소스 분석 요청 중...";
100
+ btn.classList.add('loading');
101
+
102
+ try {
103
+ if (window.triggerAutoRecovery) {
104
+ await window.triggerAutoRecovery({
105
+ message: errorMessage,
106
+ stack: errorStack || "No stack trace available",
107
+ path: window.location.pathname
108
+ });
109
+
110
+ footer.innerHTML = `✅ <b>AI 관제탑 접수 완료:</b> 분석을 바탕으로 소스 코드를 교정하고 있습니다. 잠시만 기다려주세요.`;
111
+ btn.innerText = "🚀 전송 완료";
112
+ } else {
113
+ throw new Error("window.triggerAutoRecovery 관제탑 엔진이 로드되지 않았습니다.");
114
+ }
115
+ } catch (err) {
116
+ console.error("❌ AI 전송 실패:", err);
117
+ btn.disabled = false;
118
+ btn.innerText = "❌ 재전송 시도";
119
+ footer.innerHTML = `<span style="color: #E53E3E;">⚠️ 전송 실패: ${err.message}</span>`;
120
+ }
121
+ }
122
+
123
+ #renderError(errorMessage, errorStack) {
124
+ if (this.hasError) return;
125
+ this.hasError = true;
126
+
127
+ // 💡 5. 리액트의 원본 마크업을 건드리지 않고, 슬롯이 감싸진 껍데기만 숨김 처리
128
+ const coreContainer = this.shadowRoot.querySelector('.nine-hook-core');
129
+ if (coreContainer) coreContainer.style.display = 'none';
130
+
131
+ // Shadow DOM 내부에 렌더링할 마크업 구조 설계
132
+ const errorHTML = `
133
+ <div class="nine-error-container">
134
+ <h3 class="nine-error-title">⚠️ 시스템 예외 감지 (${this.tagName.toLowerCase()})</h3>
135
+ <p class="nine-error-label">[에러 원인 로그]:</p>
136
+ <pre class="nine-error-log">${errorMessage}</pre>
137
+
138
+ <div class="nine-action-area" style="margin-top: 20px; display: flex; align-items: center; gap: 15px;">
139
+ <button class="nine-ai-btn">🤖 AI에게 소스코드 수정 요청</button>
140
+ <div class="nine-error-footer">
141
+ 버튼을 누르면 AI가 로그 및 컴포넌트 맵을 역추적하여 수정을 시작합니다.
142
+ </div>
143
+ </div>
144
+ </div>
145
+ `;
146
+
147
+ // shadowRoot 최하단에 안전하게 안착시킵니다.
148
+ this.shadowRoot.insertAdjacentHTML('beforeend', errorHTML);
149
+
150
+ // 버튼 이벤트 바인딩도 shadowRoot 기준 처리
151
+ const aiBtn = this.shadowRoot.querySelector('.nine-ai-btn');
152
+ if (aiBtn) {
153
+ aiBtn.addEventListener('click', () => this.#sendToAiAction(errorMessage, errorStack));
154
+ }
155
+ }
156
+ }
157
+
158
+ if (!customElements.get('nine-exception-hook')) {
159
+ customElements.define('nine-exception-hook', ExceptionHook);
160
+ }
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ import { NineDiff } from './components/NineDiff.js';
4
4
  import { NineDiffPopup } from './components/NineDiffPopup.js';
5
5
  import { NineMenuDiffPopup } from './components/NineMenuDiffPopup.js';
6
6
  import './components/ChatMessage.js';
7
+ import './components/exception/ExceptionHook.js';
7
8
 
8
9
  /**
9
10
  * Nine-Mu 엔진 메인 클래스