From 23396caeebdbae769b05788bd4a14948db842217 Mon Sep 17 00:00:00 2001
From: Echo <1711788888@qq.com>
Date: Mon, 2 Mar 2026 00:51:11 +0800
Subject: [PATCH] =?UTF-8?q?:art:=20=E4=BC=98=E5=8C=96=E5=89=8D=E7=AB=AF?=
=?UTF-8?q?=E6=B8=B2=E6=9F=93?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Echo <1711788888@qq.com>
---
web-app/src/components/MessageContent.tsx | 203 +++++++++++++++++-----
1 file changed, 164 insertions(+), 39 deletions(-)
diff --git a/web-app/src/components/MessageContent.tsx b/web-app/src/components/MessageContent.tsx
index 740888b..e72ffc9 100644
--- a/web-app/src/components/MessageContent.tsx
+++ b/web-app/src/components/MessageContent.tsx
@@ -68,21 +68,40 @@ function parseChoices(content: string): { choices: Choice[]; cleanContent: strin
const match = content.match(choiceRegex)
if (!match) {
- // 如果没有标准格式,尝试从HTML中提取选择项
- // 匹配类似 "1.xxx" 或 "A.xxx" 的列表项
- const htmlChoiceRegex = /
\s*(\d+|[A-Z])\s*[.、::]\s*([^<]+)/gi
+ // 尝试匹配纯文本格式的选项列表
+ // 匹配格式: A: xxx\nB: xxx\nC: xxx 或 A. xxx\nB. xxx
+ const textChoiceRegex = /^([A-E])[.、::]\s*(.+?)(?=\n[A-E][.、::]|\n*$)/gm
const choices: Choice[] = []
+ let textMatch
+
+ while ((textMatch = textChoiceRegex.exec(content)) !== null) {
+ choices.push({
+ label: textMatch[1],
+ text: textMatch[2].trim()
+ })
+ }
+
+ // 如果找到了至少2个选项,认为是有效的选择列表
+ if (choices.length >= 2) {
+ // 移除选项列表,保留其他内容
+ const cleanContent = content.replace(textChoiceRegex, '').trim()
+ return { choices, cleanContent }
+ }
+
+ // 如果没有纯文本格式,尝试从HTML中提取选择项
+ const htmlChoiceRegex = /
\s*(\d+|[A-Z])\s*[.、::]\s*([^<]+)/gi
+ const htmlChoices: Choice[] = []
let htmlMatch
while ((htmlMatch = htmlChoiceRegex.exec(content)) !== null) {
- choices.push({
+ htmlChoices.push({
label: htmlMatch[1],
text: htmlMatch[2].trim()
})
}
- if (choices.length > 0) {
- return { choices, cleanContent: content }
+ if (htmlChoices.length > 0) {
+ return { choices: htmlChoices, cleanContent: content }
}
return { choices: [], cleanContent: content }
@@ -145,7 +164,7 @@ export default function MessageContent({ content, role, onChoiceSelect }: Messag
const [showRaw, setShowRaw] = useState(false)
const [hasHtml, setHasHtml] = useState(false)
const [hasScript, setHasScript] = useState(false)
- const [allowScript, setAllowScript] = useState(true) // 默认启用脚本
+ const [allowScript, setAllowScript] = useState(false) // 默认禁用脚本
const [choices, setChoices] = useState([])
const [displayContent, setDisplayContent] = useState(content)
const [statusPanel, setStatusPanel] = useState(null)
@@ -154,32 +173,106 @@ export default function MessageContent({ content, role, onChoiceSelect }: Messag
useEffect(() => {
console.log('[MessageContent] 原始内容:', content)
+ // 提取 markdown 代码块中的 HTML,并从原始内容中移除代码块标记
+ // 支持 ```html 和不带标识符的 ``` 包裹(后者在内容含 HTML 标签时识别为 HTML 块)
+ let processedContent = content
+ let remainingContent = content
+ let isHtmlCodeBlock = false
+ const htmlTagRegex = /<[a-zA-Z][^>]*>/
+ // 先尝试匹配 ```html 代码块
+ const explicitHtmlRegex = /```html\s*([\s\S]*?)```/gi
+ const htmlCodeBlocks: string[] = []
+ let htmlMatch
+
+ while ((htmlMatch = explicitHtmlRegex.exec(content)) !== null) {
+ htmlCodeBlocks.push(htmlMatch[1].trim())
+ }
+
+ if (htmlCodeBlocks.length > 0) {
+ // 有明确的 ```html 标识
+ processedContent = htmlCodeBlocks.join('\n')
+ remainingContent = content.replace(/```html\s*[\s\S]*?```/gi, '').trim()
+ isHtmlCodeBlock = true
+ console.log('[MessageContent] 提取到 ```html 代码块:', processedContent)
+ console.log('[MessageContent] 剩余内容:', remainingContent)
+ } else {
+ // 尝试匹配无语言标识的 ``` 代码块,内容含 HTML 标签时也视为 HTML 块
+ const genericCodeRegex = /```\s*\n?([\s\S]*?)```/g
+ const genericBlocks: string[] = []
+ let genericMatch
+ while ((genericMatch = genericCodeRegex.exec(content)) !== null) {
+ const blockContent = genericMatch[1].trim()
+ if (htmlTagRegex.test(blockContent)) {
+ genericBlocks.push(blockContent)
+ }
+ }
+ if (genericBlocks.length > 0) {
+ processedContent = genericBlocks.join('\n')
+ remainingContent = content.replace(/```\s*\n?[\s\S]*?```/g, '').trim()
+ isHtmlCodeBlock = true
+ console.log('[MessageContent] 提取到通用 HTML 代码块:', processedContent)
+ console.log('[MessageContent] 剩余内容:', remainingContent)
+ }
+ }
+
// 解析状态面板
- const { status, cleanContent: contentAfterStatus } = parseStatusPanel(content)
+ const { status, cleanContent: contentAfterStatus } = parseStatusPanel(processedContent)
console.log('[MessageContent] 状态面板:', status)
setStatusPanel(status)
- // 解析选择项
- const { choices: parsedChoices, cleanContent } = parseChoices(contentAfterStatus)
- console.log('[MessageContent] 选择项:', parsedChoices)
+ // 解析选择项(从剩余内容中解析,而不是从 HTML 中解析)
+ let parsedChoices: Choice[] = []
+ let cleanContent = contentAfterStatus
+
+ if (isHtmlCodeBlock && remainingContent) {
+ // 如果有 HTML 代码块,从剩余内容中解析选项
+ const choiceResult = parseChoices(remainingContent)
+ parsedChoices = choiceResult.choices
+ console.log('[MessageContent] 从剩余内容解析选择项:', parsedChoices)
+ } else if (!isHtmlCodeBlock) {
+ // 如果没有 HTML 代码块,正常解析
+ const choiceResult = parseChoices(contentAfterStatus)
+ parsedChoices = choiceResult.choices
+ cleanContent = choiceResult.cleanContent
+ console.log('[MessageContent] 选择项:', parsedChoices)
+ }
setChoices(parsedChoices)
- // 清理脚本输出
- const finalContent = cleanScriptOutput(cleanContent)
+ // 清理脚本输出(只在非 HTML 代码块时清理)
+ // 如果是HTML代码块,直接使用提取的HTML内容(processedContent),而不是contentAfterStatus
+ const finalContent = isHtmlCodeBlock ? processedContent : cleanScriptOutput(cleanContent)
console.log('[MessageContent] 清理后内容:', finalContent)
- setDisplayContent(finalContent)
- // 检测内容类型
+ // 先检测内容类型(需要在转换代码块之前检测,以便使用原始 finalContent)
const htmlRegex = /<[^>]+>/g
const scriptRegex = /