feat: 更新依赖并优化组件
- 在 pyproject.toml 中添加了 colorlog、langchain-deepseek 和 langchain-together 依赖。 - 修改了 chat_router.py 中的日志记录方式。 - 重命名 tools_factory.py 中的函数以更好地描述其功能。 - 更新了配置文件以支持新的模型选择。 - 优化了前端组件的样式和功能,包括侧边栏和消息输入框的交互体验。
This commit is contained in:
parent
e803a79813
commit
44fad724bf
@ -5,13 +5,16 @@ description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"colorlog>=6.9.0",
|
||||
"dashscope>=1.23.2",
|
||||
"docx2txt>=0.9",
|
||||
"fastapi>=0.115.12",
|
||||
"flagembedding>=1.3.4",
|
||||
"langchain-community>=0.3.22",
|
||||
"langchain-deepseek>=0.1.3",
|
||||
"langchain-huggingface>=0.2.0",
|
||||
"langchain-openai>=0.3.14",
|
||||
"langchain-together>=0.3.0",
|
||||
"langgraph>=0.3.34",
|
||||
"langgraph-checkpoint-sqlite>=2.0.7",
|
||||
"langsmith>=0.3.37",
|
||||
|
||||
@ -213,7 +213,7 @@ async def chat_agent(agent_name: str,
|
||||
|
||||
try:
|
||||
async for msg, metadata in agent.stream_messages(messages, config_schema=runnable_config):
|
||||
logger.debug(f"msg: {msg.model_dump()}, metadata: {metadata}")
|
||||
# logger.debug(f"msg: {msg.model_dump()}, metadata: {metadata}")
|
||||
if isinstance(msg, AIMessageChunk):
|
||||
yield make_chunk(content=msg.content,
|
||||
msg=msg.model_dump(),
|
||||
|
||||
@ -49,8 +49,8 @@ class ChatbotConfiguration(Configuration):
|
||||
"configurable": True,
|
||||
"options": [
|
||||
"zhipu/glm-4-plus",
|
||||
"siliconflow/Qwen/QwQ-32B",
|
||||
"siliconflow/deepseek-ai/DeepSeek-V3",
|
||||
"siliconflow/Qwen/Qwen2.5-72B-Instruct",
|
||||
"siliconflow/Qwen/Qwen2.5-7B-Instruct",
|
||||
],
|
||||
"description": "智能体的驱动模型"
|
||||
},
|
||||
|
||||
@ -1,18 +1,9 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from typing import Any
|
||||
from datetime import datetime
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.prebuilt import ToolNode, tools_condition
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
|
||||
|
||||
from src.agents.registry import State, BaseAgent
|
||||
from src.agents.utils import load_chat_model
|
||||
from src.agents.react.configuration import ReActConfiguration, multiply
|
||||
from src.agents.registry import BaseAgent
|
||||
from src.agents.react.configuration import ReActConfiguration
|
||||
|
||||
class ReActAgent(BaseAgent):
|
||||
name = "react"
|
||||
|
||||
@ -133,17 +133,17 @@ def calculator(a: float, b: float, operation: str) -> float:
|
||||
raise ValueError(f"Invalid operation: {operation}, only support add, subtract, multiply, divide")
|
||||
|
||||
@tool
|
||||
def get_knowledge_graph(query: Annotated[str, "The query to get knowledge graph."]):
|
||||
"""Use this to get knowledge graph."""
|
||||
def query_knowledge_graph(query: Annotated[str, "The keyword to query knowledge graph."]):
|
||||
"""Use this to query knowledge graph."""
|
||||
return graph_base.query_node(query, hops=2)
|
||||
|
||||
|
||||
|
||||
|
||||
_TOOLS_REGISTRY = {
|
||||
"calculator": calculator,
|
||||
"get_knowledge_graph": get_knowledge_graph,
|
||||
"Calculator": calculator,
|
||||
"QueryKnowledgeGraph": query_knowledge_graph,
|
||||
}
|
||||
|
||||
if config.enable_web_search:
|
||||
_TOOLS_REGISTRY["TavilySearchResults"] = TavilySearchResults(max_results=10)
|
||||
_TOOLS_REGISTRY["WebSearchWithTavily"] = TavilySearchResults(max_results=10)
|
||||
|
||||
@ -30,6 +30,32 @@ def select_model(model_provider=None, model_name=None):
|
||||
from src.models.chat_model import OpenModel
|
||||
return OpenModel(model_name)
|
||||
|
||||
if model_provider == "deepseek":
|
||||
from langchain_deepseek import ChatDeepSeek
|
||||
return OpenAIBase(
|
||||
api_key=os.getenv(model_info["env"][0]),
|
||||
base_url=model_info["base_url"],
|
||||
model_name=model_name,
|
||||
chat_open_ai=ChatDeepSeek(
|
||||
model=model_name,
|
||||
api_key=os.getenv(model_info["env"][0]),
|
||||
base_url=model_info["base_url"],
|
||||
)
|
||||
)
|
||||
|
||||
if model_provider == "together":
|
||||
from langchain_together import ChatTogether
|
||||
return OpenAIBase(
|
||||
api_key=os.getenv(model_info["env"][0]),
|
||||
base_url=model_info["base_url"],
|
||||
model_name=model_name,
|
||||
chat_open_ai=ChatTogether(
|
||||
model=model_name,
|
||||
api_key=os.getenv(model_info["env"][0]),
|
||||
base_url=model_info["base_url"],
|
||||
)
|
||||
)
|
||||
|
||||
if model_provider == "custom":
|
||||
model_info = next((x for x in config.custom_models if x["custom_id"] == model_name), None)
|
||||
if model_info is None:
|
||||
|
||||
@ -5,13 +5,13 @@ from src.utils import logger, get_docker_safe_url
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
class OpenAIBase():
|
||||
def __init__(self, api_key, base_url, model_name, **kwargs):
|
||||
def __init__(self, api_key, base_url, model_name, chat_open_ai=None, **kwargs):
|
||||
self.api_key = api_key
|
||||
self.base_url = base_url
|
||||
self.client = OpenAI(api_key=api_key, base_url=base_url)
|
||||
self.model_name = model_name
|
||||
self.info = kwargs
|
||||
self.chat_open_ai = ChatOpenAI(model=model_name,
|
||||
self.chat_open_ai = chat_open_ai or ChatOpenAI(model=model_name,
|
||||
api_key=api_key,
|
||||
base_url=base_url)
|
||||
|
||||
|
||||
@ -70,6 +70,7 @@ MODEL_NAMES:
|
||||
- meta-llama/Llama-3.3-70B-Instruct-Turbo
|
||||
- meta-llama/Llama-3.3-70B-Instruct-Turbo-Free
|
||||
- deepseek-ai/DeepSeek-R1-Distill-Llama-70B-free
|
||||
- Qwen/QwQ-32B
|
||||
|
||||
dashscope:
|
||||
name: 阿里百炼 (DashScope)
|
||||
|
||||
@ -2,8 +2,10 @@ import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
from colorlog import ColoredFormatter
|
||||
|
||||
DATETIME = datetime.now().strftime('%Y-%m-%d-%H%M%S')
|
||||
DATETIME = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%Y-%m-%d-%H%M%S')
|
||||
# DATETIME = "debug" # 为了方便,调试的时候输出到 debug.log 文件
|
||||
LOG_FILE = f'saves/log/project-{DATETIME}.log'
|
||||
|
||||
@ -18,20 +20,29 @@ def setup_logger(name, level=logging.DEBUG, console=True):
|
||||
if logger.hasHandlers():
|
||||
logger.handlers.clear()
|
||||
|
||||
# File handler for logging to a file
|
||||
file_handler = logging.FileHandler(LOG_FILE)
|
||||
# 文件日志(无颜色)
|
||||
file_handler = logging.FileHandler(LOG_FILE, encoding='utf-8')
|
||||
file_handler.setLevel(level)
|
||||
|
||||
# Formatter for the logs
|
||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
file_handler.setFormatter(formatter)
|
||||
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
file_handler.setFormatter(file_formatter)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
# Console handler for logging to the console (optional)
|
||||
# 控制台日志(有颜色)
|
||||
if console:
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(level)
|
||||
console_handler.setFormatter(formatter)
|
||||
color_formatter = ColoredFormatter(
|
||||
"%(log_color)s%(asctime)s - %(levelname)s - %(name)s - %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S",
|
||||
log_colors={
|
||||
'DEBUG': 'cyan',
|
||||
'INFO': 'green',
|
||||
'WARNING': 'yellow',
|
||||
'ERROR': 'red',
|
||||
'CRITICAL': 'bold_red',
|
||||
}
|
||||
)
|
||||
console_handler.setFormatter(color_formatter)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
return logger
|
||||
|
||||
46
uv.lock
46
uv.lock
@ -425,6 +425,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorlog"
|
||||
version = "6.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d3/7a/359f4d5df2353f26172b3cc39ea32daa39af8de522205f512f458923e677/colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2", size = 16624, upload-time = "2024-10-29T18:34:51.011Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/51/9b208e85196941db2f0654ad0357ca6388ab3ed67efdbfc799f35d1f83aa/colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff", size = 11424, upload-time = "2024-10-29T18:34:49.815Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "44.0.2"
|
||||
@ -1282,6 +1294,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/40/aa440a7cd05f1dab5d7c91a1284eb776c3cf3eb59fa18ed39927650cfa38/langchain_core-0.3.59-py3-none-any.whl", hash = "sha256:9686baaff43f2c8175535da13faf40e6866769015e93130c3c1e4243e7244d70", size = 437656, upload-time = "2025-05-07T17:58:22.251Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langchain-deepseek"
|
||||
version = "0.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
{ name = "langchain-openai" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/7f/be5bcf99b3814214a02ac205bda66d49d55a7d5440d47223105cef5df063/langchain_deepseek-0.1.3.tar.gz", hash = "sha256:89dd6aa120fb50dcfcd3d593626d34c1c40deefe4510710d0807fcc19481adf5", size = 7860, upload-time = "2025-03-21T17:11:58.356Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/00/7d/51b60aa91fa77742fc461704e5a8497e856156ae878102e6942799a78915/langchain_deepseek-0.1.3-py3-none-any.whl", hash = "sha256:8588e826371b417fca65c02f4273b4061eb9815a7bfcd5eb05acaa40d603aa89", size = 7123, upload-time = "2025-03-21T17:11:57.481Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langchain-huggingface"
|
||||
version = "0.2.0"
|
||||
@ -1324,6 +1349,21 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/a3/3696ff2444658053c01b6b7443e761f28bb71217d82bb89137a978c5f66f/langchain_text_splitters-0.3.8-py3-none-any.whl", hash = "sha256:e75cc0f4ae58dcf07d9f18776400cf8ade27fadd4ff6d264df6278bb302f6f02", size = 32440, upload-time = "2025-04-04T14:03:50.6Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langchain-together"
|
||||
version = "0.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "langchain-core" },
|
||||
{ name = "langchain-openai" },
|
||||
{ name = "requests" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/24/c4/64b92524121eaf4e805ade7ea68e71d52eab5e8fdad9b6b8e62f5a99eff4/langchain_together-0.3.0.tar.gz", hash = "sha256:c8a96377e49c065526435f766c6e1c7da3f7d054361326f079de8bd368ea76f2", size = 10247, upload-time = "2025-01-10T17:06:08.729Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/69/b3cbcf5b43acbc098c012ef75035fb0dc1e0f227f5161329ef9884a25ba4/langchain_together-0.3.0-py3-none-any.whl", hash = "sha256:4dcb4f6858c910c23d2268da1ed5f54e8cd01224ecf086dc7a8adbacdc6cb686", size = 12338, upload-time = "2025-01-10T17:06:06.678Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langdetect"
|
||||
version = "1.0.9"
|
||||
@ -4515,13 +4555,16 @@ name = "yuxi-know"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "colorlog" },
|
||||
{ name = "dashscope" },
|
||||
{ name = "docx2txt" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "flagembedding" },
|
||||
{ name = "langchain-community" },
|
||||
{ name = "langchain-deepseek" },
|
||||
{ name = "langchain-huggingface" },
|
||||
{ name = "langchain-openai" },
|
||||
{ name = "langchain-together" },
|
||||
{ name = "langgraph" },
|
||||
{ name = "langgraph-checkpoint-sqlite" },
|
||||
{ name = "langsmith" },
|
||||
@ -4549,13 +4592,16 @@ dependencies = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "colorlog", specifier = ">=6.9.0" },
|
||||
{ name = "dashscope", specifier = ">=1.23.2" },
|
||||
{ name = "docx2txt", specifier = ">=0.9" },
|
||||
{ name = "fastapi", specifier = ">=0.115.12" },
|
||||
{ name = "flagembedding", specifier = ">=1.3.4" },
|
||||
{ name = "langchain-community", specifier = ">=0.3.22" },
|
||||
{ name = "langchain-deepseek", specifier = ">=0.1.3" },
|
||||
{ name = "langchain-huggingface", specifier = ">=0.2.0" },
|
||||
{ name = "langchain-openai", specifier = ">=0.3.14" },
|
||||
{ name = "langchain-together", specifier = ">=0.3.0" },
|
||||
{ name = "langgraph", specifier = ">=0.3.34" },
|
||||
{ name = "langgraph-checkpoint-sqlite", specifier = ">=2.0.7" },
|
||||
{ name = "langsmith", specifier = ">=0.3.37" },
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
"echarts-gl": "^2.0.9",
|
||||
"highlight.js": "^11.11.1",
|
||||
"less": "^4.3.0",
|
||||
"lucide-vue-next": "^0.511.0",
|
||||
"marked": "^15.0.11",
|
||||
"marked-highlight": "^2.2.1",
|
||||
"md-editor-v3": "^5.5.1",
|
||||
|
||||
@ -41,6 +41,9 @@ importers:
|
||||
less:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0
|
||||
lucide-vue-next:
|
||||
specifier: ^0.511.0
|
||||
version: 0.511.0(vue@3.5.14)
|
||||
marked:
|
||||
specifier: ^15.0.11
|
||||
version: 15.0.11
|
||||
@ -1644,6 +1647,11 @@ packages:
|
||||
peerDependencies:
|
||||
vue: '>=3.0.1'
|
||||
|
||||
lucide-vue-next@0.511.0:
|
||||
resolution: {integrity: sha512-VSv0F3pHniGN7JMMzDcLFNMQbl8381+shNnHwV8hi+El7xl2ZL8qdNuzPoiBViKk8mTKK5K3ZDfmE/wEcTZVIQ==}
|
||||
peerDependencies:
|
||||
vue: '>=3.0.1'
|
||||
|
||||
magic-string@0.30.17:
|
||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||
|
||||
@ -4008,6 +4016,10 @@ snapshots:
|
||||
dependencies:
|
||||
vue: 3.5.14
|
||||
|
||||
lucide-vue-next@0.511.0(vue@3.5.14):
|
||||
dependencies:
|
||||
vue: 3.5.14
|
||||
|
||||
magic-string@0.30.17:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
@ -19,10 +19,10 @@
|
||||
<div class="header__left">
|
||||
<slot name="header-left" class="nav-btn"></slot>
|
||||
<div class="toggle-sidebar nav-btn" v-if="!state.isSidebarOpen" @click="toggleSidebar">
|
||||
<img src="@/assets/icons/sidebar_left.svg" class="iconfont icon-20" alt="设置" />
|
||||
<PanelLeftOpen size="20" color="var(--gray-800)"/>
|
||||
</div>
|
||||
<div class="newchat nav-btn" @click="createNewChat" :disabled="state.isProcessingRequest || state.creatingNewChat">
|
||||
<PlusCircleOutlined /> <span class="text" :class="{'hide-text': isMediumContainer}">新对话</span>
|
||||
<MessageSquarePlus size="20" color="var(--gray-800)"/> <span class="text" :class="{'hide-text': isMediumContainer}">新对话</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header__center">
|
||||
@ -87,7 +87,7 @@
|
||||
@keydown="handleKeyDown"
|
||||
/>
|
||||
<div class="bottom-actions">
|
||||
<p class="note">请注意辨别内容的可靠性</p>
|
||||
<p class="note" @click="getAgentHistory">请注意辨别内容的可靠性</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -96,7 +96,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch, nextTick, computed, onUnmounted } from 'vue';
|
||||
import { ref, reactive, onMounted, watch, nextTick, computed, onUnmounted, toRaw } from 'vue';
|
||||
import {
|
||||
RobotOutlined, SendOutlined, LoadingOutlined,
|
||||
ThunderboltOutlined, ReloadOutlined, CheckCircleOutlined,
|
||||
@ -107,6 +107,7 @@ import MessageInputComponent from '@/components/MessageInputComponent.vue'
|
||||
import AgentMessageComponent from '@/components/AgentMessageComponent.vue'
|
||||
import ChatSidebarComponent from '@/components/ChatSidebarComponent.vue'
|
||||
import { chatApi, threadApi } from '@/apis/auth_api'
|
||||
import { PanelLeftOpen, MessageSquarePlus } from 'lucide-vue-next';
|
||||
|
||||
// 新增props属性,允许父组件传入agentId
|
||||
const props = defineProps({
|
||||
@ -458,6 +459,9 @@ const handleStreamResponseError = (error) => {
|
||||
// 处理流数据
|
||||
const processResponseChunk = async (data) => {
|
||||
// console.log("处理流数据:", data.msg);
|
||||
// if (data.msg.additional_kwargs?.tool_calls) {
|
||||
// console.log("tool_calls", data.msg.additional_kwargs.tool_calls);
|
||||
// }
|
||||
if (data.status === 'init') {
|
||||
// 代表服务端收到请求并返回第一个响应
|
||||
state.waitingServerResponse = false;
|
||||
@ -465,12 +469,6 @@ const processResponseChunk = async (data) => {
|
||||
onGoingConv.msgChunks[data.request_id] = [data.msg];
|
||||
|
||||
} else if (data.status === 'loading') {
|
||||
// 检查lastConv是否存在,避免undefined
|
||||
const lastConv = convs.value.length > 0 ? convs.value[convs.value.length - 1] : null;
|
||||
if (lastConv && lastConv.status === 'finished') {
|
||||
await getAgentHistory();
|
||||
}
|
||||
|
||||
if (data.msg.id) {
|
||||
if (!onGoingConv.msgChunks[data.msg.id]) {
|
||||
onGoingConv.msgChunks[data.msg.id] = []
|
||||
@ -478,7 +476,7 @@ const processResponseChunk = async (data) => {
|
||||
onGoingConv.msgChunks[data.msg.id].push(data.msg)
|
||||
}
|
||||
} else if (data.status === 'finished') {
|
||||
await getAgentHistory();
|
||||
// await getAgentHistory();
|
||||
}
|
||||
// await scrollToBottom();
|
||||
};
|
||||
@ -698,12 +696,19 @@ const handleKeyDown = (e) => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const mergeMessageChunk = (chunks) => {
|
||||
if (chunks.length === 0) return null;
|
||||
|
||||
// 以第一个chunk为基础
|
||||
const result = {...chunks[0]};
|
||||
for (const c of chunks) {
|
||||
if (c.additional_kwargs?.tool_calls) {
|
||||
console.warn("chunks", toRaw(c))
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 深拷贝第一个chunk作为结果
|
||||
const result = JSON.parse(JSON.stringify(chunks[0]));
|
||||
|
||||
result.content = result.content || '';
|
||||
|
||||
// 合并其他chunks
|
||||
@ -716,7 +721,7 @@ const mergeMessageChunk = (chunks) => {
|
||||
// 如果是当前chunk没有的 key, value, 或者当前 result[key] 为空,则添加到result中
|
||||
for (const key in chunk) {
|
||||
if (!result[key]) {
|
||||
result[key] = chunk[key]
|
||||
result[key] = JSON.parse(JSON.stringify(chunk[key]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -727,7 +732,7 @@ const mergeMessageChunk = (chunks) => {
|
||||
|
||||
for (const toolCall of chunk.additional_kwargs.tool_calls) {
|
||||
const existingToolCall = result.additional_kwargs.tool_calls.find(
|
||||
t => t.id === toolCall.id
|
||||
t => (t.id === toolCall.id || t.index === toolCall.index)
|
||||
);
|
||||
|
||||
if (existingToolCall) {
|
||||
@ -735,7 +740,7 @@ const mergeMessageChunk = (chunks) => {
|
||||
existingToolCall.function.arguments += toolCall.function.arguments;
|
||||
} else {
|
||||
// 添加新的tool call
|
||||
result.additional_kwargs.tool_calls.push({...toolCall});
|
||||
result.additional_kwargs.tool_calls.push(JSON.parse(JSON.stringify(toolCall)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -743,6 +748,9 @@ const mergeMessageChunk = (chunks) => {
|
||||
|
||||
if (result.type === 'AIMessageChunk') {
|
||||
result.type = 'ai'
|
||||
if (result.additional_kwargs?.tool_calls) {
|
||||
result.tool_calls = result.additional_kwargs.tool_calls;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -35,19 +35,19 @@
|
||||
<span v-if="!toolCall.tool_call_result">
|
||||
<LoadingOutlined />
|
||||
<span>正在调用工具: </span>
|
||||
<span class="tool-name">{{ toolCall.name }}</span>
|
||||
<span class="tool-name">{{ toolCall.name || toolCall.function.name }}</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<ThunderboltOutlined /> 工具 <span class="tool-name">{{ toolCall.name }}</span> 执行完成
|
||||
<ThunderboltOutlined /> 工具 <span class="tool-name">{{ toolCall.name || toolCall.function.name }}</span> 执行完成
|
||||
</span>
|
||||
</div>
|
||||
<div class="tool-content" v-show="expandedToolCalls.has(toolCall.id)">
|
||||
<div class="tool-params" v-if="toolCall.args">
|
||||
<div class="tool-params" v-if="toolCall.args || toolCall.function.arguments">
|
||||
<div class="tool-params-header">
|
||||
参数:
|
||||
</div>
|
||||
<div class="tool-params-content">
|
||||
<pre>{{ toolCall.args }}</pre>
|
||||
<pre>{{ toolCall.args || toolCall.function.arguments }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-params" v-if="toolCall.tool_call_result && toolCall.tool_call_result.content">
|
||||
@ -302,6 +302,11 @@ const toggleToolCall = (toolCallId) => {
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
overflow-x: auto;
|
||||
color: var(--gray-800);
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="chat" ref="chatContainer">
|
||||
<div class="chat" ref="chatContainer" :class="{ 'refs-sidebar-open': refsSidebarVisible && refsSidebarPinned }">
|
||||
<div class="chat-header">
|
||||
<div class="header__left">
|
||||
<div
|
||||
@ -8,20 +8,26 @@
|
||||
@click="state.isSidebarOpen = true"
|
||||
>
|
||||
<a-tooltip title="展开侧边栏" placement="right">
|
||||
<img src="@/assets/icons/sidebar_left.svg" class="iconfont icon-20" alt="设置" />
|
||||
<PanelLeftOpen size="20" color="var(--gray-800)"/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="newchat nav-btn nav-btn-icon-only" @click="$emit('newconv')">
|
||||
<a-tooltip title="新建对话" placement="right">
|
||||
<PlusCircleOutlined />
|
||||
<MessageSquarePlus size="20" color="var(--gray-800)"/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<ModelSelectorComponent class="nav-btn borderless" @select-model="handleModelSelect" />
|
||||
<ModelSelectorComponent
|
||||
class="nav-btn borderless max-width"
|
||||
@select-model="handleModelSelect"
|
||||
:model_name="configStore.config?.model_name"
|
||||
:model_provider="configStore.config?.model_provider"
|
||||
/>
|
||||
</div>
|
||||
<div class="header__right">
|
||||
<div class="nav-btn text" @click="opts.showPanel = !opts.showPanel">
|
||||
<component :is="opts.showPanel ? FolderOpenOutlined : FolderOutlined" /> <span class="text">选项</span>
|
||||
<Ellipsis />
|
||||
<!-- <span class="text">选项</span> -->
|
||||
</div>
|
||||
<div v-if="opts.showPanel" class="my-panal r0 top100 swing-in-top-fwd" ref="panel">
|
||||
<div class="flex-center" @click="meta.summary_title = !meta.summary_title">
|
||||
@ -128,6 +134,7 @@
|
||||
:visible="refsSidebarVisible"
|
||||
:latestRefs="currentRefs"
|
||||
@update:visible="refsSidebarVisible = $event"
|
||||
@pin-change="handleRefsPinChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -135,31 +142,12 @@
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, toRefs, nextTick, onUnmounted, watch, computed } from 'vue'
|
||||
import {
|
||||
SendOutlined,
|
||||
MenuOutlined,
|
||||
FormOutlined,
|
||||
LoadingOutlined,
|
||||
BookOutlined,
|
||||
BookFilled,
|
||||
CompassOutlined,
|
||||
ArrowUpOutlined,
|
||||
CompassFilled,
|
||||
GoldenFilled,
|
||||
GoldOutlined,
|
||||
SettingOutlined,
|
||||
SettingFilled,
|
||||
PlusCircleOutlined,
|
||||
FolderOutlined,
|
||||
FolderOpenOutlined,
|
||||
GlobalOutlined,
|
||||
FileTextOutlined,
|
||||
BulbOutlined,
|
||||
CaretRightOutlined,
|
||||
DeploymentUnitOutlined,
|
||||
PauseOutlined,
|
||||
ReloadOutlined,
|
||||
CopyOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import { Ellipsis, PanelLeftOpen, MessageSquarePlus } from 'lucide-vue-next'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@ -219,6 +207,14 @@ const meta = reactive(JSON.parse(localStorage.getItem('meta')) || {
|
||||
const refsSidebarVisible = ref(false)
|
||||
const currentRefs = ref({})
|
||||
|
||||
// 添加侧边栏固定状态
|
||||
const refsSidebarPinned = ref(false)
|
||||
|
||||
// 处理侧边栏固定状态变化
|
||||
const handleRefsPinChange = (pinned) => {
|
||||
refsSidebarPinned.value = pinned
|
||||
}
|
||||
|
||||
// 处理打开refs侧边栏
|
||||
const handleOpenRefs = ({ type, refs }) => {
|
||||
console.log('ChatComponent handleOpenRefs called with type:', type);
|
||||
@ -759,6 +755,14 @@ const findLastIndex = (array, predicate) => {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.chat {
|
||||
--refs-sidebar-floating-width: 700px;
|
||||
--refs-sidebar-pinned-width: min(40%, 450px);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chat {
|
||||
position: relative;
|
||||
@ -772,6 +776,11 @@ const findLastIndex = (array, predicate) => {
|
||||
box-sizing: border-box;
|
||||
flex: 5 5 200px;
|
||||
overflow-y: scroll;
|
||||
transition: padding-right 0.3s ease;
|
||||
|
||||
&.refs-sidebar-open {
|
||||
padding-right: var(--refs-sidebar-pinned-width); /* 与侧边栏固定时的宽度一致 */
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
user-select: none;
|
||||
@ -1124,6 +1133,18 @@ const findLastIndex = (array, predicate) => {
|
||||
}
|
||||
|
||||
// Scrollable menu styles moved to ModelSelectorComponent
|
||||
|
||||
@media (max-width: 768px) {
|
||||
&.refs-sidebar-open {
|
||||
padding-right: 280px; /* 中等屏幕上固定时的宽度 */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
&.refs-sidebar-open {
|
||||
padding-right: 0; /* 小屏幕上不调整内容区域 */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Global styles for dropdown moved to ModelSelectorComponent -->
|
||||
|
||||
@ -4,14 +4,14 @@
|
||||
<div class="header-title">{{ currentAgentId }}</div>
|
||||
<div class="header-actions">
|
||||
<div class="toggle-sidebar nav-btn" v-if="isSidebarOpen" @click="toggleCollapse">
|
||||
<img src="@/assets/icons/sidebar_left.svg" class="iconfont icon-20" alt="设置" />
|
||||
<PanelLeftClose size="20" color="var(--gray-800)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conversation-list-top">
|
||||
<a-button type="text" @click="createNewChat" class="new-chat-btn">
|
||||
<PlusCircleOutlined /> 新对话
|
||||
</a-button>
|
||||
<button type="text" @click="createNewChat" class="new-chat-btn">
|
||||
新对话
|
||||
</button>
|
||||
</div>
|
||||
<div class="conversation-list">
|
||||
<a-spin v-if="loading" />
|
||||
@ -55,12 +55,12 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch, h } from 'vue';
|
||||
import {
|
||||
PlusCircleOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
MoreOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { PanelLeftClose, MessageSquarePlus } from 'lucide-vue-next';
|
||||
|
||||
const props = defineProps({
|
||||
currentAgentId: {
|
||||
@ -214,7 +214,9 @@ const toggleCollapse = () => {
|
||||
border-radius: 16px;
|
||||
background-color: var(--gray-300);
|
||||
color: var(--main-color);
|
||||
border: none;
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--gray-400);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="input-box" :class="customClasses">
|
||||
<div class="input-box" :class="customClasses" @click="focusInput">
|
||||
<div class="input-area">
|
||||
<a-textarea
|
||||
ref="inputRef"
|
||||
@ -111,6 +111,12 @@ const handleSendOrStop = () => {
|
||||
emit('send');
|
||||
};
|
||||
|
||||
// 聚焦输入框
|
||||
const focusInput = () => {
|
||||
if (inputRef.value && !props.disabled) {
|
||||
inputRef.value.focus();
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for component to mount before setting up onStartTyping
|
||||
onMounted(() => {
|
||||
@ -147,13 +153,12 @@ onMounted(() => {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-input {
|
||||
flex: 1;
|
||||
min-height: 44px;
|
||||
padding: 0.5rem 0;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
margin: 0;
|
||||
@ -180,7 +185,6 @@ onMounted(() => {
|
||||
.input-options {
|
||||
display: flex;
|
||||
padding: 8px 0 0;
|
||||
margin-top: 6px;
|
||||
border-top: 1px solid var(--gray-100);
|
||||
|
||||
.options__left,
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<a-dropdown>
|
||||
<a class="model-select" @click.prevent>
|
||||
<BulbOutlined />
|
||||
<a-tooltip :title="configStore.config?.model_name" placement="right">
|
||||
<span class="model-text text"> {{ configStore.config?.model_name }} </span>
|
||||
<!-- <BulbOutlined /> -->
|
||||
<a-tooltip :title="model_name" placement="right">
|
||||
<span class="model-text text"> {{ model_name }} </span>
|
||||
</a-tooltip>
|
||||
<span class="text" style="color: #aaa;">{{ configStore.config?.model_provider }} </span>
|
||||
<span class="text" style="color: #aaa;">{{ model_provider }} </span>
|
||||
</a>
|
||||
<template #overlay>
|
||||
<a-menu class="scrollable-menu">
|
||||
@ -29,6 +29,17 @@ import { computed } from 'vue'
|
||||
import { BulbOutlined } from '@ant-design/icons-vue'
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
|
||||
const props = defineProps({
|
||||
model_name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
model_provider: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
const configStore = useConfigStore()
|
||||
const emit = defineEmits(['select-model'])
|
||||
|
||||
@ -50,7 +61,6 @@ const handleSelectModel = (provider, name) => {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.model-select {
|
||||
max-width: 350px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@ -67,6 +77,10 @@ const handleSelectModel = (provider, name) => {
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.max-width {
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.model-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@ -123,6 +123,7 @@ const regenerateMessage = () => {
|
||||
.refs {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
color: var(--gray-500);
|
||||
font-size: 13px;
|
||||
gap: 10px;
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-drawer
|
||||
:open="visible"
|
||||
title="参考信息"
|
||||
:width="700"
|
||||
:contentWrapperStyle="{ maxWidth: '100%'}"
|
||||
placement="right"
|
||||
class="refs-sidebar"
|
||||
rootClassName="root"
|
||||
@close="$emit('update:visible', false)"
|
||||
@afterOpenChange="handleAfterVisibleChange"
|
||||
>
|
||||
<a-tabs v-model:activeKey="activeTab">
|
||||
<div class="refs-sidebar-container" :class="{ 'visible': visible, 'pinned': isPinned }">
|
||||
<div class="overlay" v-if="visible && !isPinned" @click="$emit('update:visible', false)"></div>
|
||||
<div class="refs-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h3>参考信息</h3>
|
||||
<div class="sidebar-controls">
|
||||
<a-button type="text" @click="togglePin" class="pin-button">
|
||||
<PushpinOutlined :class="{ 'pinned': isPinned }" />
|
||||
</a-button>
|
||||
<a-button type="text" @click="$emit('update:visible', false)">
|
||||
<CloseOutlined />
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-tabs v-model:activeKey="activeTab" class="refs-tabs">
|
||||
<!-- 关系图 -->
|
||||
<a-tab-pane key="graph" tab="关系图" :disabled="!hasGraphData">
|
||||
<div v-if="hasGraphData" class="graph-container">
|
||||
@ -24,23 +26,22 @@
|
||||
|
||||
<!-- 网页搜索 -->
|
||||
<a-tab-pane key="webSearch" tab="网页搜索" :disabled="!hasWebSearchData">
|
||||
<div v-if="hasWebSearchData" class="results-list">
|
||||
<div v-for="result in latestRefs.web_search.results" :key="result.url" class="result-item">
|
||||
<div class="result-meta">
|
||||
<div class="score-info">
|
||||
<span>
|
||||
<strong>相关度:</strong>
|
||||
<a-progress :percent="getPercent(result.score)"/>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="hasWebSearchData" class="web-results-list">
|
||||
<div v-for="result in latestRefs.web_search.results" :key="result.url" class="web-result-card">
|
||||
<div class="card-header">
|
||||
<h3 class="result-title">
|
||||
<a :href="result.url" target="_blank">{{ result.title }}</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="result-content">
|
||||
<h3 class="result-title">{{ result.title }}</h3>
|
||||
<div class="result-url">
|
||||
<a :href="result.url" target="_blank">{{ result.url }}</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="result-text">{{ result.content }}</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="score-info">
|
||||
<span>相关度:{{ getPercent(result.score) }}%</span>
|
||||
<span>来源:{{ getDomain(result.url) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="empty-data">
|
||||
@ -89,7 +90,7 @@
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-drawer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -98,6 +99,8 @@ import { ref, computed, reactive, watch, nextTick } from 'vue'
|
||||
import {
|
||||
FileOutlined,
|
||||
ClockCircleOutlined,
|
||||
CloseOutlined,
|
||||
PushpinOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import GraphContainer from './GraphContainer.vue'
|
||||
|
||||
@ -112,12 +115,21 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible'])
|
||||
const emit = defineEmits(['update:visible', 'pin-change'])
|
||||
|
||||
// 标签页相关
|
||||
const activeTab = ref('graph')
|
||||
const activeFiles = ref([])
|
||||
|
||||
// 是否固定侧边栏
|
||||
const isPinned = ref(false)
|
||||
|
||||
// 切换固定状态
|
||||
const togglePin = () => {
|
||||
isPinned.value = !isPinned.value
|
||||
emit('pin-change', isPinned.value)
|
||||
}
|
||||
|
||||
// 计算属性:是否有各类数据
|
||||
const hasGraphData = computed(() => {
|
||||
try {
|
||||
@ -217,6 +229,10 @@ const getPercent = (value) => {
|
||||
return parseFloat((value * 100).toFixed(2))
|
||||
}
|
||||
|
||||
const getDomain = (url) => {
|
||||
return url.split('/')[2]
|
||||
}
|
||||
|
||||
// 日期格式化函数
|
||||
const formatDate = (timestamp) => {
|
||||
return new Date(timestamp * 1000).toLocaleString()
|
||||
@ -258,25 +274,21 @@ const setActiveTab = (tab) => {
|
||||
// 图表ref
|
||||
const graphContainerRef = ref(null);
|
||||
|
||||
// 处理抽屉完全打开后的事件
|
||||
const handleAfterVisibleChange = (visible) => {
|
||||
console.log('RefsSidebar afterOpenChange fired, visible:', visible, 'activeTab:', activeTab.value);
|
||||
|
||||
// 监听可见性变化后的处理
|
||||
watch(() => props.visible, (visible) => {
|
||||
if (!visible) return;
|
||||
|
||||
// 根据当前活动标签页执行相应的刷新操作
|
||||
nextTick(() => {
|
||||
if (activeTab.value === 'graph' && hasGraphData.value) {
|
||||
console.log('Refreshing graph after drawer opened');
|
||||
console.log('Refreshing graph after sidebar becomes visible');
|
||||
// 触发图表容器的重新布局
|
||||
if (graphContainerRef.value && graphContainerRef.value.refreshGraph) {
|
||||
graphContainerRef.value.refreshGraph();
|
||||
}
|
||||
} else {
|
||||
console.log('Current active tab is', activeTab.value, 'no need to refresh graph');
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
// 监听标签页变化
|
||||
watch(activeTab, (newTab) => {
|
||||
@ -309,7 +321,109 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.refs-sidebar {
|
||||
.refs-sidebar-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: calc(-1 * var(--refs-sidebar-floating-width)); /* 初始状态隐藏在右侧 - 使用更大宽度 */
|
||||
width: var(--refs-sidebar-floating-width); /* 未固定时的宽度 */
|
||||
height: 100vh;
|
||||
background: white;
|
||||
z-index: 1000;
|
||||
transition: right 0.3s ease, width 0.3s ease; /* 添加宽度过渡效果 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: text;
|
||||
|
||||
&::before {
|
||||
z-index: 999; // 降低遮罩层的z-index
|
||||
}
|
||||
|
||||
// 添加遮罩
|
||||
&::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.visible:not(.pinned)::before {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.visible {
|
||||
right: 0; /* 显示时移动到可见区域 */
|
||||
}
|
||||
|
||||
&.pinned {
|
||||
width: var(--refs-sidebar-pinned-width); /* 固定时的宽度 */
|
||||
box-shadow: none;
|
||||
border-left: 1px solid var(--gray-200);
|
||||
}
|
||||
|
||||
&.visible:not(.pinned) {
|
||||
box-shadow: -2px 0 15px rgba(0, 0, 0, 0.15);
|
||||
.refs-sidebar {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.refs-sidebar {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// padding: 0 16px;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sidebar-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.pin-button {
|
||||
margin-right: 8px;
|
||||
|
||||
.pinned {
|
||||
color: var(--main-color);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.refs-tabs {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.ant-tabs-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.empty-data {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -317,7 +431,7 @@ defineExpose({
|
||||
height: 200px;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
background-color: #f9f9f9;
|
||||
background-color: var(--gray-100);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@ -333,7 +447,7 @@ defineExpose({
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--gray-100);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
@ -343,9 +457,19 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
// 通用结果展示样式的LESS mixin
|
||||
.result-text-mixin() {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
// 知识库结果样式
|
||||
.results-list {
|
||||
.result-item {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
padding: 16px 0;
|
||||
|
||||
&:last-child {
|
||||
@ -369,7 +493,7 @@ defineExpose({
|
||||
strong {
|
||||
margin-right: 8px;
|
||||
white-space: nowrap;
|
||||
color: #666;
|
||||
color: var(--gray-700);
|
||||
}
|
||||
|
||||
.ant-progress {
|
||||
@ -378,7 +502,7 @@ defineExpose({
|
||||
margin-inline: 10px;
|
||||
|
||||
.ant-progress-bg {
|
||||
background-color: #666;
|
||||
background-color: var(--gray-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -386,7 +510,7 @@ defineExpose({
|
||||
|
||||
.result-id {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
color: var(--gray-600);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
@ -396,31 +520,110 @@ defineExpose({
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
color: var(--gray-800);
|
||||
}
|
||||
|
||||
|
||||
.result-url {
|
||||
font-size: 12px;
|
||||
color: #0563e7;
|
||||
color: var(--main-color);
|
||||
margin-bottom: 8px;
|
||||
|
||||
a {
|
||||
color: #0563e7;
|
||||
color: var(--main-color);
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.result-text {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
background-color: #f9f9f9;
|
||||
.result-text-mixin();
|
||||
background-color: var(--gray-100);
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e8e8e8;
|
||||
border: 1px solid var(--gray-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 网页搜索结果卡片样式
|
||||
.web-results-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.web-result-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid var(--gray-200);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px 16px 0;
|
||||
|
||||
.result-title {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 0 16px;
|
||||
|
||||
.result-text {
|
||||
.result-text-mixin();
|
||||
margin-bottom: 16px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
background-color: var(--gray-100);
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid var(--gray-200);
|
||||
|
||||
.score-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
span {
|
||||
font-size: 13px;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 500px; /* 中等屏幕上未固定时的宽度 */
|
||||
right: -500px;
|
||||
|
||||
&.pinned {
|
||||
width: 280px; /* 中等屏幕上固定时的宽度 */
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
width: 100%; /* 小屏幕上的宽度 */
|
||||
right: -100%;
|
||||
|
||||
&.pinned {
|
||||
width: 100%; /* 小屏幕上固定时也是全宽 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
@ -3,7 +3,7 @@
|
||||
<a-dropdown :trigger="['click']" v-if="userStore.isLoggedIn">
|
||||
<div class="user-info-dropdown" :data-align="showRole ? 'left' : 'center'">
|
||||
<div class="user-avatar">
|
||||
<UserOutlined />
|
||||
<CircleUser />
|
||||
<!-- <div class="user-role-badge" :class="userRoleClass"></div> -->
|
||||
</div>
|
||||
<div v-if="showRole">{{ userStore.username }}</div>
|
||||
@ -18,13 +18,13 @@
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="logout" @click="logout">
|
||||
<LogoutOutlined /> 退出登录
|
||||
<LogoutOutlined /> 退出登录
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-button v-else type="link" @click="goToLogin">
|
||||
<UserOutlined /> 登录
|
||||
<UserRoundCheck /> 登录
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
@ -35,6 +35,7 @@ import { useRouter } from 'vue-router';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { UserOutlined, LogoutOutlined } from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { CircleUser, LogOut } from 'lucide-vue-next';
|
||||
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
@ -94,6 +95,7 @@ const goToLogin = () => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--gray-800);
|
||||
// margin-bottom: 16px;
|
||||
}
|
||||
|
||||
|
||||
@ -2,29 +2,12 @@
|
||||
import { ref, reactive, KeepAlive, onMounted, computed } from 'vue'
|
||||
import { RouterLink, RouterView, useRoute } from 'vue-router'
|
||||
import {
|
||||
MessageOutlined,
|
||||
MessageFilled,
|
||||
SettingOutlined,
|
||||
SettingFilled,
|
||||
BookOutlined,
|
||||
BookFilled,
|
||||
GithubOutlined,
|
||||
FolderOutlined,
|
||||
FolderFilled,
|
||||
GoldOutlined,
|
||||
GoldFilled,
|
||||
ToolFilled,
|
||||
ToolOutlined,
|
||||
BugOutlined,
|
||||
ProjectFilled,
|
||||
ProjectOutlined,
|
||||
StarFilled,
|
||||
StarOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
RobotOutlined,
|
||||
RobotFilled,
|
||||
ApiOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import { Bot, Waypoints, LibraryBig, MessageSquareMore, Settings } from 'lucide-vue-next';
|
||||
|
||||
import { useConfigStore } from '@/stores/config'
|
||||
import { useDatabaseStore } from '@/stores/database'
|
||||
import DebugComponent from '@/components/DebugComponent.vue'
|
||||
@ -82,24 +65,24 @@ console.log(route)
|
||||
const mainList = [{
|
||||
name: '对话',
|
||||
path: '/chat',
|
||||
icon: MessageOutlined,
|
||||
activeIcon: MessageFilled,
|
||||
icon: MessageSquareMore,
|
||||
activeIcon: MessageSquareMore,
|
||||
}, {
|
||||
name: '智能体',
|
||||
path: '/agent',
|
||||
icon: ToolOutlined,
|
||||
activeIcon: ToolFilled,
|
||||
icon: Bot,
|
||||
activeIcon: Bot,
|
||||
}, {
|
||||
name: '图谱',
|
||||
path: '/graph',
|
||||
icon: ProjectOutlined,
|
||||
activeIcon: ProjectFilled,
|
||||
icon: Waypoints,
|
||||
activeIcon: Waypoints,
|
||||
// hidden: !configStore.config.enable_knowledge_graph,
|
||||
}, {
|
||||
name: '知识库',
|
||||
path: '/database',
|
||||
icon: BookOutlined,
|
||||
activeIcon: BookFilled,
|
||||
icon: LibraryBig,
|
||||
activeIcon: LibraryBig,
|
||||
// hidden: !configStore.config.enable_knowledge_base,
|
||||
}
|
||||
]
|
||||
@ -145,7 +128,7 @@ const mainList = [{
|
||||
v-show="!item.hidden"
|
||||
class="nav-item"
|
||||
active-class="active">
|
||||
<component class="icon" :is="route.path.startsWith(item.path) ? item.activeIcon : item.icon" />
|
||||
<component class="icon" :is="route.path.startsWith(item.path) ? item.activeIcon : item.icon" size="22"/>
|
||||
<span class="text">{{item.name}}</span>
|
||||
</RouterLink>
|
||||
|
||||
@ -162,7 +145,7 @@ const mainList = [{
|
||||
|
||||
<div class="github nav-item">
|
||||
<a-tooltip placement="right">
|
||||
<template #title>GitHub</template>
|
||||
<template #title>欢迎 Star</template>
|
||||
<a href="https://github.com/xerrors/Yuxi-Know" target="_blank" class="github-link">
|
||||
<GithubOutlined class="icon" style="color: #222;"/>
|
||||
<span v-if="githubStars > 0" class="github-stars">
|
||||
@ -191,7 +174,7 @@ const mainList = [{
|
||||
<RouterLink class="nav-item setting" to="/setting" active-class="active">
|
||||
<a-tooltip placement="right">
|
||||
<template #title>设置</template>
|
||||
<component class="icon" :is="route.path === '/setting' ? SettingFilled : SettingOutlined" />
|
||||
<Settings />
|
||||
</a-tooltip>
|
||||
</RouterLink>
|
||||
</div>
|
||||
@ -484,6 +467,13 @@ div.header, #app-router-view {
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
font-size: 15px; // 减小图标大小
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&:focus, &:active {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
|
||||
@ -75,8 +75,17 @@
|
||||
class="config-item"
|
||||
>
|
||||
<p v-if="value.description" class="description">{{ value.description }}</p>
|
||||
|
||||
<div v-if="key === 'model'" class="agent-model">
|
||||
<p><small>注意,部分模型对于 Tool Calling 的支持不稳定,建议采用{{ value.options }} </small></p>
|
||||
<ModelSelectorComponent
|
||||
@select-model="handleModelChange"
|
||||
:model_name="agentConfig[key] ? agentConfig[key].split('/').slice(1).join('/') : ''"
|
||||
:model_provider="agentConfig[key] ? agentConfig[key].split('/')[0] : ''"
|
||||
/>
|
||||
</div>
|
||||
<a-switch
|
||||
v-if="typeof agentConfig[key] === 'boolean'"
|
||||
v-else-if="typeof agentConfig[key] === 'boolean'"
|
||||
v-model:checked="agentConfig[key]"
|
||||
/>
|
||||
<a-textarea
|
||||
@ -155,6 +164,7 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { chatApi } from '@/apis/auth_api';
|
||||
import { systemConfigApi } from '@/apis/admin_api';
|
||||
@ -285,6 +295,10 @@ const loadAgentConfig = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelChange = (data) => {
|
||||
agentConfig.value.model = `${data.provider}/${data.name}`;
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
if (!selectedAgentId.value) {
|
||||
@ -537,6 +551,10 @@ const toggleSidebar = () => {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.agent-model {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<!-- <div class="action new" @click="addNewConv"><FormOutlined /></div> -->
|
||||
<span class="header-title">Yuxi-Know</span>
|
||||
<div class="action close" @click="state.isSidebarOpen = false">
|
||||
<img src="@/assets/icons/sidebar_left.svg" class="iconfont icon-20" alt="设置" />
|
||||
<PanelLeftClose size="20" color="var(--gray-800)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="conversation-list">
|
||||
@ -14,7 +14,7 @@
|
||||
:key="index"
|
||||
:class="{ active: curConvId === index }"
|
||||
@click="goToConversation(index)">
|
||||
<div class="conversation__title"><CommentOutlined /> {{ state.title }}</div>
|
||||
<div class="conversation__title">{{ state.title }}</div>
|
||||
<div class="conversation__delete" @click.stop="delConv(index)"><DeleteOutlined /></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -29,8 +29,9 @@
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, watch, onMounted } from 'vue'
|
||||
import { FormOutlined, MenuOutlined, DeleteOutlined, CommentOutlined } from '@ant-design/icons-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')) || [
|
||||
{
|
||||
|
||||
@ -21,7 +21,11 @@
|
||||
<div class="section">
|
||||
<div class="card card-select">
|
||||
<span class="label">对话模型</span>
|
||||
<ModelSelectorComponent @select-model="handleChatModelSelect" />
|
||||
<ModelSelectorComponent
|
||||
@select-model="handleChatModelSelect"
|
||||
:model_name="configStore.config?.model_name"
|
||||
:model_provider="configStore.config?.model_provider"
|
||||
/>
|
||||
</div>
|
||||
<div class="card card-select">
|
||||
<span class="label">{{ items?.embed_model.des }}</span>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user