Skip to content

Commit e244ce1

Browse files
committed
add smd
1 parent f764ad5 commit e244ce1

File tree

3 files changed

+1749
-50
lines changed

3 files changed

+1749
-50
lines changed

aec

Submodule aec updated 1 file

src/ui/ask/AskView.js

Lines changed: 83 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { html, css, LitElement } from '../../ui/assets/lit-core-2.7.4.min.js';
2+
import { parser, parser_write, parser_end, default_renderer } from '../../ui/assets/smd.js';
23

34
export class AskView extends LitElement {
45
static properties = {
@@ -725,6 +726,10 @@ export class AskView extends LitElement {
725726
this.DOMPurify = null;
726727
this.isLibrariesLoaded = false;
727728

729+
// SMD.js streaming markdown parser
730+
this.smdParser = null;
731+
this.smdContainer = null;
732+
this.lastProcessedLength = 0;
728733

729734
this.handleSendText = this.handleSendText.bind(this);
730735
this.handleTextKeydown = this.handleTextKeydown.bind(this);
@@ -763,13 +768,13 @@ export class AskView extends LitElement {
763768
if (container) this.resizeObserver.observe(container);
764769

765770
this.handleQuestionFromAssistant = (event, question) => {
766-
console.log('📨 AskView: Received question from ListenView:', question);
771+
console.log('AskView: Received question from ListenView:', question);
767772
this.handleSendText(null, question);
768773
};
769774

770775
if (window.api) {
771776
window.api.askView.onShowTextInput(() => {
772-
console.log('📤 Show text input signal received');
777+
console.log('Show text input signal received');
773778
if (!this.showTextInput) {
774779
this.showTextInput = true;
775780
this.updateComplete.then(() => this.focusTextInput());
@@ -797,7 +802,7 @@ export class AskView extends LitElement {
797802
}
798803
}
799804
});
800-
console.log('AskView: IPC 이벤트 리스너 등록 완료');
805+
console.log('AskView: IPC 이벤트 리스너 등록 완료');
801806
}
802807
}
803808

@@ -914,6 +919,9 @@ export class AskView extends LitElement {
914919
this.isStreaming = false;
915920
this.headerText = 'AI Response';
916921
this.showTextInput = true;
922+
this.lastProcessedLength = 0;
923+
this.smdParser = null;
924+
this.smdContainer = null;
917925
}
918926

919927
handleInputFocus() {
@@ -985,27 +993,89 @@ export class AskView extends LitElement {
985993
const responseContainer = this.shadowRoot.getElementById('responseContainer');
986994
if (!responseContainer) return;
987995

988-
// ✨ 로딩 상태를 먼저 확인
996+
// Check loading state
989997
if (this.isLoading) {
990998
responseContainer.innerHTML = `
991999
<div class="loading-dots">
9921000
<div class="loading-dot"></div>
9931001
<div class="loading-dot"></div>
9941002
<div class="loading-dot"></div>
9951003
</div>`;
1004+
this.resetStreamingParser();
9961005
return;
997-
}
1006+
}
9981007

999-
// ✨ 응답이 없을 때의 처리
1008+
// If there is no response, show empty state
10001009
if (!this.currentResponse) {
10011010
responseContainer.innerHTML = `<div class="empty-state">...</div>`;
1011+
this.resetStreamingParser();
10021012
return;
10031013
}
10041014

1005-
let textToRender = this.fixIncompleteMarkdown(this.currentResponse);
1006-
textToRender = this.fixIncompleteCodeBlocks(textToRender);
1007-
1015+
// Set streaming markdown parser
1016+
this.renderStreamingMarkdown(responseContainer);
1017+
1018+
// After updating content, recalculate window height
1019+
this.adjustWindowHeightThrottled();
1020+
}
1021+
1022+
resetStreamingParser() {
1023+
this.smdParser = null;
1024+
this.smdContainer = null;
1025+
this.lastProcessedLength = 0;
1026+
}
1027+
1028+
renderStreamingMarkdown(responseContainer) {
1029+
try {
1030+
// 파서가 없거나 컨테이너가 변경되었으면 새로 생성
1031+
if (!this.smdParser || this.smdContainer !== responseContainer) {
1032+
this.smdContainer = responseContainer;
1033+
this.smdContainer.innerHTML = '';
1034+
1035+
// smd.js의 default_renderer 사용
1036+
const renderer = default_renderer(this.smdContainer);
1037+
this.smdParser = parser(renderer);
1038+
this.lastProcessedLength = 0;
1039+
}
1040+
1041+
// 새로운 텍스트만 처리 (스트리밍 최적화)
1042+
const currentText = this.currentResponse;
1043+
const newText = currentText.slice(this.lastProcessedLength);
1044+
1045+
if (newText.length > 0) {
1046+
// 새로운 텍스트 청크를 파서에 전달
1047+
parser_write(this.smdParser, newText);
1048+
this.lastProcessedLength = currentText.length;
1049+
}
1050+
1051+
// 스트리밍이 완료되면 파서 종료
1052+
if (!this.isStreaming && !this.isLoading) {
1053+
parser_end(this.smdParser);
1054+
}
10081055

1056+
// 코드 하이라이팅 적용
1057+
if (this.hljs) {
1058+
responseContainer.querySelectorAll('pre code').forEach(block => {
1059+
if (!block.hasAttribute('data-highlighted')) {
1060+
this.hljs.highlightElement(block);
1061+
block.setAttribute('data-highlighted', 'true');
1062+
}
1063+
});
1064+
}
1065+
1066+
// 스크롤을 맨 아래로
1067+
responseContainer.scrollTop = responseContainer.scrollHeight;
1068+
1069+
} catch (error) {
1070+
console.error('Error rendering streaming markdown:', error);
1071+
// 에러 발생 시 기본 텍스트 렌더링으로 폴백
1072+
this.renderFallbackContent(responseContainer);
1073+
}
1074+
}
1075+
1076+
renderFallbackContent(responseContainer) {
1077+
const textToRender = this.currentResponse || '';
1078+
10091079
if (this.isLibrariesLoaded && this.marked && this.DOMPurify) {
10101080
try {
10111081
// 마크다운 파싱
@@ -1014,42 +1084,13 @@ export class AskView extends LitElement {
10141084
// DOMPurify로 정제
10151085
const cleanHtml = this.DOMPurify.sanitize(parsedHtml, {
10161086
ALLOWED_TAGS: [
1017-
'h1',
1018-
'h2',
1019-
'h3',
1020-
'h4',
1021-
'h5',
1022-
'h6',
1023-
'p',
1024-
'br',
1025-
'strong',
1026-
'b',
1027-
'em',
1028-
'i',
1029-
'ul',
1030-
'ol',
1031-
'li',
1032-
'blockquote',
1033-
'code',
1034-
'pre',
1035-
'a',
1036-
'img',
1037-
'table',
1038-
'thead',
1039-
'tbody',
1040-
'tr',
1041-
'th',
1042-
'td',
1043-
'hr',
1044-
'sup',
1045-
'sub',
1046-
'del',
1047-
'ins',
1087+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'strong', 'b', 'em', 'i',
1088+
'ul', 'ol', 'li', 'blockquote', 'code', 'pre', 'a', 'img', 'table', 'thead',
1089+
'tbody', 'tr', 'th', 'td', 'hr', 'sup', 'sub', 'del', 'ins',
10481090
],
10491091
ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'id', 'target', 'rel'],
10501092
});
10511093

1052-
// HTML 적용
10531094
responseContainer.innerHTML = cleanHtml;
10541095

10551096
// 코드 하이라이팅 적용
@@ -1058,12 +1099,8 @@ export class AskView extends LitElement {
10581099
this.hljs.highlightElement(block);
10591100
});
10601101
}
1061-
1062-
// 스크롤을 맨 아래로
1063-
responseContainer.scrollTop = responseContainer.scrollHeight;
10641102
} catch (error) {
1065-
console.error('Error rendering markdown:', error);
1066-
// 에러 발생 시 일반 텍스트로 표시
1103+
console.error('Error in fallback rendering:', error);
10671104
responseContainer.textContent = textToRender;
10681105
}
10691106
} else {
@@ -1080,9 +1117,6 @@ export class AskView extends LitElement {
10801117

10811118
responseContainer.innerHTML = `<p>${basicHtml}</p>`;
10821119
}
1083-
1084-
// 🚀 After updating content, recalculate window height
1085-
this.adjustWindowHeightThrottled();
10861120
}
10871121

10881122

0 commit comments

Comments
 (0)