diff --git a/frontend/src/app/workspace/chats/[thread_id]/layout.tsx b/frontend/src/app/workspace/chats/[thread_id]/layout.tsx new file mode 100644 index 0000000..744493e --- /dev/null +++ b/frontend/src/app/workspace/chats/[thread_id]/layout.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { ArtifactsProvider } from "@/components/workspace/artifacts"; + +export default function ChatLayout({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 25cb8f9..99a40ed 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -1,41 +1,48 @@ "use client"; -import type { UseStream } from "@langchain/langgraph-sdk/react"; +import { FilesIcon, XIcon } from "lucide-react"; import { useParams, useRouter } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { BreadcrumbItem } from "@/components/ui/breadcrumb"; +import { Button } from "@/components/ui/button"; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; +import { useSidebar } from "@/components/ui/sidebar"; import { ArtifactFileDetail, - ArtifactsProvider, useArtifacts, } from "@/components/workspace/artifacts"; +import { FlipDisplay } from "@/components/workspace/flip-display"; import { InputBox } from "@/components/workspace/input-box"; import { MessageList } from "@/components/workspace/messages"; -import { - WorkspaceContainer, - WorkspaceBody, - WorkspaceHeader, -} from "@/components/workspace/workspace-container"; +import { Tooltip } from "@/components/workspace/tooltip"; import { useLocalSettings } from "@/core/settings"; -import { type AgentThread, type AgentThreadState } from "@/core/threads"; +import { type AgentThread } from "@/core/threads"; import { useSubmitThread, useThreadStream } from "@/core/threads/hooks"; import { pathOfThread, titleOfThread } from "@/core/threads/utils"; import { uuid } from "@/core/utils/uuid"; +import { cn } from "@/lib/utils"; +import { ConversationEmptyState } from "@/components/ai-elements/conversation"; export default function ChatPage() { + const router = useRouter(); + const [settings, setSettings] = useLocalSettings(); + const { setOpen: setSidebarOpen } = useSidebar(); + const { + open: artifactsOpen, + setOpen: setArtifactsOpen, + selectedArtifact, + } = useArtifacts(); + const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); const isNewThread = useMemo( () => threadIdFromPath === "new", [threadIdFromPath], ); const [threadId, setThreadId] = useState(null); - useEffect(() => { if (threadIdFromPath !== "new") { setThreadId(threadIdFromPath); @@ -43,44 +50,16 @@ export default function ChatPage() { setThreadId(uuid()); } }, [threadIdFromPath]); + const thread = useThreadStream({ isNewThread, threadId, }); - return ( - - - - {isNewThread - ? "New" - : titleOfThread(thread as unknown as AgentThread)} - - - - - - - - + const title = useMemo( + () => (isNewThread ? "" : titleOfThread(thread as unknown as AgentThread)), + [thread, isNewThread], ); -} -function ThreadDetail({ - threadId, - thread, - isNewThread, -}: { - threadId?: string | null; - thread: UseStream; - isNewThread: boolean; -}) { - const router = useRouter(); - const [settings, setSettings] = useLocalSettings(); - const { open, selectedArtifact } = useArtifacts(); const handleSubmit = useSubmitThread({ isNewThread, threadId, @@ -93,34 +72,106 @@ function ThreadDetail({ const handleStop = useCallback(async () => { await thread.stop(); }, [thread]); + return ( - -
- -
-
- setSettings("context", context)} - onSubmit={handleSubmit} - onStop={handleStop} - /> + +
+
+
+ + {title} + +
+
+ {!artifactsOpen && ( + + + + )} +
+
+
+
+ +
+
+ setSettings("context", context)} + onSubmit={handleSubmit} + onStop={handleStop} + /> +
+
+
+
+ + +
+ +
+
+ {selectedArtifact ? ( + + ) : ( +
+ } + title="No artifact selected" + description="Select an artifact to view its details" + /> +
+ )}
- {open && ( - <> - - - {selectedArtifact && ( - - )} - - - )} ); } diff --git a/frontend/src/app/workspace/layout.tsx b/frontend/src/app/workspace/layout.tsx index 80f3bd9..04e5960 100644 --- a/frontend/src/app/workspace/layout.tsx +++ b/frontend/src/app/workspace/layout.tsx @@ -28,6 +28,7 @@ export default function WorkspaceLayout({ return ( { - setOpen(false); - }, [setOpen]); +export function ArtifactFileDetail({ + className, + filepath, +}: { + className?: string; + filepath: string; +}) { return ( -
-
- -
-
+
+
diff --git a/frontend/src/components/workspace/artifacts/context.tsx b/frontend/src/components/workspace/artifacts/context.tsx index a585870..41a9d72 100644 --- a/frontend/src/components/workspace/artifacts/context.tsx +++ b/frontend/src/components/workspace/artifacts/context.tsx @@ -1,5 +1,7 @@ import { createContext, useContext, useState, type ReactNode } from "react"; +import { useSidebar } from "@/components/ui/sidebar"; + export interface ArtifactsContextType { artifacts: string[]; selectedArtifact: string | null; @@ -23,6 +25,7 @@ export function ArtifactsProvider({ children }: ArtifactsProviderProps) { const [artifacts, setArtifacts] = useState([]); const [selectedArtifact, setSelectedArtifact] = useState(null); const [open, setOpen] = useState(false); + const { setOpen: setSidebarOpen } = useSidebar(); const addArtifacts = (newArtifacts: string[]) => { setArtifacts((prev) => [...prev, ...newArtifacts]); @@ -31,6 +34,7 @@ export function ArtifactsProvider({ children }: ArtifactsProviderProps) { const openArtifact = (artifact: string) => { setSelectedArtifact(artifact); setOpen(true); + setSidebarOpen(false); }; const value: ArtifactsContextType = { diff --git a/frontend/src/components/workspace/messages/message-list.tsx b/frontend/src/components/workspace/messages/message-list.tsx index 07d5655..7bc16b2 100644 --- a/frontend/src/components/workspace/messages/message-list.tsx +++ b/frontend/src/components/workspace/messages/message-list.tsx @@ -32,9 +32,9 @@ export function MessageList({ } return ( - + {groupMessages( thread.messages, (groupedMessages) => {