【gradio】從零搭建知識庫問答系統-Gradio+Ollama+Qwen2.5實現全流程

從零搭建大模型問答系統-Gradio+Ollama+Qwen2.5實現全流程(一)

  • 前言
  • 一、界面設計(計劃)
  • 二、模塊設計
    • 1.登錄模塊
    • 2.注冊模塊
    • 3. 主界面模塊
    • 4. 歷史記錄模塊
  • 三、相應的接口(前后端交互)
  • 四、實現前端界面的設計
    • config.py
    • History_g1.py
    • Login.py
    • Main.py
    • Register.py
    • App.py
  • 五、效果展示

壓抑的氣氛,疲憊的身軀,干澀的眼眶,閑暇的周末~
不甘沉浸在打瓦與go學長的對抗較量,不愿沉迷于開麥與隊友間的加密通信,
我便默默地打開電腦,選擇換個活法度過周末。

前言

隨著人工智能 AI 的快速發展,基于大語言模型(LLM)的應用逐漸成為軟件開發中的熱點。今天就學習一下如何設計和實現一個前后端交互的問答系統。在這里先進行需求分析,從以下三個角度去考慮:
技術需求 :

  • 前端使用 Gradio 構建用戶界面
  • 后端使用 Ollama(本地化模型部署工具)
  • 框架調用 Qwen2.5 大模型
  • 通信協議選擇RESTful API(JSON格式)

通過初始設計,我們需要從需求分析開始入手,再去系統實現,最后完成開發的整個過程。
功能需求 :

  • 用戶可以通過前端界面輸入問題。
  • 系統能夠基于大模型生成準確的回答。
  • 支持多輪對話。
  • 提供簡單的用戶操作反饋(如加載狀態、錯誤提示等)。

非功能性需求 :

  • 系統響應時間控制在合理范圍內。
  • 界面簡潔直觀,用戶體驗友好。

通信流程圖

通信流程
用戶->>+前端: 輸入問題
前端->>+后端: POST /api/generate
后端->>+Ollama: 模型調用請求
Ollama->>-后端: 生成響應
后端->>-前端: JSON響應
前端->>-用戶: 顯示回答

一、界面設計(計劃)

在設計的時候將預期的功能都設計出來,之后再逐個去實現,往往設計的太簡單,當后續需添加功能的時候,前端界面的布局反復修改也比較麻煩。先照葫蘆畫瓢,根據deepseek以及kimi等問答系統的界面進行模仿學習,修改設計。
這里登錄方式分為三種 : 一種是微信掃碼,手機號登錄,賬號密碼登錄。在實現過程中,暫時計劃實現一種登錄方式即可,完成整個項目的邏輯。

  1. 界面1 ,登陸界面

| 驗證碼登錄 | 密碼登錄 |
————————————
這塊就使用到我們前面學習到的多頁面布局
還有Row 和 Column的組合拳
在這里插入圖片描述
在這里插入圖片描述

  1. 界面2 ,注冊界面

  2. 界面3,主界面

在這里插入圖片描述

4.歷史記錄界面

在這里插入圖片描述
界面設計不用太細節有個草圖模樣就行,重點還是需要理解如何使用容器來布局這些設計,以及添加組件,綁定什么事件。你也可以自己設計一些更加豐富多彩的界面,有的設計在gradio中要想完美的顯示,還是需要費很大的功夫(別問為什么)。

二、模塊設計

預期有以下四個界面,先將每個界面寫出來,再去考慮整合在一塊。

1.登錄模塊

核心功能
發送驗證碼 :

用戶輸入手機號或郵箱后,點擊“發送驗證碼”按鈕。
前端調用后端接口 /send_verification_code 發送驗證碼。
如果輸入格式錯誤(如手機號不是11位數字),前端會直接提示錯誤信息。

驗證碼登錄驗證 :

用戶輸入驗證碼后,點擊“登錄”按鈕。
前端調用后端接口 /verify_code_login 驗證驗證碼是否正確。
如果驗證成功,返回“登錄成功”;否則提示錯誤信息。

密碼登錄驗證 :

用戶輸入用戶名和密碼后,點擊“登錄”按鈕。
前端調用后端接口 /login 進行密碼驗證。
如果驗證成功,返回用戶ID和成功信息;否則提示錯誤信息。

用戶注冊 :

用戶輸入用戶名和密碼后,點擊“注冊”按鈕。
前端調用后端接口 /register 提交注冊信息。
如果注冊成功,提示用戶“注冊成功,請登錄”。

忘記密碼 :

提示用戶聯系管理員重置密碼。

界面設計

使用 Gradio 的 Tabs 組件實現驗證碼登錄和密碼登錄的切換。
左側為掃碼登錄區域,右側為登錄選項卡區域。
提供清晰的輸入框、按鈕和狀態提示。

2.注冊模塊

核心功能
發送驗證碼 :

用戶輸入手機號后,點擊“獲取驗證碼”按鈕。
前端調用后端接口 /send-code 發送驗證碼。
如果手機號格式錯誤或發送失敗,前端會提示錯誤信息。

用戶注冊 :

用戶輸入手機號、驗證碼、用戶名、密碼和確認密碼后,點擊“立即注冊”按鈕。
前端調用后端接口 /register 提交注冊信息。
如果注冊成功,提示用戶“注冊成功!”;否則提示錯誤信息。

返回登錄 :

提供“返回登錄”按鈕,用戶可以跳轉回登錄界面。

界面設計

提供清晰的輸入框和按鈕。
實時顯示狀態提示(如驗證碼發送成功或失敗)。

3. 主界面模塊

核心功能
用戶輸入處理 :

用戶輸入問題后,點擊“提交”按鈕或按 Enter 鍵。
前端調用后端接口 /chat 獲取模型的回答。
將用戶輸入和模型回答更新到聊天歷史記錄中。

新建對話 :

用戶點擊“開啟新對話”按鈕。
前端調用后端接口 /save_conversation 保存當前對話記錄,并清空聊天歷史。

切換側邊欄 :

用戶可以點擊“切換側邊欄”按鈕展開或收起側邊欄。

二維碼窗口 :

用戶點擊“手機端下載”按鈕,顯示二維碼窗口。
點擊關閉按鈕隱藏二維碼窗口。

快捷問題 :

提供三個快捷問題按鈕,用戶點擊后自動填充到輸入框。

4. 歷史記錄模塊

核心功能

獲取歷史記錄 : 前端調用后端接口 /get_conversation 獲取歷史記錄。
如果后端不可用,使用本地模擬數據。

動態更新 :

用戶選擇時間范圍或輸入搜索關鍵詞后,前端動態更新歷史記錄。

返回主界面 :

提供“返回主界面”按鈕,用戶可以跳轉回主界面。

界面設計

提供時間范圍選擇器和搜索框。 使用 HTML 動態生成歷史記錄列表。

三、相應的接口(前后端交互)

在接口設計的過程中,一定要設計好請求參數相應參數,如果未確定好請求響應的參數,在與后端交互的時候會有很多不必要的麻煩。以下是我設計的相關參數,讀者也可以自己去設計添加更多的參數以優化自己系統的功能。
接口概覽表

