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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid Renderer
|
|
3
|
+
* 마크다운 테이블을 렌더링하는 컴포넌트
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { Text, Box } from 'ink';
|
|
8
|
+
import { theme } from '../design/themeColors.js';
|
|
9
|
+
import { ProcessInlineText, calculateTextWidth } from './InlineFormatter.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 마크다운 테이블을 렌더링하는 컴포넌트
|
|
13
|
+
* @param {Object} props
|
|
14
|
+
* @param {string[]} props.columnHeaders - 테이블 헤더
|
|
15
|
+
* @param {string[][]} props.dataRows - 테이블 데이터 행
|
|
16
|
+
* @param {number} props.maxWidth - 터미널 최대 너비
|
|
17
|
+
*/
|
|
18
|
+
export const GridRenderer = ({ columnHeaders, dataRows, maxWidth }) => {
|
|
19
|
+
// 컬럼 너비 계산 (마크다운 처리 후 실제 표시 너비 기준)
|
|
20
|
+
const widthPerColumn = columnHeaders.map((headerText, columnIndex) => {
|
|
21
|
+
const headerDisplayWidth = calculateTextWidth(headerText);
|
|
22
|
+
const maxDataWidth = Math.max(
|
|
23
|
+
...dataRows.map((rowData) => calculateTextWidth(rowData[columnIndex] || ''))
|
|
24
|
+
);
|
|
25
|
+
return Math.max(headerDisplayWidth, maxDataWidth) + 2; // 패딩 추가
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// 테이블이 터미널 너비를 초과하지 않도록 조정
|
|
29
|
+
const totalRequiredWidth = widthPerColumn.reduce((sum, w) => sum + w + 1, 1);
|
|
30
|
+
const shrinkRatio = totalRequiredWidth > maxWidth ? maxWidth / totalRequiredWidth : 1;
|
|
31
|
+
const finalWidths = widthPerColumn.map((w) => Math.floor(w * shrinkRatio));
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 셀 내용을 렌더링 (너비에 맞게 잘라내거나 패딩 추가)
|
|
35
|
+
*/
|
|
36
|
+
const buildCell = (cellText, cellWidth, isHeaderCell = false) => {
|
|
37
|
+
const availableWidth = Math.max(0, cellWidth - 2);
|
|
38
|
+
const actualWidth = calculateTextWidth(cellText);
|
|
39
|
+
|
|
40
|
+
let displayText = cellText;
|
|
41
|
+
if (actualWidth > availableWidth) {
|
|
42
|
+
if (availableWidth <= 3) {
|
|
43
|
+
// 너비가 매우 작으면 단순 잘라내기
|
|
44
|
+
displayText = cellText.substring(0, Math.min(cellText.length, availableWidth));
|
|
45
|
+
} else {
|
|
46
|
+
// 이진 탐색으로 마크다운을 보존하면서 최적 절단점 찾기
|
|
47
|
+
let leftBound = 0;
|
|
48
|
+
let rightBound = cellText.length;
|
|
49
|
+
let bestFit = cellText;
|
|
50
|
+
|
|
51
|
+
while (leftBound <= rightBound) {
|
|
52
|
+
const midPoint = Math.floor((leftBound + rightBound) / 2);
|
|
53
|
+
const testText = cellText.substring(0, midPoint);
|
|
54
|
+
const testWidth = calculateTextWidth(testText);
|
|
55
|
+
|
|
56
|
+
if (testWidth <= availableWidth - 3) {
|
|
57
|
+
bestFit = testText;
|
|
58
|
+
leftBound = midPoint + 1;
|
|
59
|
+
} else {
|
|
60
|
+
rightBound = midPoint - 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
displayText = bestFit + '...';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 정확한 패딩 계산
|
|
69
|
+
const finalDisplayWidth = calculateTextWidth(displayText);
|
|
70
|
+
const paddingRequired = Math.max(0, availableWidth - finalDisplayWidth);
|
|
71
|
+
|
|
72
|
+
return React.createElement(Text, null,
|
|
73
|
+
isHeaderCell
|
|
74
|
+
? React.createElement(Text, { bold: true, color: theme.text.link },
|
|
75
|
+
React.createElement(ProcessInlineText, { content: displayText })
|
|
76
|
+
)
|
|
77
|
+
: React.createElement(ProcessInlineText, { content: displayText }),
|
|
78
|
+
' '.repeat(paddingRequired)
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 테두리 렌더링
|
|
84
|
+
*/
|
|
85
|
+
const buildBorderLine = (position) => {
|
|
86
|
+
const borderStyles = {
|
|
87
|
+
top: { leftCorner: '┌', junction: '┬', rightCorner: '┐', line: '─' },
|
|
88
|
+
mid: { leftCorner: '├', junction: '┼', rightCorner: '┤', line: '─' },
|
|
89
|
+
bottom: { leftCorner: '└', junction: '┴', rightCorner: '┘', line: '─' }
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const style = borderStyles[position];
|
|
93
|
+
const segments = finalWidths.map((width) => style.line.repeat(width));
|
|
94
|
+
const borderText = style.leftCorner + segments.join(style.junction) + style.rightCorner;
|
|
95
|
+
|
|
96
|
+
return React.createElement(Text, { color: theme.text.secondary }, borderText);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 테이블 행 렌더링
|
|
101
|
+
*/
|
|
102
|
+
const buildTableRow = (rowCells, isHeaderRow = false) => {
|
|
103
|
+
const renderedCells = rowCells.map((cellContent, idx) => {
|
|
104
|
+
const width = finalWidths[idx] || 0;
|
|
105
|
+
return buildCell(cellContent || '', width, isHeaderRow);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
return React.createElement(Text, { color: theme.text.primary },
|
|
109
|
+
'│ ',
|
|
110
|
+
...renderedCells.map((cell, idx) =>
|
|
111
|
+
React.createElement(React.Fragment, { key: idx },
|
|
112
|
+
cell,
|
|
113
|
+
idx < renderedCells.length - 1 ? ' │ ' : ''
|
|
114
|
+
)
|
|
115
|
+
),
|
|
116
|
+
' │'
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return React.createElement(Box, { flexDirection: 'column', marginY: 1 },
|
|
121
|
+
// 상단 테두리
|
|
122
|
+
buildBorderLine('top'),
|
|
123
|
+
|
|
124
|
+
// 헤더 행
|
|
125
|
+
buildTableRow(columnHeaders, true),
|
|
126
|
+
|
|
127
|
+
// 중간 테두리
|
|
128
|
+
buildBorderLine('mid'),
|
|
129
|
+
|
|
130
|
+
// 데이터 행
|
|
131
|
+
...dataRows.map((row, idx) =>
|
|
132
|
+
React.createElement(React.Fragment, { key: idx },
|
|
133
|
+
buildTableRow(row)
|
|
134
|
+
)
|
|
135
|
+
),
|
|
136
|
+
|
|
137
|
+
// 하단 테두리
|
|
138
|
+
buildBorderLine('bottom')
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline Formatter
|
|
3
|
+
* 인라인 마크다운 요소를 파싱하여 Ink 컴포넌트로 변환
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { Text } from 'ink';
|
|
8
|
+
import { theme } from '../design/themeColors.js';
|
|
9
|
+
import stringWidth from 'string-width';
|
|
10
|
+
|
|
11
|
+
// 마크다운 마커 길이 상수
|
|
12
|
+
const MARKER_LENGTHS = {
|
|
13
|
+
BOLD: 2, // "**"
|
|
14
|
+
ITALIC: 1, // "*" or "_"
|
|
15
|
+
STRIKE: 2, // "~~"
|
|
16
|
+
CODE: 1, // "`"
|
|
17
|
+
UNDERLINE_START: 3, // "<u>"
|
|
18
|
+
UNDERLINE_END: 4 // "</u>"
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 인라인 마크다운을 렌더링하는 컴포넌트
|
|
23
|
+
*/
|
|
24
|
+
const ProcessInlineTextInternal = ({ content }) => {
|
|
25
|
+
// 마크다운이나 URL이 없으면 바로 반환
|
|
26
|
+
if (!/[*_~`<[https?:]/.test(content)) {
|
|
27
|
+
return React.createElement(Text, { color: theme.text.primary }, content);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const elements = [];
|
|
31
|
+
let currentPosition = 0;
|
|
32
|
+
const patternRegex = /(\*\*.*?\*\*|\*.*?\*|_.*?_|~~.*?~~|\[.*?\]\(.*?\)|`+.+?`+|<u>.*?<\/u>|https?:\/\/\S+)/g;
|
|
33
|
+
let matchResult;
|
|
34
|
+
|
|
35
|
+
while ((matchResult = patternRegex.exec(content)) !== null) {
|
|
36
|
+
if (matchResult.index > currentPosition) {
|
|
37
|
+
elements.push(
|
|
38
|
+
React.createElement(Text, { key: `plain-${currentPosition}` },
|
|
39
|
+
content.slice(currentPosition, matchResult.index)
|
|
40
|
+
)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const matchedText = matchResult[0];
|
|
45
|
+
let formattedElement = null;
|
|
46
|
+
const elementKey = `fmt-${matchResult.index}`;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// 볼드 처리
|
|
50
|
+
if (matchedText.startsWith('**') &&
|
|
51
|
+
matchedText.endsWith('**') &&
|
|
52
|
+
matchedText.length > MARKER_LENGTHS.BOLD * 2) {
|
|
53
|
+
formattedElement = React.createElement(Text, { key: elementKey, bold: true },
|
|
54
|
+
matchedText.slice(MARKER_LENGTHS.BOLD, -MARKER_LENGTHS.BOLD)
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
// 이탤릭 처리
|
|
58
|
+
else if (matchedText.length > MARKER_LENGTHS.ITALIC * 2 &&
|
|
59
|
+
((matchedText.startsWith('*') && matchedText.endsWith('*')) ||
|
|
60
|
+
(matchedText.startsWith('_') && matchedText.endsWith('_'))) &&
|
|
61
|
+
!/\w/.test(content.substring(matchResult.index - 1, matchResult.index)) &&
|
|
62
|
+
!/\w/.test(content.substring(patternRegex.lastIndex, patternRegex.lastIndex + 1)) &&
|
|
63
|
+
!/\S[./\\]/.test(content.substring(matchResult.index - 2, matchResult.index)) &&
|
|
64
|
+
!/[./\\]\S/.test(content.substring(patternRegex.lastIndex, patternRegex.lastIndex + 2))) {
|
|
65
|
+
formattedElement = React.createElement(Text, { key: elementKey, italic: true },
|
|
66
|
+
matchedText.slice(MARKER_LENGTHS.ITALIC, -MARKER_LENGTHS.ITALIC)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
// 취소선 처리
|
|
70
|
+
else if (matchedText.startsWith('~~') &&
|
|
71
|
+
matchedText.endsWith('~~') &&
|
|
72
|
+
matchedText.length > MARKER_LENGTHS.STRIKE * 2) {
|
|
73
|
+
formattedElement = React.createElement(Text, { key: elementKey, strikethrough: true },
|
|
74
|
+
matchedText.slice(MARKER_LENGTHS.STRIKE, -MARKER_LENGTHS.STRIKE)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
// 인라인 코드 처리
|
|
78
|
+
else if (matchedText.startsWith('`') &&
|
|
79
|
+
matchedText.endsWith('`') &&
|
|
80
|
+
matchedText.length > MARKER_LENGTHS.CODE) {
|
|
81
|
+
const codePattern = matchedText.match(/^(`+)(.+?)\1$/s);
|
|
82
|
+
if (codePattern && codePattern[2]) {
|
|
83
|
+
formattedElement = React.createElement(Text, {
|
|
84
|
+
key: elementKey,
|
|
85
|
+
color: theme.status.warning
|
|
86
|
+
}, codePattern[2]);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 링크 처리
|
|
90
|
+
else if (matchedText.startsWith('[') &&
|
|
91
|
+
matchedText.includes('](') &&
|
|
92
|
+
matchedText.endsWith(')')) {
|
|
93
|
+
const linkPattern = matchedText.match(/\[(.*?)\]\((.*?)\)/);
|
|
94
|
+
if (linkPattern) {
|
|
95
|
+
const linkLabel = linkPattern[1];
|
|
96
|
+
const linkUrl = linkPattern[2];
|
|
97
|
+
formattedElement = React.createElement(Text, { key: elementKey },
|
|
98
|
+
linkLabel,
|
|
99
|
+
React.createElement(Text, { color: theme.text.link }, ` (${linkUrl})`)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// 밑줄 처리
|
|
104
|
+
else if (matchedText.startsWith('<u>') &&
|
|
105
|
+
matchedText.endsWith('</u>') &&
|
|
106
|
+
matchedText.length > MARKER_LENGTHS.UNDERLINE_START + MARKER_LENGTHS.UNDERLINE_END - 1) {
|
|
107
|
+
formattedElement = React.createElement(Text, { key: elementKey, underline: true },
|
|
108
|
+
matchedText.slice(MARKER_LENGTHS.UNDERLINE_START, -MARKER_LENGTHS.UNDERLINE_END)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
// URL 처리
|
|
112
|
+
else if (matchedText.match(/^https?:\/\//)) {
|
|
113
|
+
formattedElement = React.createElement(Text, {
|
|
114
|
+
key: elementKey,
|
|
115
|
+
color: theme.text.link
|
|
116
|
+
}, matchedText);
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// 파싱 오류 발생 시 무시
|
|
120
|
+
formattedElement = null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
elements.push(
|
|
124
|
+
formattedElement ?? React.createElement(Text, { key: elementKey }, matchedText)
|
|
125
|
+
);
|
|
126
|
+
currentPosition = patternRegex.lastIndex;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (currentPosition < content.length) {
|
|
130
|
+
elements.push(
|
|
131
|
+
React.createElement(Text, { key: `plain-${currentPosition}` },
|
|
132
|
+
content.slice(currentPosition)
|
|
133
|
+
)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return React.createElement(React.Fragment, null, elements.filter(el => el !== null));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const ProcessInlineText = React.memo(ProcessInlineTextInternal);
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 마크다운 포맷팅을 제거하고 실제 텍스트 길이를 계산
|
|
144
|
+
* 테이블의 컬럼 너비 계산 등에 사용
|
|
145
|
+
*/
|
|
146
|
+
export function calculateTextWidth(content) {
|
|
147
|
+
const plainText = content
|
|
148
|
+
.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
149
|
+
.replace(/\*(.*?)\*/g, '$1')
|
|
150
|
+
.replace(/_(.*?)_/g, '$1')
|
|
151
|
+
.replace(/~~(.*?)~~/g, '$1')
|
|
152
|
+
.replace(/`(.*?)`/g, '$1')
|
|
153
|
+
.replace(/<u>(.*?)<\/u>/g, '$1')
|
|
154
|
+
.replace(/.*\[(.*?)\]\(.*\)/g, '$1');
|
|
155
|
+
return stringWidth(plainText);
|
|
156
|
+
}
|