diff --git a/docs/latest/changelog/roadmap.md b/docs/latest/changelog/roadmap.md index d3d52e11..df4782d2 100644 --- a/docs/latest/changelog/roadmap.md +++ b/docs/latest/changelog/roadmap.md @@ -11,10 +11,14 @@ - 文档解析部分的 markdown 中的图片替换为内部可访问的链接 (2/4) - chat_model 的 call 需要异步 - 优化chunk逻辑,移除 QA 分割,集成到普通分块中 +- 考虑修改附件的处理逻辑,考虑使用文件系统,将附件解析后放到文件系统中,智能体按需读取,用户可以使用 @ 来引用附件,例如 @file:reports.md 相较于现在的处理逻辑感觉会更加自然一点。在此之前可能还要考虑文件系统的后端支持问题 +- skills 如何实现还需要继续调研 ### Bugs - 部分异常状态下,智能体的模型名称出现重叠[#279](https://github.com/xerrors/Yuxi-Know/issues/279) +- 部分 local 的 mcp server 无法正常加载,但是建议在项目外部启动 mcp 服务器,然后通过 sse 的方式使用。 - DeepSeek 官方接口适配会出现问题 +- 部分推理后端与 langchain 的适配有问题: - 目前的知识库的图片存在公开访问风险 - 工具传递给模型的时候,使用英文,但部分模型不支持中文函数名(如gpt-4o-mini) - 首页加载的问题 diff --git a/src/agents/chatbot/tools.py b/src/agents/chatbot/tools.py index 0a7114cb..c19d7707 100644 --- a/src/agents/chatbot/tools.py +++ b/src/agents/chatbot/tools.py @@ -12,8 +12,8 @@ from src.utils import logger @tool -async def text_to_img_qwen(text: str) -> str: - """(用来测试文件存储)使用模型生成图片, 会返回图片的URL""" +async def text_to_img_demo(text: str) -> str: + """【测试用】使用模型生成图片, 会返回图片的URL""" url = "https://api.siliconflow.cn/v1/images/generations" @@ -27,13 +27,13 @@ async def text_to_img_qwen(text: str) -> str: response = requests.post(url, json=payload, headers=headers) response_json = response.json() except Exception as e: - logger.error(f"Failed to generate image with Kolors: {e}") + logger.error(f"Failed to generate image with: {e}") raise ValueError(f"Image generation failed: {e}") try: image_url = response_json["images"][0]["url"] except (KeyError, IndexError, TypeError) as e: - logger.error(f"Failed to parse image URL from Kolors response: {e}, {response_json=}") + logger.error(f"Failed to parse image URL from response: {e}, {response_json=}") raise ValueError(f"Image URL extraction failed: {e}") # 2. Upload to MinIO (Simplified) @@ -51,6 +51,6 @@ async def text_to_img_qwen(text: str) -> str: def get_tools() -> list[Any]: """获取所有可运行的工具(给大模型使用)""" tools = get_buildin_tools() - tools.append(text_to_img_qwen) + tools.append(text_to_img_demo) tools.extend(get_mysql_tools()) return tools diff --git a/web/src/assets/css/base.dark.css b/web/src/assets/css/base.dark.css index 406cd904..9c2daee7 100644 --- a/web/src/assets/css/base.dark.css +++ b/web/src/assets/css/base.dark.css @@ -107,12 +107,12 @@ --color-text-tertiary: rgba(255, 255, 255, 0.45); /* Shadow System - 深色模式阴影系统 */ - --shadow-0: rgba(0, 0, 0, 0.1); - --shadow-1: rgba(0, 0, 0, 0.2); - --shadow-2: rgba(0, 0, 0, 0.3); - --shadow-3: rgba(0, 0, 0, 0.4); - --shadow-4: rgba(0, 0, 0, 0.5); - --shadow-5: rgba(0, 0, 0, 0.6); + --shadow-0: rgba(255, 255, 255, 0.02); + --shadow-1: rgba(255, 255, 255, 0.05); + --shadow-2: rgba(255, 255, 255, 0.08); + --shadow-3: rgba(255, 255, 255, 0.12); + --shadow-4: rgba(255, 255, 255, 0.16); + --shadow-5: rgba(255, 255, 255, 0.20); --color-trans-light: rgba(0, 0, 0, 0.85); diff --git a/web/src/components/AgentConfigSidebar.vue b/web/src/components/AgentConfigSidebar.vue index 1b01a2d4..fabd44a4 100644 --- a/web/src/components/AgentConfigSidebar.vue +++ b/web/src/components/AgentConfigSidebar.vue @@ -684,7 +684,8 @@ const resetConfig = async () => { background-color: var(--gray-25); padding: 12px; border-radius: 8px; - box-shadow: 0px 1px 1px var(--shadow-2); + border: 1px solid var(--gray-100); + // box-shadow: 0px 0px 2px var(--shadow-3); :deep(label.form_item_model) { font-weight: 600; diff --git a/web/src/components/AgentMessageComponent.vue b/web/src/components/AgentMessageComponent.vue index ace57a95..332e7a35 100644 --- a/web/src/components/AgentMessageComponent.vue +++ b/web/src/components/AgentMessageComponent.vue @@ -4,6 +4,10 @@
+
+ + +

