【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.rhkb.cn/news/41583.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中间件漏洞-Tomcat篇

一&#xff1a;CVE-2017-12615 1.搭建服务 cd /www/wwwroot/vulhub-master/tomcat/CVE-2017-12615 docker-compose up -d 2.打开网页 3.在哥斯拉中生成jsp木马并保存为2.jpg 对当前页面进行抓包&#xff0c;修改提交方式为PUT并复制木马 4.在网页中访问我们生成的木马&#…

PHP eval 长度限制绕过与 Webshell 获取

在 PHP 代码中&#xff0c;如果 eval($param); 存在且长度受限&#xff0c;并且过滤了 eval 和 assert&#xff0c;仍然可以通过多种方法绕过限制&#xff0c;获取 Webshell。 源码 <?php $param $_REQUEST[param]; if(strlen($param)<17 && stripos($param,…

31天Python入门——第15天:日志记录

你好&#xff0c;我是安然无虞。 文章目录 日志记录python的日志记录模块创建日志处理程序并配置输出格式将日志内容输出到控制台将日志写入到文件 logging更简单的一种使用方式 日志记录 日志记录是一种重要的应用程序开发和维护技术, 它用于记录应用程序运行时的关键信息和…

特殊行车记录仪DAT视频丢失的恢复方法

行车记录仪是一种常见的车载记录仪&#xff0c;和常见的“小巧玲珑”的行车记录仪不同&#xff0c;一些特种车辆使用的记录仪的外观可以用“笨重”来形容。下边我们来看看特种车载行车记录仪删除文件后的恢复方法。 故障存储: 120GB存储设备/文件系统:exFAT /簇大小:128KB 故…

机器学习——KNN数据均一化

在KNN&#xff08;K-近邻&#xff09;算法中&#xff0c;数据均一化&#xff08;归一化&#xff09;是预处理的关键步骤&#xff0c;用于消除不同特征量纲差异对距离计算的影响。以下是两种常用的归一化操作及其核心要点&#xff1a; 质押 一 、主要思想 1. 最值归一化&#…

Element UI实现表格全选、半选

制作如图所示的表格全选、半选&#xff1a; 父组件 <template><div id"app"><SelectHost :hostArray"hostArray" /></div> </template><script> import SelectHost from ./components/SelectHost.vue export default…

深度学习入门1 基于Python的理论与实现

torch.unsqueeze()将一维数据变为二维数据&#xff0c;torch只能处理二维数据 tensor不能反向&#xff0c;variable可以反向。variable.data.numpy()转换为numpy 第3章 神经网络 实现softmax函数时的注意事项&#xff1a;为防止e的指数运算造成溢出 矩阵的第 0 维是列方向,第…

vue响应式原理剖析

一、什么是响应式? 我们先来看一下响应式意味着什么?我们来看一段代码: m有一个初始化的值,有一段代码使用了这个值; 那么在m有一个新的值时,这段代码可以自动重新执行; let m = 20 console.log(m) console.log(m * 2)m = 40上面的这样一种可以自动响应数据变量的代码机…

UDP数据报套接字编程

1.DatagramSocket API Socket是操作系统中的一个概念 本质上是一种特殊的文件 Socket就属于是把"网卡"这个设备,抽象成文件了 往Socket文件中写数据,就相当于通过网卡发送数据 从Socket文件读数据,就相当于通过网卡接受数据 在Java中就使用DatagramSocket这个类…

逼用户升级Win11,微软开始给Win10限速

随着Windows10的支持时间越来越短&#xff0c;微软也加大了对Win10用户的驱赶力度。 最近&#xff0c;微软官宣了将要在今年6月份降低OneNote for Windows 10的同步速度。软件也将和Windows10在今年的10月14日一同停止支持和维护。 这将影响实时协作和多设备访问。 对OneNote…

NodeJs之http模块