模塊接口地址請求方法功能說明調用位置
登錄模塊/send_verification_codePOST發送驗證碼登錄界面發送按鈕
/verify_code_loginPOST驗證碼登錄驗證碼登錄按鈕
/loginPOST密碼登錄密碼登錄按鈕
注冊界面/send-codePOST發送注冊驗證碼注冊界面發送按鈕
/registerPOST提交注冊信息注冊按鈕
主界面/chatPOST處理用戶提問聊天消息提交
/save_conversationPOST保存對話記錄新建對話按鈕
歷史記錄/get_conversationPOST獲取歷史對話歷史記錄頁面加載
  1. 登錄模塊
    1.1 發送驗證碼
    請求地址

/send_verification_code
請求參數

{"phone_email": "用戶輸入的手機號或郵箱"}

響應示例

{"message": "驗證碼已發送,請查收!"}
{ "detail": "錯誤信息(如手機號格式錯誤)"}

1.2 驗證碼登錄
請求地址

/verify_code_login

請求參數

{"phone_email": "用戶輸入的手機號或郵箱","code": "用戶輸入的驗證碼"
}

響應示例

{"status": "success","message": "登錄成功!"
}
{"status": "error","detail": "驗證碼錯誤,請重新輸入!"
}

1.3 密碼登錄
請求地址

/login

請求參數

{"username": "用戶名","password": "密碼"
}

響應示例

{"status": "success","message": "登錄成功!","user_id": 12345
}
{"status": "error","detail": "用戶名或密碼錯誤!"
}
  1. 注冊模塊
    2.1 發送注冊驗證碼
    請求地址

/send-code

請求參數

{"phone_number": "用戶輸入的手機號"}

響應示例

# 成功
{ "message": "驗證碼已發送至 {phone_number}"}
# 失敗
{"message": "發送失敗:錯誤信息"}

2.2 提交注冊
請求地址

/register

請求參數

{"username": "用戶名","password": "密碼"
}

響應示例

# 成功
{ "message": "注冊成功!"}
# 失敗
{ "message": "注冊失敗:錯誤信息"}
  1. 主界面

3.1 處理用戶提問
請求地址

/chat

請求參數

{"user_input": "用戶輸入的問題","chat_history": [{"role": "user", "content": "用戶輸入"},{"role": "assistant", "content": "模型回答"}]
}

響應示例

# 成功
{"status": "success","response": "模型生成的回答","chat_history": [{"role": "user", "content": "用戶輸入"},{"role": "assistant", "content": "模型回答"}]
}
# 失敗
{"status": "error","detail": "無法處理請求,請稍后再試!"
}

3.2 保存對話記錄
請求地址

/save_conversation

請求參數

{"user_id": "用戶ID","conversation": [{"user_input": "用戶輸入"},{"bot_response": "模型回答"}]
}

響應示例

# 成功
{ "message": "對話記錄保存成功"}
# 失敗
{"message": "保存失敗:錯誤信息"}
  1. 歷史記錄模塊

4.1 獲取歷史對話
請求地址

/get_conversation

請求參數

{"user_id": "用戶ID","time_period": "時間范圍(本周/本月/本年/全部)","search_query": "搜索關鍵詞"
}

響應示例

# 成功
{"status": "success","chat_history": [{"title": "會話標題","content": "會話內容","date": "會話日期"},...]
}
# 失敗
{"status": "error","detail": "無法獲取歷史記錄,請稍后再試!"
}

四、實現前端界面的設計

當確定好接口,設計好界面,我們就可以進行編程了。
這是文件目錄:
在這里插入圖片描述

config.py

# config.py# 后端 API 地址
BASE_URL = "http://localhost:8000"

History_g1.py

import gradio as gr
import requests
import json
from config import BASE_URLdef fetch_history(user_id, time_period="全部", search_query=""):"""從后端接口或 mock_history_data 獲取歷史記錄。:param user_id: 用戶的唯一標識:param time_period: 時間范圍("本周", "本月", "本年", "全部":param search_query: 搜索關鍵詞:return: 歷史記錄數據"""try:# 構造請求數據payload = {"user_id": user_id,"time_period": time_period,"search_query": search_query,}# 發送 POST 請求到后端 APIresponse = requests.post(f"{BASE_URL}/get_conversation", json=payload)# 檢查響應狀態碼if response.status_code == 200:# 解析后端返回的數據history_data = response.json().get("chat_history", [])# 數據適配:確保返回的是嵌套列表,每個元素是一個字典formatted_data = []for index, item in enumerate(history_data):# print(f"處理第 {index + 1} 條記錄: {item}")  # 輸出當前處理的記錄if isinstance(item, list):  # 判斷是否為嵌套列表valid_conversation = []for record in item:# print(f"  當前記錄: {record}, 類型: {type(record)}")  # 輸出當前記錄及其類型# 如果記錄是字符串形式的 JSON,嘗試解析為 Python 對象if isinstance(record, str) and (record.startswith("[") or record.startswith("{")):try:record = json.loads(record.replace("'", '"'))  # 替換單引號為雙引號# print(f"    成功解析字符串 JSON: {record}")except json.JSONDecodeError:# print(f"    無法解析字符串 JSON: {record}")continue# 如果解析后的對象是列表,則遍歷其中的每個元素if isinstance(record, list):for sub_record in record:if isinstance(sub_record, dict):  # 判斷是否為字典# print(f"      子記錄字典內容: {sub_record.keys()}")  # 輸出子記錄字典的鍵if "user_input" in sub_record or "bot_response" in sub_record:# 添加默認 date 字段sub_record.setdefault("date", "未知日期")valid_conversation.append(sub_record)else:print(f"      非字典子記錄: {sub_record}")elif isinstance(record, dict):  # 判斷是否為字典# print(f"    字典內容: {record.keys()}")  # 輸出字典的鍵if "user_input" in record or "bot_response" in record:# 添加默認 date 字段record.setdefault("date", "未知日期")valid_conversation.append(record)else:print(f"    非字典記錄: {record}")if valid_conversation:  # 只保留有效的對話記錄formatted_data.append(valid_conversation)else:print(f"非嵌套列表記錄: {item}")print(f"后端返回的原始數據: {history_data}")print(f"格式化后的數據: {formatted_data}")return formatted_dataelse:print(f"后端錯誤: {response.status_code}")raise Exception("后端返回錯誤狀態碼")except Exception as e:# 如果后端調用失敗,回退到本地模擬數據print(f"調用后端接口失敗: {str(e)}")def update_history(user_id, time_period="全部", search_query=""):"""根據時間范圍和搜索關鍵詞動態更新歷史記錄。:param user_id: 用戶的唯一標識:param time_period: 時間范圍("本周", "本月", "本年", "全部":param search_query: 搜索關鍵詞:return: 歷史記錄 HTML 內容"""# 獲取所有歷史記錄all_conversations = fetch_history(user_id, time_period, search_query)# 構造 HTML 輸出內容if not all_conversations:return "<div style='color: gray;'>暫無歷史記錄</div>"html_content = []for conversation in all_conversations:# 第一條提問作為超鏈接標題first_user_input = conversation[0].get("user_input", "無標題")first_date = conversation[0].get("date", "未知日期")# 超鏈接部分html_content.append(f'''<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"><button style="background: none; border: none; color: blue; text-decoration: underline; cursor: pointer;" onclick="handleButtonClick('{first_user_input}')">{first_user_input}</button><span style="margin-left: 10px; color: gray;">{first_date}</span></div>''')return "\n".join(html_content)def show_conversation_details(conversation_title, user_id, time_period="全部", search_query=""):"""根據超鏈接標題顯示對應對話的詳細信息。:param conversation_title: 對話標題(第一條提問的內容):param user_id: 用戶的唯一標識:param time_period: 時間范圍("本周", "本月", "本年", "全部":param search_query: 搜索關鍵詞:return: 對話詳細信息的 HTML 內容"""# 獲取所有歷史記錄all_conversations = fetch_history(user_id, time_period, search_query)# 找到對應標題的對話for conversation in all_conversations:if conversation[0].get("user_input") == conversation_title:html_content = []for record in conversation:if "user_input" in record:content = f"用戶提問: {record['user_input']}"elif "bot_response" in record:content = f"機器人回復: {record['bot_response']}"html_content.append(f'<div style="margin-left: 20px;">{content} <span style="margin-left: 10px; color: gray;">{record["date"]}</span></div>')return "<br>".join(html_content)return "<div style='color: gray;'>未找到相關對話</div>"def history_interface(user_id_state):"""歷史記錄界面的封裝函數"""with gr.Column() as history_content:gr.Markdown("# 歷史會話")with gr.Row(elem_classes="highlight-border"):# 添加返回按鈕back_to_main_btn = gr.Button("返回主界面", elem_classes="send-btn4", elem_id="back-to-main-btn")change_btn = gr.Button("刷新", elem_classes="send-btn4", elem_id="change_btn")# 時間范圍選擇器with gr.Row(elem_classes="highlight-border"):time_period_dropdown = gr.Dropdown(choices=["本周", "本月", "本年", "全部"],label="選擇時間范圍",value="全部")# 搜索框with gr.Row(elem_classes="highlight-border"):search_box = gr.Textbox(label="搜索歷史會話", placeholder="輸入關鍵詞搜索")# 歷史記錄展示區域with gr.Group(elem_classes="highlight-border"):history_output = gr.HTML()# # 隱藏的 Textbox 用于觸發事件# click_event_trigger = gr.Textbox(visible=False)# 刷新按鈕事件change_btn.click(fn=update_history,inputs=[user_id_state, time_period_dropdown, search_box],outputs=[history_output])# 動態生成按鈕并綁定事件def on_link_click(link_text):detailed_info = show_conversation_details(link_text, user_id_state.value, "全部", "")return detailed_info# 獲取歷史記錄并動態生成按鈕all_conversations = fetch_history(user_id_state.value, "全部", "")buttons = []for conversation in all_conversations:first_user_input = conversation[0].get("user_input", "無標題")button = gr.Button(first_user_input, elem_classes="history-button",style="background: none; border: none; color: blue; text-decoration: underline; cursor: pointer;")button.click(fn=on_link_click,inputs=[gr.State(first_user_input)],  # 使用 State 傳遞按鈕的文本outputs=[history_output])buttons.append(button)return history_content, back_to_main_btn, change_btn, history_output, search_box, time_period_dropdown

