Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/react-ui/src/components/BottomTray/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface ContainerProps {
logoUrl: string;
agentName: string;
className?: string;
showAssistantLogo?: boolean;
/** Control the open state of the tray */
isOpen?: boolean;
}
Expand All @@ -16,10 +17,15 @@ export const Container = ({
logoUrl,
agentName,
className,
showAssistantLogo = false,
isOpen = false,
}: ContainerProps) => {
return (
<ShellStoreProvider logoUrl={logoUrl} agentName={agentName}>
<ShellStoreProvider
logoUrl={logoUrl}
agentName={agentName}
showAssistantLogo={showAssistantLogo}
>
<LayoutContextProvider layout="tray">
<div
className={clsx(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useThread } from "@openuidev/react-headless";
import clsx from "clsx";
import { ArrowUp, Lightbulb } from "lucide-react";
import { Fragment, ReactNode } from "react";
import { Fragment, ReactNode, isValidElement } from "react";
import { useComposerState } from "../../hooks/useComposerState";
import { ConversationStarterIcon, ConversationStarterProps } from "../../types/ConversationStarter";
import { Carousel, CarouselContent } from "../Carousel";
import { isChatEmpty } from "../_shared/utils";
import { Separator } from "../Separator";

Expand All @@ -25,6 +27,18 @@ const renderIcon = (icon: ConversationStarterIcon | undefined): ReactNode => {
return icon;
};

const hasRenderableIcon = (icon: ReactNode): boolean => {
if (icon === null || icon === undefined || icon === false) {
return false;
}

if (isValidElement(icon) && icon.type === Fragment) {
return Boolean(icon.props.children);
}

return true;
};

const ConversationStarterItem = ({
displayText,
prompt,
Expand All @@ -33,6 +47,7 @@ const ConversationStarterItem = ({
icon,
}: ConversationStarterItemProps) => {
const renderedIcon = renderIcon(icon);
const shouldRenderIcon = hasRenderableIcon(renderedIcon);

if (variant === "short") {
return (
Expand All @@ -41,7 +56,7 @@ const ConversationStarterItem = ({
className="openui-bottom-tray-conversation-starter-item-short"
onClick={() => onClick(prompt)}
>
{renderedIcon && (
{shouldRenderIcon && (
<span className="openui-bottom-tray-conversation-starter-item-short__icon">
{renderedIcon}
</span>
Expand All @@ -61,7 +76,7 @@ const ConversationStarterItem = ({
onClick={() => onClick(prompt)}
>
<div className="openui-bottom-tray-conversation-starter-item-long__content">
{renderedIcon && (
{shouldRenderIcon && (
<span className="openui-bottom-tray-conversation-starter-item-long__icon">
{renderedIcon}
</span>
Expand Down Expand Up @@ -93,10 +108,12 @@ export const ConversationStarter = ({
className,
variant = "short",
}: ConversationStarterContainerProps) => {
const { textContent } = useComposerState();
const processMessage = useThread((s) => s.processMessage);
const isRunning = useThread((s) => s.isRunning);
const messages = useThread((s) => s.messages);
const isLoadingMessages = useThread((s) => s.isLoadingMessages);
const isDrafting = textContent.length > 0;

const handleClick = (prompt: string) => {
if (isRunning) return;
Expand All @@ -115,11 +132,43 @@ export const ConversationStarter = ({
return null;
}

if (variant === "short") {
return (
<Carousel
showButtons={false}
className={clsx(
"openui-bottom-tray-conversation-starter",
"openui-bottom-tray-conversation-starter--short",
{
"openui-bottom-tray-conversation-starter--hidden": isDrafting,
},
className,
)}
>
<CarouselContent className="openui-bottom-tray-conversation-starter__carousel-content">
{starters.map((item, index) => (
<ConversationStarterItem
key={`${item.displayText}-${index}`}
displayText={item.displayText}
prompt={item.prompt}
icon={item.icon}
onClick={handleClick}
variant={variant}
/>
))}
</CarouselContent>
</Carousel>
);
}

return (
<div
className={clsx(
"openui-bottom-tray-conversation-starter",
`openui-bottom-tray-conversation-starter--${variant}`,
{
"openui-bottom-tray-conversation-starter--hidden": isDrafting,
},
className,
)}
>
Expand Down
23 changes: 12 additions & 11 deletions packages/react-ui/src/components/BottomTray/Thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AssistantMessage, Message, ToolMessage } from "@openuidev/react-he
import { MessageProvider, useThread } from "@openuidev/react-headless";
import clsx from "clsx";
import React, { memo, useRef } from "react";
import { ComposerStateProvider } from "../../hooks/useComposerState";
import { ScrollVariant, useScrollToBottom } from "../../hooks/useScrollToBottom";
import { ArtifactOverlay } from "../_shared/artifact";
import type { AssistantMessageComponent, UserMessageComponent } from "../_shared/types";
Expand All @@ -20,15 +21,17 @@ export const ThreadContainer = ({
const isLoadingMessages = useThread((s) => s.isLoadingMessages);

return (
<div
className={clsx("openui-bottom-tray-thread-container", className)}
style={{
visibility: isLoadingMessages ? "hidden" : undefined,
}}
>
{children}
<ArtifactOverlay />
</div>
<ComposerStateProvider>
<div
className={clsx("openui-bottom-tray-thread-container", className)}
style={{
visibility: isLoadingMessages ? "hidden" : undefined,
}}
>
{children}
<ArtifactOverlay />
</div>
</ComposerStateProvider>
);
};

Expand Down Expand Up @@ -79,8 +82,6 @@ export const ScrollArea = ({
>
{children}
</div>
{/* Gradient to hide the bottom of the scroll area */}
<div className="openui-bottom-tray-thread-scroll-gradient" />
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import clsx from "clsx";
import { EllipsisVerticalIcon, MenuIcon, Trash2Icon } from "lucide-react";
import { useEffect } from "react";
import { Button } from "../Button";
import { IconButton } from "../IconButton";

const ThreadItem = ({
Expand All @@ -27,9 +28,13 @@ const ThreadItem = ({
</button>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button className="openui-bottom-tray-thread-item-menu-trigger">
<EllipsisVerticalIcon size={14} />
</button>
<IconButton
icon={<EllipsisVerticalIcon size="1em" />}
aria-label={`More actions for ${title}`}
variant="tertiary"
size="extra-small"
className="openui-bottom-tray-thread-item-menu-trigger"
/>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
Expand All @@ -39,14 +44,22 @@ const ThreadItem = ({
sideOffset={4}
>
<DropdownMenu.Item
className="openui-bottom-tray-thread-item-menu-action"
asChild
onSelect={(e) => {
e.stopPropagation();
onDelete();
}}
>
<Trash2Icon size={14} className="openui-bottom-tray-thread-item-menu-icon" />
Delete
<Button
type="button"
variant="tertiary"
buttonType="destructive"
size="small"
iconLeft={<Trash2Icon size={14} />}
className="openui-bottom-tray-thread-item-menu-action"
>
Delete
</Button>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
Expand All @@ -64,7 +77,7 @@ export const ThreadListContainer = () => {

useEffect(() => {
loadThreads();
}, []);
}, [loadThreads]);

return (
<DropdownMenu.Root>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const Composer = ({ className, placeholder = "Type your message..." }: Co
<IconButton
onClick={isRunning ? cancelMessage : handleSubmit}
icon={isRunning ? <Square size="1em" fill="currentColor" /> : <ArrowUp size="1em" />}
size="medium"
size="extra-small"
variant="primary"
className="openui-bottom-tray-thread-composer__submit-button"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
@use "../../../cssUtils" as cssUtils;

.openui-bottom-tray-thread-composer {
box-sizing: border-box;
flex-shrink: 0;
width: 100%;
padding: 0 cssUtils.$space-s cssUtils.$space-s;
margin: 0 0 cssUtils.$space-m;
padding: 0 cssUtils.$space-m;

&__input-wrapper {
background-color: cssUtils.$foreground;
border: 1px solid cssUtils.$border-interactive;
border-radius: cssUtils.$radius-l;
box-shadow: cssUtils.$shadow-s;
border-radius: cssUtils.$radius-2xl;
box-shadow: cssUtils.$shadow-m;
overflow: clip;
display: flex;
flex-direction: column;
gap: cssUtils.$space-s;
padding: cssUtils.$space-s;
padding: cssUtils.$space-s-m;
}

&__input {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-ui/src/components/BottomTray/container.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
clip-path 0.25s ease-in-out;

border: 1px solid cssUtils.$border-default;
border-radius: cssUtils.$radius-2xl cssUtils.$radius-2xl cssUtils.$radius-3xl cssUtils.$radius-3xl;
border-radius: cssUtils.$radius-4xl;
box-shadow: cssUtils.$shadow-2xl;

background: cssUtils.$chat-container-bg;
Expand Down
Loading
Loading