aiexecode 1.0.75 → 1.0.81
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.
Potentially problematic release.
This version of aiexecode might be problematic. Click here for more details.
- package/index.js +20 -1
- package/package.json +1 -1
- package/payload_viewer/out/404/index.html +1 -1
- package/payload_viewer/out/404.html +1 -1
- package/payload_viewer/out/index.html +1 -1
- package/payload_viewer/out/index.txt +1 -1
- package/prompts/orchestrator.txt +34 -0
- package/src/frontend/App.js +34 -17
- package/src/frontend/components/ConversationItem.js +9 -3
- package/src/frontend/components/Header.js +11 -4
- package/src/frontend/utils/GridRenderer.js +140 -0
- package/src/frontend/utils/InlineFormatter.js +156 -0
- package/src/frontend/utils/markdownParser.js +330 -184
- package/src/util/version_check.js +116 -0
- /package/payload_viewer/out/_next/static/{jbur8fv1gX0ZHKro3t6lN → pVFGdWdWiytr2-k-gctjG}/_buildManifest.js +0 -0
- /package/payload_viewer/out/_next/static/{jbur8fv1gX0ZHKro3t6lN → pVFGdWdWiytr2-k-gctjG}/_clientMiddlewareManifest.json +0 -0
- /package/payload_viewer/out/_next/static/{jbur8fv1gX0ZHKro3t6lN → pVFGdWdWiytr2-k-gctjG}/_ssgManifest.js +0 -0
|
@@ -6,226 +6,372 @@
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { Text, Box } from 'ink';
|
|
8
8
|
import { theme } from '../design/themeColors.js';
|
|
9
|
+
import { colorizeCode } from './syntaxHighlighter.js';
|
|
10
|
+
import { GridRenderer } from './GridRenderer.js';
|
|
11
|
+
import { ProcessInlineText } from './InlineFormatter.js';
|
|
12
|
+
|
|
13
|
+
// 렌더링 상수
|
|
14
|
+
const SPACING = {
|
|
15
|
+
EMPTY_LINE: 1,
|
|
16
|
+
CODE_PADDING: 1,
|
|
17
|
+
LIST_PREFIX_PAD: 1,
|
|
18
|
+
LIST_TEXT_GROW: 1
|
|
19
|
+
};
|
|
9
20
|
|
|
10
21
|
/**
|
|
11
22
|
* 마크다운 텍스트를 파싱하여 Ink 컴포넌트로 변환
|
|
23
|
+
* @param {string} text - 마크다운 텍스트
|
|
24
|
+
* @param {Object} options - 렌더링 옵션
|
|
25
|
+
* @param {boolean} options.isPending - 스트리밍 중 여부
|
|
26
|
+
* @param {number} options.availableHeight - 사용 가능한 터미널 높이
|
|
27
|
+
* @param {number} options.terminalWidth - 터미널 너비 (기본값: 80)
|
|
28
|
+
* @returns {React.ReactNode}
|
|
12
29
|
*/
|
|
13
|
-
export function renderMarkdown(text) {
|
|
30
|
+
export function renderMarkdown(text, options = {}) {
|
|
14
31
|
if (!text) return null;
|
|
15
32
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
33
|
+
const {
|
|
34
|
+
isPending = false,
|
|
35
|
+
availableHeight,
|
|
36
|
+
terminalWidth = 80
|
|
37
|
+
} = options;
|
|
38
|
+
|
|
39
|
+
const lineArray = text.split(/\r?\n/);
|
|
40
|
+
|
|
41
|
+
// 정규식 패턴
|
|
42
|
+
const patterns = {
|
|
43
|
+
header: /^ *(#{1,4}) +(.*)/,
|
|
44
|
+
codeFence: /^ *(`{3,}|~{3,}) *(\w*?) *$/,
|
|
45
|
+
unorderedList: /^([ \t]*)([-*+]) +(.*)/,
|
|
46
|
+
orderedList: /^([ \t]*)(\d+)\. +(.*)/,
|
|
47
|
+
horizontalRule: /^ *([-*_] *){3,} *$/,
|
|
48
|
+
tableRow: /^\s*\|(.+)\|\s*$/,
|
|
49
|
+
tableSeparator: /^\s*\|?\s*(:?-+:?)\s*(\|\s*(:?-+:?)\s*)+\|?\s*$/
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const blocks = [];
|
|
53
|
+
let previousLineWasEmpty = true;
|
|
54
|
+
|
|
55
|
+
// 상태 변수
|
|
56
|
+
let codeBlockActive = false;
|
|
57
|
+
let codeBlockLines = [];
|
|
58
|
+
let codeBlockLanguage = null;
|
|
59
|
+
let codeBlockFence = '';
|
|
60
|
+
|
|
61
|
+
let tableActive = false;
|
|
62
|
+
let tableHeaderCells = [];
|
|
63
|
+
let tableDataRows = [];
|
|
64
|
+
|
|
65
|
+
function appendBlock(block) {
|
|
66
|
+
if (block) {
|
|
67
|
+
blocks.push(block);
|
|
68
|
+
previousLineWasEmpty = false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
lineArray.forEach((currentLine, lineIndex) => {
|
|
73
|
+
const lineKey = `ln-${lineIndex}`;
|
|
74
|
+
|
|
75
|
+
// 코드 블록 내부 처리
|
|
76
|
+
if (codeBlockActive) {
|
|
77
|
+
const fenceMatch = currentLine.match(patterns.codeFence);
|
|
78
|
+
if (fenceMatch &&
|
|
79
|
+
fenceMatch[1].startsWith(codeBlockFence[0]) &&
|
|
80
|
+
fenceMatch[1].length >= codeBlockFence.length) {
|
|
81
|
+
appendBlock(
|
|
82
|
+
React.createElement(BuildCodeBlock, {
|
|
83
|
+
key: lineKey,
|
|
84
|
+
lines: codeBlockLines,
|
|
85
|
+
language: codeBlockLanguage,
|
|
86
|
+
isPending,
|
|
87
|
+
availableHeight,
|
|
88
|
+
terminalWidth
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
codeBlockActive = false;
|
|
92
|
+
codeBlockLines = [];
|
|
93
|
+
codeBlockLanguage = null;
|
|
94
|
+
codeBlockFence = '';
|
|
46
95
|
} else {
|
|
47
|
-
|
|
48
|
-
codeLanguage = line.trim().slice(3).trim();
|
|
49
|
-
inCodeBlock = true;
|
|
96
|
+
codeBlockLines.push(currentLine);
|
|
50
97
|
}
|
|
51
|
-
|
|
98
|
+
return;
|
|
52
99
|
}
|
|
53
100
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
101
|
+
// 패턴 매칭
|
|
102
|
+
const fenceMatch = currentLine.match(patterns.codeFence);
|
|
103
|
+
const headerMatch = currentLine.match(patterns.header);
|
|
104
|
+
const ulMatch = currentLine.match(patterns.unorderedList);
|
|
105
|
+
const olMatch = currentLine.match(patterns.orderedList);
|
|
106
|
+
const hrMatch = currentLine.match(patterns.horizontalRule);
|
|
107
|
+
const tableRowMatch = currentLine.match(patterns.tableRow);
|
|
108
|
+
const tableSepMatch = currentLine.match(patterns.tableSeparator);
|
|
58
109
|
|
|
59
|
-
//
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
// 코드 블록 시작
|
|
111
|
+
if (fenceMatch) {
|
|
112
|
+
codeBlockActive = true;
|
|
113
|
+
codeBlockFence = fenceMatch[1];
|
|
114
|
+
codeBlockLanguage = fenceMatch[2] || null;
|
|
115
|
+
}
|
|
116
|
+
// 테이블 시작 감지
|
|
117
|
+
else if (tableRowMatch && !tableActive) {
|
|
118
|
+
if (lineIndex + 1 < lineArray.length &&
|
|
119
|
+
lineArray[lineIndex + 1].match(patterns.tableSeparator)) {
|
|
120
|
+
tableActive = true;
|
|
121
|
+
tableHeaderCells = tableRowMatch[1].split('|').map(cell => cell.trim());
|
|
122
|
+
tableDataRows = [];
|
|
123
|
+
} else {
|
|
124
|
+
appendBlock(
|
|
125
|
+
React.createElement(Box, { key: lineKey },
|
|
126
|
+
React.createElement(Text, { wrap: 'wrap' },
|
|
127
|
+
React.createElement(ProcessInlineText, { content: currentLine })
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// 테이블 구분선 스킵
|
|
134
|
+
else if (tableActive && tableSepMatch) {
|
|
135
|
+
// 구분선은 무시
|
|
136
|
+
}
|
|
137
|
+
// 테이블 데이터 행
|
|
138
|
+
else if (tableActive && tableRowMatch) {
|
|
139
|
+
const cells = tableRowMatch[1].split('|').map(cell => cell.trim());
|
|
140
|
+
while (cells.length < tableHeaderCells.length) cells.push('');
|
|
141
|
+
if (cells.length > tableHeaderCells.length) cells.length = tableHeaderCells.length;
|
|
142
|
+
tableDataRows.push(cells);
|
|
63
143
|
}
|
|
144
|
+
// 테이블 종료
|
|
145
|
+
else if (tableActive && !tableRowMatch) {
|
|
146
|
+
if (tableHeaderCells.length > 0 && tableDataRows.length > 0) {
|
|
147
|
+
appendBlock(
|
|
148
|
+
React.createElement(BuildTable, {
|
|
149
|
+
key: `table-${blocks.length}`,
|
|
150
|
+
columnHeaders: tableHeaderCells,
|
|
151
|
+
dataRows: tableDataRows,
|
|
152
|
+
maxWidth: terminalWidth
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
tableActive = false;
|
|
157
|
+
tableDataRows = [];
|
|
158
|
+
tableHeaderCells = [];
|
|
64
159
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}, renderInlineMarkdown(line.slice(4)))
|
|
76
|
-
)
|
|
77
|
-
);
|
|
78
|
-
continue;
|
|
160
|
+
// 현재 줄 처리
|
|
161
|
+
if (currentLine.trim().length > 0) {
|
|
162
|
+
appendBlock(
|
|
163
|
+
React.createElement(Box, { key: lineKey },
|
|
164
|
+
React.createElement(Text, { wrap: 'wrap' },
|
|
165
|
+
React.createElement(ProcessInlineText, { content: currentLine })
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
}
|
|
79
170
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
},
|
|
86
|
-
React.createElement(Text, {
|
|
87
|
-
color: theme.text.accent,
|
|
88
|
-
bold: true
|
|
89
|
-
}, renderInlineMarkdown(line.slice(3)))
|
|
171
|
+
// 수평선
|
|
172
|
+
else if (hrMatch) {
|
|
173
|
+
appendBlock(
|
|
174
|
+
React.createElement(Box, { key: lineKey },
|
|
175
|
+
React.createElement(Text, { dimColor: true }, '---')
|
|
90
176
|
)
|
|
91
177
|
);
|
|
92
|
-
continue;
|
|
93
178
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
179
|
+
// 헤더
|
|
180
|
+
else if (headerMatch) {
|
|
181
|
+
const level = headerMatch[1].length;
|
|
182
|
+
const headerText = headerMatch[2];
|
|
183
|
+
let headerElement = null;
|
|
184
|
+
|
|
185
|
+
switch (level) {
|
|
186
|
+
case 1:
|
|
187
|
+
headerElement = React.createElement(Text, { bold: true, color: theme.text.link },
|
|
188
|
+
React.createElement(ProcessInlineText, { content: headerText })
|
|
189
|
+
);
|
|
190
|
+
break;
|
|
191
|
+
case 2:
|
|
192
|
+
headerElement = React.createElement(Text, { bold: true, color: theme.text.link },
|
|
193
|
+
React.createElement(ProcessInlineText, { content: headerText })
|
|
194
|
+
);
|
|
195
|
+
break;
|
|
196
|
+
case 3:
|
|
197
|
+
headerElement = React.createElement(Text, { bold: true, color: theme.text.primary },
|
|
198
|
+
React.createElement(ProcessInlineText, { content: headerText })
|
|
199
|
+
);
|
|
200
|
+
break;
|
|
201
|
+
case 4:
|
|
202
|
+
headerElement = React.createElement(Text, { italic: true, color: theme.text.secondary },
|
|
203
|
+
React.createElement(ProcessInlineText, { content: headerText })
|
|
204
|
+
);
|
|
205
|
+
break;
|
|
206
|
+
default:
|
|
207
|
+
headerElement = React.createElement(Text, { color: theme.text.primary },
|
|
208
|
+
React.createElement(ProcessInlineText, { content: headerText })
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (headerElement) {
|
|
212
|
+
appendBlock(React.createElement(Box, { key: lineKey }, headerElement));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// 순서 없는 리스트
|
|
216
|
+
else if (ulMatch) {
|
|
217
|
+
const [, indent, marker, content] = ulMatch;
|
|
218
|
+
appendBlock(
|
|
219
|
+
React.createElement(BuildListItem, {
|
|
220
|
+
key: lineKey,
|
|
221
|
+
itemText: content,
|
|
222
|
+
listType: 'ul',
|
|
223
|
+
marker: marker,
|
|
224
|
+
indentation: indent
|
|
225
|
+
})
|
|
105
226
|
);
|
|
106
|
-
continue;
|
|
107
227
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
React.createElement(Text, { color: theme.text.accent }, bullet + ' '),
|
|
120
|
-
React.createElement(Text, null, renderInlineMarkdown(content))
|
|
121
|
-
)
|
|
228
|
+
// 순서 있는 리스트
|
|
229
|
+
else if (olMatch) {
|
|
230
|
+
const [, indent, marker, content] = olMatch;
|
|
231
|
+
appendBlock(
|
|
232
|
+
React.createElement(BuildListItem, {
|
|
233
|
+
key: lineKey,
|
|
234
|
+
itemText: content,
|
|
235
|
+
listType: 'ol',
|
|
236
|
+
marker: marker,
|
|
237
|
+
indentation: indent
|
|
238
|
+
})
|
|
122
239
|
);
|
|
123
|
-
continue;
|
|
124
240
|
}
|
|
241
|
+
// 빈 줄 또는 일반 텍스트
|
|
242
|
+
else {
|
|
243
|
+
if (currentLine.trim().length === 0 && !codeBlockActive) {
|
|
244
|
+
if (!previousLineWasEmpty) {
|
|
245
|
+
blocks.push(
|
|
246
|
+
React.createElement(Box, {
|
|
247
|
+
key: `space-${lineIndex}`,
|
|
248
|
+
height: SPACING.EMPTY_LINE
|
|
249
|
+
})
|
|
250
|
+
);
|
|
251
|
+
previousLineWasEmpty = true;
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
appendBlock(
|
|
255
|
+
React.createElement(Box, { key: lineKey },
|
|
256
|
+
React.createElement(Text, { wrap: 'wrap', color: theme.text.primary },
|
|
257
|
+
React.createElement(ProcessInlineText, { content: currentLine })
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
125
264
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
265
|
+
// 닫히지 않은 코드 블록 처리
|
|
266
|
+
if (codeBlockActive) {
|
|
267
|
+
appendBlock(
|
|
268
|
+
React.createElement(BuildCodeBlock, {
|
|
269
|
+
key: 'eof-code',
|
|
270
|
+
lines: codeBlockLines,
|
|
271
|
+
language: codeBlockLanguage,
|
|
272
|
+
isPending,
|
|
273
|
+
availableHeight,
|
|
274
|
+
terminalWidth
|
|
275
|
+
})
|
|
134
276
|
);
|
|
135
277
|
}
|
|
136
278
|
|
|
137
|
-
|
|
279
|
+
// 닫히지 않은 테이블 처리
|
|
280
|
+
if (tableActive && tableHeaderCells.length > 0 && tableDataRows.length > 0) {
|
|
281
|
+
appendBlock(
|
|
282
|
+
React.createElement(BuildTable, {
|
|
283
|
+
key: `table-${blocks.length}`,
|
|
284
|
+
columnHeaders: tableHeaderCells,
|
|
285
|
+
dataRows: tableDataRows,
|
|
286
|
+
maxWidth: terminalWidth
|
|
287
|
+
})
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return React.createElement(React.Fragment, null, blocks);
|
|
138
292
|
}
|
|
139
293
|
|
|
140
294
|
/**
|
|
141
|
-
*
|
|
295
|
+
* 코드 블록 렌더링 컴포넌트
|
|
142
296
|
*/
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
let partKey = 0;
|
|
147
|
-
|
|
148
|
-
// 모든 마크다운 패턴 찾기 (정규식 재생성하여 lastIndex 문제 방지)
|
|
149
|
-
const patterns = [];
|
|
150
|
-
|
|
151
|
-
// **bold** 패턴 찾기
|
|
152
|
-
let boldRegex = /\*\*([^*]+)\*\*/g;
|
|
153
|
-
let match;
|
|
154
|
-
while ((match = boldRegex.exec(text)) !== null) {
|
|
155
|
-
patterns.push({
|
|
156
|
-
type: 'bold',
|
|
157
|
-
start: match.index,
|
|
158
|
-
end: match.index + match[0].length,
|
|
159
|
-
content: match[1]
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// `code` 패턴 찾기
|
|
164
|
-
let codeRegex = /`([^`]+)`/g;
|
|
165
|
-
while ((match = codeRegex.exec(text)) !== null) {
|
|
166
|
-
patterns.push({
|
|
167
|
-
type: 'code',
|
|
168
|
-
start: match.index,
|
|
169
|
-
end: match.index + match[0].length,
|
|
170
|
-
content: match[1]
|
|
171
|
-
});
|
|
172
|
-
}
|
|
297
|
+
const BuildCodeBlockInternal = ({ lines, language, isPending, availableHeight, terminalWidth }) => {
|
|
298
|
+
const MIN_LINES_BEFORE_MSG = 1;
|
|
299
|
+
const RESERVED_LINES = 2;
|
|
173
300
|
|
|
174
|
-
|
|
175
|
-
|
|
301
|
+
if (isPending && availableHeight !== undefined) {
|
|
302
|
+
const maxLines = Math.max(0, availableHeight - RESERVED_LINES);
|
|
176
303
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
lastEnd = pattern.end;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// 패턴 적용
|
|
188
|
-
for (const pattern of filteredPatterns) {
|
|
189
|
-
// 이전 텍스트 추가
|
|
190
|
-
if (currentIndex < pattern.start) {
|
|
191
|
-
const plainText = text.slice(currentIndex, pattern.start);
|
|
192
|
-
if (plainText) {
|
|
193
|
-
parts.push(
|
|
194
|
-
React.createElement(Text, { key: `text-${partKey++}` }, plainText)
|
|
304
|
+
if (lines.length > maxLines) {
|
|
305
|
+
if (maxLines < MIN_LINES_BEFORE_MSG) {
|
|
306
|
+
return React.createElement(Box, { paddingLeft: SPACING.CODE_PADDING },
|
|
307
|
+
React.createElement(Text, { color: theme.text.secondary },
|
|
308
|
+
'... code is being written ...'
|
|
309
|
+
)
|
|
195
310
|
);
|
|
196
311
|
}
|
|
197
|
-
}
|
|
198
312
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
parts.push(
|
|
202
|
-
React.createElement(Text, {
|
|
203
|
-
key: `bold-${partKey++}`,
|
|
204
|
-
bold: true,
|
|
205
|
-
color: theme.text.primary
|
|
206
|
-
}, pattern.content)
|
|
207
|
-
);
|
|
208
|
-
} else if (pattern.type === 'code') {
|
|
209
|
-
parts.push(
|
|
210
|
-
React.createElement(Text, {
|
|
211
|
-
key: `code-${partKey++}`,
|
|
212
|
-
color: theme.status.warning
|
|
213
|
-
}, pattern.content)
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
currentIndex = pattern.end;
|
|
218
|
-
}
|
|
313
|
+
const truncatedLines = lines.slice(0, maxLines);
|
|
314
|
+
const truncatedCode = colorizeCode(truncatedLines.join('\n'), language, true);
|
|
219
315
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
React.createElement(Text, {
|
|
316
|
+
return React.createElement(Box, {
|
|
317
|
+
paddingLeft: SPACING.CODE_PADDING,
|
|
318
|
+
flexDirection: 'column'
|
|
319
|
+
},
|
|
320
|
+
truncatedCode,
|
|
321
|
+
React.createElement(Text, { color: theme.text.secondary },
|
|
322
|
+
'... generating more ...'
|
|
323
|
+
)
|
|
226
324
|
);
|
|
227
325
|
}
|
|
228
326
|
}
|
|
229
327
|
|
|
230
|
-
|
|
231
|
-
|
|
328
|
+
const fullCode = lines.join('\n');
|
|
329
|
+
const highlightedCode = colorizeCode(fullCode, language, true);
|
|
330
|
+
|
|
331
|
+
return React.createElement(Box, {
|
|
332
|
+
paddingLeft: SPACING.CODE_PADDING,
|
|
333
|
+
flexDirection: 'column',
|
|
334
|
+
width: terminalWidth,
|
|
335
|
+
flexShrink: 0
|
|
336
|
+
}, highlightedCode);
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const BuildCodeBlock = React.memo(BuildCodeBlockInternal);
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 리스트 항목 렌더링 컴포넌트
|
|
343
|
+
*/
|
|
344
|
+
const BuildListItemInternal = ({ itemText, listType, marker, indentation = '' }) => {
|
|
345
|
+
const displayPrefix = listType === 'ol' ? `${marker}. ` : `${marker} `;
|
|
346
|
+
const prefixLength = displayPrefix.length;
|
|
347
|
+
const indentAmount = indentation.length;
|
|
348
|
+
|
|
349
|
+
return React.createElement(Box, {
|
|
350
|
+
paddingLeft: indentAmount + SPACING.LIST_PREFIX_PAD,
|
|
351
|
+
flexDirection: 'row'
|
|
352
|
+
},
|
|
353
|
+
React.createElement(Box, { width: prefixLength },
|
|
354
|
+
React.createElement(Text, { color: theme.text.primary }, displayPrefix)
|
|
355
|
+
),
|
|
356
|
+
React.createElement(Box, { flexGrow: SPACING.LIST_TEXT_GROW },
|
|
357
|
+
React.createElement(Text, { wrap: 'wrap', color: theme.text.primary },
|
|
358
|
+
React.createElement(ProcessInlineText, { content: itemText })
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const BuildListItem = React.memo(BuildListItemInternal);
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 테이블 렌더링 컴포넌트
|
|
368
|
+
*/
|
|
369
|
+
const BuildTableInternal = ({ columnHeaders, dataRows, maxWidth }) => {
|
|
370
|
+
return React.createElement(GridRenderer, {
|
|
371
|
+
columnHeaders,
|
|
372
|
+
dataRows,
|
|
373
|
+
maxWidth
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const BuildTable = React.memo(BuildTableInternal);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version check utility
|
|
3
|
+
* Compares local version with remote version from GitHub
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createDebugLogger } from './debug_log.js';
|
|
7
|
+
import https from 'https';
|
|
8
|
+
|
|
9
|
+
const debugLog = createDebugLogger('version_check.log', 'version_check');
|
|
10
|
+
|
|
11
|
+
const REMOTE_PACKAGE_JSON_URL = 'https://raw.githubusercontent.com/kstost/aiexecode/refs/heads/main/package.json';
|
|
12
|
+
const TIMEOUT_MS = 5000; // 5 seconds timeout
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Fetch remote package.json from GitHub
|
|
16
|
+
* @returns {Promise<Object|null>} Remote package.json object or null on error
|
|
17
|
+
*/
|
|
18
|
+
async function fetchRemotePackageJson() {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const req = https.get(REMOTE_PACKAGE_JSON_URL, { timeout: TIMEOUT_MS }, (res) => {
|
|
21
|
+
let data = '';
|
|
22
|
+
|
|
23
|
+
res.on('data', (chunk) => {
|
|
24
|
+
data += chunk;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
res.on('end', () => {
|
|
28
|
+
if (res.statusCode === 200) {
|
|
29
|
+
try {
|
|
30
|
+
const packageJson = JSON.parse(data);
|
|
31
|
+
resolve(packageJson);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
debugLog(`Failed to parse remote package.json: ${err.message}`);
|
|
34
|
+
resolve(null);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
debugLog(`HTTP ${res.statusCode} when fetching remote package.json`);
|
|
38
|
+
resolve(null);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
req.on('error', (err) => {
|
|
44
|
+
debugLog(`Network error when fetching remote package.json: ${err.message}`);
|
|
45
|
+
resolve(null);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
req.on('timeout', () => {
|
|
49
|
+
debugLog('Timeout when fetching remote package.json');
|
|
50
|
+
req.destroy();
|
|
51
|
+
resolve(null);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Compare two semantic versions
|
|
58
|
+
* @param {string} version1 - First version (e.g., "1.0.76")
|
|
59
|
+
* @param {string} version2 - Second version (e.g., "1.0.77")
|
|
60
|
+
* @returns {number} -1 if version1 < version2, 0 if equal, 1 if version1 > version2
|
|
61
|
+
*/
|
|
62
|
+
function compareVersions(version1, version2) {
|
|
63
|
+
const v1parts = version1.split('.').map(Number);
|
|
64
|
+
const v2parts = version2.split('.').map(Number);
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
|
|
67
|
+
const v1 = v1parts[i] || 0;
|
|
68
|
+
const v2 = v2parts[i] || 0;
|
|
69
|
+
|
|
70
|
+
if (v1 < v2) return -1;
|
|
71
|
+
if (v1 > v2) return 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check for available updates
|
|
79
|
+
* @param {string} currentVersion - Current local version
|
|
80
|
+
* @returns {Promise<Object>} Object with { hasUpdate: boolean, remoteVersion: string|null, updateAvailable: boolean }
|
|
81
|
+
*/
|
|
82
|
+
export async function checkForUpdates(currentVersion) {
|
|
83
|
+
debugLog(`Checking for updates... Current version: ${currentVersion}`);
|
|
84
|
+
|
|
85
|
+
const remotePackageJson = await fetchRemotePackageJson();
|
|
86
|
+
|
|
87
|
+
if (!remotePackageJson || !remotePackageJson.version) {
|
|
88
|
+
debugLog('Could not fetch remote version');
|
|
89
|
+
return {
|
|
90
|
+
hasUpdate: false,
|
|
91
|
+
remoteVersion: null,
|
|
92
|
+
updateAvailable: false
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const remoteVersion = remotePackageJson.version;
|
|
97
|
+
debugLog(`Remote version: ${remoteVersion}`);
|
|
98
|
+
|
|
99
|
+
const comparison = compareVersions(currentVersion, remoteVersion);
|
|
100
|
+
|
|
101
|
+
if (comparison < 0) {
|
|
102
|
+
debugLog(`Update available: ${currentVersion} → ${remoteVersion}`);
|
|
103
|
+
return {
|
|
104
|
+
hasUpdate: true,
|
|
105
|
+
remoteVersion,
|
|
106
|
+
updateAvailable: true
|
|
107
|
+
};
|
|
108
|
+
} else {
|
|
109
|
+
debugLog('No update available (local version is up to date or newer)');
|
|
110
|
+
return {
|
|
111
|
+
hasUpdate: false,
|
|
112
|
+
remoteVersion,
|
|
113
|
+
updateAvailable: false
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|