Login.py

import gradio as gr
import requests
from config import BASE_URL
import global_vars# 后端 API 地址# 發送驗證碼
def send_verification_code(phone_email):"""發送驗證碼到后端"""if len(phone_email) != 11 or not phone_email.isdigit():return "手機號格式錯誤,請輸入11位數字!"url = f"{BASE_URL}/send_verification_code"response = requests.post(url, json={"phone_email": phone_email})if response.status_code == 200:return response.json().get("message", "驗證碼已發送,請查收!")else:return response.json().get("detail", "沒鏈接呢")# 驗證碼登錄驗證
def login_with_code(phone_email, code):"""驗證碼登錄驗證"""url = f"{BASE_URL}/verify_code_login"response = requests.post(url, json={"phone_email": phone_email, "code": code})if response.status_code == 200:return "登錄成功!"else:return response.json().get("detail", "驗證碼錯誤,請重新輸入!")
# 密碼登錄驗證
'''
返回結果:
{"status": "success","message": "登錄成功!","user_id": 12345
}
{"status": "error","detail": "用戶名或密碼錯誤!"
}
'''
def login_handler(username, password):"""密碼登錄驗證的前端邏輯"""if not username or not password:return "用戶名和密碼不能為空!", False  # 返回錯誤信息和跳轉標志# 打印調試信息(可選)print(f"正在驗證:用戶名={username}, 密碼={password[:4]}")# 調用后端接口進行驗證url = f"{BASE_URL}/login"response = requests.post(url, json={"username": username, "password": password})if response.status_code == 200:data = response.json()if data.get("status") == "success":user_id = data.get("user_id", "")global_vars.user_id_state = usernamereturn "登錄成功!", True  # 返回成功信息和跳轉標志else:return data.get("detail", "登錄失敗!"), False  # 返回錯誤信息和跳轉標志else:return response.json().get("detail", "服務器錯誤!"), False  # 返回錯誤信息和跳轉標志# 用戶注冊
def register_handler(username, password):"""用戶注冊的前端邏輯"""if not username or not password:return "用戶名和密碼不能為空!"# 打印調試信息(可選)print(f"正在注冊:用戶名={username}, 密碼={password[:4]}****")# 調用后端接口進行注冊url = f"{BASE_URL}/register"response = requests.post(url, json={"username": username, "password": password})if response.status_code == 200:return "注冊成功,請登錄!"else:return response.json().get("detail", "注冊失敗!")def forgot_password():return "請聯系管理員重置密碼!"def login_interface():"""登錄界面的封裝函數"""with gr.Column() as login_content:  # 使用 Column 而不是 Blocksgr.Markdown("# logo+名稱", elem_classes="centered-containerL")with gr.Row(elem_classes="gradio-containerL"):# 左側掃碼登錄區域with gr.Column(scale=1,elem_classes="highlight-border"):gr.Markdown("""<h1 style="font-size: 20px; color: #007BFF; text-align: center;">微信掃碼 快速登錄</h1>""")gr.Markdown("""<h1 style="font-size: 20px; color: #000000; text-align: center;">——————————————</h1>""")image = gr.Image("WX.jpg", elem_id="custom-image", height=300)# 驗證碼登錄選項卡with gr.TabItem("驗證碼登錄",elem_classes="highlight-border"):phone_email = gr.Textbox(label="手機號/郵箱",placeholder="請輸入手機號或郵箱",info="區分大小寫,不含空格",elem_classes="input-field",)code = gr.Textbox(show_label=False,placeholder="輸入驗證碼",)send_btn = gr.Button("發送驗證碼",elem_classes="send-btnL",)send_status = gr.Markdown(value="", elem_classes="status-text")login_code_button = gr.Button("登錄", variant="primary", elem_id="login-confirm-btn")login_result = gr.Textbox(label="操作結果", interactive=False)# 密碼登錄選項卡with gr.TabItem("密碼登錄",elem_classes="highlight-border"):username = gr.Textbox(show_label=False,placeholder="用戶名",info="區分大小寫,不含空格",elem_classes="input-field",)password = gr.Textbox(show_label=False,type="password",placeholder="輸入密碼",)forgot_btn = gr.Button("忘記密碼?")login_pwd_button = gr.Button("登錄", variant="primary", elem_id="login-confirm-btn")register_button = gr.Button("注冊",  elem_id="register-btn")login_result = gr.Textbox(label="操作結果", interactive=False) # interactive=False 無法編輯should_redirect = gr.State(False)send_btn.click(send_verification_code,inputs=[phone_email],outputs=[send_status])login_code_button.click(login_with_code,inputs=[phone_email, code],outputs=[login_result])# 綁定登錄按鈕事件login_pwd_button.click(login_handler,inputs=[username, password],outputs=[login_result, should_redirect])forgot_btn.click(forgot_password,inputs=[],outputs=[login_result])register_button.click(register_handler,inputs=[username, password],outputs=[login_result])return login_content, register_button, login_code_button, login_pwd_button,should_redirect,login_result,username

