Skip to content

Commit

Permalink
cr
Browse files Browse the repository at this point in the history
  • Loading branch information
bracesproul committed Nov 4, 2024
1 parent 47e9cdc commit 1a954d3
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 40 deletions.
38 changes: 38 additions & 0 deletions src/components/assistant-select/create-edit-assistant-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import { Label } from "../ui/label";
import { Input } from "../ui/input";
import { IconSelect } from "./icon-select";
import React from "react";
import { useToast } from "@/hooks/use-toast";

interface CreateEditAssistantDialogProps {
open: boolean;
setOpen: Dispatch<SetStateAction<boolean>>;
userId: string | undefined;
isEditing: boolean;
assistant?: Assistant;
createCustomAssistant: (
Expand All @@ -37,6 +39,7 @@ interface CreateEditAssistantDialogProps {
export function CreateEditAssistantDialog(
props: CreateEditAssistantDialogProps
) {
const { toast } = useToast();
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [iconName, setIconName] = useState<keyof typeof Icons>("User");
Expand All @@ -45,6 +48,41 @@ export function CreateEditAssistantDialog(

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!props.userId) {
toast({
title: "User not found",
variant: "destructive",
duration: 5000,
});
return;
}

const res = await props.createCustomAssistant(
{
name,
description,
iconData: {
iconName,
iconColor,
},
},
props.userId
);

if (res) {
toast({
title: "Assistant created successfully",
duration: 5000,
});
} else {
toast({
title: "Failed to create assistant",
variant: "destructive",
duration: 5000,
});
}

props.setOpen(false);
};

const handleClearState = () => {
Expand Down
68 changes: 48 additions & 20 deletions src/components/assistant-select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,8 @@ import { TooltipIconButton } from "../ui/assistant-ui/tooltip-icon-button";
import { useGraphContext } from "@/contexts/GraphContext";
import { Assistant } from "@langchain/langgraph-sdk";
import { CreateEditAssistantDialog } from "./create-edit-assistant-dialog";

const getIcon = (iconName?: string) => {
if (iconName && Icons[iconName as keyof typeof Icons]) {
return React.createElement(
Icons[iconName as keyof typeof Icons] as React.ElementType
);
}
return React.createElement(Icons.User);
};
import { cn } from "@/lib/utils";
import { getIcon } from "./utils";

function AssistantItem({
assistant,
Expand All @@ -35,23 +28,36 @@ function AssistantItem({
}) {
const isDefault = assistant.metadata?.is_default as boolean | undefined;
const isSelected = assistant.assistant_id === selectedAssistantId;
const metadata = assistant.metadata as Record<string, any>;

return (
<DropdownMenuItem
onClick={onClick}
className="flex items-center justify-start gap-1"
className={cn(
"flex items-center justify-start gap-2",
isSelected && "bg-gray-50"
)}
>
{isSelected && <></>}
{getIcon(assistant.metadata?.iconName as string | undefined)}
<span
style={{ color: metadata?.iconData?.iconColor || "#4b5563" }}
className="flex items-center justify-start w-4 h-4"
>
{getIcon(metadata?.iconData?.iconName as string | undefined)}
</span>
{assistant.name}
{isDefault && (
<span className="text-xs text-gray-500">{"(default)"}</span>
<span className="text-xs text-gray-500 ml-auto">{"(default)"}</span>
)}
{isSelected && <span className="ml-auto"></span>}
</DropdownMenuItem>
);
}

function AssistantSelectComponent() {
interface AssistantSelectProps {
userId: string | undefined;
}

function AssistantSelectComponent(props: AssistantSelectProps) {
const [open, setOpen] = useState(false);
const [createEditDialogOpen, setCreateEditDialogOpen] = useState(false);
const {
Expand All @@ -71,6 +77,8 @@ function AssistantSelectComponent() {
setCreateEditDialogOpen(true);
};

const metadata = selectedAssistant?.metadata as Record<string, any>;

return (
<>
<DropdownMenu open={open} onOpenChange={setOpen}>
Expand All @@ -80,10 +88,9 @@ function AssistantSelectComponent() {
variant="ghost"
delayDuration={200}
className="w-8 h-8 transition-colors ease-in-out duration-200"
style={{ color: metadata?.iconData?.iconColor || "#4b5563" }}
>
{getIcon(
selectedAssistant?.metadata?.iconName as string | undefined
)}
{getIcon(metadata?.iconData?.iconName as string | undefined)}
</TooltipIconButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="max-h-[600px] max-w-[300px] overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300 scrollbar-track-gray-100 ml-4">
Expand All @@ -100,7 +107,7 @@ function AssistantSelectComponent() {
<>
<DropdownMenuItem
onSelect={handleNewAssistantClick}
className="flex items-center justify-start gap-1"
className="flex items-center justify-start gap-2"
>
<Icons.CirclePlus className="w-4 h-4" />
<TighterText className="font-medium">New</TighterText>
Expand Down Expand Up @@ -128,9 +135,30 @@ function AssistantSelectComponent() {
<CreateEditAssistantDialog
open={createEditDialogOpen}
setOpen={setCreateEditDialogOpen}
userId={props.userId}
isEditing={false}
createCustomAssistant={createCustomAssistant}
editCustomAssistant={editCustomAssistant}
createCustomAssistant={async (
newAssistant,
userId,
successCallback
) => {
const res = await createCustomAssistant(
newAssistant,
userId,
successCallback
);
setOpen(false);
return res;
}}
editCustomAssistant={async (editedAssistant, assistantId, userId) => {
const res = await editCustomAssistant(
editedAssistant,
assistantId,
userId
);
setOpen(false);
return res;
}}
isLoading={isLoadingAllAssistants}
/>
</>
Expand Down
11 changes: 11 additions & 0 deletions src/components/assistant-select/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Icons from "lucide-react";
import React from "react";

export const getIcon = (iconName?: string) => {
if (iconName && Icons[iconName as keyof typeof Icons]) {
return React.createElement(
Icons[iconName as keyof typeof Icons] as React.ElementType
);
}
return React.createElement(Icons.User);
};
1 change: 1 addition & 0 deletions src/components/canvas/content-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export function ContentComposerChatInterfaceComponent(
<div className="h-full">
<AssistantRuntimeProvider runtime={runtime}>
<Thread
userId={userData?.user?.id}
setChatStarted={props.setChatStarted}
handleQuickStart={props.handleQuickStart}
hasChatStarted={props.hasChatStarted}
Expand Down
8 changes: 6 additions & 2 deletions src/components/chat-interface/composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ const CircleStopIcon = () => {
);
};

export const Composer: FC = () => {
interface ComposerProps {
userId: string | undefined;
}

export const Composer: FC<ComposerProps> = (props: ComposerProps) => {
return (
<ComposerPrimitive.Root className="focus-within:border-aui-ring/20 flex w-full min-h-[64px] flex-wrap items-center rounded-lg border px-2.5 shadow-sm transition-colors ease-in bg-white">
<AssistantSelect />
<AssistantSelect userId={props.userId} />
<ComposerPrimitive.Input
autoFocus
placeholder="Write a message..."
Expand Down
7 changes: 4 additions & 3 deletions src/components/chat-interface/thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const ThreadScrollToBottom: FC = () => {
};

export interface ThreadProps {
userId: string | undefined;
hasChatStarted: boolean;
handleQuickStart: (
type: "text" | "code",
Expand Down Expand Up @@ -107,15 +108,15 @@ export const Thread: FC<ThreadProps> = (props: ThreadProps) => {
</TooltipIconButton>
) : (
<div className="flex flex-row gap-2 items-center">
<ReflectionsDialog assistantId={selectedAssistant?.assistant_id} />
<ReflectionsDialog selectedAssistant={selectedAssistant} />
</div>
)}
</div>
<ThreadPrimitive.Viewport className="flex-1 overflow-y-auto scroll-smooth bg-inherit px-4 pt-8">
{!hasChatStarted && (
<ThreadWelcome
handleQuickStart={handleQuickStart}
composer={<Composer />}
composer={<Composer userId={props.userId} />}
/>
)}
<ThreadPrimitive.Messages
Expand All @@ -142,7 +143,7 @@ export const Thread: FC<ThreadProps> = (props: ThreadProps) => {
modelName={modelName}
setModelName={setModelName}
/>
<Composer />
<Composer userId={props.userId} />
</div>
)}
</div>
Expand Down
62 changes: 47 additions & 15 deletions src/components/reflections-dialog/ReflectionsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@ import { TooltipIconButton } from "../ui/assistant-ui/tooltip-icon-button";
import { TighterText } from "../ui/header";
import { useStore } from "@/hooks/useStore";
import { useToast } from "@/hooks/use-toast";
import { Assistant } from "@langchain/langgraph-sdk";
import { Badge } from "../ui/badge";
import { getIcon } from "../assistant-select/utils";

export interface NoReflectionsProps {
assistantId: string | undefined;
selectedAssistant: Assistant | undefined;
getReflections: (assistantId: string) => Promise<void>;
}

function NoReflections(props: NoReflectionsProps) {
const { assistantId } = props;
const { selectedAssistant } = props;
const { toast } = useToast();

const getReflections = async () => {
if (!assistantId) {
if (!selectedAssistant) {
toast({
title: "Error",
description: "Assistant ID not found.",
Expand All @@ -34,7 +37,7 @@ function NoReflections(props: NoReflectionsProps) {
});
return;
}
await props.getReflections(assistantId);
await props.getReflections(selectedAssistant.assistant_id);
};

return (
Expand All @@ -52,13 +55,13 @@ function NoReflections(props: NoReflectionsProps) {
}

interface ReflectionsDialogProps {
assistantId: string | undefined;
selectedAssistant: Assistant | undefined;
}

export function ReflectionsDialog(props: ReflectionsDialogProps) {
const { toast } = useToast();
const [open, setOpen] = useState(false);
const { assistantId } = props;
const { selectedAssistant } = props;
const {
isLoadingReflections,
reflections,
Expand All @@ -67,19 +70,19 @@ export function ReflectionsDialog(props: ReflectionsDialogProps) {
} = useStore();

useEffect(() => {
if (!assistantId || typeof window === "undefined") return;
if (!selectedAssistant || typeof window === "undefined") return;
// Don't re-fetch reflections if they already exist & are for the same assistant
if (
(reflections?.content || reflections?.styleRules) &&
reflections.assistantId === assistantId
reflections.assistantId === selectedAssistant.assistant_id
)
return;

getReflections(assistantId);
}, [assistantId]);
getReflections(selectedAssistant.assistant_id);
}, [selectedAssistant]);

const handleDelete = async () => {
if (!assistantId) {
if (!selectedAssistant) {
toast({
title: "Error",
description: "Assistant ID not found.",
Expand All @@ -89,9 +92,12 @@ export function ReflectionsDialog(props: ReflectionsDialogProps) {
return false;
}
setOpen(false);
return await deleteReflections(assistantId);
return await deleteReflections(selectedAssistant.assistant_id);
};

const iconData = (selectedAssistant?.metadata as Record<string, any>)
?.iconData;

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
Expand All @@ -106,8 +112,34 @@ export function ReflectionsDialog(props: ReflectionsDialogProps) {
</DialogTrigger>
<DialogContent className="max-w-xl p-8 bg-white rounded-lg shadow-xl">
<DialogHeader>
<DialogTitle className="text-3xl font-light text-gray-800">
<TighterText>Reflections</TighterText>
<DialogTitle className="flex items-center justify-between">
<TighterText className="text-3xl font-light text-gray-800">
Reflections
</TighterText>
{selectedAssistant && (
<Badge
style={{
...(iconData
? {
color: iconData.iconColor,
backgroundColor: `${iconData.iconColor}20`, // 33 in hex is ~20% opacity
}
: {
color: "#000000",
backgroundColor: "#00000020",
}),
}}
className="flex items-center justify-center gap-2 px-2 py-1"
>
<span className="flex items-center justify-start w-4 h-4">
{getIcon(
(selectedAssistant?.metadata as Record<string, any>)
?.iconData?.iconName
)}
</span>
{selectedAssistant?.name}
</Badge>
)}
</DialogTitle>
<DialogDescription className="mt-2 text-md font-light text-gray-600">
<TighterText>
Expand All @@ -117,7 +149,7 @@ export function ReflectionsDialog(props: ReflectionsDialogProps) {
"Current reflections generated by the assistant for content generation."
) : (
<NoReflections
assistantId={assistantId}
selectedAssistant={selectedAssistant}
getReflections={getReflections}
/>
)}
Expand Down
Loading

0 comments on commit 1a954d3

Please sign in to comment.