Merge pull request #870 from AnishSarkar22/fix/ui-tests-ci

fix: document cleanup logic and mock Celery task in tests & some UI fixes
This commit is contained in:
Rohan Verma 2026-03-11 00:11:45 -07:00 committed by GitHub
commit a56383ce55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 240 additions and 127 deletions

View file

@ -183,7 +183,7 @@ async def _cleanup_documents(
for doc_id in cleanup_doc_ids:
try:
resp = await delete_document(client, headers, doc_id)
if resp.status_code == 409:
if resp.status_code != 200:
remaining_ids.append(doc_id)
except Exception:
remaining_ids.append(doc_id)
@ -274,6 +274,15 @@ def _mock_external_apis(monkeypatch):
)
@pytest.fixture(autouse=True)
def _mock_celery_delete_task(monkeypatch):
"""Mock Celery delete dispatch — no broker is available in CI."""
monkeypatch.setattr(
"app.tasks.celery_tasks.document_tasks.delete_document_task.delay",
lambda *args, **kwargs: None,
)
@pytest.fixture(autouse=True)
def _mock_redis_heartbeat(monkeypatch):
"""Mock Redis heartbeat — Redis is an external infrastructure boundary."""

View file

@ -1,6 +1,6 @@
import { atom } from "jotai";
import { atomWithQuery } from "jotai-tanstack-query";
import { agentToolsApiService, type AgentToolInfo } from "@/lib/apis/agent-tools-api.service";
import { type AgentToolInfo, agentToolsApiService } from "@/lib/apis/agent-tools-api.service";
import { cacheKeys } from "@/lib/query-client/cache-keys";
import { activeSearchSpaceIdAtom } from "../search-spaces/search-space-query.atoms";

View file

