diff --git a/frontend/ops_vue_js/src/views/aichat/AiChatView.vue b/frontend/ops_vue_js/src/views/aichat/AiChatView.vue
index d479444..f7e9d20 100644
--- a/frontend/ops_vue_js/src/views/aichat/AiChatView.vue
+++ b/frontend/ops_vue_js/src/views/aichat/AiChatView.vue
@@ -463,6 +463,128 @@ function makeTitle(items) {
return text.length > 40 ? `${text.slice(0, 40)}...` : text
}
+const markdownCache = new Map()
+
+function escapeHtml(value) {
+ return String(value)
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+}
+
+function renderInlineMarkdown(value) {
+ return escapeHtml(value)
+ .replace(/`([^`]+)`/g, '$1')
+ .replace(/!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/g, '$1')
+ .replace(/(?$1')
+ .replace(/\*\*([^*]+)\*\*/g, '$1')
+ .replace(/__([^_]+)__/g, '$1')
+ .replace(/~~([^~]+)~~/g, '$1')
+ .replace(/(^|\s)\*([^*]+)\*(?=\s|$)/g, '$1$2')
+ .replace(/(^|\s)_([^_]+)_(?=\s|$)/g, '$1$2')
+}
+
+function renderMarkdown(value) {
+ const source = String(value || '')
+ if (!source) return ''
+ const cached = markdownCache.get(source)
+ if (cached) return cached
+
+ const lines = source.replace(/\r\n/g, '\n').split('\n')
+ const html = []
+ let paragraph = []
+
+ const flushParagraph = () => {
+ if (!paragraph.length) return
+ html.push(`
${renderInlineMarkdown(paragraph.join('\n')).replace(/\n/g, '
')}
${escapeHtml(code.join('\n'))}`)
+ continue
+ }
+
+ if (!line.trim()) {
+ flushParagraph()
+ continue
+ }
+
+ const heading = line.match(/^(#{1,6})\s+(.+)$/)
+ if (heading) {
+ flushParagraph()
+ const level = heading[1].length
+ html.push(`${renderInlineMarkdown(quotes.join('\n')).replace(/\n/g, '`) + continue + } + + const unordered = line.match(/^\s*[-*+]\s+(.+)$/) + if (unordered) { + flushParagraph() + const items = [unordered[1]] + while (index + 1 < lines.length && /^\s*[-*+]\s+/.test(lines[index + 1])) { + index += 1 + items.push(lines[index].replace(/^\s*[-*+]\s+/, '')) + } + html.push(`
')}
- {{ message.content || (message.role === 'assistant' && pending ? t('aichat.thinking') : '') }} +
++ {{ message.content }}