最近在做智能客服的时候,编写的 chatbot 发现不同的人访问,上下文有点紊乱,最后发现是 gradio 默认不支持不同客户端对话的单独存储,于是整理了一下文章。

1. 利用 Gradio 的 Session State
import gradio as gr

def chatbot(input, history):
    # 如果没有历史记录,则初始化为空列表
    if history is None:
        history = []
    
    # 将当前输入添加到历史记录中
    history.append(("User: " + input, "Bot: Response to " + input))
    
    # 返回更新后的对话历史和状态
    return history, history

# 创建 Gradio 界面
with gr.Blocks() as demo:
    chat_history = gr.State([])  # 用于存储每个用户的对话历史
    chatbot_output = gr.Chatbot()  # 显示对话内容
    user_input = gr.Textbox(label="输入你的问题")
    
    # 用户提交输入时触发 chatbot 函数
    user_input.submit(chatbot, inputs=[user_input, chat_history], outputs=[chatbot_output, chat_history])

demo.launch()
2. 为每个用户生成唯一的会话 ID
import gradio as gr
import uuid

# 存储所有用户的会话数据
session_data = {}

def get_session_id():
    """生成唯一会话 ID"""
    return str(uuid.uuid4())

def chatbot(input, session_id):
    # 如果是新用户,初始化会话数据
    if session_id not in session_data:
        session_data[session_id] = []
    
    # 获取当前用户的会话历史
    history = session_data[session_id]
    
    # 更新会话历史
    history.append(("User: " + input, "Bot: Response to " + input))
    
    # 返回更新后的对话历史
    return history

# 创建 Gradio 界面
with gr.Blocks() as demo:
    session_id = gr.State(get_session_id())  # 为每个用户生成唯一会话 ID
    chatbot_output = gr.Chatbot()  # 显示对话内容
    user_input = gr.Textbox(label="输入你的问题")
    
    # 用户提交输入时触发 chatbot 函数
    user_input.submit(chatbot, inputs=[user_input, session_id], outputs=[chatbot_output])

demo.launch()
3. 基于用户浏览器的 cook

import gradio as gr
import uuid
from http.cookies import SimpleCookie

# 存储所有用户的会话数据
session_data = {}

def get_or_create_session_id(cookies):
    """从 Cookie 中获取会话 ID,如果不存在则生成新的"""
    cookie = SimpleCookie()
    cookie.load(cookies)
    
    if "session_id" in cookie:
        session_id = cookie["session_id"].value
    else:
        session_id = str(uuid.uuid4())  # 生成唯一会话 ID
        session_data[session_id] = []  # 初始化会话数据
    
    return session_id

def chatbot(input, cookies):
    # 获取或创建会话 ID
    session_id = get_or_create_session_id(cookies)
    
    # 获取当前用户的会话历史
    history = session_data[session_id]
    
    # 更新会话历史
    history.append(("User: " + input, "Bot: Response to " + input))
    
    # 返回更新后的对话历史和新的 Cookie
    response_cookie = f"session_id={session_id}; Path=/; HttpOnly"
    return history, response_cookie

# 创建 Gradio 界面
with gr.Blocks() as demo:
    chatbot_output = gr.Chatbot()  # 显示对话内容
    user_input = gr.Textbox(label="输入你的问题")
    cookies = gr.Textbox(label="Cookies", visible=False)  # 隐藏的输入框,用于传递 Cookie
    
    # 用户提交输入时触发 chatbot 函数
    user_input.submit(chatbot, inputs=[user_input, cookies], outputs=[chatbot_output, cookies])

demo.launch()

在 Gradio 中,直接操作浏览器的 LocalStorageIndexedDB 需要借助 JavaScript。Gradio 本身是一个 Python 框架,但可以通过 gr.HTMLgr.Javascript 组件嵌入自定义的 JavaScript 代码来实现对浏览器存储的操作。

4. 使用 LocalStorage 存储和读取数据
import gradio as gr

# 定义 JavaScript 代码
js_code = """
// 将数据保存到 LocalStorage
function saveToLocalStorage(key, value) {
    localStorage.setItem(key, JSON.stringify(value));
}

// 从 LocalStorage 读取数据
function loadFromLocalStorage(key) {
    const data = localStorage.getItem(key);
    return data ? JSON.parse(data) : [];
}
"""

# 定义 Chatbot 的逻辑
def chatbot(input, history):
    if history is None:
        history = []
    
    # 更新对话历史
    history.append(("User: " + input, "Bot: Response to " + input))
    return history, history

