@nine-lab/nine-mu 0.1.367 → 0.1.369

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.367",
3
+ "version": "0.1.369",
4
4
  "description": "AI-Driven Full-Stack Code Fabrication Engine",
5
5
  "type": "module",
6
6
  "main": "./dist/nine-mu.umd.js",
@@ -0,0 +1,160 @@
1
+ import React, { createContext, useState, useContext } from 'react';
2
+
3
+ // ==========================================
4
+ // 1. 내부 기능 A: ScreenContext 및 구조
5
+ // ==========================================
6
+ const ScreenContext = createContext();
7
+ export const useScreen = () => useContext(ScreenContext);
8
+
9
+ const ScreenProvider = ({ children }) => {
10
+ const [activeView, setActiveView] = useState('list-wrapper');
11
+ const [selectedData, setSelectedData] = useState(null);
12
+
13
+ const goto = (selector, data) => {
14
+ setActiveView(selector);
15
+ if (data) setSelectedData(data);
16
+ };
17
+
18
+ return React.createElement(
19
+ ScreenContext.Provider,
20
+ { value: { activeView, selectedData, goto } },
21
+ children
22
+ );
23
+ };
24
+
25
+ // ==========================================
26
+ // 2. 내부 기능 B: 에러 바운더리 및 CSS 스타일
27
+ // ==========================================
28
+ export const NineExceptionStyles = {
29
+ container: {
30
+ minHeight: '100%',
31
+ flexGrow: 1,
32
+ padding: '25px',
33
+ background: '#121314',
34
+ fontFamily: 'Consolas, Monaco, "Courier New", monospace',
35
+ fontSize: '14px',
36
+ textAlign: 'left',
37
+ boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
38
+ display: 'flex',
39
+ flexDirection: 'column',
40
+ gap: '10px',
41
+ lineHeight: '1.5'
42
+ },
43
+ systemError: { color: '#999', fontWeight: 'bold', margin: '0', padding: '0', display: 'block' },
44
+ pre: { background: 'transparent', color: '#666', padding: '0', margin: '0', border: 'none', overflowX: 'auto', whiteSpace: 'pre-wrap', fontFamily: 'inherit', fontSize: 'inherit', display: 'block' },
45
+ locationRow: { color: '#999', margin: '0', padding: '0', display: 'block', fontFamily: 'inherit', fontSize: 'inherit' },
46
+ footer: { margin: '0', padding: '0', display: 'none' },
47
+ promptRow: { margin: '0', padding: '0', display: 'block', color: '#999' },
48
+ cliLink: { color: '#38A169', padding: '0', fontFamily: 'inherit', fontSize: 'inherit', fontWeight: 'bold', cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: '3px' }
49
+ };
50
+
51
+ class NineExceptionHook extends React.Component {
52
+ constructor(props) {
53
+ super(props);
54
+ this.state = { hasError: false, error: null, reportStatus: 'idle', errorInfo: null };
55
+ this.lastPath = window.location.pathname;
56
+ this.handleLocationChange = this.handleLocationChange.bind(this);
57
+ this.handleReport = this.handleReport.bind(this);
58
+ this.handleKeyDown = this.handleKeyDown.bind(this);
59
+ }
60
+ static getDerivedStateFromError(error) { return { hasError: true, error, reportStatus: 'idle' }; }
61
+ componentDidCatch(error, errorInfo) {
62
+ this.setState({ errorInfo: errorInfo || null });
63
+ if (this.props.onCatch) this.props.onCatch(error, errorInfo);
64
+ }
65
+ componentDidMount() {
66
+ window.addEventListener('popstate', this.handleLocationChange);
67
+ document.addEventListener('click', this.handleLocationChange, true);
68
+ window.addEventListener('keydown', this.handleKeyDown);
69
+ }
70
+ componentWillUnmount() {
71
+ window.removeEventListener('popstate', this.handleLocationChange);
72
+ document.removeEventListener('click', this.handleLocationChange, true);
73
+ window.removeEventListener('keydown', this.handleKeyDown);
74
+ }
75
+ handleKeyDown(e) {
76
+ if (this.state.hasError && this.state.reportStatus === 'idle') {
77
+ if (e.key === 'y' || e.key === 'Y') { e.preventDefault(); this.handleReport(); }
78
+ }
79
+ }
80
+ handleLocationChange() {
81
+ setTimeout(() => {
82
+ if (this.state.hasError && this.lastPath !== window.location.pathname) {
83
+ this.lastPath = window.location.pathname;
84
+ this.setState({ hasError: false, error: null, reportStatus: 'idle', errorInfo: null });
85
+ }
86
+ }, 10);
87
+ }
88
+ handleReport() {
89
+ if (this.state.reportStatus !== 'idle') return;
90
+ this.setState({ reportStatus: 'sending' });
91
+ const errorMessage = this.state.error?.message || String(this.state.error);
92
+ setTimeout(() => {
93
+ if (window.triggerAutoRecovery) window.triggerAutoRecovery(errorMessage);
94
+ this.setState({ reportStatus: 'success' });
95
+ }, 600);
96
+ }
97
+ render() {
98
+ if (this.state.hasError) {
99
+ if (this.props.fallback) return this.props.fallback(this.state.error);
100
+ let errorMessage = '';
101
+ if (this.state.error) {
102
+ const errorName = this.state.error.name ? `[${this.state.error.name}] ` : '';
103
+ const mainMessage = this.state.error.message || String(this.state.error);
104
+ if (this.state.error.stack) {
105
+ const stackLines = this.state.error.stack.split('\n');
106
+ errorMessage = stackLines[0].includes(mainMessage) ? stackLines[0] : `${errorName}${mainMessage}`;
107
+ } else { errorMessage = `${errorName}${mainMessage}`; }
108
+ } else { errorMessage = 'Unknown runtime exception occurred.'; }
109
+
110
+ const customStyles = this.props.styles || {};
111
+ let errorLocation = '';
112
+ if (this.state.errorInfo?.componentStack) {
113
+ const lines = this.state.errorInfo.componentStack.trim().split('\n');
114
+ const targetLine = lines[0] || '';
115
+ const match = targetLine.match(/at\s+(\w+)\s+\((.*)\)/) || targetLine.match(/at\s+(.*)/);
116
+ if (match) {
117
+ const componentName = match[1];
118
+ let rawPath = match[2] || '';
119
+ if (rawPath.includes('http://') || rawPath.includes('https://')) {
120
+ rawPath = rawPath.replace(/^https?:\/\/[^\/]+/, '');
121
+ }
122
+ errorLocation = rawPath ? `at ${componentName} (${rawPath})` : `at ${componentName}`;
123
+ } else { errorLocation = targetLine.trim(); }
124
+ }
125
+
126
+ return React.createElement(
127
+ 'div',
128
+ { style: { ...NineExceptionStyles.container, ...customStyles.container, ...this.props.containerStyle } },
129
+ React.createElement('div', { style: NineExceptionStyles.systemError }, '[SYSTEM] Component render error detected.'),
130
+ React.createElement('pre', { style: NineExceptionStyles.pre }, errorMessage),
131
+ errorLocation && React.createElement('div', { style: NineExceptionStyles.locationRow }, errorLocation),
132
+ React.createElement(
133
+ 'div',
134
+ { style: NineExceptionStyles.footer },
135
+ this.state.reportStatus === 'idle' && React.createElement('div', { style: NineExceptionStyles.promptRow }, 'Send error log to Control Tower (', React.createElement('span', { style: NineExceptionStyles.cliLink, onClick: this.handleReport }, 'y'), '/n)?'),
136
+ this.state.reportStatus === 'sending' && React.createElement('div', { style: { color: '#ED8936' } }, '>> Connecting to Control Tower... Sending payload.'),
137
+ this.state.reportStatus === 'success' && React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '12px' } }, React.createElement('div', { style: { color: '#38A169', fontWeight: 'bold' } }, '>> Transmit complete. Core engine initialized.'), React.createElement('div', { style: { color: '#A0AEC0', fontStyle: 'italic', fontSize: '13px' } }, 'AI가 소스 코드를 재수정하는 동안 잠시만 기다려주세요...'))
138
+ )
139
+ );
140
+ }
141
+ return this.props.children;
142
+ }
143
+ }
144
+
145
+ // ==========================================
146
+ // 🎯 3. 핵심: 사용자가 한 줄로 쓰게 만들 통합 마스터 엔진
147
+ // ==========================================
148
+ export function NineHook({ children, onCatch, fallback, styles, containerStyle }) {
149
+ return React.createElement(
150
+ ScreenProvider,
151
+ null,
152
+ React.createElement(
153
+ NineExceptionHook,
154
+ { onCatch, fallback, styles, containerStyle },
155
+ children
156
+ )
157
+ );
158
+ }
159
+
160
+ export default NineHook;
package/src/index.js CHANGED
@@ -4,7 +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 { NineExceptionHook } from './components/exception/ExceptionHook.js';
7
+ import { NineHook } from './components/hook/NineHook.js';
8
8
 
