优化事件处理

This commit is contained in:
xintaofei
2026-03-08 17:08:31 +08:00
parent 7a4cbcb73e
commit c1220e1a8f
12 changed files with 178 additions and 64 deletions

View File

@@ -3,6 +3,7 @@
import { useEffect, useRef, useState } from "react"
import { useTranslations } from "next-intl"
import { acpListAgents } from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import type { AgentType, AcpAgentInfo } from "@/lib/types"
import { AGENT_LABELS } from "@/lib/types"
import { AgentIcon } from "@/components/agent-icon"
@@ -100,7 +101,7 @@ export function AgentSelector({
)
.then((dispose) => {
if (cancelled) {
dispose()
disposeTauriListener(dispose, "AgentSelector.agentsUpdated")
return
}
unlisten = dispose
@@ -112,9 +113,7 @@ export function AgentSelector({
return () => {
cancelled = true
window.removeEventListener("focus", onWindowFocus)
if (unlisten) {
unlisten()
}
disposeTauriListener(unlisten, "AgentSelector.agentsUpdated")
}
}, [defaultAgentType])

View File

@@ -1,6 +1,7 @@
"use client"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { TauriEvent } from "@tauri-apps/api/event"
import { getCurrentWebview } from "@tauri-apps/api/webview"
import { open } from "@tauri-apps/plugin-dialog"
import Image from "next/image"
@@ -10,6 +11,7 @@ import { Textarea } from "@/components/ui/textarea"
import { FileSearch, Plus, Send, Square, X } from "lucide-react"
import { cn } from "@/lib/utils"
import { readFileBase64 } from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import type {
AvailableCommandInfo,
PromptCapabilitiesInfo,
@@ -686,46 +688,109 @@ export function MessageInput({
}, [appendResourceAttachments, attachmentTabId])
useEffect(() => {
let unlisten: (() => void) | null = null
let cancelled = false
const unlisteners: Array<() => void | Promise<void>> = []
getCurrentWebview()
.onDragDropEvent((event) => {
const host = containerRef.current
if (!host) return
const payload = event.payload
if (payload.type === "leave") {
setIsDragActive(false)
return
const cleanupListeners = () => {
for (const fn of unlisteners.splice(0)) {
disposeTauriListener(fn, "MessageInput.dragDrop")
}
}
type DragDropPayload =
| {
type: "enter" | "drop"
paths: string[]
position: { x: number; y: number }
}
const inside = pointWithinElement(payload.position, host)
if (payload.type === "drop") {
setIsDragActive(false)
if (Date.now() - lastDomDropAtRef.current < 250) return
if (!inside || disabled || isPrompting) return
void appendPathsFromDrop(payload.paths).catch((error) => {
console.error("[MessageInput] drag drop paths failed:", error)
| {
type: "over"
position: { x: number; y: number }
}
| { type: "leave" }
const handlePayload = (payload: DragDropPayload) => {
const host = containerRef.current
if (!host) return
if (payload.type === "leave") {
setIsDragActive(false)
return
}
const inside = pointWithinElement(payload.position, host)
if (payload.type === "drop") {
setIsDragActive(false)
if (Date.now() - lastDomDropAtRef.current < 250) return
if (!inside || disabled || isPrompting) return
void appendPathsFromDrop(payload.paths).catch((error) => {
console.error("[MessageInput] drag drop paths failed:", error)
})
return
}
setIsDragActive(inside && !disabled && !isPrompting)
}
const setup = async () => {
const webview = getCurrentWebview()
try {
const unlistenEnter = await webview.listen<{
paths: string[]
position: { x: number; y: number }
}>(TauriEvent.DRAG_ENTER, (event) => {
if (cancelled) return
handlePayload({
type: "enter",
paths: event.payload.paths,
position: event.payload.position,
})
return
}
setIsDragActive(inside && !disabled && !isPrompting)
})
.then((fn) => {
if (cancelled) {
fn()
} else {
unlisten = fn
}
})
.catch(() => {
})
unlisteners.push(unlistenEnter)
const unlistenOver = await webview.listen<{
position: { x: number; y: number }
}>(TauriEvent.DRAG_OVER, (event) => {
if (cancelled) return
handlePayload({
type: "over",
position: event.payload.position,
})
})
unlisteners.push(unlistenOver)
const unlistenDrop = await webview.listen<{
paths: string[]
position: { x: number; y: number }
}>(TauriEvent.DRAG_DROP, (event) => {
if (cancelled) return
handlePayload({
type: "drop",
paths: event.payload.paths,
position: event.payload.position,
})
})
unlisteners.push(unlistenDrop)
const unlistenLeave = await webview.listen(
TauriEvent.DRAG_LEAVE,
() => {
if (cancelled) return
handlePayload({ type: "leave" })
}
)
unlisteners.push(unlistenLeave)
} catch {
// Ignore non-Tauri environments.
})
} finally {
if (cancelled) {
cleanupListeners()
}
}
}
void setup()
return () => {
cancelled = true
if (unlisten) {
unlisten()
}
cleanupListeners()
}
}, [appendPathsFromDrop, disabled, isPrompting])

View File

@@ -36,6 +36,7 @@ import {
updateConversationStatus,
updateConversationExternalId,
} from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import { AgentSelector } from "@/components/chat/agent-selector"
import { LiveMessageBlock } from "@/components/chat/live-message-block"
import { AgentPlanOverlay } from "@/components/chat/agent-plan-overlay"
@@ -448,7 +449,7 @@ export function WelcomeInputPanel({
)
.then((dispose) => {
if (cancelled) {
dispose()
disposeTauriListener(dispose, "WelcomeInputPanel.agentsUpdated")
return
}
unlisten = dispose
@@ -463,9 +464,7 @@ export function WelcomeInputPanel({
clearTimeout(agentStatusRefreshTimerRef.current)
agentStatusRefreshTimerRef.current = null
}
if (unlisten) {
unlisten()
}
disposeTauriListener(unlisten, "WelcomeInputPanel.agentsUpdated")
}
}, [])

View File

@@ -23,6 +23,7 @@ import {
type IntlLocale,
} from "@/lib/i18n"
import { getSystemLanguageSettings } from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import { AppBootLoading } from "@/components/layout/app-boot-loading"
import type { AppLocale, SystemLanguageSettings } from "@/lib/types"
@@ -162,7 +163,7 @@ export function AppI18nProvider({
)
.then((dispose) => {
if (cancelled) {
dispose()
disposeTauriListener(dispose, "I18nProvider.languageSettings")
return
}
unlisten = dispose
@@ -174,9 +175,7 @@ export function AppI18nProvider({
return () => {
cancelled = true
window.removeEventListener("storage", onStorage)
if (unlisten) {
unlisten()
}
disposeTauriListener(unlisten, "I18nProvider.languageSettings")
}
}, [setLanguageSettings])

View File

@@ -35,6 +35,7 @@ import {
startFileTreeWatch,
stopFileTreeWatch,
} from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import { emitAttachFileToSession } from "@/lib/session-attachment-events"
import type {
FileTreeChangedEvent,
@@ -1961,9 +1962,7 @@ export function FileTreeTab() {
pendingTreeRefreshRef.current = false
pendingTreeRefreshNeedsStatusRef.current = false
pendingStatusRefreshRef.current = false
if (unlisten) {
unlisten()
}
disposeTauriListener(unlisten, "AuxPanelFileTree.fileTreeChanged")
void stopFileTreeWatch(rootPath)
}
}, [fetchTree, folder?.path, openFilePreview, t])

View File

@@ -46,6 +46,7 @@ import {
startFileTreeWatch,
stopFileTreeWatch,
} from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import type { FileTreeChangedEvent, GitStatusEntry } from "@/lib/types"
import {
AlertDialog,
@@ -630,9 +631,7 @@ export function GitChangesTab() {
clearTimeout(refreshTimerRef.current)
refreshTimerRef.current = null
}
if (unlisten) {
unlisten()
}
disposeTauriListener(unlisten, "AuxPanelGitChanges.fileTreeChanged")
void stopFileTreeWatch(rootPath)
}
}, [fetchChanges, folder?.path, isChangesTabActive])

View File

@@ -80,6 +80,7 @@ import {
openCommitWindow,
setFolderParentBranch,
} from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import type { GitBranchList } from "@/lib/types"
import { toast } from "sonner"
import { useFolderContext } from "@/contexts/folder-context"
@@ -161,7 +162,7 @@ export function BranchDropdown({
})
return () => {
if (unlisten) unlisten()
disposeTauriListener(unlisten, "BranchDropdown.gitCommitSucceeded")
}
}, [folder, onBranchChange, t])

View File

@@ -20,6 +20,7 @@ import {
terminalKill,
terminalList,
} from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import type { FolderCommand, TerminalEvent } from "@/lib/types"
import { CommandManageDialog } from "./command-manage-dialog"
@@ -66,7 +67,7 @@ export function CommandDropdown() {
const clearRunningByTerminalId = useCallback((terminalId: string) => {
const unlisten = exitUnlistenersRef.current.get(terminalId)
if (unlisten) {
unlisten()
disposeTauriListener(unlisten, "CommandDropdown.terminalExit")
exitUnlistenersRef.current.delete(terminalId)
}
@@ -85,7 +86,7 @@ export function CommandDropdown() {
const clearAllRunningStates = useCallback(() => {
for (const unlisten of exitUnlistenersRef.current.values()) {
unlisten()
disposeTauriListener(unlisten, "CommandDropdown.terminalExit")
}
exitUnlistenersRef.current.clear()
setRunningCommandTerminals({})

View File

@@ -4,6 +4,7 @@ import { useEffect, useState } from "react"
import { getCurrentWindow } from "@tauri-apps/api/window"
import { useTranslations } from "next-intl"
import { usePlatform } from "@/hooks/use-platform"
import { disposeTauriListener } from "@/lib/tauri-listener"
import { cn } from "@/lib/utils"
export function WindowControls() {
@@ -59,7 +60,7 @@ export function WindowControls() {
if (resizeFrame !== null) {
window.cancelAnimationFrame(resizeFrame)
}
unlistenResize?.()
disposeTauriListener(unlistenResize, "WindowControls.resize")
}
}, [isWindows])

View File

@@ -3,6 +3,7 @@
import { useEffect, useRef } from "react"
import { listen } from "@tauri-apps/api/event"
import { terminalWrite, terminalResize } from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import type { TerminalEvent } from "@/lib/types"
import type { ITheme } from "@xterm/xterm"
@@ -186,8 +187,8 @@ export function TerminalView({
themeObserver.disconnect()
onDataDisposable.dispose()
onResizeDisposable.dispose()
unlisten()
unlistenExit()
disposeTauriListener(unlisten, "TerminalView.output")
disposeTauriListener(unlistenExit, "TerminalView.exit")
term.dispose()
return
}
@@ -221,8 +222,8 @@ export function TerminalView({
themeObserver.disconnect()
onDataDisposable.dispose()
onResizeDisposable.dispose()
unlisten()
unlistenExit()
disposeTauriListener(unlisten, "TerminalView.output")
disposeTauriListener(unlistenExit, "TerminalView.exit")
resizeObserver.disconnect()
term.dispose()
fitAddonRef.current = null