mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-10 07:38:04 -05:00
chore: refinements frontend
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import React from "react";
|
||||
import { ChatContainer } from "./components/ChatContainer/ChatContainer";
|
||||
import { ChatErrorState } from "./components/ChatErrorState/ChatErrorState";
|
||||
import { ChatLoadingState } from "./components/ChatLoadingState/ChatLoadingState";
|
||||
import { useChat } from "./useChat";
|
||||
|
||||
export interface ChatProps {
|
||||
className?: string;
|
||||
headerTitle?: React.ReactNode;
|
||||
showHeader?: boolean;
|
||||
showSessionInfo?: boolean;
|
||||
showNewChatButton?: boolean;
|
||||
onNewChat?: () => void;
|
||||
headerActions?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Chat({
|
||||
className,
|
||||
headerTitle = "Chat",
|
||||
showHeader = true,
|
||||
showSessionInfo = true,
|
||||
showNewChatButton = true,
|
||||
onNewChat,
|
||||
headerActions,
|
||||
}: ChatProps) {
|
||||
const {
|
||||
messages,
|
||||
isLoading,
|
||||
isCreating,
|
||||
error,
|
||||
sessionId,
|
||||
createSession,
|
||||
clearSession,
|
||||
refreshSession,
|
||||
} = useChat();
|
||||
|
||||
const handleNewChat = () => {
|
||||
clearSession();
|
||||
onNewChat?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("flex h-full flex-col", className)}>
|
||||
{/* Header */}
|
||||
{showHeader && (
|
||||
<header className="shrink-0 border-b border-zinc-200 bg-white p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
{typeof headerTitle === "string" ? (
|
||||
<h2 className="text-xl font-semibold">{headerTitle}</h2>
|
||||
) : (
|
||||
headerTitle
|
||||
)}
|
||||
<div className="flex items-center gap-4">
|
||||
{showSessionInfo && sessionId && (
|
||||
<>
|
||||
<span className="text-sm text-zinc-600">
|
||||
Session: {sessionId.slice(0, 8)}...
|
||||
</span>
|
||||
{showNewChatButton && (
|
||||
<button
|
||||
onClick={handleNewChat}
|
||||
className="text-sm text-zinc-600 hover:text-zinc-900"
|
||||
>
|
||||
New Chat
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{headerActions}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
{/* Loading State - show when explicitly loading/creating OR when we don't have a session yet and no error */}
|
||||
{(isLoading || isCreating || (!sessionId && !error)) && (
|
||||
<ChatLoadingState
|
||||
message={isCreating ? "Creating session..." : "Loading..."}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Error State */}
|
||||
{error && !isLoading && (
|
||||
<ChatErrorState error={error} onRetry={createSession} />
|
||||
)}
|
||||
|
||||
{/* Session Content */}
|
||||
{sessionId && !isLoading && !error && (
|
||||
<ChatContainer
|
||||
sessionId={sessionId}
|
||||
initialMessages={messages}
|
||||
onRefreshSession={refreshSession}
|
||||
className="flex-1"
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -7,10 +7,7 @@ import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
|
||||
import { X } from "@phosphor-icons/react";
|
||||
import { useEffect } from "react";
|
||||
import { Drawer } from "vaul";
|
||||
import { ChatContainer } from "./components/ChatContainer/ChatContainer";
|
||||
import { ChatErrorState } from "./components/ChatErrorState/ChatErrorState";
|
||||
import { ChatLoadingState } from "./components/ChatLoadingState/ChatLoadingState";
|
||||
import { useChat } from "./useChat";
|
||||
import { Chat } from "./Chat";
|
||||
import { useChatDrawer } from "./useChatDrawer";
|
||||
|
||||
interface ChatDrawerProps {
|
||||
@@ -20,16 +17,6 @@ interface ChatDrawerProps {
|
||||
export function ChatDrawer({ blurBackground = true }: ChatDrawerProps) {
|
||||
const isChatEnabled = useGetFlag(Flag.CHAT);
|
||||
const { isOpen, close } = useChatDrawer();
|
||||
const {
|
||||
messages,
|
||||
isLoading,
|
||||
isCreating,
|
||||
error,
|
||||
sessionId,
|
||||
createSession,
|
||||
clearSession,
|
||||
refreshSession,
|
||||
} = useChat();
|
||||
|
||||
useEffect(() => {
|
||||
if (isChatEnabled === false && isOpen) {
|
||||
@@ -68,62 +55,23 @@ export function ChatDrawer({ blurBackground = true }: ChatDrawerProps) {
|
||||
scrollbarStyles,
|
||||
)}
|
||||
>
|
||||
{/* Header */}
|
||||
<header className="shrink-0 border-b border-zinc-200 bg-white p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Chat
|
||||
headerTitle={
|
||||
<Drawer.Title className="text-xl font-semibold">
|
||||
Chat
|
||||
</Drawer.Title>
|
||||
<div className="flex items-center gap-4">
|
||||
{sessionId && (
|
||||
<>
|
||||
<span className="text-sm text-zinc-600">
|
||||
Session: {sessionId.slice(0, 8)}...
|
||||
</span>
|
||||
<button
|
||||
onClick={clearSession}
|
||||
className="text-sm text-zinc-600 hover:text-zinc-900"
|
||||
>
|
||||
New Chat
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
variant="link"
|
||||
aria-label="Close"
|
||||
onClick={close}
|
||||
className="!focus-visible:ring-0 p-0"
|
||||
>
|
||||
<X width="1.5rem" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
{/* Loading State - show when explicitly loading/creating OR when we don't have a session yet and no error */}
|
||||
{(isLoading || isCreating || (!sessionId && !error)) && (
|
||||
<ChatLoadingState
|
||||
message={isCreating ? "Creating session..." : "Loading..."}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Error State */}
|
||||
{error && !isLoading && (
|
||||
<ChatErrorState error={error} onRetry={createSession} />
|
||||
)}
|
||||
|
||||
{/* Session Content */}
|
||||
{sessionId && !isLoading && !error && (
|
||||
<ChatContainer
|
||||
sessionId={sessionId}
|
||||
initialMessages={messages}
|
||||
onRefreshSession={refreshSession}
|
||||
className="flex-1"
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
}
|
||||
headerActions={
|
||||
<Button
|
||||
variant="link"
|
||||
aria-label="Close"
|
||||
onClick={close}
|
||||
className="!focus-visible:ring-0 p-0"
|
||||
>
|
||||
<X width="1.5rem" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Drawer.Content>
|
||||
</Drawer.Portal>
|
||||
</Drawer.Root>
|
||||
|
||||
@@ -3,12 +3,14 @@ import { Card } from "@/components/atoms/Card/Card";
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ArrowRight, List, Robot } from "@phosphor-icons/react";
|
||||
import Image from "next/image";
|
||||
|
||||
export interface Agent {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
version?: number;
|
||||
image_url?: string;
|
||||
}
|
||||
|
||||
export interface AgentCarouselMessageProps {
|
||||
@@ -56,8 +58,23 @@ export function AgentCarouselMessage({
|
||||
className="border border-purple-200 bg-white p-4"
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-purple-100">
|
||||
<Robot size={20} weight="bold" className="text-purple-600" />
|
||||
<div className="relative h-10 w-10 flex-shrink-0 overflow-hidden rounded-lg bg-purple-100">
|
||||
{agent.image_url ? (
|
||||
<Image
|
||||
src={agent.image_url}
|
||||
alt={`${agent.name} preview image`}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Robot
|
||||
size={20}
|
||||
weight="bold"
|
||||
className="text-purple-600"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 space-y-2">
|
||||
<div>
|
||||
|
||||
@@ -63,6 +63,7 @@ export function isAgentArray(value: unknown): value is Array<{
|
||||
name: string;
|
||||
description: string;
|
||||
version?: number;
|
||||
image_url?: string;
|
||||
}> {
|
||||
if (!Array.isArray(value)) {
|
||||
return false;
|
||||
@@ -77,7 +78,8 @@ export function isAgentArray(value: unknown): value is Array<{
|
||||
typeof item.name === "string" &&
|
||||
"description" in item &&
|
||||
typeof item.description === "string" &&
|
||||
(!("version" in item) || typeof item.version === "number"),
|
||||
(!("version" in item) || typeof item.version === "number") &&
|
||||
(!("image_url" in item) || typeof item.image_url === "string"),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +1,18 @@
|
||||
import { Text } from "@/components/atoms/Text/Text";
|
||||
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ArrowClockwiseIcon } from "@phosphor-icons/react";
|
||||
|
||||
export interface ChatLoadingStateProps {
|
||||
message?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function ChatLoadingState({
|
||||
message = "Loading...",
|
||||
className,
|
||||
}: ChatLoadingStateProps) {
|
||||
export function ChatLoadingState({ className }: ChatLoadingStateProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn("flex flex-1 items-center justify-center p-6", className)}
|
||||
>
|
||||
<div className="flex flex-col items-center gap-4 text-center">
|
||||
<ArrowClockwiseIcon
|
||||
size={32}
|
||||
weight="bold"
|
||||
className="animate-spin text-purple-500"
|
||||
/>
|
||||
<Text variant="body" className="text-zinc-600">
|
||||
{message}
|
||||
</Text>
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import type { ToolArguments, ToolResult } from "@/types/chat";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
|
||||
export type ChatMessageData =
|
||||
| {
|
||||
@@ -65,6 +65,7 @@ export type ChatMessageData =
|
||||
name: string;
|
||||
description: string;
|
||||
version?: number;
|
||||
image_url?: string;
|
||||
}>;
|
||||
totalCount?: number;
|
||||
timestamp?: string | Date;
|
||||
|
||||
@@ -20,26 +20,71 @@ export function QuickActionsWelcome({
|
||||
}: QuickActionsWelcomeProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn("flex flex-1 items-center justify-center p-4", className)}
|
||||
className={cn("flex flex-1 items-center justify-center p-8", className)}
|
||||
>
|
||||
<div className="max-w-2xl text-center">
|
||||
<Text variant="h2" className="mb-4 text-3xl font-bold text-zinc-900">
|
||||
{title}
|
||||
</Text>
|
||||
<Text variant="body" className="mb-8 text-zinc-600">
|
||||
{description}
|
||||
</Text>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{actions.map((action) => (
|
||||
<button
|
||||
key={action}
|
||||
onClick={() => onActionClick(action)}
|
||||
disabled={disabled}
|
||||
className="rounded-lg border border-zinc-200 bg-white p-4 text-left text-sm hover:bg-zinc-50 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{action}
|
||||
</button>
|
||||
))}
|
||||
<div className="w-full max-w-3xl">
|
||||
<div className="mb-12 text-center">
|
||||
<Text
|
||||
variant="h2"
|
||||
className="mb-3 text-2xl font-semibold text-zinc-900"
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
<Text variant="body" className="text-zinc-500">
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
{actions.map((action) => {
|
||||
// Use slate theme for all cards
|
||||
const theme = {
|
||||
bg: "bg-slate-50/30",
|
||||
border: "border-slate-200",
|
||||
hoverBg: "hover:bg-slate-100",
|
||||
hoverBorder: "hover:border-slate-200",
|
||||
gradient: "from-slate-300/20 via-slate-400/10 to-transparent",
|
||||
text: "text-slate-900",
|
||||
hoverText: "group-hover:text-slate-900",
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
key={action}
|
||||
onClick={() => onActionClick(action)}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"group relative overflow-hidden rounded-xl border p-5 text-left backdrop-blur-xl",
|
||||
"transition-all duration-200",
|
||||
theme.bg,
|
||||
theme.border,
|
||||
theme.hoverBg,
|
||||
theme.hoverBorder,
|
||||
"hover:shadow-sm",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/50 focus-visible:ring-offset-2",
|
||||
"disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:shadow-none",
|
||||
)}
|
||||
>
|
||||
{/* Gradient flare background */}
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 bg-gradient-to-br",
|
||||
theme.gradient,
|
||||
)}
|
||||
/>
|
||||
|
||||
<Text
|
||||
variant="body"
|
||||
className={cn(
|
||||
"relative z-10 font-medium",
|
||||
theme.text,
|
||||
theme.hoverText,
|
||||
)}
|
||||
>
|
||||
{action}
|
||||
</Text>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user