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)):
|
||||
knowledge_base.restart()
|
||||
graph_base.start()
|
||||
retriever.restart()
|
||||
return {"message": "Restarted!"}
|
||||
|
||||
@base.get("/log")
|
||||
|
||||
@ -10,7 +10,7 @@ from langchain_core.messages import AIMessageChunk, HumanMessage
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
|
||||
from src import executor, config, retriever
|
||||
from src import executor, config
|
||||
from src.core import HistoryManager
|
||||
from src.agents import agent_manager
|
||||
from src.models import select_model
|
||||
@ -67,82 +67,6 @@ async def chat_get(current_user: User = Depends(get_required_user)):
|
||||
"""聊天服务健康检查(需要登录)"""
|
||||
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")
|
||||
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
|
||||
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_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("default_agent_id", default="", des="默认智能体ID")
|
||||
@ -57,7 +55,7 @@ class Config(SimpleConfig):
|
||||
## 注意这里是模型名,而不是具体的模型路径,默认使用 HuggingFace 的路径
|
||||
## 如果需要自定义本地模型路径,则在 src/.env 中配置 MODEL_DIR
|
||||
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("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()
|
||||
|
||||
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")
|
||||
username = os.environ.get("NEO4J_USERNAME", "neo4j")
|
||||
password = os.environ.get("NEO4J_PASSWORD", "0123456789")
|
||||
@ -46,7 +43,6 @@ class GraphDatabase:
|
||||
self.save_graph_info(self.kgdb_name)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to Neo4j: {e}, {uri}, {self.kgdb_name}, {username}, {password}")
|
||||
self.config.enable_knowledge_graph = False
|
||||
|
||||
def close(self):
|
||||
"""关闭数据库连接"""
|
||||
@ -54,10 +50,7 @@ class GraphDatabase:
|
||||
|
||||
def is_running(self):
|
||||
"""检查图数据库是否正在运行"""
|
||||
if not config.enable_knowledge_graph or not config.enable_knowledge_base:
|
||||
return False
|
||||
else:
|
||||
return self.status == "open"
|
||||
return self.status == "open"
|
||||
|
||||
def get_sample_nodes(self, kgdb_name='neo4j', num=50):
|
||||
"""获取指定数据库的 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():
|
||||
if not config.enable_knowledge_base:
|
||||
return None
|
||||
|
||||
provider, model_name = config.embed_model.split('/', 1)
|
||||
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}"
|
||||
|
||||
@ -98,14 +98,13 @@
|
||||
</div>
|
||||
<div
|
||||
:class="{'switch': true, 'opt-item': true, 'active': meta.use_graph}"
|
||||
v-if="configStore.config.enable_knowledge_graph"
|
||||
@click="meta.use_graph=!meta.use_graph"
|
||||
>
|
||||
<Waypoints style="margin-right: 3px;" size="14"/>
|
||||
知识图谱
|
||||
</div>
|
||||
<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}"
|
||||
>
|
||||
<a class="ant-dropdown-link" @click.prevent>
|
||||
|
||||
@ -27,14 +27,35 @@ const layoutSettings = reactive({
|
||||
const githubStars = ref(0)
|
||||
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 = () => {
|
||||
configStore.refreshConfig()
|
||||
}
|
||||
|
||||
const getRemoteDatabase = () => {
|
||||
if (!configStore.config.enable_knowledge_base) {
|
||||
return
|
||||
}
|
||||
databaseStore.refreshDatabase()
|
||||
}
|
||||
|
||||
@ -68,11 +89,6 @@ console.log(route)
|
||||
|
||||
// 下面是导航菜单部分,添加智能体项
|
||||
const mainList = [{
|
||||
name: '对话',
|
||||
path: '/chat',
|
||||
icon: MessageSquareMore,
|
||||
activeIcon: MessageSquareMore,
|
||||
}, {
|
||||
name: '智能体',
|
||||
path: '/agent',
|
||||
icon: Bot,
|
||||
@ -82,13 +98,11 @@ const mainList = [{
|
||||
path: '/graph',
|
||||
icon: Waypoints,
|
||||
activeIcon: Waypoints,
|
||||
// hidden: !configStore.config.enable_knowledge_graph,
|
||||
}, {
|
||||
name: '知识库',
|
||||
path: '/database',
|
||||
icon: LibraryBig,
|
||||
activeIcon: LibraryBig,
|
||||
// hidden: !configStore.config.enable_knowledge_base,
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
@ -25,19 +25,6 @@ const router = createRouter({
|
||||
component: () => import('../views/LoginView.vue'),
|
||||
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',
|
||||
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) {
|
||||
// 管理员用户跳转到聊天页面
|
||||
router.push('/chat');
|
||||
router.push('/agent');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -221,7 +221,7 @@ const handleLogin = async () => {
|
||||
if (redirectPath === '/') {
|
||||
// 如果是管理员,直接跳转到/chat页面
|
||||
if (userStore.isAdmin) {
|
||||
router.push('/chat');
|
||||
router.push('/agent');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
<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 === '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>
|
||||
</div>
|
||||
<div class="setting" v-if="(state.windowWidth <= 520 || state.section === 'base') && userStore.isSuperAdmin">
|
||||
@ -72,23 +71,6 @@
|
||||
</a-select>
|
||||
</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 class="setting" v-if="(state.windowWidth <= 520 || state.section === 'model') && userStore.isSuperAdmin">
|
||||
<h3>模型配置</h3>
|
||||
@ -138,19 +120,8 @@ const handleModelLocalPathsUpdate = (config) => {
|
||||
}
|
||||
|
||||
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'
|
||||
|| key == 'enable_knowledge_graph'
|
||||
|| key == 'enable_knowledge_base'
|
||||
|| key == 'embed_model'
|
||||
|| key == 'reranker'
|
||||
|| key == 'model_local_paths') {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user