mirror of https://github.com/kortix-ai/suna.git
Merge pull request #518 from kubet/fix/pointer-native-event
Fix/pointer native event
This commit is contained in:
commit
606bff01f0
|
@ -61,38 +61,38 @@ export function NavAgents() {
|
|||
const { performDelete } = useDeleteOperation();
|
||||
const isPerformingActionRef = useRef(false);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
const [selectedThreads, setSelectedThreads] = useState<Set<string>>(new Set());
|
||||
const [deleteProgress, setDeleteProgress] = useState(0);
|
||||
const [totalToDelete, setTotalToDelete] = useState(0);
|
||||
|
||||
const {
|
||||
data: projects = [],
|
||||
const {
|
||||
data: projects = [],
|
||||
isLoading: isProjectsLoading,
|
||||
error: projectsError
|
||||
error: projectsError
|
||||
} = useProjects();
|
||||
|
||||
const {
|
||||
data: threads = [],
|
||||
|
||||
const {
|
||||
data: threads = [],
|
||||
isLoading: isThreadsLoading,
|
||||
error: threadsError
|
||||
error: threadsError
|
||||
} = useThreads();
|
||||
|
||||
const { mutate: deleteThreadMutation, isPending: isDeletingSingle } = useDeleteThread();
|
||||
const {
|
||||
mutate: deleteMultipleThreadsMutation,
|
||||
isPending: isDeletingMultiple
|
||||
const {
|
||||
mutate: deleteMultipleThreadsMutation,
|
||||
isPending: isDeletingMultiple
|
||||
} = useDeleteMultipleThreads();
|
||||
|
||||
const combinedThreads: ThreadWithProject[] =
|
||||
!isProjectsLoading && !isThreadsLoading ?
|
||||
processThreadsWithProjects(threads, projects) : [];
|
||||
const combinedThreads: ThreadWithProject[] =
|
||||
!isProjectsLoading && !isThreadsLoading ?
|
||||
processThreadsWithProjects(threads, projects) : [];
|
||||
|
||||
const handleDeletionProgress = (completed: number, total: number) => {
|
||||
const percentage = (completed / total) * 100;
|
||||
setDeleteProgress(percentage);
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const handleProjectUpdate = (event: Event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
|
@ -145,7 +145,7 @@ export function NavAgents() {
|
|||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
e.preventDefault()
|
||||
setLoadingThreadId(threadId)
|
||||
router.push(url)
|
||||
|
@ -157,7 +157,7 @@ export function NavAgents() {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
|
||||
setSelectedThreads(prev => {
|
||||
const newSelection = new Set(prev);
|
||||
if (newSelection.has(threadId)) {
|
||||
|
@ -189,18 +189,18 @@ export function NavAgents() {
|
|||
// Function to handle multi-delete
|
||||
const handleMultiDelete = () => {
|
||||
if (selectedThreads.size === 0) return;
|
||||
|
||||
|
||||
// Get thread names for confirmation dialog
|
||||
const threadsToDelete = combinedThreads.filter(t => selectedThreads.has(t.threadId));
|
||||
const threadNames = threadsToDelete.map(t => t.projectName).join(", ");
|
||||
|
||||
setThreadToDelete({
|
||||
id: "multiple",
|
||||
name: selectedThreads.size > 3
|
||||
? `${selectedThreads.size} conversations`
|
||||
: threadNames
|
||||
|
||||
setThreadToDelete({
|
||||
id: "multiple",
|
||||
name: selectedThreads.size > 3
|
||||
? `${selectedThreads.size} conversations`
|
||||
: threadNames
|
||||
});
|
||||
|
||||
|
||||
setTotalToDelete(selectedThreads.size);
|
||||
setDeleteProgress(0);
|
||||
setIsDeleteDialogOpen(true);
|
||||
|
@ -261,10 +261,10 @@ export function NavAgents() {
|
|||
// Multi-thread deletion
|
||||
const threadIdsToDelete = Array.from(selectedThreads);
|
||||
const isActiveThreadIncluded = threadIdsToDelete.some(id => pathname?.includes(id));
|
||||
|
||||
|
||||
// Show initial toast
|
||||
toast.info(`Deleting ${threadIdsToDelete.length} conversations...`);
|
||||
|
||||
|
||||
try {
|
||||
// If the active thread is included, handle navigation first
|
||||
if (isActiveThreadIncluded) {
|
||||
|
@ -272,14 +272,14 @@ export function NavAgents() {
|
|||
isNavigatingRef.current = true;
|
||||
document.body.style.pointerEvents = 'none';
|
||||
router.push('/dashboard');
|
||||
|
||||
|
||||
// Wait a moment for navigation to start
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
|
||||
// Use the mutation for bulk deletion
|
||||
deleteMultipleThreadsMutation(
|
||||
{
|
||||
{
|
||||
threadIds: threadIdsToDelete,
|
||||
onProgress: handleDeletionProgress
|
||||
},
|
||||
|
@ -287,15 +287,15 @@ export function NavAgents() {
|
|||
onSuccess: (data) => {
|
||||
// Invalidate queries to refresh the list
|
||||
queryClient.invalidateQueries({ queryKey: threadKeys.lists() });
|
||||
|
||||
|
||||
// Show success message
|
||||
toast.success(`Successfully deleted ${data.successful.length} conversations`);
|
||||
|
||||
|
||||
// If some deletions failed, show warning
|
||||
if (data.failed.length > 0) {
|
||||
toast.warning(`Failed to delete ${data.failed.length} conversations`);
|
||||
}
|
||||
|
||||
|
||||
// Reset states
|
||||
setSelectedThreads(new Set());
|
||||
setDeleteProgress(0);
|
||||
|
@ -316,7 +316,7 @@ export function NavAgents() {
|
|||
} catch (err) {
|
||||
console.error('Error initiating bulk deletion:', err);
|
||||
toast.error('Error initiating deletion process');
|
||||
|
||||
|
||||
// Reset states
|
||||
setSelectedThreads(new Set());
|
||||
setThreadToDelete(null);
|
||||
|
@ -330,7 +330,7 @@ export function NavAgents() {
|
|||
// Loading state or error handling
|
||||
const isLoading = isProjectsLoading || isThreadsLoading;
|
||||
const hasError = projectsError || threadsError;
|
||||
|
||||
|
||||
if (hasError) {
|
||||
console.error('Error loading data:', { projectsError, threadsError });
|
||||
}
|
||||
|
@ -343,16 +343,16 @@ export function NavAgents() {
|
|||
<div className="flex items-center space-x-1">
|
||||
{selectedThreads.size > 0 ? (
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={deselectAllThreads}
|
||||
className="h-7 w-7"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={selectAllThreads}
|
||||
disabled={selectedThreads.size === combinedThreads.length}
|
||||
|
@ -360,8 +360,8 @@ export function NavAgents() {
|
|||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleMultiDelete}
|
||||
className="h-7 w-7 text-destructive"
|
||||
|
@ -436,8 +436,8 @@ export function NavAgents() {
|
|||
<SidebarMenuButton
|
||||
asChild
|
||||
className={
|
||||
isActive ? 'bg-accent text-accent-foreground' :
|
||||
isSelected ? 'bg-primary/10' : ''
|
||||
isActive ? 'bg-accent text-accent-foreground' :
|
||||
isSelected ? 'bg-primary/10' : ''
|
||||
}
|
||||
>
|
||||
<Link
|
||||
|
@ -462,13 +462,12 @@ export function NavAgents() {
|
|||
<div className="relative">
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
className={`relative ${
|
||||
isActive
|
||||
? 'bg-accent text-accent-foreground font-medium'
|
||||
: isSelected
|
||||
? 'bg-primary/10'
|
||||
className={`relative ${isActive
|
||||
? 'bg-accent text-accent-foreground font-medium'
|
||||
: isSelected
|
||||
? 'bg-primary/10'
|
||||
: ''
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
<Link
|
||||
href={thread.url}
|
||||
|
@ -484,27 +483,24 @@ export function NavAgents() {
|
|||
) : (
|
||||
<>
|
||||
{/* MessagesSquare icon - hidden on hover if not selected */}
|
||||
<MessagesSquare
|
||||
className={`h-4 w-4 transition-opacity duration-150 ${
|
||||
isSelected ? 'opacity-0' : 'opacity-100 group-hover/icon:opacity-0'
|
||||
}`}
|
||||
<MessagesSquare
|
||||
className={`h-4 w-4 transition-opacity duration-150 ${isSelected ? 'opacity-0' : 'opacity-100 group-hover/icon:opacity-0'
|
||||
}`}
|
||||
/>
|
||||
|
||||
|
||||
{/* Checkbox - appears on hover or when selected */}
|
||||
<div
|
||||
className={`absolute inset-0 flex items-center justify-center transition-opacity duration-150 ${
|
||||
isSelected
|
||||
? 'opacity-100'
|
||||
: 'opacity-0 group-hover/icon:opacity-100'
|
||||
}`}
|
||||
<div
|
||||
className={`absolute inset-0 flex items-center justify-center transition-opacity duration-150 ${isSelected
|
||||
? 'opacity-100'
|
||||
: 'opacity-0 group-hover/icon:opacity-100'
|
||||
}`}
|
||||
onClick={(e) => toggleThreadSelection(thread.threadId, e)}
|
||||
>
|
||||
<div
|
||||
className={`h-4 w-4 border rounded cursor-pointer hover:bg-muted/50 transition-colors flex items-center justify-center ${
|
||||
isSelected
|
||||
? 'bg-primary border-primary'
|
||||
: 'border-muted-foreground/30 bg-background'
|
||||
}`}
|
||||
<div
|
||||
className={`h-4 w-4 border rounded cursor-pointer hover:bg-muted/50 transition-colors flex items-center justify-center ${isSelected
|
||||
? 'bg-primary border-primary'
|
||||
: 'border-muted-foreground/30 bg-background'
|
||||
}`}
|
||||
>
|
||||
{isSelected && <Check className="h-3 w-3 text-primary-foreground" />}
|
||||
</div>
|
||||
|
@ -520,7 +516,14 @@ export function NavAgents() {
|
|||
{state !== 'collapsed' && !isSelected && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction showOnHover className="group-hover:opacity-100">
|
||||
<SidebarMenuAction
|
||||
showOnHover
|
||||
className="group-hover:opacity-100"
|
||||
onClick={() => {
|
||||
// Ensure pointer events are enabled when dropdown opens
|
||||
document.body.style.pointerEvents = 'auto';
|
||||
}}
|
||||
>
|
||||
<MoreHorizontal />
|
||||
<span className="sr-only">More</span>
|
||||
</SidebarMenuAction>
|
||||
|
@ -575,21 +578,21 @@ export function NavAgents() {
|
|||
</SidebarMenuItem>
|
||||
)}
|
||||
</SidebarMenu>
|
||||
|
||||
|
||||
{(isDeletingSingle || isDeletingMultiple) && totalToDelete > 0 && (
|
||||
<div className="mt-2 px-2">
|
||||
<div className="text-xs text-muted-foreground mb-1">
|
||||
Deleting {deleteProgress > 0 ? `(${Math.floor(deleteProgress)}%)` : '...'}
|
||||
</div>
|
||||
<div className="w-full bg-secondary h-1 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="bg-primary h-1 transition-all duration-300 ease-in-out"
|
||||
<div
|
||||
className="bg-primary h-1 transition-all duration-300 ease-in-out"
|
||||
style={{ width: `${deleteProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
<ShareModal
|
||||
isOpen={showShareModal}
|
||||
onClose={() => setShowShareModal(false)}
|
||||
|
|
|
@ -28,7 +28,14 @@ export function ShareModal({ isOpen, onClose, threadId, projectId }: ShareModalP
|
|||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
const [isCopying, setIsCopying] = useState(false);
|
||||
|
||||
|
||||
// Reset pointer events when modal opens
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.pointerEvents = 'auto';
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const updateThreadMutation = useUpdateThreadMutation();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -105,7 +112,7 @@ export function ShareModal({ isOpen, onClose, threadId, projectId }: ShareModalP
|
|||
const updatePublicStatus = async (isPublic: boolean) => {
|
||||
console.log("Updating public status for thread:", threadId, "and project:", projectId, "to", isPublic);
|
||||
if (!threadId) return;
|
||||
|
||||
|
||||
await updateProject(projectId, { is_public: isPublic });
|
||||
await updateThreadMutation.mutateAsync({
|
||||
threadId,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
import {
|
||||
|
@ -32,6 +32,13 @@ export function DeleteConfirmationDialog({
|
|||
threadName,
|
||||
isDeleting,
|
||||
}: DeleteConfirmationDialogProps) {
|
||||
// Reset pointer events when dialog opens
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
document.body.style.pointerEvents = 'auto';
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<AlertDialog open={isOpen} onOpenChange={onClose}>
|
||||
<AlertDialogContent>
|
||||
|
|
|
@ -96,7 +96,7 @@ export const MessageInput = forwardRef<HTMLTextAreaElement, MessageInputProps>(
|
|||
}, [value, ref]);
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
|
||||
e.preventDefault();
|
||||
if (
|
||||
(value.trim() || uploadedFiles.length > 0) &&
|
||||
|
|
Loading…
Reference in New Issue