Main.py

import gradio as gr
import requestsfrom global_vars import user_id_state
from config import BASE_URL
from copy import deepcopy'''
后端返回的類型:正確{"status": "success","response": "你好!有什么可以幫您的嗎?","chat_history": [{"role": "user", "content": "你好"},{"role": "assistant", "content": "你好!有什么可以幫您的嗎?"}{"role": "user", "content": "你好"},{"role": "assistant", "content": "你好!有什么可以幫您的嗎?"}]
錯誤:{"status": "error","detail": "無法處理請求,請稍后再試!"}
'''#之前的
# def process_user_input(user_input, chat_history):
#     """處理用戶輸入并更新聊天記錄。"""
#     try:
#         #chat_history 發送格式 List[Dict[str, str]]
#         formatted_chat_history = chat_history if isinstance(chat_history, list) else []
#         formatted_chat_history.append({"role": "user", "content": user_input})
#
#         # 構造請求數據
#         payload = {
#             "user_input": user_input,
#             "chat_history": formatted_chat_history  # 發送 JSON 格式正確的 chat_history
#         }
#         # 發送 POST 請求到后端 API
#         response = requests.post(f"{BASE_URL}/chat", json=payload)
#
#         # 檢查響應狀態碼
#         if response.status_code == 200:
#             data = response.json()
#             if data.get("status") == "success":
#                 bot_response = data.get("response", "后端未返回有效數據")
#                 #返回 Gradio Chatbot 需要 List[Dict[str, str]]
#                 chat_history.append({"role": "assistant", "content": bot_response})
#             else:
#                 error_message = data.get("detail", "后端返回無效數據")
#                 chat_history.append({"role": "assistant", "content": error_message})
#         else:
#             error_message = f"后端錯誤: {response.status_code}"
#             chat_history.append({"role": "assistant", "content": error_message})
#     except Exception as e:
#         error_message = f"通信失敗: {str(e)}"
#         chat_history.append({"role": "assistant", "content": error_message})
#
#     return "", chat_historydef process_user_input(user_input, chat_history):"""處理用戶輸入并更新聊天記錄。"""try:# 構造請求數據payload = {"user_input": user_input,"chat_history": chat_history  # 發送 JSON 格式正確的 chat_history}# 發送 POST 請求到后端 APIresponse = requests.post(f"{BASE_URL}/chat", json=payload)# 檢查響應狀態碼if response.status_code == 200:data = response.json()if data.get("status") == "success":bot_response = data.get("response", "后端未返回有效數據")chat_history.append({"role": "assistant", "content": bot_response})else:error_message = data.get("detail", "后端返回無效數據")chat_history.append({"role": "assistant", "content": error_message})else:error_message = f"后端錯誤: {response.status_code}"chat_history.append({"role": "assistant", "content": error_message})except Exception as e:error_message = f"通信失敗: {str(e)}"chat_history.append({"role": "assistant", "content": error_message})return "", chat_historydef toggle_sidebar(expand):"""切換側邊欄處理"""if expand:return gr.update(visible=True), gr.update(visible=False)else:return gr.update(visible=False), gr.update(visible=True)def toggle_qrcode(show_qrcode):"""顯示或隱藏二維碼窗口。"""return gr.update(visible=show_qrcode)def fill_input(text, user_input):return textdef update_and_scroll(user_input, chat_history):"""更新聊天記錄并模擬滾動到底部"""# Step 1: 立即更新用戶輸入到聊天記錄中if not chat_history:chat_history = []chat_history.append({"role": "user", "content": user_input})# 返回清空的輸入框和更新后的聊天記錄(顯示用戶輸入)yield "", chat_history# Step 2: 異步處理后端請求_, updated_chat_history = process_user_input(user_input, chat_history)# 返回最終結果(包含后端響應)yield "", updated_chat_history# def update_and_scroll(user_input, chat_history):
#
#     # Step 1: 立即更新用戶輸入到聊天記錄中
#     if not chat_history:
#         chat_history = []
#     chat_history.append({"role": "user", "content": user_input})
#
#     # 返回清空的輸入框和更新后的聊天記錄(顯示用戶輸入)
#     yield "", deepcopy(chat_history)
#
#     # Step 2: 異步處理后端請求
#     _, updated_chat_history = process_user_input(user_input, deepcopy(chat_history))
#
#     # 返回最終結果(包含后端響應)
#     yield "", deepcopy(updated_chat_history)#調用后端
def save_and_clear_conversation(chat_history,user_id_state):"""新建對話功能事件1.保存當前對話記錄到后端,并清空聊天記錄。:param chat_history: 當前的聊天記錄(List[Dict[str, str]] 格式):param user_id_state: 用戶 ID(用于標識用戶):return: 清空后的聊天記錄"""try:# 將 chat_history 轉換為后端所需的格式formatted_conversation = []for entry in chat_history:role = entry.get("role", "")content = entry.get("content", "")if role == "user":formatted_conversation.append({"user_input": content})elif role == "assistant":formatted_conversation.append({"bot_response": content})# 構造請求數據payload = {"user_id": user_id_state,"conversation": formatted_conversation}# 發送 POST 請求到后端 APIresponse = requests.post(f"{BASE_URL}/save_conversation", json=payload)# 檢查響應狀態碼if response.status_code == 200:print("對話記錄保存成功")else:print(f"后端錯誤: {response.status_code}")except Exception as e:print(f"通信失敗: {str(e)}")# 清空聊天記錄return []# # 定義全局變量用于存儲聊天記錄狀態
# chat_history_state = gr.State([])
def main_interface(user_id_state):"""主界面的封裝函數"""with gr.Column() as register_content:# 插入自定義 CSSgr.HTML("""<style>.custom-button {width:50px;height: 40px;font-size: 14px;}/* 自定義 Chatbot 樣式 */.chatbot-wrap {max-height: 1000px; /* 設置最大高度 */overflow-y: auto; /* 啟用垂直滾動條 */border: 1px solid #ccc; /* 添加邊框 */padding: 10px; /* 內邊距 */border-radius: 8px; /* 圓角 */}/* 二維碼窗口樣式 */.qrcode-window {position: fixed; /* 固定定位 */top: 20px;right: 20px;width: 250px;background-color: white;border: 1px solid #ccc;padding: 15px;box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);z-index: 1000; /* 確保在最上層 */}.qrcode-window h3 {margin-top: 0;}.qrcode-close-btn {float: right;cursor: pointer;color: red;}</style>""")with gr.Row(elem_classes="highlight-border"):with gr.Row(elem_classes="highlight-border"):# toggle_button = gr.Button("切換側邊欄", elem_classes="send-btn2")# 添加返回按鈕back_tologin_btn = gr.Button("退出", elem_classes="send-btn4", elem_id="back-to-login-btn")with gr.Column(min_width=200, scale=1, visible=True,elem_classes="highlight-border") as sidebar_expanded:# gr.Markdown("側邊欄展開")gr.Image(value="panda.jpg", elem_classes="highlight-border")new_chat_button = gr.Button("開啟新對話", elem_classes="send-btn")history_btn = gr.Button("歷史記錄", elem_classes="send-btn")more_features_button = gr.Button("更多功能", elem_classes="send-btn")favorites_button = gr.Button("收藏對話", elem_classes="send-btn")settings_button = gr.Button("個人設置", elem_classes="send-btn")mobile_download_button = gr.Button("手機端下載", elem_classes="send-btn")desktop_download_button = gr.Button("電腦端下載", elem_classes="send-btn")with gr.Column(min_width=100, scale=1, visible=False,elem_classes="highlight-border") as sidebar_collapsed:gr.Markdown("縮小")gr.Image(value="panda.jpg" ,elem_classes="highlight-border")gr.Button("新對話", elem_classes="send-btn")# 添加跳轉按鈕history_button = gr.Button("查看歷史記錄", elem_id="history-btn")gr.Button("更多", elem_classes="send-btn")gr.Button("收藏", elem_classes="send-btn")gr.Button("設置", elem_classes="send-btn")gr.Button("手載", elem_classes="send-btn")gr.Button("電載", elem_classes="send-btn")# toggle_button.click(lambda: toggle_sidebar(True), outputs=[sidebar_expanded, sidebar_collapsed])# toggle_button.click(lambda: toggle_sidebar(False), outputs=[sidebar_expanded, sidebar_collapsed])with gr.Column(scale=4,elem_classes="highlight-border"):gr.Markdown("""<h1 style="font-size: 60px; color: #007BFF; text-align: center;">我是小希,很高興與您交流</h1><p style="font-size: 24px; color: #333; text-align: center;">我可以幫你寫代碼、讀文件、寫作各種創意內容,請把你的任務交給我吧~</p>""")# 聊天歷史記錄組件chat_history = gr.Chatbot(label="聊天框", elem_classes="chatbot-wrap",type="messages")user_input = gr.Textbox(label="請輸入您的問題", placeholder="宇宙超強大腦小希為您解憂消愁,擺脫一切煩惱!")with gr.Row(elem_classes="highlight-border"):# 左側占位(可留空)gr.HTML("")gr.HTML("")gr.HTML("")gr.HTML("")gr.HTML("")submit_button = gr.Button("提交", elem_classes="send-btn3")# 創建一個隱藏的文本框用于存儲問題hidden_textbox = gr.Textbox(visible=False)# 使用 gr.Row 將三個按鈕放在一行展示with gr.Row(elem_classes="highlight-border"):weather_question = gr.Button("貸款流程是什么?", elem_classes="send-btn2")guide_question = gr.Button("貸款材料需要什么",elem_classes="send-btn2")click_answer = gr.Button("點擊就可解答",elem_classes="send-btn2")weather_question.click(lambda: fill_input("貸款流程是什么?", hidden_textbox), outputs=hidden_textbox)guide_question.click(lambda: fill_input("貸款材料需要什么", hidden_textbox), outputs=hidden_textbox)click_answer.click(lambda: fill_input("點擊就可解答", hidden_textbox), outputs=hidden_textbox)# 將隱藏文本框的內容復制到用戶輸入框hidden_textbox.change(lambda x: x, inputs=hidden_textbox, outputs=user_input)# 手機端下載二維碼窗口(懸浮窗口)with gr.Column(visible=False, elem_classes="highlight-border") as qrcode_window:gr.Markdown("### 掃碼下載")close_button = gr.Button("×", elem_classes="qrcode-close-btn")gr.Image(value="WX.jpg", label="手機端下載二維碼")# 按鈕綁定事件mobile_download_button.click(lambda: toggle_qrcode(True), outputs=[qrcode_window])close_button.click(lambda: toggle_qrcode(False), outputs=[qrcode_window])# desktop_download_button.click(lambda: show_page("desktop_download"), outputs=[register_content, desktop_download_page])# back_to_home_button.click(lambda: show_page("home"), outputs=[register_content, desktop_download_page])# 其他按鈕事件# 在 main_interface 函數中綁定 new_chat_button 的事件new_chat_button.click(save_and_clear_conversation,inputs=[chat_history,user_id_state],outputs=[chat_history])history_button.click()history_btn.click()more_features_button.click()favorites_button.click()settings_button.click()# 將按鈕和 Textbox 的 Enter 鍵綁定到同一個回調函數submit_button.click(update_and_scroll,inputs=[user_input, chat_history],outputs=[user_input, chat_history])# 監聽 Enter 鍵事件user_input.submit(update_and_scroll,inputs=[user_input, chat_history],outputs=[user_input, chat_history])return register_content, history_btn, history_button, back_tologin_btn

