如何从零开始搭建你的专属本地知识库问答系统

在数字化时代,管理和查询大量本地资料成为了一项挑战。为此,我们开发了一款智能知识库助手,旨在帮助用户高效地管理和查询本地文件中的信息。这款应用集成了先进的自然语言处理技术和向量数据库,提供了一个用户友好的图形界面,让知识管理变得简单而高效。

功能亮点:

· 一键选择文件夹:用户可以通过简单的点击操作,选择包含重要资料的本地文件夹。无论是PDF、Word文档还是纯文本文件,都能轻松加载。

· 智能问答系统:基于大模型的强大能力,用户可以输入自然语言问题,系统将自动从知识库中检索相关信息并生成准确的回答。无论是技术文档、研究报告还是会议纪要,都能快速找到所需内容。

· 实时聊天记录:所有的问答记录都会被保存,方便用户随时回顾和引用。同时,系统支持流式输出,让用户在等待回答时也能看到逐步生成的结果。

· 多种模型选项:用户可以根据需要选择不同的大模型,如ERNIE-Speed-128K、gemma2-9b-it等,以满足不同的性能和精度需求(模型可以根据相关网站自行选择)。

· 配置灵活:所有设置都可以通过配置文件进行管理,方便用户调整和优化系统性能。

无论是企业内部的知识管理,还是个人学习资料的整理,这款智能知识库助手都是您的得力助手。立即体验,让您的知识管理更加高效便捷!

一、代码介绍

1. 导入模块

import tkinter as tk``from tkinter import filedialog, scrolledtext, ttk, messagebox``import os``import json``import threading``import time
  • tkinter:用于创建图形用户界面。

  • filedialog:用于打开文件选择对话框。

  • scrolledtext:用于创建带有滚动条的多行文本框。

  • ttk:用于创建更现代的GUI组件。

  • messagebox:用于显示消息框。

  • os:用于文件和目录操作。

  • json:用于读写JSON文件。

  • threading:用于多线程操作,防止UI阻塞。

  • time:用于模拟流式输出的延迟。

2. 全局变量

CONFIG_FILE = "config.json"``ChatHistory_path = 'ChatHistory.txt'``vectorstort = None
  • CONFIG_FILE:配置文件的路径,用于保存选择的文件夹路径和模型选项。

  • ChatHistory_path:聊天记录文件的路径。

  • vectorstort:全局变量,用于存储向量存储对象。

3. InputWindow

