Nanbeige 4.1-3B WebUI从零开始:手机短信风UI+流式输出完整指南
Nanbeige 4.1-3B WebUI从零开始手机短信风UI流式输出完整指南你是不是已经厌倦了那些千篇一律、布局死板的大模型Web界面左边一个侧边栏右边一个聊天框头像方方正正气泡挤在一起看着就让人提不起聊天的兴趣。今天我要带你从零开始搭建一个完全不一样的Nanbeige 4.1-3B对话界面。这个界面看起来就像是直接从你手机里跑出来的短信应用或者是某个二次元游戏的聊天窗口界面清爽动画流畅体验丝滑。想象一下你打开浏览器看到的不是传统的工具界面而是一个充满设计感的对话空间。你的消息在右侧AI的回复在左侧气泡圆润背景优雅文字像打字机一样一个个跳出来。更重要的是如果AI在“思考”进行推理这个过程会被优雅地折叠起来不会干扰你的阅读。这就是我们要做的。整个过程只需要一个Python文件不需要React不需要Vue不需要复杂的前端工程。跟着我一步步来你就能拥有一个既好看又好用的本地大模型聊天界面。1. 为什么你需要这个WebUI在开始动手之前我们先看看这个项目能解决什么问题以及它到底有什么特别之处。1.1 传统界面的痛点如果你用过一些开源的大模型Web界面可能会遇到这些问题界面老旧很多界面还停留在“工具软件”的审美缺乏现代感。布局死板Streamlit原生的组件排列方式有限很难做出灵活的聊天布局。体验割裂输入和输出分离没有连贯的对话流感觉。思考过程暴露对于有推理能力的模型中间思考过程直接显示打断了对话的流畅性。缺乏动效输出是整段出现没有打字机效果缺乏交互感。1.2 这个项目的核心亮点这个项目就是为了解决上述痛点而生的手机短信风格模仿现代聊天应用的UI左右气泡对齐视觉上更自然。纯Streamlit实现没有引入额外的前端框架维护简单部署方便。智能折叠思考过程自动识别模型的推理内容并优雅地收纳起来。真正的流式输出文字像打字机一样逐个出现响应迅速无闪烁。单文件部署所有代码都在一个app.py文件里复制就能用。最重要的是这一切都是通过“CSS魔法”实现的——我们用纯CSS重新定义了Streamlit组件的样式让它们看起来完全不像Streamlit。2. 环境准备与快速部署让我们开始动手。整个过程非常快大概10分钟就能看到效果。2.1 安装必要的软件包首先确保你的Python环境是3.10或更高版本。然后打开终端安装所需的库pip install streamlit torch transformers accelerate这几个包的作用分别是streamlit构建Web界面的框架torchPyTorch深度学习框架transformersHugging Face的模型加载和推理库accelerate优化模型加载和推理速度2.2 下载模型权重这个界面是为Nanbeige 4.1-3B模型设计的你需要先准备好模型文件。访问Hugging Face的Nanbeige模型页面下载4.1-3B版本的模型权重到你的本地。如果你不知道怎么做可以直接用这个命令确保你有足够的磁盘空间和网络环境# 示例命令实际路径可能需要调整 git lfs install git clone https://huggingface.co/Nanbeige/Nanbeige4___1-3B /path/to/your/model把模型下载到一个你记得住的路径比如/home/username/models/nanbeige-4.1-3B/。2.3 获取项目代码接下来我们需要创建主程序文件。新建一个文件命名为app.py然后把下面的完整代码复制进去。import streamlit as st import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer from threading import Thread import time # 页面配置 st.set_page_config( page_titleNanbeige 4.1-3B Chat, page_icon, layoutwide, initial_sidebar_statecollapsed ) # 自定义CSS - 这是实现手机短信风格的关键 st.markdown( style /* 全局样式重置 */ .stApp { background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%) !important; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; } /* 隐藏Streamlit默认的边栏和多余元素 */ #MainMenu {visibility: hidden;} footer {visibility: hidden;} header {visibility: hidden;} div[data-testidstToolbar] {display: none;} /* 聊天容器 - 模仿手机短信界面 */ .chat-container { max-width: 800px; margin: 0 auto; padding: 20px; height: calc(100vh - 120px); overflow-y: auto; background: radial-gradient(circle at 25px 25px, rgba(100, 150, 255, 0.1) 2px, transparent 2px), radial-gradient(circle at 75px 75px, rgba(100, 150, 255, 0.05) 2px, transparent 2px); background-size: 100px 100px; background-attachment: fixed; } /* 消息气泡基础样式 */ .message-bubble { max-width: 70%; margin: 12px 0; padding: 14px 18px; border-radius: 20px; position: relative; word-wrap: break-word; line-height: 1.5; animation: fadeIn 0.3s ease-out; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } /* 用户消息 - 右侧蓝色气泡 */ .user-message { background: linear-gradient(135deg, #4a90e2, #357ae8); color: white; margin-left: auto; border-bottom-right-radius: 4px; } /* AI消息 - 左侧白色气泡 */ .ai-message { background: white; color: #333; margin-right: auto; border-bottom-left-radius: 4px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); } /* 打字机效果 */ .typing { display: inline-block; overflow: hidden; border-right: 2px solid #4a90e2; white-space: nowrap; animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite; } /* 思考过程折叠面板 */ .thinking-container { margin: 8px 0; border-left: 3px solid #ff6b6b; padding-left: 12px; } .thinking-toggle { color: #666; font-size: 0.9em; cursor: pointer; user-select: none; display: flex; align-items: center; gap: 6px; } .thinking-content { margin-top: 8px; padding: 10px; background: rgba(255, 107, 107, 0.05); border-radius: 8px; font-family: Monaco, Menlo, monospace; font-size: 0.85em; color: #666; line-height: 1.4; max-height: 200px; overflow-y: auto; } /* 输入框容器 */ .input-container { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); width: 90%; max-width: 800px; background: white; border-radius: 25px; padding: 8px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); z-index: 100; } /* 动画效果 */ keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } keyframes typing { from { width: 0 } to { width: 100% } } keyframes blink-caret { from, to { border-color: transparent } 50% { border-color: #4a90e2; } } /* 响应式调整 */ media (max-width: 768px) { .chat-container { padding: 10px; } .message-bubble { max-width: 85%; } .input-container { width: 95%; } } /style , unsafe_allow_htmlTrue) # 模型路径配置 MODEL_PATH /path/to/your/nanbeige/model # 修改为你的实际路径 # 初始化session state if messages not in st.session_state: st.session_state.messages [] if model_loaded not in st.session_state: st.session_state.model_loaded False if thinking_visible not in st.session_state: st.session_state.thinking_visible {} # 加载模型 st.cache_resource def load_model(): 加载模型和tokenizer try: st.info(正在加载模型第一次加载可能需要几分钟...) # 加载tokenizer tokenizer AutoTokenizer.from_pretrained( MODEL_PATH, trust_remote_codeTrue ) # 加载模型 model AutoModelForCausalLM.from_pretrained( MODEL_PATH, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue ) # 设置为评估模式 model.eval() st.success(模型加载完成) return model, tokenizer except Exception as e: st.error(f模型加载失败: {str(e)}) return None, None # 生成回复 def generate_response(prompt, model, tokenizer): 生成AI回复支持流式输出 # 构建对话历史 messages st.session_state.messages.copy() messages.append({role: user, content: prompt}) # 准备模型输入 text tokenizer.apply_chat_template( messages, tokenizeFalse, add_generation_promptTrue ) inputs tokenizer(text, return_tensorspt).to(model.device) # 创建流式输出器 streamer TextIteratorStreamer( tokenizer, skip_promptTrue, skip_special_tokensTrue ) # 在单独线程中生成 generation_kwargs dict( inputs, streamerstreamer, max_new_tokens1024, temperature0.7, top_p0.9, do_sampleTrue, pad_token_idtokenizer.eos_token_id ) thread Thread(targetmodel.generate, kwargsgeneration_kwargs) thread.start() # 收集生成的文本 generated_text thinking_content in_thinking False for new_text in streamer: generated_text new_text # 处理思考过程CoT if /think in new_text: in_thinking False # 移除思考标记 new_text new_text.replace(/think, ) if in_thinking: thinking_content new_text new_text # 不在主界面显示思考过程 if think in new_text: in_thinking True thinking_start new_text.find(think) thinking_content new_text[thinking_start 5:] # 5是think的长度 new_text new_text[:thinking_start] if new_text: yield new_text, thinking_content # 最后yield一次确保完整内容 yield generated_text, thinking_content # 主界面 def main(): # 标题区域 col1, col2 st.columns([3, 1]) with col1: st.title( Nanbeige 4.1-3B Chat) st.caption(手机短信风格的AI对话界面 | 支持流式输出与思考过程折叠) with col2: if st.button(清空记录, typesecondary): st.session_state.messages [] st.session_state.thinking_visible {} st.rerun() # 聊天记录显示区域 chat_container st.container() with chat_container: st.markdown(div classchat-container, unsafe_allow_htmlTrue) for i, message in enumerate(st.session_state.messages): role message[role] content message[content] # 用户消息 if role user: st.markdown(f div classmessage-bubble user-message div stylefont-weight: 500;你/div div{content}/div /div , unsafe_allow_htmlTrue) # AI消息 elif role assistant: # 检查是否有思考过程 has_thinking str(i) in st.session_state.thinking_visible thinking_content message.get(thinking, ) st.markdown(f div classmessage-bubble ai-message div stylefont-weight: 500; color: #666; margin-bottom: 4px;Nanbeige/div div{content}/div /div , unsafe_allow_htmlTrue) # 显示思考过程如果存在且用户选择展开 if thinking_content and has_thinking and st.session_state.thinking_visible[str(i)]: st.markdown(f div classthinking-container div classthinking-content {thinking_content} /div /div , unsafe_allow_htmlTrue) # 思考过程折叠按钮 if thinking_content: col_a, col_b st.columns([1, 6]) with col_a: toggle_key fthinking_toggle_{i} current_state st.session_state.thinking_visible.get(str(i), False) if st.button( ️ 显示思考过程 if not current_state else ️ 隐藏思考过程, keytoggle_key, typesecondary ): st.session_state.thinking_visible[str(i)] not current_state st.rerun() st.markdown(/div, unsafe_allow_htmlTrue) # 输入区域 st.markdown(div classinput-container, unsafe_allow_htmlTrue) with st.form(keychat_form, clear_on_submitTrue): col1, col2 st.columns([5, 1]) with col1: user_input st.text_input( 输入你的消息..., keyuser_input, label_visibilitycollapsed, placeholder和Nanbeige聊点什么... ) with col2: submit_button st.form_submit_button( 发送, typeprimary, use_container_widthTrue ) st.markdown(/div, unsafe_allow_htmlTrue) # 处理用户输入 if submit_button and user_input: if not st.session_state.model_loaded: model, tokenizer load_model() if model and tokenizer: st.session_state.model model st.session_state.tokenizer tokenizer st.session_state.model_loaded True else: st.error(模型加载失败请检查路径和配置) return # 添加用户消息到历史 st.session_state.messages.append({role: user, content: user_input}) # 创建AI消息占位符 ai_message_index len(st.session_state.messages) st.session_state.messages.append({role: assistant, content: }) # 生成回复 model st.session_state.model tokenizer st.session_state.tokenizer # 创建消息容器用于流式输出 message_placeholder st.empty() full_response thinking_content # 流式生成 for chunk, thinking in generate_response(user_input, model, tokenizer): full_response chunk if thinking: thinking_content thinking # 更新显示 st.session_state.messages[-1][content] full_response if thinking_content: st.session_state.messages[-1][thinking] thinking_content # 重新渲染聊天记录 st.rerun() # 确保思考过程默认折叠 st.session_state.thinking_visible[str(ai_message_index)] False # 运行主程序 if __name__ __main__: main()2.4 修改模型路径现在打开你刚刚创建的app.py文件找到这一行MODEL_PATH /path/to/your/nanbeige/model # 修改为你的实际路径把/path/to/your/nanbeige/model替换成你实际下载模型权重的路径。比如MODEL_PATH /home/yourname/models/Nanbeige4___1-3B/ # 你的实际路径2.5 启动Web服务保存文件后在终端中进入app.py所在的目录运行streamlit run app.py你会看到类似这样的输出You can now view your Streamlit app in your browser. Local URL: http://localhost:8501 Network URL: http://192.168.1.x:8501打开浏览器访问http://localhost:8501就能看到你的手机短信风格聊天界面了3. 界面功能详解现在界面已经跑起来了让我们详细看看它的各个部分是怎么工作的。3.1 视觉设计如何实现手机短信风格这个界面的核心魔法都在CSS里。我们完全重写了Streamlit的默认样式背景设计使用了浅灰蓝色的渐变背景营造柔和的视觉感受添加了极简的圆点矩阵网格就像手机壁纸一样去掉了所有Streamlit的默认边框和阴影聊天气泡用户消息右侧对齐天蓝色渐变背景白色文字AI消息左侧对齐纯白背景轻微阴影增加层次感圆角设计20px的大圆角模仿真实聊天气泡间距合理消息之间有舒适的12px间距输入区域固定在底部中央像手机键盘一样药丸形状的输入框带阴影悬浮效果发送按钮集成在输入框右侧响应式布局在电脑上聊天区域最大宽度800px居中显示在手机上自动调整气泡宽度和间距保持良好体验3.2 流式输出打字机效果实现流式输出是这个项目的另一个亮点。传统的做法是等模型完全生成后再显示而我们是边生成边显示技术实现使用TextIteratorStreamer从模型获取实时的token流在单独的线程中运行模型生成避免阻塞界面每收到一个新的token就立即更新显示通过CSS动画实现打字机光标效果用户体验文字逐个出现像有人在打字响应迅速没有等待的焦虑感生成过程中界面不会卡顿或闪烁3.3 思考过程折叠智能处理模型推理Nanbeige 4.1-3B支持思维链推理会在输出中使用think.../think标签包裹思考过程。我们的界面能智能识别并处理这些内容自动识别实时监控模型输出检测think和/think标签将思考过程内容提取出来单独保存主界面只显示最终的答案部分优雅折叠思考过程默认是折叠状态不干扰阅读点击“显示思考过程”按钮可以展开查看思考内容显示在灰色背景的面板中与主对话区分支持多个消息的思考过程独立展开/折叠3.4 对话历史管理界面还提供了一些实用的对话管理功能历史记录所有对话自动保存在session state中刷新页面不会丢失对话历史支持长对话的多轮交互一键清空右上角的“清空记录”按钮可以快速重置对话同时清除所有思考过程的展开状态界面立即刷新回到初始状态4. 实际使用体验让我们实际用一下这个界面看看效果如何。4.1 开始第一次对话打开界面后你会看到一个清爽的聊天窗口。在底部的输入框里试着问一个问题请用Python写一个快速排序算法点击发送你会看到你的消息以蓝色气泡出现在右侧AI的消息开始以打字机效果在左侧出现如果模型有思考过程你会看到一个“显示思考过程”的按钮整个过程非常流畅没有任何卡顿。4.2 查看思考过程对于复杂的问题Nanbeige可能会进行推理。比如你问如果我有3个苹果吃了1个又买了5个然后给了朋友2个最后还剩几个点击AI回复下方的“显示思考过程”按钮你会看到模型是如何一步步推理的用户有3个苹果。 吃掉1个后3 - 1 2个苹果。 买了5个后2 5 7个苹果。 给朋友2个后7 - 2 5个苹果。 所以最后剩下5个苹果。这个功能对于学习模型的推理过程特别有用。4.3 多轮对话测试这个界面完美支持多轮对话。你可以连续问多个问题对话历史会完整保留你什么是Python的列表推导式 AI[解释列表推导式] 你那字典推导式呢 AI[解释字典推导式并引用之前的对话]模型能记住上下文给出连贯的回答。5. 自定义与扩展如果你对这个界面有更多的想法可以很容易地进行修改和扩展。5.1 修改界面样式所有的样式都在CSS部分你可以随意调整修改颜色主题/* 用户气泡颜色 */ .user-message { background: linear-gradient(135deg, #FF6B6B, #FF8E8E); /* 改为粉色 */ color: white; } /* AI气泡颜色 */ .ai-message { background: #F0F8FF; /* 改为淡蓝色 */ color: #333; } /* 背景 */ .stApp { background: linear-gradient(135deg, #FFF5E1 0%, #FFE4E1 100%) !important; }调整布局/* 气泡宽度 */ .message-bubble { max-width: 80%; /* 调宽气泡 */ } /* 输入框位置 */ .input-container { bottom: 40px; /* 调高输入框 */ max-width: 700px; /* 调窄输入框 */ }5.2 适配其他模型这个界面不仅适用于Nanbeige也可以适配其他支持类似格式的大模型修改模型加载# 例如适配Qwen模型 MODEL_PATH /path/to/your/qwen/model tokenizer AutoTokenizer.from_pretrained( MODEL_PATH, trust_remote_codeTrue ) model AutoModelForCausalLM.from_pretrained( MODEL_PATH, torch_dtypetorch.float16, device_mapauto, trust_remote_codeTrue )调整对话模板 不同的模型可能需要不同的对话模板格式你可以在generate_response函数中调整apply_chat_template的部分。5.3 添加新功能如果你想要更多功能可以在此基础上扩展添加历史导出# 在清空按钮旁边添加导出按钮 if st.button(导出对话, typesecondary): # 将对话历史导出为JSON或文本 import json history json.dumps(st.session_state.messages, ensure_asciiFalse, indent2) st.download_button(下载对话记录, history, conversation.json)添加参数调整# 在侧边栏添加生成参数控制 with st.sidebar: st.slider(温度, 0.1, 1.0, 0.7, keytemperature) st.slider(最大生成长度, 100, 2048, 1024, keymax_tokens)6. 常见问题与解决在使用的过程中你可能会遇到一些问题这里是一些常见的解决方案。6.1 模型加载失败问题启动时提示模型加载失败。可能原因和解决路径错误检查MODEL_PATH是否正确指向模型文件夹内存不足3B模型需要约6-8GB显存确保你的显卡足够依赖版本尝试更新torch和transformers到最新版本6.2 界面显示异常问题界面样式混乱不像截图那样美观。解决清除浏览器缓存强制刷新页面CtrlF5检查CSS代码是否完整复制特别是style标签确保Streamlit版本在1.28以上6.3 流式输出卡顿问题文字输出不流畅有卡顿感。优化建议确保模型在GPU上运行检查device_mapauto是否生效降低max_new_tokens参数减少单次生成长度如果使用CPU考虑使用量化版本模型6.4 思考过程识别错误问题模型的思考过程没有被正确折叠。调整方法 在generate_response函数中调整思考过程的识别逻辑# 如果模型使用不同的思考标记 if think in new_text: # 修改为你的模型使用的标记 in_thinking True7. 总结通过这个教程我们完成了一个既美观又实用的Nanbeige 4.1-3B Web界面。让我们回顾一下关键点界面设计方面我们彻底改造了Streamlit的默认样式实现了手机短信风格的聊天界面。关键技巧包括使用CSS渐变背景、圆角气泡、浮动输入框以及通过:has()伪类实现动态布局。功能实现方面我们实现了真正的流式输出让文字像打字机一样逐个出现。同时智能识别并折叠模型的思考过程保持界面的清爽。整个应用只有一个Python文件部署和维护都非常简单。使用体验方面这个界面不仅好看而且好用。响应迅速交互自然支持多轮对话还能查看模型的推理过程。无论是日常使用还是学习研究都是一个很好的工具。这个项目的价值在于它证明了一点即使只用Streamlit我们也能做出媲美专业前端框架的界面效果。所有的“魔法”都来自于对CSS的深入理解和巧妙应用。现在你已经拥有了一个完全属于自己的、风格独特的AI对话界面。你可以随意修改它的样式添加新功能或者适配其他大模型。最重要的是你知道了如何用简单的技术做出不简单的效果。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。