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