news 2026/4/16 11:31:16

Chandra开源大模型实战:Ollama WebUI源码二次开发,增加历史会话导出功能

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Chandra开源大模型实战:Ollama WebUI源码二次开发,增加历史会话导出功能

Chandra开源大模型实战:Ollama WebUI源码二次开发,增加历史会话导出功能

1. 为什么需要给Chandra加个“导出键”

你有没有过这样的经历:和AI聊了半小时,从写周报到改简历再到生成会议纪要,内容越积越多,结果一刷新页面,所有对话全没了?或者想把某次特别精彩的问答整理成文档发给同事,却只能靠截图、复制粘贴,手忙脚乱还容易漏行?

Chandra镜像确实很轻快——本地跑gemma:2b,不联网、不传数据、秒响应,但它的WebUI界面太“干净”了,干净到连个“保存聊天记录”的按钮都没有。这不是设计缺陷,而是Ollama官方WebUI的默认取舍:聚焦实时交互,弱化历史管理。

可真实工作场景里,我们不是在做Demo,而是在用AI干活。一次高质量对话就是一份可复用的知识资产。导出历史会话,不是锦上添花的功能,而是让Chandra从“玩具级聊天框”升级为“个人AI工作台”的关键一步。

本文不讲高深理论,也不堆砌配置参数。我会带你从零开始,真正动手修改Chandra所依赖的Ollama WebUI源码,一行一行加功能、测效果、打包部署。整个过程不需要你懂React全家桶,只要你会看HTML结构、能写基础JavaScript、会运行npm命令——就像修自家书桌抽屉,拧几颗螺丝,换块木板,完事就能用。

你将获得:

  • 一个带“导出当前会话”按钮的真实可用WebUI
  • 完整可复用的二次开发流程(适配任何Ollama WebUI版本)
  • 导出文件为标准JSON格式,兼容Obsidian、Notion、Typora等主流工具
  • 零外部依赖,所有代码都在前端完成,不碰后端API

前置知识只要一条:你知道git clone是下载代码,npm run dev是启动本地服务。其余,咱们边做边聊。

2. 拆解Chandra的WebUI结构:找到那个“能改的地方”

Chandra镜像的前端,本质是Ollama官方维护的Ollama WebUI项目的一个定制分支。它不是黑盒,而是一套清晰的前端工程:Vite构建、React编写、Tailwind CSS样式。我们要加功能,就得先看清它的骨架。

2.1 快速定位核心文件

启动Chandra镜像后,访问http://localhost:3000打开界面。右键→“查看页面源代码”,你会发现所有资源都来自/static/路径。这说明前端是静态打包产物。但源码在哪?答案就在镜像的构建逻辑里。

进入Ollama WebUI官方仓库,主目录下有三个关键区域:

  • src/App.tsx:整个应用的根组件,负责路由和全局状态
  • src/components/ChatWindow.tsx:聊天窗口主体,包含消息列表、输入框、发送按钮
  • src/components/ChatHistory.tsx:左侧历史会话列表,管理会话ID、标题、时间戳

我们要加“导出”功能,最自然的位置是当前会话的上下文操作区——也就是每条聊天记录右上角那个“⋯”菜单。但Chandra默认没这个菜单。所以第一步,得先给它“长”出来。

2.2 分析现有会话管理逻辑

打开src/components/ChatWindow.tsx,搜索关键词useEffectmessages,很快能找到消息加载的核心逻辑:

// src/components/ChatWindow.tsx 约第85行 const loadMessages = useCallback(async () => { if (!currentChatId) return; const res = await fetch(`/api/chat/${currentChatId}/messages`); const data = await res.json(); setMessages(data); }, [currentChatId]);

看到没?它通过/api/chat/{id}/messages这个API拉取当前会话全部消息。这个接口返回的就是标准JSON数组,结构类似:

[ { "role": "user", "content": "你好" }, { "role": "assistant", "content": "你好!我是Chandra。" } ]

这意味着:导出功能完全可以在前端实现——我们不需要改后端,只要拿到这个数组,用JSON.stringify()格式化,再触发浏览器下载即可。安全、简单、零侵入。

2.3 设计导出按钮的落点

现在问题变成:按钮放哪?有三个合理选项:

  • 顶部工具栏:在聊天窗口右上角,和“新建会话”“删除会话”并列
  • 消息列表底部:在最后一条消息下方,加一个醒目按钮
  • 右键菜单:长按某条消息弹出“导出本条”或“导出全部”

对普通用户最友好、开发成本最低的是第一种。Chandra的顶部栏已有New ChatDelete Chat按钮,我们加一个Export,风格统一,位置直观,符合用户心智模型。

打开src/components/ChatWindow.tsx,找到渲染顶部栏的代码块(通常在return语句开头附近),你会看到类似这样的结构:

<div className="flex items-center justify-between p-4 border-b"> <h2 className="text-lg font-semibold">Chat #{currentChatId}</h2> <div className="flex space-x-2"> <button onClick={handleNewChat} className="...">New Chat</button> <button onClick={handleDeleteChat} className="...">Delete</button> </div> </div>