Register.py

import gradio as gr
import requests
from config import BASE_URLdef send_verification_code(phone_number, status_text):"""調用后端發送驗證碼接口"""if not phone_number.isdigit() or len(phone_number) != 11:return gr.update(value="?? 手機號格式不正確"), status_texttry:# 模擬調用后端發送驗證碼接口response = requests.post(f"{BASE_URL}/send-code",  # 使用 BACKEND_URLjson={"phone_number": phone_number})if response.status_code == 200:# 返回成功消息return gr.update(value=f"? 驗證碼已發送至 {phone_number}"), status_textelse:# 返回錯誤消息error_message = response.json().get("message", "未知錯誤")return gr.update(value=f"? 發送失敗:{error_message}"), status_textexcept Exception as e:# 捕獲網絡錯誤return gr.update(value=f"? 網絡錯誤:{str(e)}"), status_textdef register_user(phone, code, username, password, confirm_pwd, status_text):"""調用后端注冊接口"""# 驗證密碼一致性if password != confirm_pwd:return gr.update(value="?? 兩次輸入的密碼不一致"), status_texttry:# 調用后端注冊接口response = requests.post(f"{BASE_URL}/register",  # 使用 BACKEND_URLjson={# "phone_number": phone,# "code": code,"username": username,"password": password,# "confirm_password": confirm_pwd})if response.status_code == 200:# 注冊成功return gr.update(value="🎉 注冊成功!"), status_textelse:# 注冊失敗,返回錯誤信息error_message = response.json().get("message", "注冊失敗")return gr.update(value=f"? {error_message}"), status_textexcept Exception as e:# 捕獲網絡錯誤return gr.update(value=f"? 網絡錯誤:{str(e)}"), status_textdef register_interface():"""注冊界面的封裝函數"""with gr.Column() as register_content:  # 移除 css 參數gr.Markdown("# 用戶注冊", elem_classes="centered-containerR")with gr.Column(elem_classes="gradio-containerR"):with gr.Row(elem_classes="highlight-border"):gr.Button("手機號注冊", variant="secondary")with gr.Row(elem_classes="highlight-border"):username = gr.Textbox(label="用戶名(必填)", placeholder="請輸入用戶名")with gr.Row(elem_classes="highlight-border"):phone = gr.Textbox(label="+86(中國) 手機號", placeholder="請輸入手機號")password = gr.Textbox(label="密碼(必填)", type="password", placeholder="請輸入密碼")with gr.Row(elem_classes="highlight-border"):code_input = gr.Textbox(label="短信驗證碼", placeholder="請輸入收到的6位驗證碼")confirm_password = gr.Textbox(label="確認密碼(必填)", type="password", placeholder="請確認密碼")with gr.Row(elem_classes="highlight-border"):send_code_btn = gr.Button("獲取驗證碼", variant="primary", elem_classes="send-btn3")with gr.Row(elem_classes="highlight-border"):gr.Markdown("已有賬號,[去登錄](#) 返回到初始界面", elem_classes="centered-containerR")register_btn = gr.Button("立即注冊", variant="success")# 添加返回按鈕back_to_login_btn = gr.Button("返回登錄", elem_id="back-to-login-btn")status_text = gr.Textbox(label="狀態提示",interactive=False)# # 綁定發送驗證碼事件# send_code_btn.click(#     send_verification_code,#     inputs=[phone, status_text],#     outputs=[status_text]# )# 綁定注冊事件register_btn.click(register_user,inputs=[phone, code_input, username, password, confirm_password, status_text],outputs=[status_text])return register_content, send_code_btn, register_btn, back_to_login_btn

