Skip to content
Merged
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
20 changes: 19 additions & 1 deletion apps/web/app/(app)/[emailAccountId]/automation/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,24 @@ const commonPrompts = [

const common = `${commonPrompts.map((prompt) => `* ${prompt}`).join(".\n")}.`;

export const initialChatExamples = [
{
label: "Label all pitch decks and investor updates",
message:
"When I get an email with a pitch deck or investor update, label it as 'Pitch Deck'",
},
{
label: "Respond to sponsorship inquiries with my pricing",
message:
"When I get an email with a sponsorship inquiry, respond with the link to my pricing deck: https://www.example.com/pricing-deck",
},
{
label: "Forward all receipts to my accountant",
message:
"When I get an email with a receipt, forward it to my accountant: jane@example.com",
},
];

export const examplePrompts = [
...commonPrompts,
'Label pitch decks as "Pitch Deck" and forward them to john@investing.com',
Expand All @@ -33,7 +51,7 @@ export const examplePrompts = [
"If a founder asks to set up a call, send them my calendar link: https://cal.com/example",
"If someone asks to cancel a plan, ask to set up a call by sending my calendar link",
'If a founder sends me an investor update, label it "Investor Update" and archive it',
'If someone pitches me their startup, label it as "Investing", archive it, and respond with a friendly reply that I no longer have time to look at the email but if they get a warm intro, that\'s their best bet to get funding from me',
'If someone pitches me their startup, label it as "Investing", archive it, and draft a friendly reply that I no longer have time to look at the email but if they get a warm intro, that\'s their best bet to get funding from me',
"If someone asks for a discount, reply with the discount code INBOX20",
"If someone asks for help with Product or Company, tell them I no longer work there, but they should reach out to Company for support",
"Review any emails from questions@pr.com and see if any are about finance. If so, draft a friendly reply that answers the question",
Expand Down
47 changes: 34 additions & 13 deletions apps/web/components/assistant-chat/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { type ScopedMutator, SWRConfig, useSWRConfig } from "swr";
import type { UIMessage } from "ai";
import { useChat } from "@ai-sdk/react";
import { toast } from "sonner";
import { HistoryIcon, Loader2 } from "lucide-react";
import { HistoryIcon, Loader2, PlusIcon } from "lucide-react";
import { useQueryState } from "nuqs";
import { MultimodalInput } from "@/components/assistant-chat/multimodal-input";
import { Messages } from "./messages";
Expand All @@ -29,6 +29,8 @@ import { LoadingContent } from "@/components/LoadingContent";
import { useChatMessages } from "@/hooks/useChatMessages";
import type { GetChatResponse } from "@/app/api/chats/[chatId]/route";
import { useIsMobile } from "@/hooks/use-mobile";
import { ExamplesDialog } from "@/components/assistant-chat/examples-dialog";
import { Tooltip } from "@/components/Tooltip";

// Some mega hacky code used here to workaround AI SDK's use of SWR
// AI SDK uses SWR too and this messes with the global SWR config
Expand Down Expand Up @@ -140,7 +142,9 @@ function ChatUI({

return (
<div className="flex h-full min-w-0 flex-col bg-background">
<div className="flex items-center justify-end px-2 pt-2">
<div className="flex items-center justify-end gap-1 px-2 pt-2">
<NewChatButton />
<ExamplesDialog setInput={setInput} />
<SWRProvider>
<ChatHistoryDropdown />
</SWRProvider>
Expand Down Expand Up @@ -188,24 +192,41 @@ function ChatUI({
);
}

function NewChatButton() {
const [_chatId, setChatId] = useQueryState("chatId");

const handleNewChat = () => setChatId(null);

return (
<Tooltip content="Start a new conversation">
<Button variant="ghost" size="icon" onClick={handleNewChat}>
<PlusIcon className="size-5" />
<span className="sr-only">New Chat</span>
</Button>
</Tooltip>
);
}

function ChatHistoryDropdown() {
const [_chatId, setChatId] = useQueryState("chatId");
const [shouldLoadChats, setShouldLoadChats] = useState(false);
const { data, error, isLoading, mutate } = useChats(shouldLoadChats);

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
onMouseEnter={() => setShouldLoadChats(true)}
onClick={() => mutate()}
>
<HistoryIcon className="size-5" />
<span className="sr-only">Chat History</span>
</Button>
</DropdownMenuTrigger>
<Tooltip content="View previous conversations">
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
onMouseEnter={() => setShouldLoadChats(true)}
onClick={() => mutate()}
>
<HistoryIcon className="size-5" />
<span className="sr-only">Chat History</span>
</Button>
</DropdownMenuTrigger>
</Tooltip>
<DropdownMenuContent align="end">
<LoadingContent
loading={isLoading}
Expand Down
86 changes: 86 additions & 0 deletions apps/web/components/assistant-chat/examples-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
"use client";

import { useState } from "react";
import type { UseChatHelpers } from "@ai-sdk/react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { ScrollArea } from "@/components/ui/scroll-area";
import { LightbulbIcon } from "lucide-react";
import {
examplePrompts,
initialChatExamples,
} from "@/app/(app)/[emailAccountId]/automation/examples";
import { Tooltip } from "@/components/Tooltip";

interface ExamplesDialogProps {
setInput: UseChatHelpers["setInput"];
children?: React.ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}

export function ExamplesDialog({
setInput,
children,
open,
onOpenChange,
}: ExamplesDialogProps) {
const [internalOpen, setInternalOpen] = useState(false);

const isOpen = open !== undefined ? open : internalOpen;
const setIsOpen = onOpenChange || setInternalOpen;

const handleExampleClick = (prompt: string) => {
setInput(prompt);
setIsOpen(false);
};

const allExamples = [
...initialChatExamples.map((example) => example.message),
...examplePrompts,
];

return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Tooltip content="Choose from examples">
{children ? (
<DialogTrigger asChild>{children}</DialogTrigger>
) : (
<DialogTrigger asChild>
<Button variant="ghost" size="icon">
<LightbulbIcon className="size-5" />
<span className="sr-only">Show Examples</span>
</Button>
</DialogTrigger>
)}
</Tooltip>
<DialogContent className="max-h-[80vh] max-w-2xl">
<DialogHeader>
<DialogTitle>Email Automation Examples</DialogTitle>
</DialogHeader>
<ScrollArea className="max-h-[60vh] pr-4">
<div className="space-y-3">
{allExamples.map((example, index) => (
<Button
key={index}
variant="outline"
className="h-auto min-h-[2.5rem] w-full justify-start text-wrap px-4 py-3 text-left text-sm leading-relaxed"
onClick={() => handleExampleClick(example)}
>
<span className="whitespace-normal text-wrap break-words">
{example}
</span>
</Button>
))}
</div>
</ScrollArea>
</DialogContent>
</Dialog>
);
}
31 changes: 15 additions & 16 deletions apps/web/components/assistant-chat/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { UseChatHelpers } from "@ai-sdk/react";
import { MessageIcon } from "./icons";
import { Button } from "@/components/ui/button";
import { MessageText, TypographyH3 } from "@/components/Typography";
import { ExamplesDialog } from "./examples-dialog";
import { initialChatExamples } from "@/app/(app)/[emailAccountId]/automation/examples";

export const Overview = ({
setInput,
Expand Down Expand Up @@ -32,21 +34,18 @@ export const Overview = ({
</MessageText>

<div className="flex flex-col gap-3 pt-8">
<OverviewButton
label="Label all pitch decks and investor updates"
message="When I get an email with a pitch deck or investor update, label it as 'Pitch Deck'"
setInput={setInput}
/>
<OverviewButton
label="Respond to sponsorship inquiries with my pricing"
message="When I get an email with a sponsorship inquiry, respond with the link to my pricing deck: https://www.example.com/pricing-deck"
setInput={setInput}
/>
<OverviewButton
label="Forward all receipts to my accountant"
message="When I get an email with a receipt, forward it to my accountant: jane@example.com"
setInput={setInput}
/>
{initialChatExamples.map((example) => (
<OverviewButton
key={example.label}
label={example.label}
message={example.message}
setInput={setInput}
/>
))}

<ExamplesDialog setInput={setInput}>
<Button variant="ghost">Show more examples</Button>
</ExamplesDialog>
</div>
</div>
</motion.div>
Expand All @@ -65,7 +64,7 @@ function OverviewButton({
return (
<Button
variant="outline"
className="justify-start text-left"
className="h-auto justify-start text-wrap py-3 text-left"
onClick={() => {
setInput(message);
}}
Expand Down