11import { 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
34export 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