App.py

# 主文件,負責整合所有界面
import gradio as gr
from Login import login_interface
from Register import register_interface
from Main import main_interface
from History import history_interface,fetch_history
import global_vars'''
通用返回值設計形式:
{"status": "success/error",  // 請求狀態"message": "操作成功的描述信息",  // 成功時的提示信息"data": {                   // 成功時的附加數據(可選)"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...","user_id": 12345},"detail": "失敗時的具體原因"  // 錯誤時的詳細信息
}
'''
# 定義全局變量用于跟蹤當前界面
current_page = "login"# 定義跳轉邏輯
def navigate_to_register():"""從登錄界面跳轉到注冊界面"""return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)def navigate_to_login():"""從注冊界面跳轉到登錄界面"""return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)def navigate_to_main2():"""從登錄界面跳轉到主界面"""return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)def navigate_to_main1(login_result, should_redirect):"""根據登錄結果決定是否跳轉到主界面。"""if should_redirect:return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)else:return gr.update(), gr.update()  # 不跳轉,保持當前界面def navigate_to_history():"""從主界面跳轉到歷史記錄界面"""# print(global_vars.user_id_state)# # update_history(user_id_state)# user_id_state.value = global_vars.user_id_state# # history_content,back_to_main_btn,change_btn,history_output, search_box= history_interface(user_id_state)# # print(global_vars.user_id_state)# print(f"history_context: {history_content}")# print(f"history_output: {history_output}")# # history_context = "123456"return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=True),gr.update(value=history_output.value)def navigate_to_main_from_history():"""從歷史記錄界面跳轉到主界面"""return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)def updateA_history(user_id_state):history_data = fetch_history(user_id_state)return gr.update(value=history_data)# 定義全局 CSS 樣式
css = """
.gradio-containerR{    /* Register的相關 */max-width: 50%;margin: 40px auto;padding: 40px;border: 1px solid #ccc;border-radius: 8px;box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.centered-containerR{text-align: center;
}
.input-rowR{display: flex;justify-content: space-between;margin-bottom: 16px;
}
.input-rowR > * {width: 48%;
}
.button-rowR {display: flex;justify-content: center;margin-top: 16px;
}.gradio-containerL { /*登錄界面*/width: 840px; /* 固定寬度 */height: 550px; /* 固定高度 */margin: auto; /* 水平居中 */display: flex; /* 使用 Flexbox 實現內容居中 */align-items: center; /* 垂直居中 */justify-content: center; /* 水平居中 */border: 6px solid #e0e0e0; /* 外邊框 */border-radius: 16px; /* 圓角 */box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); /* 陰影效果 */background: linear-gradient(145deg, #ffffff, #f8f9fa); /* 漸變背景 */
}.verification-boxL {height: 60px; margin-bottom: 15px;}
.send-btnL {width: 100%; background: #4CAF50; color: white; border: none; padding: 12px 0;}
#custom-galleryL {background-color: #f0feee !important; border-radius: 8px;}/* 新增樣式 */
.button-primary {background-color: #4CAF50 !important;color: white !important;border: none !important;padding: 10px 20px !important;font-size: 16px !important;cursor: pointer !important;
}.button-secondar {background-color: #f0f0f0 !important;color: black !important;border: none !important;padding: 10px 20px !important;font-size: 16px !important;cursor: pointer !important;
}.send-btn {width: 80%;height: 50%; /* 固定高度 */background-color: 	#F4A460; /*  */color: black; /* 白色文字 */font-size: 16px;border: none;border-radius: 5px;cursor: pointer;/* 居中對齊 */display: block; /* 塊級元素:將按鈕設置為塊級元素,以便可以使用 margin 屬性進行居中 */margin-left: 30px; /* 左外邊距自動:水平方向左對齊 */margin-right: 10px; /* 右外邊距自動:水平方向右對齊 */margin-top: 5px; /* 上外邊距:設置按鈕距離上方 10px 的間距(可選) */margin-bottom: 5px; /* 下外邊距:設置按鈕距離下方 10px 的間距(可選) */
}
.send-btn2 {    # width: 50px;# height: 50px; /* 較小的高度 */background-color: #8B003; /* 木色 */color: white;font-size: 18px;border: none;border-radius: 5px;cursor: pointer;/* 居中對齊 */display: block; /* 塊級元素:將按鈕設置為塊級元素,以便可以使用 margin 屬性進行居中 */margin-left: 80px; /* 左外邊距自動:水平方向左對齊 */margin-right: 80px; /* 右外邊距自動:水平方向右對齊 */margin-top: 5px; /* 上外邊距:設置按鈕距離上方 10px 的間距(可選) */margin-bottom: 5px; /* 下外邊距:設置按鈕距離下方 10px 的間距(可選) */}.send-btn3 {width: 50px; /* 自動寬度:按鈕寬度根據內容自動調整 */height: 50px; /* 中等高度:設置按鈕的固定高度為 35px */background-color:  #4CAF50; /* 藍色背景:設置按鈕的背景顏色為藍色 (#2196F3) */color: white; /* 白色文字:設置按鈕的文字顏色為白色 */font-size: 15px; /* 字體大小:設置按鈕文字的字體大小為 15px */border: none; /* 無邊框:移除按鈕的默認邊框 */border-radius: 20px; /* 圓角:設置按鈕的圓角半徑為 5px,使其看起來更柔和 */cursor: pointer; /* 鼠標懸停時顯示手型光標:提示用戶該按鈕是可點擊的 *//* 居中對齊 */display: block; /* 塊級元素:將按鈕設置為塊級元素,以便可以使用 margin 屬性進行居中 */margin-left: auto; /* 左外邊距自動:將按鈕推到右邊 */margin-right: 0; /* 右外邊距為 0:確保按鈕緊貼容器右邊 */}
.send-btn4 {width: 50px; /* 自動寬度:按鈕寬度根據內容自動調整 */height: 50px; /* 中等高度:設置按鈕的固定高度為 35px */background-color: #808080; /* 藍色背景:設置按鈕的背景顏色為藍色 (#2196F3) */color: white; /* 白色文字:設置按鈕的文字顏色為白色 */font-size: 15px; /* 字體大小:設置按鈕文字的字體大小為 15px */border: none; /* 無邊框:移除按鈕的默認邊框 */border-radius: 5px; /* 圓角:設置按鈕的圓角半徑為 5px,使其看起來更柔和 */cursor: pointer; /* 鼠標懸停時顯示手型光標:提示用戶該按鈕是可點擊的 *//* 居中對齊 */display: block; /* 塊級元素:將按鈕設置為塊級元素,以便可以使用 margin 屬性進行居中 */margin-left: 0; /* 左外邊距自動:水平方向左對齊 */margin-right: 0; /* 右外邊距自動:水平方向右對齊 */margin-top: 0; /* 上外邊距:設置按鈕距離上方 10px 的間距(可選) */margin-bottom: 0; /* 下外邊距:設置按鈕距離下方 10px 的間距(可選) */}.highlight-border {border: 2px solid #007BFF; /* 藍色邊框 */padding: 10px;             /* 內邊距 */margin: 5px;               /* 外邊距 */border-radius: 5px;        /* 圓角 */}"""# 創建主應用
with gr.Blocks(title="知識庫問答系統", css=css) as demo:# 創建一個標題gr.Markdown("# 知識庫問答系統")Login_state = 0# 登錄界面with gr.Row(visible=True) as login_row:login_content, register_button, login_code_button, login_pwd_button ,should_redirect,login_result,username = login_interface()# 定義全局變量用于存儲用戶信息user_id_state = usernameprint(f"user_id_state: {global_vars.user_id_state}")# 注冊界面with gr.Row(visible=False) as register_row:register_content, send_code_btn, register_btn,back_to_login_btn = register_interface()# 主界面with gr.Row(visible=False) as main_row:main_content,history_btn,history_button,back_tologin_btn= main_interface(user_id_state)# 歷史記錄界面with gr.Row(visible=False) as history_row:history_content,back_to_main_btn,change_btn,history_output,search_box,time_period_dropdown  = history_interface(user_id_state)from History import update_historysearch_box.change(lambda search_query, user_id: update_history(user_id, search_query),inputs=[search_box, user_id_state],outputs=[history_output])# 綁定按鈕事件register_button.click(navigate_to_register,inputs=[],outputs=[login_row, register_row, main_row, history_row])back_to_login_btn.click(navigate_to_login,inputs=[],outputs=[login_row, register_row, main_row, history_row])back_tologin_btn.click(navigate_to_login,inputs=[],outputs=[login_row, register_row, main_row, history_row])# 密碼登錄跳轉綁定login_pwd_button.click(navigate_to_main1,inputs=[login_result, should_redirect],outputs=[login_row, register_row, main_row, history_row]  # 假設 main_row 是主界面,login_row 是登錄界面)# 驗證碼跳轉綁定login_code_button.click(navigate_to_main2,inputs=[],outputs=[login_row, register_row, main_row, history_row])history_btn.click(navigate_to_history,inputs=[],outputs=[login_row, register_row, main_row, history_row, history_output]).then(fn=updateA_history,inputs=[user_id_state],outputs=[history_output])# 跳轉歷史記錄綁定history_button.click(navigate_to_history,inputs=[],outputs=[login_row, register_row, main_row, history_row, history_output])back_to_main_btn.click(navigate_to_main_from_history,inputs=[],outputs=[login_row, register_row, main_row, history_row])# 監聽時間范圍選擇器和搜索框的變化,動態更新歷史記錄def on_change(user_id, time_period, search_query):return update_history(user_id, time_period, search_query)time_period_dropdown.change(on_change,inputs=[user_id_state, time_period_dropdown, search_box],outputs=[history_output])search_box.change(on_change,inputs=[user_id_state, time_period_dropdown, search_box],outputs=[history_output])# change_btn.click(#     change_function,#     inputs=[user_id_state],#     outputs=[login_row, register_row, main_row, history_row]# )# 啟動應用
demo.launch(server_name="0.0.0.0")

