From 8e88830e2d29322e8885c099f239df31c1ffdcdb Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Tue, 24 Mar 2026 21:42:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E6=B7=BB=E5=8A=A0=E5=8F=91?= =?UTF-8?q?=E9=80=81=E6=8C=89=E9=92=AE=E5=86=B7=E5=8D=B4=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=EF=BC=8C=E9=98=B2=E6=AD=A2=E8=BF=9E=E7=BB=AD=E5=8F=91=E9=80=81?= =?UTF-8?q?=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/AgentChatComponent.vue | 32 +++++++++++++++++++++-- web/src/components/AgentInputArea.vue | 6 ++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 9d7093c9..2a5d6fae 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -150,7 +150,7 @@ v-model="userInput" :is-loading="isProcessing" :disabled="!currentAgent" - :send-button-disabled="(!userInput || !currentAgent) && !isProcessing" + :send-button-disabled="isSendButtonDisabled" placeholder="输入问题..." :mention="mentionConfig" :supports-file-upload="supportsFileUpload" @@ -274,6 +274,8 @@ const { organization } = storeToRefs(infoStore) // ==================== LOCAL CHAT & UI STATE ==================== const userInput = ref('') +const sendCooldownActive = ref(false) +let sendCooldownTimer = null const sidebarLogo = computed(() => organization.value?.logo || organization.value?.avatar || '') const useRunsApi = import.meta.env.VITE_USE_RUNS_API === 'true' && @@ -498,6 +500,20 @@ const isStreaming = computed(() => { return threadState ? threadState.isStreaming : false }) const isProcessing = computed(() => isStreaming.value) +const isSendButtonDisabled = computed(() => { + return sendCooldownActive.value || (((!userInput.value || !currentAgent.value) && !isProcessing.value)) +}) + +const startSendCooldown = () => { + sendCooldownActive.value = true + if (sendCooldownTimer) { + clearTimeout(sendCooldownTimer) + } + sendCooldownTimer = setTimeout(() => { + sendCooldownActive.value = false + sendCooldownTimer = null + }, 2000) +} // ==================== SCROLL & RESIZE HANDLING ==================== const scrollController = new ScrollController('.chat-main') @@ -546,6 +562,10 @@ onUnmounted(() => { if (chatMainResizeObserver) { chatMainResizeObserver.disconnect() } + if (sendCooldownTimer) { + clearTimeout(sendCooldownTimer) + sendCooldownTimer = null + } // 清理所有线程状态 resetOnGoingConv() }) @@ -1010,7 +1030,11 @@ const togglePinChat = async (chatId) => { const handleSendMessage = async ({ image } = {}) => { const text = userInput.value.trim() - if ((!text && !image) || !currentAgent.value || isProcessing.value) return + if ((!text && !image) || !currentAgent.value || isProcessing.value || sendCooldownActive.value) + return + + // 发送后进入短暂冷却,防止连续触发停止 + startSendCooldown() let threadId = currentChatId.value if (!threadId) { @@ -1109,6 +1133,10 @@ const handleSendMessage = async ({ image } = {}) => { // 发送或中断 const handleSendOrStop = async (payload) => { + if (sendCooldownActive.value) { + return + } + const threadId = currentChatId.value const threadState = getThreadState(threadId) if (isProcessing.value && threadState) { diff --git a/web/src/components/AgentInputArea.vue b/web/src/components/AgentInputArea.vue index 459c3641..a1f6e870 100644 --- a/web/src/components/AgentInputArea.vue +++ b/web/src/components/AgentInputArea.vue @@ -58,7 +58,7 @@ import ImagePreviewComponent from '@/components/ImagePreviewComponent.vue' import AttachmentOptionsComponent from '@/components/AttachmentOptionsComponent.vue' import { FolderCode } from 'lucide-vue-next' -defineProps({ +const props = defineProps({ modelValue: { type: String, default: '' }, isLoading: { type: Boolean, default: false }, disabled: { type: Boolean, default: false }, @@ -112,6 +112,10 @@ const handleSend = () => { } const handleKeyDown = (e) => { + if (props.sendButtonDisabled) { + return + } + if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault() handleSend()