feat: 优化 Agent 组件和模型选择器,调整 UI 细节
This commit is contained in:
parent
253cc0b149
commit
7f454e64e6
@ -22,6 +22,7 @@
|
||||
- [ ] 子智能体的双向通信,缺少 ask_for_main_agent 的机制
|
||||
- [ ] 子智能体与子智能体的通信机制
|
||||
- [ ] 如何停掉一个子智能体、查看智能体的进度
|
||||
- [ ] 优化 Agent `read_file` 工具:至少对齐 DeepAgents 的读取行为
|
||||
- [ ] RAG 评估支持 Agent 模式 <Badge text="v0.7.1" />
|
||||
- [ ] 添加 Agent 独立调用接口,方便后续评估使用
|
||||
- [ ] 任务队列 <Badge text="v0.7.2" />
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
aria-controls="agent-state-panel"
|
||||
@click.stop="toggleStatePanel"
|
||||
>
|
||||
<SquareCheck size="18" class="nav-btn-icon" />
|
||||
<LayoutList size="18" class="nav-btn-icon" />
|
||||
<span class="hide-text">状态</span>
|
||||
</button>
|
||||
<button
|
||||
@ -157,6 +157,9 @@
|
||||
@remove-attachment="handleAttachmentRemove"
|
||||
>
|
||||
<template #actions-left-extra>
|
||||
<slot name="input-actions-left" :has-active-thread="!!currentChatId"></slot>
|
||||
</template>
|
||||
<template #actions-right-extra>
|
||||
<div class="input-model-selector">
|
||||
<ModelSelectorComponent
|
||||
:model_spec="currentModelSpec"
|
||||
@ -166,9 +169,6 @@
|
||||
@select-model="handleModelSelect"
|
||||
/>
|
||||
</div>
|
||||
<slot name="input-actions-left" :has-active-thread="!!currentChatId"></slot>
|
||||
</template>
|
||||
<template #actions-right-extra>
|
||||
<slot name="input-actions-right" :has-active-thread="!!currentChatId"></slot>
|
||||
</template>
|
||||
</AgentInputArea>
|
||||
@ -497,7 +497,7 @@ import {
|
||||
onDeactivated
|
||||
} from 'vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { ChevronDown, FolderKanban, RefreshCw, SquareCheck } from 'lucide-vue-next'
|
||||
import { ChevronDown, FolderKanban, RefreshCw, LayoutList } from 'lucide-vue-next'
|
||||
import { formatFileSize } from '@/utils/file_utils'
|
||||
import FileTypeIcon from '@/components/common/FileTypeIcon.vue'
|
||||
import { generatePixelAvatar } from '@/utils/pixelAvatar'
|
||||
@ -2757,7 +2757,8 @@ watch(currentChatId, (threadId, oldThreadId) => {
|
||||
|
||||
.side-panel--state.is-docked {
|
||||
align-self: flex-start;
|
||||
margin: 0 8px 8px 0;
|
||||
margin: 8px 8px 8px 0;
|
||||
max-height: calc(100% - 16px);
|
||||
}
|
||||
|
||||
.side-panel--state.is-floating {
|
||||
|
||||
@ -122,6 +122,7 @@ const v2Models = ref({})
|
||||
const loadingV2Models = ref(false)
|
||||
const dropdownOpen = ref(false)
|
||||
const modelSearchKeyword = ref('')
|
||||
let fetchV2ModelsPromise = null
|
||||
|
||||
const filteredV2Models = computed(() => {
|
||||
const keyword = modelSearchKeyword.value.trim().toLowerCase()
|
||||
@ -165,28 +166,42 @@ const getProviderDisplayName = (providerId, providerData = {}) => {
|
||||
|
||||
// 拉取 v2 模型列表
|
||||
const fetchV2Models = async () => {
|
||||
if (loadingV2Models.value) return
|
||||
if (fetchV2ModelsPromise) return fetchV2ModelsPromise
|
||||
|
||||
loadingV2Models.value = true
|
||||
try {
|
||||
const response = await modelProviderApi.getV2Models('chat')
|
||||
if (response.success) {
|
||||
v2Models.value = response.data || {}
|
||||
fetchV2ModelsPromise = (async () => {
|
||||
try {
|
||||
const response = await modelProviderApi.getV2Models('chat')
|
||||
if (response.success) {
|
||||
v2Models.value = response.data || {}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load v2 models:', error)
|
||||
} finally {
|
||||
loadingV2Models.value = false
|
||||
fetchV2ModelsPromise = null
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load v2 models:', error)
|
||||
} finally {
|
||||
loadingV2Models.value = false
|
||||
}
|
||||
})()
|
||||
|
||||
return fetchV2ModelsPromise
|
||||
}
|
||||
|
||||
// 下拉展开时触发实时刷新(仅在打开瞬间触发,关闭时忽略)
|
||||
const handleOpenChange = (open) => {
|
||||
// 下拉展开前先刷新模型列表,避免弹层打开后再因数据加载发生高度跳变。
|
||||
const handleOpenChange = async (open) => {
|
||||
if (props.disabled) {
|
||||
dropdownOpen.value = false
|
||||
return
|
||||
}
|
||||
dropdownOpen.value = open
|
||||
if (open) fetchV2Models()
|
||||
|
||||
if (!open) {
|
||||
dropdownOpen.value = false
|
||||
return
|
||||
}
|
||||
|
||||
await fetchV2Models()
|
||||
if (!props.disabled) {
|
||||
dropdownOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 强制刷新缓存
|
||||
|
||||
@ -21,13 +21,46 @@
|
||||
<User class="icon" />
|
||||
<span class="user-greeting">{{ greeting }}</span>
|
||||
</div>
|
||||
<div class="task-center-entry" @click="openTaskCenter">
|
||||
<a-badge :count="activeTaskCount" :overflow-count="99" class="task-center-badge">
|
||||
<span class="task-center-button">
|
||||
<div class="header-actions">
|
||||
<a-tooltip title="系统设置">
|
||||
<button
|
||||
type="button"
|
||||
class="header-action-button"
|
||||
aria-label="系统设置"
|
||||
@click="openSettings"
|
||||
>
|
||||
<Settings class="icon" />
|
||||
</button>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="themeStore.isDark ? '切换到浅色模式' : '切换到深色模式'">
|
||||
<button
|
||||
type="button"
|
||||
class="header-action-button"
|
||||
aria-label="切换主题"
|
||||
@click="toggleTheme"
|
||||
>
|
||||
<Sun v-if="themeStore.isDark" class="icon" />
|
||||
<Moon v-else class="icon" />
|
||||
</button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="任务中心">
|
||||
<button
|
||||
type="button"
|
||||
class="header-action-button task-center-button"
|
||||
:class="{ active: taskerStore.isDrawerOpen }"
|
||||
aria-label="任务中心"
|
||||
@click="openTaskCenter"
|
||||
>
|
||||
<ClipboardList class="icon" />
|
||||
<span class="task-center-label">任务中心</span>
|
||||
</span>
|
||||
</a-badge>
|
||||
<a-badge
|
||||
:count="activeTaskCount"
|
||||
:overflow-count="99"
|
||||
class="task-center-badge"
|
||||
size="small"
|
||||
/>
|
||||
</button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -35,11 +68,12 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { ref, computed, inject, onMounted, onUnmounted } from 'vue'
|
||||
import { useInfoStore } from '@/stores/info'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { Clock, User, ClipboardList } from 'lucide-vue-next'
|
||||
import { Clock, User, ClipboardList, Settings, Sun, Moon } from 'lucide-vue-next'
|
||||
import { useTaskerStore } from '@/stores/tasker'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import dayjs from '@/utils/time'
|
||||
|
||||
@ -47,7 +81,9 @@ import dayjs from '@/utils/time'
|
||||
const infoStore = useInfoStore()
|
||||
const userStore = useUserStore()
|
||||
const taskerStore = useTaskerStore()
|
||||
const themeStore = useThemeStore()
|
||||
const { activeCount: activeCountRef } = storeToRefs(taskerStore)
|
||||
const { openSettingsModal } = inject('settingsModal', {})
|
||||
|
||||
// 响应式数据
|
||||
const currentTime = ref('')
|
||||
@ -86,6 +122,14 @@ const openTaskCenter = () => {
|
||||
taskerStore.openDrawer()
|
||||
}
|
||||
|
||||
const openSettings = () => {
|
||||
openSettingsModal?.(userStore.isAdmin ? 'base' : 'account')
|
||||
}
|
||||
|
||||
const toggleTheme = () => {
|
||||
themeStore.toggleTheme()
|
||||
}
|
||||
|
||||
// 更新时间
|
||||
const updateTime = () => {
|
||||
const now = dayjs().tz('Asia/Shanghai')
|
||||
@ -159,57 +203,62 @@ onUnmounted(() => {
|
||||
.status-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
gap: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.task-center-entry {
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.task-center-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.task-center-button {
|
||||
.header-action-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 14px;
|
||||
border-radius: 999px;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
background-color: transparent;
|
||||
color: var(--main-600, #2563eb);
|
||||
color: var(--gray-600, #4b5563);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
border: 1px solid rgba(37, 99, 235, 0.3);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
border-color: var(--gray-150, #e5e7eb);
|
||||
background: var(--gray-0, #fff);
|
||||
color: var(--gray-900, #111827);
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.task-center-button .icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
color: inherit;
|
||||
.task-center-button {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.task-center-label {
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.task-center-entry:hover .task-center-button {
|
||||
background-color: rgba(37, 99, 235, 0.08);
|
||||
color: var(--main-700, #1d4ed8);
|
||||
border-color: rgba(37, 99, 235, 0.5);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.task-center-badge :deep(.ant-badge-count) {
|
||||
background-color: var(--main-color, #1d4ed8);
|
||||
box-shadow: 0 0 0 1px var(--gray-25, #f9fafb);
|
||||
}
|
||||
|
||||
.time-info,
|
||||
@ -255,7 +304,15 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.status-right {
|
||||
gap: 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.task-center-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.time-info,
|
||||
|
||||
@ -313,7 +313,7 @@ const formatResultData = (data) => {
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--gray-500);
|
||||
color: var(--gray-600);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@ -329,7 +329,7 @@ const formatResultData = (data) => {
|
||||
:deep(.tag) {
|
||||
font-size: 11px;
|
||||
color: var(--gray-500);
|
||||
background-color: var(--gray-50);
|
||||
// background-color: var(--gray-50);
|
||||
padding: 0px 4px;
|
||||
border-radius: 4px;
|
||||
margin-left: 8px;
|
||||
@ -337,11 +337,11 @@ const formatResultData = (data) => {
|
||||
|
||||
&.success {
|
||||
color: var(--color-success-500);
|
||||
background-color: var(--color-success-50);
|
||||
// background-color: var(--color-success-50);
|
||||
}
|
||||
&.error {
|
||||
color: var(--color-error-500);
|
||||
background-color: var(--color-error-50);
|
||||
// background-color: var(--color-error-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
@click="toggleToolCallsExpanded"
|
||||
>
|
||||
<span class="summary-leading">
|
||||
<Wrench size="14" />
|
||||
<Atom size="14" />
|
||||
</span>
|
||||
<span class="summary-content">
|
||||
<span class="summary-title">{{ toolCallsSummaryTitle }}</span>
|
||||
@ -40,7 +40,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch, inject } from 'vue'
|
||||
import { ChevronDown, ChevronRight, Wrench } from 'lucide-vue-next'
|
||||
import { ChevronDown, ChevronRight, Atom } from 'lucide-vue-next'
|
||||
import { ToolCallRenderer } from '@/components/ToolCallingResult'
|
||||
import { getToolCallId, normalizeToolCalls } from '@/components/ToolCallingResult/toolRegistry'
|
||||
|
||||
@ -106,7 +106,7 @@ const getToolCallLabel = (toolCall) => {
|
||||
|
||||
const toolCallsSummaryTitle = computed(() => {
|
||||
if (normalizedToolCalls.value.length === 1) {
|
||||
return `使用了工具: ${getToolCallLabel(normalizedToolCalls.value[0])}`
|
||||
return `调用: ${getToolCallLabel(normalizedToolCalls.value[0])}`
|
||||
}
|
||||
return `已调用 ${normalizedToolCalls.value.length} 个工具`
|
||||
})
|
||||
@ -211,7 +211,7 @@ const toggleToolCallsExpanded = () => {
|
||||
margin-left: 4px;
|
||||
font-size: 11px;
|
||||
padding: 0px 4px;
|
||||
background: var(--gray-25);
|
||||
// background: var(--gray-25);
|
||||
color: var(--gray-600);
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
|
||||
@ -56,14 +56,13 @@
|
||||
<template #bodyCell="{ column, record, index }">
|
||||
<template v-if="column.key === 'rank'">
|
||||
<div class="rank-display">
|
||||
<span v-if="index < 3" class="rank-medal">
|
||||
{{ index === 0 ? '🥇' : index === 1 ? '🥈' : '🥉' }}
|
||||
</span>
|
||||
<span v-else class="rank-number">{{ index + 1 }}</span>
|
||||
<span class="rank-number" :class="{ featured: index < 3 }">{{ index + 1 }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'agent_id'">
|
||||
<a-tag color="blue">{{ resolveAgentName(record.agent_id) }}</a-tag>
|
||||
<span class="agent-name" :title="resolveAgentName(record.agent_id)">
|
||||
{{ resolveAgentName(record.agent_id) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'satisfaction_rate'">
|
||||
<a-statistic
|
||||
@ -379,23 +378,38 @@ defineExpose({
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.rank-medal {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.rank-number {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: var(--gray-100);
|
||||
background-color: var(--gray-50);
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--gray-600);
|
||||
border: 1px solid var(--gray-200);
|
||||
border: 1px solid var(--gray-150);
|
||||
}
|
||||
|
||||
.rank-number.featured {
|
||||
background-color: var(--main-20);
|
||||
border-color: var(--main-100);
|
||||
color: var(--main-color);
|
||||
}
|
||||
}
|
||||
|
||||
.agent-name {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
color: var(--gray-900);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// AgentStatsComponent 特有的样式
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ basicStats?.total_conversations || 0 }}</div>
|
||||
<div class="stat-label">总对话数</div>
|
||||
<div class="stat-label">累计会话</div>
|
||||
<div class="stat-trend" v-if="basicStats?.conversation_trend">
|
||||
<TrendingUp v-if="basicStats.conversation_trend > 0" class="trend-icon up" />
|
||||
<TrendingDown v-else-if="basicStats.conversation_trend < 0" class="trend-icon down" />
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
:single-mode="false"
|
||||
@thread-change="handleThreadChange"
|
||||
>
|
||||
<template #input-actions-right="{ hasActiveThread }">
|
||||
<template #input-actions-left="{ hasActiveThread }">
|
||||
<a-dropdown
|
||||
v-if="selectedAgentId"
|
||||
v-model:open="agentDropdownOpen"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user