Skip to content

Commit

Permalink
cleanup chat ui
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianP8701 committed Sep 23, 2024
1 parent 27b3a54 commit 379de08
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 137 deletions.
11 changes: 6 additions & 5 deletions client/src/components/custom/DialogPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { ReactElement } from "react";
import React, { ReactElement } from "react";
import { Dialog, DialogTrigger, DialogContent } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Expand } from "lucide-react";

interface DialogPreviewProps {
previewComponent: ReactElement;
dialogContent: ReactElement;
dialogProps?: Record<string, unknown>;
}

export default function DialogPreview({ previewComponent, dialogContent }: DialogPreviewProps) {
export default function DialogPreview({ previewComponent, dialogContent, dialogProps }: DialogPreviewProps) {
return (
<div className="relative w-full h-full bg-slate-800 rounded-xl">
<div className="relative w-full h-full bg-neutral-800 rounded-xl">
<div className="w-full h-full">
{previewComponent}
</div>
Expand All @@ -23,8 +24,8 @@ export default function DialogPreview({ previewComponent, dialogContent }: Dialo
<Expand size={20} />
</Button>
</DialogTrigger>
<DialogContent className="w-[95vw] h-[95vh] max-w-[95vw] p-0 bg-slate-800 rounded-xl">
{dialogContent}
<DialogContent className="w-[95vw] h-[95vh] max-w-[95vw] p-0 rounded-xl bg-neutral-800">
{React.cloneElement(dialogContent, { ...dialogProps, isDialogMode: true })}
</DialogContent>
</Dialog>
</div>
Expand Down
30 changes: 29 additions & 1 deletion client/src/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,34 @@
--radius: 0.5rem;
}

.neutral {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--radius: 0.5rem;
}

.ice {
--background: 182 50% 10%;
--foreground: 182 5% 100%;
Expand Down Expand Up @@ -282,7 +310,7 @@

body {
@apply bg-background text-foreground;
@apply midnight;
@apply neutral;
}
}

Expand Down
22 changes: 5 additions & 17 deletions client/src/views/Chat/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { useState } from "react";
import { Expand, PanelRightOpen, PanelRightClose } from "lucide-react";
import { PanelRightOpen, PanelRightClose } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { ExpandedChat, ChatContent } from "./ChatUtils";
import { ChatContent } from "./ChatContent";
import ChatSidebar from "@/views/Chat/ChatSidebar";
import { SwarmWithDataFragment } from "@/src/graphql/generated/graphql";

export interface ChatProps {
swarm: SwarmWithDataFragment | undefined
isDialogMode?: boolean;
}

export default function Chat({ swarm }: ChatProps) {
export default function Chat({ swarm, isDialogMode }: ChatProps) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const chats = [
{ value: "1", label: "Chat 1" },
Expand All @@ -33,19 +33,7 @@ export default function Chat({ swarm }: ChatProps) {
<div className={`z-20 absolute top-0 left-0 h-full transition-all duration-300 ${isSidebarOpen ? 'w-64' : 'w-0'} overflow-hidden`}>
<ChatSidebar options={chats} onSelect={handleChatChange} />
</div>
<ChatContent swarm={swarm} />
<Dialog>
<DialogTrigger asChild>
<Button className="absolute top-2 right-2 px-1 py-2 z-30" variant="ghost">
<Expand size={20} />
</Button>
</DialogTrigger>
<DialogContent className="w-[95vw] h-[95vh] max-w-full p-0 bg-secondary">
<div className="w-full h-full">
<ExpandedChat chats={chats} onChatChange={handleChatChange} swarm={swarm} />
</div>
</DialogContent>
</Dialog>
<ChatContent swarm={swarm} isDialogMode={isDialogMode} />
</div>
);
}
32 changes: 32 additions & 0 deletions client/src/views/Chat/ChatContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ChatInput } from "./ChatInput";
import { Loader2Icon } from "lucide-react";
import { SwarmWithDataFragment, MessageRoleEnum } from "@/graphql/generated/graphql";
import { ChatMessages } from "./ChatMessages";

export function ChatContent({ swarm, isDialogMode }: { swarm: SwarmWithDataFragment | undefined, isDialogMode?: boolean }) {
const messages = [
{ id: "1", content: "Hello, how are you?", role: MessageRoleEnum.User },
{ id: "2", content: "I'm fine, thank you!", role: MessageRoleEnum.Assistant },
{ id: "3", content: "What is your name?", role: MessageRoleEnum.User },
{ id: "4", content: "The question about context on my mind right now is more of a question of typing. There are two kinds of context: Operational and Node context. At the moment context is stored as a JSON column. The reason for this was because all the arguments being passed between actions became messy. But having unknown types is even worst. We could imagine for each ActionEnum defining a Pydantic type for it's node level context and it's spawn operation context. What about action and termination operations? In the first iteration we decoupled by every blocking operation, which wasn't strictly necessary. Now the only time we consider a node being \"blocked\" is when it needs to perform a search. And for a search node it's when it needs to ask the user questions. Those are two very specific points in the system and other than that I believe we can count on not having to think of blocking operations? Yet that is not true. We can easily imagine there being many more blocking operations and we need a standard way to handle them. So I think we might want to consider bringing back the Blocking Operation. In fact now we only have one Blocking Operation. Performing a search is a Spawn Operation.", role: MessageRoleEnum.Assistant },
{ id: "5", content: "The question about context on my mind right now is more of a question of typing. There are two kinds of context: Operational and Node context. At the moment context is stored as a JSON column. The reason for this was because all the arguments being passed between actions became messy. But having unknown types is even worst. We could imagine for each ActionEnum defining a Pydantic type for it's node level context and it's spawn operation context. What about action and termination operations? In the first iteration we decoupled by every blocking operation, which wasn't strictly necessary. Now the only time we consider a node being \"blocked\" is when it needs to perform a search. And for a search node it's when it needs to ask the user questions. Those are two very specific points in the system and other than that I believe we can count on not having to think of blocking operations? Yet that is not true. We can easily imagine there being many more blocking operations and we need a standard way to handle them. So I think we might want to consider bringing back the Blocking Operation. In fact now we only have one Blocking Operation. Performing a search is a Spawn Operation.", role: MessageRoleEnum.User },
];

return (
<div className="flex flex-col h-full">
{swarm === undefined ? (
<div className="flex-1 flex items-center justify-center">
<Loader2Icon className="animate-spin" size={32} />
</div>
) : (
<div className="flex-1 overflow-y-auto">
<ChatMessages messages={messages} isDialogMode={isDialogMode} />
</div>
)}
<div className="w-full max-w-[800px] mx-auto px-4 pb-4">
<ChatInput />
</div>
</div>
);
}

4 changes: 2 additions & 2 deletions client/src/views/Chat/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ export function ChatInput() {
}, [message])

return (
<div className="relative overflow-hidden rounded-lg bg-background w-full min-h-[48px] max-h-[240px]">
<div className="relative overflow-hidden rounded-3xl bg-background w-full min-h-[48px] max-h-[240px]">
<Textarea
ref={textareaRef}
id="message"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type your message here..."
placeholder=""
className=" resize-none border-0 py-3 pl-3 pr-14 w-full"
/>
<Button
Expand Down
62 changes: 62 additions & 0 deletions client/src/views/Chat/ChatMessages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useEffect, useRef } from "react";
import { MessageRoleEnum } from "@/graphql/generated/graphql";

interface Message {
id: string;
content: string;
role: MessageRoleEnum;
}

interface UserMessageProps {
content: string;
}

const UserMessage: React.FC<UserMessageProps> = ({ content }) => (
<div className="self-center w-[90%] max-w-[700px]">
<div className="flex justify-end">
<div className="inline-block bg-stone-700 rounded-3xl p-3 max-w-[77.78%] text-left">
{content}
</div>
</div>
</div>
);

interface AiMessageProps {
content: string;
}

const AiMessage: React.FC<AiMessageProps> = ({ content }) => (
<div className="self-center w-[90%] max-w-[700px]">
<div className="text-left rounded-3xl p-3">
{content}
</div>
</div>
);

interface ChatMessagesProps {
messages: Message[];
isDialogMode?: boolean;
}

export function ChatMessages({ messages, isDialogMode }: ChatMessagesProps) {
const messagesEndRef = useRef<HTMLDivElement>(null);

useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);

const heightClass = isDialogMode ? "h-[calc(100vh-163px)]" : "h-[calc(100vh-200px)]";

return (
<div className={`flex flex-col justify-start overflow-y-auto mt-10 gap-4 ${heightClass}`}>
{messages.map((message) => (
message.role === MessageRoleEnum.User ? (
<UserMessage key={message.id} content={message.content} />
) : (
<AiMessage key={message.id} content={message.content} />
)
))}
<div ref={messagesEndRef} />
</div>
);
}
108 changes: 0 additions & 108 deletions client/src/views/Chat/ChatUtils.tsx

This file was deleted.

16 changes: 12 additions & 4 deletions client/src/views/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable"
import {
useFetchSwarmLazyQuery,
import {
useFetchSwarmLazyQuery,
SwarmWithDataFragment
} from "../graphql/generated/graphql";
import { TreeVisualizer } from "../components/custom/tree/TreeVisualizer";
Expand Down Expand Up @@ -77,8 +77,16 @@ export default function HomePage() {
<main className="flex-1">
<ResizablePanelGroup direction="horizontal">
<ResizablePanel defaultSize={50} minSize={20}>
<div className="h-full p-4">
<Chat swarm={swarm} />
<div className="h-full p-4">
<DialogPreview
previewComponent={
<Chat swarm={swarm} />
}
dialogContent={
<Chat swarm={swarm} />
}
dialogProps={{ swarm }}
/>
</div>
</ResizablePanel>
<ResizableHandle withHandle fadeStart fadeEnd />
Expand Down

0 comments on commit 379de08

Please sign in to comment.