一、概念&#xff1a; 1、协议&#xff1a;双方必须共同遵从的一组约定。 Hypertext Transfer Protocol&#xff1a;HTTP&#xff0c;超文本传输协议 2、请求&#xff1a; ① 请求报文的组成&#xff1a; 请求行请求头空行请求体 ② 请求行&#xff1a;

26考研——图_图的应用(6)

408答疑 文章目录 四、图的应用图的应用考查形式最小生成树最小生成树概念最小生成树的性质最小生成树中某顶点到其他顶点是否具有最短路径的分析构造最小生成树的算法Prim 算法Prim 算法概述Prim 算法的构建思想Prim 算法的步骤Prim 算法的示例Prim 算法的性质 Kruskal 算法Kr…

Photoshop 2025安装包下载及Photoshop 2025详细图文安装教程

文章目录 前言一、Photoshop 2025安装包下载二、Photoshop 2025安装教程1.解压安装包2.运行程序3.修改安装路径4.设安装目录5.开始安装6.等安装完成7.关闭安装向导8.启动软件9.安装完成 前言 无论你是专业设计师&#xff0c;还是初涉图像处理的小白&#xff0c;Photoshop 2025…

MySQL-存储过程

介绍 基本语法 创建 调用 查看 删除 变量 系统变量 查看 设置 用户定义变量 赋值 使用 局部变量 声明 赋值 流程控制 参数 条件结构 IF case 循环结构 while repeat loop 游标 条件处理程序 介绍 举个简单的例子&#xff0c;我们先select某数据&…

debug 笔记:llama 3.2 部署bug 之cutlassF: no kernel found to launch!

1 问题描述 按照官方的写法 import torch from transformers import pipeline import os os.environ["HF_TOKEN"] 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 model_id "meta-llama/Llama-3.2-3B"pipe pipeline("text-generation", modelmode…

《Python实战进阶》No34:卷积神经网络(CNN)图像分类实战

第34集&#xff1a;卷积神经网络&#xff08;CNN&#xff09;图像分类实战 摘要 卷积神经网络&#xff08;CNN&#xff09;是计算机视觉领域的核心技术&#xff0c;特别擅长处理图像分类任务。本集将深入讲解 CNN 的核心组件&#xff08;卷积层、池化层、全连接层&#xff09;…

【银河麒麟系统常识】命令:uname -m(查看系统架构)

命令&#xff1a; uname -m 功能 常用的 Linux/Unix 终端命令&#xff0c;用于显示当前系统的硬件架构&#xff1b; 返回 返回系统的CPU架构类型&#xff0c;用于判断软件兼容性&#xff1b; 输出结果架构说明常见设备x86_64Intel/AMD 64位 CPU主流 PC、服务器aarch64ARM 64位 …

游戏引擎学习第183天

回顾和今天的计划 我对接下来的进展感到非常兴奋。虽然我们可能会遇到一些问题&#xff0c;但昨天我们差不多完成了将所有内容迁移到新的日志系统的工作&#xff0c;我们正在把一些内容整合进来&#xff0c;甚至是之前通过不同方式记录时间戳的旧平台层部分&#xff0c;现在也…

Redisson 实现分布式锁简单解析

目录 Redisson 实现分布式锁业务方法&#xff1a;加锁逻辑LockUtil 工具类锁余额方法&#xff1a;工具类代码枚举代码 RedisUtil 工具类tryLock 方法及重载【分布式锁具体实现】Supplier 函数式接口调用分析 Redisson 实现分布式锁 业务方法&#xff1a; 如图&#xff0c;简单…

鸿蒙Flutter实战:19-Flutter集成高德地图,跳转页面方式

前言 在之前的文章现有Flutter项目支持鸿蒙II中&#xff0c;介绍了如何使用第三方插件&#xff0c;同时给出了非常多的使用案例&#xff0c;如 flutter_inappwebview&#xff0c;video_player, image_picker 等&#xff0c;本文将开始介绍如何集成高德地图。 整体方案 通过 …