feat(agent): 更新代理面板和输入区域,优化状态管理和界面交互

This commit is contained in:
Wenjie Zhang 2026-05-12 15:26:48 +08:00
parent 369a749c78
commit ef963617fc
4 changed files with 28 additions and 93 deletions

View File

@ -12,8 +12,12 @@
</div> </div>
</div> </div>
<div class="header__right"> <div class="header__right">
<!-- AgentState 显示按钮已移动到输入框底部 --> <slot
<slot name="header-right"></slot> name="header-right"
:is-agent-panel-open="isAgentPanelOpen"
:has-active-thread="!!currentChatId"
:toggle-agent-panel="toggleAgentPanel"
></slot>
</div> </div>
</div> </div>
@ -150,12 +154,10 @@
:send-button-disabled="isSendButtonDisabled" :send-button-disabled="isSendButtonDisabled"
:mention="mentionConfig" :mention="mentionConfig"
:supports-file-upload="supportsFileUpload" :supports-file-upload="supportsFileUpload"
:is-panel-open="isAgentPanelOpen"
:has-active-thread="!!currentChatId" :has-active-thread="!!currentChatId"
:todos="currentTodos" :todos="currentTodos"
@send="handleSendOrStop" @send="handleSendOrStop"
@upload-attachment="handleAttachmentUpload" @upload-attachment="handleAttachmentUpload"
@toggle-panel="toggleAgentPanel"
> >
<template #actions-left-extra> <template #actions-left-extra>
<slot name="input-actions-left"></slot> <slot name="input-actions-left"></slot>
@ -193,8 +195,7 @@
ref="panelWrapperRef" ref="panelWrapperRef"
:class="{ :class="{
'is-visible': isAgentPanelOpen, 'is-visible': isAgentPanelOpen,
'no-transition': isResizing, 'no-transition': isResizing
'is-expanded': isAgentPanelExpanded
}" }"
:style="{ :style="{
flexBasis: isAgentPanelOpen ? `${panelRatio * 100}%` : '0px' flexBasis: isAgentPanelOpen ? `${panelRatio * 100}%` : '0px'
@ -208,10 +209,7 @@
:agent-id="currentThread?.agent_id || currentAgentId" :agent-id="currentThread?.agent_id || currentAgentId"
:agent-config-id="selectedAgentConfigId" :agent-config-id="selectedAgentConfigId"
:panel-ratio="panelRatio" :panel-ratio="panelRatio"
:is-expanded="isAgentPanelExpanded"
@refresh="handleAgentStateRefresh" @refresh="handleAgentStateRefresh"
@close="toggleAgentPanel"
@toggle-expand="togglePanelExpanded"
@resize="handlePanelResize" @resize="handlePanelResize"
@resizing="handleResizingChange" @resizing="handleResizingChange"
/> />
@ -360,7 +358,6 @@ const localUIState = reactive({
// Agent Panel State // Agent Panel State
const isAgentPanelOpen = ref(false) const isAgentPanelOpen = ref(false)
const isAgentPanelExpanded = ref(false)
const isResizing = ref(false) const isResizing = ref(false)
const panelRatio = ref(0.3) // (0-1) const panelRatio = ref(0.3) // (0-1)
const panelWrapperRef = ref(null) // DOM const panelWrapperRef = ref(null) // DOM
@ -1583,20 +1580,11 @@ const toggleAgentPanel = async () => {
const nextOpen = !isAgentPanelOpen.value const nextOpen = !isAgentPanelOpen.value
isAgentPanelOpen.value = nextOpen isAgentPanelOpen.value = nextOpen
if (!nextOpen) {
isAgentPanelExpanded.value = false
}
if (nextOpen) { if (nextOpen) {
await handleAgentStateRefresh() await handleAgentStateRefresh()
} }
} }
const togglePanelExpanded = () => {
if (!isAgentPanelOpen.value) return
isAgentPanelExpanded.value = !isAgentPanelExpanded.value
}
// 使 // 使
// (deltaX > 0)(deltaX < 0) // (deltaX > 0)(deltaX < 0)
const handlePanelResize = (clientX) => { const handlePanelResize = (clientX) => {
@ -2006,11 +1994,11 @@ watch(currentChatId, (threadId, oldThreadId) => {
.agent-panel-wrapper { .agent-panel-wrapper {
flex: 0 0 auto; flex: 0 0 auto;
align-self: flex-end; align-self: stretch;
height: 70vh; height: auto;
overflow: hidden; overflow: hidden;
z-index: 20; z-index: 20;
margin: 28px 8px; margin: 0 8px 8px;
margin-left: 0; margin-left: 0;
background: var(--gray-0); background: var(--gray-0);
border-radius: 16px; border-radius: 16px;
@ -2019,22 +2007,6 @@ watch(currentChatId, (threadId, oldThreadId) => {
will-change: flex-basis; will-change: flex-basis;
} }
.agent-panel-wrapper.is-expanded {
align-self: stretch;
height: calc(100% - 16px);
margin-top: 8px;
margin-bottom: 8px;
}
@media (max-height: 700px) {
.agent-panel-wrapper {
height: calc(100% - 56px);
}
.agent-panel-wrapper.is-expanded {
height: calc(100% - 16px);
}
}
/* Workbench transition animations */ /* Workbench transition animations */
.agent-panel-wrapper { .agent-panel-wrapper {

View File

@ -87,16 +87,6 @@
</template> </template>
<template #actions-right> <template #actions-right>
<div class="input-actions-right"> <div class="input-actions-right">
<button
v-if="hasActiveThread"
class="input-action-btn"
:class="{ active: isPanelOpen }"
@click.stop="$emit('toggle-panel')"
title="查看文件"
>
<FolderKanban :size="18" />
<span>文件</span>
</button>
<slot name="actions-left-extra"></slot> <slot name="actions-left-extra"></slot>
</div> </div>
</template> </template>
@ -108,7 +98,7 @@ import { computed, ref, watch } from 'vue'
import MessageInputComponent from '@/components/MessageInputComponent.vue' import MessageInputComponent from '@/components/MessageInputComponent.vue'
import ImagePreviewComponent from '@/components/ImagePreviewComponent.vue' import ImagePreviewComponent from '@/components/ImagePreviewComponent.vue'
import AttachmentOptionsComponent from '@/components/AttachmentOptionsComponent.vue' import AttachmentOptionsComponent from '@/components/AttachmentOptionsComponent.vue'
import { FolderKanban, SquareCheck } from 'lucide-vue-next' import { SquareCheck } from 'lucide-vue-next'
import { import {
CheckCircleOutlined, CheckCircleOutlined,
ClockCircleOutlined, ClockCircleOutlined,
@ -124,7 +114,6 @@ const props = defineProps({
sendButtonDisabled: { type: Boolean, default: false }, sendButtonDisabled: { type: Boolean, default: false },
mention: { type: Object, default: () => null }, mention: { type: Object, default: () => null },
supportsFileUpload: { type: Boolean, default: false }, supportsFileUpload: { type: Boolean, default: false },
isPanelOpen: { type: Boolean, default: false },
hasActiveThread: { type: Boolean, default: true }, hasActiveThread: { type: Boolean, default: true },
todos: { todos: {
type: Array, type: Array,
@ -132,13 +121,7 @@ const props = defineProps({
} }
}) })
const emit = defineEmits([ const emit = defineEmits(['update:modelValue', 'send', 'keydown', 'upload-attachment'])
'update:modelValue',
'send',
'keydown',
'upload-attachment',
'toggle-panel'
])
const inputRef = ref(null) const inputRef = ref(null)
const currentImage = ref(null) const currentImage = ref(null)

View File

@ -11,16 +11,6 @@
<button class="header-action-btn" title="刷新" @click="emitRefresh"> <button class="header-action-btn" title="刷新" @click="emitRefresh">
<RefreshCw :size="15" /> <RefreshCw :size="15" />
</button> </button>
<button
class="header-action-btn"
:title="isExpanded ? '恢复高度' : '向上展开'"
@click="emit('toggle-expand')"
>
<component :is="isExpanded ? ChevronsDownUp : ChevronsUpDown" :size="15" />
</button>
<button class="close-btn" @click="$emit('close')">
<X :size="18" />
</button>
</div> </div>
</div> </div>
</div> </div>
@ -124,7 +114,7 @@
<script setup> <script setup>
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { ChevronsDownUp, ChevronsUpDown, Download, RefreshCw, Trash2, X } from 'lucide-vue-next' import { Download, RefreshCw, Trash2 } from 'lucide-vue-next'
import { Modal, message } from 'ant-design-vue' import { Modal, message } from 'ant-design-vue'
import FileTreeComponent from '@/components/FileTreeComponent.vue' import FileTreeComponent from '@/components/FileTreeComponent.vue'
import AgentFilePreview from '@/components/AgentFilePreview.vue' import AgentFilePreview from '@/components/AgentFilePreview.vue'
@ -159,14 +149,10 @@ const props = defineProps({
panelRatio: { panelRatio: {
type: Number, type: Number,
default: 0.35 default: 0.35
},
isExpanded: {
type: Boolean,
default: false
} }
}) })
const emit = defineEmits(['refresh', 'close', 'resize', 'resizing', 'toggle-expand']) const emit = defineEmits(['refresh', 'resize', 'resizing'])
const INLINE_PREVIEW_MIN_WIDTH = 920 const INLINE_PREVIEW_MIN_WIDTH = 920
const DEFAULT_EXPANDED_ROOT_DIRECTORY_NAME = 'user-data' const DEFAULT_EXPANDED_ROOT_DIRECTORY_NAME = 'user-data'
@ -751,23 +737,6 @@ watch(useInlinePreview, (isInline) => {
flex-shrink: 0; flex-shrink: 0;
} }
.close-btn {
border: none;
background: transparent;
cursor: pointer;
color: var(--gray-500);
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background: var(--gray-100);
color: var(--gray-700);
}
}
.tab-content { .tab-content {
flex: 1; flex: 1;

View File

@ -72,9 +72,20 @@
</a-dropdown> </a-dropdown>
</template> </template>
<template #header-right v-if="userStore.isAdmin"> <template #header-right="{ isAgentPanelOpen, hasActiveThread, toggleAgentPanel }">
<button
v-if="hasActiveThread"
type="button"
class="agent-nav-btn agent-state-btn"
:class="{ active: isAgentPanelOpen }"
title="查看文件"
@click.stop="toggleAgentPanel"
>
<FolderKanban size="18" class="nav-btn-icon" />
<span class="hide-text">文件</span>
</button>
<div <div
v-if="selectedAgentId" v-if="userStore.isAdmin && selectedAgentId"
ref="moreButtonRef" ref="moreButtonRef"
type="button" type="button"
class="agent-nav-btn" class="agent-nav-btn"
@ -141,7 +152,7 @@
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import { MessageOutlined, ShareAltOutlined } from '@ant-design/icons-vue' import { MessageOutlined, ShareAltOutlined } from '@ant-design/icons-vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { Settings2, Ellipsis, ChevronDown, Check, Plus } from 'lucide-vue-next' import { Settings2, Ellipsis, ChevronDown, Check, Plus, FolderKanban } from 'lucide-vue-next'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import AgentChatComponent from '@/components/AgentChatComponent.vue' import AgentChatComponent from '@/components/AgentChatComponent.vue'
import AgentConfigSidebar from '@/components/AgentConfigSidebar.vue' import AgentConfigSidebar from '@/components/AgentConfigSidebar.vue'