Merge branch 'dev' of https://github.com/xerrors/ProjectAthena into dev
This commit is contained in:
commit
14d2d6594f
@ -49,7 +49,7 @@ class Config(SimpleConfig):
|
|||||||
# 模型配置
|
# 模型配置
|
||||||
## 注意这里是模型名,而不是具体的模型路径,默认使用 HuggingFace 的路径
|
## 注意这里是模型名,而不是具体的模型路径,默认使用 HuggingFace 的路径
|
||||||
## 如果需要自定义路径,则在 config/base.yaml 中配置 model_local_paths
|
## 如果需要自定义路径,则在 config/base.yaml 中配置 model_local_paths
|
||||||
self.add_item("model_provider", default="qianfan", des="模型提供商", choices=["qianfan", "vllm", "zhipu", "deepseek", "dashscope"])
|
self.add_item("model_provider", default="zhipu", des="模型提供商", choices=["qianfan", "vllm", "zhipu", "deepseek", "dashscope"])
|
||||||
self.add_item("model_name", default=None, des="模型名称")
|
self.add_item("model_name", default=None, des="模型名称")
|
||||||
self.add_item("embed_model", default="bge-large-zh-v1.5", des="Embedding 模型", choices=["bge-large-zh-v1.5", "zhipu"])
|
self.add_item("embed_model", default="bge-large-zh-v1.5", des="Embedding 模型", choices=["bge-large-zh-v1.5", "zhipu"])
|
||||||
self.add_item("reranker", default="bge-reranker-v2-m3", des="Re-Ranker 模型", choices=["bge-reranker-v2-m3"])
|
self.add_item("reranker", default="bge-reranker-v2-m3", des="Re-Ranker 模型", choices=["bge-reranker-v2-m3"])
|
||||||
|
|||||||
@ -2,10 +2,7 @@ import os
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from src.utils import hashstr, setup_logger, is_text_pdf
|
from src.utils import hashstr, setup_logger, is_text_pdf
|
||||||
from src.plugins import pdf2txt
|
|
||||||
from src.core.knowledgebase import KnowledgeBase
|
|
||||||
from src.core.filereader import pdfreader, plainreader
|
from src.core.filereader import pdfreader, plainreader
|
||||||
from src.core.graphbase import GraphDatabase
|
|
||||||
from src.models.embedding import get_embedding_model
|
from src.models.embedding import get_embedding_model
|
||||||
|
|
||||||
logger = setup_logger("DataBaseManager")
|
logger = setup_logger("DataBaseManager")
|
||||||
@ -57,6 +54,8 @@ class DataBaseManager:
|
|||||||
self.embed_model = get_embedding_model(config)
|
self.embed_model = get_embedding_model(config)
|
||||||
|
|
||||||
if self.config.enable_knowledge_base:
|
if self.config.enable_knowledge_base:
|
||||||
|
from src.core.knowledgebase import KnowledgeBase
|
||||||
|
from src.core.graphbase import GraphDatabase
|
||||||
self.knowledge_base = KnowledgeBase(config, self.embed_model)
|
self.knowledge_base = KnowledgeBase(config, self.embed_model)
|
||||||
self.graph_base = GraphDatabase(self.config, self.embed_model)
|
self.graph_base = GraphDatabase(self.config, self.embed_model)
|
||||||
self.graph_base.start()
|
self.graph_base.start()
|
||||||
@ -180,6 +179,7 @@ class DataBaseManager:
|
|||||||
if is_text_pdf(file):
|
if is_text_pdf(file):
|
||||||
return pdfreader(file)
|
return pdfreader(file)
|
||||||
else:
|
else:
|
||||||
|
from src.plugins import pdf2txt
|
||||||
return pdf2txt(file, return_text=True)
|
return pdf2txt(file, return_text=True)
|
||||||
|
|
||||||
elif file.endswith(".txt") or file.endswith(".md"):
|
elif file.endswith(".txt") or file.endswith(".md"):
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from llama_index.readers.file import PDFReader
|
|
||||||
|
|
||||||
|
|
||||||
def pdfreader(file_path):
|
def pdfreader(file_path):
|
||||||
"""读取PDF文件并返回text文本"""
|
"""读取PDF文件并返回text文本"""
|
||||||
assert os.path.exists(file_path), "File not found"
|
assert os.path.exists(file_path), "File not found"
|
||||||
assert file_path.endswith(".pdf"), "File format not supported"
|
assert file_path.endswith(".pdf"), "File format not supported"
|
||||||
|
|
||||||
|
from llama_index.readers.file import PDFReader
|
||||||
doc = PDFReader().load_data(file=Path(file_path))
|
doc = PDFReader().load_data(file=Path(file_path))
|
||||||
|
|
||||||
# 简单的拼接起来之后返回纯文本
|
# 简单的拼接起来之后返回纯文本
|
||||||
|
|||||||
@ -25,9 +25,13 @@ class HistoryManager():
|
|||||||
self.add_ai(content)
|
self.add_ai(content)
|
||||||
return self.messages
|
return self.messages
|
||||||
|
|
||||||
def get_history_with_msg(self, msg, role="user"):
|
def get_history_with_msg(self, msg, role="user", max_rounds=None):
|
||||||
"""Get history with new message, but not append it to history."""
|
"""Get history with new message, but not append it to history."""
|
||||||
|
if max_rounds is None:
|
||||||
history = self.messages[:]
|
history = self.messages[:]
|
||||||
|
else:
|
||||||
|
history = self.messages[-(2*max_rounds):]
|
||||||
|
|
||||||
history.append({"role": role, "content": msg})
|
history.append({"role": role, "content": msg})
|
||||||
return history
|
return history
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ class KnowledgeBase:
|
|||||||
|
|
||||||
assert embed_model, "embed_model=None"
|
assert embed_model, "embed_model=None"
|
||||||
self.embed_model = embed_model
|
self.embed_model = embed_model
|
||||||
|
|
||||||
self.client = MilvusClient(self.milvus_path)
|
self.client = MilvusClient(self.milvus_path)
|
||||||
|
|
||||||
def _init_config(self, config):
|
def _init_config(self, config):
|
||||||
@ -72,15 +73,17 @@ class KnowledgeBase:
|
|||||||
def search(self, query, collection_name, limit=3):
|
def search(self, query, collection_name, limit=3):
|
||||||
|
|
||||||
query_vectors = self.embed_model.encode_queries([query])
|
query_vectors = self.embed_model.encode_queries([query])
|
||||||
|
return self.search_by_vector(query_vectors[0], collection_name, limit)
|
||||||
|
|
||||||
|
def search_by_vector(self, vector, collection_name, limit=3):
|
||||||
res = self.client.search(
|
res = self.client.search(
|
||||||
collection_name=collection_name, # target collection
|
collection_name=collection_name, # target collection
|
||||||
data=query_vectors, # query vectors
|
data=[vector], # query vectors
|
||||||
limit=limit, # number of returned entities
|
limit=limit, # number of returned entities
|
||||||
output_fields=["text", "file_id"], # specifies fields to be returned
|
output_fields=["text", "file_id"], # specifies fields to be returned
|
||||||
)
|
)
|
||||||
|
|
||||||
return res[0] # 因为 query 只有一个
|
return res[0]
|
||||||
|
|
||||||
def examples(self, collection_name, limit=20):
|
def examples(self, collection_name, limit=20):
|
||||||
res = self.client.query(
|
res = self.client.query(
|
||||||
|
|||||||
@ -15,9 +15,7 @@ class Retriever:
|
|||||||
|
|
||||||
def retrieval(self, query, history, meta):
|
def retrieval(self, query, history, meta):
|
||||||
|
|
||||||
refs = {}
|
refs = {"query": query, "history": history, "meta": meta}
|
||||||
refs["meta"] = meta
|
|
||||||
refs["rewritten_query"] = self.rewrite_query(query, history, refs)
|
|
||||||
refs["entities"] = self.reco_entities(query, history, refs)
|
refs["entities"] = self.reco_entities(query, history, refs)
|
||||||
refs["knowledge_base"] = self.query_knowledgebase(query, history, refs)
|
refs["knowledge_base"] = self.query_knowledgebase(query, history, refs)
|
||||||
refs["graph_base"] = self.query_graph(query, history, refs)
|
refs["graph_base"] = self.query_graph(query, history, refs)
|
||||||
@ -68,37 +66,47 @@ class Retriever:
|
|||||||
|
|
||||||
def query_knowledgebase(self, query, history, refs):
|
def query_knowledgebase(self, query, history, refs):
|
||||||
"""查询知识库"""
|
"""查询知识库"""
|
||||||
query = refs.get("rewritten_query", query)
|
rw_query = self.rewrite_query(query, history, refs)
|
||||||
|
|
||||||
kb_res = []
|
kb_res = []
|
||||||
final_res = []
|
final_res = []
|
||||||
if refs["meta"].get("db_name") and self.config.enable_knowledge_base:
|
if refs["meta"].get("db_name") and self.config.enable_knowledge_base:
|
||||||
|
|
||||||
db_name = refs["meta"]["db_name"]
|
db_name = refs["meta"]["db_name"]
|
||||||
kb = self.dbm.metaname2db[refs["meta"]["db_name"]]
|
kb = self.dbm.metaname2db[db_name]
|
||||||
limit = refs["meta"].get("queryCount", 10)
|
limit = refs["meta"].get("queryCount", 10)
|
||||||
kb_res = self.dbm.knowledge_base.search(query, db_name, limit=limit)
|
|
||||||
|
kb_res = self.dbm.knowledge_base.search(rw_query, db_name, limit=limit)
|
||||||
for r in kb_res:
|
for r in kb_res:
|
||||||
r["file"] = kb.id2file(r["entity"]["file_id"])
|
r["file"] = kb.id2file(r["entity"]["file_id"])
|
||||||
|
|
||||||
if self.config.enable_reranker:
|
if self.config.enable_reranker:
|
||||||
|
RERANK_THRESHOLD = 0.1
|
||||||
for r in kb_res:
|
for r in kb_res:
|
||||||
r["rerank_score"] = self.reranker.compute_score([query, r["entity"]["text"]], normalize=True)
|
r["rerank_score"] = self.reranker.compute_score([query, r["entity"]["text"]], normalize=True)
|
||||||
kb_res.sort(key=lambda x: x["rerank_score"], reverse=True)
|
kb_res.sort(key=lambda x: x["rerank_score"], reverse=True)
|
||||||
final_res = [_res for _res in kb_res if _res["rerank_score"] > 0.1]
|
final_res = [_res for _res in kb_res if _res["rerank_score"] > RERANK_THRESHOLD]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
final_res = kb_res[:5]
|
final_res = kb_res[:5]
|
||||||
|
|
||||||
return {"results": final_res, "all_results": kb_res}
|
return {"results": final_res, "all_results": kb_res, "rw_query": rw_query}
|
||||||
|
|
||||||
def rewrite_query(self, query, history, refs):
|
def rewrite_query(self, query, history, refs):
|
||||||
"""重写查询"""
|
"""重写查询"""
|
||||||
if refs["meta"].get("rewrite_query") is None or history == []:
|
rewrite_query_span = refs["meta"].get("rewrite_query", None)
|
||||||
|
if rewrite_query_span is None or rewrite_query_span == "OFF":
|
||||||
rewritten_query = query
|
rewritten_query = query
|
||||||
else:
|
else:
|
||||||
from src.utils.prompts import rewritten_query_prompt_template
|
from src.utils.prompts import rewritten_query_prompt_template
|
||||||
rewritten_query_prompt = rewritten_query_prompt_template.format(history=[entry['content'] for entry in history if entry['role'] == 'user'], query=query)
|
history_query = [entry['content'] for entry in history if entry['role'] == 'user'] if history else ""
|
||||||
|
rewritten_query_prompt = rewritten_query_prompt_template.format(history=history_query, query=query)
|
||||||
rewritten_query = self.model.predict(rewritten_query_prompt).content
|
rewritten_query = self.model.predict(rewritten_query_prompt).content
|
||||||
|
|
||||||
|
if rewrite_query_span == "HyDE":
|
||||||
|
hy_doc = self.model.predict(rewritten_query).content
|
||||||
|
rewritten_query = f"{rewritten_query} {hy_doc}"
|
||||||
|
|
||||||
return rewritten_query
|
return rewritten_query
|
||||||
|
|
||||||
def reco_entities(self, query, history, refs):
|
def reco_entities(self, query, history, refs):
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
### 1. 对话模型支持
|
### 1. 对话模型支持
|
||||||
|
|
||||||
模型仅支持通过API调用的模型,如果是需要运行本地模型,则建议使用 vllm 转成 API 服务之后使用。
|
模型仅支持通过API调用的模型,如果是需要运行本地模型,则建议使用 vllm 转成 API 服务之后使用。使用前请配置 APIKEY 后使用,配置项目参考:[.env.template](../.env.template)
|
||||||
|
|
||||||
|模型供应商(`config.model_provider`)|默认模型(`config.model_name`)|配置项目(`.env`)|
|
|模型供应商(`config.model_provider`)|默认模型(`config.model_name`)|配置项目(`.env`)|
|
||||||
|:-|:-|:-|
|
|:-|:-|:-|
|
||||||
|`qianfan`|`ernie_speed`|`QIANFAN_ACCESS_KEY`, `QIANFAN_SECRET_KEY`|
|
|`qianfan`|`ernie_speed`|`QIANFAN_ACCESS_KEY`, `QIANFAN_SECRET_KEY`|
|
||||||
|`zhipu`|`glm-4`|`ZHIPUAPI`|
|
|`zhipu`(default)|`glm-4`|`ZHIPUAPI`|
|
||||||
|`deepseek`|`deepseek-chat`|`DEEPSEEKAPI`|
|
|`deepseek`|`deepseek-chat`|`DEEPSEEKAPI`|
|
||||||
|`vllm`|`vllm`|`VLLM_API_KEY`, `VLLM_API_BASE`|
|
|`vllm`|`vllm`|`VLLM_API_KEY`, `VLLM_API_BASE`|
|
||||||
|
|
||||||
|
|||||||
@ -64,7 +64,6 @@ class VLLM(OpenAIBase):
|
|||||||
super().__init__(api_key=api_key, base_url=base_url, model_name=model_name)
|
super().__init__(api_key=api_key, base_url=base_url, model_name=model_name)
|
||||||
|
|
||||||
|
|
||||||
import qianfan
|
|
||||||
|
|
||||||
|
|
||||||
class GeneralResponse:
|
class GeneralResponse:
|
||||||
@ -76,6 +75,7 @@ class GeneralResponse:
|
|||||||
class Qianfan:
|
class Qianfan:
|
||||||
|
|
||||||
def __init__(self, model_name="ernie_speed") -> None:
|
def __init__(self, model_name="ernie_speed") -> None:
|
||||||
|
import qianfan
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
access_key = os.getenv("QIANFAN_ACCESS_KEY")
|
access_key = os.getenv("QIANFAN_ACCESS_KEY")
|
||||||
secret_key = os.getenv("QIANFAN_SECRET_KEY")
|
secret_key = os.getenv("QIANFAN_SECRET_KEY")
|
||||||
|
|||||||
@ -1,38 +1,19 @@
|
|||||||
import os
|
import os
|
||||||
import fitz # fitz就是pip install PyMuPDF
|
import fitz # fitz就是pip install PyMuPDF
|
||||||
import cv2
|
import cv2
|
||||||
from paddleocr import PPStructure, save_structure_res
|
|
||||||
from paddleocr.ppstructure.recovery.recovery_to_doc import sorted_layout_boxes, convert_info_docx
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
def pdf2txt(pdf_path, return_text=False):
|
def pdf2txt(pdf_path, return_text=False):
|
||||||
|
from paddleocr import PPStructure, save_structure_res
|
||||||
|
from paddleocr.ppstructure.recovery.recovery_to_doc import sorted_layout_boxes, convert_info_docx
|
||||||
output_dir = os.path.join('tmp', 'pdf2txt', os.path.basename(pdf_path).split('.')[0])
|
output_dir = os.path.join('tmp', 'pdf2txt', os.path.basename(pdf_path).split('.')[0])
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
table_engine = PPStructure(recovery=True, lang='ch')
|
table_engine = PPStructure(recovery=True, lang='ch')
|
||||||
|
|
||||||
imgs = []
|
imgs = convert_imgs(pdf_path, output_dir)
|
||||||
img_dir = os.path.join(output_dir, 'imgs')
|
|
||||||
if not os.path.exists(img_dir):
|
|
||||||
os.makedirs(img_dir)
|
|
||||||
pdfDoc = fitz.open(pdf_path)
|
|
||||||
totalPage = pdfDoc.page_count
|
|
||||||
for pg in tqdm(range(totalPage), desc='to imgs', ncols=100):
|
|
||||||
page = pdfDoc[pg]
|
|
||||||
rotate = int(0)
|
|
||||||
zoom_x = 2
|
|
||||||
zoom_y = 2
|
|
||||||
mat = fitz.Matrix(zoom_x, zoom_y).prerotate(rotate)
|
|
||||||
pix = page.get_pixmap(matrix=mat, alpha=False)
|
|
||||||
img_filename = os.path.join(img_dir, f'images_{pg+1}.png')
|
|
||||||
pix.save(img_filename) # os.sep
|
|
||||||
imgs.append(img_filename)
|
|
||||||
else:
|
|
||||||
img_names = sorted(os.listdir(img_dir))
|
|
||||||
imgs = [os.path.join(img_dir, img_name) for img_name in img_names]
|
|
||||||
|
|
||||||
respath = os.path.join(output_dir, 'res.txt')
|
respath = os.path.join(output_dir, 'res.txt')
|
||||||
|
|
||||||
text = []
|
text = []
|
||||||
@ -59,7 +40,7 @@ def pdf2txt(pdf_path, return_text=False):
|
|||||||
continue # 如果不是字典或者缺少 'text' 键,跳过当前循环
|
continue # 如果不是字典或者缺少 'text' 键,跳过当前循环
|
||||||
text.append('\n')
|
text.append('\n')
|
||||||
|
|
||||||
whole_text = '\n'.join(text)
|
whole_text = ''.join(text)
|
||||||
with open(respath, 'w', encoding='utf-8') as f:
|
with open(respath, 'w', encoding='utf-8') as f:
|
||||||
f.write(whole_text)
|
f.write(whole_text)
|
||||||
|
|
||||||
@ -68,6 +49,29 @@ def pdf2txt(pdf_path, return_text=False):
|
|||||||
|
|
||||||
return respath
|
return respath
|
||||||
|
|
||||||
|
def convert_imgs(pdf_path, output_dir):
|
||||||
|
imgs = []
|
||||||
|
img_dir = os.path.join(output_dir, 'imgs')
|
||||||
|
if not os.path.exists(img_dir):
|
||||||
|
os.makedirs(img_dir)
|
||||||
|
pdfDoc = fitz.open(pdf_path)
|
||||||
|
totalPage = pdfDoc.page_count
|
||||||
|
for pg in tqdm(range(totalPage), desc='to imgs', ncols=100):
|
||||||
|
page = pdfDoc[pg]
|
||||||
|
rotate = int(0)
|
||||||
|
zoom_x = 2
|
||||||
|
zoom_y = 2
|
||||||
|
mat = fitz.Matrix(zoom_x, zoom_y).prerotate(rotate)
|
||||||
|
pix = page.get_pixmap(matrix=mat, alpha=False)
|
||||||
|
img_filename = os.path.join(img_dir, f'images_{pg+1}.png')
|
||||||
|
pix.save(img_filename) # os.sep
|
||||||
|
imgs.append(img_filename)
|
||||||
|
else:
|
||||||
|
img_names = sorted(os.listdir(img_dir))
|
||||||
|
imgs = [os.path.join(img_dir, img_name) for img_name in img_names]
|
||||||
|
|
||||||
|
return imgs
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
pdf_path = r'data/file/焙烤食品工艺学.pdf'
|
pdf_path = r'saves/data/uploads/2e04d5_保健食品.pdf'
|
||||||
print(pdf2txt(pdf_path))
|
print(pdf2txt(pdf_path))
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
FlagEmbedding==1.2.10
|
FlagEmbedding==1.2.10
|
||||||
Flask==3.0.3
|
Flask==3.0.3
|
||||||
Flask_Cors==4.0.1
|
Flask_Cors==4.0.1
|
||||||
llama_index==0.10.53
|
|
||||||
openai==1.35.10
|
openai==1.35.10
|
||||||
pymilvus==2.4.4
|
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
qianfan==0.4.0.1
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ def chat():
|
|||||||
|
|
||||||
new_query, refs = startup.retriever(query, history_manager.messages, meta)
|
new_query, refs = startup.retriever(query, history_manager.messages, meta)
|
||||||
|
|
||||||
messages = history_manager.get_history_with_msg(new_query)
|
messages = history_manager.get_history_with_msg(new_query, max_rounds=meta.get('history_round'))
|
||||||
history_manager.add_user(query)
|
history_manager.add_user(query)
|
||||||
logger.debug(f"Web history: {history_manager}")
|
logger.debug(f"Web history: {history_manager}")
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,6 @@
|
|||||||
--main-light-6: #FAFDFD;
|
--main-light-6: #FAFDFD;
|
||||||
--min-width: 400px;
|
--min-width: 400px;
|
||||||
--min-header-width: 80px;
|
--min-header-width: 80px;
|
||||||
--min-sider-width: 100px;
|
|
||||||
--error-color: #f50a0d;
|
--error-color: #f50a0d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,11 +39,11 @@
|
|||||||
</a-menu>
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
<div class="nav-btn text" @click="opts.showPanel = !opts.showPanel">
|
<div class="nav-btn text " @click="opts.showPanel = !opts.showPanel">
|
||||||
<component :is="opts.showPanel ? FolderOpenOutlined : FolderOutlined" /> <span class="text">选项</span>
|
<component :is="opts.showPanel ? FolderOpenOutlined : FolderOutlined" /> <span class="text">选项</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="opts.showPanel" class="my-panal" ref="panel">
|
<div v-if="opts.showPanel" class="my-panal swing-in-top-fwd" ref="panel">
|
||||||
<div class="graphbase flex-center" v-if="configStore.config.enable_knowledge_base">
|
<div class="flex-center" v-if="configStore.config.enable_knowledge_base">
|
||||||
知识库
|
知识库
|
||||||
<div @click.stop>
|
<div @click.stop>
|
||||||
<a-dropdown>
|
<a-dropdown>
|
||||||
@ -65,21 +65,24 @@
|
|||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphbase flex-center" @click="meta.use_graph = !meta.use_graph" v-if="configStore.config.enable_knowledge_base">
|
<div class="flex-center" @click="meta.use_graph = !meta.use_graph" v-if="configStore.config.enable_knowledge_base">
|
||||||
图数据库 <div @click.stop><a-switch v-model:checked="meta.use_graph" /></div>
|
图数据库 <div @click.stop><a-switch v-model:checked="meta.use_graph" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphbase flex-center" @click="meta.use_web = !meta.use_web" v-if="configStore.config.enable_search_engine">
|
<div class="flex-center" @click="meta.use_web = !meta.use_web" v-if="configStore.config.enable_search_engine">
|
||||||
搜索引擎(Bing) <div @click.stop><a-switch v-model:checked="meta.use_web" /></div>
|
搜索引擎(Bing) <div @click.stop><a-switch v-model:checked="meta.use_web" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphbase flex-center" @click="meta.rewrite_query = !meta.rewrite_query" v-if="configStore.config.enable_reranker">
|
<div class="flex-center" @click="meta.rewrite_query = !meta.rewrite_query" v-if="configStore.config.enable_reranker">
|
||||||
重写查询 <div @click.stop><a-switch v-model:checked="meta.rewrite_query" /></div>
|
重写查询 <div @click.stop><a-switch v-model:checked="meta.rewrite_query" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphbase flex-center" @click="meta.rewrite_query = !meta.rewrite_query">
|
<div class="flex-center" @click="meta.rewrite_query = !meta.rewrite_query">
|
||||||
流式输出 <div @click.stop><a-switch v-model:checked="meta.stream" /></div>
|
流式输出 <div @click.stop><a-switch v-model:checked="meta.stream" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="graphbase flex-center" @click="meta.summary_title = !meta.summary_title">
|
<div class="flex-center" @click="meta.summary_title = !meta.summary_title">
|
||||||
总结对话标题 <div @click.stop><a-switch v-model:checked="meta.summary_title" /></div>
|
总结对话标题 <div @click.stop><a-switch v-model:checked="meta.summary_title" /></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-center">
|
||||||
|
最大历史轮数 <a-input-number id="inputNumber" v-model:value="meta.history_round" :min="1" :max="50" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -134,8 +137,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-for="(res, idx) in results" :key="idx" class="result-item">
|
<div v-for="(res, idx) in results" :key="idx" class="result-item">
|
||||||
<p class="result-id"><strong>ID:</strong> #{{ res.id }}</p>
|
<p class="result-id"><strong>ID:</strong> #{{ res.id }}</p>
|
||||||
<p class="result-distance"><strong>相似度距离:</strong> {{ res.distance }}</p>
|
<p class="result-distance">
|
||||||
<p class="result-rerank-score"><strong>重排序分数:</strong> {{ res.rerank_score }}</p>
|
<strong>相似度距离:</strong>
|
||||||
|
<div class=scorebar>
|
||||||
|
<a-progress :percent="(res.distance * 100).toFixed(2)" stroke-color="#1677FF" :size="[200, 10]"/>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
<p class="result-rerank-score">
|
||||||
|
<strong>重排序分数:</strong>
|
||||||
|
<div class=scorebar>
|
||||||
|
<a-progress :percent="(res.rerank_score * 100).toFixed(2)" stroke-color="#1677FF" :size="[200, 10]"/>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
<p class="result-text">{{ res.entity.text }}</p>
|
<p class="result-text">{{ res.entity.text }}</p>
|
||||||
</div>
|
</div>
|
||||||
</a-drawer>
|
</a-drawer>
|
||||||
@ -216,6 +229,7 @@ const meta = reactive(JSON.parse(localStorage.getItem('meta')) || {
|
|||||||
selectedKB: null,
|
selectedKB: null,
|
||||||
stream: true,
|
stream: true,
|
||||||
summary_title: true,
|
summary_title: true,
|
||||||
|
history_round: 5,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 更多选项可以在 marked 文档中找到:https://marked.js.org/
|
// 更多选项可以在 marked 文档中找到:https://marked.js.org/
|
||||||
@ -517,9 +531,6 @@ watch(
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
|
||||||
|
|
||||||
.graphbase {
|
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -663,6 +674,18 @@ watch(
|
|||||||
.result-text {
|
.result-text {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scorebar {
|
||||||
|
margin-left: 10px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 200px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -780,6 +803,10 @@ button:disabled {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.slide-out-left{-webkit-animation:slide-out-left .5s cubic-bezier(.55,.085,.68,.53) both;animation:slide-out-left .5s cubic-bezier(.55,.085,.68,.53) both}
|
||||||
|
.swing-in-top-fwd {-webkit-animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;animation: swing-in-top-fwd 0.5s cubic-bezier(0.175, 0.885, 0.320, 1.275) both;}
|
||||||
|
@-webkit-keyframes swing-in-top-fwd{0%{-webkit-transform:rotateX(-100deg);transform:rotateX(-100deg);-webkit-transform-origin:top;transform-origin:top;opacity:0}100%{-webkit-transform:rotateX(0deg);transform:rotateX(0deg);-webkit-transform-origin:top;transform-origin:top;opacity:1}}@keyframes swing-in-top-fwd{0%{-webkit-transform:rotateX(-100deg);transform:rotateX(-100deg);-webkit-transform-origin:top;transform-origin:top;opacity:0}100%{-webkit-transform:rotateX(0deg);transform:rotateX(0deg);-webkit-transform-origin:top;transform-origin:top;opacity:1}}
|
||||||
|
@-webkit-keyframes slide-out-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform:translateX(-1000px);transform:translateX(-1000px);opacity:0}}@keyframes slide-out-left{0%{-webkit-transform:translateX(0);transform:translateX(0);opacity:1}100%{-webkit-transform:translateX(-1000px);transform:translateX(-1000px);opacity:0}}
|
||||||
|
|
||||||
@media (max-width: 520px) {
|
@media (max-width: 520px) {
|
||||||
.chat {
|
.chat {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chat-container">
|
<div class="chat-container">
|
||||||
<div v-if="state.isSidebarOpen" class="conversations">
|
<div class="conversations" :class="['conversations', { 'is-open': state.isSidebarOpen }]">
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<!-- <div class="action new" @click="addNewConv"><FormOutlined /></div> -->
|
<!-- <div class="action new" @click="addNewConv"><FormOutlined /></div> -->
|
||||||
<span style="font-weight: bold;">对话历史</span>
|
<span style="font-weight: bold;">对话历史</span>
|
||||||
@ -120,20 +120,29 @@ onMounted(() => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-container .conversations {
|
.chat-container .conversations:not(.is-open) {
|
||||||
flex: 1 1 auto;
|
width: 0;
|
||||||
|
opacity: 0;
|
||||||
|
flex: 0 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat-container .conversations.is-open {
|
||||||
|
overflow: hidden; /* 确保内容不溢出 */
|
||||||
|
white-space: nowrap; /* 防止文本换行 */
|
||||||
|
flex: 1 1 auto; /* 当侧边栏打开时,占据可用空间 */
|
||||||
|
}
|
||||||
.conversations {
|
.conversations {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100px;
|
width: 230px; /* 初始宽度 */
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
max-width: 230px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
border-right: 1px solid var(--main-light-3);
|
border-right: 1px solid var(--main-light-3);
|
||||||
min-width: var(--min-sider-width);
|
|
||||||
max-width: 200px;
|
|
||||||
background-color: #FAFCFD;
|
background-color: #FAFCFD;
|
||||||
|
overflow: hidden; /* 确保内容不溢出 */
|
||||||
|
white-space: nowrap; /* 防止文本换行 */
|
||||||
|
transition: all 0.2s ease-out;
|
||||||
|
|
||||||
& .actions {
|
& .actions {
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
@ -234,7 +243,6 @@ onMounted(() => {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 520px) {
|
@media (max-width: 520px) {
|
||||||
.conversations {
|
.conversations {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@ -34,7 +34,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="params-item">
|
<div class="params-item">
|
||||||
<p>过滤低质量:</p>
|
<p>过滤低质量:</p>
|
||||||
<a-switch v-model:checked="meta.filter" size="small" />
|
<a-switch v-model:checked="meta.filter" />
|
||||||
|
</div>
|
||||||
|
<div class="params-item">
|
||||||
|
<p>重写查询</p>
|
||||||
|
<a-segmented v-model:value="meta.rewrite_query" :options="['OFF', 'ON', 'HyDE']" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -122,7 +127,7 @@
|
|||||||
:auto-size="{ minRows: 2, maxRows: 10 }"
|
:auto-size="{ minRows: 2, maxRows: 10 }"
|
||||||
/>
|
/>
|
||||||
<!-- :loading="state.searchLoading" -->
|
<!-- :loading="state.searchLoading" -->
|
||||||
<a-button @click="onQuery" :disabled="queryText.length == 0">
|
<a-button @click="onQuery" :disabled="queryText.length == 0" :loading="state.searchLoading">
|
||||||
<SearchOutlined v-if="!state.searchLoading"/>检索
|
<SearchOutlined v-if="!state.searchLoading"/>检索
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
@ -183,6 +188,7 @@ const state = reactive({
|
|||||||
const meta = reactive({
|
const meta = reactive({
|
||||||
queryCount: 10,
|
queryCount: 10,
|
||||||
filter: false,
|
filter: false,
|
||||||
|
rewrite_query: 'OFF',
|
||||||
});
|
});
|
||||||
|
|
||||||
const onQuery = () => {
|
const onQuery = () => {
|
||||||
@ -507,9 +513,8 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: var(--main-light-4);
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 20px;
|
margin: 20px 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|
||||||
@ -523,6 +528,10 @@ onMounted(() => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.params-item.col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
<div class="setting">
|
<div class="setting">
|
||||||
<h2>设置</h2>
|
<h2>设置</h2>
|
||||||
<h3>模型配置</h3>
|
<h3>模型配置</h3>
|
||||||
|
<p>请在 <code>src/.env</code> 文件中配置对应的 APIKEY,可参考 <code>src/.env.template</code></p>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span class="label">
|
<span class="label">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user