feat: 优化智能体对话与扩展界面体验
- 智能体状态面板改为仅展示有内容的 section,并为空状态保留统一提示 - 将待办名称在提示词、对话状态面板和工具结果中统一限制为 20 个中文汉字以内,同时通过 title 保留完整内容 - v2 模型列表补充供应商展示名,模型选择器展示并支持按供应商名称搜索 - 收紧文件预览编辑浮动操作、附件状态标签、工作区预览分隔线等界面细节 - 更新扩展/管理页命名、官网首页截图与 Demo 链接,并补充 changelog/roadmap
This commit is contained in:
parent
48d4a6b36f
commit
fd2660d21a
@ -8,7 +8,7 @@
|
||||
[](https://github.com/xerrors/Yuxi/blob/main/LICENSE)
|
||||
[](https://deepwiki.com/xerrors/Yuxi)
|
||||
[](https://zread.ai/xerrors/Yuxi)
|
||||
[](https://www.bilibili.com/video/BV1DF14BTETq/)
|
||||
[](https://www.bilibili.com/video/BV1TZEx6NEit/)
|
||||
|
||||
<a href="https://trendshift.io/repositories/24335" target="_blank"><img src="https://trendshift.io/api/badge/repositories/24335" alt="xerrors%2FYuxi | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
[](https://github.com/xerrors/Yuxi/issues)
|
||||
[](https://github.com/xerrors/Yuxi/blob/main/LICENSE)
|
||||
[](https://deepwiki.com/xerrors/Yuxi)
|
||||
[](https://www.bilibili.com/video/BV1DF14BTETq/)
|
||||
[](https://www.bilibili.com/video/BV1TZEx6NEit/)
|
||||
|
||||
|
||||
<a href="https://trendshift.io/repositories/24335" target="_blank"><img src="https://trendshift.io/api/badge/repositories/24335" alt="xerrors%2FYuxi | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
@ -46,6 +46,7 @@ SOURCE_CITE_PROMPT = """
|
||||
|
||||
TODO_MID_PROMPT = """
|
||||
你需要根据任务的复杂程度来使用 write_todos 来记录规划和待办事项,确保任务的每个步骤都被记录和跟踪。
|
||||
每个待办任务名称必须简短,控制在 20 个中文汉字以内。
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@ -205,6 +205,7 @@ async def refresh_model_cache(
|
||||
async def get_v2_models(
|
||||
model_type: str = "chat",
|
||||
_current_user: User = Depends(get_required_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""获取 v2 格式的模型列表,按 provider 分组。
|
||||
|
||||
@ -214,10 +215,16 @@ async def get_v2_models(
|
||||
from yuxi.models.providers.cache import model_cache
|
||||
|
||||
grouped = model_cache.get_specs_grouped_by_provider(model_type)
|
||||
providers = await get_all_model_providers(db)
|
||||
provider_name_by_id = {
|
||||
provider.provider_id: provider.display_name or provider.provider_id for provider in providers
|
||||
}
|
||||
|
||||
result = {}
|
||||
for provider_id, models in grouped.items():
|
||||
result[provider_id] = {
|
||||
"provider_id": provider_id,
|
||||
"provider_display_name": provider_name_by_id.get(provider_id, provider_id),
|
||||
"models": [
|
||||
{
|
||||
"spec": m.spec,
|
||||
|
||||
@ -3,7 +3,7 @@ import { ref, computed } from 'vue'
|
||||
import { withBase } from 'vitepress'
|
||||
|
||||
const GITHUB = 'https://github.com/xerrors/Yuxi'
|
||||
const DEMO = 'https://www.bilibili.com/video/BV1DF14BTETq/'
|
||||
const DEMO = 'https://www.bilibili.com/video/BV1TZEx6NEit/'
|
||||
|
||||
// 关键数字(占位,后续替换为真实数据)
|
||||
const stats = [
|
||||
@ -126,7 +126,7 @@ const shots = [
|
||||
{ title: '对话工作台', desc: '类 ChatGPT 的智能体对话与产物交付' },
|
||||
{ title: '智能体配置', desc: '挂载 Skills / MCP / 子智能体与中间件' },
|
||||
{ title: '知识图谱可视化', desc: '实体关系抽取与子图检索展示' },
|
||||
{ title: '拓展管理', desc: '统一管理 Skills 与 MCP 服务' }
|
||||
{ title: '智能体拓展', desc: '统一管理 Skills 与 MCP 服务' }
|
||||
]
|
||||
|
||||
// 企业级
|
||||
@ -225,7 +225,7 @@ const vReveal = {
|
||||
<div class="yx-hero__shot">
|
||||
<img
|
||||
class="yx-hero__img"
|
||||
src="https://xerrors.oss-cn-shanghai.aliyuncs.com/github/image-20260604203514048.png"
|
||||
src="https://xerrors.oss-cn-shanghai.aliyuncs.com/github/image-20260608002434299.png"
|
||||
alt="语析 Yuxi 产品界面预览"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
@ -53,6 +53,7 @@
|
||||
- 工作区知识库分类显示:知识库侧边栏按创建者分组为“我的知识库”和“共享知识库”,自己创建的知识库显示在“我的知识库”下,非自己创建的显示在“共享知识库”下;`knowledge_bases` 表新增 `created_by` 字段记录创建者 uid。
|
||||
- 工作区文件上传支持多选:`/workspace/upload` 与 Viewer 工作区上传统一使用 `files` 多文件字段,一次最多上传 50 个文件,批量上传失败时清理本次已写入文件。
|
||||
- 聊天附件新增 MinIO tmp 临时上传、可选 PDF/图片解析、确认后加入线程附件的流程;前端改为弹窗内上传、解析与确认。
|
||||
- 优化智能体对话页细节:状态面板隐藏空 section,待办名称限制为 20 个中文汉字以内,模型选择器展示供应商名称,并收紧附件状态标签与文件编辑浮动操作样式。
|
||||
- 标准化 Agent run/SSE 执行链路:run 创建时持久化输入消息并提交后入队,worker 统一写入 Redis Stream envelope,SSE 输出 `event/data/id`、心跳注释、`Last-Event-ID` 回放和终止 `end` 事件;前端强制使用 run API 并支持 ask_user_question 中断后以 resume run 恢复;事件 envelope 构造收敛到统一 helper,前端优先使用 envelope 一级 `thread_id` 路由。
|
||||
- Agent run SSE 新增 `verbose=false` 精简模式:默认仍返回完整事件载荷;精简模式仅在 SSE 输出前重建最小 payload,跳过 `metadata` 和空 `yuxi.agent_state`,将同一 data 内的 `request_id` 外提为单个字段,移除 chunk 中重复的 `meta`、`metadata`、`thread_id`、`response`、空 `namespace` 和图片 base64 等调试字段,保留消息增量、工具调用、工具结果、非空 Agent state、终止状态和 SSE 游标,前端订阅默认使用精简模式。
|
||||
- 收敛后端模块边界:文档解析从 `plugins.parser` 移动到 `knowledge.parser`,内容审查从 `plugins.guard` 移动到 `services.guard`。
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
- [ ] 调研将当前知识库映射为虚拟文件系统的可行性,先明确文件树映射、权限边界、内容读取与 Agent 工具调用形态,再决定是否实现
|
||||
- [ ] 增强知识库检索体验:增强 metadata、标签等
|
||||
- [ ] 优化思维导图构建的接口设计,支持增量构建和更新
|
||||
- [ ] 个人工作区增加可检索能力(但是不做向量化)
|
||||
|
||||
|
||||
**智能体**
|
||||
@ -25,6 +26,7 @@
|
||||
- [ ] 添加 Agent 独立调用接口,方便后续评估使用
|
||||
|
||||
**其他**
|
||||
- [ ] 历史对话新增分组(或者叫做项目)
|
||||
- [ ] 集成 Memory,基于 deepagents 的文件后端实现,需要考虑定位
|
||||
|
||||
**仅设想**
|
||||
|
||||
@ -221,6 +221,7 @@
|
||||
|
||||
<div class="state-panel-body">
|
||||
<section
|
||||
v-if="currentTodos.length"
|
||||
class="state-section"
|
||||
:class="{ 'is-collapsed': !isStateSectionExpanded('todos') }"
|
||||
>
|
||||
@ -248,10 +249,10 @@
|
||||
id="state-section-todos"
|
||||
class="state-section-content"
|
||||
>
|
||||
<div v-if="currentTodos.length" class="todo-panel-list">
|
||||
<div class="todo-panel-list">
|
||||
<div
|
||||
v-for="(todo, index) in currentTodos"
|
||||
:key="`${todo.content}-${index}`"
|
||||
:key="`${todo.fullContent}-${index}`"
|
||||
class="todo-item"
|
||||
>
|
||||
<div class="todo-item-icon" :class="todo.status || 'unknown'">
|
||||
@ -262,15 +263,17 @@
|
||||
<QuestionCircleOutlined v-else />
|
||||
</div>
|
||||
<div class="todo-item-body">
|
||||
<span class="todo-item-text">{{ todo.content }}</span>
|
||||
<span class="todo-item-text" :title="todo.fullContent">
|
||||
{{ todo.displayContent }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="state-section-empty">暂无待办</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="currentStateFiles.length"
|
||||
class="state-section"
|
||||
:class="{ 'is-collapsed': !isStateSectionExpanded('files') }"
|
||||
>
|
||||
@ -296,7 +299,7 @@
|
||||
id="state-section-files"
|
||||
class="state-section-content"
|
||||
>
|
||||
<div v-if="currentStateFiles.length" class="state-list">
|
||||
<div class="state-list">
|
||||
<div v-for="file in currentStateFiles" :key="file.key" class="state-list-item">
|
||||
<FileTypeIcon
|
||||
:name="file.name || file.path"
|
||||
@ -309,11 +312,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="state-section-empty">暂无附件或文件</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="currentArtifactFiles.length"
|
||||
class="state-section"
|
||||
:class="{ 'is-collapsed': !isStateSectionExpanded('artifacts') }"
|
||||
>
|
||||
@ -339,7 +342,7 @@
|
||||
id="state-section-artifacts"
|
||||
class="state-section-content"
|
||||
>
|
||||
<div v-if="currentArtifactFiles.length" class="state-list">
|
||||
<div class="state-list">
|
||||
<button
|
||||
v-for="file in currentArtifactFiles"
|
||||
:key="file.path"
|
||||
@ -359,11 +362,11 @@
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="state-section-empty">暂无产物</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="displaySubagentRuns.length"
|
||||
class="state-section"
|
||||
:class="{ 'is-collapsed': !isStateSectionExpanded('subagents') }"
|
||||
>
|
||||
@ -389,7 +392,7 @@
|
||||
id="state-section-subagents"
|
||||
class="state-section-content"
|
||||
>
|
||||
<div v-if="displaySubagentRuns.length" class="state-list">
|
||||
<div class="state-list">
|
||||
<div
|
||||
v-for="(run, index) in displaySubagentRuns"
|
||||
:key="run.id || `${run.subagent_type || 'subagent'}-${index}`"
|
||||
@ -427,9 +430,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="state-section-empty">暂无子智能体运行</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div v-if="!hasVisibleStateSections" class="state-panel-empty">暂无状态内容</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -600,11 +604,16 @@ const agentPanelActivePreviewPath = ref('')
|
||||
const agentPanelViewMode = ref('tree')
|
||||
const chatContentContainerRef = ref(null)
|
||||
const panelWrapperRef = ref(null) // 直接操作 DOM
|
||||
const TODO_NAME_MAX_LENGTH = 20
|
||||
let resizeStartX = 0
|
||||
let resizeStartWidth = 0
|
||||
let panelContainerWidth = 0
|
||||
let streamingStateRefreshTimer = null
|
||||
|
||||
const formatTodoName = (content) => {
|
||||
return Array.from(String(content || '')).slice(0, TODO_NAME_MAX_LENGTH).join('')
|
||||
}
|
||||
|
||||
const getPanelContainerWidth = () => {
|
||||
const container = chatContentContainerRef.value || panelWrapperRef.value?.parentElement
|
||||
return container?.clientWidth || (typeof window !== 'undefined' ? window.innerWidth : 0)
|
||||
@ -851,7 +860,15 @@ const currentArtifactFiles = computed(() =>
|
||||
)
|
||||
const currentTodos = computed(() => {
|
||||
const todos = currentAgentState.value?.todos
|
||||
return Array.isArray(todos) ? todos : []
|
||||
if (!Array.isArray(todos)) return []
|
||||
return todos.map((todo) => {
|
||||
const fullContent = String(todo?.content || '')
|
||||
return {
|
||||
...todo,
|
||||
fullContent,
|
||||
displayContent: formatTodoName(fullContent)
|
||||
}
|
||||
})
|
||||
})
|
||||
const currentSubagentRuns = computed(() => {
|
||||
const runs = currentAgentState.value?.subagent_runs
|
||||
@ -939,6 +956,13 @@ const stateSummaryLabel = computed(() => {
|
||||
displaySubagentRuns.value.length
|
||||
return total ? `${total} 项` : '暂无内容'
|
||||
})
|
||||
const hasVisibleStateSections = computed(
|
||||
() =>
|
||||
currentTodos.value.length > 0 ||
|
||||
currentStateFiles.value.length > 0 ||
|
||||
currentArtifactFiles.value.length > 0 ||
|
||||
displaySubagentRuns.value.length > 0
|
||||
)
|
||||
|
||||
const { mentionConfig } = useAgentMentionConfig({
|
||||
currentAgentState,
|
||||
@ -3140,12 +3164,13 @@ watch(currentChatId, (threadId, oldThreadId) => {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.state-section-empty {
|
||||
.state-panel-empty {
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
background: var(--gray-25);
|
||||
color: var(--gray-500);
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.todo-panel-list {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
class="agent-file-preview"
|
||||
:class="[containerClass, { 'is-full-height': fullHeight, 'is-borderless': borderless }]"
|
||||
:class="[
|
||||
containerClass,
|
||||
{ 'is-full-height': fullHeight, 'is-borderless': borderless, 'has-preview-header': showHeader }
|
||||
]"
|
||||
>
|
||||
<div v-if="showHeader" class="preview-header">
|
||||
<div class="file-title">
|
||||
@ -21,24 +24,15 @@
|
||||
{{ variant.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="canEdit" class="preview-mode-switch">
|
||||
<button
|
||||
class="preview-mode-btn"
|
||||
:class="{ active: editMode === 'preview' }"
|
||||
@click="editMode = 'preview'"
|
||||
title="预览"
|
||||
>
|
||||
<Eye :size="16" />
|
||||
</button>
|
||||
<button
|
||||
class="preview-mode-btn"
|
||||
:class="{ active: editMode === 'edit' }"
|
||||
@click="editMode = 'edit'"
|
||||
title="编辑"
|
||||
>
|
||||
<Pencil :size="16" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="canEdit && editMode !== 'edit'"
|
||||
class="modal-action-btn"
|
||||
@click="editMode = 'edit'"
|
||||
title="编辑"
|
||||
aria-label="编辑"
|
||||
>
|
||||
<FilePen :size="18" />
|
||||
</button>
|
||||
|
||||
<div v-if="isHtmlFile" class="preview-mode-switch">
|
||||
<button
|
||||
@ -86,34 +80,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="canEdit && editMode === 'edit'" class="edit-floating-actions">
|
||||
<span v-if="draftChanged" class="edit-status-badge">未保存</span>
|
||||
<button
|
||||
v-if="draftChanged"
|
||||
class="edit-floating-btn edit-floating-btn-primary"
|
||||
:disabled="saving"
|
||||
@click="requestSave"
|
||||
:title="saving ? '保存中' : '保存'"
|
||||
:aria-label="saving ? '保存中' : '保存'"
|
||||
>
|
||||
<Save :size="14" />
|
||||
</button>
|
||||
<button
|
||||
class="edit-floating-btn"
|
||||
:class="{ 'edit-floating-btn-danger': draftChanged }"
|
||||
:disabled="saving"
|
||||
@click="cancelEdit"
|
||||
title="取消"
|
||||
aria-label="取消"
|
||||
>
|
||||
<X :size="14" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="file-content"
|
||||
:class="[
|
||||
contentClass,
|
||||
{
|
||||
'is-editing': canEdit && editMode === 'edit',
|
||||
'is-iframe-preview':
|
||||
file?.previewType === 'pdf' || (isHtmlFile && htmlPreviewMode === 'render')
|
||||
}
|
||||
]"
|
||||
>
|
||||
<div v-if="canEdit && editMode === 'edit'" class="edit-floating-actions">
|
||||
<span v-if="draftChanged" class="edit-status-badge">修改未保存</span>
|
||||
<button
|
||||
v-if="draftChanged"
|
||||
class="edit-floating-btn edit-floating-btn-primary"
|
||||
:disabled="saving"
|
||||
@click="requestSave"
|
||||
title="保存"
|
||||
>
|
||||
<Save :size="16" />
|
||||
<span v-if="saving">保存中...</span>
|
||||
<span v-else>保存</span>
|
||||
</button>
|
||||
<button class="edit-floating-btn" :disabled="saving" @click="cancelEdit" title="取消">
|
||||
<X :size="16" />
|
||||
<span>取消</span>
|
||||
</button>
|
||||
</div>
|
||||
<template v-if="canEdit && editMode === 'edit'">
|
||||
<textarea
|
||||
v-model="draftContent"
|
||||
@ -255,11 +256,10 @@ import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
import {
|
||||
Code2,
|
||||
Download,
|
||||
Eye,
|
||||
Globe,
|
||||
Maximize,
|
||||
PanelRightClose,
|
||||
Pencil,
|
||||
FilePen,
|
||||
Save,
|
||||
X
|
||||
} from 'lucide-vue-next'
|
||||
@ -487,6 +487,7 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped lang="less">
|
||||
.agent-file-preview {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
@ -598,6 +599,10 @@ onUnmounted(() => {
|
||||
overflow-y: auto;
|
||||
border-radius: 0px;
|
||||
|
||||
&.is-editing {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.is-iframe-preview {
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -627,51 +632,69 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.edit-floating-actions {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: var(--gray-0);
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.agent-file-preview.has-preview-header .edit-floating-actions {
|
||||
top: 52px;
|
||||
}
|
||||
|
||||
.edit-status-badge {
|
||||
margin-right: auto;
|
||||
font-size: 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--color-warning-100);
|
||||
border-radius: 999px;
|
||||
background: var(--color-warning-50);
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
color: var(--color-warning-700);
|
||||
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
|
||||
pointer-events: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.edit-floating-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
border: 1px solid var(--gray-200);
|
||||
border-radius: 6px;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border: 1px solid var(--gray-150);
|
||||
border-radius: 50%;
|
||||
background: var(--gray-0);
|
||||
color: var(--gray-700);
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.14);
|
||||
pointer-events: auto;
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
border-color 0.15s ease,
|
||||
color 0.15s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: var(--gray-250);
|
||||
background: var(--gray-50);
|
||||
border-color: var(--gray-300);
|
||||
color: var(--gray-900);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: var(--gray-300);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--gray-150);
|
||||
background: var(--gray-0);
|
||||
border-color: var(--gray-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -701,9 +724,17 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.edit-floating-btn-danger {
|
||||
&:hover:not(:disabled) {
|
||||
border-color: var(--color-error-500);
|
||||
background: var(--color-error-50);
|
||||
color: var(--color-error-700);
|
||||
}
|
||||
}
|
||||
|
||||
.file-edit-textarea {
|
||||
width: 100%;
|
||||
min-height: calc(80vh - 40px);
|
||||
min-height: 100%;
|
||||
padding: 12px;
|
||||
border: 0;
|
||||
outline: none;
|
||||
@ -711,8 +742,8 @@ onUnmounted(() => {
|
||||
background: var(--gray-0);
|
||||
color: var(--gray-1000);
|
||||
font-family: 'JetBrains Mono', 'Fira Code', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.file-edit-textarea:disabled {
|
||||
|
||||
@ -673,6 +673,20 @@ watch(
|
||||
&.resizing {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.side-preview-shell) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
:deep(.preview-header) {
|
||||
min-height: 32px;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
|
||||
@ -42,7 +42,13 @@
|
||||
|
||||
<div class="attachment-status-row">
|
||||
<div class="attachment-status-meta">
|
||||
<a-tag :color="getStatusColor(item.status)">{{ getStatusLabel(item.status) }}</a-tag>
|
||||
<a-tag
|
||||
:color="getStatusColor(item.status)"
|
||||
:bordered="false"
|
||||
class="attachment-status-tag"
|
||||
>
|
||||
{{ getStatusLabel(item.status) }}
|
||||
</a-tag>
|
||||
<span>{{ formatFileSize(item.fileSize) }}</span>
|
||||
<span v-if="item.error" class="attachment-error">{{ item.error }}</span>
|
||||
<span v-else-if="item.parseError" class="attachment-error">{{
|
||||
@ -560,17 +566,28 @@ const formatFileSize = (size) => {
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
color: var(--gray-500);
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.attachment-status-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.attachment-status-tag {
|
||||
min-height: 18px;
|
||||
margin-inline-end: 0;
|
||||
padding: 0 5px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.attachment-error {
|
||||
color: var(--color-error-700);
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
:key="`v2-${providerId}`"
|
||||
>
|
||||
<template #title>
|
||||
<span>{{ providerId }}</span>
|
||||
<span :title="providerId">{{ getProviderDisplayName(providerId, providerData) }}</span>
|
||||
</template>
|
||||
<a-menu-item
|
||||
v-for="model in providerData.models"
|
||||
@ -127,7 +127,13 @@ const filteredV2Models = computed(() => {
|
||||
|
||||
return Object.entries(v2Models.value).reduce((result, [providerId, providerData]) => {
|
||||
const models = (providerData.models || []).filter((model) => {
|
||||
return [providerId, model.spec, model.model_id, model.display_name].some((value) =>
|
||||
return [
|
||||
providerId,
|
||||
getProviderDisplayName(providerId, providerData),
|
||||
model.spec,
|
||||
model.model_id,
|
||||
model.display_name
|
||||
].some((value) =>
|
||||
String(value || '')
|
||||
.toLowerCase()
|
||||
.includes(keyword)
|
||||
@ -146,6 +152,15 @@ const hasFilteredModels = computed(() => {
|
||||
return Object.values(filteredV2Models.value).some((providerData) => providerData.models?.length)
|
||||
})
|
||||
|
||||
const getProviderDisplayName = (providerId, providerData = {}) => {
|
||||
return (
|
||||
providerData.provider_display_name ||
|
||||
providerData.display_name ||
|
||||
providerData.name ||
|
||||
providerId
|
||||
)
|
||||
}
|
||||
|
||||
// 拉取 v2 模型列表
|
||||
const fetchV2Models = async () => {
|
||||
if (loadingV2Models.value) return
|
||||
|
||||
@ -22,7 +22,9 @@
|
||||
<CloseCircleOutlined v-else-if="todo.status === 'cancelled'" class="icon cancelled" />
|
||||
<QuestionCircleOutlined v-else class="icon unknown" />
|
||||
</div>
|
||||
<span class="todo-text">{{ todo.content }}</span>
|
||||
<span class="todo-text" :title="formatTodoNameTitle(todo.content)">
|
||||
{{ formatTodoName(todo.content) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="todoListData(resultContent).length === 0" class="no-results">
|
||||
@ -51,6 +53,14 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const TODO_NAME_MAX_LENGTH = 20
|
||||
|
||||
const formatTodoName = (content) => {
|
||||
return Array.from(String(content || '')).slice(0, TODO_NAME_MAX_LENGTH).join('')
|
||||
}
|
||||
|
||||
const formatTodoNameTitle = (content) => String(content || '')
|
||||
|
||||
const query = computed(() => {
|
||||
// 1. Try to get status from result content (Priority)
|
||||
const content = props.toolCall.tool_call_result?.content
|
||||
@ -59,15 +69,15 @@ const query = computed(() => {
|
||||
if (list && list.length > 0) {
|
||||
// 1. In Progress
|
||||
const inProgress = list.find((item) => item.status === 'in_progress')
|
||||
if (inProgress) return `进行中: ${inProgress.content}`
|
||||
if (inProgress) return `进行中: ${formatTodoName(inProgress.content)}`
|
||||
|
||||
// 2. Pending
|
||||
const pending = list.find((item) => item.status === 'pending')
|
||||
if (pending) return `待处理: ${pending.content}`
|
||||
if (pending) return `待处理: ${formatTodoName(pending.content)}`
|
||||
|
||||
// 3. Last item fallback
|
||||
const last = list[list.length - 1]
|
||||
return `更新: ${last.content}`
|
||||
return `更新: ${formatTodoName(last.content)}`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,15 +30,6 @@
|
||||
<span>上传 Skill</span>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a-button
|
||||
:href="SKILLS_DOC_URL"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="lucide-icon-btn"
|
||||
>
|
||||
<ExternalLink :size="14" />
|
||||
<span>安装文档</span>
|
||||
</a-button>
|
||||
<a-tooltip title="刷新 Skills" placement="bottom">
|
||||
<a-button class="lucide-icon-btn" :disabled="loading" @click="fetchSkills">
|
||||
<RefreshCw :size="14" />
|
||||
@ -611,7 +602,6 @@ import {
|
||||
Check,
|
||||
Plus,
|
||||
Minus,
|
||||
ExternalLink,
|
||||
LoaderCircle
|
||||
} from 'lucide-vue-next'
|
||||
import { skillApi } from '@/apis/skill_api'
|
||||
@ -623,7 +613,6 @@ import MarkdownPreview from '@/components/common/MarkdownPreview.vue'
|
||||
import { formatExtensionCardTitle } from '@/utils/extensionDisplayName'
|
||||
|
||||
const BookMarkedIcon = BookMarked
|
||||
const SKILLS_DOC_URL = 'https://xerrors.github.io/Yuxi/agents/skills-management.html'
|
||||
const RECOMMENDED_SKILLS = [
|
||||
{
|
||||
slug: 'skill-creator',
|
||||
|
||||
@ -891,10 +891,6 @@ onMounted(() => {
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
:deep(.skill-file-preview-content .file-edit-textarea) {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
:deep(.skill-file-preview-content .file-content-pre.code-highlight code) {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@ -48,7 +48,6 @@ defineEmits(['close', 'save', 'switchVariant'])
|
||||
.workspace-preview-pane {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border-left: 1px solid var(--gray-100);
|
||||
background: var(--gray-0);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@ -3,11 +3,11 @@
|
||||
<PageHeader
|
||||
v-if="!isDetailPage"
|
||||
v-model:active-key="activeTab"
|
||||
title="扩展管理"
|
||||
title="智能体扩展"
|
||||
:tabs="extensionTabs"
|
||||
:loading="activeChildLoading"
|
||||
:show-border="true"
|
||||
aria-label="扩展管理视图切换"
|
||||
aria-label="智能体扩展视图切换"
|
||||
/>
|
||||
|
||||
<div v-if="!isDetailPage" class="extensions-content">
|
||||
|
||||
@ -57,11 +57,11 @@ watch(activeTab, (tab) => {
|
||||
<div class="model-manage-view">
|
||||
<PageHeader
|
||||
v-model:active-key="activeTab"
|
||||
title="模型管理"
|
||||
title="智能体管理"
|
||||
:tabs="modelManageTabs"
|
||||
:loading="activeLoading"
|
||||
:show-border="true"
|
||||
aria-label="模型管理视图切换"
|
||||
aria-label="智能体管理视图切换"
|
||||
>
|
||||
<template #info>
|
||||
<div v-if="activeTab === 'agents'" class="summary-strip">
|
||||
|
||||
@ -912,15 +912,13 @@ watch(useInlinePreview, (isInline, wasInline) => {
|
||||
}
|
||||
|
||||
.workspace-preview-resizer {
|
||||
width: 3px;
|
||||
min-width: 3px;
|
||||
border-left: 1px solid var(--gray-100);
|
||||
border-right: 1px solid var(--gray-100);
|
||||
background: var(--gray-25);
|
||||
width: 2px;
|
||||
min-width: 2px;
|
||||
background: var(--gray-100);
|
||||
cursor: col-resize;
|
||||
|
||||
&:hover {
|
||||
background: var(--main-20);
|
||||
background: var(--gray-200);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user