feat: whyrating - initial project from turbostarter boilerplate
This commit is contained in:
78
apps/web/src/modules/chat/layout/examples.tsx
Normal file
78
apps/web/src/modules/chat/layout/examples.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "motion/react";
|
||||
import { memo } from "react";
|
||||
|
||||
import { useTranslation } from "@turbostarter/i18n";
|
||||
import { cn } from "@turbostarter/ui";
|
||||
import { Button } from "@turbostarter/ui-web/button";
|
||||
import { Icons } from "@turbostarter/ui-web/icons";
|
||||
|
||||
import { useComposer } from "~/modules/chat/composer/hooks/use-composer";
|
||||
|
||||
const examples = [
|
||||
{
|
||||
icon: Icons.FileText,
|
||||
label: "chat.example.summarize.label",
|
||||
prompt: "chat.example.summarize.prompt",
|
||||
},
|
||||
{
|
||||
icon: Icons.ChartNoAxesColumn,
|
||||
label: "chat.example.analyze.label",
|
||||
prompt: "chat.example.analyze.prompt",
|
||||
},
|
||||
{
|
||||
icon: Icons.Code,
|
||||
label: "chat.example.code.label",
|
||||
prompt: "chat.example.code.prompt",
|
||||
},
|
||||
{
|
||||
icon: Icons.Zap,
|
||||
label: "chat.example.brainstorm.label",
|
||||
prompt: "chat.example.brainstorm.prompt",
|
||||
},
|
||||
{
|
||||
icon: Icons.PackageOpen,
|
||||
label: "chat.example.surprise.label",
|
||||
prompt: "chat.example.surprise.prompt",
|
||||
},
|
||||
] as const;
|
||||
|
||||
interface ExamplesProps {
|
||||
readonly id?: string;
|
||||
readonly className?: string;
|
||||
}
|
||||
|
||||
export const Examples = memo<ExamplesProps>(({ className, id }) => {
|
||||
const { t } = useTranslation("ai");
|
||||
const { onSubmit } = useComposer({ id });
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full flex-row flex-wrap items-center justify-center gap-2 px-3 @sm:gap-2",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{examples.map(({ icon: Icon, label, prompt }, index) => (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
|
||||
initial={{ opacity: 0, y: 3, filter: "blur(4px)" }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
key={label}
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="text-muted-foreground gap-2 rounded-full"
|
||||
onClick={() => onSubmit(t(prompt))}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
<span>{t(label)}</span>
|
||||
</Button>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Examples.displayName = "Examples";
|
||||
14
apps/web/src/modules/chat/layout/headline.tsx
Normal file
14
apps/web/src/modules/chat/layout/headline.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { useTranslation } from "@turbostarter/i18n";
|
||||
import { getGreeting } from "@turbostarter/shared/utils";
|
||||
|
||||
export const Headline = () => {
|
||||
const { t } = useTranslation(["common", "ai"]);
|
||||
const { text, emoji } = getGreeting();
|
||||
|
||||
return (
|
||||
<h1 className="leading-tighter flex w-full flex-col items-center justify-center text-center text-2xl tracking-tight @sm:text-3xl @md:text-4xl">
|
||||
{t(`greeting.${text}`)} {emoji}
|
||||
<span className="text-muted-foreground">{t("ai:chat.headline")}</span>
|
||||
</h1>
|
||||
);
|
||||
};
|
||||
32
apps/web/src/modules/chat/layout/new.tsx
Normal file
32
apps/web/src/modules/chat/layout/new.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { memo } from "react";
|
||||
|
||||
import { ChatComposer } from "~/modules/chat/composer";
|
||||
import { ChatDropzone } from "~/modules/chat/composer/dropzone";
|
||||
import { useComposer } from "~/modules/chat/composer/hooks/use-composer";
|
||||
import { Examples } from "~/modules/chat/layout/examples";
|
||||
import { Headline } from "~/modules/chat/layout/headline";
|
||||
|
||||
interface NewChatProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const NewChat = memo<NewChatProps>(({ id }) => {
|
||||
const { model } = useComposer({ id });
|
||||
return (
|
||||
<ChatDropzone disabled={!model?.attachments}>
|
||||
<div className="mx-auto flex h-full w-full flex-col items-center justify-between gap-6 md:justify-center md:gap-9 md:p-2">
|
||||
<div className="flex w-full grow items-end">
|
||||
<Headline />
|
||||
</div>
|
||||
<div className="flex w-full grow flex-col items-center justify-between md:flex-col-reverse md:justify-end md:gap-5">
|
||||
<Examples className="flex" id={id} />
|
||||
<div className="relative w-full px-3 pb-3">
|
||||
<ChatComposer id={id} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ChatDropzone>
|
||||
);
|
||||
});
|
||||
|
||||
NewChat.displayName = "NewChat";
|
||||
34
apps/web/src/modules/chat/layout/view.tsx
Normal file
34
apps/web/src/modules/chat/layout/view.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { memo } from "react";
|
||||
|
||||
import { ChatComposer } from "~/modules/chat/composer";
|
||||
import { ChatDropzone } from "~/modules/chat/composer/dropzone";
|
||||
import { Chat } from "~/modules/chat/thread";
|
||||
|
||||
import { useComposer } from "../composer/hooks/use-composer";
|
||||
|
||||
import type { ChatMessage } from "@turbostarter/ai/chat/types";
|
||||
|
||||
interface ViewChatProps {
|
||||
readonly id: string;
|
||||
readonly initialMessages?: ChatMessage[];
|
||||
}
|
||||
|
||||
export const ViewChat = memo<ViewChatProps>(({ id, initialMessages }) => {
|
||||
const { model } = useComposer({ id, initialMessages });
|
||||
|
||||
return (
|
||||
<ChatDropzone disabled={!model?.attachments}>
|
||||
<Chat id={id} initialMessages={initialMessages} />
|
||||
|
||||
<div className="absolute inset-x-0 bottom-0 z-50 mx-auto max-w-[50rem]">
|
||||
<div className="relative z-40 flex w-full flex-col items-center px-3 pb-3">
|
||||
<ChatComposer id={id} initialMessages={initialMessages} />
|
||||
</div>
|
||||
</div>
|
||||
</ChatDropzone>
|
||||
);
|
||||
});
|
||||
|
||||
ViewChat.displayName = "ViewChat";
|
||||
Reference in New Issue
Block a user