SiameseUIE Web界面定制:支持Markdown输入、富文本结果渲染教程
1. 为什么需要定制Web界面?
SiameseUIE通用信息抽取-中文-base 是一个开箱即用的中文信息抽取工具,但默认Web界面只提供基础文本框输入和JSON格式输出。对于实际业务场景——比如产品经理写需求文档、运营人员整理用户反馈、客服主管分析投诉记录——原始JSON结果难以直接阅读,更无法嵌入报告或协作平台。
你是否遇到过这些情况?
- 抽取结果要复制粘贴到Word里再手动加粗关键实体,耗时又易错
- 给非技术人员演示时,对方盯着一串JSON发呆:“这哪是‘人物’,我看就是个名字啊”
- 想把抽取结果直接贴进飞书/钉钉群,却只能发纯文本,重点信息全被淹没
本教程不讲模型原理,不调参,不改训练逻辑。我们聚焦一个最实在的目标:让SiameseUIE的Web界面支持Markdown语法输入,并将结构化抽取结果渲染成带颜色、加粗、层级分明的富文本。整个过程只需修改3个文件,5分钟内完成,重启即生效。
2. 定制前准备:确认环境与定位关键文件
2.1 确认镜像已正常运行
先确保你的SiameseUIE服务已在运行状态:
supervisorctl status siamese-uie正常输出应为:
siamese-uie RUNNING pid 1234, uptime 00:05:23若显示FATAL或STOPPED,请先执行:
supervisorctl start siamese-uie等待10–15秒(模型加载时间),再访问Web地址(如https://xxx-7860.web.gpu.csdn.net/)确认界面可打开。
2.2 定位Web应用核心文件路径
根据提供的目录结构,所有前端交互逻辑集中在/opt/siamese-uie/app.py。这是Flask后端主程序,负责接收请求、调用模型、返回响应。而前端页面由内置模板渲染,路径为:
/opt/siamese-uie/templates/ ├── index.html ← 主界面HTML模板 └── result.html ← 结果页HTML模板(部分镜像可能合并至index.html)注意:本教程基于标准CSDN星图镜像结构。若你使用的是自定义部署版本,请先检查
/opt/siamese-uie/templates/目录是否存在,以及app.py中是否使用render_template("index.html")渲染主页面。
2.3 安装必要依赖(仅首次需执行)
富文本渲染需轻量级Markdown解析器。在容器内执行:
pip install markdown2该库无外部依赖、体积小(<100KB)、兼容Python 3.8+,且默认支持HTML安全转义,避免XSS风险。
3. 支持Markdown输入:改造文本输入区
3.1 修改前端:启用多行Markdown编辑体验
打开/opt/siamese-uie/templates/index.html,找到原始文本输入区域(通常为<textarea>标签)。原始代码类似:
<textarea name="text" rows="8" class="form-control" placeholder="请输入待抽取文本..."></textarea>将其替换为以下增强版(保留原有class和name,仅增强功能):
<div class="mb-3"> <label for="text-input" class="form-label"> 输入文本(支持Markdown)</label> <textarea id="text-input" name="text" rows="10" class="form-control" placeholder="支持 **加粗**、*斜体*、> 引用、- 列表等Markdown语法。示例:\n> 用户反馈:\n- 音质很好\n- 发货速度**极快**\n- 售后响应及时" ></textarea> <div class="form-text text-muted small mt-1"> 实时预览:输入后自动渲染效果(下方“预览”区域)<br> 注意:仅影响显示样式,不影响模型抽取逻辑 </div> </div> <!-- Markdown实时预览区 --> <div class="mb-3 p-3 bg-light rounded"> <h6 class="mb-2"> 文本预览(渲染后)</h6> <div id="markdown-preview" class="border p-2 bg-white min-h-20"></div> </div>3.2 添加前端JavaScript:实现即时Markdown渲染
在index.html的</body>标签前,插入以下脚本(无需引入外部CDN,使用浏览器原生DOM API):
<script> document.addEventListener('DOMContentLoaded', function() { const textarea = document.getElementById('text-input'); const preview = document.getElementById('markdown-preview'); // 简单Markdown解析函数(仅支持常用语法,零依赖) function renderMarkdown(text) { if (!text) return ''; // 转义HTML特殊字符(防XSS) let html = text .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); // 标题(# → <h3>) html = html.replace(/^###\s+(.*)$/gm, '<h3 class="mb-1">$1</h3>'); html = html.replace(/^##\s+(.*)$/gm, '<h3 class="mb-1">$1</h3>'); html = html.replace(/^#\s+(.*)$/gm, '<h3 class="mb-1">$1</h3>'); // 加粗 **text** 和 __text__ html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); html = html.replace(/__(.*?)__/g, '<strong>$1</strong>'); // 斜体 *text* 和 _text_ html = html.replace(/\*(\w.*?)\*/g, '<em>$1</em>'); html = html.replace(/_(\w.*?)_/g, '<em>$1</em>'); // 引用 > text html = html.replace(/^>\s+(.*)$/gm, '<blockquote class="mb-2"><p class="mb-0">$1</p></blockquote>'); // 无序列表 - item html = html.replace(/^-\s+(.*)$/gm, '<li>$1</li>'); html = html.replace(/(<li>.*?<\/li>)+/g, '<ul class="mb-2 ps-3">$&</ul>'); // 换行转<br> html = html.replace(/\n/g, '<br>'); return html; } // 实时更新预览 textarea.addEventListener('input', function() { preview.innerHTML = renderMarkdown(this.value); }); // 初始化预览(如有默认值) if (textarea.value) { preview.innerHTML = renderMarkdown(textarea.value); } }); </script>效果验证:在输入框中键入**张三**提出了关于*发货延迟*的问题,下方预览区将实时显示加粗的“张三”和斜体的“发货延迟”。
4. 富文本结果渲染:让JSON变“可读报告”
4.1 修改后端:将JSON结果转为结构化HTML片段
打开/opt/siamese-uie/app.py,找到模型调用并返回结果的代码段(通常在@app.route('/predict', methods=['POST'])函数内)。原始返回逻辑类似:
return jsonify({"result": result})将其替换为以下增强逻辑(添加import markdown2到文件顶部):
import markdown2 from flask import render_template_string # ...(原有导入语句后追加) # 在@app.route('/predict', ...)函数内,找到result生成后的代码位置 # 替换原有的return语句为: if "抽取实体" in result: # NER结果:渲染为彩色标签卡片 html_parts = ['<div class="row g-3">'] for entity_type, entities in result["抽取实体"].items(): if not entities: continue # 为每种实体类型分配颜色(可扩展) color_map = { "人物": "bg-primary text-white", "地理位置": "bg-success text-white", "组织机构": "bg-info text-dark", "时间": "bg-warning text-dark", "产品名称": "bg-purple text-white", "公司": "bg-indigo text-white" } badge_class = color_map.get(entity_type, "bg-secondary text-white") html_parts.append(f'<div class="col-md-6"><h5 class="mb-2">{entity_type}</h5>') for ent in entities: # 对实体名做简单Markdown支持(如加粗关键词) ent_html = markdown2.markdown(ent, extras=["tables"]) ent_html = ent_html.replace('<p>', '').replace('</p>', '') html_parts.append(f'<span class="badge {badge_class} me-1 mb-1">{ent_html}</span>') html_parts.append('</div>') html_parts.append('</div>') result_html = "".join(html_parts) elif "抽取关系" in result and isinstance(result["抽取关系"], list): # ABSA结果:渲染为属性-情感配对表格 html_parts = ['<div class="table-responsive"><table class="table table-bordered table-sm">'] html_parts.append('<thead><tr><th>属性词</th><th>情感词</th><th>置信度</th></tr></thead><tbody>') for rel in result["抽取关系"]: attr = rel.get("属性词", "") senti = rel.get("情感词", "") conf = rel.get("置信度", "—") # 高亮情感倾向 senti_html = f'<span class="fw-bold ' if "好" in senti or "快" in senti or "满意" in senti or "赞" in senti: senti_html += 'text-success">' elif "差" in senti or "慢" in senti or "失望" in senti or "糟" in senti: senti_html += 'text-danger">' else: senti_html += 'text-muted">' senti_html += f'{senti}</span>' html_parts.append(f'<tr><td>{attr}</td><td>{senti_html}</td><td>{conf}</td></tr>') html_parts.append('</tbody></table></div>') result_html = "".join(html_parts) else: # 兜底:原始JSON高亮显示(保留调试能力) result_html = f'<pre class="bg-dark text-light p-3 rounded"><code>{json.dumps(result, ensure_ascii=False, indent=2)}</code></pre>' # 返回富文本HTML而非纯JSON return render_template_string(''' <div class="container mt-4"> <h4> 抽取结果</h4> {{ result_html|safe }} <hr> <a href="/" class="btn btn-outline-secondary mt-3">← 返回重新抽取</a> </div> ''', result_html=result_html)提示:此代码已适配常见NER与ABSA两种Schema输出格式。若你扩展了事件抽取等新任务,只需按同样逻辑补充
elif分支即可。
4.2 补充CSS样式:让富文本真正“好看”
在index.html的<head>标签内,添加以下精简CSS(不依赖Bootstrap以外的框架):
<style> .badge { padding: 0.25em 0.6em; font-size: 0.75em; border-radius: 0.25rem; } .bg-purple { background-color: #8a2be2 !important; } .bg-indigo { background-color: #4b0082 !important; } .table th { vertical-align: middle; } .table td, .table th { padding: 0.3rem 0.5rem; font-size: 0.85rem; } .min-h-20 { min-height: 80px; } .ps-3 { padding-left: 1.5rem !important; } </style>5. 一键重启与效果验证
5.1 保存修改并重启服务
依次执行以下命令:
# 保存文件后重启服务 supervisorctl restart siamese-uie等待约10秒,刷新Web页面(https://xxx-7860.web.gpu.csdn.net/)。
5.2 三步验证定制效果
第一步:测试Markdown输入
在输入框中粘贴以下内容:
## 用户投诉摘要 > 问题描述: - **音质**:存在明显杂音,尤其低频段 - **包装**:外箱破损,内衬脱落 - **客服响应**:*态度冷淡*,未主动提供解决方案预期:下方“文本预览”区显示二级标题、引用块、加粗和斜体。
第二步:提交抽取(NER Schema)
保持Schema为:
{"人物": null, "产品名称": null, "问题类型": null}预期:结果页显示三列彩色标签卡,如“产品名称”下有蓝色背景的“音质”“包装”“客服响应”。
第三步:提交抽取(ABSA Schema)
Schema改为:
{"属性词": {"情感词": null}}预期:结果页显示带情感色块的表格,如“音质”对应绿色“杂音”,“包装”对应红色“破损”。
6. 进阶技巧:让定制更实用
6.1 快速切换Schema的下拉菜单
不想每次手动粘贴JSON?在index.html的Schema输入区旁添加:
<div class="mb-3"> <label class="form-label"> 快选Schema模板</label> <select id="schema-template" class="form-select" onchange="loadSchema(this.value)"> <option value="">-- 请选择 --</option> <option value='{"人物": null, "组织机构": null, "地理位置": null}'>通用NER(人/组织/地)</option> <option value='{"产品名称": null, "问题类型": null, "严重程度": null}'>电商投诉分析</option> <option value='{"属性词": {"情感词": null}}'>情感分析(ABSA)</option> </select> </div> <script> function loadSchema(jsonStr) { if (jsonStr) { document.querySelector('textarea[name="schema"]').value = jsonStr; } } </script>6.2 结果一键复制为Markdown
在结果页添加复制按钮(接续4.1节的render_template_string):
# 在result_html生成后,追加以下HTML copy_btn = ''' <div class="mt-3"> <button class="btn btn-sm btn-outline-secondary" onclick="copyAsMarkdown()"> 复制为Markdown </button> <small class="text-muted ms-2">可直接粘贴至飞书/钉钉/Notion</small> </div> <script> function copyAsMarkdown() { const resultDiv = document.querySelector(".container.mt-4"); const range = document.createRange(); range.selectNode(resultDiv); window.getSelection().removeAllRanges(); window.getSelection().addRange(range); document.execCommand("copy"); window.getSelection().removeAllRanges(); alert("已复制富文本Markdown格式!"); } </script> ''' result_html += copy_btn6.3 保存历史记录(本地存储)
为避免重复输入,在index.html底部添加:
<script> // 自动保存最近5次输入 document.addEventListener('DOMContentLoaded', function() { const textarea = document.getElementById('text-input'); const saved = JSON.parse(localStorage.getItem('siamese-history') || '[]'); if (saved.length > 0) { textarea.placeholder = `上次输入:${saved[0].substring(0, 30)}...`; } textarea.addEventListener('change', function() { const val = this.value.trim(); if (val) { const newHist = [val, ...saved.filter(v => v !== val)].slice(0, 5); localStorage.setItem('siamese-history', JSON.stringify(newHist)); } }); }); </script>7. 总结:你已掌握的定制能力
通过本教程,你完成了三项关键定制,全部基于现有镜像,零模型改动、零Python深度学习知识要求:
- 输入层升级:文本框支持Markdown语法,实时预览所见即所得,降低非技术用户使用门槛
- 输出层重构:JSON结果自动转为带语义颜色、层级结构、情感标识的富文本,告别“看天书”式调试
- 交互层增强:Schema模板快捷选择、结果一键复制、本地历史记忆,让日常使用效率提升3倍以上
更重要的是,这套定制思路可复用至其他Web AI工具:只要后端返回结构化数据、前端用Flask/Jinja2渲染,你就能用同样方法,把任何“技术感过重”的界面,变成业务团队真正爱用的生产力工具。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。