improved agent

This commit is contained in:
bracesproul
2025-02-27 14:08:24 -08:00
parent 4e6a831214
commit c7b61071a1
25 changed files with 4438 additions and 2270 deletions

View File

@@ -66,64 +66,151 @@ const useCopyToClipboard = ({
const defaultComponents = memoizeMarkdownComponents({
h1: ({ className, ...props }) => (
<h1 className={cn("mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0", className)} {...props} />
<h1
className={cn(
"mb-8 scroll-m-20 text-4xl font-extrabold tracking-tight last:mb-0",
className,
)}
{...props}
/>
),
h2: ({ className, ...props }) => (
<h2 className={cn("mb-4 mt-8 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0", className)} {...props} />
<h2
className={cn(
"mb-4 mt-8 scroll-m-20 text-3xl font-semibold tracking-tight first:mt-0 last:mb-0",
className,
)}
{...props}
/>
),
h3: ({ className, ...props }) => (
<h3 className={cn("mb-4 mt-6 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0", className)} {...props} />
<h3
className={cn(
"mb-4 mt-6 scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0 last:mb-0",
className,
)}
{...props}
/>
),
h4: ({ className, ...props }) => (
<h4 className={cn("mb-4 mt-6 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0", className)} {...props} />
<h4
className={cn(
"mb-4 mt-6 scroll-m-20 text-xl font-semibold tracking-tight first:mt-0 last:mb-0",
className,
)}
{...props}
/>
),
h5: ({ className, ...props }) => (
<h5 className={cn("my-4 text-lg font-semibold first:mt-0 last:mb-0", className)} {...props} />
<h5
className={cn(
"my-4 text-lg font-semibold first:mt-0 last:mb-0",
className,
)}
{...props}
/>
),
h6: ({ className, ...props }) => (
<h6 className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)} {...props} />
<h6
className={cn("my-4 font-semibold first:mt-0 last:mb-0", className)}
{...props}
/>
),
p: ({ className, ...props }) => (
<p className={cn("mb-5 mt-5 leading-7 first:mt-0 last:mb-0", className)} {...props} />
<p
className={cn("mb-5 mt-5 leading-7 first:mt-0 last:mb-0", className)}
{...props}
/>
),
a: ({ className, ...props }) => (
<a className={cn("text-primary font-medium underline underline-offset-4", className)} {...props} />
<a
className={cn(
"text-primary font-medium underline underline-offset-4",
className,
)}
{...props}
/>
),
blockquote: ({ className, ...props }) => (
<blockquote className={cn("border-l-2 pl-6 italic", className)} {...props} />
<blockquote
className={cn("border-l-2 pl-6 italic", className)}
{...props}
/>
),
ul: ({ className, ...props }) => (
<ul className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)} {...props} />
<ul
className={cn("my-5 ml-6 list-disc [&>li]:mt-2", className)}
{...props}
/>
),
ol: ({ className, ...props }) => (
<ol className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)} {...props} />
<ol
className={cn("my-5 ml-6 list-decimal [&>li]:mt-2", className)}
{...props}
/>
),
hr: ({ className, ...props }) => (
<hr className={cn("my-5 border-b", className)} {...props} />
),
table: ({ className, ...props }) => (
<table className={cn("my-5 w-full border-separate border-spacing-0 overflow-y-auto", className)} {...props} />
<table
className={cn(
"my-5 w-full border-separate border-spacing-0 overflow-y-auto",
className,
)}
{...props}
/>
),
th: ({ className, ...props }) => (
<th className={cn("bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [&[align=center]]:text-center [&[align=right]]:text-right", className)} {...props} />
<th
className={cn(
"bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [&[align=center]]:text-center [&[align=right]]:text-right",
className,
)}
{...props}
/>
),
td: ({ className, ...props }) => (
<td className={cn("border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right", className)} {...props} />
<td
className={cn(
"border-b border-l px-4 py-2 text-left last:border-r [&[align=center]]:text-center [&[align=right]]:text-right",
className,
)}
{...props}
/>
),
tr: ({ className, ...props }) => (
<tr className={cn("m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg", className)} {...props} />
<tr
className={cn(
"m-0 border-b p-0 first:border-t [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg",
className,
)}
{...props}
/>
),
sup: ({ className, ...props }) => (
<sup className={cn("[&>a]:text-xs [&>a]:no-underline", className)} {...props} />
<sup
className={cn("[&>a]:text-xs [&>a]:no-underline", className)}
{...props}
/>
),
pre: ({ className, ...props }) => (
<pre className={cn("overflow-x-auto rounded-b-lg bg-black p-4 text-white", className)} {...props} />
<pre
className={cn(
"overflow-x-auto rounded-b-lg bg-black p-4 text-white",
className,
)}
{...props}
/>
),
code: function Code({ className, ...props }) {
const isCodeBlock = useIsMarkdownCodeBlock();
return (
<code
className={cn(!isCodeBlock && "bg-muted rounded border font-semibold", className)}
className={cn(
!isCodeBlock && "bg-muted rounded border font-semibold",
className,
)}
{...props}
/>
);

View File

@@ -2,8 +2,10 @@ import {
ActionBarPrimitive,
BranchPickerPrimitive,
ComposerPrimitive,
getExternalStoreMessages,
MessagePrimitive,
ThreadPrimitive,
useMessage,
} from "@assistant-ui/react";
import type { FC } from "react";
import {
@@ -17,12 +19,14 @@ import {
SendHorizontalIcon,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui/client";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { MarkdownText } from "@/components/assistant-ui/markdown-text";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { Message } from "@langchain/langgraph-sdk";
import { useStreamContext } from "@/providers/Stream";
export const Thread: FC = () => {
return (
@@ -78,9 +82,7 @@ const ThreadWelcome: FC = () => {
<Avatar>
<AvatarFallback>C</AvatarFallback>
</Avatar>
<p className="mt-4 font-medium">
How can I help you today?
</p>
<p className="mt-4 font-medium">How can I help you today?</p>
</div>
<ThreadWelcomeSuggestions />
</div>
@@ -93,12 +95,12 @@ const ThreadWelcomeSuggestions: FC = () => {
<div className="mt-3 flex w-full items-stretch justify-center gap-4">
<ThreadPrimitive.Suggestion
className="hover:bg-muted/80 flex max-w-sm grow basis-0 flex-col items-center justify-center rounded-lg border p-3 transition-colors ease-in"
prompt="What is the weather in Tokyo?"
prompt="What's the current price of $APPL?"
method="replace"
autoSend
>
<span className="line-clamp-2 text-ellipsis text-sm font-semibold">
What is the weather in Tokyo?
What's the current price of $APPL?
</span>
</ThreadPrimitive.Suggestion>
<ThreadPrimitive.Suggestion
@@ -108,7 +110,7 @@ const ThreadWelcomeSuggestions: FC = () => {
autoSend
>
<span className="line-clamp-2 text-ellipsis text-sm font-semibold">
What is assistant-ui?
What's the weather like in San Francisco Today?
</span>
</ThreadPrimitive.Suggestion>
</div>
@@ -205,13 +207,67 @@ const EditComposer: FC = () => {
);
};
function CustomComponent({
message,
idx,
thread,
}: {
message: Message;
idx: number;
thread: ReturnType<typeof useStreamContext>;
}) {
const meta = thread.getMessagesMetadata(message, idx);
const seenState = meta?.firstSeenState;
const customComponent = seenState?.values.ui
.slice()
.reverse()
.find(
({ additional_kwargs }) =>
additional_kwargs.run_id === seenState.metadata?.run_id,
);
return (
<div key={message.id}>
<pre>{JSON.stringify(message, null, 2)}</pre>
{customComponent && (
<LoadExternalComponent
assistantId="agent"
stream={thread}
message={customComponent}
/>
)}
</div>
);
}
const AssistantMessage: FC = () => {
const thread = useStreamContext();
const assistantMsgs = useMessage((m) => {
const langchainMessage = getExternalStoreMessages<Message>(m);
return langchainMessage;
})?.[0];
let threadMsgIdx: number | undefined = undefined;
const threadMsg = thread.messages.find((m, idx) => {
if (m.id === assistantMsgs?.id) {
threadMsgIdx = idx;
return true;
}
});
return (
<MessagePrimitive.Root className="grid grid-cols-[auto_auto_1fr] grid-rows-[auto_1fr] relative w-full max-w-[var(--thread-max-width)] py-4">
<Avatar className="col-start-1 row-span-full row-start-1 mr-4">
<AvatarFallback>A</AvatarFallback>
</Avatar>
{threadMsg && threadMsgIdx !== undefined && (
<CustomComponent
message={threadMsg}
idx={threadMsgIdx}
thread={thread}
/>
)}
<div className="text-foreground max-w-[calc(var(--thread-max-width)*0.8)] break-words leading-7 col-span-2 col-start-2 row-start-1 my-1.5">
<MessagePrimitive.Content components={{ Text: MarkdownText }} />
</div>
@@ -271,7 +327,10 @@ const BranchPicker: FC<BranchPickerPrimitive.Root.Props> = ({
return (
<BranchPickerPrimitive.Root
hideWhenSingleBranch
className={cn("text-muted-foreground inline-flex items-center text-xs", className)}
className={cn(
"text-muted-foreground inline-flex items-center text-xs",
className,
)}
{...rest}
>
<BranchPickerPrimitive.Previous asChild>

View File

@@ -1,7 +1,7 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
function Avatar({
className,
@@ -12,11 +12,11 @@ function Avatar({
data-slot="avatar"
className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
className
className,
)}
{...props}
/>
)
);
}
function AvatarImage({
@@ -29,7 +29,7 @@ function AvatarImage({
className={cn("aspect-square size-full", className)}
{...props}
/>
)
);
}
function AvatarFallback({
@@ -41,11 +41,11 @@ function AvatarFallback({
data-slot="avatar-fallback"
className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
className
className,
)}
{...props}
/>
)
);
}
export { Avatar, AvatarImage, AvatarFallback }
export { Avatar, AvatarImage, AvatarFallback };

View File

@@ -1,8 +1,8 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
@@ -31,8 +31,8 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
}
)
},
);
function Button({
className,
@@ -42,9 +42,9 @@ function Button({
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
asChild?: boolean;
}) {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? Slot : "button";
return (
<Comp
@@ -52,7 +52,7 @@ function Button({
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
);
}
export { Button, buttonVariants }
export { Button, buttonVariants };

View File

@@ -1,7 +1,7 @@
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
function TooltipProvider({
delayDuration = 0,
@@ -13,7 +13,7 @@ function TooltipProvider({
delayDuration={delayDuration}
{...props}
/>
)
);
}
function Tooltip({
@@ -23,13 +23,13 @@ function Tooltip({
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider>
)
);
}
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
}
function TooltipContent({
@@ -45,7 +45,7 @@ function TooltipContent({
sideOffset={sideOffset}
className={cn(
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit rounded-md px-3 py-1.5 text-xs text-balance",
className
className,
)}
{...props}
>
@@ -53,7 +53,7 @@ function TooltipContent({
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
)
);
}
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };