尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Nanbeige 4.1-3B Streamlit WebUI实战教程:添加Markdown渲染与代码高亮

Nanbeige 4.1-3B Streamlit WebUI实战教程:添加Markdown渲染与代码高亮 Nanbeige 4.1-3B Streamlit WebUI实战教程添加Markdown渲染与代码高亮1. 引言如果你已经体验过那个极简清爽的Nanbeige 4.1-3B Streamlit WebUI可能会发现一个美中不足的地方当AI模型输出包含代码块或Markdown格式的内容时界面只是把它们当作普通文本显示。代码没有高亮Markdown也没有渲染这让技术对话的体验打了折扣。想象一下这样的场景你问模型“Python里怎么用列表推导式”它回答了一段包含代码示例的完整解释但在你的聊天界面上代码和普通文字混在一起完全看不出结构。或者模型输出了一个带有序号列表的操作步骤结果显示出来就是一堆带数字的普通文本毫无层次感。这就是我们今天要解决的问题。我将带你一步步为这个已经很好看的WebUI添加Markdown渲染和代码高亮功能让技术对话的体验再上一个台阶。整个过程不需要你懂前端框架也不需要复杂的配置只需要在现有的app.py基础上添加一些代码和CSS样式。学完这篇教程你的WebUI将能够自动识别并高亮显示代码块支持Python、JavaScript、Java等多种语言正确渲染Markdown格式包括标题、列表、链接、加粗、斜体等保持原有的极简风格和流畅体验新功能无缝集成代码量很少改动点集中容易理解和维护无论你是想自己用还是想分享给朋友这个增强版的WebUI都会让技术交流变得更加直观和高效。让我们开始吧。2. 理解现有架构在开始添加新功能之前我们先花几分钟了解一下现有的WebUI是怎么工作的。这样你就能明白我们要在哪里做改动为什么要这样改。2.1 当前的消息显示机制打开你的app.py文件找到显示聊天消息的部分。核心逻辑大概长这样# 这是简化的示例实际代码可能略有不同 for message in st.session_state.messages: with st.chat_message(message[role]): st.markdown(message[content])这里的关键点是st.markdown()函数。Streamlit原生支持Markdown渲染但在这个WebUI中我们只是把消息内容当作纯文本传给st.markdown()。实际上st.markdown()是能够渲染Markdown语法的只是我们之前没有利用这个特性。2.2 CSS样式的工作方式这个WebUI的视觉魅力很大程度上来自精心设计的CSS。通过st.markdown()注入CSS样式然后利用:has()伪类选择器实现聊天气泡的左右对齐。比如st.markdown( style /* 这里有一大堆CSS样式定义 */ /style , unsafe_allow_htmlTrue)CSS通过类名class来匹配HTML元素并应用样式。当前的消息气泡大概是这样结构的div classchat-message div classmessage-content !-- 这里是消息文本 -- /div /div2.3 流式输出的处理WebUI使用了TextIteratorStreamer来实现打字机效果的流式输出。这意味着消息是一个字符一个字符显示出来的而不是一次性显示完整内容。这对我们添加Markdown渲染提出了一个挑战如果一边输出一边尝试渲染不完整的Markdown语法可能会出现渲染错误。举个例子如果模型正在输出一个代码块python print(Hello)如果在前三个反引号出现时就尝试渲染系统会认为这是一个不完整的代码块标记可能导致渲染失败。 理解了这些基础我们就可以开始动手了。我们的目标是在不破坏现有美观界面和流畅体验的前提下让Markdown和代码高亮功能正常工作。 ## 3. 添加Markdown渲染功能 让WebUI支持Markdown渲染其实比想象中简单因为Streamlit已经内置了这个能力。我们只需要做两件事确保消息内容被正确识别为Markdown然后处理一些边界情况。 ### 3.1 修改消息显示逻辑 找到显示消息的代码部分我们只需要做一个小小的改动。原来的代码可能是这样的 python for message in st.session_state.messages: with st.chat_message(message[role]): st.markdown(message[content])这个代码本身没有问题st.markdown()确实会尝试渲染Markdown。但问题在于当消息内容中包含一些特殊字符时可能会被误解。我们需要确保内容被正确传递。更稳妥的做法是明确指定我们允许HTML因为Markdown中可以包含HTML标签for message in st.session_state.messages: with st.chat_message(message[role]): st.markdown(message[content], unsafe_allow_htmlTrue)添加unsafe_allow_htmlTrue参数后Streamlit会正确渲染消息中的Markdown语法包括一些基本的HTML标签。3.2 处理流式输出的Markdown流式输出是这里的主要挑战。当消息一个字一个字显示时如果中间出现了不完整的Markdown语法可能会让渲染引擎困惑。解决方案是在流式输出过程中先不渲染Markdown等消息完整后再重新渲染。我们可以这样做在流式输出时先用一个临时变量存储原始文本显示时暂时不渲染Markdown或者只做简单处理当流式输出完成后用完整的消息替换临时显示并进行完整的Markdown渲染在实际代码中这可能需要调整流式输出的回调函数。不过对于大多数情况有一个更简单的方案让模型在思考时输出完整的Markdown块而不是零碎的片段。3.3 测试Markdown渲染改完代码后让我们测试一下各种Markdown元素是否能正确显示标题测试# 一级标题 ## 二级标题 ### 三级标题列表测试- 无序列表项1 - 无序列表项2 - 子列表项 1. 有序列表项1 2. 有序列表项2格式测试**粗体文字** *斜体文字* ~~删除线~~链接和图片[这是一个链接](https://example.com) ![图片描述](图片地址)引用块 这是一段引用 可以有多行如果这些都能正确渲染那么基本的Markdown功能就已经实现了。不过我们可能还需要调整一下CSS样式让渲染后的元素符合WebUI的整体视觉风格。4. 实现代码高亮代码高亮是技术对话中特别有用的功能。当AI解释编程概念或提供代码示例时有语法高亮的代码块不仅看起来更专业也更容易阅读和理解。4.1 选择代码高亮方案我们有几种方案可以选择使用Streamlit内置支持Streamlit的st.markdown()支持GitHub风格的代码块但高亮功能有限使用第三方库比如pygments功能强大支持多种语言使用前端库比如highlight.js或prism.js通过JavaScript在浏览器端实现高亮考虑到我们的WebUI已经是纯Streamlit实现我不想引入复杂的前端框架。而且我们想要保持极简的风格所以我会选择方案1但进行一些增强。实际上Streamlit使用的是markdown库来渲染Markdown而markdown库可以通过扩展支持代码高亮。但更简单的方法是我们直接告诉Streamlit我们想要代码高亮它会处理剩下的。4.2 修改CSS支持代码高亮即使Streamlit渲染了代码块我们可能还需要调整CSS让它们看起来更美观。在现有的CSS中添加以下样式st.markdown( style /* 代码块的通用样式 */ pre { background-color: #f6f8fa; border-radius: 6px; padding: 16px; overflow: auto; font-size: 14px; line-height: 1.45; margin: 10px 0; border: 1px solid #e1e4e8; } /* 内联代码的样式 */ code { background-color: rgba(175, 184, 193, 0.2); border-radius: 3px; padding: 2px 4px; font-size: 90%; font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; } /* 代码块中的代码 */ pre code { background-color: transparent; padding: 0; border-radius: 0; } /* 深色模式下的代码块样式 */ media (prefers-color-scheme: dark) { pre { background-color: #2d2d2d; border-color: #404040; } code { background-color: rgba(110, 118, 129, 0.4); } } /style , unsafe_allow_htmlTrue)这些CSS样式会让代码块有浅灰色背景、圆角边框和适当的内边距内联代码也会有轻微的背景色让它们从普通文本中突出出来。4.3 测试代码高亮现在让我们测试一下代码高亮是否工作。你可以让AI模型输出一些包含代码的回复或者手动在测试时输入一些代码块。测试不同语言的代码块python def hello_world(): print(Hello, World!) return True javascript function greet(name) { console.log(Hello, ${name}!); return Greeted ${name}; } java public class Main { public static void main(String[] args) { System.out.println(Hello, World!); } } 你应该能看到不同语言的代码块都有不同的语法高亮。Python的关键字如def、return会是不同的颜色JavaScript的模板字符串也会有特殊显示。如果某些语言的高亮不明显可能是因为Streamlit的Markdown渲染器对那种语言的支持有限。不过对于常见的编程语言如Python、JavaScript、Java、C、HTML、CSS等应该都有不错的高亮效果。5. 处理边界情况和优化功能基本实现后我们需要处理一些边界情况确保在各种场景下都能正常工作。5.1 处理混合内容有时候AI的回复可能混合了普通文本、Markdown格式和代码块。比如要解决这个问题你可以使用Python的列表推导式 python squares [x**2 for x in range(10)]注意这种方法的时间复杂度是O(n)。主要步骤创建一个范围对每个元素进行平方收集结果我们的渲染系统需要能正确处理这种混合内容。幸运的是Streamlit的st.markdown()函数本身就能处理这种情况它会正确识别代码块边界不会把代码块内的Markdown符号当作格式标记。 ### 5.2 防止代码注入安全风险 当我们允许渲染HTML和Markdown时需要考虑到安全风险。恶意用户可能会尝试注入脚本或其他有害内容。 Streamlit的st.markdown()函数在默认情况下是安全的它会过滤掉潜在的恶意脚本。当我们设置unsafe_allow_htmlTrue时需要确信我们的应用场景不会受到用户输入的攻击。 在这个WebUI中消息主要来自两个方面 1. 用户输入用户在前端输入的内容 2. AI回复模型生成的内容 对于AI生成的内容风险相对较低因为模型通常不会生成恶意代码除非特意引导。但为了安全起见我们可以考虑对输出进行基本的过滤或者使用更安全的Markdown渲染选项。 ### 5.3 优化长代码块的显示 当代码块特别长时可能会出现滚动或布局问题。我们可以添加一些CSS来优化 css /* 确保长代码块不会破坏布局 */ pre { max-height: 400px; /* 设置最大高度 */ overflow-y: auto; /* 垂直滚动 */ } /* 代码行号可选增强 */ pre code { counter-reset: line; } pre code .line { counter-increment: line; display: block; } pre code .line::before { content: counter(line); display: inline-block; width: 2em; margin-right: 1em; text-align: right; color: #6a737d; }行号功能需要更复杂的处理因为我们需要在渲染前给代码行添加特定的HTML标签。如果你觉得有必要可以进一步研究但对于大多数情况基本的代码高亮已经足够了。5.4 保持性能添加Markdown渲染和代码高亮不应该显著影响WebUI的性能。Streamlit的Markdown渲染是在服务器端完成的然后发送HTML到浏览器。这对于大多数情况来说性能足够好。但是如果聊天记录非常长比如几百条消息每次重新渲染所有消息的Markdown可能会有点慢。这时我们可以考虑只渲染最新消息的Markdown历史消息缓存渲染结果使用虚拟滚动只渲染可视区域内的消息对于大多数使用场景简单的实现已经足够不需要过度优化。6. 完整代码示例为了让所有改动更清晰我在这里提供一个修改后的核心代码示例。请注意这只是一个示例你需要根据你的实际代码结构进行调整。import streamlit as st import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer from threading import Thread # 页面配置 st.set_page_config( page_titleNanbeige 4.1-3B Chat, page_icon, layoutwide ) # 自定义CSS - 添加了代码高亮样式 st.markdown( style /* 原有的聊天界面样式 */ /* ... 你原有的CSS样式 ... */ /* 新增的代码高亮样式 */ pre { background-color: #f6f8fa; border-radius: 6px; padding: 16px; overflow: auto; font-size: 14px; line-height: 1.45; margin: 10px 0; border: 1px solid #e1e4e8; font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; } code { background-color: rgba(175, 184, 193, 0.2); border-radius: 3px; padding: 2px 4px; font-size: 90%; font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; } pre code { background-color: transparent; padding: 0; border-radius: 0; font-size: inherit; } /* 深色模式适配 */ media (prefers-color-scheme: dark) { pre { background-color: #2d2d2d; border-color: #404040; color: #f8f8f2; } code { background-color: rgba(110, 118, 129, 0.4); color: #f8f8f2; } } /* 长代码块处理 */ pre { max-height: 400px; overflow-y: auto; } /style , unsafe_allow_htmlTrue) # 初始化session state if messages not in st.session_state: st.session_state.messages [] # 模型加载简化示例 st.cache_resource def load_model(): MODEL_PATH /path/to/your/model tokenizer AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( MODEL_PATH, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) return model, tokenizer model, tokenizer load_model() # 标题和清空按钮 col1, col2 st.columns([0.8, 0.2]) with col1: st.title( Nanbeige 4.1-3B Chat) with col2: if st.button(清空记录, use_container_widthTrue): st.session_state.messages [] st.rerun() # 显示聊天记录 - 修改了这里以支持Markdown渲染 for message in st.session_state.messages: with st.chat_message(message[role]): # 使用unsafe_allow_htmlTrue允许Markdown渲染 st.markdown(message[content], unsafe_allow_htmlTrue) # 用户输入 if prompt : st.chat_input(输入你的消息...): # 添加用户消息 st.session_state.messages.append({role: user, content: prompt}) with st.chat_message(user): st.markdown(prompt, unsafe_allow_htmlTrue) # 生成AI回复 with st.chat_message(assistant): message_placeholder st.empty() full_response # 准备输入 messages_for_model st.session_state.messages.copy() # 这里可能需要根据模型要求格式化消息 # 流式生成 inputs tokenizer.apply_chat_template( messages_for_model, tokenizeTrue, add_generation_promptTrue, return_tensorspt ).to(model.device) streamer TextIteratorStreamer(tokenizer, skip_promptTrue) generation_kwargs dict( inputsinputs, streamerstreamer, max_new_tokens2048, do_sampleTrue, temperature0.7, top_p0.9 ) thread Thread(targetmodel.generate, kwargsgeneration_kwargs) thread.start() # 流式显示 for token in streamer: full_response token # 流式输出时暂时不渲染Markdown避免不完整语法导致的渲染问题 message_placeholder.markdown(full_response ▌) # 流式输出完成后用完整的Markdown渲染 message_placeholder.markdown(full_response, unsafe_allow_htmlTrue) # 添加AI回复到历史 st.session_state.messages.append({role: assistant, content: full_response})这个示例展示了主要的修改点在CSS中添加了代码高亮样式在显示消息时使用unsafe_allow_htmlTrue参数在流式输出时先不渲染Markdown避免不完整语法问题等输出完成后再完整渲染7. 总结通过这篇教程我们成功地为Nanbeige 4.1-3B Streamlit WebUI添加了Markdown渲染和代码高亮功能。让我们回顾一下主要步骤和收获实现的核心改动CSS样式增强添加了代码块和内联代码的样式定义让它们有合适的背景色、边框和间距Markdown渲染启用在st.markdown()调用中添加unsafe_allow_htmlTrue参数启用完整的Markdown渲染能力流式输出处理调整了流式显示逻辑避免在不完整的Markdown语法上尝试渲染达到的效果AI回复中的代码块现在会有语法高亮不同语言的关键字会有不同颜色Markdown格式如标题、列表、加粗、斜体等都能正确渲染混合内容文本代码Markdown也能正确处理保持了原有的极简界面风格和流畅的交互体验实际应用价值 现在当你与Nanbeige模型讨论技术问题时代码示例会以高亮形式显示操作步骤会以清晰的列表呈现重要概念可以用加粗强调。这不仅提升了阅读体验也使得技术交流更加高效。这个增强版的WebUI特别适合学习编程时与AI助手对话讨论技术方案和代码实现编写技术文档和教程任何需要清晰格式展示的对话场景进一步优化的想法 如果你想让这个WebUI更加强大还可以考虑添加代码复制按钮让用户一键复制代码块内容支持更多的代码语言高亮添加深色/浅色主题切换让代码高亮在不同主题下都有好效果实现代码行号显示方便讨论具体的代码行最重要的是这个实现保持了WebUI的极简哲学没有引入复杂的前端框架没有增加繁琐的配置步骤只是巧妙地利用了Streamlit已有的能力通过一些CSS和参数调整就实现了强大的功能。现在你的Nanbeige WebUI不仅外观精美交互流畅还能完美展示技术内容了。快去试试和它讨论一些编程问题看看那些高亮的代码块和格式清晰的回答吧获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。
返回列表