9
9
  /**
10
10
  * Nine-Mu 엔진 메인 클래스
@@ -20,4 +20,4 @@ export const NineMu = {
20
20
 
21
21
  // 기본 export 및 컴포넌트 export
22
22
  export default NineMu;
23
- export { NineChat, NineDiff, NineDiffPopup, NineExceptionHook };
23
+ export { NineChat, NineDiff, NineDiffPopup, NineHook };
@@ -1,266 +0,0 @@
1
- import React from 'react';
2
-
3
- // ==========================================
4
- // 1. 터미널 프롬프트 테마 스타일 (색상 및 행간 정밀 리튠)
5
- // ==========================================
6
- export const NineExceptionStyles = {
7
- container: {
8
- minHeight: '100%',
9
- flexGrow: 1,
10
- padding: '25px',
11
- background: '#121314',
12
- fontFamily: 'Consolas, Monaco, "Courier New", monospace',
13
- fontSize: '14px',
14
- textAlign: 'left',
15
- boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
16
- display: 'flex',
17
- flexDirection: 'column',
18
- gap: '10px',
19
- // ⭐ 수정: 아까 보기 좋았던 넉넉하고 정돈된 줄간격 비율로 복구
20
- lineHeight: '1.5'
21
- },
22
- // 1. 모든 라인의 display 형식을 'block'으로 맞추고 마진/패딩을 0으로 통일
23
- systemError: {
24
- color: '#999',
25
- fontWeight: 'bold',
26
- margin: '0',
27
- padding: '0',
28
- display: 'block'
29
- },
30
- // 2. 에러 본문 텍스트도 불필요한 속성을 다 버리고 일반 라인과 똑같이 흐르게 교정
31
- pre: {
32
- background: 'transparent',
33
- color: '#666', // aa is not defined는 요청하신 #666 적용
34
- padding: '0',
35
- margin: '0',
36
- border: 'none',
37
- overflowX: 'auto',
38
- whiteSpace: 'pre-wrap',
39
- fontFamily: 'inherit', // 부모 폰트 그대로 상속
40
- fontSize: 'inherit', // 부모 크기 그대로 상속
41
- display: 'block'
42
- },
43
- // 3. 에러 위치 추적 라인
44
- locationRow: {
45
- color: '#999',
46
- margin: '0',
47
- padding: '0',
48
- display: 'block',
49
- fontFamily: 'inherit',
50
- fontSize: 'inherit'
51
- },
52
- // 4. 하단 영역의 flex와 gap을 완전히 제거하여 줄간격 왜곡 원천 차단
53
- footer: {
54
- margin: '0',
55
- padding: '0',
56
- display: 'none'
57
- },
58
- promptRow: {
59
- margin: '0',
60
- padding: '0',
61
- display: 'block',
62
- color: '#999'
63
- },
64
- cliLink: {
65
- color: '#38A169', // y 링크 터미널 그린
66
- padding: '0',
67
- fontFamily: 'inherit',
68
- fontSize: 'inherit',
69
- fontWeight: 'bold',
70
- cursor: 'pointer',
71
- textDecoration: 'underline',
72
- textUnderlineOffset: '3px'
73
- }
74
- };
75
-
76
- // ==========================================
77
- // 2. 컴포넌트 본문
78
- // ==========================================
79
- export class NineExceptionHook extends React.Component {
80
-
81
- constructor(props) {
82
- super(props);
83
- this.state = { hasError: false, error: null, reportStatus: 'idle' };
84
- this.lastPath = window.location.pathname;
85
- this.handleLocationChange = this.handleLocationChange.bind(this);
86
- this.handleReport = this.handleReport.bind(this);
87
- this.handleKeyDown = this.handleKeyDown.bind(this);
88
- }
89
-
90
- static getDerivedStateFromError(error) {
91
- return { hasError: true, error, reportStatus: 'idle' };
92
- }
93
-
94
- componentDidCatch(error, errorInfo) {
95
- console.error("🚨 [Nine-Library] 본문 렌더링 에러 포착:", error);
96
-
97
- this.setState({
98
- errorInfo: errorInfo || null
99
- });
100
-
101
- if (this.props.onCatch) {
102
- this.props.onCatch(error, errorInfo);
103
- }
104
- }
105
-
106
- componentDidMount() {
107
- window.addEventListener('popstate', this.handleLocationChange);
108
- document.addEventListener('click', this.handleLocationChange, true);
109
- window.addEventListener('keydown', this.handleKeyDown);
110
- }
111
-
112
- componentWillUnmount() {
113
- window.removeEventListener('popstate', this.handleLocationChange);
114
- document.removeEventListener('click', this.handleLocationChange, true);
115
- window.removeEventListener('keydown', this.handleKeyDown);
116
- }
117
-
118
- handleKeyDown(e) {
119
- if (this.state.hasError && this.state.reportStatus === 'idle') {
120
- if (e.key === 'y' || e.key === 'Y') {
121
- e.preventDefault();
122
- this.handleReport();
123
- }
124
- }
125
- }
126
-
127
- handleLocationChange() {
128
- setTimeout(() => {
129
- // reportStatus 조건 대신, 에러가 나 있는 상태에서 주소가 바뀌었는지만 체크해야 안전합니다.
130
- if (this.state.hasError && this.lastPath !== window.location.pathname) {
131
- console.log("[Nine-Library] 페이지 주소 변경 감지 -> 에러 상태 초기화");
132
- this.lastPath = window.location.pathname;
133
- this.setState({ hasError: false, error: null, reportStatus: 'idle' });
134
- }
135
- }, 10);
136
- }
137
-
138
- handleReport() {
139
- if (this.state.reportStatus !== 'idle') return;
140
-
141
- this.setState({ reportStatus: 'sending' });
142
- const errorMessage = this.state.error?.message || String(this.state.error);
143
-
144
- setTimeout(() => {
145
- if (window.triggerAutoRecovery) {
146
- window.triggerAutoRecovery(errorMessage);
147
- } else {
148
- console.warn("[Nine-Library] window.triggerAutoRecovery 가 정의되지 않았습니다.");
149
- }
150
- this.setState({ reportStatus: 'success' });
151
- }, 600);
152
- }
153
-
154
- render() {
155
- if (this.state.hasError) {
156
- if (this.props.fallback) {
157
- return this.props.fallback(this.state.error);
158
- }
159
-
160
- // 1. 🔍 에러 메시지 상세화 (기본 메시지 외에 에러 타입과 핵심 요약 추출)
161
- let errorMessage = '';
162
- if (this.state.error) {
163
- const errorName = this.state.error.name ? `[${this.state.error.name}] ` : '';
164
- const mainMessage = this.state.error.message || String(this.state.error);
165
-
166
- // 에러 객체의 stack 정보가 있으면 첫 번째 라인(상세 원인)을 조합합니다.
167
- if (this.state.error.stack) {
168
- const stackLines = this.state.error.stack.split('\n');
169
- // stack의 첫 줄이 이미 에러명을 포함하는 경우가 많으므로 정제하여 매칭
170
- errorMessage = stackLines[0].includes(mainMessage)
171
- ? stackLines[0]
172
- : `${errorName}${mainMessage}`;
173
- } else {
174
- errorMessage = `${errorName}${mainMessage}`;
175
- }
176
- } else {
177
- errorMessage = 'Unknown runtime exception occurred.';
178
- }
179
-
180
- const customStyles = this.props.styles || {};
181
-
182
- // 2. 🔍 React 컴포넌트 스택에서 로컬호스트 주소를 걷어낸 깔끔한 경로 추출
183
- let errorLocation = '';
184
- if (this.state.errorInfo?.componentStack) {
185
- const lines = this.state.errorInfo.componentStack.trim().split('\n');
186
- const targetLine = lines[0] || '';
187
-
188
- const match = targetLine.match(/at\s+(\w+)\s+\((.*)\)/) || targetLine.match(/at\s+(.*)/);
189
- if (match) {
190
- const componentName = match[1];
191
- let rawPath = match[2] || '';
192
-
193
- if (rawPath.includes('http://') || rawPath.includes('https://')) {
194
- rawPath = rawPath.replace(/^https?:\/\/[^\/]+/, '');
195
- }
196
-
197
- errorLocation = rawPath ? `at ${componentName} (${rawPath})` : `at ${componentName}`;
198
- } else {
199
- errorLocation = targetLine.trim();
200
- }
201
- }
202
-
203
- return React.createElement(
204
- 'div',
205
- {
206
- style: { ...NineExceptionStyles.container, ...customStyles.container, ...this.props.containerStyle }
207
- },
208
-
209
- // 1. 에러 감지 로그
210
- React.createElement(
211
- 'div',
212
- { style: { ...NineExceptionStyles.systemError, ...customStyles.systemError } },
213
- '[SYSTEM] Component render error detected.'
214
- ),
215
-
216
- // 2. 상세화된 에러 내용 (예: ReferenceError: aa is not defined)
217
- React.createElement('pre', { style: { ...NineExceptionStyles.pre, ...customStyles.pre } }, errorMessage),
218
-
219
- // 3. 파싱된 에러 발생 파일명 및 라인 위치
220
- errorLocation && React.createElement(
221
- 'div',
222
- { style: { ...NineExceptionStyles.locationRow, ...customStyles.locationRow } },
223
- errorLocation
224
- ),
225
-
226
- // 4. CLI 프롬프트 영역
227
- React.createElement(
228
- 'div',
229
- { style: { ...NineExceptionStyles.footer, ...customStyles.footer } },
230
-
231
- this.state.reportStatus === 'idle' && React.createElement(
232
- 'div',
233
- { style: { ...NineExceptionStyles.promptRow, ...customStyles.promptRow } },
234
- React.createElement('span', null, 'Send error log to Control Tower ('),
235
- React.createElement(
236
- 'span',
237
- {
238
- style: { ...NineExceptionStyles.cliLink, ...customStyles.cliLink },
239
- onClick: this.handleReport
240
- },
241
- 'y'
242
- ),
243
- React.createElement('span', null, '/n)?')
244
- ),
245
-
246
- this.state.reportStatus === 'sending' && React.createElement(
247
- 'div',
248
- { style: { color: '#ED8936', margin: '0' } },
249
- '>> Connecting to Control Tower... Sending payload.'
250
- ),
251
-
252
- this.state.reportStatus === 'success' && React.createElement(
253
- 'div',
254
- { style: { display: 'flex', flexDirection: 'column', gap: '12px', margin: '0' } },
255
- React.createElement('div', { style: { color: '#38A169', fontWeight: 'bold' } }, '>> Transmit complete. Core engine initialized.'),
256
- React.createElement('div', { style: { color: '#A0AEC0', fontStyle: 'italic', fontSize: '13px' } }, 'AI가 소스 코드를 재수정하는 동안 잠시만 기다려주세요...')
257
- )
258
- )
259
- );
260
- }
261
-
262
- return this.props.children;
263
- }
264
- }
265
-
266
- export default NineExceptionHook;