Nanbeige 4.1-3B清爽WebUI教程对话历史本地持久化存储实现方案1. 引言1.1 从清爽到实用WebUI的进化需求如果你已经体验过Nanbeige 4.1-3B那个清爽的Streamlit WebUI一定会被它极简的二次元风格和流畅的对话体验所吸引。左右对齐的聊天气泡、智能折叠的思考过程、丝滑的流式输出——这一切都让对话变得赏心悦目。但用久了你会发现一个问题每次刷新页面刚才的对话记录就消失了。这就像用手机聊天聊完就清空想回顾之前的对话内容都找不到。对于需要长期使用、积累对话历史的场景来说这确实是个痛点。1.2 本地持久化存储的价值想象一下这些场景你正在用AI助手写一篇长文每次刷新都要重新开始你在调试一个复杂问题需要多次对话才能解决你想记录和AI的有趣对话作为灵感素材你需要保存重要的技术问答方便日后查阅如果没有对话历史保存功能这些场景都会变得很麻烦。今天我们就来解决这个问题——为Nanbeige 4.1-3B的清爽WebUI添加本地持久化存储功能。1.3 本教程能帮你实现什么通过本教程你将学会如何在不破坏原有UI风格的前提下为WebUI添加数据存储功能如何设计一个简单高效的对话历史存储方案如何实现对话记录的保存、加载和清空功能如何确保数据安全避免对话历史泄露最重要的是这一切都将在保持原有清爽界面的基础上完成不会增加任何复杂的操作步骤。2. 技术方案设计2.1 存储方案选择为WebUI添加持久化存储我们有几种选择方案一文件存储JSON格式优点简单直接无需额外依赖缺点并发访问可能有问题适合个人使用单用户场景方案二SQLite数据库优点结构化存储查询方便缺点需要数据库操作知识适合需要复杂查询的场景方案三Streamlit Session State 文件存储优点利用Streamlit原生特性缺点Session State在页面刷新后会重置适合临时存储考虑到我们的需求是个人使用、简单可靠我选择了方案一JSON文件存储。原因很简单对话历史本身就是结构化的数据用户消息、AI回复、时间戳JSON格式人类可读调试方便实现简单几行代码就能搞定性能足够对话历史不会太大2.2 数据结构设计一个对话记录需要包含哪些信息我们这样设计{ conversations: [ { id: unique_id_1, timestamp: 2024-01-15 10:30:25, role: user, # 或 assistant content: 你好请介绍一下你自己, thinking: 用户想了解我的基本信息... # 可选如果有思考过程的话 }, { id: unique_id_2, timestamp: 2024-01-15 10:30:30, role: assistant, content: 我是Nanbeige 4.1-3B模型..., thinking: 这是一个自我介绍的问题... } ], metadata: { model: Nanbeige-4.1-3B, created_at: 2024-01-15 10:30:00, last_updated: 2024-01-15 10:35:00, total_messages: 10 } }这样的设计有几个好处每条消息都有唯一ID方便后续管理时间戳记录对话发生的时间role字段区分用户和AI的发言thinking字段保存AI的思考过程如果有的话metadata记录对话的基本信息2.3 文件存储策略我们采用简单的文件存储策略每个对话会话保存为一个独立的JSON文件文件名包含时间戳方便查找默认保存到conversations/目录下提供对话列表功能可以加载历史对话文件命名示例conversations/对话_20240115_103025.json conversations/技术讨论_20240116_143012.json conversations/创意写作_20240117_090045.json3. 代码实现详解3.1 准备工作创建存储目录首先我们需要在项目根目录下创建一个存储对话历史的目录import os import json from datetime import datetime import uuid # 创建对话存储目录 CONVERSATIONS_DIR conversations os.makedirs(CONVERSATIONS_DIR, exist_okTrue)这段代码做了三件事导入必要的库os用于文件操作json用于数据序列化datetime用于时间处理uuid用于生成唯一ID定义存储目录名称创建目录如果不存在的话3.2 核心存储函数实现接下来我们实现几个核心的函数来处理对话历史的保存和加载def save_conversation(messages, conversation_nameNone): 保存对话历史到JSON文件 参数: messages: 对话消息列表格式为 [{role: user, content: ...}, ...] conversation_name: 对话名称如果不提供则自动生成 返回: 保存的文件路径 # 生成唯一ID和时间戳 conversation_id str(uuid.uuid4())[:8] # 取前8位足够唯一且简洁 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) # 如果没有提供对话名称使用默认格式 if not conversation_name: conversation_name f对话_{timestamp} # 构建文件名 filename f{conversation_name}_{conversation_id}.json filepath os.path.join(CONVERSATIONS_DIR, filename) # 构建数据结构 conversation_data { id: conversation_id, name: conversation_name, created_at: datetime.now().isoformat(), last_updated: datetime.now().isoformat(), total_messages: len(messages), messages: messages } # 保存到文件 with open(filepath, w, encodingutf-8) as f: json.dump(conversation_data, f, ensure_asciiFalse, indent2) print(f对话已保存: {filepath}) return filepath这个函数的主要功能为每个对话生成唯一ID和时间戳构建完整的对话数据结构以JSON格式保存到文件使用UTF-8编码确保中文正常显示使用indent2让JSON文件更易读3.3 对话加载函数保存了对话当然还需要能加载回来def load_conversation(filepath): 从JSON文件加载对话历史 参数: filepath: JSON文件路径 返回: 对话数据字典如果文件不存在或格式错误则返回None try: with open(filepath, r, encodingutf-8) as f: conversation_data json.load(f) # 验证数据格式 if messages not in conversation_data: print(f警告: {filepath} 格式不正确缺少messages字段) return None return conversation_data except FileNotFoundError: print(f错误: 文件不存在 {filepath}) return None except json.JSONDecodeError: print(f错误: {filepath} 不是有效的JSON格式) return None这个函数包含了错误处理文件不存在的处理JSON格式错误的处理数据格式验证3.4 获取对话列表为了方便用户选择要加载的对话我们需要一个函数来列出所有保存的对话def list_conversations(): 列出所有保存的对话 返回: 对话信息列表每个元素包含文件名、对话名称、创建时间、消息数量 conversations [] # 遍历conversations目录下的所有JSON文件 for filename in os.listdir(CONVERSATIONS_DIR): if filename.endswith(.json): filepath os.path.join(CONVERSATIONS_DIR, filename) try: with open(filepath, r, encodingutf-8) as f: data json.load(f) # 提取基本信息 conversation_info { filename: filename, filepath: filepath, name: data.get(name, 未命名对话), created_at: data.get(created_at, 未知时间), total_messages: data.get(total_messages, 0), last_updated: data.get(last_updated, 未知时间) } conversations.append(conversation_info) except (json.JSONDecodeError, KeyError): # 跳过格式错误的文件 continue # 按最后更新时间倒序排列最新的在前面 conversations.sort(keylambda x: x.get(last_updated, ), reverseTrue) return conversations3.5 删除对话功能有时候用户可能想清理一些旧的对话我们也需要提供删除功能def delete_conversation(filepath): 删除指定的对话文件 参数: filepath: 要删除的文件路径 返回: 成功返回True失败返回False try: if os.path.exists(filepath): os.remove(filepath) print(f已删除对话: {filepath}) return True else: print(f文件不存在: {filepath}) return False except Exception as e: print(f删除文件时出错: {e}) return False4. 集成到原有WebUI4.1 修改app.py文件现在我们需要把这些功能集成到原来的Nanbeige WebUI中。打开app.py文件在合适的位置添加我们的存储功能。首先在文件开头导入必要的库并添加存储相关的函数# 在原有导入语句后面添加 import os import json import uuid from datetime import datetime然后在模型初始化代码后面添加存储目录创建代码# 创建对话存储目录在模型加载代码后面添加 CONVERSATIONS_DIR conversations os.makedirs(CONVERSATIONS_DIR, exist_okTrue)4.2 修改Streamlit界面原来的WebUI界面很简洁我们需要在不破坏原有风格的前提下添加存储功能。我建议在右上角清空记录按钮旁边添加几个新按钮# 在原有的清空按钮代码附近添加 col1, col2, col3, col4 st.columns([1, 1, 1, 1]) with col1: if st.button( 保存对话, use_container_widthTrue): if st.session_state.messages: # 生成对话名称 default_name f对话_{datetime.now().strftime(%Y%m%d_%H%M)} conversation_name st.text_input(请输入对话名称:, valuedefault_name) if conversation_name: # 调用保存函数 filepath save_conversation(st.session_state.messages, conversation_name) st.success(f对话已保存到: {filepath}) else: st.warning(没有对话内容可以保存) with col2: if st.button( 加载历史, use_container_widthTrue): # 获取对话列表 conversations list_conversations() if conversations: # 创建选择框 conversation_options [f{c[name]} ({c[total_messages]}条消息) for c in conversations] selected_index st.selectbox(选择要加载的对话:, range(len(conversation_options)), format_funclambda i: conversation_options[i]) if st.button(确认加载): selected_conversation conversations[selected_index] conversation_data load_conversation(selected_conversation[filepath]) if conversation_data: # 清空当前对话 st.session_state.messages [] # 加载历史消息 for msg in conversation_data[messages]: st.session_state.messages.append(msg) st.success(f已加载对话: {selected_conversation[name]}) st.rerun() # 刷新界面显示加载的内容 else: st.info(还没有保存的对话) with col3: if st.button(️ 删除对话, use_container_widthTrue): conversations list_conversations() if conversations: conversation_options [f{c[name]} ({c[total_messages]}条消息) for c in conversations] selected_index st.selectbox(选择要删除的对话:, range(len(conversation_options)), format_funclambda i: conversation_options[i], keydelete_select) if st.button(确认删除, keyconfirm_delete): selected_conversation conversations[selected_index] if delete_conversation(selected_conversation[filepath]): st.success(f已删除: {selected_conversation[name]}) st.rerun() else: st.info(没有可删除的对话) with col4: # 原有的清空按钮 if st.button(️ 清空记录, use_container_widthTrue): st.session_state.messages [] st.rerun()4.3 修改消息处理逻辑原来的代码中消息是存储在st.session_state.messages中的。我们需要确保在保存和加载时消息格式是一致的。在原来的消息处理代码中当用户发送消息和AI回复时消息是这样添加的# 用户发送消息 st.session_state.messages.append({role: user, content: prompt}) # AI回复消息在流式输出完成后 st.session_state.messages.append({ role: assistant, content: full_response, thinking: thinking_content # 如果有思考过程的话 })为了支持思考过程的保存我们需要稍微修改一下消息结构。如果AI的回复中包含思考过程被think.../think包裹的内容我们把它提取出来单独保存def extract_thinking(content): 从AI回复中提取思考过程 import re thinking_pattern rthink(.*?)/think matches re.findall(thinking_pattern, content, re.DOTALL) if matches: # 移除思考过程标记只保留最终回复 clean_content re.sub(thinking_pattern, , content, flagsre.DOTALL).strip() thinking_content .join(matches) return clean_content, thinking_content else: return content, None # 在AI回复完成后 clean_response, thinking_content extract_thinking(full_response) message_to_save { role: assistant, content: clean_response } if thinking_content: message_to_save[thinking] thinking_content st.session_state.messages.append(message_to_save)4.4 自动保存功能除了手动保存我们还可以添加自动保存功能。比如在对话达到一定数量时自动保存或者在页面关闭前提示保存。# 在适当的位置添加自动保存检查 def check_auto_save(messages): 检查是否需要自动保存 # 每10条消息自动保存一次 if len(messages) 0 and len(messages) % 10 0: auto_save_name f自动保存_{datetime.now().strftime(%Y%m%d_%H%M%S)} save_conversation(messages, auto_save_name) return True return False # 在添加新消息后调用 if check_auto_save(st.session_state.messages): st.toast(对话已自动保存, icon)5. 使用教程与示例5.1 完整的使用流程现在让我们看看添加了持久化存储功能的WebUI如何使用启动WebUI和之前一样运行streamlit run app.py开始对话在输入框中输入问题和AI正常对话保存对话点击右上角的 保存对话按钮系统会提示你输入对话名称输入名称后点击确认对话就保存到本地了加载历史对话点击 加载历史按钮会显示所有保存的对话列表选择要加载的对话点击确认加载之前的对话内容就会显示在界面上删除对话点击️ 删除对话按钮选择要删除的对话确认删除即可自动保存每10条消息会自动保存一次防止意外丢失5.2 实际效果展示让我用一个实际例子展示存储功能的效果假设你正在用Nanbeige 4.1-3B学习Python编程你们进行了如下对话你请解释一下Python中的列表推导式 AI列表推导式是Python中创建列表的简洁方式... 你能举个例子吗 AI比如[x*2 for x in range(10)]会生成[0, 2, 4, ..., 18]... 你那字典推导式呢 AI字典推导式类似{x: x*2 for x in range(5)}生成{0:0, 1:2, 2:4, 3:6, 4:8}...对话结束后你点击保存对话输入名称Python推导式学习系统会生成一个JSON文件{ id: a1b2c3d4, name: Python推导式学习, created_at: 2024-01-15T14:30:25, last_updated: 2024-01-15T14:35:10, total_messages: 6, messages: [ { role: user, content: 请解释一下Python中的列表推导式, timestamp: 2024-01-15T14:30:25 }, { role: assistant, content: 列表推导式是Python中创建列表的简洁方式..., timestamp: 2024-01-15T14:30:30 }, // ... 其他消息 ] }几天后你想回顾这个对话只需点击加载历史选择Python推导式学习所有内容就都回来了。5.3 数据文件管理保存的对话文件都存储在conversations/目录下你可以直接查看和管理这些文件conversations/ ├── Python推导式学习_a1b2c3d4.json ├── 技术方案讨论_e5f6g7h8.json ├── 创意写作_i9j0k1l2.json └── 自动保存_20240115_143010_m3n4o5p6.json每个文件都是标准的JSON格式你可以用文本编辑器直接打开查看备份到其他位置分享给其他人注意不要包含敏感信息用Python脚本批量处理6. 进阶功能与优化建议6.1 搜索功能实现如果保存的对话很多找起来可能不太方便。我们可以添加简单的搜索功能def search_conversations(keyword): 搜索包含关键词的对话 results [] conversations list_conversations() for conv in conversations: filepath conv[filepath] try: with open(filepath, r, encodingutf-8) as f: data json.load(f) # 在对话名称和消息内容中搜索 name_match keyword.lower() in conv[name].lower() content_match any( keyword.lower() in msg.get(content, ).lower() for msg in data.get(messages, []) ) if name_match or content_match: results.append(conv) except: continue return results # 在Streamlit界面中添加搜索框 search_keyword st.text_input( 搜索对话内容:, placeholder输入关键词...) if search_keyword: search_results search_conversations(search_keyword) # 显示搜索结果...6.2 对话导出功能有时候你可能想把对话导出为其他格式比如Markdown、PDF或者纯文本def export_to_markdown(conversation_data, output_path): 将对话导出为Markdown格式 with open(output_path, w, encodingutf-8) as f: f.write(f# {conversation_data[name]}\n\n) f.write(f创建时间: {conversation_data[created_at]}\n) f.write(f消息数量: {conversation_data[total_messages]}\n\n) f.write(---\n\n) for msg in conversation_data[messages]: role 用户 if msg[role] user else AI助手 content msg[content] f.write(f## {role}\n\n) f.write(f{content}\n\n) if thinking in msg: f.write(f*思考过程:* {msg[thinking]}\n\n) f.write(---\n\n) return output_path6.3 数据安全考虑对话历史可能包含敏感信息我们需要考虑数据安全本地存储所有数据都保存在本地不会上传到任何服务器加密选项可以添加可选的加密功能清理功能定期清理旧的对话记录导出备份重要对话可以导出备份后从本地删除import hashlib def encrypt_content(content, password): 简单的加密函数示例 # 实际使用时应该使用更安全的加密库 salt nanbeige_salt key hashlib.pbkdf2_hmac(sha256, password.encode(), salt.encode(), 100000) # 这里简化处理实际需要完整的加密实现 return fencrypted:{content[:10]}... # 简化示例 def save_encrypted_conversation(messages, password): 保存加密的对话 encrypted_messages [] for msg in messages: encrypted_msg msg.copy() encrypted_msg[content] encrypt_content(msg[content], password) if thinking in msg: encrypted_msg[thinking] encrypt_content(msg[thinking], password) encrypted_messages.append(encrypted_msg) return save_conversation(encrypted_messages, encrypted_conversation)6.4 性能优化建议如果对话历史非常多可能会影响加载速度。可以考虑以下优化分页加载不要一次性加载所有对话列表缓存机制对已加载的对话进行缓存懒加载只在需要时加载对话内容定期归档将旧的对话打包压缩import pickle import gzip def archive_old_conversations(days_old30): 归档30天前的对话 cutoff_date datetime.now() - timedelta(daysdays_old) archived [] for conv in list_conversations(): created_at datetime.fromisoformat(conv[created_at].replace(Z, 00:00)) if created_at cutoff_date: # 加载对话数据 data load_conversation(conv[filepath]) if data: # 保存到归档文件 archive_file farchived/{conv[filename]}.gz os.makedirs(archived, exist_okTrue) with gzip.open(archive_file, wb) as f: pickle.dump(data, f) # 删除原文件 delete_conversation(conv[filepath]) archived.append(conv[name]) return archived7. 总结7.1 功能回顾通过本教程我们成功为Nanbeige 4.1-3B的清爽WebUI添加了完整的对话历史持久化存储功能。现在这个WebUI不仅界面美观、对话流畅还具备了实用的数据管理能力对话保存可以随时保存当前对话支持自定义名称历史加载可以加载之前保存的任何对话对话管理可以查看、搜索、删除保存的对话自动保存防止意外丢失重要对话数据导出支持将对话导出为其他格式7.2 技术要点总结实现这个功能的关键技术点包括JSON文件存储使用简单可靠的JSON格式保存对话数据Streamlit集成在不破坏原有UI风格的前提下添加新功能数据格式设计合理设计消息数据结构支持思考过程保存错误处理完善的错误处理机制确保稳定性用户体验保持操作简单直观符合原有界面风格7.3 实际应用价值这个持久化存储功能虽然看起来简单但实际价值很大学习助手保存学习过程中的问答形成个人知识库工作记录保存工作讨论和技术方案方便回顾创意积累保存创意对话作为灵感来源调试帮助保存问题解决过程方便后续排查类似问题7.4 下一步改进方向如果你对这个功能还有更多需求可以考虑云同步添加云存储支持在多设备间同步对话标签系统为对话添加标签方便分类管理分享功能生成分享链接方便与他人共享对话统计分析分析对话数据了解使用习惯插件系统支持第三方插件扩展存储功能最重要的是这个实现保持了原有WebUI的清爽风格所有存储功能都通过简洁的按钮实现不会给用户增加学习成本。现在你可以放心地和Nanbeige进行长时间对话了所有精彩内容都不会丢失。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。