@ -1,5 +1,4 @@
import { atom } from "jotai";
import { documentsSidebarOpenAtom } from "@/atoms/documents/ui.atoms";
import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right-panel.atom";
interface ReportPanelState {
@ -25,11 +24,14 @@ export const reportPanelAtom = atom<ReportPanelState>(initialState);
/** Derived read-only atom for checking if panel is open */
export const reportPanelOpenAtom = atom((get) => get(reportPanelAtom).isOpen);
/** Snapshot of `rightPanelCollapsedAtom` taken before the report opens */
const preReportCollapsedAtom = atom<boolean | null>(null);
/** Action atom to open the report panel with a specific report */
export const openReportPanelAtom = atom(
null,
(
_get,
get,
set,
{
reportId,
@ -38,6 +40,9 @@ export const openReportPanelAtom = atom(
shareToken,
}: { reportId: number; title: string; wordCount?: number; shareToken?: string | null }
) => {
if (!get(reportPanelAtom).isOpen) {
set(preReportCollapsedAtom, get(rightPanelCollapsedAtom));
}
set(reportPanelAtom, {
isOpen: true,
reportId,
@ -47,12 +52,16 @@ export const openReportPanelAtom = atom(
});
set(rightPanelTabAtom, "report");
set(rightPanelCollapsedAtom, false);
set(documentsSidebarOpenAtom, true);
}
);
/** Action atom to close the report panel */
export const closeReportPanelAtom = atom(null, (_get, set) => {
export const closeReportPanelAtom = atom(null, (get, set) => {
set(reportPanelAtom, initialState);
set(rightPanelTabAtom, "sources");
const prev = get(preReportCollapsedAtom);
if (prev !== null) {
set(rightPanelCollapsedAtom, prev);
set(preReportCollapsedAtom, null);
}
});

View file

@ -235,39 +235,39 @@ export const ComposioDriveConfig: FC<ComposioDriveConfigProps> = ({
</div>
)}
{isEditMode ? (
<div className="space-y-2">
<button
type="button"
onClick={() => setIsFolderTreeOpen(!isFolderTreeOpen)}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground transition-colors w-fit"
>
{isFolderTreeOpen ? (
<ChevronDown className="size-4" />
) : (
<ChevronRight className="size-4" />
{isEditMode ? (
<div className="space-y-2">
<button
type="button"
onClick={() => setIsFolderTreeOpen(!isFolderTreeOpen)}
className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground hover:text-foreground transition-colors w-fit"
>
{isFolderTreeOpen ? (
<ChevronDown className="size-4" />
) : (
<ChevronRight className="size-4" />
)}
Change Selection
</button>
{isFolderTreeOpen && (
<ComposioDriveFolderTree
connectorId={connector.id}
selectedFolders={selectedFolders}
onSelectFolders={handleSelectFolders}
selectedFiles={selectedFiles}
onSelectFiles={handleSelectFiles}
/>
)}
Change Selection
</button>
{isFolderTreeOpen && (
<ComposioDriveFolderTree
connectorId={connector.id}
selectedFolders={selectedFolders}
onSelectFolders={handleSelectFolders}
selectedFiles={selectedFiles}
onSelectFiles={handleSelectFiles}
/>
)}
</div>
) : (
<ComposioDriveFolderTree
connectorId={connector.id}
selectedFolders={selectedFolders}
onSelectFolders={handleSelectFolders}
selectedFiles={selectedFiles}
onSelectFiles={handleSelectFiles}
/>
)}
</div>
) : (
<ComposioDriveFolderTree
connectorId={connector.id}
selectedFolders={selectedFolders}
onSelectFolders={handleSelectFolders}
selectedFiles={selectedFiles}
onSelectFiles={handleSelectFiles}
/>
)}
</div>
{/* Indexing Options */}

View file

@ -123,11 +123,7 @@ export const useConnectorDialog = () => {
}, []);
const handleAutoIndex = useCallback(
async (
connector: SearchSourceConnector,
connectorTitle: string,
connectorType: string
) => {
async (connector: SearchSourceConnector, connectorTitle: string, connectorType: string) => {
if (!searchSpaceId || isAutoIndexingRef.current) return;
isAutoIndexingRef.current = true;
@ -159,12 +155,10 @@ export const useConnectorDialog = () => {
},
});
trackIndexWithDateRangeStarted(
Number(searchSpaceId),
connectorType,
connector.id,
{ hasStartDate: true, hasEndDate: true }
);
trackIndexWithDateRangeStarted(Number(searchSpaceId), connectorType, connector.id, {
hasStartDate: true,
hasEndDate: true,
});
toast.success(`${connectorTitle} connected!`, {
id: toastId,
@ -317,8 +311,14 @@ export const useConnectorDialog = () => {
}
}
} else {
setIsOpen(false);
// Clear indexing config when modal is closed
// Do NOT call setIsOpen(false) here. Closing the dialog is handled
// explicitly by handleOpenChange and the individual action handlers.
// Relying on URL params to close the dialog caused a race condition
// where Next.js router updates from tab switches briefly produced
// stale searchParams without the "modal" key, closing the popup.
// Still clean up sub-view state when the modal param is gone
// (e.g. after browser back navigation or explicit handler URL cleanup).
if (indexingConfig) {
setIndexingConfig(null);
setIndexingConnector(null);
@ -331,7 +331,6 @@ export const useConnectorDialog = () => {
setIsScrolled(false);
setSearchQuery("");
}
// Clear editing connector when modal is closed
if (editingConnector) {
setEditingConnector(null);
setConnectorName(null);
@ -344,15 +343,12 @@ export const useConnectorDialog = () => {
setIsScrolled(false);
setSearchQuery("");
}
// Clear connecting connector type when modal is closed
if (connectingConnectorType) {
setConnectingConnectorType(null);
}
// Clear viewing accounts type when modal is closed
if (viewingAccountsType) {
setViewingAccountsType(null);
}
// Clear YouTube view when modal is closed (handled by view param check)
}
} catch (error) {
// Invalid query params - log but don't crash
@ -412,6 +408,7 @@ export const useConnectorDialog = () => {
if (earlyConnector && AUTO_INDEX_CONNECTOR_TYPES.has(earlyConnector.connectorType)) {
toast.loading(`Setting up ${earlyConnector.title}...`, { id: "auto-index" });
setIsOpen(false);
const url = new URL(window.location.href);
url.searchParams.delete("success");
url.searchParams.delete("connector");
@ -795,6 +792,8 @@ export const useConnectorDialog = () => {
: `${connectorTitle} connected and syncing started!`;
toast.success(successMessage);
// Close dialog and clean up URL
setIsOpen(false);
const url = new URL(window.location.href);
url.searchParams.delete("modal");
url.searchParams.delete("tab");
@ -860,7 +859,8 @@ export const useConnectorDialog = () => {
// Refresh connectors list before closing modal
await refetchAllConnectors();
// Close modal and return to main view
// Close dialog and clean up URL
setIsOpen(false);
const url = new URL(window.location.href);
url.searchParams.delete("modal");
url.searchParams.delete("tab");
@ -894,6 +894,7 @@ export const useConnectorDialog = () => {
updateConnector,
indexConnector,
router,
setIsOpen,
]
);
@ -1124,7 +1125,8 @@ export const useConnectorDialog = () => {
toast.success(`${indexingConfig.connectorTitle} indexing started`);
// Update URL - the effect will handle closing the modal and clearing state
// Close dialog and clean up URL
setIsOpen(false);
const url = new URL(window.location.href);
url.searchParams.delete("modal");
url.searchParams.delete("tab");
@ -1156,12 +1158,14 @@ export const useConnectorDialog = () => {
enableSummary,
router,
indexingConnectorConfig,
setIsOpen,
]
);
// Handle skipping indexing
const handleSkipIndexing = useCallback(() => {
// Update URL - the effect will handle closing the modal and clearing state
// Close dialog and clean up URL
setIsOpen(false);
const url = new URL(window.location.href);
url.searchParams.delete("modal");
url.searchParams.delete("tab");
@ -1169,7 +1173,7 @@ export const useConnectorDialog = () => {
url.searchParams.delete("connector");
url.searchParams.delete("view");
router.replace(url.pathname + url.search, { scroll: false });
}, [router]);
}, [router, setIsOpen]);
// Handle starting edit mode
const handleStartEdit = useCallback(
@ -1411,7 +1415,8 @@ export const useConnectorDialog = () => {
: indexingDescription,
});
// Update URL - the effect will handle closing the modal and clearing state
// Close dialog and clean up URL
setIsOpen(false);
const url = new URL(window.location.href);
url.searchParams.delete("modal");
url.searchParams.delete("tab");
@ -1445,6 +1450,7 @@ export const useConnectorDialog = () => {
router,
connectorConfig,
connectorName,
setIsOpen,
]
);
@ -1481,7 +1487,8 @@ export const useConnectorDialog = () => {
url.searchParams.set("view", "mcp-list");
url.searchParams.delete("connectorId");
} else {
// Close modal for all other cases
// Close dialog for all other cases
setIsOpen(false);
url.searchParams.delete("modal");
url.searchParams.delete("tab");
url.searchParams.delete("view");
@ -1500,7 +1507,7 @@ export const useConnectorDialog = () => {
setIsDisconnecting(false);
}
},
[editingConnector, searchSpaceId, deleteConnector, router, cameFromMCPList]
[editingConnector, searchSpaceId, deleteConnector, router, cameFromMCPList, setIsOpen]
);
// Handle quick index (index with selected date range, or backend defaults if none selected)

View file

@ -28,6 +28,13 @@ import {
import { useParams } from "next/navigation";
import { type FC, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { createPortal } from "react-dom";
import {
agentToolsAtom,
disabledToolsAtom,
enabledToolCountAtom,
hydrateDisabledToolsAtom,
toggleToolAtom,
} from "@/atoms/agent-tools/agent-tools.atoms";
import { chatSessionStateAtom } from "@/atoms/chat/chat-session-state.atom";
import {
mentionedDocumentsAtom,
@ -66,20 +73,14 @@ import {
import type { ThinkingStep } from "@/components/tool-ui/deepagent-thinking";
import { Avatar, AvatarFallback, AvatarGroup } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Switch } from "@/components/ui/switch";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { Document } from "@/contracts/types/document.types";
import { useBatchCommentsPreload } from "@/hooks/use-comments";
import { useCommentsElectric } from "@/hooks/use-comments-electric";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Switch } from "@/components/ui/switch";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import {
agentToolsAtom,
disabledToolsAtom,
enabledToolCountAtom,
hydrateDisabledToolsAtom,
toggleToolAtom,
} from "@/atoms/agent-tools/agent-tools.atoms";
import { useMediaQuery } from "@/hooks/use-media-query";
import { cn } from "@/lib/utils";
/** Placeholder texts that cycle in new chats when input is empty */
@ -562,7 +563,16 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
const mentionedDocuments = useAtomValue(mentionedDocumentsAtom);
const sidebarDocs = useAtomValue(sidebarSelectedDocumentsAtom);
const setDocumentsSidebarOpen = useSetAtom(documentsSidebarOpenAtom);
const setConnectorDialogOpen = useSetAtom(connectorDialogOpenAtom);
const [toolsPopoverOpen, setToolsPopoverOpen] = useState(false);
const isDesktop = useMediaQuery("(min-width: 640px)");
const [toolsScrollPos, setToolsScrollPos] = useState<"top" | "middle" | "bottom">("top");
const handleToolsScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
const el = e.currentTarget;
const atTop = el.scrollTop <= 2;
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight <= 2;
setToolsScrollPos(atTop ? "top" : atBottom ? "bottom" : "middle");
}, []);
const isComposerTextEmpty = useAssistantState(({ composer }) => {
const text = composer.text?.trim() || "";
return text.length === 0;
@ -614,32 +624,46 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
</TooltipIconButton>
</PopoverTrigger>
<PopoverContent
side="top"
side="bottom"
align="start"
sideOffset={12}
className="w-[calc(100vw-2rem)] max-w-80 sm:w-80 p-0"
className="w-[calc(100vw-2rem)] max-w-56 sm:max-w-72 sm:w-72 p-0 select-none"
onOpenAutoFocus={(e) => e.preventDefault()}
>
<div className="flex items-center justify-between px-3 py-2.5 border-b">
<span className="text-sm font-medium">Agent Tools</span>
<span className="text-xs text-muted-foreground">
<div className="flex items-center justify-between px-2.5 py-2 sm:px-3 sm:py-2.5 border-b">
<span className="text-xs sm:text-sm font-medium">Agent Tools</span>
<span className="text-[10px] sm:text-xs text-muted-foreground">
{enabledCount}/{agentTools?.length ?? 0} enabled
</span>
</div>
<div className="max-h-64 overflow-y-auto py-1">
<div
className="max-h-48 sm:max-h-64 overflow-y-auto py-0.5 sm:py-1"
onScroll={handleToolsScroll}
style={{
maskImage: `linear-gradient(to bottom, ${toolsScrollPos === "top" ? "black" : "transparent"}, black 16px, black calc(100% - 16px), ${toolsScrollPos === "bottom" ? "black" : "transparent"})`,
WebkitMaskImage: `linear-gradient(to bottom, ${toolsScrollPos === "top" ? "black" : "transparent"}, black 16px, black calc(100% - 16px), ${toolsScrollPos === "bottom" ? "black" : "transparent"})`,
}}
>
{agentTools?.map((tool) => {
const isDisabled = disabledTools.includes(tool.name);
const row = (
<label className="flex items-center gap-2 sm:gap-3 px-2.5 sm:px-3 py-1 sm:py-1.5 cursor-pointer hover:bg-muted-foreground/10 transition-colors">
<span className="flex-1 min-w-0 text-xs sm:text-sm font-medium truncate">
{formatToolName(tool.name)}
</span>
<Switch
checked={!isDisabled}
onCheckedChange={() => toggleTool(tool.name)}
className="shrink-0 scale-[0.6] sm:scale-75"
/>
</label>
);
if (!isDesktop) {
return <div key={tool.name}>{row}</div>;
}
return (
<Tooltip key={tool.name}>
<TooltipTrigger asChild>
<label className="flex items-center gap-3 px-3 py-1.5 cursor-pointer hover:bg-muted-foreground/10 transition-colors">
<span className="flex-1 min-w-0 text-sm font-medium truncate">{formatToolName(tool.name)}</span>
<Switch
checked={!isDisabled}
onCheckedChange={() => toggleTool(tool.name)}
className="shrink-0 scale-75"
/>
</label>
</TooltipTrigger>
<TooltipTrigger asChild>{row}</TooltipTrigger>
<TooltipContent side="right" className="max-w-64 text-xs">
{tool.description}
</TooltipContent>
@ -654,6 +678,19 @@ const ComposerAction: FC<ComposerActionProps> = ({ isBlockedByOtherUser = false
</div>
</PopoverContent>
</Popover>
{!isDesktop && (
<TooltipIconButton
tooltip="Manage connectors"
side="bottom"
variant="ghost"
size="icon"
className="size-[34px] rounded-full p-1 font-semibold text-xs hover:bg-muted-foreground/15 dark:border-muted-foreground/15 dark:hover:bg-muted-foreground/30"
aria-label="Manage connectors"
onClick={() => setConnectorDialogOpen(true)}
>
<Unplug className="size-4" />
</TooltipIconButton>
)}
{sidebarDocs.length > 0 && (
<button
type="button"

View file

@ -309,7 +309,7 @@ export function LayoutShell({
/>
<motion.main
layout="position"
layout={isResizing ? false : "position"}
style={{ contain: "inline-size" }}
className="flex-1 flex flex-col min-w-0"
>

View file

@ -8,7 +8,6 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useLongPress } from "@/hooks/use-long-press";
@ -20,6 +19,8 @@ interface ChatListItemProps {
name: string;
isActive?: boolean;
archived?: boolean;
dropdownOpen?: boolean;
onDropdownOpenChange?: (open: boolean) => void;
onClick?: () => void;
onRename?: () => void;
onArchive?: () => void;
@ -30,6 +31,8 @@ export function ChatListItem({
name,
isActive,
archived,
dropdownOpen: controlledOpen,
onDropdownOpenChange,
onClick,
onRename,
onArchive,
@ -37,11 +40,13 @@ export function ChatListItem({
}: ChatListItemProps) {
const t = useTranslations("sidebar");
const isMobile = useIsMobile();
const [dropdownOpen, setDropdownOpen] = useState(false);
const [internalOpen, setInternalOpen] = useState(false);
const dropdownOpen = controlledOpen ?? internalOpen;
const setDropdownOpen = onDropdownOpenChange ?? setInternalOpen;
const animatedName = useTypewriter(name);
const { handlers: longPressHandlers, wasLongPress } = useLongPress(
useCallback(() => setDropdownOpen(true), [])
useCallback(() => setDropdownOpen(true), [setDropdownOpen])
);
const handleClick = useCallback(() => {
@ -68,12 +73,12 @@ export function ChatListItem({
{/* Actions dropdown - trigger hidden on mobile, long-press opens it instead */}
<div
className={cn(
"absolute right-0 top-0 bottom-0 flex items-center pr-1 pl-6 rounded-r-md",
"pointer-events-none absolute right-0 top-0 bottom-0 flex items-center pr-1 pl-6 rounded-r-md",
isActive
? "bg-gradient-to-l from-accent from-60% to-transparent"
: "bg-gradient-to-l from-sidebar from-60% to-transparent group-hover/item:from-accent",
isMobile
? "opacity-0 pointer-events-none"
? "opacity-0"
: isActive
? "opacity-100"
: "opacity-0 group-hover/item:opacity-100"
@ -81,7 +86,7 @@ export function ChatListItem({
>
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-6 w-6">
<Button variant="ghost" size="icon" className="pointer-events-auto h-6 w-6">
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
<span className="sr-only">{t("more_options")}</span>
</Button>
@ -118,7 +123,6 @@ export function ChatListItem({
)}
</DropdownMenuItem>
)}
{onArchive && onDelete && <DropdownMenuSeparator />}
{onDelete && (
<DropdownMenuItem
onClick={(e) => {

View file

@ -233,34 +233,69 @@ export function DocumentsSidebar({
</div>
{/* Connected tools strip */}
<div className="shrink-0 mx-4 mt-2 mb-3 flex select-none items-center gap-2 rounded-lg border bg-muted/50 px-3 py-2">
<div className="shrink-0 mx-4 mt-2 mb-3 flex select-none items-center gap-2 rounded-lg border bg-muted/50 transition-colors hover:bg-muted/80">
<button
type="button"
onClick={() => setConnectorDialogOpen(true)}
className="flex items-center gap-2 min-w-0 flex-1 text-left"
className="flex items-center gap-2 min-w-0 flex-1 text-left px-3 py-2"
>
<Unplug className="size-4 shrink-0 text-muted-foreground" />
<span className="truncate text-xs text-muted-foreground">
{connectorCount > 0 ? "Manage connectors" : "Connect connectors"}
{connectorCount > 0 ? "Manage connectors" : "Connect your connectors"}
</span>
{connectorCount > 0 && (
<span className="ml-auto shrink-0 text-xs font-medium text-muted-foreground">{connectorCount}</span>
<span className="shrink-0 rounded-full bg-muted-foreground/15 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
{connectorCount}
</span>
)}
<AvatarGroup className="ml-auto shrink-0">
{SHOWCASE_CONNECTORS.map(({ type, label }, i) => (
<Tooltip key={type}>
<TooltipTrigger asChild>
<Avatar className="size-6" style={{ zIndex: SHOWCASE_CONNECTORS.length - i }}>
<AvatarFallback className="bg-muted text-[10px]">
{getConnectorIcon(type, "size-3.5")}
</AvatarFallback>
</Avatar>
</TooltipTrigger>
<TooltipContent side="top" className="text-xs">
{label}
</TooltipContent>
</Tooltip>
))}
{connectorCount > 0 && connectors
? connectors.slice(0, isMobile ? 5 : 9).map((connector, i) => {
const avatar = (
<Avatar
key={connector.id}
className="size-6"
style={{ zIndex: Math.max(9 - i, 1) }}
>
<AvatarFallback className="bg-muted text-[10px]">
{getConnectorIcon(connector.connector_type, "size-3.5")}
</AvatarFallback>
</Avatar>
);
if (isMobile) return avatar;
return (
<Tooltip key={connector.id}>
<TooltipTrigger asChild>{avatar}</TooltipTrigger>
<TooltipContent side="top" className="text-xs">
{connector.name}
</TooltipContent>
</Tooltip>
);
})
: (isMobile ? SHOWCASE_CONNECTORS.slice(0, 5) : SHOWCASE_CONNECTORS).map(
({ type, label }, i) => {
const avatar = (
<Avatar
key={type}
className="size-6"
style={{ zIndex: SHOWCASE_CONNECTORS.length - i }}
>
<AvatarFallback className="bg-muted text-[10px]">
{getConnectorIcon(type, "size-3.5")}
</AvatarFallback>
</Avatar>
);
if (isMobile) return avatar;
return (
<Tooltip key={type}>
<TooltipTrigger asChild>{avatar}</TooltipTrigger>
<TooltipContent side="top" className="text-xs">
{label}
</TooltipContent>
</Tooltip>
);
}
)}
</AvatarGroup>
</button>
</div>

View file

@ -2,6 +2,7 @@
import { FolderOpen, PenSquare } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
@ -89,6 +90,7 @@ export function Sidebar({
isResizing = false,
}: SidebarProps) {
const t = useTranslations("sidebar");
const [openDropdownChatId, setOpenDropdownChatId] = useState<number | null>(null);
return (
<div
@ -103,6 +105,12 @@ export function Sidebar({
{/* Resize handle on right border */}
{!isCollapsed && onResizeMouseDown && (
<div
role="slider"
aria-label="Resize sidebar"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={50}
tabIndex={0}
onMouseDown={onResizeMouseDown}
className="absolute right-0 top-0 h-full w-1 cursor-col-resize hover:bg-border active:bg-border z-10"
/>
@ -215,6 +223,8 @@ export function Sidebar({
name={chat.name}
isActive={chat.id === activeChatId}
archived={chat.archived}
dropdownOpen={openDropdownChatId === chat.id}
onDropdownOpenChange={(open) => setOpenDropdownChatId(open ? chat.id : null)}
onClick={() => onChatSelect(chat)}
onRename={() => onChatRename?.(chat)}
onArchive={() => onChatArchive?.(chat)}
@ -287,6 +297,8 @@ export function Sidebar({
name={chat.name}
isActive={chat.id === activeChatId}
archived={chat.archived}
dropdownOpen={openDropdownChatId === chat.id}
onDropdownOpenChange={(open) => setOpenDropdownChatId(open ? chat.id : null)}
onClick={() => onChatSelect(chat)}
onRename={() => onChatRename?.(chat)}
onArchive={() => onChatArchive?.(chat)}

View file

@ -9,8 +9,8 @@ import {
Laptop,
LogOut,
Moon,
Settings,
Sun,
UserCog,
} from "lucide-react";
import Image from "next/image";
import { useTranslations } from "next-intl";
@ -206,7 +206,7 @@ export function SidebarUserProfile({
<DropdownMenuSeparator className="dark:bg-neutral-700" />
<DropdownMenuItem onClick={onUserSettings}>
<Settings className="h-4 w-4" />
<UserCog className="h-4 w-4" />
{t("user_settings")}
</DropdownMenuItem>
@ -351,7 +351,7 @@ export function SidebarUserProfile({
<DropdownMenuSeparator className="dark:bg-neutral-700" />
<DropdownMenuItem onClick={onUserSettings}>
<Settings className="h-4 w-4" />
<UserCog className="h-4 w-4" />
{t("user_settings")}
</DropdownMenuItem>

View file

@ -154,17 +154,17 @@ export function useGooglePicker({ connectorId, onPicked }: UseGooglePickerOption
}
}
if (action === google.picker.Action.ERROR) {
setError("Google Drive encountered an error. Please try again.");
}
if (action === google.picker.Action.ERROR) {
setError("Google Drive encountered an error. Please try again.");
}
if (
action === google.picker.Action.PICKED ||
action === google.picker.Action.CANCEL ||
action === google.picker.Action.ERROR
) {
closePicker();
}
if (
action === google.picker.Action.PICKED ||
action === google.picker.Action.CANCEL ||
action === google.picker.Action.ERROR
) {
closePicker();
}
})
.build();