feat: 智能体对话界面优化

- 消息列表修改为所有的对话,不区分智能体
- 智能体会在新建对话的时候切换
- 配置选择从右上角移动到输入框
- 其他细节优化
This commit is contained in:
Wenjie Zhang 2026-03-16 21:39:49 +08:00
parent 06cc8cbf7a
commit 1764ffedb0
8 changed files with 236 additions and 429 deletions

View File

@ -746,7 +746,7 @@ async def create_thread(
@chat.get("/threads", response_model=list[ThreadResponse])
async def list_threads(
agent_id: str,
agent_id: str | None = Query(None),
limit: int = Query(100, ge=1, le=500),
offset: int = Query(0, ge=0),
db: AsyncSession = Depends(get_db),

View File

@ -153,15 +153,12 @@ async def create_thread_view(
async def list_threads_view(
*,
agent_id: str,
agent_id: str | None,
db: AsyncSession,
current_user_id: str,
limit: int | None = None,
offset: int = 0,
) -> list[dict]:
if not agent_id:
raise HTTPException(status_code=422, detail="agent_id 不能为空")
conv_repo = ConversationRepository(db)
conversations = await conv_repo.list_conversations(
user_id=str(current_user_id),

View File

@ -285,13 +285,20 @@ export const multimodalApi = {
export const threadApi = {
/**
* 获取对话线程列表
* @param {string} agentId - 智能体ID
* @param {string | null | undefined} agentId - 智能体ID可选不传时返回全部智能体对话
* @param {number} limit - 返回数量限制默认100
* @param {number} offset - 偏移量默认0
* @returns {Promise} - 对话线程列表
*/
getThreads: (agentId, limit = 100, offset = 0) => {
const url = `/api/chat/threads?agent_id=${agentId}&limit=${limit}&offset=${offset}`
getThreads: (agentId = null, limit = 100, offset = 0) => {
const params = new URLSearchParams({
limit: String(limit),
offset: String(offset)
})
if (agentId) {
params.set('agent_id', agentId)
}
const url = `/api/chat/threads?${params.toString()}`
return apiGet(url)
},

View File

@ -17,7 +17,6 @@
@rename-chat="renameChat"
@toggle-pin="togglePinChat"
@toggle-sidebar="toggleSidebar"
@open-agent-modal="openAgentModal"
@load-more-chats="loadMoreChats"
:class="{
'sidebar-open': chatUIStore.isSidebarOpen,
@ -51,14 +50,6 @@
<MessageCirclePlus v-else class="nav-btn-icon" size="16" />
<span class="text">新对话</span>
</div>
<div v-if="!props.singleMode" class="agent-nav-btn" @click="openAgentModal">
<LoaderCircle v-if="!currentAgent" class="nav-btn-icon loading-icon" size="18" />
<Bot v-else :size="18" class="nav-btn-icon" />
<span class="text hide-text">
{{ currentAgentName || '选择智能体' }}
</span>
<ChevronDown size="16" class="switch-icon" />
</div>
</div>
<div class="header__right">
<!-- AgentState 显示按钮已移动到输入框底部 -->
@ -131,6 +122,14 @@
<h1>👋 您好我是{{ currentAgentName }}</h1>
</div>
<div v-if="showStartAgentSegment" class="agent-segment-wrapper">
<a-segmented
:value="currentAgentId"
:options="agentSegmentOptions"
@change="handleStartAgentChange"
/>
</div>
<AgentInputArea
ref="messageInputRef"
v-model="userInput"
@ -148,7 +147,11 @@
@send="handleSendOrStop"
@attachment-changed="handleAgentStateRefresh"
@toggle-panel="toggleAgentPanel"
/>
>
<template #actions-left-extra>
<slot name="input-actions-left"></slot>
</template>
</AgentInputArea>
<!-- 示例问题 -->
<div
@ -167,8 +170,8 @@
</div>
</div>
<div class="bottom-actions" v-else>
<p class="note">请注意辨别内容的可靠性</p>
<div class="bottom-actions" v-if="conversations.length > 0">
<p class="note">当前智能体{{ currentThreadAgentName }}请注意辨别内容的可靠性</p>
</div>
</div>
</div>
@ -210,7 +213,7 @@ import AgentInputArea from '@/components/AgentInputArea.vue'
import AgentMessageComponent from '@/components/AgentMessageComponent.vue'
import ChatSidebarComponent from '@/components/ChatSidebarComponent.vue'
import RefsComponent from '@/components/RefsComponent.vue'
import { PanelLeftOpen, MessageCirclePlus, LoaderCircle, ChevronDown, Bot } from 'lucide-vue-next'
import { PanelLeftOpen, MessageCirclePlus, LoaderCircle } from 'lucide-vue-next'
import { handleChatError, handleValidationError } from '@/utils/errorHandler'
import { ScrollController } from '@/utils/scrollController'
import { AgentValidator } from '@/utils/agentValidator'
@ -229,7 +232,6 @@ const props = defineProps({
agentId: { type: String, default: '' },
singleMode: { type: Boolean, default: true }
})
const emit = defineEmits(['open-config', 'open-agent-modal'])
// ==================== STORE MANAGEMENT ====================
const agentStore = useAgentStore()
@ -336,6 +338,17 @@ const currentThread = computed(() => {
return threads.value.find((thread) => thread.id === currentChatId.value) || null
})
const currentThreadAgentName = computed(() => {
const threadAgentId = currentThread.value?.agent_id
if (threadAgentId && agents.value?.length) {
const threadAgent = agents.value.find((agent) => agent.id === threadAgentId)
if (threadAgent?.name) {
return threadAgent.name
}
}
return currentAgentName.value
})
//
const supportsFileUpload = computed(() => {
if (!currentAgent.value) return false
@ -510,6 +523,27 @@ const conversations = computed(() => {
return historyConvs
})
const agentSegmentOptions = computed(() => {
return (agents.value || []).map((agent) => ({
label: agent.name || 'Unknown',
value: agent.id
}))
})
const showStartAgentSegment = computed(() => {
return !props.singleMode && !conversations.value.length && agentSegmentOptions.value.length > 1
})
const handleStartAgentChange = async (agentId) => {
if (!agentId || agentId === currentAgentId.value) return
if (conversations.value.length > 0) return
try {
await agentStore.selectAgent(agentId)
} catch (error) {
handleChatError(error, 'load')
}
}
const isLoadingMessages = computed(() => chatUIStore.isLoadingMessages)
const isStreaming = computed(() => {
const threadState = currentThreadState.value
@ -610,8 +644,8 @@ const resetOnGoingConv = (threadId = null) => {
// ==================== 线 ====================
// 线
const fetchThreads = async (agentId = null) => {
const targetAgentId = agentId || currentAgentId.value
if (!targetAgentId) return
const targetAgentId = props.singleMode ? agentId || currentAgentId.value : agentId
if (props.singleMode && !targetAgentId) return
chatUIStore.isLoadingThreads = true
try {
@ -632,8 +666,8 @@ const fetchThreads = async (agentId = null) => {
const loadMoreChats = async () => {
if (isLoadingMoreChats.value || !hasMoreChats.value) return
const targetAgentId = currentAgentId.value
if (!targetAgentId) return
const targetAgentId = props.singleMode ? currentAgentId.value : null
if (props.singleMode && !targetAgentId) return
isLoadingMoreChats.value = true
try {
@ -1350,17 +1384,19 @@ const createNewChat = async () => {
}
const selectChat = async (chatId) => {
if (
!AgentValidator.validateAgentIdWithError(
currentAgentId.value,
'选择对话',
handleValidationError
)
)
const targetChat = threads.value.find((chat) => chat.id === chatId) || null
const targetAgentId = targetChat?.agent_id || currentAgentId.value
const previousThreadId = chatState.currentThreadId
if (!targetAgentId) {
handleValidationError('选择对话失败:缺少智能体信息')
return
}
if (!AgentValidator.validateAgentIdWithError(targetAgentId, '选择对话', handleValidationError))
return
// 线
const previousThreadId = chatState.currentThreadId
if (previousThreadId && previousThreadId !== chatId) {
const previousThreadState = getThreadState(previousThreadId)
if (previousThreadState?.isStreaming && previousThreadState.streamAbortController) {
@ -1372,10 +1408,22 @@ const selectChat = async (chatId) => {
stopRunStreamSubscription(previousThreadId)
}
// 线
chatState.currentThreadId = chatId
if (!props.singleMode && targetChat?.agent_id && targetChat.agent_id !== currentAgentId.value) {
try {
await agentStore.selectAgent(targetChat.agent_id)
} catch (error) {
chatState.currentThreadId = previousThreadId
handleChatError(error, 'load')
return
}
}
chatUIStore.isLoadingMessages = true
try {
await fetchThreadMessages({ agentId: currentAgentId.value, threadId: chatId })
await fetchThreadMessages({ agentId: targetAgentId, threadId: chatId })
} catch (error) {
handleChatError(error, 'load')
} finally {
@ -1384,7 +1432,7 @@ const selectChat = async (chatId) => {
await nextTick()
scrollController.scrollToBottomStaticForce()
await fetchAgentState(currentAgentId.value, chatId)
await fetchAgentState(targetAgentId, chatId)
await resumeActiveRunForThread(chatId)
}
@ -1657,7 +1705,6 @@ defineExpose({
const toggleSidebar = () => {
chatUIStore.toggleSidebar()
}
const openAgentModal = () => emit('open-agent-modal')
const handleAgentStateRefresh = async (threadId = null) => {
if (!currentAgentId.value) return
@ -1744,8 +1791,8 @@ const getConversationSources = (conv) => {
// ==================== LIFECYCLE & WATCHERS ====================
const loadChatsList = async () => {
const agentId = currentAgentId.value
if (!agentId) {
const agentId = props.singleMode ? currentAgentId.value : null
if (props.singleMode && !agentId) {
console.warn('No agent selected, cannot load chats list')
threads.value = []
chatState.currentThreadId = null
@ -1754,7 +1801,7 @@ const loadChatsList = async () => {
try {
await fetchThreads(agentId)
if (currentAgentId.value !== agentId) return
if (props.singleMode && currentAgentId.value !== agentId) return
// 线线线
if (
@ -1791,6 +1838,13 @@ onMounted(async () => {
watch(
currentAgentId,
async (newAgentId, oldAgentId) => {
if (!props.singleMode) {
if (oldAgentId === undefined) {
await loadChatsList()
}
return
}
if (newAgentId !== oldAgentId) {
// 线
chatState.currentThreadId = null
@ -1895,19 +1949,26 @@ watch(
.agent-panel-wrapper {
flex: 0 0 auto;
height: calc(100% - 56px);
align-self: flex-end;
height: 70vh;
overflow: hidden;
z-index: 20;
margin: 28px 8px;
margin-left: 0;
background: var(--gray-0);
border-radius: 12px;
border-radius: 16px;
box-shadow: 0 4px 20px var(--shadow-1);
border: 1px solid var(--gray-150);
min-width: 0;
will-change: flex-basis;
}
@media (max-height: 700px) {
.agent-panel-wrapper {
height: calc(100% - 56px);
}
}
/* Workbench transition animations */
.agent-panel-wrapper {
transition: flex-basis 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@ -1937,6 +1998,43 @@ watch(
}
}
.agent-segment-wrapper {
width: fit-content;
max-width: 100%;
margin: 0 auto 10px;
overflow-x: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
:deep(.ant-segmented) {
width: auto;
max-width: 100%;
white-space: nowrap;
background: var(--gray-50);
border: 1px solid var(--gray-150);
border-radius: 10px;
}
:deep(.ant-segmented-group) {
width: auto;
display: inline-flex;
}
:deep(.ant-segmented-item) {
flex: 0 0 auto;
min-width: 0;
}
:deep(.ant-segmented-item-label) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.example-questions {
margin-top: 16px;
text-align: center;
@ -2144,6 +2242,14 @@ watch(
}
@media (max-width: 768px) {
.agent-segment-wrapper {
margin-bottom: 8px;
:deep(.ant-segmented-item-label) {
font-size: 12px;
}
}
.chat-header {
.header__left {
.text {

View File

@ -8,7 +8,6 @@
:disabled="disabled"
:send-button-disabled="sendButtonDisabled"
:placeholder="placeholder"
:force-multi-line="hasStateContent"
:mention="mention"
@send="handleSend"
@keydown="handleKeyDown"
@ -32,6 +31,7 @@
</template>
<template #actions-left>
<div class="input-actions-left">
<slot name="actions-left-extra"></slot>
<!-- State Toggle Button -->
<div
v-if="hasStateContent"
@ -210,7 +210,6 @@ defineExpose({
font-weight: 500;
}
&.disabled {
opacity: 0.5;
cursor: not-allowed;

View File

@ -4,7 +4,7 @@
<div class="resize-handle" @mousedown="startResize"></div>
<div class="panel-header">
<div class="panel-title">
<FolderCode :size="18" class="header-icon" />
<FolderCode :size="20" class="header-icon" />
<span><strong>状态工作台</strong></span>
</div>
<div class="header-actions">
@ -275,9 +275,13 @@ const updateActiveTab = () => {
}
// files todos tab
watch([() => props.agentState?.files, () => props.agentState?.todos], () => {
updateActiveTab()
}, { deep: true })
watch(
[() => props.agentState?.files, () => props.agentState?.todos],
() => {
updateActiveTab()
},
{ deep: true }
)
const expandedKeys = ref([])
@ -560,7 +564,7 @@ const stopResize = () => {
align-items: center;
justify-content: space-between;
padding: 4px 16px;
height: 48px;
height: 56px;
background: var(--gray-25);
flex-shrink: 0;
}
@ -568,7 +572,7 @@ const stopResize = () => {
.panel-title {
display: flex;
align-items: center;
gap: 6px;
gap: 12px;
font-weight: 600;
font-size: 14px;
color: var(--gray-900);

View File

@ -1,9 +1,5 @@
<template>
<div
class="input-box"
:class="[customClasses, { 'single-line': isSingleLine }]"
@click="focusInput"
>
<div class="input-box" :class="customClasses" @click="focusInput">
<div class="top-slot">
<slot name="top"></slot>
</div>
@ -129,16 +125,7 @@
</template>
<script setup>
import {
ref,
computed,
onMounted,
nextTick,
watch,
onBeforeUnmount,
useSlots,
onUnmounted
} from 'vue'
import { ref, computed, onMounted, nextTick, watch, onBeforeUnmount, useSlots } from 'vue'
import {
SendOutlined,
ArrowUpOutlined,
@ -158,7 +145,6 @@ const closeMentionPopup = (e) => {
const inputRef = ref(null)
const optionsExpanded = ref(false)
const singleLineHeight = ref(0) // Add this
//
const debounceTimer = ref(null)
const props = defineProps({
@ -194,10 +180,6 @@ const props = defineProps({
type: Object,
default: () => ({})
},
forceMultiLine: {
type: Boolean,
default: false
},
mention: {
type: Object,
default: () => null
@ -281,7 +263,11 @@ const updateMentionItems = (query = '') => {
item.type,
mentionTypePrefixMap[item.type]
]
return searchTexts.some((text) => String(text || '').toLowerCase().includes(lowerQuery))
return searchTexts.some((text) =>
String(text || '')
.toLowerCase()
.includes(lowerQuery)
)
})
const fileItems = files.map((f) => {
@ -470,13 +456,6 @@ const handleMentionNavigation = (e) => {
}
}
// Update isSingleLine logic to respect forceMultiLine
const isSingleLineMode = ref(true)
const isSingleLine = computed(() => {
if (props.forceMultiLine) return false
return isSingleLineMode.value
})
const hasOptionsLeft = computed(() => {
const slot = slots['options-left']
if (!slot) {
@ -572,62 +551,20 @@ const handleSendOrStop = () => {
emit('send')
}
//
const singleLineWidth = ref(0)
// @
const mentionPopupVisible = ref(false)
const mentionQuery = ref('')
const mentionItems = ref({ files: [], knowledgeBases: [], mcps: [], skills: [] })
const mentionSelectedIndex = ref(0)
//
const checkLineCount = () => {
if (!inputRef.value || singleLineHeight.value === 0) {
const adjustTextareaHeight = () => {
if (!inputRef.value) {
return
}
const textarea = inputRef.value
const content = inputValue.value
//
const hasNewlines = content.includes('\n')
// 使
let contentExceedsWidth = false
if (!hasNewlines && content.trim() && singleLineWidth.value > 0) {
// 使
const measureDiv = document.createElement('div')
measureDiv.style.cssText = `
position: absolute;
visibility: hidden;
white-space: nowrap;
font-family: ${getComputedStyle(textarea).fontFamily};
font-size: ${getComputedStyle(textarea).fontSize};
line-height: ${getComputedStyle(textarea).lineHeight};
padding: 0;
border: none;
width: ${singleLineWidth.value}px;
`
measureDiv.textContent = content
document.body.appendChild(measureDiv)
//
contentExceedsWidth = measureDiv.scrollWidth > measureDiv.clientWidth
document.body.removeChild(measureDiv)
}
const shouldBeMultiLine = hasNewlines || contentExceedsWidth
isSingleLineMode.value = !shouldBeMultiLine
//
if (shouldBeMultiLine || props.forceMultiLine) {
// textarea
textarea.style.height = 'auto'
textarea.style.height = `${Math.max(textarea.scrollHeight, singleLineHeight.value)}px`
} else {
// CSS
textarea.style.height = ''
}
textarea.style.height = 'auto'
textarea.style.height = `${textarea.scrollHeight}px`
}
//
@ -639,44 +576,24 @@ const focusInput = () => {
//
watch(inputValue, () => {
nextTick(() => {
checkLineCount()
})
if (debounceTimer.value) {
clearTimeout(debounceTimer.value)
}
debounceTimer.value = setTimeout(() => {
nextTick(() => {
adjustTextareaHeight()
})
}, 100)
})
//
/* const observeTextareaResize = () => {
if (inputRef.value) {
const textarea = inputRef.value;
if (textarea) {
// ResizeObserver
const resizeObserver = new ResizeObserver(() => {
checkLineCount();
});
resizeObserver.observe(textarea);
//
onBeforeUnmount(() => {
resizeObserver.disconnect();
});
}
}
}; */
// Wait for component to mount before setting up onStartTyping
onMounted(() => {
// console.log('Component mounted');
document.addEventListener('click', closeMentionPopup)
nextTick(() => {
if (inputRef.value) {
//
singleLineHeight.value = inputRef.value.clientHeight
singleLineWidth.value = inputRef.value.clientWidth
checkLineCount()
adjustTextareaHeight()
inputRef.value.focus()
}
})
// observeTextareaResize();
})
//
@ -748,46 +665,6 @@ defineExpose({
// background: var(--gray-0);
// box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
// }
&.single-line {
padding: 0.75rem 0.75rem;
grid-template-columns: auto 1fr auto;
grid-template-rows: auto 1fr auto;
grid-template-areas:
'top top top'
'options input send'
'bottom bottom bottom';
align-items: center;
gap: 0px;
.user-input {
min-height: 24px;
height: 24px; /* Fix height for single line */
align-self: center;
white-space: nowrap;
overflow: hidden;
margin-bottom: 0rem;
}
.expand-options,
.send-button-container {
align-self: center;
}
.expand-options {
display: flex;
align-items: center;
gap: 8px;
}
.top-slot {
grid-area: top;
}
.bottom-slot {
grid-area: bottom;
}
}
}
.expand-options {

View File

@ -1,44 +1,6 @@
<template>
<div class="agent-view">
<div class="agent-view-body">
<!-- 智能体选择弹窗 -->
<a-modal
v-model:open="chatUIStore.agentModalOpen"
title="选择智能体"
:width="800"
:footer="null"
:maskClosable="true"
class="agent-modal"
>
<div class="agent-modal-content">
<div class="agents-grid">
<div
v-for="agent in agents"
:key="agent.id"
class="agent-card"
:class="{ selected: agent.id === selectedAgentId }"
@click="selectAgentFromModal(agent.id)"
>
<div class="agent-card-header">
<div class="agent-card-title">
<span class="agent-card-name">{{ agent.name || 'Unknown' }}</span>
</div>
<StarFilled v-if="agent.id === defaultAgentId" class="default-icon" />
<StarOutlined
v-else
@click.prevent="setAsDefaultAgent(agent.id)"
class="default-icon"
/>
</div>
<div class="agent-card-description">
{{ agent.description || '' }}
</div>
</div>
</div>
</div>
</a-modal>
<a-modal
v-model:open="createConfigModalOpen"
title="新建配置"
@ -55,13 +17,19 @@
<AgentChatComponent
ref="chatComponentRef"
:single-mode="false"
@open-config="toggleConf"
@open-agent-modal="openAgentModal"
@close-config-sidebar="() => (chatUIStore.isConfigSidebarOpen = false)"
>
<template #header-right>
<a-dropdown v-if="selectedAgentId" :trigger="['click']">
<div type="button" class="agent-nav-btn">
<template #input-actions-left>
<a-dropdown
v-if="selectedAgentId"
v-model:open="configDropdownOpen"
:trigger="['click']"
>
<div
type="button"
class="agent-nav-btn config-toggle-btn"
:class="{ active: configDropdownOpen }"
>
<Settings2 size="18" class="nav-btn-icon" />
<span class="text hide-text">
{{ selectedConfigSummary?.name || '配置' }}
@ -95,7 +63,7 @@
@click="openCreateConfigModal"
>
<div class="menu-item-layout">
<Plus :size="16" />
<CirclePlus :size="16" />
<span>新建配置</span>
</div>
</a-menu-item>
@ -112,6 +80,9 @@
</a-menu>
</template>
</a-dropdown>
</template>
<template #header-right>
<div
v-if="selectedAgentId"
ref="moreButtonRef"
@ -166,16 +137,10 @@
</template>
<script setup>
import { ref, watch } from 'vue'
import {
StarOutlined,
StarFilled,
MessageOutlined,
ShareAltOutlined,
EyeOutlined
} from '@ant-design/icons-vue'
import { ref } from 'vue'
import { MessageOutlined, ShareAltOutlined, EyeOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue'
import { Settings2, Ellipsis, ChevronDown, Star, Plus, SquarePen } from 'lucide-vue-next'
import { Settings2, Ellipsis, ChevronDown, Star, CirclePlus, SquarePen } from 'lucide-vue-next'
import AgentChatComponent from '@/components/AgentChatComponent.vue'
import AgentConfigSidebar from '@/components/AgentConfigSidebar.vue'
import FeedbackModalComponent from '@/components/dashboard/FeedbackModalComponent.vue'
@ -191,6 +156,7 @@ import { storeToRefs } from 'pinia'
//
const feedbackModal = ref(null)
const chatComponentRef = ref(null)
const configDropdownOpen = ref(false)
// Stores
const userStore = useUserStore()
@ -198,49 +164,8 @@ const agentStore = useAgentStore()
const chatUIStore = useChatUIStore()
// agentStore
const {
agents,
selectedAgentId,
defaultAgentId,
agentConfigs,
selectedAgentConfigId,
selectedConfigSummary
} = storeToRefs(agentStore)
//
const setAsDefaultAgent = async (agentId) => {
if (!agentId || !userStore.isAdmin) return
try {
await agentStore.setDefaultAgent(agentId)
message.success('已将当前智能体设为默认')
} catch (error) {
console.error('设置默认智能体错误:', error)
message.error(error.message || '设置默认智能体时发生错误')
}
}
// agentStore
// 使store
const selectAgent = async (agentId) => {
await agentStore.selectAgent(agentId)
}
//
const openAgentModal = () => {
chatUIStore.agentModalOpen = true
}
//
const selectAgentFromModal = async (agentId) => {
await selectAgent(agentId)
chatUIStore.agentModalOpen = false
}
const toggleConf = () => {
chatUIStore.isConfigSidebarOpen = !chatUIStore.isConfigSidebarOpen
}
const { selectedAgentId, agentConfigs, selectedAgentConfigId, selectedConfigSummary } =
storeToRefs(agentStore)
const openConfigSidebar = () => {
chatUIStore.isConfigSidebarOpen = true
@ -689,6 +614,7 @@ const handlePreview = () => {
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
@ -832,145 +758,6 @@ const handlePreview = () => {
}
}
//
.agent-selector {
border: 1px solid var(--gray-300);
border-radius: 8px;
padding: 8px 12px;
background: var(--gray-0);
transition: border-color 0.2s ease;
&:hover {
border-color: var(--main-color);
}
.selected-agent-display {
display: flex;
justify-content: space-between;
align-items: center;
.agent-name {
font-size: 14px;
color: var(--gray-900);
font-weight: 500;
}
}
}
//
.agent-modal {
:deep(.ant-modal-content) {
border-radius: 8px;
overflow: hidden;
}
:deep(.ant-modal-header) {
background: var(--gray-0);
border-bottom: 1px solid var(--gray-200);
padding: 16px 20px;
.ant-modal-title {
font-size: 16px;
font-weight: 600;
color: var(--gray-900);
}
}
:deep(.ant-modal-body) {
padding: 20px;
background: var(--gray-0);
}
.agent-modal-content {
.agents-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 12px;
max-height: 500px;
overflow-y: auto;
}
.agent-card {
border: 1px solid var(--gray-200);
border-radius: 8px;
padding: 16px;
cursor: pointer;
transition: border-color 0.2s ease;
background: var(--gray-0);
&:hover {
border-color: var(--main-color);
}
.agent-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 12px;
.agent-card-title {
flex: 1;
.agent-card-name {
font-size: 16px;
font-weight: 600;
color: var(--gray-900);
line-height: 1.4;
}
}
.default-icon {
color: var(--color-warning-500);
font-size: 16px;
flex-shrink: 0;
margin-left: 8px;
cursor: pointer;
&:hover {
color: var(--color-warning-600);
}
}
}
.agent-card-description {
font-size: 14px;
color: var(--gray-700);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
&.selected {
border-color: var(--main-color);
background: var(--main-20);
// outline: 2px solid var(--main-color);
.agent-card-header .agent-card-title .agent-card-name {
color: var(--main-color);
}
.agent-card-description {
color: var(--gray-900);
}
}
}
}
}
//
@media (max-width: 768px) {
.agent-modal {
.agent-modal-content {
.agents-grid {
grid-template-columns: 1fr;
}
}
}
}
//
.more-popup-menu {
position: fixed;
@ -1127,6 +914,36 @@ const handlePreview = () => {
width: 100%;
}
.agent-nav-btn.config-toggle-btn {
gap: 6px;
padding: 0 8px;
height: 28px;
border-radius: 8px;
font-size: 14px;
color: var(--gray-600);
transition: all 0.2s ease;
user-select: none;
.nav-btn-icon {
height: 16px;
}
.text {
line-height: 1;
}
&:hover {
color: var(--main-color);
background: var(--gray-100);
}
&.active {
color: var(--main-color);
background: var(--main-50);
font-weight: 500;
}
}
@media (max-width: 768px) {
.hide-text {
display: none;