chore: 移除 Chat 、Retriever 相关代码,知识库与知识图谱默认开启且无法修改
- 删除 retriever.py 文件,移除不再使用的检索器逻辑。 - 更新 base_router.py,简化重启逻辑,移除 retriever.restart() 调用。 - 修改 config.py,移除知识库和知识图谱的配置项。 - 更新前端组件,调整与知识库相关的显示逻辑,确保界面一致性。 - 修改路由配置,移除与聊天相关的路由,优化用户导航体验。
This commit is contained in:
parent
a9b5e84618
commit
9bc5b086f9
@ -93,7 +93,6 @@ async def update_config_item(
|
|||||||
async def restart(current_user: User = Depends(get_superadmin_user)):
|
async def restart(current_user: User = Depends(get_superadmin_user)):
|
||||||
knowledge_base.restart()
|
knowledge_base.restart()
|
||||||
graph_base.start()
|
graph_base.start()
|
||||||
retriever.restart()
|
|
||||||
return {"message": "Restarted!"}
|
return {"message": "Restarted!"}
|
||||||
|
|
||||||
@base.get("/log")
|
@base.get("/log")
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from langchain_core.messages import AIMessageChunk, HumanMessage
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from src import executor, config, retriever
|
from src import executor, config
|
||||||
from src.core import HistoryManager
|
from src.core import HistoryManager
|
||||||
from src.agents import agent_manager
|
from src.agents import agent_manager
|
||||||
from src.models import select_model
|
from src.models import select_model
|
||||||
@ -67,82 +67,6 @@ async def chat_get(current_user: User = Depends(get_required_user)):
|
|||||||
"""聊天服务健康检查(需要登录)"""
|
"""聊天服务健康检查(需要登录)"""
|
||||||
return "Chat Get!"
|
return "Chat Get!"
|
||||||
|
|
||||||
@chat.post("/")
|
|
||||||
async def chat_post(
|
|
||||||
query: str = Body(...),
|
|
||||||
meta: dict = Body(None),
|
|
||||||
history: list[dict] | None = Body(None),
|
|
||||||
thread_id: str | None = Body(None),
|
|
||||||
current_user: User = Depends(get_required_user)):
|
|
||||||
"""处理聊天请求的主要端点(需要登录)"""
|
|
||||||
|
|
||||||
model = select_model()
|
|
||||||
meta["server_model_name"] = model.model_name
|
|
||||||
history_manager = HistoryManager(history, system_prompt=meta.get("system_prompt"))
|
|
||||||
logger.debug(f"Received query: {query} with meta: {meta}")
|
|
||||||
|
|
||||||
def make_chunk(content=None, **kwargs):
|
|
||||||
return json.dumps({
|
|
||||||
"response": content,
|
|
||||||
"meta": meta,
|
|
||||||
**kwargs
|
|
||||||
}, ensure_ascii=False).encode('utf-8') + b"\n"
|
|
||||||
|
|
||||||
def need_retrieve(meta):
|
|
||||||
return meta.get("use_web") or meta.get("use_graph") or meta.get("db_id")
|
|
||||||
|
|
||||||
def generate_response():
|
|
||||||
modified_query = query
|
|
||||||
refs = None
|
|
||||||
|
|
||||||
# 处理知识库检索
|
|
||||||
if meta and need_retrieve(meta):
|
|
||||||
chunk = make_chunk(status="searching")
|
|
||||||
yield chunk
|
|
||||||
|
|
||||||
try:
|
|
||||||
modified_query, refs = retriever(modified_query, history_manager.messages, meta)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Retriever error: {e}, {traceback.format_exc()}")
|
|
||||||
yield make_chunk(message=f"Retriever error: {e}", status="error")
|
|
||||||
return
|
|
||||||
|
|
||||||
yield make_chunk(status="generating")
|
|
||||||
|
|
||||||
messages = history_manager.get_history_with_msg(modified_query, max_rounds=meta.get('history_round'))
|
|
||||||
history_manager.add_user(query) # 注意这里使用原始查询
|
|
||||||
|
|
||||||
content = ""
|
|
||||||
reasoning_content = ""
|
|
||||||
try:
|
|
||||||
for delta in model.predict(messages, stream=True):
|
|
||||||
if not delta.content and hasattr(delta, 'reasoning_content'):
|
|
||||||
reasoning_content += delta.reasoning_content or ""
|
|
||||||
chunk = make_chunk(reasoning_content=reasoning_content, status="reasoning")
|
|
||||||
yield chunk
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 文心一言
|
|
||||||
if hasattr(delta, 'is_full') and delta.is_full:
|
|
||||||
content = delta.content
|
|
||||||
else:
|
|
||||||
content += delta.content or ""
|
|
||||||
|
|
||||||
chunk = make_chunk(content=delta.content, status="loading")
|
|
||||||
yield chunk
|
|
||||||
|
|
||||||
logger.debug(f"Final response: {content}")
|
|
||||||
logger.debug(f"Final reasoning response: {reasoning_content}")
|
|
||||||
yield make_chunk(status="finished",
|
|
||||||
history=history_manager.update_ai(content),
|
|
||||||
refs=refs)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Model error: {e}, {traceback.format_exc()}")
|
|
||||||
yield make_chunk(message=f"Model error: {e}", status="error")
|
|
||||||
return
|
|
||||||
|
|
||||||
return StreamingResponse(generate_response(), media_type='application/json')
|
|
||||||
|
|
||||||
@chat.post("/call")
|
@chat.post("/call")
|
||||||
async def call(query: str = Body(...), meta: dict = Body(None), current_user: User = Depends(get_required_user)):
|
async def call(query: str = Body(...), meta: dict = Body(None), current_user: User = Depends(get_required_user)):
|
||||||
"""调用模型进行简单问答(需要登录)"""
|
"""调用模型进行简单问答(需要登录)"""
|
||||||
|
|||||||
@ -13,6 +13,3 @@ knowledge_base = KnowledgeBase()
|
|||||||
|
|
||||||
from src.core import GraphDatabase # noqa: E402
|
from src.core import GraphDatabase # noqa: E402
|
||||||
graph_base = GraphDatabase()
|
graph_base = GraphDatabase()
|
||||||
|
|
||||||
from src.core.retriever import Retriever # noqa: E402
|
|
||||||
retriever = Retriever()
|
|
||||||
|
|||||||
@ -48,8 +48,6 @@ class Config(SimpleConfig):
|
|||||||
### >>> 默认配置
|
### >>> 默认配置
|
||||||
# 功能选项
|
# 功能选项
|
||||||
self.add_item("enable_reranker", default=False, des="是否开启重排序")
|
self.add_item("enable_reranker", default=False, des="是否开启重排序")
|
||||||
self.add_item("enable_knowledge_base", default=False, des="是否开启知识库")
|
|
||||||
self.add_item("enable_knowledge_graph", default=False, des="是否开启知识图谱")
|
|
||||||
self.add_item("enable_web_search", default=False, des="是否开启网页搜索(注:现阶段会根据 TAVILY_API_KEY 自动开启,无法手动配置,将会在下个版本移除此配置项)") # noqa: E501
|
self.add_item("enable_web_search", default=False, des="是否开启网页搜索(注:现阶段会根据 TAVILY_API_KEY 自动开启,无法手动配置,将会在下个版本移除此配置项)") # noqa: E501
|
||||||
# 默认智能体配置
|
# 默认智能体配置
|
||||||
self.add_item("default_agent_id", default="", des="默认智能体ID")
|
self.add_item("default_agent_id", default="", des="默认智能体ID")
|
||||||
@ -57,7 +55,7 @@ class Config(SimpleConfig):
|
|||||||
## 注意这里是模型名,而不是具体的模型路径,默认使用 HuggingFace 的路径
|
## 注意这里是模型名,而不是具体的模型路径,默认使用 HuggingFace 的路径
|
||||||
## 如果需要自定义本地模型路径,则在 src/.env 中配置 MODEL_DIR
|
## 如果需要自定义本地模型路径,则在 src/.env 中配置 MODEL_DIR
|
||||||
self.add_item("model_provider", default="siliconflow", des="模型提供商", choices=list(self.model_names.keys()))
|
self.add_item("model_provider", default="siliconflow", des="模型提供商", choices=list(self.model_names.keys()))
|
||||||
self.add_item("model_name", default="Qwen/Qwen2.5-7B-Instruct", des="模型名称")
|
self.add_item("model_name", default="Qwen/Qwen3-32B", des="模型名称")
|
||||||
|
|
||||||
self.add_item("embed_model", default="siliconflow/BAAI/bge-m3", des="Embedding 模型", choices=list(self.embed_model_names.keys()))
|
self.add_item("embed_model", default="siliconflow/BAAI/bge-m3", des="Embedding 模型", choices=list(self.embed_model_names.keys()))
|
||||||
self.add_item("reranker", default="siliconflow/BAAI/bge-reranker-v2-m3", des="Re-Ranker 模型", choices=list(self.reranker_names.keys())) # noqa: E501
|
self.add_item("reranker", default="siliconflow/BAAI/bge-reranker-v2-m3", des="Re-Ranker 模型", choices=list(self.reranker_names.keys())) # noqa: E501
|
||||||
|
|||||||
@ -31,9 +31,6 @@ class GraphDatabase:
|
|||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
if not config.enable_knowledge_graph or not config.enable_knowledge_base:
|
|
||||||
return
|
|
||||||
|
|
||||||
uri = os.environ.get("NEO4J_URI", "bolt://localhost:7687")
|
uri = os.environ.get("NEO4J_URI", "bolt://localhost:7687")
|
||||||
username = os.environ.get("NEO4J_USERNAME", "neo4j")
|
username = os.environ.get("NEO4J_USERNAME", "neo4j")
|
||||||
password = os.environ.get("NEO4J_PASSWORD", "0123456789")
|
password = os.environ.get("NEO4J_PASSWORD", "0123456789")
|
||||||
@ -46,7 +43,6 @@ class GraphDatabase:
|
|||||||
self.save_graph_info(self.kgdb_name)
|
self.save_graph_info(self.kgdb_name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to connect to Neo4j: {e}, {uri}, {self.kgdb_name}, {username}, {password}")
|
logger.error(f"Failed to connect to Neo4j: {e}, {uri}, {self.kgdb_name}, {username}, {password}")
|
||||||
self.config.enable_knowledge_graph = False
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""关闭数据库连接"""
|
"""关闭数据库连接"""
|
||||||
@ -54,10 +50,7 @@ class GraphDatabase:
|
|||||||
|
|
||||||
def is_running(self):
|
def is_running(self):
|
||||||
"""检查图数据库是否正在运行"""
|
"""检查图数据库是否正在运行"""
|
||||||
if not config.enable_knowledge_graph or not config.enable_knowledge_base:
|
return self.status == "open"
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return self.status == "open"
|
|
||||||
|
|
||||||
def get_sample_nodes(self, kgdb_name='neo4j', num=50):
|
def get_sample_nodes(self, kgdb_name='neo4j', num=50):
|
||||||
"""获取指定数据库的 num 个节点信息"""
|
"""获取指定数据库的 num 个节点信息"""
|
||||||
|
|||||||
@ -1,182 +0,0 @@
|
|||||||
import traceback
|
|
||||||
|
|
||||||
from src import config, knowledge_base, graph_base
|
|
||||||
from src.models.rerank_model import get_reranker
|
|
||||||
from src.utils.logging_config import logger
|
|
||||||
from src.models import select_model
|
|
||||||
from src.core.operators import HyDEOperator
|
|
||||||
|
|
||||||
class Retriever:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._load_models()
|
|
||||||
|
|
||||||
def _load_models(self):
|
|
||||||
if config.enable_reranker:
|
|
||||||
self.reranker = get_reranker()
|
|
||||||
|
|
||||||
if config.enable_web_search:
|
|
||||||
from src.utils.web_search import WebSearcher
|
|
||||||
self.web_searcher = WebSearcher()
|
|
||||||
|
|
||||||
def retrieval(self, query, history, meta):
|
|
||||||
refs = {"query": query, "history": history, "meta": meta}
|
|
||||||
refs["model_name"] = config.model_name
|
|
||||||
refs["entities"] = self.reco_entities(query, history, refs)
|
|
||||||
refs["knowledge_base"] = self.query_knowledgebase(query, history, refs)
|
|
||||||
refs["graph_base"] = self.query_graph(query, history, refs)
|
|
||||||
refs["web_search"] = self.query_web(query, history, refs)
|
|
||||||
|
|
||||||
return refs
|
|
||||||
|
|
||||||
def restart(self):
|
|
||||||
"""所有需要重启的模型"""
|
|
||||||
self._load_models()
|
|
||||||
|
|
||||||
def construct_query(self, query, refs, meta):
|
|
||||||
logger.debug(f"{refs=}")
|
|
||||||
if not refs or len(refs) == 0:
|
|
||||||
return query
|
|
||||||
|
|
||||||
external_parts = []
|
|
||||||
|
|
||||||
# 解析知识库的结果
|
|
||||||
kb_res = refs.get("knowledge_base", {}).get("results", [])
|
|
||||||
if kb_res:
|
|
||||||
kb_text = "\n".join(f"{r['id']}: {r['entity']['text']}" for r in kb_res)
|
|
||||||
external_parts.extend(["知识库信息:", kb_text])
|
|
||||||
|
|
||||||
# 解析图数据库的结果
|
|
||||||
db_res = refs.get("graph_base", {}).get("results", {})
|
|
||||||
if db_res.get("nodes") and len(db_res["nodes"]) > 0:
|
|
||||||
db_text = "\n".join(
|
|
||||||
[f"{edge['source_name']}和{edge['target_name']}的关系是{edge['type']}" for edge in db_res.get("edges", [])]
|
|
||||||
)
|
|
||||||
external_parts.extend(["图数据库信息:", db_text])
|
|
||||||
|
|
||||||
# 解析网络搜索的结果
|
|
||||||
web_res = refs.get("web_search", {}).get("results", [])
|
|
||||||
if web_res:
|
|
||||||
web_text = "\n".join(f"{r['title']}: {r['content']}" for r in web_res)
|
|
||||||
external_parts.extend(["网络搜索信息:", web_text])
|
|
||||||
|
|
||||||
# 构造查询
|
|
||||||
from src.utils.prompts import knowbase_qa_template
|
|
||||||
if external_parts and len(external_parts) > 0:
|
|
||||||
external = "\n\n".join(external_parts)
|
|
||||||
query = knowbase_qa_template.format(external=external, query=query)
|
|
||||||
|
|
||||||
return query
|
|
||||||
|
|
||||||
def query_classification(self, query):
|
|
||||||
"""判断是否需要查询
|
|
||||||
- 对于完全基于用户给定信息的任务,称之为"足够""sufficient",不需要检索;
|
|
||||||
- 否则,称之为"不足""insufficient",可能需要检索,
|
|
||||||
"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def query_graph(self, query, history, refs):
|
|
||||||
results = []
|
|
||||||
if refs["meta"].get("use_graph") and config.enable_knowledge_base:
|
|
||||||
for entity in refs["entities"]:
|
|
||||||
if entity == "":
|
|
||||||
continue
|
|
||||||
result = graph_base.query_node(entity)
|
|
||||||
if result != []:
|
|
||||||
results.extend(result)
|
|
||||||
return {"results": graph_base.format_query_result_to_graph(results)}
|
|
||||||
|
|
||||||
|
|
||||||
def query_knowledgebase(self, query, history, refs):
|
|
||||||
"""查询知识库"""
|
|
||||||
|
|
||||||
response = {
|
|
||||||
"results": [],
|
|
||||||
"all_results": [],
|
|
||||||
"rw_query": query,
|
|
||||||
"message": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
meta = refs["meta"]
|
|
||||||
|
|
||||||
db_id = meta.get("db_id")
|
|
||||||
if not db_id or not config.enable_knowledge_base:
|
|
||||||
response["message"] = "知识库未启用、或未指定知识库、或知识库不存在"
|
|
||||||
return response
|
|
||||||
|
|
||||||
rw_query = self.rewrite_query(query, history, refs)
|
|
||||||
|
|
||||||
logger.debug(f"{meta=}")
|
|
||||||
query_result = knowledge_base.query(query_text=rw_query,
|
|
||||||
db_id=db_id,
|
|
||||||
distance_threshold=meta.get("distanceThreshold", 0.5),
|
|
||||||
rerank_threshold=meta.get("rerankThreshold", 0.1),
|
|
||||||
max_query_count=meta.get("maxQueryCount", 20),
|
|
||||||
top_k=meta.get("topK", 10))
|
|
||||||
|
|
||||||
response["results"] = query_result["results"]
|
|
||||||
response["all_results"] = query_result["all_results"]
|
|
||||||
response["rw_query"] = rw_query
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def query_web(self, query, history, refs):
|
|
||||||
"""查询网络"""
|
|
||||||
|
|
||||||
if not (refs["meta"].get("use_web") or not config.enable_web_search):
|
|
||||||
return {"results": [], "message": "Web search is disabled"}
|
|
||||||
|
|
||||||
try:
|
|
||||||
search_results = self.web_searcher.search(query, max_results=5)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Web search error: {str(e)}")
|
|
||||||
return {"results": [], "message": "Web search error"}
|
|
||||||
|
|
||||||
return {"results": search_results}
|
|
||||||
|
|
||||||
def rewrite_query(self, query, history, refs):
|
|
||||||
"""重写查询"""
|
|
||||||
model_provider = config.model_provider
|
|
||||||
model_name = config.model_name
|
|
||||||
model = select_model(model_provider=model_provider, model_name=model_name)
|
|
||||||
if refs["meta"].get("mode") == "search": # 如果是搜索模式,就使用 meta 的配置,否则就使用全局的配置
|
|
||||||
rewrite_query_span = refs["meta"].get("use_rewrite_query", "off")
|
|
||||||
else:
|
|
||||||
rewrite_query_span = config.use_rewrite_query
|
|
||||||
|
|
||||||
if rewrite_query_span == "off":
|
|
||||||
return query
|
|
||||||
|
|
||||||
from src.utils.prompts import rewritten_query_prompt_template2 as rw_template
|
|
||||||
history_query = [entry["content"] for entry in history if entry["role"] == "user"] if history else ""
|
|
||||||
rewritten_query_prompt = rw_template.format(history=history_query, query=query)
|
|
||||||
rewritten_query = model.predict(rewritten_query_prompt).content
|
|
||||||
|
|
||||||
if rewrite_query_span == "hyde":
|
|
||||||
res = HyDEOperator.call(model_callable=model.predict, query=query, context_str=history_query)
|
|
||||||
rewritten_query = res.content
|
|
||||||
|
|
||||||
return rewritten_query
|
|
||||||
|
|
||||||
def reco_entities(self, query, history, refs):
|
|
||||||
"""识别句子中的实体"""
|
|
||||||
query = refs.get("rewritten_query", query)
|
|
||||||
model_provider = config.model_provider
|
|
||||||
model_name = config.model_name
|
|
||||||
model = select_model(model_provider=model_provider, model_name=model_name)
|
|
||||||
|
|
||||||
entities = []
|
|
||||||
if refs["meta"].get("use_graph"):
|
|
||||||
from src.utils.prompts import entity_extraction_prompt_template as entity_template
|
|
||||||
# from src.utils.prompts import keywords_prompt_template as entity_templat|e
|
|
||||||
|
|
||||||
entity_extraction_prompt = entity_template.format(text=query)
|
|
||||||
entities = model.predict(entity_extraction_prompt).content.split("<->")
|
|
||||||
# entities = [entity for entity in entities if all(char.isalnum() or char in "汉字" for char in entity)]
|
|
||||||
|
|
||||||
return entities
|
|
||||||
|
|
||||||
def __call__(self, query, history, meta):
|
|
||||||
refs = self.retrieval(query, history, meta)
|
|
||||||
query = self.construct_query(query, refs, meta)
|
|
||||||
return query, refs
|
|
||||||
@ -182,9 +182,6 @@ class OtherEmbedding(BaseEmbeddingModel):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_embedding_model():
|
def get_embedding_model():
|
||||||
if not config.enable_knowledge_base:
|
|
||||||
return None
|
|
||||||
|
|
||||||
provider, model_name = config.embed_model.split('/', 1)
|
provider, model_name = config.embed_model.split('/', 1)
|
||||||
support_embed_models = config.embed_model_names.keys()
|
support_embed_models = config.embed_model_names.keys()
|
||||||
assert config.embed_model in support_embed_models, f"Unsupported embed model: {config.embed_model}, only support {support_embed_models}"
|
assert config.embed_model in support_embed_models, f"Unsupported embed model: {config.embed_model}, only support {support_embed_models}"
|
||||||
|
|||||||
@ -98,14 +98,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:class="{'switch': true, 'opt-item': true, 'active': meta.use_graph}"
|
:class="{'switch': true, 'opt-item': true, 'active': meta.use_graph}"
|
||||||
v-if="configStore.config.enable_knowledge_graph"
|
|
||||||
@click="meta.use_graph=!meta.use_graph"
|
@click="meta.use_graph=!meta.use_graph"
|
||||||
>
|
>
|
||||||
<Waypoints style="margin-right: 3px;" size="14"/>
|
<Waypoints style="margin-right: 3px;" size="14"/>
|
||||||
知识图谱
|
知识图谱
|
||||||
</div>
|
</div>
|
||||||
<a-dropdown
|
<a-dropdown
|
||||||
v-if="configStore.config.enable_knowledge_base && opts.databases.length > 0"
|
v-if="opts.databases.length > 0"
|
||||||
:class="{'opt-item': true, 'active': meta.selectedKB !== null}"
|
:class="{'opt-item': true, 'active': meta.selectedKB !== null}"
|
||||||
>
|
>
|
||||||
<a class="ant-dropdown-link" @click.prevent>
|
<a class="ant-dropdown-link" @click.prevent>
|
||||||
|
|||||||
@ -27,14 +27,35 @@ const layoutSettings = reactive({
|
|||||||
const githubStars = ref(0)
|
const githubStars = ref(0)
|
||||||
const isLoadingStars = ref(false)
|
const isLoadingStars = ref(false)
|
||||||
|
|
||||||
|
// Add state for debug modal
|
||||||
|
const showDebugModal = ref(false)
|
||||||
|
const htmlRefHook = useTemplateRef('htmlRefHook')
|
||||||
|
|
||||||
|
// Setup long press for debug modal
|
||||||
|
onLongPress(
|
||||||
|
htmlRefHook,
|
||||||
|
() => {
|
||||||
|
console.log('long press')
|
||||||
|
showDebugModal.value = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
delay: 1000, // 1秒长按
|
||||||
|
modifiers: {
|
||||||
|
prevent: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle debug modal close
|
||||||
|
const handleDebugModalClose = () => {
|
||||||
|
showDebugModal.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const getRemoteConfig = () => {
|
const getRemoteConfig = () => {
|
||||||
configStore.refreshConfig()
|
configStore.refreshConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRemoteDatabase = () => {
|
const getRemoteDatabase = () => {
|
||||||
if (!configStore.config.enable_knowledge_base) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
databaseStore.refreshDatabase()
|
databaseStore.refreshDatabase()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,11 +89,6 @@ console.log(route)
|
|||||||
|
|
||||||
// 下面是导航菜单部分,添加智能体项
|
// 下面是导航菜单部分,添加智能体项
|
||||||
const mainList = [{
|
const mainList = [{
|
||||||
name: '对话',
|
|
||||||
path: '/chat',
|
|
||||||
icon: MessageSquareMore,
|
|
||||||
activeIcon: MessageSquareMore,
|
|
||||||
}, {
|
|
||||||
name: '智能体',
|
name: '智能体',
|
||||||
path: '/agent',
|
path: '/agent',
|
||||||
icon: Bot,
|
icon: Bot,
|
||||||
@ -82,13 +98,11 @@ const mainList = [{
|
|||||||
path: '/graph',
|
path: '/graph',
|
||||||
icon: Waypoints,
|
icon: Waypoints,
|
||||||
activeIcon: Waypoints,
|
activeIcon: Waypoints,
|
||||||
// hidden: !configStore.config.enable_knowledge_graph,
|
|
||||||
}, {
|
}, {
|
||||||
name: '知识库',
|
name: '知识库',
|
||||||
path: '/database',
|
path: '/database',
|
||||||
icon: LibraryBig,
|
icon: LibraryBig,
|
||||||
activeIcon: LibraryBig,
|
activeIcon: LibraryBig,
|
||||||
// hidden: !configStore.config.enable_knowledge_base,
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -25,19 +25,6 @@ const router = createRouter({
|
|||||||
component: () => import('../views/LoginView.vue'),
|
component: () => import('../views/LoginView.vue'),
|
||||||
meta: { requiresAuth: false }
|
meta: { requiresAuth: false }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/chat',
|
|
||||||
name: 'chat',
|
|
||||||
component: AppLayout,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
name: 'ChatComp',
|
|
||||||
component: () => import('../views/ChatView.vue'),
|
|
||||||
meta: { keepAlive: true, requiresAuth: true, requiresAdmin: true }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/agent',
|
path: '/agent',
|
||||||
name: 'AgentMain',
|
name: 'AgentMain',
|
||||||
|
|||||||
@ -1,288 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="chat-container">
|
|
||||||
<div class="conversations" :class="{ 'is-open': state.isSidebarOpen }">
|
|
||||||
<div class="actions">
|
|
||||||
<!-- <div class="action new" @click="addNewConv"><FormOutlined /></div> -->
|
|
||||||
<span class="header-title">Yuxi-Know</span>
|
|
||||||
<div class="action close" @click="state.isSidebarOpen = false">
|
|
||||||
<PanelLeftClose size="20" color="var(--gray-800)"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="conversation-list">
|
|
||||||
<div class="conversation"
|
|
||||||
v-for="(state, index) in convs"
|
|
||||||
:key="index"
|
|
||||||
:class="{ active: curConvId === index }"
|
|
||||||
@click="goToConversation(index)">
|
|
||||||
<div class="conversation__title">{{ state.title }}</div>
|
|
||||||
<div class="conversation__delete" @click.stop="delConv(index)"><DeleteOutlined /></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ChatComponent
|
|
||||||
:conv="convs[curConvId]"
|
|
||||||
:state="state"
|
|
||||||
@rename-title="renameTitle"
|
|
||||||
@newconv="addNewConv"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { reactive, ref, watch, onMounted } from 'vue'
|
|
||||||
import { DeleteOutlined } from '@ant-design/icons-vue'
|
|
||||||
import ChatComponent from '@/components/ChatComponent.vue'
|
|
||||||
import { MessageSquareMore, PanelLeftClose } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
const convs = reactive(JSON.parse(localStorage.getItem('chat-convs')) || [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
title: '新对话',
|
|
||||||
history: [],
|
|
||||||
messages: [],
|
|
||||||
inputText: ''
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
isSidebarOpen: JSON.parse(localStorage.getItem('chat-sidebar-open') || 'true'),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Watch isSidebarOpen and save to localStorage
|
|
||||||
watch(
|
|
||||||
() => state.isSidebarOpen,
|
|
||||||
(newValue) => {
|
|
||||||
localStorage.setItem('chat-sidebar-open', JSON.stringify(newValue))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const curConvId = ref(0)
|
|
||||||
|
|
||||||
const generateRandomHash = (length) => {
|
|
||||||
let chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
||||||
let hash = '';
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
hash += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
const renameTitle = (newTitle) => {
|
|
||||||
convs[curConvId.value].title = newTitle
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToConversation = (index) => {
|
|
||||||
curConvId.value = index
|
|
||||||
console.log(convs[curConvId.value])
|
|
||||||
}
|
|
||||||
|
|
||||||
const addNewConv = () => {
|
|
||||||
curConvId.value = 0
|
|
||||||
if (convs.length > 0 && convs[0].messages.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
convs.unshift({
|
|
||||||
id: generateRandomHash(8),
|
|
||||||
title: `新对话`,
|
|
||||||
history: [],
|
|
||||||
messages: [],
|
|
||||||
inputText: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const delConv = (index) => {
|
|
||||||
convs.splice(index, 1)
|
|
||||||
|
|
||||||
if (index < curConvId.value) {
|
|
||||||
curConvId.value -= 1
|
|
||||||
} else if (index === curConvId.value) {
|
|
||||||
curConvId.value = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (convs.length === 0) {
|
|
||||||
addNewConv()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch convs and save to localStorage
|
|
||||||
watch(
|
|
||||||
() => convs,
|
|
||||||
(newStates) => {
|
|
||||||
localStorage.setItem('chat-convs', JSON.stringify(newStates))
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
// Load convs from localStorage on mount
|
|
||||||
onMounted(() => {
|
|
||||||
const savedSonvs = JSON.parse(localStorage.getItem('chat-convs'))
|
|
||||||
if (savedSonvs) {
|
|
||||||
for (let i = 0; i < savedSonvs.length; i++) {
|
|
||||||
convs[i] = savedSonvs[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
@import '@/assets/main.css';
|
|
||||||
|
|
||||||
.chat-container {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversations {
|
|
||||||
width: 230px;
|
|
||||||
max-width: 230px;
|
|
||||||
border-right: 1px solid var(--main-light-3);
|
|
||||||
background-color: var(--bg-sider);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
white-space: nowrap; /* 防止文本换行 */
|
|
||||||
overflow: hidden; /* 确保内容不溢出 */
|
|
||||||
|
|
||||||
&.is-open {
|
|
||||||
width: 230px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.is-open) {
|
|
||||||
width: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .actions {
|
|
||||||
height: var(--header-height);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
z-index: 9;
|
|
||||||
border-bottom: 1px solid var(--main-light-3);
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-weight: bold;
|
|
||||||
user-select: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 2.5rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: var(--gray-800);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--main-light-3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-btn-icon {
|
|
||||||
width: 1.2rem;
|
|
||||||
height: 1.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list .conversation {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 100%;
|
|
||||||
user-select: none;
|
|
||||||
transition: background-color 0.2s ease-in-out;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
color: var(--gray-700);
|
|
||||||
white-space: nowrap; /* 禁止换行 */
|
|
||||||
overflow: hidden; /* 超出部分隐藏 */
|
|
||||||
text-overflow: ellipsis; /* 显示省略号 */
|
|
||||||
}
|
|
||||||
|
|
||||||
&__delete {
|
|
||||||
display: none;
|
|
||||||
color: var(--gray-500);
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #F93A37;
|
|
||||||
background-color: #EEE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
border-right: 3px solid var(--main-500);
|
|
||||||
padding-right: 13px;
|
|
||||||
background-color: var(--gray-200);
|
|
||||||
|
|
||||||
& .conversation__title {
|
|
||||||
color: var(--gray-1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.active):hover {
|
|
||||||
background-color: var(--main-light-3);
|
|
||||||
|
|
||||||
& .conversation__delete {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list::-webkit-scrollbar {
|
|
||||||
position: absolute;
|
|
||||||
width: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--gray-400);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: rgb(100, 100, 100);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list::-webkit-scrollbar-thumb:active {
|
|
||||||
background: rgb(68, 68, 68);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 520px) {
|
|
||||||
.conversations {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 101;
|
|
||||||
width: 300px;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 0 16px 16px 0;
|
|
||||||
box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
&:not(.is-open) {
|
|
||||||
width: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -71,7 +71,7 @@ const goToChat = async () => {
|
|||||||
// 根据用户角色进行跳转
|
// 根据用户角色进行跳转
|
||||||
if (userStore.isAdmin) {
|
if (userStore.isAdmin) {
|
||||||
// 管理员用户跳转到聊天页面
|
// 管理员用户跳转到聊天页面
|
||||||
router.push('/chat');
|
router.push('/agent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -221,7 +221,7 @@ const handleLogin = async () => {
|
|||||||
if (redirectPath === '/') {
|
if (redirectPath === '/') {
|
||||||
// 如果是管理员,直接跳转到/chat页面
|
// 如果是管理员,直接跳转到/chat页面
|
||||||
if (userStore.isAdmin) {
|
if (userStore.isAdmin) {
|
||||||
router.push('/chat');
|
router.push('/agent');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
<div class="sider" v-if="state.windowWidth > 520">
|
<div class="sider" v-if="state.windowWidth > 520">
|
||||||
<a-button type="text" v-if="userStore.isSuperAdmin" :class="{ activesec: state.section === 'base'}" @click="state.section='base'" :icon="h(SettingOutlined)"> 基本设置 </a-button>
|
<a-button type="text" v-if="userStore.isSuperAdmin" :class="{ activesec: state.section === 'base'}" @click="state.section='base'" :icon="h(SettingOutlined)"> 基本设置 </a-button>
|
||||||
<a-button type="text" v-if="userStore.isSuperAdmin" :class="{ activesec: state.section === 'model'}" @click="state.section='model'" :icon="h(CodeOutlined)"> 模型配置 </a-button>
|
<a-button type="text" v-if="userStore.isSuperAdmin" :class="{ activesec: state.section === 'model'}" @click="state.section='model'" :icon="h(CodeOutlined)"> 模型配置 </a-button>
|
||||||
<a-button type="text" v-if="userStore.isSuperAdmin" :class="{ activesec: state.section === 'path'}" @click="state.section='path'" :icon="h(FolderOutlined)"> 路径配置 </a-button>
|
|
||||||
<a-button type="text" :class="{ activesec: state.section === 'user'}" @click="state.section='user'" :icon="h(UserOutlined)" v-if="userStore.isAdmin"> 用户管理 </a-button>
|
<a-button type="text" :class="{ activesec: state.section === 'user'}" @click="state.section='user'" :icon="h(UserOutlined)" v-if="userStore.isAdmin"> 用户管理 </a-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting" v-if="(state.windowWidth <= 520 || state.section === 'base') && userStore.isSuperAdmin">
|
<div class="setting" v-if="(state.windowWidth <= 520 || state.section === 'base') && userStore.isSuperAdmin">
|
||||||
@ -72,23 +71,6 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3>功能配置</h3>
|
|
||||||
<div class="section">
|
|
||||||
<div class="card">
|
|
||||||
<span class="label">{{ items?.enable_knowledge_base.des }}</span>
|
|
||||||
<a-switch
|
|
||||||
:checked="configStore.config.enable_knowledge_base"
|
|
||||||
@change="handleChange('enable_knowledge_base', !configStore.config.enable_knowledge_base)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<span class="label">{{ items?.enable_knowledge_graph.des }}</span>
|
|
||||||
<a-switch
|
|
||||||
:checked="configStore.config.enable_knowledge_graph"
|
|
||||||
@change="handleChange('enable_knowledge_graph', !configStore.config.enable_knowledge_graph)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="setting" v-if="(state.windowWidth <= 520 || state.section === 'model') && userStore.isSuperAdmin">
|
<div class="setting" v-if="(state.windowWidth <= 520 || state.section === 'model') && userStore.isSuperAdmin">
|
||||||
<h3>模型配置</h3>
|
<h3>模型配置</h3>
|
||||||
@ -138,19 +120,8 @@ const handleModelLocalPathsUpdate = (config) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const preHandleChange = (key, e) => {
|
const preHandleChange = (key, e) => {
|
||||||
if (key == 'enable_knowledge_graph' && e && !configStore.config.enable_knowledge_base) {
|
|
||||||
message.error('启动知识图谱必须请先启用知识库功能')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == 'enable_knowledge_base' && !e && configStore.config.enable_knowledge_graph) {
|
|
||||||
message.error('关闭知识库功能必须请先关闭知识图谱功能')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key == 'enable_reranker'
|
if (key == 'enable_reranker'
|
||||||
|| key == 'enable_knowledge_graph'
|
|
||||||
|| key == 'enable_knowledge_base'
|
|
||||||
|| key == 'embed_model'
|
|| key == 'embed_model'
|
||||||
|| key == 'reranker'
|
|| key == 'reranker'
|
||||||
|| key == 'model_local_paths') {
|
|| key == 'model_local_paths') {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user