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>
195 lines
3.9 KiB
Vue
195 lines
3.9 KiB
Vue
<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>
|