feat(app): add markdown rendering in chat messages
Install markdown-it and render assistant messages through it with safe defaults (html: false, linkify, breaks). Add CSS for code blocks, lists, links, blockquotes, headings. Links open in new tab. User messages remain plain text. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
cb3a50a920
commit
81ccd69aae
@@ -9,8 +9,17 @@
|
||||
:class="[bubbleClasses, { 'cursor-pointer': hasContext }]"
|
||||
@click="handleBubbleClick"
|
||||
>
|
||||
<p class="text-sm leading-relaxed whitespace-pre-wrap break-words"
|
||||
:class="isDark ? 'text-white/90' : 'text-gray-800'">{{ displayText }}</p>
|
||||
<div
|
||||
v-if="!isUser"
|
||||
class="chat-markdown text-sm leading-relaxed break-words"
|
||||
:class="isDark ? 'text-white/90' : 'text-gray-800'"
|
||||
v-html="renderedMarkdown"
|
||||
/>
|
||||
<p
|
||||
v-else
|
||||
class="text-sm leading-relaxed whitespace-pre-wrap break-words"
|
||||
:class="isDark ? 'text-white/90' : 'text-gray-800'"
|
||||
>{{ displayText }}</p>
|
||||
|
||||
<div v-if="inlineFilms.length > 0" class="mt-3 space-y-1" @click.stop>
|
||||
<FilmCard
|
||||
@@ -164,6 +173,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import type { Message, WebSearchResult } from '@aiui/core/types/message'
|
||||
import type { Film, Song, Podcast, Book, TVSeries, ImageItem, Place } from '@aiui/core/types/content'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
@@ -231,6 +241,22 @@ const hasContext = computed(() => !isUser.value && (
|
||||
inlineMagazineSections.value.length > 0
|
||||
))
|
||||
|
||||
const md = new MarkdownIt({
|
||||
html: false,
|
||||
linkify: true,
|
||||
breaks: true,
|
||||
})
|
||||
|
||||
// Open links in new tab
|
||||
const defaultRender = md.renderer.rules.link_open || function (tokens, idx, options, _env, self) {
|
||||
return self.renderToken(tokens, idx, options)
|
||||
}
|
||||
md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
|
||||
tokens[idx].attrSet('target', '_blank')
|
||||
tokens[idx].attrSet('rel', 'noopener noreferrer')
|
||||
return defaultRender(tokens, idx, options, env, self)
|
||||
}
|
||||
|
||||
const displayText = computed(() => {
|
||||
if (isUser.value) return props.message.content
|
||||
let text = stripContentTags(props.message.content)
|
||||
@@ -238,6 +264,10 @@ const displayText = computed(() => {
|
||||
return text
|
||||
})
|
||||
|
||||
const renderedMarkdown = computed(() => {
|
||||
return md.render(displayText.value)
|
||||
})
|
||||
|
||||
const formattedTime = computed(() => {
|
||||
const d = new Date(props.message.timestamp)
|
||||
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
|
||||
Reference in New Issue
Block a user