五、效果展示

這是前端實現完與后端進行交互之后的結果,相應的注冊信息,聊天記錄都是存在數據庫中。

  1. 首先進行注冊: 這里手機號功能后端暫未實現,只用輸入用戶名和密碼。點擊注冊,會與后端進行交互存儲用戶信息,返回一個結果,前端根據返回結果進行相應的提示(注冊成功!)
    在這里插入圖片描述

  2. 登錄過程,驗證碼、手機登錄后端暫未實現,暫時支持密碼登錄(輸入注冊的用戶名和密碼),系統根據信息會給一個返回值,根據結果顯示狀態(登陸成功!):
    在這里插入圖片描述

  3. 歷史記錄,這塊是為了記錄我們歷史對話過程,剛注冊的賬號沒有對話記錄:

在這里插入圖片描述

  1. 一輪對話:當輸入問題并且有回復就說明我們與后端的交互是沒有問題的,后端處理請求是基于數據庫回答,在數據庫中沒有的情況下基于千問大模型接口來進行回答。
    第一輪對話首個問題: 貸款材料需要什么
    在這里插入圖片描述
    多輪對話:在第一輪對話之后,提問回答過程中的相關內容,看它是否有分析檢索的能力(這部分內容是數據庫中沒有的)
    在這里插入圖片描述
  2. 驗證基于數據庫與大模型的回答

以下問題是數據庫中的問題,看是否可以根據數據庫中的內容直接回答。
這是第二次會話: 你好,銀行貸款的五級分類

在這里插入圖片描述

同樣的問題,這是在千問中請求的結果,對比來看,回答的形式不同。

在這里插入圖片描述

再查看數據庫中的內容,這部分是完全直接輸出給用戶請求了。說明首先還是基于數據庫進行回復的。

在這里插入圖片描述

下圖為再次咨詢回答中某一條相關信息的具體內容的時候,他回復的在數據庫中并沒有,是根據學習數據庫中的內容以及借助千問大模型給出的回復。

在這里插入圖片描述

再重新進行一輪對話,我們問問數據庫中沒有的,第三輪對話內容:我有個數學難題不會解決,1+2等于,是可以正常輸出的,此時就是調用千問接口進行回復的。

在這里插入圖片描述
6. 查看歷史會話記錄

根據我們前面三次會話的第一問作為超鏈接顯示某個會話,如圖所示。

在這里插入圖片描述
前端基本實現了,與剛開始設計的界面多少有差距,但是整體交互邏輯沒問題。部分內容沒有更新到位,希望這個筆記能更好的促進我們使用gradio,也期待寶子們的實踐成果。
后端的內容請學習以下文章內容:
ollama+qwen2.5+nomic本地部署及通過API調用模型示例
使用FastAPI為知識庫問答系統前端提供后端功能接口

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/73617.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/73617.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/73617.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

案例分享|樹莓派媒體播放器,重構商場廣告的“黃金三秒”

研究顯示&#xff0c;與傳統戶外廣告相比&#xff0c;數字戶外廣告在消費者心中的記憶率提高了17%&#xff0c;而動態戶外廣告更是能提升16%的銷售業績&#xff0c;整體廣告效率提升了17%。這一顯著優勢&#xff0c;使得越來越多資源和技術流入數字廣告行業。 戶外裸眼3D廣告 無…

23種設計模式-裝飾器(Decorator)設計模式

