Files
llm-in-text/src/components/MarkdownPreview.vue
ydy0615 59334e4057 Stabilize pro editing without heavy office runtime
The workspace now carries the pro editing flow, streaming completion path, and lighter Office preview state as one checkpoint so the remote has the current runnable project shape.

Constraint: Preserve the current workspace as a single reviewable project commit while excluding local agent state and verification artifacts. Removed stale Univer runtime dependencies from the lockfile so installs match package.json.

Rejected: Commit runtime screenshots, .omx state, and coverage files | they are local artifacts rather than source state.

Confidence: medium

Scope-risk: broad

Directive: Keep package.json and package-lock.json synchronized when changing frontend dependencies.

Tested: npm run build; C:\Users\ydy\.conda\envs\llmwebsite\python.exe -m pytest backend/tests/test_main_endpoints.py backend/tests/test_main_cancel.py backend/tests/test_llm.py backend/tests/test_llm_extended.py -v -o addopts= (44 passed).

Not-tested: Full pytest with repository coverage addopts currently reports 0% coverage because pytest-cov watches backend.* module names while tests import top-level backend modules.

Co-authored-by: OmX <omx@oh-my-codex.dev>
2026-05-24 23:30:32 +08:00

195 lines
3.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="preview-container" v-html="renderedContent"></div>
</template>
<script setup>
import { computed } from 'vue'
import MarkdownIt from 'markdown-it'
import katex from 'katex'
import 'katex/dist/katex.min.css'
import { hiddenTextMarkdownItPlugin } from '../utils/hiddenText.js'
const props = defineProps({
content: {
type: String,
default: ''
}
})
const md = new MarkdownIt({
html: false,
linkify: true,
typographer: true
})
md.use(hiddenTextMarkdownItPlugin)
// 预处理 markdown转换 $...$ 为 <span class="math-inline">...</span>
const preprocessLatex = (text) => {
// 处理 $$...$$ 块级公式
text = text.replace(/\$\$([\s\S]*?)\$\$/g, (match, content) => {
try {
const html = katex.renderToString(content.trim(), {
displayMode: true,
throwOnError: false
})
return `<div class="math-block">${html}</div>`
} catch (e) {
return `<div class="math-error">$$${content}$$</div>`
}
})
// 处理 $...$ 行内公式
// 使用负向前瞻和负向后瞻来避免与 $$ 冲突
text = text.replace(/(?<!\$)\$(?!\$)([^\$\n]+?)\$(?!\$)/g, (match, content) => {
try {
const html = katex.renderToString(content.trim(), {
displayMode: false,
throwOnError: false
})
return `<span class="math-inline">${html}</span>`
} catch (e) {
return `<span class="math-error">${match}</span>`
}
})
return text
}
const renderedContent = computed(() => {
if (!props.content) return '<p></p>'
// 先预处理 LaTeX
const processed = preprocessLatex(props.content)
// 然后渲染 markdown
return md.render(processed)
})
</script>
<style scoped>
.preview-container {
width: 100%;
height: 100%;
padding: 20px 40px;
overflow-y: auto;
background-color: #ffffff;
color: #333;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
}
.preview-container :deep(.math-block) {
display: block;
margin: 1em 0;
text-align: center;
overflow-x: auto;
overflow-y: hidden;
padding: 8px 0;
}
.preview-container :deep(.math-inline) {
font-size: 1.1em;
padding: 0 2px;
}
.preview-container :deep(.math-error) {
color: #dc3545;
background-color: #f8d7da;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
}
.preview-container :deep(h1),
.preview-container :deep(h2),
.preview-container :deep(h3),
.preview-container :deep(h4),
.preview-container :deep(h5),
.preview-container :deep(h6) {
margin-top: 1em;
margin-bottom: 0.5em;
font-weight: 600;
line-height: 1.25;
}
.preview-container :deep(p) {
margin: 1em 0;
}
.preview-container :deep(code) {
background-color: #f5f5f5;
padding: 0.2em 0.4em;
border-radius: 3px;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Mono', monospace;
font-size: 0.9em;
}
.preview-container :deep(pre) {
background-color: #f5f5f5;
padding: 16px;
border-radius: 6px;
overflow-x: auto;
}
.preview-container :deep(pre code) {
background-color: transparent;
padding: 0;
}
.preview-container :deep(blockquote) {
border-left: 4px solid #ddd;
margin: 1em 0;
padding-left: 16px;
color: #666;
}
.preview-container :deep(ul),
.preview-container :deep(ol) {
padding-left: 2em;
margin: 1em 0;
}
.preview-container :deep(li) {
margin: 0.25em 0;
}
.preview-container :deep(a) {
color: #4a90d9;
text-decoration: none;
}
.preview-container :deep(a:hover) {
text-decoration: underline;
}
.preview-container :deep(img) {
max-width: 100%;
height: auto;
}
.preview-container :deep(table) {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
.preview-container :deep(th),
.preview-container :deep(td) {
border: 1px solid #ddd;
padding: 8px 12px;
text-align: left;
}
.preview-container :deep(th) {
background-color: #f5f5f5;
font-weight: 600;
}
.preview-container :deep(hr) {
border: none;
border-top: 1px solid #ddd;
margin: 2em 0;
}
</style>