3.1初始化方法
def __init__(self, master):`    `self.master = master`    `self.master.title("选择本地知识库")`    `self.master.configure(bg="#e6f7ff")``   `    `screen_width = master.winfo_screenwidth()`    `screen_height = master.winfo_screenheight()``   `    `x = (screen_width // 2) - (400 // 2)`    `y = (screen_height // 2) - (250 // 2)``   `    `master.geometry(f"400x250+{x}+{y}")``   `    `frame = tk.Frame(master, bg="#e6f7ff")`    `frame.pack(expand=True, fill=tk.BOTH)``   `    `top_frame = tk.Frame(frame)`    `top_frame.pack(side=tk.TOP, pady=(10, 0))`    `self.label = tk.Label(top_frame, text="请选择本地知识库(文件夹)", font=('Arial', 14), fg="blue")`    `self.label.pack(side=tk.LEFT, padx=10, pady=10)``   `    `middle_frame = tk.Frame(frame, bg="#e6f7ff")`    `middle_frame.pack(side=tk.TOP, pady=10, fill=tk.X)``   `    `self.entry = tk.Entry(middle_frame, width=40)`    `self.entry.pack(side=tk.LEFT, padx=10, pady=10)``   `    `self.select_button = tk.Button(middle_frame, text="选择文件夹", command=self.select_directory, bg="#e6f7ff", fg="green")`    `self.select_button.pack(side=tk.RIGHT, padx=10, pady=10)``   `    `bottom_frame = tk.Frame(frame, bg="#e6f7ff")`    `bottom_frame.pack(side=tk.BOTTOM, pady=5)``   `    `self.loading_label = tk.Label(bottom_frame, text="等待文件加载", font=('Arial', 12))`    `self.loading_label.pack(pady=10)``   `    `self.submit_button = tk.Button(bottom_frame, text="确        定", command=self.on_submit, width=30, height=2, font=('Arial', 12), bg="green")`    `self.submit_button.pack(pady=10)``   `    `self.directory = None`    `self.submit_sign = None
  • 功能:初始化选择文件夹窗口,设置窗口标题、背景颜色、大小和位置,创建必要的UI组件。

  • 主要组件:

  • label:提示用户选择本地知识库。

  • entry:显示用户选择的文件夹路径。

  • select_button:按钮,触发文件夹选择对话框。

  • loading_label:显示文件加载状态。

  • submit_button:按钮,提交用户选择的文件夹路径。

3.2选择文件夹方法

def select_directory(self):`    `directory = filedialog.askdirectory()`    `if directory:`        `self.entry.delete(0, tk.END)`        `self.entry.insert(0, directory)`        `self.directory = directory
  • 功能:打开文件夹选择对话框,获取用户选择的文件夹路径并显示在输入框中。

3.3提交方法

def on_submit(self):`    `if not self.submit_sign:`        `self.directory = self.entry.get()`        `if self.directory:`            `self.submit_sign = True`            `self.loading_label.config(text="文件加载时间可能较长,请稍后!", fg="blue", font=('Arial', 12))`            `self.master.update_idletasks()``   `            `self.load_files()`        `else:`            `self.submit_sign = None`            `messagebox.showwarning("警告", "请选择一个文件夹!")
  • 功能:验证用户是否选择了文件夹,如果选择了则加载文件并打开聊天窗口,否则显示警告。
3.4加载文件方法
def load_files(self):`    `try:`        `self.save_to_config(self.directory)``   `        `global vectorstort`        `vectorstort = vecstorstrt()``   `        `self.open_chat_window()`    `except Exception as e:`        `messagebox.showerror("错误", str(e))
  • 功能:将选择的文件夹路径保存到配置文件,初始化向量存储,并打开聊天窗口

3.5保存配置方法

def save_to_config(self, directory):`    `config = {'folder_path': directory, 'option': "ERNIE-Speed-128K"}`    `with open(CONFIG_FILE, 'w') as f:`        `json.dump(config, f)
  • 功能:将选择的文件夹路径和默认模型选项保存到配置文件中。

3.6打开聊天窗口方法

def open_chat_window(self):`    `clear_file(ChatHistory_path)``   `    `self.master.destroy()``   `    `root2 = tk.Tk()`    `ChatWindow(root2)`    `root2.mainloop()
  • 功能:清空聊天记录文件,销毁当前窗口,创建并显示聊天窗口。

**4.**ChatWindow(对话窗口)

4.1初始化方法

def __init__(self, root):`    `self.root = root`    `self.root.title("知识库问答")`    `self.root.configure(bg="#e6f7ff")``   `    `screen_width = root.winfo_screenwidth()`    `screen_height = root.winfo_screenheight()``   `    `x = (screen_width // 2) - (800 // 2)`    `y = (screen_height // 2) - (600 // 2)``   `    `root.geometry(f"800x600+{x}+{y}")``   `    `self.main_frame = tk.Frame(root, bg="#e6f7ff")`    `self.main_frame.pack(fill=tk.BOTH, expand=True)``   `    `self.top_frame = tk.Frame(self.main_frame, bg="#e6f7ff")`    `self.top_frame.pack(fill=tk.X, padx=10, pady=5)``   `    `self.option_var = tk.StringVar(value="ERNIE-Speed-128K")`    `self.option_menu = ttk.Combobox(self.top_frame, textvariable=self.option_var, values=["ERNIE-Speed-128K", "gemma2-9b-it", "mixtral-8x7b-32768", "llama-guard-3-8b", "llama3-70b-8192"], state="readonly", width=26)`    `self.option_menu.pack(side=tk.LEFT, padx=10, pady=5)``   `    `self.folder_path_var = tk.StringVar()`    `self.folder_entry = tk.Entry(self.top_frame, textvariable=self.folder_path_var, state='readonly', width=70)`    `self.folder_entry.pack(side=tk.RIGHT, padx=5, pady=5)``   `    `self.folder_button = tk.Label(self.top_frame, text="知识库:")`    `self.folder_button.pack(side=tk.LEFT)``   `    `self.chat_area = scrolledtext.ScrolledText(self.main_frame, wrap=tk.WORD, font=("Helvetica", 12), bg="#ffffff", fg="#000000")`    `self.chat_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)`    `self.chat_area.tag_configure('send', justify='left', lmargin1=74, lmargin2=74)`    `self.chat_area.tag_configure('response', justify='left', lmargin1=74, lmargin2=74)`    `self.chat_area.tag_configure('send1', justify='left', foreground='blue', background='#d1e7dd')`    `self.chat_area.tag_configure('response1', justify='left', foreground='green', background='#e0f7fa')`    `self.chat_area.tag_configure('sel', background='green')`    `self.chat_area.config(state=tk.NORMAL)`    `self.chat_area.config(state=tk.DISABLED)``   `    `self.input_frame = tk.Frame(self.main_frame, bg="#e6f7ff")`    `self.input_frame.pack(fill=tk.X, padx=10, pady=10)``   `    `self.input_box = tk.Entry(self.input_frame, font=("Helvetica", 12))`    `self.input_box.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5, pady=5)`    `self.input_box.bind("<Return>", self.send_message)``   `    `self.send_button = tk.Button(self.input_frame, text="发送", command=self.send_message, bg="#007acc", fg="white", font=("Helvetica", 12, "bold"))`    `self.send_button.pack(side=tk.RIGHT, padx=5, pady=5)``   `    `self.load_config()`    `global vectorstort`    `self.vectorstort = vectorstort
  • 功能:初始化聊天窗口,设置窗口标题、背景颜色、大小和位置,创建必要的UI组件。

  • 主要组件:

  • option_menu:下拉菜单,用户可以选择不同的模型选项。

  • folder_entry:显示当前的知识库路径。

  • chat_area:显示对话内容的文本框,带有滚动条。

  • input_box:用户输入问题的输入框。

  • send_button:发送按钮,触发消息发送。

4.2发送消息方法

def send_message(self, event=None):`    `question = self.input_box.get()`    `if question:`        `self.save_config()`        `self.chat_area.config(state=tk.NORMAL)`        `self.chat_area.insert(tk.END, "用户提问:" + "\n", 'send1')`        `self.chat_area.insert(tk.END, question + "\n", 'send')`        `self.chat_area.config(state=tk.DISABLED)`        `self.input_box.delete(0, tk.END)`        `threading.Thread(target=self.get_response, args=(question,)).start()
  • 功能:获取用户输入的问题,显示在对话区域,并启动新线程获取回答。

4.3获取回答方法

def get_response(self, question):`    `if os.path.exists(CONFIG_FILE):`        `with open(CONFIG_FILE, "r") as f:`            `config = json.load(f)`            `llm = config.get("option", "ERNIE-Speed-128K")`            `vectorstort = self.vectorstort`            `response_generator = my_rag(llm, vectorstort, question)`            `self.chat_area.config(state=tk.NORMAL)`            `self.chat_area.insert(tk.END, "检索回答:" + "\n", 'response1')`            `self.chat_area.config(state=tk.DISABLED)`            `for response_part in response_generator:`                `self.display_message(response_part, partial=True)`                `time.sleep(0.1)`            `self.display_message("")`            `self.chat_area.config(state=tk.DISABLED)
  • 功能:读取配置文件,调用my_rag函数获取回答,并以流式的方式显示在对话区域。

4.4显示消息方法

def display_message(self, message, partial=False):`    `self.chat_area.config(state=tk.NORMAL)`    `if partial:`        `self.chat_area.insert(tk.END, message, 'response')`    `else:`        `self.chat_area.insert(tk.END, message + "\n", 'response')`    `self.chat_area.config(state=tk.DISABLED)`    `self.chat_area.see(tk.END)
  • 功能:将消息显示在对话区域,支持部分显示和完整显示两种模式。

4.5加载配置方法

def load_config(self):`    `if os.path.exists(CONFIG_FILE):`        `with open(CONFIG_FILE, "r") as f:`            `config = json.load(f)`            `folder_path = config.get("folder_path", "")`            `option = config.get("option", "ERNIE-Speed-128K")`            `self.folder_path_var.set(folder_path)`            `self.option_var.set(option)
  • 功能:从配置文件中读取知识库路径和模型选项,并显示在相应的UI组件中。

4.6加载配置方法

def load_config(self):`    `if os.path.exists(CONFIG_FILE):`        `with open(CONFIG_FILE, "r") as f:`            `config = json.load(f)`            `self.folder_path_var.set(config.get("folder_path", ""))`            `self.option_var.set(config.get("option", "ERNIE-Speed-128K"))`            `self.save_config()
  • 功能:从配置文件 config.json 中读取知识库路径和模型选项,并更新相应的UI组件。

4.7保存配置方法

def save_config(self):`    `config = {`        `"folder_path": self.folder_path_var.get(),`        `"option": self.option_var.get()`    `}`    `with open(CONFIG_FILE, "w") as f:`        `json.dump(config, f)
  • 功能:将当前的知识库路径和模型选项保存到 config.json 文件中。

5.大模型检索回答函数

def my_rag(llm, vectorstort, question):`    `from langchain_openai import ChatOpenAI`    `import os`    `api_key = os.getenv("agicto_test_apikey")`    `api_url = "https://api.agicto.cn/v1"`    `llm = ChatOpenAI(model=llm, api_key=api_key, base_url=api_url)``   `    `retriever = vectorstort.as_retriever(`        `search_type="similarity",`        `search_kwargs={"k": 3},`    `)``   `    `from langchain_core.output_parsers import StrOutputParser`    `from langchain_core.prompts import PromptTemplate``   `    `system_prompt = """你是问答任务的助手,仅使用检索到的下上下文和历史聊天记录来回答问题,如果你不知道答案,就说你“不知道”,不要试图编造答案。`                       `内容要全面,并尽可能简洁地回答。`                       `用中文回答问题。`                       `检索到的上下文:{context}`                       `历史聊天记录:{chat_history}`                       `用户提问:{question}`                       `helpful answer:`                    `"""`    `contextualize_q_prompt = PromptTemplate.from_template(system_prompt)``   `    `def process_docs_with_llm(question, chat_history, llm):`        `from langchain_core.output_parsers import StrOutputParser`        `out_parser = StrOutputParser()`        `from langchain_core.prompts import PromptTemplate`        `template = """请使用历史聊天记录的上下文在结合问题生成一个新的问题。`         `这个新问题中的指代词要结合历史聊天记录的上下文替换掉。`         `指代词如:"这",“这个”,“它”,“它们”,“他”,“他们”等。`         `历史聊天记录:{chat_history}`         `问题:{question}`         `helpful answer:`        `"""`        `custom_rag_prompt = PromptTemplate.from_template(template)`        `chain = custom_rag_prompt | llm | out_parser`        `doc = chain.invoke({"question": question, "chat_history": chat_history})`        `return doc``   `    `chat_history = read_ChatHistory(ChatHistory_path, n=10)`    `new_question = process_docs_with_llm(question, chat_history, llm)`    `relevant_docs = retriever.invoke(new_question)`    `rag_chain = contextualize_q_prompt | llm | StrOutputParser()`    `result = rag_chain.invoke({"question": question, "context": relevant_docs, "chat_history": chat_history})`    `save_user_message(ChatHistory_path, question)`    `save_ai_message(ChatHistory_path, result)``   `    `for i in range(0, len(result), 10):`        `yield result[i:i + 10]
  • 功能:使用大模型和向量数据库生成回答

  • 步骤:

  • 配置大模型 ChatOpenAI

  • 创建向量数据库的检索器。

  • 定义系统提示和上下文提示。

  • 定义 process_docs_with_llm 函数,用于生成新的问题。

  • 读取历史聊天记录。

  • 生成新的问题并检索相关文档。

  • 构建 RAG 链并生成最终回答。

  • 保存用户和AI的消息到聊天记录文件。

  • 模拟流式输出,每次输出10个字符。

6.向量数据库加载

def vecstorstrt():`    `import os`    `if vectorstort is None:`        `if os.path.exists(CONFIG_FILE):`            `with open(CONFIG_FILE, "r") as f:`                `config = json.load(f)`                `directory_to_walk = config.get("folder_path", "")``   `            `def save_file_paths(directory):`                `file_paths = []`                `for root, dirs, files in os.walk(directory):`                    `for file in files:`                        `file_path = os.path.join(root, file)`                        `file_paths.append(file_path)`                `return file_paths``   `            `directory_to_walk = directory_to_walk`            `file_paths = save_file_paths(directory_to_walk)``   `            `from langchain_community.document_loaders import PDFPlumberLoader`            `def pdfplumber_loader(pdf_file):`                `loader = PDFPlumberLoader(pdf_file)`                `docs = loader.load()`                `return docs``   `            `import chardet`            `from langchain_community.document_loaders import TextLoader`            `def detect_encoding(file_path):`                `with open(file_path, 'rb') as f:`                    `rawdata = f.read()`                `result = chardet.detect(rawdata)`                `return result['encoding']``   `            `def text_loader(text_file):`                `encoding = detect_encoding(file_path)`                `loader = TextLoader(text_file, encoding=encoding)`                `docs = loader.load()`                `return docs``   `            `import win32com.client`            `def convert_doc_to_docx(doc_file_path):`                `word = win32com.client.Dispatch("Word.Application")`                `word.Visible = False`                `doc = word.Documents.Open(doc_file_path)`                `new_file_path = os.path.splitext(doc_file_path)[0] + ".docx"`                `doc.SaveAs(new_file_path, FileFormat=16)`                `doc.Close()`                `word.Quit()`                `return new_file_path``   `            `from langchain_community.document_loaders import Docx2txtLoader`            `def word_loader(word_file):`                `loader = Docx2txtLoader(word_file)`                `docs = loader.load()`                `return docs``   `            `texts = []`            `for file_path in file_paths:`                `if Path(file_path).suffix.lower() == '.doc':`                    `docx_file_path = convert_doc_to_docx(file_path)`                    `texts.extend(word_loader(docx_file_path))`                    `os.remove(docx_file_path)`                `elif Path(file_path).suffix.lower() == '.pdf':`                    `texts.extend(pdfplumber_loader(file_path))`                `elif Path(file_path).suffix.lower() in ['.txt', '.md']:`                    `texts.extend(text_loader(file_path))`                `elif Path(file_path).suffix.lower() == '.docx':`                    `texts.extend(word_loader(file_path))``   `            `from langchain_text_splitters import RecursiveCharacterTextSplitter`            `test_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100, add_start_index=True)`            `all_splits = test_splitter.split_documents(texts)``   `            `from langchain_chroma import Chroma`            `from langchain_community.embeddings import QianfanEmbeddingsEndpoint`            `api_key = os.getenv("qianfan_apikey")`            `secret_key = os.getenv("qianfan_secretkey")`            `os.environ['QIANFAN_AK'] = api_key`            `os.environ['QIANFAN_SK'] = secret_key`            `kk = Chroma.from_documents(all_splits, embedding=QianfanEmbeddingsEndpoint())`    `return kk
  • 功能:加载知识库文件,将文件内容转换为文本,并将文本向量化存储到向量数据库中。

  • 步骤:

  • 检查 vectorstort 是否为空。

  • 从配置文件中读取知识库路径。

  • 遍历指定文件夹,获取所有文件的路径。

  • 定义多个加载器函数,分别处理不同类型的文件(PDF、文本、Word)。

  • 遍历文件路径,根据文件类型调用相应的加载器函数,将文件内容转换为文本。

  • 使用 RecursiveCharacterTextSplitter 将文本切分为小块。

  • 使用 ChromaQianfanEmbeddingsEndpoint 将文本块向量化并存储到向量数据库中。

  • 返回向量数据库对象。

7.保存AI message

def save_ai_message(file_path, aiMessage):`    `chat_history = [f"检索回答:{aiMessage}"]`    `with open(file_path, 'a', encoding='utf-8') as file:`        `for message in chat_history:`            `file.write(message + '\n')
  • 功能:将AI的回答保存到聊天记录文件中。

  • 步骤:

  • 创建包含AI回答的列表。

  • 以追加模式打开聊天记录文件。

  • 将AI的回答写入文件。

8.保存用户message

def save_user_message(file_path, userMessage):`    `chat_history = [f"提问:{userMessage}"]`    `with open(file_path, 'a', encoding='utf-8') as file:`        `for message in chat_history:`            `file.write(message + '\n')
  • 功能:将用户的提问保存到聊天记录文件中。

  • 步骤:

  • 创建包含用户提问的列表。

  • 以追加模式打开聊天记录文件。

  • 将用户的提问写入文件。

9.清除聊天历史函数

def clear_file(file_path):`    `with open(file_path, 'w', encoding='utf-8') as file:`        `file.write('')
  • 功能:清空指定文件的所有内容。

  • 步骤:

  • 以写入模式打开文件。

  • 清空文件内容。

10.读取聊天历史函数

def read_ChatHistory(file_path, n=6):`    `with open(file_path, 'r', encoding='utf-8') as file:`        `buffer = []`        `while True:`            `line = file.readline()`            `if not line:`                `break`            `buffer.append(line)`            `if len(buffer) > n:`                `buffer.pop(0)`    `return buffer
  • 功能:读取聊天记录文件的最后几行内容。

  • 步骤:

  • 以读取模式打开聊天记录文件。

  • 逐行读取文件内容,将其添加到缓冲区。

  • 如果缓冲区中的行数超过指定数量 n,则移除最早的行。

  • 返回缓冲区中的内容。

11.主程序入口

if __name__ == "__main__":`    `root1 = tk.Tk()`    `app = InputWindow(root1)`    `root1.mainloop()
  • 功能:启动应用程序,显示选择文件夹窗口。

  • 步骤:

  • 创建 Tkinter 主窗口。

  • 实例化 InputWindow 类。

  • 启动 Tkinter 主循环。

二、界面展示

零基础如何学习AI大模型

领取方式在文末

为什么要学习大模型?

学习大模型课程的重要性在于它能够极大地促进个人在人工智能领域的专业发展。大模型技术,如自然语言处理和图像识别,正在推动着人工智能的新发展阶段。通过学习大模型课程,可以掌握设计和实现基于大模型的应用系统所需的基本原理和技术,从而提升自己在数据处理、分析和决策制定方面的能力。此外,大模型技术在多个行业中的应用日益增加,掌握这一技术将有助于提高就业竞争力,并为未来的创新创业提供坚实的基础。

大模型典型应用场景

AI+教育:智能教学助手和自动评分系统使个性化教育成为可能。通过AI分析学生的学习数据,提供量身定制的学习方案,提高学习效果。
AI+医疗:智能诊断系统和个性化医疗方案让医疗服务更加精准高效。AI可以分析医学影像,辅助医生进行早期诊断,同时根据患者数据制定个性化治疗方案。
AI+金融:智能投顾和风险管理系统帮助投资者做出更明智的决策,并实时监控金融市场,识别潜在风险。
AI+制造:智能制造和自动化工厂提高了生产效率和质量。通过AI技术,工厂可以实现设备预测性维护,减少停机时间。

这些案例表明,学习大模型课程不仅能够提升个人技能,还能为企业带来实际效益,推动行业创新发展。

学习资料领取

如果你对大模型感兴趣,可以看看我整合并且整理成了一份AI大模型资料包,需要的小伙伴文末免费领取哦,无偿分享!!!
vx扫描下方二维码即可
加上后会一个个给大家发

在这里插入图片描述

部分资料展示

一、 AI大模型学习路线图

整个学习分为7个阶段
在这里插入图片描述
请添加图片描述

二、AI大模型实战案例

涵盖AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,皆可用。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

三、视频和书籍PDF合集

从入门到进阶这里都有,跟着老师学习事半功倍。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

四、LLM面试题

在这里插入图片描述
在这里插入图片描述

五、AI产品经理面试题

在这里插入图片描述

😝朋友们如果有需要的话,可以V扫描下方二维码联系领取~
在这里插入图片描述

👉[CSDN大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享(安全链接,放心点击)]👈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值