这就是我们的“插入点”。

3. 动手写代码:三步实现导出功能

3.1 第一步:添加导出按钮UI

<div className="flex space-x-2">内部,<button>标签之间,插入新的导出按钮:

<button onClick={handleExportChat} className="px-3 py-1.5 text-sm font-medium rounded-md bg-gray-100 hover:bg-gray-200 text-gray-700 transition-colors" title="导出当前会话为JSON文件" > Export </button>

注意这里用了onClick={handleExportChat},表示点击时调用一个叫handleExportChat的函数。这个函数还没写,别急,马上来。

3.2 第二步:实现导出逻辑

在同一个文件ChatWindow.tsx中,找到const handleDeleteChat = ...这类函数定义的位置,在它下方,新增handleExportChat函数:

const handleExportChat = () => { if (!currentChatId || messages.length === 0) return; // 构建导出文件名:chat_20240520_143022.json const now = new Date(); const filename = `chat_${now.toISOString().slice(0, 10)}_${now.toTimeString().slice(0, 8).replace(/:/g, '')}.json`; // 将消息数组转为JSON字符串,并添加元信息 const exportData = { exportedAt: now.toISOString(), chatId: currentChatId, model: 'gemma:2b', messages: messages.map(msg => ({ role: msg.role, content: msg.content, timestamp: msg.timestamp || new Date().toISOString() })) }; const jsonStr = JSON.stringify(exportData, null, 2); const blob = new Blob([jsonStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); };

这段代码做了五件事:

  1. 校验是否有会话ID和消息,避免空导出
  2. 生成带日期时间的文件名,避免覆盖
  3. 构建结构化JSON对象,包含导出时间、会话ID、模型名、消息列表
  4. 将JSON转为Blob对象,创建临时下载URL
  5. 模拟点击触发浏览器下载,并清理内存

它不依赖任何第三方库,纯原生JavaScript,兼容所有现代浏览器。

3.3 第三步:优化用户体验细节

现在按钮有了,功能也通了,但还差一点“人味”。比如:

  • 用户点击后,按钮应该短暂变灰,防止重复点击
  • 导出成功后,给个轻量提示,而不是静默完成

我们在handleExportChat开头加个loading状态,在结尾加个Toast提示:

const [isExporting, setIsExporting] = useState(false); const handleExportChat = () => { if (!currentChatId || messages.length === 0 || isExporting) return; setIsExporting(true); // ... 原有导出逻辑 ... // 导出完成后 setIsExporting(false); // 显示成功提示(使用原生alert或更优雅的Toast) if (typeof window !== 'undefined') { // 简单起见,用原生提示;生产环境建议替换为自定义Toast组件 alert(` 已导出 ${messages.length} 条消息到 ${filename}`); } };

同时,把按钮的className更新一下,加入禁用态样式:

<button onClick={handleExportChat} disabled={isExporting} className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${ isExporting ? 'bg-gray-300 cursor-not-allowed' : 'bg-gray-100 hover:bg-gray-200 text-gray-700' }`} title={isExporting ? "正在导出..." : "导出当前会话为JSON文件"} > {isExporting ? 'Exporting...' : 'Export'} </button>

至此,功能闭环。按钮点击→生成JSON→触发下载→提示成功,一气呵成。

4. 构建与部署:让修改生效到Chandra镜像

写完代码只是第一步。Chandra是Docker镜像,我们必须把修改后的前端代码重新打包,再集成进镜像。

4.1 本地验证:先跑起来看看

进入你克隆的Ollama WebUI源码目录(如果你还没克隆,请执行):

git clone https://github.com/ollama-webui/ollama-webui.git cd ollama-webui

安装依赖并启动开发服务器:

npm install npm run dev

打开http://localhost:5173,你应该能看到和Chandra一模一样的界面,但右上角多了一个Export按钮。随便聊几句,点击它——文件立刻下载,打开JSON,内容清晰完整。

4.2 打包生产版本

确认功能无误后,执行构建:

npm run build

构建完成后,dist/目录下生成所有静态文件。这就是我们要塞进Docker镜像的前端资源。

4.3 修改Chandra镜像Dockerfile

Chandra镜像的Dockerfile(通常在CSDN星图镜像广场提供)会类似这样:

FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 3000 CMD ["nginx", "-g", "daemon off;"]

关键点在于:它用的是官方Ollama WebUI的源码构建。我们要做的,就是把上面构建好的dist/目录,直接替换掉镜像中原本的前端资源。

最稳妥的方式是:基于Chandra镜像,写一个极简的Dockerfile,只做一件事——把你的dist/目录COPY进去:

FROM registry.cn-hangzhou.aliyuncs.com/csdn-mirror/chandra:latest COPY ./dist/ /usr/share/nginx/html/

然后构建新镜像:

docker build -t my-chandra-export . docker run -d -p 3000:3000 --name chandra-export my-chandra-export

访问http://localhost:3000,导出按钮已就位。

4.4 一键部署到CSDN星图(可选)

如果你希望把这个增强版Chandra分享给团队,可以直接上传到CSDN星图镜像广场。上传时,在镜像描述中注明:“已集成历史会话导出功能,点击右上角Export按钮即可保存为JSON”。

5. 进阶思考:不只是导出,还能做什么

导出功能看似简单,但它打开了Chandra能力边界的第一道门。基于这个基础,你可以轻松延伸出更多实用能力:

5.1 导入功能:让知识流动起来

有了导出,自然需要导入。只需在ChatWindow.tsx中加一个Import按钮,读取用户选择的JSON文件,解析后调用Ollama API的/api/chat接口创建新会话。代码量不到20行。

5.2 会话分组与标签

当前Chandra的历史列表只按时间排序。你可以修改ChatHistory.tsx,为每个会话添加tag字段(如“工作”“学习”“创意”),支持按标签筛选。数据存在localStorage里,无需后端。

5.3 Markdown导出(给内容创作者)

很多用户导出是为了发公众号或写博客。把JSON里的content字段用marked库转成Markdown,再包装成.md文件下载,体验直接提升一个档次。

这些都不是空中楼阁。它们共享同一个底层逻辑:理解Ollama WebUI的数据流,信任前端的可控性,用最小改动解决最大痛点

Chandra的价值,从来不在它预装了哪个模型,而在于它给你留了一扇开着的门——门后是完整的、可触摸的、属于你自己的AI工作流。

6. 总结:你刚刚完成的,是一次真正的工程实践

回顾整个过程,你没有调用任何神秘API,没有配置复杂中间件,甚至没动一行后端代码。你只是:

  • 看懂了一个开源项目的文件结构
  • 在正确的位置加了三段逻辑清晰的代码
  • 用标准Web技术完成了数据导出
  • 把成果打包进容器,一键运行

这恰恰是AI时代最该被重视的能力:不迷信黑盒,不畏惧源码,用工程思维把大模型真正“用起来”

Chandra的gemma:2b模型很小,但你的这次修改,让它承载了真实的工作价值。下次当你导出一份会议纪要、一份产品需求草稿、一份学习笔记时,你会记得:这个按钮,是你亲手拧上去的。

技术的价值,永远体现在它如何服务于人的具体行动。而你,已经开始了。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:04:33

WAN2.2文生视频GPU算力优化:显存复用策略与多任务并发调度实测

WAN2.2文生视频GPU算力优化&#xff1a;显存复用策略与多任务并发调度实测 1. 为什么WAN2.2的显存占用让人皱眉&#xff1f; 你刚下载完WAN2.2模型&#xff0c;兴冲冲打开ComfyUI&#xff0c;加载完工作流&#xff0c;点下执行——结果显存直接飙到98%&#xff0c;GPU温度瞬间…

作者头像 李华
网站建设 2026/4/16 12:00:09

CCS安装操作指南:驱动与Java环境预配置

CCS安装实战手记&#xff1a;Java环境与XDS110驱动的“隐形门槛”全解析刚拆开一块TMS320F28379D LaunchPad&#xff0c;兴奋地双击ccs.exe——结果弹出一个冷冰冰的报错框&#xff1a;“Failed to create the Java Virtual Machine”又或者&#xff0c;CCS终于启动了&#xff…

作者头像 李华
网站建设 2026/4/16 11:57:07

零基础玩转Youtu-2B:腾讯优图大模型保姆级对话应用教程

零基础玩转Youtu-2B&#xff1a;腾讯优图大模型保姆级对话应用教程 1. 为什么你需要一个“轻量但能打”的大模型&#xff1f; 你有没有遇到过这些情况&#xff1a; 想在自己的笔记本或边缘设备上跑个大模型&#xff0c;结果显存不够、卡顿严重&#xff0c;甚至直接报错OOM&a…

作者头像 李华
网站建设 2026/4/16 11:58:43

Qwen3-ASR-0.6B教育应用:在线课堂实时字幕系统

Qwen3-ASR-0.6B教育应用&#xff1a;在线课堂实时字幕系统 1. 在线课堂的“听不见”难题&#xff0c;正在悄悄改变教学体验 你有没有遇到过这样的情况&#xff1a;国际课程里老师带着浓重口音&#xff0c;学生频频皱眉&#xff1b;听障学生盯着黑板上的PPT&#xff0c;却错过…

作者头像 李华
网站建设 2026/4/16 12:05:25

Qwen3-4B-Instruct-2507商业应用:合规部署注意事项

Qwen3-4B-Instruct-2507商业应用&#xff1a;合规部署注意事项 1. 模型定位与核心价值再认识 通义千问3-4B-Instruct-2507&#xff08;以下简称Qwen3-4B-Instruct-2507&#xff09;不是又一个参数堆砌的“大模型”&#xff0c;而是一次面向真实业务场景的精准工程实践。它由阿…

作者头像 李华