diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py
index 750defe7..dff71a3a 100644
--- a/server/routers/chat_router.py
+++ b/server/routers/chat_router.py
@@ -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),
diff --git a/src/services/conversation_service.py b/src/services/conversation_service.py
index 6bd6aa55..11536571 100644
--- a/src/services/conversation_service.py
+++ b/src/services/conversation_service.py
@@ -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),
diff --git a/web/src/apis/agent_api.js b/web/src/apis/agent_api.js
index 6b1c2ce7..f1dba43e 100644
--- a/web/src/apis/agent_api.js
+++ b/web/src/apis/agent_api.js
@@ -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)
},
diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue
index 2578aaa7..32be7e80 100644
--- a/web/src/components/AgentChatComponent.vue
+++ b/web/src/components/AgentChatComponent.vue
@@ -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 @@
新对话
-
-
-
-
- {{ currentAgentName || '选择智能体' }}
-
-
-
+
+
+ >
+
+
+
+
-
-
请注意辨别内容的可靠性
+
+
当前智能体:{{ currentThreadAgentName }};请注意辨别内容的可靠性
@@ -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 {
diff --git a/web/src/components/AgentInputArea.vue b/web/src/components/AgentInputArea.vue
index c564c590..fbbe22d1 100644
--- a/web/src/components/AgentInputArea.vue
+++ b/web/src/components/AgentInputArea.vue
@@ -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 @@