From b525c56c264cb0d10b8679f3352f9d3c10867f65 Mon Sep 17 00:00:00 2001 From: marko-kraemer Date: Mon, 21 Apr 2025 15:59:20 +0100 Subject: [PATCH] mobile responsive v1 --- .../src/app/(dashboard)/dashboard/page.tsx | 26 +++ .../components/thread/thread-site-header.tsx | 18 +- .../thread/tool-call-side-panel.tsx | 211 ++++++++++++++---- 3 files changed, 209 insertions(+), 46 deletions(-) diff --git a/frontend/src/app/(dashboard)/dashboard/page.tsx b/frontend/src/app/(dashboard)/dashboard/page.tsx index 2bfc6b8e..09df65fd 100644 --- a/frontend/src/app/(dashboard)/dashboard/page.tsx +++ b/frontend/src/app/(dashboard)/dashboard/page.tsx @@ -3,9 +3,14 @@ import React, { useState, Suspense, useEffect } from 'react'; import { Skeleton } from "@/components/ui/skeleton"; import { useRouter } from 'next/navigation'; +import { Menu } from "lucide-react"; import { ChatInput } from '@/components/thread/chat-input'; import { createProject, addUserMessage, startAgent, createThread } from "@/lib/api"; import { generateThreadName } from "@/lib/actions/threads"; +import { useIsMobile } from "@/hooks/use-mobile"; +import { useSidebar } from "@/components/ui/sidebar"; +import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; // Constant for localStorage key to ensure consistency const PENDING_PROMPT_KEY = 'pendingAgentPrompt'; @@ -15,6 +20,8 @@ function DashboardContent() { const [isSubmitting, setIsSubmitting] = useState(false); const [autoSubmit, setAutoSubmit] = useState(false); const router = useRouter(); + const isMobile = useIsMobile(); + const { setOpenMobile } = useSidebar(); const handleSubmit = async (message: string, options?: { model_name?: string; enable_thinking?: boolean }) => { if (!message.trim() || isSubmitting) return; @@ -84,6 +91,25 @@ function DashboardContent() { return (
+ {isMobile && ( +
+ + + + + Open menu + +
+ )} +

Hey

diff --git a/frontend/src/components/thread/thread-site-header.tsx b/frontend/src/components/thread/thread-site-header.tsx index 81d7fc45..7af9bed5 100644 --- a/frontend/src/components/thread/thread-site-header.tsx +++ b/frontend/src/components/thread/thread-site-header.tsx @@ -1,7 +1,7 @@ "use client" import { Button } from "@/components/ui/button" -import { FolderOpen, Link, PanelRightOpen, Check, X } from "lucide-react" +import { FolderOpen, Link, PanelRightOpen, Check, X, Menu } from "lucide-react" import { usePathname } from "next/navigation" import { toast } from "sonner" import { @@ -16,6 +16,7 @@ import { updateProject } from "@/lib/api" import { Skeleton } from "@/components/ui/skeleton" import { useIsMobile } from "@/hooks/use-mobile" import { cn } from "@/lib/utils" +import { useSidebar } from "@/components/ui/sidebar" interface ThreadSiteHeaderProps { threadId: string @@ -41,6 +42,7 @@ export function SiteHeader({ const [editName, setEditName] = useState(projectName) const inputRef = useRef(null) const isMobile = useIsMobile() || isMobileView + const { setOpenMobile } = useSidebar() const copyCurrentUrl = () => { const url = window.location.origin + pathname @@ -106,9 +108,21 @@ export function SiteHeader({ return (
+ {isMobile && ( + + )} +
{isEditing ? (
diff --git a/frontend/src/components/thread/tool-call-side-panel.tsx b/frontend/src/components/thread/tool-call-side-panel.tsx index 0f1fbaad..3ea855d8 100644 --- a/frontend/src/components/thread/tool-call-side-panel.tsx +++ b/frontend/src/components/thread/tool-call-side-panel.tsx @@ -5,7 +5,7 @@ import { getToolIcon } from "@/components/thread/utils"; import React from "react"; import { Slider } from "@/components/ui/slider"; import { ApiMessageType } from '@/components/thread/types'; -import { CircleDashed, X } from "lucide-react"; +import { CircleDashed, X, ChevronLeft, ChevronRight } from "lucide-react"; import { cn } from "@/lib/utils"; import { useIsMobile } from "@/hooks/use-mobile"; import { Button } from "@/components/ui/button"; @@ -267,13 +267,47 @@ export function ToolCallSidePanel({ return () => clearInterval(interval); }, [isStreaming]); + // Handle navigation with safety checks + const navigateToPrevious = React.useCallback(() => { + if (currentIndex > 0) { + onNavigate(currentIndex - 1); + } + }, [currentIndex, onNavigate]); + + const navigateToNext = React.useCallback(() => { + if (currentIndex < totalCalls - 1) { + onNavigate(currentIndex + 1); + } + }, [currentIndex, totalCalls, onNavigate]); + if (!isOpen) return null; const renderContent = () => { if (!currentToolCall) { return ( -
-

No tool call details available.

+
+ {/* Always show header with close button for empty state */} +
+
+
+

Suna's Computer

+
+ + +
+
+ + {/* Empty state message */} +
+

No tool call details available.

+
); } @@ -293,7 +327,34 @@ export function ToolCallSidePanel({ isStreaming ); - if (!toolView) return null; + if (!toolView) { + return ( +
+ {/* Header with close button even when no tool view */} +
+
+
+

Suna's Computer

+
+ + +
+
+ + {/* Error state message */} +
+

Unable to display tool details.

+
+
+ ); + } return (
@@ -303,17 +364,6 @@ export function ToolCallSidePanel({

Suna's Computer

- {isMobile && ( - - )} - {currentToolCall.toolResult?.content && !isStreaming && (
@@ -333,17 +383,53 @@ export function ToolCallSidePanel({ )}> {isSuccess ? 'Success' : 'Failed'}
+ + {/* Add close button */} +
)} {isStreaming && ( -
- - Running +
+
+ + Running +
+ + {/* Add close button */} +
)} + + {/* Show close button when no status is available */} + {!currentToolCall.toolResult?.content && !isStreaming && ( + + )}
+ + {/* Content area */}
{toolView}
@@ -353,42 +439,79 @@ export function ToolCallSidePanel({ return (
-
+
{renderContent()}
+ + {/* Navigation controls */} {totalCalls > 1 && ( -
-
-
-
- -
- - {currentToolName} {isStreaming && `(Running${dots})`} - -
+
+ {!isMobile && ( +
+
+
+ +
+ + {currentToolName} {isStreaming && `(Running${dots})`} + +
- - Step {currentIndex + 1} of {totalCalls} - -
- onNavigate(newValue)} - className="w-full [&>span:first-child]:h-1 [&>span:first-child]:bg-zinc-200 dark:[&>span:first-child]:bg-zinc-800 [&>span:first-child>span]:bg-zinc-500 dark:[&>span:first-child>span]:bg-zinc-400 [&>span:first-child>span]:h-1" - /> + + Step {currentIndex + 1} of {totalCalls} + +
+ )} + + {isMobile ? ( +
+ + + + {currentIndex + 1} / {totalCalls} + + + +
+ ) : ( + onNavigate(newValue)} + className="w-full [&>span:first-child]:h-1 [&>span:first-child]:bg-zinc-200 dark:[&>span:first-child]:bg-zinc-800 [&>span:first-child>span]:bg-zinc-500 dark:[&>span:first-child>span]:bg-zinc-400 [&>span:first-child>span]:h-1" + /> + )}
)} -
); } \ No newline at end of file