feat: 智能体对话界面优化
- 消息列表修改为所有的对话,不区分智能体 - 智能体会在新建对话的时候切换 - 配置选择从右上角移动到输入框 - 其他细节优化
This commit is contained in:
parent
06cc8cbf7a
commit
1764ffedb0
@ -746,7 +746,7 @@ async def create_thread(
|
|||||||
|
|
||||||
@chat.get("/threads", response_model=list[ThreadResponse])
|
@chat.get("/threads", response_model=list[ThreadResponse])
|
||||||
async def list_threads(
|
async def list_threads(
|
||||||
agent_id: str,
|
agent_id: str | None = Query(None),
|
||||||
limit: int = Query(100, ge=1, le=500),
|
limit: int = Query(100, ge=1, le=500),
|
||||||
offset: int = Query(0, ge=0),
|
offset: int = Query(0, ge=0),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
|
|||||||
@ -153,15 +153,12 @@ async def create_thread_view(
|
|||||||
|
|
||||||
async def list_threads_view(
|
async def list_threads_view(
|
||||||
*,
|
*,
|
||||||
agent_id: str,
|
agent_id: str | None,
|
||||||
db: AsyncSession,
|
db: AsyncSession,
|
||||||
current_user_id: str,
|
current_user_id: str,
|
||||||
limit: int | None = None,
|
limit: int | None = None,
|
||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
if not agent_id:
|
|
||||||
raise HTTPException(status_code=422, detail="agent_id 不能为空")
|
|
||||||
|
|
||||||
conv_repo = ConversationRepository(db)
|
conv_repo = ConversationRepository(db)
|
||||||
conversations = await conv_repo.list_conversations(
|
conversations = await conv_repo.list_conversations(
|
||||||
user_id=str(current_user_id),
|
user_id=str(current_user_id),
|
||||||
|
|||||||
@ -285,13 +285,20 @@ export const multimodalApi = {
|
|||||||
export const threadApi = {
|
export const threadApi = {
|
||||||
/**
|
/**
|
||||||
* 获取对话线程列表
|
* 获取对话线程列表
|
||||||
* @param {string} agentId - 智能体ID
|
* @param {string | null | undefined} agentId - 智能体ID,可选;不传时返回全部智能体对话
|
||||||
* @param {number} limit - 返回数量限制,默认100
|
* @param {number} limit - 返回数量限制,默认100
|
||||||
* @param {number} offset - 偏移量,默认0
|
* @param {number} offset - 偏移量,默认0
|
||||||
* @returns {Promise} - 对话线程列表
|
* @returns {Promise} - 对话线程列表
|
||||||
*/
|
*/
|
||||||
getThreads: (agentId, limit = 100, offset = 0) => {
|
getThreads: (agentId = null, limit = 100, offset = 0) => {
|
||||||
const url = `/api/chat/threads?agent_id=${agentId}&limit=${limit}&offset=${offset}`
|
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)
|
return apiGet(url)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,6 @@
|
|||||||
@rename-chat="renameChat"
|
@rename-chat="renameChat"
|
||||||
@toggle-pin="togglePinChat"
|
@toggle-pin="togglePinChat"
|
||||||
@toggle-sidebar="toggleSidebar"
|
@toggle-sidebar="toggleSidebar"
|
||||||
@open-agent-modal="openAgentModal"
|
|
||||||
@load-more-chats="loadMoreChats"
|
@load-more-chats="loadMoreChats"
|
||||||
:class="{
|
:class="{
|
||||||
'sidebar-open': chatUIStore.isSidebarOpen,
|
'sidebar-open': chatUIStore.isSidebarOpen,
|
||||||
@ -51,14 +50,6 @@
|
|||||||
<MessageCirclePlus v-else class="nav-btn-icon" size="16" />
|
<MessageCirclePlus v-else class="nav-btn-icon" size="16" />
|
||||||
<span class="text">新对话</span>
|
<span class="text">新对话</span>
|
||||||
</div>
|
</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>
|
||||||
<div class="header__right">
|
<div class="header__right">
|
||||||
<!-- AgentState 显示按钮已移动到输入框底部 -->
|
<!-- AgentState 显示按钮已移动到输入框底部 -->
|
||||||
@ -131,6 +122,14 @@
|
|||||||
<h1>👋 您好,我是{{ currentAgentName }}!</h1>
|
<h1>👋 您好,我是{{ currentAgentName }}!</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showStartAgentSegment" class="agent-segment-wrapper">
|
||||||
|
<a-segmented
|
||||||
|
:value="currentAgentId"
|
||||||
|
:options="agentSegmentOptions"
|
||||||
|
@change="handleStartAgentChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<AgentInputArea
|
<AgentInputArea
|
||||||
ref="messageInputRef"
|
ref="messageInputRef"
|
||||||
v-model="userInput"
|
v-model="userInput"
|
||||||
@ -148,7 +147,11 @@
|
|||||||
@send="handleSendOrStop"
|
@send="handleSendOrStop"
|
||||||
@attachment-changed="handleAgentStateRefresh"
|
@attachment-changed="handleAgentStateRefresh"
|
||||||
@toggle-panel="toggleAgentPanel"
|
@toggle-panel="toggleAgentPanel"
|
||||||
/>
|
>
|
||||||
|
<template #actions-left-extra>
|
||||||
|
<slot name="input-actions-left"></slot>
|
||||||
|
</template>
|
||||||
|
</AgentInputArea>
|
||||||
|
|
||||||
<!-- 示例问题 -->
|
<!-- 示例问题 -->
|
||||||
<div
|
<div
|
||||||
@ -167,8 +170,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom-actions" v-else>
|
<div class="bottom-actions" v-if="conversations.length > 0">
|
||||||
<p class="note">请注意辨别内容的可靠性</p>
|
<p class="note">当前智能体:{{ currentThreadAgentName }};请注意辨别内容的可靠性</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -210,7 +213,7 @@ import AgentInputArea from '@/components/AgentInputArea.vue'
|
|||||||
import AgentMessageComponent from '@/components/AgentMessageComponent.vue'
|
import AgentMessageComponent from '@/components/AgentMessageComponent.vue'
|
||||||
import ChatSidebarComponent from '@/components/ChatSidebarComponent.vue'
|
import ChatSidebarComponent from '@/components/ChatSidebarComponent.vue'
|
||||||
import RefsComponent from '@/components/RefsComponent.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 { handleChatError, handleValidationError } from '@/utils/errorHandler'
|
||||||
import { ScrollController } from '@/utils/scrollController'
|
import { ScrollController } from '@/utils/scrollController'
|
||||||
import { AgentValidator } from '@/utils/agentValidator'
|
import { AgentValidator } from '@/utils/agentValidator'
|
||||||
@ -229,7 +232,6 @@ const props = defineProps({
|
|||||||
agentId: { type: String, default: '' },
|
agentId: { type: String, default: '' },
|
||||||
singleMode: { type: Boolean, default: true }
|
singleMode: { type: Boolean, default: true }
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['open-config', 'open-agent-modal'])
|
|
||||||
|
|
||||||
// ==================== STORE MANAGEMENT ====================
|
// ==================== STORE MANAGEMENT ====================
|
||||||
const agentStore = useAgentStore()
|
const agentStore = useAgentStore()
|
||||||
@ -336,6 +338,17 @@ const currentThread = computed(() => {
|
|||||||
return threads.value.find((thread) => thread.id === currentChatId.value) || null
|
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(() => {
|
const supportsFileUpload = computed(() => {
|
||||||
if (!currentAgent.value) return false
|
if (!currentAgent.value) return false
|
||||||
@ -510,6 +523,27 @@ const conversations = computed(() => {
|
|||||||
return historyConvs
|
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 isLoadingMessages = computed(() => chatUIStore.isLoadingMessages)
|
||||||
const isStreaming = computed(() => {
|
const isStreaming = computed(() => {
|
||||||
const threadState = currentThreadState.value
|
const threadState = currentThreadState.value
|
||||||
@ -610,8 +644,8 @@ const resetOnGoingConv = (threadId = null) => {
|
|||||||
// ==================== 线程管理方法 ====================
|
// ==================== 线程管理方法 ====================
|
||||||
// 获取当前智能体的线程列表
|
// 获取当前智能体的线程列表
|
||||||
const fetchThreads = async (agentId = null) => {
|
const fetchThreads = async (agentId = null) => {
|
||||||
const targetAgentId = agentId || currentAgentId.value
|
const targetAgentId = props.singleMode ? agentId || currentAgentId.value : agentId
|
||||||
if (!targetAgentId) return
|
if (props.singleMode && !targetAgentId) return
|
||||||
|
|
||||||
chatUIStore.isLoadingThreads = true
|
chatUIStore.isLoadingThreads = true
|
||||||
try {
|
try {
|
||||||
@ -632,8 +666,8 @@ const fetchThreads = async (agentId = null) => {
|
|||||||
const loadMoreChats = async () => {
|
const loadMoreChats = async () => {
|
||||||
if (isLoadingMoreChats.value || !hasMoreChats.value) return
|
if (isLoadingMoreChats.value || !hasMoreChats.value) return
|
||||||
|
|
||||||
const targetAgentId = currentAgentId.value
|
const targetAgentId = props.singleMode ? currentAgentId.value : null
|
||||||
if (!targetAgentId) return
|
if (props.singleMode && !targetAgentId) return
|
||||||
|
|
||||||
isLoadingMoreChats.value = true
|
isLoadingMoreChats.value = true
|
||||||
try {
|
try {
|
||||||
@ -1350,17 +1384,19 @@ const createNewChat = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectChat = async (chatId) => {
|
const selectChat = async (chatId) => {
|
||||||
if (
|
const targetChat = threads.value.find((chat) => chat.id === chatId) || null
|
||||||
!AgentValidator.validateAgentIdWithError(
|
const targetAgentId = targetChat?.agent_id || currentAgentId.value
|
||||||
currentAgentId.value,
|
const previousThreadId = chatState.currentThreadId
|
||||||
'选择对话',
|
|
||||||
handleValidationError
|
if (!targetAgentId) {
|
||||||
)
|
handleValidationError('选择对话失败:缺少智能体信息')
|
||||||
)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AgentValidator.validateAgentIdWithError(targetAgentId, '选择对话', handleValidationError))
|
||||||
return
|
return
|
||||||
|
|
||||||
// 中断之前线程的流式输出(如果存在)
|
// 中断之前线程的流式输出(如果存在)
|
||||||
const previousThreadId = chatState.currentThreadId
|
|
||||||
if (previousThreadId && previousThreadId !== chatId) {
|
if (previousThreadId && previousThreadId !== chatId) {
|
||||||
const previousThreadState = getThreadState(previousThreadId)
|
const previousThreadState = getThreadState(previousThreadId)
|
||||||
if (previousThreadState?.isStreaming && previousThreadState.streamAbortController) {
|
if (previousThreadState?.isStreaming && previousThreadState.streamAbortController) {
|
||||||
@ -1372,10 +1408,22 @@ const selectChat = async (chatId) => {
|
|||||||
stopRunStreamSubscription(previousThreadId)
|
stopRunStreamSubscription(previousThreadId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 先更新当前线程,确保底部智能体名称与选中项即时同步
|
||||||
chatState.currentThreadId = chatId
|
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
|
chatUIStore.isLoadingMessages = true
|
||||||
try {
|
try {
|
||||||
await fetchThreadMessages({ agentId: currentAgentId.value, threadId: chatId })
|
await fetchThreadMessages({ agentId: targetAgentId, threadId: chatId })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleChatError(error, 'load')
|
handleChatError(error, 'load')
|
||||||
} finally {
|
} finally {
|
||||||
@ -1384,7 +1432,7 @@ const selectChat = async (chatId) => {
|
|||||||
|
|
||||||
await nextTick()
|
await nextTick()
|
||||||
scrollController.scrollToBottomStaticForce()
|
scrollController.scrollToBottomStaticForce()
|
||||||
await fetchAgentState(currentAgentId.value, chatId)
|
await fetchAgentState(targetAgentId, chatId)
|
||||||
await resumeActiveRunForThread(chatId)
|
await resumeActiveRunForThread(chatId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1657,7 +1705,6 @@ defineExpose({
|
|||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
chatUIStore.toggleSidebar()
|
chatUIStore.toggleSidebar()
|
||||||
}
|
}
|
||||||
const openAgentModal = () => emit('open-agent-modal')
|
|
||||||
|
|
||||||
const handleAgentStateRefresh = async (threadId = null) => {
|
const handleAgentStateRefresh = async (threadId = null) => {
|
||||||
if (!currentAgentId.value) return
|
if (!currentAgentId.value) return
|
||||||
@ -1744,8 +1791,8 @@ const getConversationSources = (conv) => {
|
|||||||
|
|
||||||
// ==================== LIFECYCLE & WATCHERS ====================
|
// ==================== LIFECYCLE & WATCHERS ====================
|
||||||
const loadChatsList = async () => {
|
const loadChatsList = async () => {
|
||||||
const agentId = currentAgentId.value
|
const agentId = props.singleMode ? currentAgentId.value : null
|
||||||
if (!agentId) {
|
if (props.singleMode && !agentId) {
|
||||||
console.warn('No agent selected, cannot load chats list')
|
console.warn('No agent selected, cannot load chats list')
|
||||||
threads.value = []
|
threads.value = []
|
||||||
chatState.currentThreadId = null
|
chatState.currentThreadId = null
|
||||||
@ -1754,7 +1801,7 @@ const loadChatsList = async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await fetchThreads(agentId)
|
await fetchThreads(agentId)
|
||||||
if (currentAgentId.value !== agentId) return
|
if (props.singleMode && currentAgentId.value !== agentId) return
|
||||||
|
|
||||||
// 如果当前线程不在线程列表中,清空当前线程
|
// 如果当前线程不在线程列表中,清空当前线程
|
||||||
if (
|
if (
|
||||||
@ -1791,6 +1838,13 @@ onMounted(async () => {
|
|||||||
watch(
|
watch(
|
||||||
currentAgentId,
|
currentAgentId,
|
||||||
async (newAgentId, oldAgentId) => {
|
async (newAgentId, oldAgentId) => {
|
||||||
|
if (!props.singleMode) {
|
||||||
|
if (oldAgentId === undefined) {
|
||||||
|
await loadChatsList()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (newAgentId !== oldAgentId) {
|
if (newAgentId !== oldAgentId) {
|
||||||
// 清理当前线程状态
|
// 清理当前线程状态
|
||||||
chatState.currentThreadId = null
|
chatState.currentThreadId = null
|
||||||
@ -1895,19 +1949,26 @@ watch(
|
|||||||
|
|
||||||
.agent-panel-wrapper {
|
.agent-panel-wrapper {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
height: calc(100% - 56px);
|
align-self: flex-end;
|
||||||
|
height: 70vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
margin: 28px 8px;
|
margin: 28px 8px;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
background: var(--gray-0);
|
background: var(--gray-0);
|
||||||
border-radius: 12px;
|
border-radius: 16px;
|
||||||
box-shadow: 0 4px 20px var(--shadow-1);
|
box-shadow: 0 4px 20px var(--shadow-1);
|
||||||
border: 1px solid var(--gray-150);
|
border: 1px solid var(--gray-150);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
will-change: flex-basis;
|
will-change: flex-basis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-height: 700px) {
|
||||||
|
.agent-panel-wrapper {
|
||||||
|
height: calc(100% - 56px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Workbench transition animations */
|
/* Workbench transition animations */
|
||||||
.agent-panel-wrapper {
|
.agent-panel-wrapper {
|
||||||
transition: flex-basis 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
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 {
|
.example-questions {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -2144,6 +2242,14 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
.agent-segment-wrapper {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
:deep(.ant-segmented-item-label) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.chat-header {
|
.chat-header {
|
||||||
.header__left {
|
.header__left {
|
||||||
.text {
|
.text {
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:send-button-disabled="sendButtonDisabled"
|
:send-button-disabled="sendButtonDisabled"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:force-multi-line="hasStateContent"
|
|
||||||
:mention="mention"
|
:mention="mention"
|
||||||
@send="handleSend"
|
@send="handleSend"
|
||||||
@keydown="handleKeyDown"
|
@keydown="handleKeyDown"
|
||||||
@ -32,6 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #actions-left>
|
<template #actions-left>
|
||||||
<div class="input-actions-left">
|
<div class="input-actions-left">
|
||||||
|
<slot name="actions-left-extra"></slot>
|
||||||
<!-- State Toggle Button -->
|
<!-- State Toggle Button -->
|
||||||
<div
|
<div
|
||||||
v-if="hasStateContent"
|
v-if="hasStateContent"
|
||||||
@ -210,7 +210,6 @@ defineExpose({
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<div class="resize-handle" @mousedown="startResize"></div>
|
<div class="resize-handle" @mousedown="startResize"></div>
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<div class="panel-title">
|
<div class="panel-title">
|
||||||
<FolderCode :size="18" class="header-icon" />
|
<FolderCode :size="20" class="header-icon" />
|
||||||
<span><strong>状态工作台</strong></span>
|
<span><strong>状态工作台</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
@ -275,9 +275,13 @@ const updateActiveTab = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 监听 files 和 todos 变化,自动切换 tab
|
// 监听 files 和 todos 变化,自动切换 tab
|
||||||
watch([() => props.agentState?.files, () => props.agentState?.todos], () => {
|
watch(
|
||||||
updateActiveTab()
|
[() => props.agentState?.files, () => props.agentState?.todos],
|
||||||
}, { deep: true })
|
() => {
|
||||||
|
updateActiveTab()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
const expandedKeys = ref([])
|
const expandedKeys = ref([])
|
||||||
|
|
||||||
@ -560,7 +564,7 @@ const stopResize = () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
height: 48px;
|
height: 56px;
|
||||||
background: var(--gray-25);
|
background: var(--gray-25);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@ -568,7 +572,7 @@ const stopResize = () => {
|
|||||||
.panel-title {
|
.panel-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--gray-900);
|
color: var(--gray-900);
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="input-box" :class="customClasses" @click="focusInput">
|
||||||
class="input-box"
|
|
||||||
:class="[customClasses, { 'single-line': isSingleLine }]"
|
|
||||||
@click="focusInput"
|
|
||||||
>
|
|
||||||
<div class="top-slot">
|
<div class="top-slot">
|
||||||
<slot name="top"></slot>
|
<slot name="top"></slot>
|
||||||
</div>
|
</div>
|
||||||
@ -129,16 +125,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import { ref, computed, onMounted, nextTick, watch, onBeforeUnmount, useSlots } from 'vue'
|
||||||
ref,
|
|
||||||
computed,
|
|
||||||
onMounted,
|
|
||||||
nextTick,
|
|
||||||
watch,
|
|
||||||
onBeforeUnmount,
|
|
||||||
useSlots,
|
|
||||||
onUnmounted
|
|
||||||
} from 'vue'
|
|
||||||
import {
|
import {
|
||||||
SendOutlined,
|
SendOutlined,
|
||||||
ArrowUpOutlined,
|
ArrowUpOutlined,
|
||||||
@ -158,7 +145,6 @@ const closeMentionPopup = (e) => {
|
|||||||
|
|
||||||
const inputRef = ref(null)
|
const inputRef = ref(null)
|
||||||
const optionsExpanded = ref(false)
|
const optionsExpanded = ref(false)
|
||||||
const singleLineHeight = ref(0) // Add this
|
|
||||||
// 用于防抖的定时器
|
// 用于防抖的定时器
|
||||||
const debounceTimer = ref(null)
|
const debounceTimer = ref(null)
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -194,10 +180,6 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
},
|
},
|
||||||
forceMultiLine: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
mention: {
|
mention: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => null
|
default: () => null
|
||||||
@ -281,7 +263,11 @@ const updateMentionItems = (query = '') => {
|
|||||||
item.type,
|
item.type,
|
||||||
mentionTypePrefixMap[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) => {
|
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 hasOptionsLeft = computed(() => {
|
||||||
const slot = slots['options-left']
|
const slot = slots['options-left']
|
||||||
if (!slot) {
|
if (!slot) {
|
||||||
@ -572,62 +551,20 @@ const handleSendOrStop = () => {
|
|||||||
emit('send')
|
emit('send')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用于存储固定的单行宽度基准
|
|
||||||
const singleLineWidth = ref(0)
|
|
||||||
|
|
||||||
// @ 提及功能状态
|
// @ 提及功能状态
|
||||||
const mentionPopupVisible = ref(false)
|
const mentionPopupVisible = ref(false)
|
||||||
const mentionQuery = ref('')
|
const mentionQuery = ref('')
|
||||||
const mentionItems = ref({ files: [], knowledgeBases: [], mcps: [], skills: [] })
|
const mentionItems = ref({ files: [], knowledgeBases: [], mcps: [], skills: [] })
|
||||||
const mentionSelectedIndex = ref(0)
|
const mentionSelectedIndex = ref(0)
|
||||||
|
|
||||||
// 检查行数
|
const adjustTextareaHeight = () => {
|
||||||
const checkLineCount = () => {
|
if (!inputRef.value) {
|
||||||
if (!inputRef.value || singleLineHeight.value === 0) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const textarea = inputRef.value
|
const textarea = inputRef.value
|
||||||
const content = inputValue.value
|
textarea.style.height = 'auto'
|
||||||
|
textarea.style.height = `${textarea.scrollHeight}px`
|
||||||
// 主要判断依据:内容是否包含换行符
|
|
||||||
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 = ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 聚焦输入框
|
// 聚焦输入框
|
||||||
@ -639,44 +576,24 @@ const focusInput = () => {
|
|||||||
|
|
||||||
// 监听输入值变化
|
// 监听输入值变化
|
||||||
watch(inputValue, () => {
|
watch(inputValue, () => {
|
||||||
nextTick(() => {
|
if (debounceTimer.value) {
|
||||||
checkLineCount()
|
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(() => {
|
onMounted(() => {
|
||||||
// console.log('Component mounted');
|
|
||||||
document.addEventListener('click', closeMentionPopup)
|
document.addEventListener('click', closeMentionPopup)
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (inputRef.value) {
|
if (inputRef.value) {
|
||||||
// 记录单行模式下的高度和宽度基准
|
adjustTextareaHeight()
|
||||||
singleLineHeight.value = inputRef.value.clientHeight
|
|
||||||
singleLineWidth.value = inputRef.value.clientWidth
|
|
||||||
checkLineCount()
|
|
||||||
inputRef.value.focus()
|
inputRef.value.focus()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// observeTextareaResize();
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 组件卸载时清除定时器和事件监听器
|
// 组件卸载时清除定时器和事件监听器
|
||||||
@ -748,46 +665,6 @@ defineExpose({
|
|||||||
// background: var(--gray-0);
|
// background: var(--gray-0);
|
||||||
// box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
// 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 {
|
.expand-options {
|
||||||
|
|||||||
@ -1,44 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="agent-view">
|
<div class="agent-view">
|
||||||
<div class="agent-view-body">
|
<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
|
<a-modal
|
||||||
v-model:open="createConfigModalOpen"
|
v-model:open="createConfigModalOpen"
|
||||||
title="新建配置"
|
title="新建配置"
|
||||||
@ -55,13 +17,19 @@
|
|||||||
<AgentChatComponent
|
<AgentChatComponent
|
||||||
ref="chatComponentRef"
|
ref="chatComponentRef"
|
||||||
:single-mode="false"
|
:single-mode="false"
|
||||||
@open-config="toggleConf"
|
|
||||||
@open-agent-modal="openAgentModal"
|
|
||||||
@close-config-sidebar="() => (chatUIStore.isConfigSidebarOpen = false)"
|
@close-config-sidebar="() => (chatUIStore.isConfigSidebarOpen = false)"
|
||||||
>
|
>
|
||||||
<template #header-right>
|
<template #input-actions-left>
|
||||||
<a-dropdown v-if="selectedAgentId" :trigger="['click']">
|
<a-dropdown
|
||||||
<div type="button" class="agent-nav-btn">
|
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" />
|
<Settings2 size="18" class="nav-btn-icon" />
|
||||||
<span class="text hide-text">
|
<span class="text hide-text">
|
||||||
{{ selectedConfigSummary?.name || '配置' }}
|
{{ selectedConfigSummary?.name || '配置' }}
|
||||||
@ -95,7 +63,7 @@
|
|||||||
@click="openCreateConfigModal"
|
@click="openCreateConfigModal"
|
||||||
>
|
>
|
||||||
<div class="menu-item-layout">
|
<div class="menu-item-layout">
|
||||||
<Plus :size="16" />
|
<CirclePlus :size="16" />
|
||||||
<span>新建配置</span>
|
<span>新建配置</span>
|
||||||
</div>
|
</div>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
@ -112,6 +80,9 @@
|
|||||||
</a-menu>
|
</a-menu>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</a-dropdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #header-right>
|
||||||
<div
|
<div
|
||||||
v-if="selectedAgentId"
|
v-if="selectedAgentId"
|
||||||
ref="moreButtonRef"
|
ref="moreButtonRef"
|
||||||
@ -166,16 +137,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue'
|
import { ref } from 'vue'
|
||||||
import {
|
import { MessageOutlined, ShareAltOutlined, EyeOutlined } from '@ant-design/icons-vue'
|
||||||
StarOutlined,
|
|
||||||
StarFilled,
|
|
||||||
MessageOutlined,
|
|
||||||
ShareAltOutlined,
|
|
||||||
EyeOutlined
|
|
||||||
} from '@ant-design/icons-vue'
|
|
||||||
import { message } from 'ant-design-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 AgentChatComponent from '@/components/AgentChatComponent.vue'
|
||||||
import AgentConfigSidebar from '@/components/AgentConfigSidebar.vue'
|
import AgentConfigSidebar from '@/components/AgentConfigSidebar.vue'
|
||||||
import FeedbackModalComponent from '@/components/dashboard/FeedbackModalComponent.vue'
|
import FeedbackModalComponent from '@/components/dashboard/FeedbackModalComponent.vue'
|
||||||
@ -191,6 +156,7 @@ import { storeToRefs } from 'pinia'
|
|||||||
// 组件引用
|
// 组件引用
|
||||||
const feedbackModal = ref(null)
|
const feedbackModal = ref(null)
|
||||||
const chatComponentRef = ref(null)
|
const chatComponentRef = ref(null)
|
||||||
|
const configDropdownOpen = ref(false)
|
||||||
|
|
||||||
// Stores
|
// Stores
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@ -198,49 +164,8 @@ const agentStore = useAgentStore()
|
|||||||
const chatUIStore = useChatUIStore()
|
const chatUIStore = useChatUIStore()
|
||||||
|
|
||||||
// 从 agentStore 中获取响应式状态
|
// 从 agentStore 中获取响应式状态
|
||||||
const {
|
const { selectedAgentId, agentConfigs, selectedAgentConfigId, selectedConfigSummary } =
|
||||||
agents,
|
storeToRefs(agentStore)
|
||||||
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 openConfigSidebar = () => {
|
const openConfigSidebar = () => {
|
||||||
chatUIStore.isConfigSidebarOpen = true
|
chatUIStore.isConfigSidebarOpen = true
|
||||||
@ -689,6 +614,7 @@ const handlePreview = () => {
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
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 {
|
.more-popup-menu {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -1127,6 +914,36 @@ const handlePreview = () => {
|
|||||||
width: 100%;
|
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) {
|
@media (max-width: 768px) {
|
||||||
.hide-text {
|
.hide-text {
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user