裝飾器設計模式 &#x1f6a9;什么是裝飾器設計模式&#xff1f;&#x1f6a9;裝飾器設計模式的特點&#x1f6a9;裝飾器設計模式的結構&#x1f6a9;裝飾器設計模式的優缺點&#x1f6a9;裝飾器設計模式的Java實現&#x1f6a9;代碼總結&#x1f6a9;總結 &#x1f6a9;什么是…

[Vue]事件修飾符

文章目錄 一、語法介紹二、添加代碼三、結果展示四、參考文獻 如有錯誤&#xff0c;請指正&#xff01;&#xff01;&#xff01; 一、語法介紹 1、問題來源 我們在處理網頁時&#xff0c;當點擊按鈕時會觸發對應事件&#xff0c;但是有時并不想觸發該時間&#xff0c…

Go 語言 sync 包使用教程

Go 語言 sync 包使用教程 Go 語言的 sync 包提供了基本的同步原語&#xff0c;用于在并發編程中協調 goroutine 之間的操作。 1. 互斥鎖 (Mutex) 互斥鎖用于保護共享資源&#xff0c;確保同一時間只有一個 goroutine 可以訪問。 特點&#xff1a; 最基本的同步原語&#x…

ubuntu22.04安裝搜狗輸入法保姆教程~

一、添加中文語言支持 1.首先打開設置,找到Language and Region 2.點擊Manage Installed Languages 3.點擊 Install/Remove Languages... 4.選中Chinese (simplified),點擊Apply

docker中間件部署

1.docker安裝 # 1.卸載舊版本 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine# 2.需要的安裝包 yum install -y yum-utils# 3.設置鏡像的倉庫 # 3.1.默認是國外的&#x…

python康復日記-request庫的使用,爬蟲自動化測試

一&#xff0c;request的簡單應用 #1請求地址 URLhttps://example.com/login #2參數表單 form_data {username: admin,password: secret } #3返回的響應對象response response requests.post(URL,dataform_data,timeout5 ) #4處理返回結果&#xff0c;這里直接打印返回網頁的…

強化學習和智能決策:Q-Learning和Deep Q-Learning算法

強化學習(Reinforcement Learning, RL)是機器學習的一個重要分支,它通過智能體(Agent)與環境交互來學習最優決策策略,旨在最大化智能體的長期累積獎勵。Q-Learning和Deep Q-Learning是強化學習中的兩種關鍵算法,它們在智能決策領域發揮著重要作用。 一、強化學習基礎 …

ubuntu22.04 安裝Jitsi meet 開源會議系統,代替騰訊會議

0.安裝 官方安裝教程Self-Hosting Guide - Debian/Ubuntu server | Jitsi Meet 一定要用域名訪問&#xff0c; 一定要用域名訪問&#xff0c; 一定要用域名訪問&#xff0c; 一定要用域名訪問&#xff0c; 域名一定要有ssl證書&#xff0c;域名一定要有ssl證書&#xff0c;域名…

專家管理系統(源碼+文檔+講解+演示)

引言 在知識經濟時代&#xff0c;專家管理系統成為了企業優化知識資源、提升決策效率的重要工具。本文將介紹一款創新的專家管理系統&#xff0c;該系統通過智能化工具&#xff0c;助力企業實現專家資源的高效管理和利用。 平臺概述 專家管理系統采用前后端分離的架構設計&a…

css基礎-選擇器

選擇器進階 子串選擇器 /* 匹配 href 以 "https" 開頭的鏈接 */ a[href^"https"] {color: green; }/* 匹配 href 包含 "example" 的鏈接 */ a[href*"example"] {text-decoration: underline; }/* 匹配 href 以 ".pdf" 結尾…

Spring Boot屬性設置方法及優先級完整說明+表格對比

Spring Boot屬性設置方法及優先級完整說明 官網參考&#xff1a; https://docs.spring.io/spring-boot/3.4-SNAPSHOT/reference/features/external-config.html#features.external-config.files 屬性設置方法優先級順序&#xff08;從高到低&#xff09; 命令行參數&#xf…

上門家政小程序實戰,從0到1解決方案

一、邏輯分析 上門家政小程序主要涉及用戶端和服務端兩大部分。用戶端需要實現服務瀏覽、預約下單、訂單跟蹤等功能&#xff1b;服務端則要處理訂單管理、服務人員管理、數據統計等任務。以下是詳細的功能模塊分析&#xff1a; 用戶注冊與登錄&#xff1a;用戶通過手機號或第三…

LLVM學習-DragonEgg工具

2.2.2 使用DragonEgg和LLVM工具了解編譯流程 如果希望看到前端的運行情況&#xff0c;請使用-S -fplugin-arg-dragonegg-emit-ir標志&#xff0c;該標志將產生以LLVM IR代碼表示的人工可讀文件。 一旦編譯器將程序轉換為IR則停止編譯&#xff0c;并將內存中的表示內容寫入磁盤的…

關于cmd中出現無法識別某某指令的問題

今天來解決以下這個比較常見的問題&#xff0c;安裝各種軟件都可能會發生&#xff0c;一般是安裝時沒勾選注冊環境變量&#xff0c;導致cmd無法識別該指令。例如mysql&#xff0c;git等&#xff0c;一般初學者可能不太清楚。 解決這類問題最主要的是了解環境變量的概念&#x…

ThreadLocal詳解與高頻場景實戰指南

ThreadLocal詳解與高頻場景實戰指南 1. ThreadLocal概述 ThreadLocal是Java提供的線程本地變量機制&#xff0c;用于實現線程級別的數據隔離。每個訪問該變量的線程都會獲得獨立的變量副本&#xff0c;適用于需要避免線程間共享數據的場景。 特點&#xff1a; 線程封閉性&a…

【C++初階】---類和對象(上)

1.類的定義 1.1類的定義格式 ? class為定義類的關鍵字&#xff0c;Data為類的名字&#xff0c;{}中為類的主體&#xff0c;注意類定義結束時后?分號不能省略。類體中內容稱為類的成員&#xff1a;類中的變量稱為類的屬性或成員變量;類中的函數稱為類的?法或者成員函數。 ?…

Rust安裝并配置配置vscode編譯器

一. 下載rustup-init.exe rust下載網址&#xff1a;Getting started - Rust Programming Language 根據系統&#xff0c;選擇適合的exe文件 我選擇的的是右邊64bit的 打開下載的文件 輸入1&#xff0c;回車 二. Visual C 安裝 自動下載安裝vs 等待安裝完畢 三. Rust 安裝…

openGl片段著色器的含義

片段著色器的含義及代碼中的應用說明&#xff1a; 1. 片段著色器的基本概念 片段著色器&#xff08;Fragment Shader&#xff09;是OpenGL著色器管線中的關鍵組件&#xff0c;主要用于計算屏幕空間中每個片段&#xff08;對應像素&#xff09;的最終顏色。它是圖形渲染流程的…

事務的四大特性(ACID)詳解

事務的四大特性&#xff08;ACID&#xff09;詳解 在數據庫管理系統&#xff08;如 MySQL&#xff09;中&#xff0c;事務&#xff08;Transaction&#xff09; 是指一組要么全部執行、要么全部不執行的數據庫操作&#xff0c;通常用于確保數據的完整性和一致性。事務有四大核…