11<script setup lang="ts">
22import { ref , reactive , computed , watch , onMounted , nextTick } from ' vue'
33import { useData , useRoute } from ' vitepress'
4- import { marked } from ' marked'
4+ import MarkdownRender , { getMarkdown , parseMarkdownToStructure } from ' markstream-vue'
5+ import ' markstream-vue/index.css'
56import type { Message , Session , Settings , UIState , ProviderId , PageContext } from ' ../types'
67import { PROVIDERS } from ' ../types'
78import { encrypt , decrypt , getEncryptionSecret } from ' ../services/crypto'
@@ -16,18 +17,15 @@ import {
1617import { chat } from ' ../services/ai-provider'
1718import { getMessages } from ' ../locales'
1819
19- // Configure marked for safe rendering
20- marked .setOptions ({
21- gfm: true ,
22- breaks: true ,
23- })
24-
2520const { lang, page, frontmatter } = useData ()
2621const route = useRoute ()
2722
2823// i18n - auto switch based on VitePress locale
2924const t = computed (() => getMessages (lang .value ))
3025
26+ // Initialize markdown parser for streaming
27+ const markdownParser = getMarkdown ()
28+
3129// Get current page context for AI
3230function getPageContext(): PageContext {
3331 const title = frontmatter .value ?.title || page .value ?.title || document .title
@@ -219,6 +217,8 @@ async function sendMessage() {
219217 role: ' user' ,
220218 content: inputMessage .value .trim (),
221219 createdAt: Date .now (),
220+ // Parse user message to nodes immediately
221+ nodes: parseMarkdownToStructure (inputMessage .value .trim (), markdownParser ),
222222 }
223223
224224 session .messages .push (userMessage )
@@ -269,6 +269,8 @@ async function sendMessage() {
269269 }
270270
271271 assistantMessage .content += chunk .content
272+ // Real-time parse content to nodes for streaming rendering
273+ assistantMessage .nodes = parseMarkdownToStructure (assistantMessage .content , markdownParser )
272274 scrollToBottom ()
273275
274276 if (chunk .done ) {
@@ -315,15 +317,6 @@ function deleteMessage(messageId: string) {
315317 saveSessions (sessions .value )
316318}
317319
318- function renderMarkdown(content : string ): string {
319- if (! content ) return ' '
320- try {
321- return marked .parse (content ) as string
322- } catch {
323- return content
324- }
325- }
326-
327320// ============ Lifecycle ============
328321
329322onMounted (() => {
@@ -590,7 +583,13 @@ watch(() => settings.provider, (newProvider) => {
590583 <div v-else-if =" message.error" class =" error-message" >
591584 {{ message.error }}
592585 </div >
593- <div v-else class =" message-text markdown-body" v-html =" renderMarkdown(message.content)" ></div >
586+ <MarkdownRender
587+ v-else
588+ class =" message-text"
589+ :nodes =" (message.nodes || parseMarkdownToStructure(message.content, markdownParser)) as any"
590+ :max-live-nodes =" 0"
591+ custom-id =" ai-chat"
592+ />
594593 <button
595594 v-if =" !message.loading"
596595 class =" delete-message-btn"
@@ -1215,99 +1214,10 @@ watch(() => settings.provider, (newProvider) => {
12151214 color : var (--vp-c-danger-1 );
12161215}
12171216
1218- /* Markdown content styles */
1219- .markdown-body {
1220- word-wrap : break-word ;
1221- }
1222-
1223- .markdown-body :deep(p ) {
1224- margin : 0 0 8px ;
1225- }
1226-
1227- .markdown-body :deep(p :last-child ) {
1228- margin-bottom : 0 ;
1229- }
1230-
1231- .markdown-body :deep(pre ) {
1232- background : var (--vp-c-bg-mute );
1233- padding : 12px ;
1234- border-radius : 6px ;
1235- overflow-x : auto ;
1236- margin : 8px 0 ;
1237- }
1238-
1239- .markdown-body :deep(code ) {
1240- background : var (--vp-c-bg-mute );
1241- padding : 2px 6px ;
1242- border-radius : 4px ;
1243- font-size : 13px ;
1244- font-family : var (--vp-font-family-mono );
1245- }
1246-
1247- .markdown-body :deep(pre code ) {
1248- background : transparent ;
1249- padding : 0 ;
1250- }
1251-
1252- .markdown-body :deep(ul ),
1253- .markdown-body :deep(ol ) {
1254- padding-left : 20px ;
1255- margin : 8px 0 ;
1256- }
1257-
1258- .markdown-body :deep(li ) {
1259- margin : 4px 0 ;
1260- }
1261-
1262- .markdown-body :deep(blockquote ) {
1263- border-left : 3px solid var (--vp-c-brand-1 );
1264- padding-left : 12px ;
1265- margin : 8px 0 ;
1266- color : var (--vp-c-text-2 );
1267- }
1268-
1269- .markdown-body :deep(h1 ),
1270- .markdown-body :deep(h2 ),
1271- .markdown-body :deep(h3 ),
1272- .markdown-body :deep(h4 ) {
1273- margin : 12px 0 8px ;
1274- font-weight : 600 ;
1275- }
1276-
1277- .markdown-body :deep(h1 ) { font-size : 1.4em ; }
1278- .markdown-body :deep(h2 ) { font-size : 1.2em ; }
1279- .markdown-body :deep(h3 ) { font-size : 1.1em ; }
1280-
1281- .markdown-body :deep(a ) {
1282- color : var (--vp-c-brand-1 );
1283- text-decoration : none ;
1284- }
1285-
1286- .markdown-body :deep(a :hover ) {
1287- text-decoration : underline ;
1288- }
1289-
1290- .markdown-body :deep(table ) {
1291- border-collapse : collapse ;
1292- width : 100% ;
1293- margin : 8px 0 ;
1294- }
1295-
1296- .markdown-body :deep(th ),
1297- .markdown-body :deep(td ) {
1298- border : 1px solid var (--vp-c-divider );
1299- padding : 8px ;
1300- text-align : left ;
1301- }
1302-
1303- .markdown-body :deep(th ) {
1304- background : var (--vp-c-bg-soft );
1305- }
1306-
1307- .markdown-body :deep(hr ) {
1308- border : none ;
1309- border-top : 1px solid var (--vp-c-divider );
1310- margin : 12px 0 ;
1217+ /* Override markstream-vue styles for chat context */
1218+ .message-text :deep(.markstream-vue ) {
1219+ font-size : 14px ;
1220+ line-height : 1.6 ;
13111221}
13121222
13131223@media (max-width : 768px ) {
0 commit comments