# 创建 Gradio 界面
with gr.Blocks() as demo:
    # 嵌入 JavaScript 代码
    gr.HTML(f"<script>{js_code}</script>")
    
    # 定义组件
    chatbot_output = gr.Chatbot()
    user_input = gr.Textbox(label="输入你的问题")
    history = gr.JSON(value=[], visible=False)  # 用于传递历史记录
    
    # 保存到 LocalStorage 的按钮
    save_button = gr.Button("保存对话历史")
    save_button.click(
        None,
        inputs=[history],
        _js="(history) => saveToLocalStorage('chat_history', history)"
    )
    
    # 从 LocalStorage 加载对话历史的按钮
    load_button = gr.Button("加载对话历史")
    load_button.click(
        None,
        outputs=[history],
        _js="() => loadFromLocalStorage('chat_history')"
    )
    
    # 用户提交输入时触发 chatbot 函数
    user_input.submit(chatbot, inputs=[user_input, history], outputs=[chatbot_output, history])

demo.launch()

4.1. 代码解析
4.1.1. JavaScript 部分
  • saveToLocalStorage:将对话历史(JSON 格式)保存到浏览器的 LocalStorage
  • loadFromLocalStorage:从 LocalStorage 中读取对话历史,并返回一个数组(如果不存在则返回空数组)。
4.1.2. Python 部分
  • gr.HTML:嵌入 JavaScript 代码,使浏览器能够执行 saveToLocalStorageloadFromLocalStorage 函数。
  • _js 参数:Gradio 提供了 _js 参数,可以直接调用嵌入的 JavaScript 函数。
    • save_button.click 中,通过 _js 调用 saveToLocalStorage,将对话历史保存到 LocalStorage
    • load_button.click 中,通过 _js 调用 loadFromLocalStorage,从 LocalStorage 加载对话历史。
4.2. 流程说明
  1. 用户与 Chatbot 交互时,对话历史会实时更新并显示在界面上。
  2. 点击“保存对话历史”按钮时,当前的对话历史会被保存到浏览器的 LocalStorage
  3. 点击“加载对话历史”按钮时,对话历史会从 LocalStorage 加载并显示在界面上。

5. IndexedDB 示例(更复杂的数据存储)

IndexedDB 是一种更强大的客户端存储机制,适合存储大量结构化数据。

import gradio as gr

# 定义 JavaScript 代码
js_code = """
let db;

// 打开 IndexedDB 数据库
function openDatabase() {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open("ChatHistoryDB", 1);
        request.onerror = () => reject("数据库打开失败");
        request.onsuccess = () => {
            db = request.result;
            resolve();
        };
        request.onupgradeneeded = (event) => {
            const db = event.target.result;
            if (!db.objectStoreNames.contains("history")) {
                db.createObjectStore("history", { keyPath: "id" });
            }
        };
    });
}

// 保存数据到 IndexedDB
function saveToIndexedDB(key, value) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(["history"], "readwrite");
        const store = transaction.objectStore("history");
        const request = store.put({ id: key, data: value });
        request.onsuccess = () => resolve();
        request.onerror = () => reject("保存失败");
    });
}

// 从 IndexedDB 读取数据
function loadFromIndexedDB(key) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction(["history"], "readonly");
        const store = transaction.objectStore("history");
        const request = store.get(key);
        request.onsuccess = () => resolve(request.result ? request.result.data : []);
        request.onerror = () => reject("读取失败");
    });
}
"""

# 定义 Chatbot 的逻辑
def chatbot(input, history):
    if history is None:
        history = []
    
    # 更新对话历史
    history.append(("User: " + input, "Bot: Response to " + input))
    return history, history

# 创建 Gradio 界面
with gr.Blocks() as demo:
    # 嵌入 JavaScript 代码
    gr.HTML(f"<script>{js_code}</script>")
    
    # 定义组件
    chatbot_output = gr.Chatbot()
    user_input = gr.Textbox(label="输入你的问题")
    history = gr.JSON(value=[], visible=False)  # 用于传递历史记录
    
    # 打开数据库
    gr.HTML("<script>openDatabase();</script>")
    
    # 保存到 IndexedDB 的按钮
    save_button = gr.Button("保存对话历史")
    save_button.click(
        None,
        inputs=[history],
        _js="(history) => saveToIndexedDB('chat_history', history)"
    )
    
    # 从 IndexedDB 加载对话历史的按钮
    load_button = gr.Button("加载对话历史")
    load_button.click(
        None,
        outputs=[history],
        _js="() => loadFromIndexedDB('chat_history')"
    )
    
    # 用户提交输入时触发 chatbot 函数
    user_input.submit(chatbot, inputs=[user_input, history], outputs=[chatbot_output, history])

demo.launch()

6. 总结
  • LocalStorage:简单易用,适合存储小量数据(如对话历史)。
  • IndexedDB:功能强大,适合存储大量结构化数据。
  • Gradio 结合 JavaScript:通过 _js 参数调用自定义的 JavaScript 函数,可以轻松实现浏览器端的数据存储与读取。

无论是 LocalStorage 还是 IndexedDB,它们都提供了持久化存储的能力,确保用户在刷新页面或重新打开浏览器后仍能保留对话历史。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