{{ message.content }}

{{ message.content }}

@@ -102,7 +106,7 @@ import { computed, ref } from 'vue'; import { CaretRightOutlined, ThunderboltOutlined, LoadingOutlined } from '@ant-design/icons-vue'; import RefsComponent from '@/components/RefsComponent.vue' -import { Loader, CircleCheckBig } from 'lucide-vue-next'; +import { Loader, CircleCheckBig, Copy, Check } from 'lucide-vue-next'; import { ToolResultRenderer } from '@/components/ToolCallingResult' import { useAgentStore } from '@/stores/agent' import { useInfoStore } from '@/stores/info' @@ -150,6 +154,21 @@ const editorRef = ref() const emit = defineEmits(['retry', 'retryStoppedMessage', 'openRefs']); +// 复制状态 +const isCopied = ref(false); + +const copyToClipboard = async (text) => { + try { + await navigator.clipboard.writeText(text); + isCopied.value = true; + setTimeout(() => { + isCopied.value = false; + }, 2000); + } catch (err) { + console.error('Failed to copy: ', err); + } +}; + // 推理面板展开状态 const reasoningActiveKey = ref(['hide']); const expandedToolCalls = ref(new Set()); // 展开的工具调用集合 @@ -321,6 +340,38 @@ const toggleToolCall = (toolCallId) => { white-space: pre-line; } + .message-copy-btn { + cursor: pointer; + color: var(--gray-400); + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + flex-shrink: 0; + + &:hover { + color: var(--main-color); + } + + &.is-copied { + color: var(--color-success-500); + opacity: 1; + } + + &.human-copy { + position: absolute; + left: -28px; + bottom: 8px; + } + } + + &:hover { + .message-copy-btn { + opacity: 1; + } + } + .message-text-system { max-width: 100%; margin-bottom: 0; diff --git a/web/src/components/KnowledgeGraphSection.vue b/web/src/components/KnowledgeGraphSection.vue index e27980c2..7413bd45 100644 --- a/web/src/components/KnowledgeGraphSection.vue +++ b/web/src/components/KnowledgeGraphSection.vue @@ -264,7 +264,7 @@ onUnmounted(() => { backdrop-filter: blur(4px); padding: 2px; border-radius: 8px; - box-shadow: 0 0px 4px var(--shadow-3); + box-shadow: 0 0 4px 0px var(--shadow-2); } :deep(.ant-input-affix-wrapper) { @@ -272,10 +272,10 @@ onUnmounted(() => { border-radius: 6px; border-color: transparent; box-shadow: none; - background: rgba(255, 255, 255, 0.6); + background: var(--color-trans-light); &:hover, &:focus, &-focused { - background: #fff; + background: var(--main-0); border-color: var(--primary-color); } diff --git a/web/src/components/RefsComponent.vue b/web/src/components/RefsComponent.vue index 17d9833b..e54f6b82 100644 --- a/web/src/components/RefsComponent.vue +++ b/web/src/components/RefsComponent.vue @@ -8,8 +8,7 @@ @click="likeThisResponse(msg)" :title="feedbackState.hasSubmitted && feedbackState.rating === 'like' ? '已点赞' : '点赞'" > - - + - - + - {{ getModelName(msg) }} + {{ getModelName(msg) }} + class="item btn" @click="copyText(msg.content)" title="复制"> + + + class="item btn" @click="regenerateMessage()" title="重新生成">
@@ -63,14 +63,13 @@ import { ref, computed, reactive, onMounted } from 'vue' import { useClipboard } from '@vueuse/core' import { message } from 'ant-design-vue' import { - CopyOutlined, - BulbOutlined, - ReloadOutlined, - LikeOutlined, - LikeFilled, - DislikeOutlined, - DislikeFilled, -} from '@ant-design/icons-vue' + ThumbsUp, + ThumbsDown, + Bot, + Copy, + Check, + RotateCcw, +} from 'lucide-vue-next' import { agentApi } from '@/apis' const emit = defineEmits(['retry', 'openRefs']); @@ -110,12 +109,19 @@ const showKey = (key) => { return props.showRefs.includes(key) } +// 复制状态 +const isCopied = ref(false) + // 定义 copy 方法 const copyText = async (text) => { if (isSupported) { try { await copy(text) message.success('文本已复制到剪贴板') + isCopied.value = true + setTimeout(() => { + isCopied.value = false + }, 2000) } catch (error) { console.error('复制失败:', error) message.error('复制失败,请手动复制') @@ -255,11 +261,16 @@ const cancelDislike = () => { .item { background: var(--gray-50); color: var(--gray-700); - padding: 2px 8px; + padding: 6px 8px; border-radius: 8px; font-size: 13px; user-select: none; transition: all 0.2s ease; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + line-height: 1; &.btn { cursor: pointer; @@ -272,8 +283,6 @@ const cancelDislike = () => { // Disabled state - when feedback has been submitted &.disabled { - cursor: not-allowed; - opacity: 0.7; &:hover { background: var(--gray-50);