Spaces:
Sleeping
Sleeping
feat: Restruct Composer and Homepage (#64)
Browse files
- app/api/vision-agent/route.ts +2 -3
- app/chat/page.tsx +31 -84
- app/layout.tsx +1 -1
- components/Header.tsx +29 -0
- components/chat/ChatClient.tsx +39 -13
- components/chat/ChatMessage.tsx +2 -7
- components/chat/Composer.tsx +100 -111
- components/ui/Chip.tsx +1 -1
- components/ui/Icons.tsx +23 -3
- components/ui/Tooltip.tsx +1 -1
- lib/db/functions.ts +3 -1
- lib/hooks/useImageUpload.ts +0 -38
- components/chat/ImageSelector.tsx → lib/hooks/useMediaUpload.ts +30 -49
- lib/hooks/useScrollAnchor.tsx +4 -4
- lib/hooks/useVisionAgent.ts +5 -21
- package.json +1 -1
- pnpm-lock.yaml +102 -11
- tailwind.config.ts +48 -39
app/api/vision-agent/route.ts
CHANGED
|
@@ -17,11 +17,10 @@ export const POST = withLogging(
|
|
| 17 |
messages: MessageBase[];
|
| 18 |
id: string;
|
| 19 |
mediaUrl: string;
|
| 20 |
-
enableSelfReflection: boolean;
|
| 21 |
},
|
| 22 |
request,
|
| 23 |
) => {
|
| 24 |
-
const { messages, mediaUrl
|
| 25 |
|
| 26 |
// const session = await auth();
|
| 27 |
// if (!session?.user?.email) {
|
|
@@ -56,7 +55,7 @@ export const POST = withLogging(
|
|
| 56 |
formData.append('image', mediaUrl);
|
| 57 |
|
| 58 |
const fetchResponse = await fetch(
|
| 59 |
-
`https://api.dev.landing.ai/v1/agent/chat?agent_class=vision_agent&
|
| 60 |
// `http://localhost:5001/v1/agent/chat?agent_class=vision_agent&self_reflection=${enableSelfReflection}`,
|
| 61 |
{
|
| 62 |
method: 'POST',
|
|
|
|
| 17 |
messages: MessageBase[];
|
| 18 |
id: string;
|
| 19 |
mediaUrl: string;
|
|
|
|
| 20 |
},
|
| 21 |
request,
|
| 22 |
) => {
|
| 23 |
+
const { messages, mediaUrl } = json;
|
| 24 |
|
| 25 |
// const session = await auth();
|
| 26 |
// if (!session?.user?.email) {
|
|
|
|
| 55 |
formData.append('image', mediaUrl);
|
| 56 |
|
| 57 |
const fetchResponse = await fetch(
|
| 58 |
+
`https://api.dev.landing.ai/v1/agent/chat?agent_class=vision_agent&self_reflection=false`,
|
| 59 |
// `http://localhost:5001/v1/agent/chat?agent_class=vision_agent&self_reflection=${enableSelfReflection}`,
|
| 60 |
{
|
| 61 |
method: 'POST',
|
app/chat/page.tsx
CHANGED
|
@@ -1,25 +1,14 @@
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
-
import ImageSelector from '@/components/chat/ImageSelector';
|
| 4 |
import { generateInputImageMarkdown } from '@/lib/messageUtils';
|
| 5 |
-
import { fetcher } from '@/lib/utils';
|
| 6 |
import { useRouter } from 'next/navigation';
|
| 7 |
|
| 8 |
-
import {
|
| 9 |
-
Tooltip,
|
| 10 |
-
TooltipContent,
|
| 11 |
-
TooltipTrigger,
|
| 12 |
-
} from '@/components/ui/Tooltip';
|
| 13 |
-
import { IconDiscord, IconGitHub } from '@/components/ui/Icons';
|
| 14 |
-
import Link from 'next/link';
|
| 15 |
-
import { Button } from '@/components/ui/Button';
|
| 16 |
-
import Img from '@/components/ui/Img';
|
| 17 |
import { MessageRaw } from '@/lib/db/types';
|
| 18 |
-
import { dbPostCreateChat } from '@/lib/db/functions';
|
| 19 |
import { useState } from 'react';
|
| 20 |
-
import
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
// const EXAMPLE_URL = 'https://landing-lens-support.s3.us-east-2.amazonaws.com/vision-agent-examples/cereal-example.jpg';
|
| 23 |
const EXAMPLE_URL =
|
| 24 |
'https://vision-agent-dev.s3.us-east-2.amazonaws.com/examples/flower.png';
|
| 25 |
const EXAMPLE_HEADER = 'Count and find';
|
|
@@ -51,77 +40,35 @@ const exampleMessages = [
|
|
| 51 |
|
| 52 |
export default function Page() {
|
| 53 |
const router = useRouter();
|
| 54 |
-
const [isUploading, setUploading] = useState<false | Number>(false);
|
| 55 |
return (
|
| 56 |
-
<div className="mx-auto max-w-
|
| 57 |
-
<
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
</Link>
|
| 85 |
-
</Button>
|
| 86 |
-
</TooltipTrigger>
|
| 87 |
-
<TooltipContent>Discord</TooltipContent>
|
| 88 |
-
</Tooltip>
|
| 89 |
-
</div>
|
| 90 |
-
<ImageSelector />
|
| 91 |
-
</div>
|
| 92 |
-
<div className="mb-4 grid grid-cols-2 gap-2 px-4 sm:px-0">
|
| 93 |
-
{exampleMessages.map((example, index) => (
|
| 94 |
-
<div
|
| 95 |
-
key={index}
|
| 96 |
-
className={`relative cursor-pointer rounded-lg border bg-white p-4 hover:bg-zinc-50 dark:bg-zinc-950 dark:hover:bg-zinc-900 flex items-center size-full ${
|
| 97 |
-
index > 1 && 'hidden md:block'
|
| 98 |
-
}`}
|
| 99 |
-
onClick={async () => {
|
| 100 |
-
setUploading(index);
|
| 101 |
-
const resp = await dbPostCreateChat({
|
| 102 |
-
mediaUrl: example.url,
|
| 103 |
-
initMessages: example.initMessages,
|
| 104 |
-
title: example.heading,
|
| 105 |
-
});
|
| 106 |
-
setUploading(false);
|
| 107 |
-
if (resp) {
|
| 108 |
-
router.push(`/chat/${resp.id}`);
|
| 109 |
-
}
|
| 110 |
-
}}
|
| 111 |
-
>
|
| 112 |
-
{isUploading === index && (
|
| 113 |
-
<div className="absolute top-0 left-0 size-full flex items-center justify-center bg-white/60">
|
| 114 |
-
<Loading />
|
| 115 |
-
</div>
|
| 116 |
-
)}
|
| 117 |
-
<Img src={example.url} alt="example images" className="w-1/4" />
|
| 118 |
-
<div className="flex items-start flex-col h-full ml-3 w-3/4">
|
| 119 |
-
<div className="text-sm font-semibold">{example.heading}</div>
|
| 120 |
-
<div className="text-sm text-zinc-600">{example.subheading}</div>
|
| 121 |
-
</div>
|
| 122 |
-
</div>
|
| 123 |
-
))}
|
| 124 |
-
</div>
|
| 125 |
</div>
|
| 126 |
);
|
| 127 |
}
|
|
|
|
| 1 |
'use client';
|
| 2 |
|
|
|
|
| 3 |
import { generateInputImageMarkdown } from '@/lib/messageUtils';
|
|
|
|
| 4 |
import { useRouter } from 'next/navigation';
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
import { MessageRaw } from '@/lib/db/types';
|
|
|
|
| 7 |
import { useState } from 'react';
|
| 8 |
+
import { Composer } from '@/components/chat/Composer';
|
| 9 |
+
import { dbPostCreateChat } from '@/lib/db/functions';
|
| 10 |
+
import { nanoid } from '@/lib/utils';
|
| 11 |
|
|
|
|
| 12 |
const EXAMPLE_URL =
|
| 13 |
'https://vision-agent-dev.s3.us-east-2.amazonaws.com/examples/flower.png';
|
| 14 |
const EXAMPLE_HEADER = 'Count and find';
|
|
|
|
| 40 |
|
| 41 |
export default function Page() {
|
| 42 |
const router = useRouter();
|
|
|
|
| 43 |
return (
|
| 44 |
+
<div className="mx-auto w-[1024px] max-w-full px-4 mt-[200px]">
|
| 45 |
+
<h1 className="mb-4 text-5xl text-center">Vision Agent</h1>
|
| 46 |
+
<h4 className="mb-8 text-center">
|
| 47 |
+
Generate code to solve your vision problem with simple prompts.
|
| 48 |
+
</h4>
|
| 49 |
+
<Composer
|
| 50 |
+
onSubmit={async ({ input, mediaUrl }) => {
|
| 51 |
+
const newId = nanoid();
|
| 52 |
+
const resp = await dbPostCreateChat({
|
| 53 |
+
id: newId,
|
| 54 |
+
mediaUrl: mediaUrl,
|
| 55 |
+
title: `conversation-${newId}`,
|
| 56 |
+
initMessages: [
|
| 57 |
+
{
|
| 58 |
+
role: 'user',
|
| 59 |
+
content:
|
| 60 |
+
input +
|
| 61 |
+
(mediaUrl
|
| 62 |
+
? '\n\n' + generateInputImageMarkdown(mediaUrl)
|
| 63 |
+
: ''),
|
| 64 |
+
},
|
| 65 |
+
],
|
| 66 |
+
});
|
| 67 |
+
if (resp) {
|
| 68 |
+
router.push(`/chat/${newId}`);
|
| 69 |
+
}
|
| 70 |
+
}}
|
| 71 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
</div>
|
| 73 |
);
|
| 74 |
}
|
app/layout.tsx
CHANGED
|
@@ -53,7 +53,7 @@ export default function RootLayout(props: RootLayoutProps) {
|
|
| 53 |
>
|
| 54 |
<div className="flex flex-col min-h-screen">
|
| 55 |
<Header />
|
| 56 |
-
<main className="flex py-8 h-[calc(100vh-64px)] bg-
|
| 57 |
{children}
|
| 58 |
</main>
|
| 59 |
</div>
|
|
|
|
| 53 |
>
|
| 54 |
<div className="flex flex-col min-h-screen">
|
| 55 |
<Header />
|
| 56 |
+
<main className="flex py-8 h-[calc(100vh-64px)] bg-background overflow-hidden relative">
|
| 57 |
{children}
|
| 58 |
</main>
|
| 59 |
</div>
|
components/Header.tsx
CHANGED
|
@@ -12,6 +12,12 @@ import LandingLogo from '@/assets/svg/LandingAI_white.svg';
|
|
| 12 |
import ChatSelectServer from './ChatSelectServer';
|
| 13 |
import Loading from './ui/Loading';
|
| 14 |
import { Skeleton } from './ui/Skeleton';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
export async function Header() {
|
| 17 |
const session = await auth();
|
|
@@ -64,6 +70,29 @@ export async function Header() {
|
|
| 64 |
<Button variant="link" asChild className="mr-2">
|
| 65 |
<Link href="/chat">New conversation</Link>
|
| 66 |
</Button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
<IconSeparator className="size-6 text-muted-foreground/50" />
|
| 68 |
<div className="flex items-center grow-0">
|
| 69 |
{session?.user ? <UserMenu user={session!.user} /> : <LoginMenu />}
|
|
|
|
| 12 |
import ChatSelectServer from './ChatSelectServer';
|
| 13 |
import Loading from './ui/Loading';
|
| 14 |
import { Skeleton } from './ui/Skeleton';
|
| 15 |
+
import {
|
| 16 |
+
Tooltip,
|
| 17 |
+
TooltipContent,
|
| 18 |
+
TooltipTrigger,
|
| 19 |
+
} from '@/components/ui/Tooltip';
|
| 20 |
+
import { IconDiscord, IconGitHub } from '@/components/ui/Icons';
|
| 21 |
|
| 22 |
export async function Header() {
|
| 23 |
const session = await auth();
|
|
|
|
| 70 |
<Button variant="link" asChild className="mr-2">
|
| 71 |
<Link href="/chat">New conversation</Link>
|
| 72 |
</Button>
|
| 73 |
+
<Tooltip>
|
| 74 |
+
<TooltipTrigger asChild>
|
| 75 |
+
<Button variant="link" size="icon" asChild className="mr-2">
|
| 76 |
+
<Link
|
| 77 |
+
href="https://github.com/landing-ai/vision-agent"
|
| 78 |
+
target="_blank"
|
| 79 |
+
>
|
| 80 |
+
<IconGitHub className="size-5" />
|
| 81 |
+
</Link>
|
| 82 |
+
</Button>
|
| 83 |
+
</TooltipTrigger>
|
| 84 |
+
<TooltipContent>Github</TooltipContent>
|
| 85 |
+
</Tooltip>
|
| 86 |
+
<Tooltip>
|
| 87 |
+
<TooltipTrigger asChild>
|
| 88 |
+
<Button variant="link" size="icon" asChild className="mr-2">
|
| 89 |
+
<Link href="https://discord.gg/wZ2A7J69" target="_blank">
|
| 90 |
+
<IconDiscord className="size-5" />
|
| 91 |
+
</Link>
|
| 92 |
+
</Button>
|
| 93 |
+
</TooltipTrigger>
|
| 94 |
+
<TooltipContent>Discord</TooltipContent>
|
| 95 |
+
</Tooltip>
|
| 96 |
<IconSeparator className="size-6 text-muted-foreground/50" />
|
| 97 |
<div className="flex items-center grow-0">
|
| 98 |
{session?.user ? <UserMenu user={session!.user} /> : <LoginMenu />}
|
components/chat/ChatClient.tsx
CHANGED
|
@@ -5,9 +5,13 @@ import { Composer } from '@/components/chat/Composer';
|
|
| 5 |
import useVisionAgent from '@/lib/hooks/useVisionAgent';
|
| 6 |
import { useScrollAnchor } from '@/lib/hooks/useScrollAnchor';
|
| 7 |
import { Session } from 'next-auth';
|
| 8 |
-
import {
|
| 9 |
import { ChatWithMessages } from '@/lib/db/types';
|
| 10 |
import { ChatMessage } from './ChatMessage';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
export interface ChatClientProps {
|
| 13 |
chat: ChatWithMessages;
|
|
@@ -15,15 +19,21 @@ export interface ChatClientProps {
|
|
| 15 |
|
| 16 |
const ChatClient: React.FC<ChatClientProps> = ({ chat }) => {
|
| 17 |
const { mediaUrl, id } = chat;
|
| 18 |
-
const { messages, append,
|
| 19 |
-
useVisionAgent(chat);
|
| 20 |
|
| 21 |
-
const { messagesRef, scrollRef, visibilityRef,
|
| 22 |
useScrollAnchor();
|
| 23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
return (
|
| 25 |
<div
|
| 26 |
-
className="h-full overflow-auto mx-auto
|
| 27 |
ref={scrollRef}
|
| 28 |
>
|
| 29 |
<div className="overflow-auto h-full pt-6 px-6" ref={messagesRef}>
|
|
@@ -36,21 +46,37 @@ const ChatClient: React.FC<ChatClientProps> = ({ chat }) => {
|
|
| 36 |
isLoading={isLoading && index === messages.length - 1}
|
| 37 |
/>
|
| 38 |
))}
|
| 39 |
-
<div className="h-
|
| 40 |
</div>
|
| 41 |
-
<div className="
|
| 42 |
<Composer
|
| 43 |
id={id}
|
| 44 |
mediaUrl={mediaUrl}
|
| 45 |
isLoading={isLoading}
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
/>
|
| 53 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
</div>
|
| 55 |
);
|
| 56 |
};
|
|
|
|
| 5 |
import useVisionAgent from '@/lib/hooks/useVisionAgent';
|
| 6 |
import { useScrollAnchor } from '@/lib/hooks/useScrollAnchor';
|
| 7 |
import { Session } from 'next-auth';
|
| 8 |
+
import { useEffect } from 'react';
|
| 9 |
import { ChatWithMessages } from '@/lib/db/types';
|
| 10 |
import { ChatMessage } from './ChatMessage';
|
| 11 |
+
import { Button } from '../ui/Button';
|
| 12 |
+
import { cn } from '@/lib/utils';
|
| 13 |
+
import { IconArrowDown } from '../ui/Icons';
|
| 14 |
+
import { generateInputImageMarkdown } from '@/lib/messageUtils';
|
| 15 |
|
| 16 |
export interface ChatClientProps {
|
| 17 |
chat: ChatWithMessages;
|
|
|
|
| 19 |
|
| 20 |
const ChatClient: React.FC<ChatClientProps> = ({ chat }) => {
|
| 21 |
const { mediaUrl, id } = chat;
|
| 22 |
+
const { messages, append, isLoading, reload } = useVisionAgent(chat);
|
|
|
|
| 23 |
|
| 24 |
+
const { messagesRef, scrollRef, visibilityRef, isVisible, scrollToBottom } =
|
| 25 |
useScrollAnchor();
|
| 26 |
|
| 27 |
+
// Scroll to bottom when messages are loading
|
| 28 |
+
useEffect(() => {
|
| 29 |
+
if (isLoading && messages.length) {
|
| 30 |
+
scrollToBottom();
|
| 31 |
+
}
|
| 32 |
+
}, [isLoading, scrollToBottom, messages]);
|
| 33 |
+
|
| 34 |
return (
|
| 35 |
<div
|
| 36 |
+
className="h-full overflow-auto mx-auto w-[1024px] max-w-full border rounded-lg relative"
|
| 37 |
ref={scrollRef}
|
| 38 |
>
|
| 39 |
<div className="overflow-auto h-full pt-6 px-6" ref={messagesRef}>
|
|
|
|
| 46 |
isLoading={isLoading && index === messages.length - 1}
|
| 47 |
/>
|
| 48 |
))}
|
| 49 |
+
<div className="h-[108px] w-full" ref={visibilityRef} />
|
| 50 |
</div>
|
| 51 |
+
<div className="absolute bottom-3 w-full">
|
| 52 |
<Composer
|
| 53 |
id={id}
|
| 54 |
mediaUrl={mediaUrl}
|
| 55 |
isLoading={isLoading}
|
| 56 |
+
onSubmit={async ({ input, mediaUrl: newMediaUrl }) => {
|
| 57 |
+
append({
|
| 58 |
+
id,
|
| 59 |
+
content:
|
| 60 |
+
input +
|
| 61 |
+
(newMediaUrl
|
| 62 |
+
? '\n\n' + generateInputImageMarkdown(newMediaUrl)
|
| 63 |
+
: ''),
|
| 64 |
+
role: 'user',
|
| 65 |
+
});
|
| 66 |
+
}}
|
| 67 |
/>
|
| 68 |
</div>
|
| 69 |
+
{/* Scroll to bottom Icon */}
|
| 70 |
+
<Button
|
| 71 |
+
size="icon"
|
| 72 |
+
className={cn(
|
| 73 |
+
'absolute bottom-3 right-3 transition-opacity duration-300 size-6',
|
| 74 |
+
isVisible ? 'opacity-0' : 'opacity-100',
|
| 75 |
+
)}
|
| 76 |
+
onClick={() => scrollToBottom()}
|
| 77 |
+
>
|
| 78 |
+
<IconArrowDown className="size-3" />
|
| 79 |
+
</Button>
|
| 80 |
</div>
|
| 81 |
);
|
| 82 |
};
|
components/chat/ChatMessage.tsx
CHANGED
|
@@ -138,23 +138,18 @@ const Markdown: React.FC<{
|
|
| 138 |
);
|
| 139 |
};
|
| 140 |
|
| 141 |
-
export function ChatMessage({
|
| 142 |
-
message,
|
| 143 |
-
isLoading,
|
| 144 |
-
}: ChatMessageProps) {
|
| 145 |
const { content } = useMemo(() => {
|
| 146 |
return getCleanedUpMessages({
|
| 147 |
content: message.content,
|
| 148 |
role: message.role,
|
| 149 |
});
|
| 150 |
}, [message.content, message.role]);
|
| 151 |
-
console.log('[Ming] content:', content);
|
| 152 |
-
console.log('[Ming] raw:', message.content);
|
| 153 |
const [details, setDetails] = useState<string>('');
|
| 154 |
return (
|
| 155 |
<div
|
| 156 |
className={cn(
|
| 157 |
-
'group relative mb-6 flex rounded-md bg-muted
|
| 158 |
message.role === 'user' ? 'ml-auto mr-0 w-3/5' : 'w-4/5',
|
| 159 |
)}
|
| 160 |
>
|
|
|
|
| 138 |
);
|
| 139 |
};
|
| 140 |
|
| 141 |
+
export function ChatMessage({ message, isLoading }: ChatMessageProps) {
|
|
|
|
|
|
|
|
|
|
| 142 |
const { content } = useMemo(() => {
|
| 143 |
return getCleanedUpMessages({
|
| 144 |
content: message.content,
|
| 145 |
role: message.role,
|
| 146 |
});
|
| 147 |
}, [message.content, message.role]);
|
|
|
|
|
|
|
| 148 |
const [details, setDetails] = useState<string>('');
|
| 149 |
return (
|
| 150 |
<div
|
| 151 |
className={cn(
|
| 152 |
+
'group relative mb-6 flex rounded-md bg-muted p-4',
|
| 153 |
message.role === 'user' ? 'ml-auto mr-0 w-3/5' : 'w-4/5',
|
| 154 |
)}
|
| 155 |
>
|
components/chat/Composer.tsx
CHANGED
|
@@ -1,11 +1,8 @@
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
-
import
|
| 4 |
-
import { type UseChatHelpers } from 'ai/react';
|
| 5 |
-
import Textarea from 'react-textarea-autosize';
|
| 6 |
|
| 7 |
import { Button } from '@/components/ui/Button';
|
| 8 |
-
import { MessageBase } from '../../lib/types';
|
| 9 |
import { useEnterSubmit } from '@/lib/hooks/useEnterSubmit';
|
| 10 |
import Img from '../ui/Img';
|
| 11 |
import {
|
|
@@ -14,85 +11,126 @@ import {
|
|
| 14 |
TooltipTrigger,
|
| 15 |
} from '@/components/ui/Tooltip';
|
| 16 |
import {
|
| 17 |
-
IconArrowDown,
|
| 18 |
IconArrowElbow,
|
| 19 |
IconImage,
|
|
|
|
| 20 |
IconRefresh,
|
| 21 |
IconStop,
|
|
|
|
| 22 |
} from '@/components/ui/Icons';
|
| 23 |
import { cn } from '@/lib/utils';
|
| 24 |
import { generateInputImageMarkdown } from '@/lib/messageUtils';
|
| 25 |
import { Switch } from '../ui/Switch';
|
| 26 |
import Chip from '../ui/Chip';
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
export interface ComposerProps
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
'append' | 'isLoading' | 'reload' | 'stop' | 'input' | 'setInput'
|
| 32 |
-
> {
|
| 33 |
id?: string;
|
| 34 |
title?: string;
|
| 35 |
-
messages: MessageBase[];
|
| 36 |
mediaUrl?: string;
|
| 37 |
}
|
| 38 |
|
| 39 |
-
export function Composer({
|
| 40 |
-
id,
|
| 41 |
-
isLoading,
|
| 42 |
-
append,
|
| 43 |
-
input,
|
| 44 |
-
setInput,
|
| 45 |
-
mediaUrl,
|
| 46 |
-
// isAtBottom,
|
| 47 |
-
}: ComposerProps) {
|
| 48 |
const { formRef, onKeyDown } = useEnterSubmit();
|
| 49 |
-
const inputRef =
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
if (inputRef.current) {
|
| 52 |
inputRef.current.focus();
|
| 53 |
}
|
| 54 |
}, []);
|
| 55 |
|
| 56 |
-
const mediaName =
|
| 57 |
return (
|
| 58 |
-
<div
|
| 59 |
-
{
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
)}
|
| 79 |
<form
|
| 80 |
onSubmit={async e => {
|
| 81 |
e.preventDefault();
|
| 82 |
-
if (!input?.trim()) {
|
| 83 |
return;
|
| 84 |
}
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
});
|
| 93 |
}}
|
| 94 |
ref={formRef}
|
| 95 |
-
className="h-full"
|
| 96 |
>
|
| 97 |
{/* <div className="border-gray-500 flex overflow-hidden size-full flex flex-row items-center"> */}
|
| 98 |
<Textarea
|
|
@@ -101,78 +139,29 @@ export function Composer({
|
|
| 101 |
onKeyDown={onKeyDown}
|
| 102 |
rows={1}
|
| 103 |
value={input}
|
| 104 |
-
disabled={
|
| 105 |
onChange={e => setInput(e.target.value)}
|
| 106 |
-
placeholder={
|
|
|
|
|
|
|
| 107 |
spellCheck={false}
|
| 108 |
-
className="w-full grow resize-none bg-transparent focus-within:outline-none
|
| 109 |
/>
|
| 110 |
-
{/* Stop / Regenerate Icon */}
|
| 111 |
-
{/* <div className="absolute bottom-14 right-4">
|
| 112 |
-
{isLoading ? (
|
| 113 |
-
<Tooltip>
|
| 114 |
-
<TooltipTrigger asChild>
|
| 115 |
-
<Button
|
| 116 |
-
variant="outline"
|
| 117 |
-
size="icon"
|
| 118 |
-
className="bg-background"
|
| 119 |
-
onClick={() => stop()}
|
| 120 |
-
>
|
| 121 |
-
<IconStop />
|
| 122 |
-
</Button>
|
| 123 |
-
</TooltipTrigger>
|
| 124 |
-
<TooltipContent>Stop generating</TooltipContent>
|
| 125 |
-
</Tooltip>
|
| 126 |
-
) : (
|
| 127 |
-
messages?.length >= 2 && (
|
| 128 |
-
<Tooltip>
|
| 129 |
-
<TooltipTrigger asChild>
|
| 130 |
-
<Button
|
| 131 |
-
variant="outline"
|
| 132 |
-
size="icon"
|
| 133 |
-
className="bg-background"
|
| 134 |
-
onClick={() => reload()}
|
| 135 |
-
>
|
| 136 |
-
<IconRefresh />
|
| 137 |
-
</Button>
|
| 138 |
-
</TooltipTrigger>
|
| 139 |
-
<TooltipContent>Regenerate response</TooltipContent>
|
| 140 |
-
</Tooltip>
|
| 141 |
-
)
|
| 142 |
-
)}
|
| 143 |
-
</div> */}
|
| 144 |
-
{/* </div> */}
|
| 145 |
{/* Submit Icon */}
|
| 146 |
<Tooltip>
|
| 147 |
<TooltipTrigger asChild>
|
| 148 |
<Button
|
| 149 |
type="submit"
|
| 150 |
size="icon"
|
| 151 |
-
className=
|
| 152 |
-
disabled={
|
| 153 |
>
|
| 154 |
-
<
|
| 155 |
</Button>
|
| 156 |
</TooltipTrigger>
|
| 157 |
-
<TooltipContent>
|
| 158 |
</Tooltip>
|
| 159 |
</form>
|
| 160 |
-
{/* Scroll to bottom Icon */}
|
| 161 |
-
{/* <Tooltip>
|
| 162 |
-
<TooltipTrigger asChild>
|
| 163 |
-
<Button
|
| 164 |
-
size="icon"
|
| 165 |
-
className={cn(
|
| 166 |
-
'absolute top-1 right-3 transition-opacity duration-300 size-6',
|
| 167 |
-
isAtBottom ? 'opacity-0' : 'opacity-100',
|
| 168 |
-
)}
|
| 169 |
-
onClick={() => scrollToBottom()}
|
| 170 |
-
>
|
| 171 |
-
<IconArrowDown className="size-3" />
|
| 172 |
-
</Button>
|
| 173 |
-
</TooltipTrigger>
|
| 174 |
-
<TooltipContent>Scroll to bottom</TooltipContent>
|
| 175 |
-
</Tooltip> */}
|
| 176 |
</div>
|
| 177 |
);
|
| 178 |
}
|
|
|
|
| 1 |
'use client';
|
| 2 |
|
| 3 |
+
import { useState, useEffect, useRef } from 'react';
|
|
|
|
|
|
|
| 4 |
|
| 5 |
import { Button } from '@/components/ui/Button';
|
|
|
|
| 6 |
import { useEnterSubmit } from '@/lib/hooks/useEnterSubmit';
|
| 7 |
import Img from '../ui/Img';
|
| 8 |
import {
|
|
|
|
| 11 |
TooltipTrigger,
|
| 12 |
} from '@/components/ui/Tooltip';
|
| 13 |
import {
|
|
|
|
| 14 |
IconArrowElbow,
|
| 15 |
IconImage,
|
| 16 |
+
IconArrowUp,
|
| 17 |
IconRefresh,
|
| 18 |
IconStop,
|
| 19 |
+
IconClose,
|
| 20 |
} from '@/components/ui/Icons';
|
| 21 |
import { cn } from '@/lib/utils';
|
| 22 |
import { generateInputImageMarkdown } from '@/lib/messageUtils';
|
| 23 |
import { Switch } from '../ui/Switch';
|
| 24 |
import Chip from '../ui/Chip';
|
| 25 |
+
import Textarea from 'react-textarea-autosize';
|
| 26 |
+
import useMediaUpload from '@/lib/hooks/useMediaUpload';
|
| 27 |
|
| 28 |
+
export interface ComposerProps {
|
| 29 |
+
onSubmit: (params: { input: string; mediaUrl: string }) => Promise<void>;
|
| 30 |
+
isLoading?: boolean;
|
|
|
|
|
|
|
| 31 |
id?: string;
|
| 32 |
title?: string;
|
|
|
|
| 33 |
mediaUrl?: string;
|
| 34 |
}
|
| 35 |
|
| 36 |
+
export function Composer({ id, isLoading, onSubmit, mediaUrl }: ComposerProps) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
const { formRef, onKeyDown } = useEnterSubmit();
|
| 38 |
+
const inputRef = useRef<HTMLTextAreaElement>(null);
|
| 39 |
+
const [localMediaUrl, setLocalMediaUrl] = useState<string | undefined>(
|
| 40 |
+
mediaUrl,
|
| 41 |
+
);
|
| 42 |
+
// For local loading state such as submitting
|
| 43 |
+
const [localLoading, setLocalLoading] = useState<boolean>(false);
|
| 44 |
+
const [input, setInput] = useState('');
|
| 45 |
+
const noMediaValidation = !localMediaUrl && !!input;
|
| 46 |
+
const { getRootProps, getInputProps, isDragActive, isUploading, openUpload } =
|
| 47 |
+
useMediaUpload(uploadUrl => setLocalMediaUrl(uploadUrl));
|
| 48 |
+
|
| 49 |
+
const finalLoading = isLoading || isUploading || localLoading;
|
| 50 |
+
|
| 51 |
+
useEffect(() => {
|
| 52 |
if (inputRef.current) {
|
| 53 |
inputRef.current.focus();
|
| 54 |
}
|
| 55 |
}, []);
|
| 56 |
|
| 57 |
+
const mediaName = localMediaUrl?.split('/').pop();
|
| 58 |
return (
|
| 59 |
+
<div
|
| 60 |
+
{...getRootProps()}
|
| 61 |
+
className={cn(
|
| 62 |
+
'w-full mx-auto max-w-2xl px-6 py-4 bg-zinc-700 rounded-xl relative shadow-lg shadow-zinc-700/40',
|
| 63 |
+
isDragActive && 'bg-indigo-700/50',
|
| 64 |
+
)}
|
| 65 |
+
>
|
| 66 |
+
<input {...getInputProps()} />
|
| 67 |
+
<div
|
| 68 |
+
className={cn(
|
| 69 |
+
'w-1/3 h-1 rounded-full overflow-hidden bg-zinc-700 absolute left-1/2 -translate-x-1/2',
|
| 70 |
+
finalLoading ? 'opacity-100' : 'opacity-0',
|
| 71 |
+
)}
|
| 72 |
+
>
|
| 73 |
+
<div className="h-full bg-primary animate-progress origin-left-right" />
|
| 74 |
+
</div>
|
| 75 |
+
{localMediaUrl ? (
|
| 76 |
+
<Chip className="mb-0.5">
|
| 77 |
+
<div className="flex flex-row items-center space-x-2">
|
| 78 |
+
<Tooltip>
|
| 79 |
+
<TooltipTrigger>
|
| 80 |
+
<div className="flex flex-row items-center space-x-2">
|
| 81 |
+
<IconImage className="size-3" />
|
| 82 |
+
<p>{mediaName ?? 'unnamed_media'}</p>
|
| 83 |
+
</div>
|
| 84 |
+
</TooltipTrigger>
|
| 85 |
+
<TooltipContent sideOffset={8}>
|
| 86 |
+
<Img
|
| 87 |
+
src={localMediaUrl}
|
| 88 |
+
className="m-1"
|
| 89 |
+
quality={100}
|
| 90 |
+
alt="zoomed-in-image"
|
| 91 |
+
/>
|
| 92 |
+
</TooltipContent>
|
| 93 |
+
</Tooltip>
|
| 94 |
+
<Button
|
| 95 |
+
size="icon"
|
| 96 |
+
variant="ghost"
|
| 97 |
+
className="size-4"
|
| 98 |
+
onClick={() => setLocalMediaUrl(undefined)}
|
| 99 |
+
>
|
| 100 |
+
<IconClose className="size-3" />
|
| 101 |
+
</Button>
|
| 102 |
+
</div>
|
| 103 |
+
</Chip>
|
| 104 |
+
) : (
|
| 105 |
+
<Button
|
| 106 |
+
variant="ghost"
|
| 107 |
+
size="sm"
|
| 108 |
+
className={cn(
|
| 109 |
+
'ml-[-10px] border-2 border-transparent',
|
| 110 |
+
noMediaValidation && 'border-red-500/50 border-2 text-red-500',
|
| 111 |
+
)}
|
| 112 |
+
onClick={openUpload}
|
| 113 |
+
>
|
| 114 |
+
<IconImage className="mr-2 size-4" />
|
| 115 |
+
{noMediaValidation ? 'Select media (required)' : 'Select media'}
|
| 116 |
+
</Button>
|
| 117 |
)}
|
| 118 |
<form
|
| 119 |
onSubmit={async e => {
|
| 120 |
e.preventDefault();
|
| 121 |
+
if (!input?.trim() || !localMediaUrl) {
|
| 122 |
return;
|
| 123 |
}
|
| 124 |
+
setLocalLoading(true);
|
| 125 |
+
try {
|
| 126 |
+
await onSubmit({ input, mediaUrl: localMediaUrl });
|
| 127 |
+
} finally {
|
| 128 |
+
setLocalLoading(false);
|
| 129 |
+
setInput('');
|
| 130 |
+
}
|
|
|
|
| 131 |
}}
|
| 132 |
ref={formRef}
|
| 133 |
+
className="h-full mt-4"
|
| 134 |
>
|
| 135 |
{/* <div className="border-gray-500 flex overflow-hidden size-full flex flex-row items-center"> */}
|
| 136 |
<Textarea
|
|
|
|
| 139 |
onKeyDown={onKeyDown}
|
| 140 |
rows={1}
|
| 141 |
value={input}
|
| 142 |
+
disabled={finalLoading}
|
| 143 |
onChange={e => setInput(e.target.value)}
|
| 144 |
+
placeholder={
|
| 145 |
+
finalLoading ? '🤖 Agent working ✨' : 'Message Vision Agent'
|
| 146 |
+
}
|
| 147 |
spellCheck={false}
|
| 148 |
+
className="w-full grow resize-none bg-transparent focus-within:outline-none"
|
| 149 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
{/* Submit Icon */}
|
| 151 |
<Tooltip>
|
| 152 |
<TooltipTrigger asChild>
|
| 153 |
<Button
|
| 154 |
type="submit"
|
| 155 |
size="icon"
|
| 156 |
+
className={cn('size-6 absolute bottom-3 right-3')}
|
| 157 |
+
disabled={finalLoading || input === '' || noMediaValidation}
|
| 158 |
>
|
| 159 |
+
<IconArrowUp className="size-3" />
|
| 160 |
</Button>
|
| 161 |
</TooltipTrigger>
|
| 162 |
+
<TooltipContent>Message Vision Agent</TooltipContent>
|
| 163 |
</Tooltip>
|
| 164 |
</form>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
</div>
|
| 166 |
);
|
| 167 |
}
|
components/ui/Chip.tsx
CHANGED
|
@@ -17,7 +17,7 @@ const Chip: React.FC<ChipProps> = ({
|
|
| 17 |
return (
|
| 18 |
<div
|
| 19 |
className={cn(
|
| 20 |
-
'inline-flex items-center rounded-full text-xs mr-2 bg-gray-100 text-gray-500 px-2 py-
|
| 21 |
`bg-${color}-100 text-${color}-500`,
|
| 22 |
className,
|
| 23 |
)}
|
|
|
|
| 17 |
return (
|
| 18 |
<div
|
| 19 |
className={cn(
|
| 20 |
+
'inline-flex items-center rounded-full text-xs mr-2 bg-gray-100 text-gray-500 px-2 py-1',
|
| 21 |
`bg-${color}-100 text-${color}-500`,
|
| 22 |
className,
|
| 23 |
)}
|
components/ui/Icons.tsx
CHANGED
|
@@ -128,6 +128,25 @@ function IconSeparator({ className, ...props }: React.ComponentProps<'svg'>) {
|
|
| 128 |
);
|
| 129 |
}
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
function IconArrowDown({ className, ...props }: React.ComponentProps<'svg'>) {
|
| 132 |
return (
|
| 133 |
<svg
|
|
@@ -519,15 +538,15 @@ function IconImage({ className, ...props }: React.ComponentProps<'svg'>) {
|
|
| 519 |
<svg
|
| 520 |
data-testid="geist-icon"
|
| 521 |
height="16"
|
| 522 |
-
|
| 523 |
viewBox="0 0 16 16"
|
| 524 |
width="16"
|
| 525 |
className={cn('size-4', className)}
|
| 526 |
{...props}
|
| 527 |
>
|
| 528 |
<path
|
| 529 |
-
|
| 530 |
-
|
| 531 |
d="M14.5 2.5H1.5V9.18933L2.96966 7.71967L3.18933 7.5H3.49999H6.63001H6.93933L6.96966 7.46967L10.4697 3.96967L11.5303 3.96967L14.5 6.93934V2.5ZM8.00066 8.55999L9.53034 10.0897L10.0607 10.62L9.00001 11.6807L8.46968 11.1503L6.31935 9H3.81065L1.53032 11.2803L1.5 11.3106V12.5C1.5 13.0523 1.94772 13.5 2.5 13.5H13.5C14.0523 13.5 14.5 13.0523 14.5 12.5V9.06066L11 5.56066L8.03032 8.53033L8.00066 8.55999ZM4.05312e-06 10.8107V12.5C4.05312e-06 13.8807 1.11929 15 2.5 15H13.5C14.8807 15 16 13.8807 16 12.5V9.56066L16.5607 9L16.0303 8.46967L16 8.43934V2.5V1H14.5H1.5H4.05312e-06V2.5V10.6893L-0.0606689 10.75L4.05312e-06 10.8107Z"
|
| 532 |
fill="currentColor"
|
| 533 |
></path>
|
|
@@ -542,6 +561,7 @@ export {
|
|
| 542 |
IconSeparator,
|
| 543 |
IconArrowDown,
|
| 544 |
IconArrowRight,
|
|
|
|
| 545 |
IconUser,
|
| 546 |
IconPlus,
|
| 547 |
IconArrowElbow,
|
|
|
|
| 128 |
);
|
| 129 |
}
|
| 130 |
|
| 131 |
+
function IconArrowUp({ className, ...props }: React.ComponentProps<'svg'>) {
|
| 132 |
+
return (
|
| 133 |
+
<svg
|
| 134 |
+
height="16"
|
| 135 |
+
strokeLinejoin="round"
|
| 136 |
+
viewBox="0 0 16 16"
|
| 137 |
+
className={cn('size-4', className)}
|
| 138 |
+
{...props}
|
| 139 |
+
>
|
| 140 |
+
<path
|
| 141 |
+
fillRule="evenodd"
|
| 142 |
+
clipRule="evenodd"
|
| 143 |
+
d="M8.70711 1.39644C8.31659 1.00592 7.68342 1.00592 7.2929 1.39644L2.21968 6.46966L1.68935 6.99999L2.75001 8.06065L3.28034 7.53032L7.25001 3.56065V14.25V15H8.75001V14.25V3.56065L12.7197 7.53032L13.25 8.06065L14.3107 6.99999L13.7803 6.46966L8.70711 1.39644Z"
|
| 144 |
+
fill="currentColor"
|
| 145 |
+
></path>
|
| 146 |
+
</svg>
|
| 147 |
+
);
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
function IconArrowDown({ className, ...props }: React.ComponentProps<'svg'>) {
|
| 151 |
return (
|
| 152 |
<svg
|
|
|
|
| 538 |
<svg
|
| 539 |
data-testid="geist-icon"
|
| 540 |
height="16"
|
| 541 |
+
strokeLinejoin="round"
|
| 542 |
viewBox="0 0 16 16"
|
| 543 |
width="16"
|
| 544 |
className={cn('size-4', className)}
|
| 545 |
{...props}
|
| 546 |
>
|
| 547 |
<path
|
| 548 |
+
fillRule="evenodd"
|
| 549 |
+
clipRule="evenodd"
|
| 550 |
d="M14.5 2.5H1.5V9.18933L2.96966 7.71967L3.18933 7.5H3.49999H6.63001H6.93933L6.96966 7.46967L10.4697 3.96967L11.5303 3.96967L14.5 6.93934V2.5ZM8.00066 8.55999L9.53034 10.0897L10.0607 10.62L9.00001 11.6807L8.46968 11.1503L6.31935 9H3.81065L1.53032 11.2803L1.5 11.3106V12.5C1.5 13.0523 1.94772 13.5 2.5 13.5H13.5C14.0523 13.5 14.5 13.0523 14.5 12.5V9.06066L11 5.56066L8.03032 8.53033L8.00066 8.55999ZM4.05312e-06 10.8107V12.5C4.05312e-06 13.8807 1.11929 15 2.5 15H13.5C14.8807 15 16 13.8807 16 12.5V9.56066L16.5607 9L16.0303 8.46967L16 8.43934V2.5V1H14.5H1.5H4.05312e-06V2.5V10.6893L-0.0606689 10.75L4.05312e-06 10.8107Z"
|
| 551 |
fill="currentColor"
|
| 552 |
></path>
|
|
|
|
| 561 |
IconSeparator,
|
| 562 |
IconArrowDown,
|
| 563 |
IconArrowRight,
|
| 564 |
+
IconArrowUp,
|
| 565 |
IconUser,
|
| 566 |
IconPlus,
|
| 567 |
IconArrowElbow,
|
components/ui/Tooltip.tsx
CHANGED
|
@@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
|
|
| 19 |
ref={ref}
|
| 20 |
sideOffset={sideOffset}
|
| 21 |
className={cn(
|
| 22 |
-
'z-50 overflow-hidden rounded-md bg-muted px-3 py-1.5 text-xs text-primary
|
| 23 |
className,
|
| 24 |
)}
|
| 25 |
{...props}
|
|
|
|
| 19 |
ref={ref}
|
| 20 |
sideOffset={sideOffset}
|
| 21 |
className={cn(
|
| 22 |
+
'z-50 overflow-hidden rounded-md bg-muted px-3 py-1.5 text-xs text-primary 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',
|
| 23 |
className,
|
| 24 |
)}
|
| 25 |
{...props}
|
lib/db/functions.ts
CHANGED
|
@@ -150,7 +150,7 @@ export async function dbPostCreateMessage(chatId: string, message: MessageRaw) {
|
|
| 150 |
}
|
| 151 |
: {};
|
| 152 |
|
| 153 |
-
|
| 154 |
data: {
|
| 155 |
content: message.content,
|
| 156 |
role: message.role,
|
|
@@ -160,6 +160,8 @@ export async function dbPostCreateMessage(chatId: string, message: MessageRaw) {
|
|
| 160 |
...userConnect,
|
| 161 |
},
|
| 162 |
});
|
|
|
|
|
|
|
| 163 |
}
|
| 164 |
|
| 165 |
export async function dbDeleteChat(chatId: string) {
|
|
|
|
| 150 |
}
|
| 151 |
: {};
|
| 152 |
|
| 153 |
+
await prisma.message.create({
|
| 154 |
data: {
|
| 155 |
content: message.content,
|
| 156 |
role: message.role,
|
|
|
|
| 160 |
...userConnect,
|
| 161 |
},
|
| 162 |
});
|
| 163 |
+
|
| 164 |
+
revalidatePath('/chat');
|
| 165 |
}
|
| 166 |
|
| 167 |
export async function dbDeleteChat(chatId: string) {
|
lib/hooks/useImageUpload.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
import { DropzoneOptions, useDropzone } from 'react-dropzone';
|
| 2 |
-
// import { toast } from 'react-hot-toast';
|
| 3 |
-
|
| 4 |
-
const useImageUpload = (
|
| 5 |
-
options?: Partial<DropzoneOptions>,
|
| 6 |
-
onDrop?: (files: File[]) => void,
|
| 7 |
-
) => {
|
| 8 |
-
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
| 9 |
-
accept: {
|
| 10 |
-
'image/*': ['.jpeg', '.png'],
|
| 11 |
-
'video/mp4': ['.mp4', '.MP4'],
|
| 12 |
-
},
|
| 13 |
-
multiple: false,
|
| 14 |
-
onDrop: onDrop
|
| 15 |
-
? onDrop
|
| 16 |
-
: acceptedFiles => {
|
| 17 |
-
// if (acceptedFiles.length > 10) {
|
| 18 |
-
// toast('You can only upload 10 images max.', {
|
| 19 |
-
// icon: '⚠️',
|
| 20 |
-
// });
|
| 21 |
-
// }
|
| 22 |
-
acceptedFiles.forEach(file => {
|
| 23 |
-
try {
|
| 24 |
-
const reader = new FileReader();
|
| 25 |
-
reader.onloadend = () => {};
|
| 26 |
-
reader.readAsDataURL(file);
|
| 27 |
-
} catch (err) {
|
| 28 |
-
console.error(err);
|
| 29 |
-
}
|
| 30 |
-
});
|
| 31 |
-
},
|
| 32 |
-
...options,
|
| 33 |
-
});
|
| 34 |
-
|
| 35 |
-
return { getRootProps, getInputProps, isDragActive };
|
| 36 |
-
};
|
| 37 |
-
|
| 38 |
-
export default useImageUpload;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
components/chat/ImageSelector.tsx → lib/hooks/useMediaUpload.ts
RENAMED
|
@@ -1,28 +1,17 @@
|
|
| 1 |
-
'use client';
|
| 2 |
-
|
| 3 |
-
import React, { useCallback, useState } from 'react';
|
| 4 |
-
import useImageUpload from '../../lib/hooks/useImageUpload';
|
| 5 |
-
import { cn, fetcher } from '@/lib/utils';
|
| 6 |
-
import { SignedPayload, MessageBase } from '@/lib/types';
|
| 7 |
-
import { useRouter } from 'next/navigation';
|
| 8 |
-
import Loading from '../ui/Loading';
|
| 9 |
-
import toast from 'react-hot-toast';
|
| 10 |
import {
|
| 11 |
generateVideoThumbnails,
|
| 12 |
getVideoDurationFromVideoFile,
|
| 13 |
} from '@rajesh896/video-thumbnails-generator';
|
| 14 |
-
import {
|
| 15 |
-
import {
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
type Example = {
|
| 20 |
-
url: string;
|
| 21 |
-
initMessages: MessageBase[];
|
| 22 |
-
};
|
| 23 |
|
| 24 |
-
const
|
| 25 |
-
|
|
|
|
|
|
|
| 26 |
const [isUploading, setUploading] = useState(false);
|
| 27 |
|
| 28 |
const upload = useCallback(async (file: File, chatId?: string) => {
|
|
@@ -57,10 +46,15 @@ const ImageSelector: React.FC<ImageSelectorProps> = () => {
|
|
| 57 |
};
|
| 58 |
}, []);
|
| 59 |
|
| 60 |
-
const { getRootProps, getInputProps, isDragActive } =
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
if (files.length !== 1) {
|
| 65 |
throw new Error('Only one image can be uploaded at a time');
|
| 66 |
}
|
|
@@ -100,33 +94,20 @@ const ImageSelector: React.FC<ImageSelectorProps> = () => {
|
|
| 100 |
return upload(thumbnailFile, resp.id);
|
| 101 |
});
|
| 102 |
}
|
| 103 |
-
|
| 104 |
-
id: resp.id,
|
| 105 |
-
mediaUrl: resp.publicUrl,
|
| 106 |
-
});
|
| 107 |
setUploading(false);
|
| 108 |
-
router.push(`/chat/${resp.id}`);
|
| 109 |
};
|
| 110 |
},
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
<div className="text-gray-400 text-md">
|
| 122 |
-
{isUploading ? (
|
| 123 |
-
<Loading />
|
| 124 |
-
) : (
|
| 125 |
-
'Start using Vision Agent by selecting an image'
|
| 126 |
-
)}
|
| 127 |
-
</div>
|
| 128 |
-
</div>
|
| 129 |
-
);
|
| 130 |
};
|
| 131 |
|
| 132 |
-
export default
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import {
|
| 2 |
generateVideoThumbnails,
|
| 3 |
getVideoDurationFromVideoFile,
|
| 4 |
} from '@rajesh896/video-thumbnails-generator';
|
| 5 |
+
import { useCallback, useState } from 'react';
|
| 6 |
+
import { DropzoneOptions, useDropzone } from 'react-dropzone';
|
| 7 |
+
import { toast } from 'react-hot-toast';
|
| 8 |
+
import { fetcher } from '../utils';
|
| 9 |
+
import { SignedPayload } from '../types';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
const useMediaUpload = (
|
| 12 |
+
onUpload: (uploadUrl: string) => void,
|
| 13 |
+
options?: Partial<DropzoneOptions>,
|
| 14 |
+
) => {
|
| 15 |
const [isUploading, setUploading] = useState(false);
|
| 16 |
|
| 17 |
const upload = useCallback(async (file: File, chatId?: string) => {
|
|
|
|
| 46 |
};
|
| 47 |
}, []);
|
| 48 |
|
| 49 |
+
const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
|
| 50 |
+
accept: {
|
| 51 |
+
'image/*': ['.jpeg', '.png'],
|
| 52 |
+
'video/mp4': ['.mp4', '.MP4'],
|
| 53 |
+
},
|
| 54 |
+
noClick: true,
|
| 55 |
+
noKeyboard: true,
|
| 56 |
+
multiple: false,
|
| 57 |
+
onDrop: async files => {
|
| 58 |
if (files.length !== 1) {
|
| 59 |
throw new Error('Only one image can be uploaded at a time');
|
| 60 |
}
|
|
|
|
| 94 |
return upload(thumbnailFile, resp.id);
|
| 95 |
});
|
| 96 |
}
|
| 97 |
+
onUpload(resp.publicUrl);
|
|
|
|
|
|
|
|
|
|
| 98 |
setUploading(false);
|
|
|
|
| 99 |
};
|
| 100 |
},
|
| 101 |
+
...options,
|
| 102 |
+
});
|
| 103 |
+
|
| 104 |
+
return {
|
| 105 |
+
getRootProps,
|
| 106 |
+
getInputProps,
|
| 107 |
+
isDragActive,
|
| 108 |
+
isUploading,
|
| 109 |
+
openUpload: open,
|
| 110 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
};
|
| 112 |
|
| 113 |
+
export default useMediaUpload;
|
lib/hooks/useScrollAnchor.tsx
CHANGED
|
@@ -9,8 +9,8 @@ export const useScrollAnchor = () => {
|
|
| 9 |
const [isVisible, setIsVisible] = useState(false);
|
| 10 |
|
| 11 |
const scrollToBottom = useCallback(() => {
|
| 12 |
-
if (
|
| 13 |
-
|
| 14 |
block: 'end',
|
| 15 |
behavior: 'smooth',
|
| 16 |
});
|
|
@@ -72,7 +72,7 @@ export const useScrollAnchor = () => {
|
|
| 72 |
});
|
| 73 |
},
|
| 74 |
{
|
| 75 |
-
rootMargin: '0px 0px -
|
| 76 |
},
|
| 77 |
);
|
| 78 |
|
|
@@ -89,6 +89,6 @@ export const useScrollAnchor = () => {
|
|
| 89 |
scrollRef,
|
| 90 |
visibilityRef,
|
| 91 |
scrollToBottom,
|
| 92 |
-
|
| 93 |
};
|
| 94 |
};
|
|
|
|
| 9 |
const [isVisible, setIsVisible] = useState(false);
|
| 10 |
|
| 11 |
const scrollToBottom = useCallback(() => {
|
| 12 |
+
if (visibilityRef.current) {
|
| 13 |
+
visibilityRef.current.scrollIntoView({
|
| 14 |
block: 'end',
|
| 15 |
behavior: 'smooth',
|
| 16 |
});
|
|
|
|
| 72 |
});
|
| 73 |
},
|
| 74 |
{
|
| 75 |
+
rootMargin: '0px 0px -108px 0px',
|
| 76 |
},
|
| 77 |
);
|
| 78 |
|
|
|
|
| 89 |
scrollRef,
|
| 90 |
visibilityRef,
|
| 91 |
scrollToBottom,
|
| 92 |
+
isVisible,
|
| 93 |
};
|
| 94 |
};
|
lib/hooks/useVisionAgent.ts
CHANGED
|
@@ -53,21 +53,16 @@ const uploadBase64 = async (
|
|
| 53 |
|
| 54 |
const useVisionAgent = (chat: ChatWithMessages) => {
|
| 55 |
const { messages: initialMessages, id, mediaUrl } = chat;
|
| 56 |
-
const searchParams = useSearchParams();
|
| 57 |
-
const reflectionValue = searchParams.get('reflection');
|
| 58 |
|
| 59 |
const {
|
| 60 |
messages,
|
| 61 |
append: appendRaw,
|
| 62 |
-
reload,
|
| 63 |
-
stop,
|
| 64 |
isLoading,
|
| 65 |
-
|
| 66 |
-
setInput,
|
| 67 |
-
setMessages,
|
| 68 |
-
error,
|
| 69 |
} = useChat({
|
| 70 |
api: '/api/vision-agent',
|
|
|
|
|
|
|
| 71 |
onResponse(response) {
|
| 72 |
if (response.status !== 200) {
|
| 73 |
toast.error(response.statusText);
|
|
@@ -83,22 +78,14 @@ const useVisionAgent = (chat: ChatWithMessages) => {
|
|
| 83 |
body: {
|
| 84 |
mediaUrl,
|
| 85 |
id,
|
| 86 |
-
enableSelfReflection: reflectionValue === 'true',
|
| 87 |
},
|
| 88 |
});
|
| 89 |
|
| 90 |
/**
|
| 91 |
-
* If
|
| 92 |
-
* There are 2 scenarios when this might happen
|
| 93 |
-
* 1. Navigated from example images, init message only include preset user message
|
| 94 |
-
* 2. Last time the assistant message failed or not saved to database.
|
| 95 |
*/
|
| 96 |
useEffect(() => {
|
| 97 |
-
if (
|
| 98 |
-
!isLoading &&
|
| 99 |
-
messages.length &&
|
| 100 |
-
messages[messages.length - 1].role === 'user'
|
| 101 |
-
) {
|
| 102 |
reload();
|
| 103 |
}
|
| 104 |
}, [isLoading, messages, reload]);
|
|
@@ -115,10 +102,7 @@ const useVisionAgent = (chat: ChatWithMessages) => {
|
|
| 115 |
messages: messages as MessageBase[],
|
| 116 |
append,
|
| 117 |
reload,
|
| 118 |
-
stop,
|
| 119 |
isLoading,
|
| 120 |
-
input,
|
| 121 |
-
setInput,
|
| 122 |
};
|
| 123 |
};
|
| 124 |
|
|
|
|
| 53 |
|
| 54 |
const useVisionAgent = (chat: ChatWithMessages) => {
|
| 55 |
const { messages: initialMessages, id, mediaUrl } = chat;
|
|
|
|
|
|
|
| 56 |
|
| 57 |
const {
|
| 58 |
messages,
|
| 59 |
append: appendRaw,
|
|
|
|
|
|
|
| 60 |
isLoading,
|
| 61 |
+
reload,
|
|
|
|
|
|
|
|
|
|
| 62 |
} = useChat({
|
| 63 |
api: '/api/vision-agent',
|
| 64 |
+
// @ts-ignore https://sdk.vercel.ai/docs/troubleshooting/common-issues/use-chat-failed-to-parse-stream
|
| 65 |
+
streamMode: 'text',
|
| 66 |
onResponse(response) {
|
| 67 |
if (response.status !== 200) {
|
| 68 |
toast.error(response.statusText);
|
|
|
|
| 78 |
body: {
|
| 79 |
mediaUrl,
|
| 80 |
id,
|
|
|
|
| 81 |
},
|
| 82 |
});
|
| 83 |
|
| 84 |
/**
|
| 85 |
+
* If case this is first time user navigated with init message, we need to reload the chat for the first response
|
|
|
|
|
|
|
|
|
|
| 86 |
*/
|
| 87 |
useEffect(() => {
|
| 88 |
+
if (!isLoading && messages.length === 1 && messages[0].role === 'user') {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
reload();
|
| 90 |
}
|
| 91 |
}, [isLoading, messages, reload]);
|
|
|
|
| 102 |
messages: messages as MessageBase[],
|
| 103 |
append,
|
| 104 |
reload,
|
|
|
|
| 105 |
isLoading,
|
|
|
|
|
|
|
| 106 |
};
|
| 107 |
};
|
| 108 |
|
package.json
CHANGED
|
@@ -28,7 +28,7 @@
|
|
| 28 |
"@radix-ui/react-tooltip": "^1.0.7",
|
| 29 |
"@rajesh896/video-thumbnails-generator": "^2.3.9",
|
| 30 |
"@vercel/kv": "^1.0.1",
|
| 31 |
-
"ai": "^
|
| 32 |
"class-variance-authority": "^0.7.0",
|
| 33 |
"clsx": "^2.1.0",
|
| 34 |
"date-fns": "^3.6.0",
|
|
|
|
| 28 |
"@radix-ui/react-tooltip": "^1.0.7",
|
| 29 |
"@rajesh896/video-thumbnails-generator": "^2.3.9",
|
| 30 |
"@vercel/kv": "^1.0.1",
|
| 31 |
+
"ai": "^3.1.12",
|
| 32 |
"class-variance-authority": "^0.7.0",
|
| 33 |
"clsx": "^2.1.0",
|
| 34 |
"date-fns": "^3.6.0",
|
pnpm-lock.yaml
CHANGED
|
@@ -51,8 +51,8 @@ importers:
|
|
| 51 |
specifier: ^1.0.1
|
| 52 |
version: 1.0.1
|
| 53 |
ai:
|
| 54 |
-
specifier: ^
|
| 55 |
-
version:
|
| 56 |
class-variance-authority:
|
| 57 |
specifier: ^0.7.0
|
| 58 |
version: 0.7.0
|
|
@@ -214,6 +214,19 @@ packages:
|
|
| 214 |
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
|
| 215 |
engines: {node: '>=0.10.0'}
|
| 216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
'@alloc/quick-lru@5.2.0':
|
| 218 |
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
| 219 |
engines: {node: '>=10'}
|
|
@@ -1309,6 +1322,9 @@ packages:
|
|
| 1309 |
'@types/debug@4.1.12':
|
| 1310 |
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
| 1311 |
|
|
|
|
|
|
|
|
|
|
| 1312 |
'@types/estree@1.0.5':
|
| 1313 |
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
| 1314 |
|
|
@@ -1463,15 +1479,19 @@ packages:
|
|
| 1463 |
resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
|
| 1464 |
engines: {node: '>= 8.0.0'}
|
| 1465 |
|
| 1466 |
-
ai@
|
| 1467 |
-
resolution: {integrity: sha512-
|
| 1468 |
-
engines: {node: '>=
|
| 1469 |
peerDependencies:
|
| 1470 |
-
|
|
|
|
| 1471 |
solid-js: ^1.7.7
|
| 1472 |
svelte: ^3.0.0 || ^4.0.0
|
| 1473 |
vue: ^3.3.4
|
|
|
|
| 1474 |
peerDependenciesMeta:
|
|
|
|
|
|
|
| 1475 |
react:
|
| 1476 |
optional: true
|
| 1477 |
solid-js:
|
|
@@ -1480,6 +1500,8 @@ packages:
|
|
| 1480 |
optional: true
|
| 1481 |
vue:
|
| 1482 |
optional: true
|
|
|
|
|
|
|
| 1483 |
|
| 1484 |
ajv@6.12.6:
|
| 1485 |
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
|
@@ -1661,6 +1683,10 @@ packages:
|
|
| 1661 |
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
| 1662 |
engines: {node: '>=10'}
|
| 1663 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1664 |
character-entities-legacy@1.1.4:
|
| 1665 |
resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==}
|
| 1666 |
|
|
@@ -1837,6 +1863,9 @@ packages:
|
|
| 1837 |
didyoumean@1.2.2:
|
| 1838 |
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
| 1839 |
|
|
|
|
|
|
|
|
|
|
| 1840 |
diff@5.2.0:
|
| 1841 |
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
|
| 1842 |
engines: {node: '>=0.3.1'}
|
|
@@ -2049,8 +2078,8 @@ packages:
|
|
| 2049 |
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
| 2050 |
engines: {node: '>=0.8.x'}
|
| 2051 |
|
| 2052 |
-
eventsource-parser@1.
|
| 2053 |
-
resolution: {integrity: sha512-
|
| 2054 |
engines: {node: '>=14.18'}
|
| 2055 |
|
| 2056 |
extend@3.0.2:
|
|
@@ -2524,6 +2553,9 @@ packages:
|
|
| 2524 |
json-schema-traverse@0.4.1:
|
| 2525 |
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
| 2526 |
|
|
|
|
|
|
|
|
|
|
| 2527 |
json-stable-stringify-without-jsonify@1.0.1:
|
| 2528 |
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
| 2529 |
|
|
@@ -2531,6 +2563,11 @@ packages:
|
|
| 2531 |
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
|
| 2532 |
hasBin: true
|
| 2533 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2534 |
jsx-ast-utils@3.3.5:
|
| 2535 |
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
| 2536 |
engines: {node: '>=4.0'}
|
|
@@ -3357,6 +3394,9 @@ packages:
|
|
| 3357 |
scheduler@0.23.0:
|
| 3358 |
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
|
| 3359 |
|
|
|
|
|
|
|
|
|
|
| 3360 |
semver@6.3.1:
|
| 3361 |
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
| 3362 |
hasBin: true
|
|
@@ -3837,6 +3877,14 @@ packages:
|
|
| 3837 |
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
| 3838 |
engines: {node: '>=10'}
|
| 3839 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3840 |
zwitch@2.0.4:
|
| 3841 |
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
| 3842 |
|
|
@@ -3844,6 +3892,19 @@ snapshots:
|
|
| 3844 |
|
| 3845 |
'@aashutoshrathi/word-wrap@1.2.6': {}
|
| 3846 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3847 |
'@alloc/quick-lru@5.2.0': {}
|
| 3848 |
|
| 3849 |
'@ampproject/remapping@2.3.0':
|
|
@@ -5454,6 +5515,8 @@ snapshots:
|
|
| 5454 |
dependencies:
|
| 5455 |
'@types/ms': 0.7.34
|
| 5456 |
|
|
|
|
|
|
|
| 5457 |
'@types/estree@1.0.5': {}
|
| 5458 |
|
| 5459 |
'@types/hast@2.3.10':
|
|
@@ -5643,20 +5706,28 @@ snapshots:
|
|
| 5643 |
dependencies:
|
| 5644 |
humanize-ms: 1.2.1
|
| 5645 |
|
| 5646 |
-
ai@
|
| 5647 |
dependencies:
|
| 5648 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5649 |
nanoid: 3.3.6
|
|
|
|
| 5650 |
solid-swr-store: 0.10.7(solid-js@1.8.16)(swr-store@0.10.6)
|
| 5651 |
sswr: 2.0.0(svelte@4.2.15)
|
| 5652 |
swr: 2.2.0(react@18.2.0)
|
| 5653 |
swr-store: 0.10.6
|
| 5654 |
swrv: 1.0.4(vue@3.4.23(typescript@5.4.5))
|
|
|
|
| 5655 |
optionalDependencies:
|
|
|
|
| 5656 |
react: 18.2.0
|
| 5657 |
solid-js: 1.8.16
|
| 5658 |
svelte: 4.2.15
|
| 5659 |
vue: 3.4.23(typescript@5.4.5)
|
|
|
|
| 5660 |
|
| 5661 |
ajv@6.12.6:
|
| 5662 |
dependencies:
|
|
@@ -5872,6 +5943,8 @@ snapshots:
|
|
| 5872 |
ansi-styles: 4.3.0
|
| 5873 |
supports-color: 7.2.0
|
| 5874 |
|
|
|
|
|
|
|
| 5875 |
character-entities-legacy@1.1.4: {}
|
| 5876 |
|
| 5877 |
character-entities@1.2.4: {}
|
|
@@ -6033,6 +6106,8 @@ snapshots:
|
|
| 6033 |
|
| 6034 |
didyoumean@1.2.2: {}
|
| 6035 |
|
|
|
|
|
|
|
| 6036 |
diff@5.2.0: {}
|
| 6037 |
|
| 6038 |
dir-glob@3.0.1:
|
|
@@ -6381,7 +6456,7 @@ snapshots:
|
|
| 6381 |
|
| 6382 |
events@3.3.0: {}
|
| 6383 |
|
| 6384 |
-
eventsource-parser@1.
|
| 6385 |
|
| 6386 |
extend@3.0.2: {}
|
| 6387 |
|
|
@@ -6888,12 +6963,20 @@ snapshots:
|
|
| 6888 |
|
| 6889 |
json-schema-traverse@0.4.1: {}
|
| 6890 |
|
|
|
|
|
|
|
| 6891 |
json-stable-stringify-without-jsonify@1.0.1: {}
|
| 6892 |
|
| 6893 |
json5@1.0.2:
|
| 6894 |
dependencies:
|
| 6895 |
minimist: 1.2.8
|
| 6896 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6897 |
jsx-ast-utils@3.3.5:
|
| 6898 |
dependencies:
|
| 6899 |
array-includes: 3.1.8
|
|
@@ -7930,6 +8013,8 @@ snapshots:
|
|
| 7930 |
dependencies:
|
| 7931 |
loose-envify: 1.4.0
|
| 7932 |
|
|
|
|
|
|
|
| 7933 |
semver@6.3.1: {}
|
| 7934 |
|
| 7935 |
semver@7.6.0:
|
|
@@ -8517,4 +8602,10 @@ snapshots:
|
|
| 8517 |
|
| 8518 |
yocto-queue@0.1.0: {}
|
| 8519 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8520 |
zwitch@2.0.4: {}
|
|
|
|
| 51 |
specifier: ^1.0.1
|
| 52 |
version: 1.0.1
|
| 53 |
ai:
|
| 54 |
+
specifier: ^3.1.12
|
| 55 |
+
version: 3.1.22(openai@4.38.1)(react@18.2.0)(solid-js@1.8.16)(svelte@4.2.15)(vue@3.4.23(typescript@5.4.5))(zod@3.23.8)
|
| 56 |
class-variance-authority:
|
| 57 |
specifier: ^0.7.0
|
| 58 |
version: 0.7.0
|
|
|
|
| 214 |
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
|
| 215 |
engines: {node: '>=0.10.0'}
|
| 216 |
|
| 217 |
+
'@ai-sdk/provider-utils@0.0.11':
|
| 218 |
+
resolution: {integrity: sha512-JRDrqL2FGDmLh+a4R5qbS8UrWN9Lt7DpDIY1x6owgXjXkz3Umm1czs1X32VlL0M1dpoSxu4hGBFtXd56+kDzXA==}
|
| 219 |
+
engines: {node: '>=18'}
|
| 220 |
+
peerDependencies:
|
| 221 |
+
zod: ^3.0.0
|
| 222 |
+
peerDependenciesMeta:
|
| 223 |
+
zod:
|
| 224 |
+
optional: true
|
| 225 |
+
|
| 226 |
+
'@ai-sdk/provider@0.0.8':
|
| 227 |
+
resolution: {integrity: sha512-+gcMvyPUDfDXV9caN3CG5Le0M5K4CjqTdMV1ODg/AosApQiJW9ByN5imJPdI043zVdt+HS9WG+s0j4am7ca4bg==}
|
| 228 |
+
engines: {node: '>=18'}
|
| 229 |
+
|
| 230 |
'@alloc/quick-lru@5.2.0':
|
| 231 |
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
|
| 232 |
engines: {node: '>=10'}
|
|
|
|
| 1322 |
'@types/debug@4.1.12':
|
| 1323 |
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
| 1324 |
|
| 1325 |
+
'@types/diff-match-patch@1.0.36':
|
| 1326 |
+
resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==}
|
| 1327 |
+
|
| 1328 |
'@types/estree@1.0.5':
|
| 1329 |
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
|
| 1330 |
|
|
|
|
| 1479 |
resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
|
| 1480 |
engines: {node: '>= 8.0.0'}
|
| 1481 |
|
| 1482 |
+
ai@3.1.22:
|
| 1483 |
+
resolution: {integrity: sha512-Vgy490Q6p6pZ39VrRzL9ovr2N1YPsR+KvWNs+n73VAQoGBZtU/vwiiWrFU9LHXGhB9X+EBfQD0vixnTDS2dJWA==}
|
| 1484 |
+
engines: {node: '>=18'}
|
| 1485 |
peerDependencies:
|
| 1486 |
+
openai: ^4.42.0
|
| 1487 |
+
react: ^18 || ^19
|
| 1488 |
solid-js: ^1.7.7
|
| 1489 |
svelte: ^3.0.0 || ^4.0.0
|
| 1490 |
vue: ^3.3.4
|
| 1491 |
+
zod: ^3.0.0
|
| 1492 |
peerDependenciesMeta:
|
| 1493 |
+
openai:
|
| 1494 |
+
optional: true
|
| 1495 |
react:
|
| 1496 |
optional: true
|
| 1497 |
solid-js:
|
|
|
|
| 1500 |
optional: true
|
| 1501 |
vue:
|
| 1502 |
optional: true
|
| 1503 |
+
zod:
|
| 1504 |
+
optional: true
|
| 1505 |
|
| 1506 |
ajv@6.12.6:
|
| 1507 |
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
|
|
|
|
| 1683 |
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
| 1684 |
engines: {node: '>=10'}
|
| 1685 |
|
| 1686 |
+
chalk@5.3.0:
|
| 1687 |
+
resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==}
|
| 1688 |
+
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
| 1689 |
+
|
| 1690 |
character-entities-legacy@1.1.4:
|
| 1691 |
resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==}
|
| 1692 |
|
|
|
|
| 1863 |
didyoumean@1.2.2:
|
| 1864 |
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
| 1865 |
|
| 1866 |
+
diff-match-patch@1.0.5:
|
| 1867 |
+
resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
|
| 1868 |
+
|
| 1869 |
diff@5.2.0:
|
| 1870 |
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
|
| 1871 |
engines: {node: '>=0.3.1'}
|
|
|
|
| 2078 |
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
| 2079 |
engines: {node: '>=0.8.x'}
|
| 2080 |
|
| 2081 |
+
eventsource-parser@1.1.2:
|
| 2082 |
+
resolution: {integrity: sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==}
|
| 2083 |
engines: {node: '>=14.18'}
|
| 2084 |
|
| 2085 |
extend@3.0.2:
|
|
|
|
| 2553 |
json-schema-traverse@0.4.1:
|
| 2554 |
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
| 2555 |
|
| 2556 |
+
json-schema@0.4.0:
|
| 2557 |
+
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
|
| 2558 |
+
|
| 2559 |
json-stable-stringify-without-jsonify@1.0.1:
|
| 2560 |
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
| 2561 |
|
|
|
|
| 2563 |
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
|
| 2564 |
hasBin: true
|
| 2565 |
|
| 2566 |
+
jsondiffpatch@0.6.0:
|
| 2567 |
+
resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==}
|
| 2568 |
+
engines: {node: ^18.0.0 || >=20.0.0}
|
| 2569 |
+
hasBin: true
|
| 2570 |
+
|
| 2571 |
jsx-ast-utils@3.3.5:
|
| 2572 |
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
|
| 2573 |
engines: {node: '>=4.0'}
|
|
|
|
| 3394 |
scheduler@0.23.0:
|
| 3395 |
resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
|
| 3396 |
|
| 3397 |
+
secure-json-parse@2.7.0:
|
| 3398 |
+
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
| 3399 |
+
|
| 3400 |
semver@6.3.1:
|
| 3401 |
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
| 3402 |
hasBin: true
|
|
|
|
| 3877 |
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
| 3878 |
engines: {node: '>=10'}
|
| 3879 |
|
| 3880 |
+
zod-to-json-schema@3.22.5:
|
| 3881 |
+
resolution: {integrity: sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==}
|
| 3882 |
+
peerDependencies:
|
| 3883 |
+
zod: ^3.22.4
|
| 3884 |
+
|
| 3885 |
+
zod@3.23.8:
|
| 3886 |
+
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
|
| 3887 |
+
|
| 3888 |
zwitch@2.0.4:
|
| 3889 |
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
| 3890 |
|
|
|
|
| 3892 |
|
| 3893 |
'@aashutoshrathi/word-wrap@1.2.6': {}
|
| 3894 |
|
| 3895 |
+
'@ai-sdk/provider-utils@0.0.11(zod@3.23.8)':
|
| 3896 |
+
dependencies:
|
| 3897 |
+
'@ai-sdk/provider': 0.0.8
|
| 3898 |
+
eventsource-parser: 1.1.2
|
| 3899 |
+
nanoid: 3.3.6
|
| 3900 |
+
secure-json-parse: 2.7.0
|
| 3901 |
+
optionalDependencies:
|
| 3902 |
+
zod: 3.23.8
|
| 3903 |
+
|
| 3904 |
+
'@ai-sdk/provider@0.0.8':
|
| 3905 |
+
dependencies:
|
| 3906 |
+
json-schema: 0.4.0
|
| 3907 |
+
|
| 3908 |
'@alloc/quick-lru@5.2.0': {}
|
| 3909 |
|
| 3910 |
'@ampproject/remapping@2.3.0':
|
|
|
|
| 5515 |
dependencies:
|
| 5516 |
'@types/ms': 0.7.34
|
| 5517 |
|
| 5518 |
+
'@types/diff-match-patch@1.0.36': {}
|
| 5519 |
+
|
| 5520 |
'@types/estree@1.0.5': {}
|
| 5521 |
|
| 5522 |
'@types/hast@2.3.10':
|
|
|
|
| 5706 |
dependencies:
|
| 5707 |
humanize-ms: 1.2.1
|
| 5708 |
|
| 5709 |
+
ai@3.1.22(openai@4.38.1)(react@18.2.0)(solid-js@1.8.16)(svelte@4.2.15)(vue@3.4.23(typescript@5.4.5))(zod@3.23.8):
|
| 5710 |
dependencies:
|
| 5711 |
+
'@ai-sdk/provider': 0.0.8
|
| 5712 |
+
'@ai-sdk/provider-utils': 0.0.11(zod@3.23.8)
|
| 5713 |
+
eventsource-parser: 1.1.2
|
| 5714 |
+
json-schema: 0.4.0
|
| 5715 |
+
jsondiffpatch: 0.6.0
|
| 5716 |
nanoid: 3.3.6
|
| 5717 |
+
secure-json-parse: 2.7.0
|
| 5718 |
solid-swr-store: 0.10.7(solid-js@1.8.16)(swr-store@0.10.6)
|
| 5719 |
sswr: 2.0.0(svelte@4.2.15)
|
| 5720 |
swr: 2.2.0(react@18.2.0)
|
| 5721 |
swr-store: 0.10.6
|
| 5722 |
swrv: 1.0.4(vue@3.4.23(typescript@5.4.5))
|
| 5723 |
+
zod-to-json-schema: 3.22.5(zod@3.23.8)
|
| 5724 |
optionalDependencies:
|
| 5725 |
+
openai: 4.38.1
|
| 5726 |
react: 18.2.0
|
| 5727 |
solid-js: 1.8.16
|
| 5728 |
svelte: 4.2.15
|
| 5729 |
vue: 3.4.23(typescript@5.4.5)
|
| 5730 |
+
zod: 3.23.8
|
| 5731 |
|
| 5732 |
ajv@6.12.6:
|
| 5733 |
dependencies:
|
|
|
|
| 5943 |
ansi-styles: 4.3.0
|
| 5944 |
supports-color: 7.2.0
|
| 5945 |
|
| 5946 |
+
chalk@5.3.0: {}
|
| 5947 |
+
|
| 5948 |
character-entities-legacy@1.1.4: {}
|
| 5949 |
|
| 5950 |
character-entities@1.2.4: {}
|
|
|
|
| 6106 |
|
| 6107 |
didyoumean@1.2.2: {}
|
| 6108 |
|
| 6109 |
+
diff-match-patch@1.0.5: {}
|
| 6110 |
+
|
| 6111 |
diff@5.2.0: {}
|
| 6112 |
|
| 6113 |
dir-glob@3.0.1:
|
|
|
|
| 6456 |
|
| 6457 |
events@3.3.0: {}
|
| 6458 |
|
| 6459 |
+
eventsource-parser@1.1.2: {}
|
| 6460 |
|
| 6461 |
extend@3.0.2: {}
|
| 6462 |
|
|
|
|
| 6963 |
|
| 6964 |
json-schema-traverse@0.4.1: {}
|
| 6965 |
|
| 6966 |
+
json-schema@0.4.0: {}
|
| 6967 |
+
|
| 6968 |
json-stable-stringify-without-jsonify@1.0.1: {}
|
| 6969 |
|
| 6970 |
json5@1.0.2:
|
| 6971 |
dependencies:
|
| 6972 |
minimist: 1.2.8
|
| 6973 |
|
| 6974 |
+
jsondiffpatch@0.6.0:
|
| 6975 |
+
dependencies:
|
| 6976 |
+
'@types/diff-match-patch': 1.0.36
|
| 6977 |
+
chalk: 5.3.0
|
| 6978 |
+
diff-match-patch: 1.0.5
|
| 6979 |
+
|
| 6980 |
jsx-ast-utils@3.3.5:
|
| 6981 |
dependencies:
|
| 6982 |
array-includes: 3.1.8
|
|
|
|
| 8013 |
dependencies:
|
| 8014 |
loose-envify: 1.4.0
|
| 8015 |
|
| 8016 |
+
secure-json-parse@2.7.0: {}
|
| 8017 |
+
|
| 8018 |
semver@6.3.1: {}
|
| 8019 |
|
| 8020 |
semver@7.6.0:
|
|
|
|
| 8602 |
|
| 8603 |
yocto-queue@0.1.0: {}
|
| 8604 |
|
| 8605 |
+
zod-to-json-schema@3.22.5(zod@3.23.8):
|
| 8606 |
+
dependencies:
|
| 8607 |
+
zod: 3.23.8
|
| 8608 |
+
|
| 8609 |
+
zod@3.23.8: {}
|
| 8610 |
+
|
| 8611 |
zwitch@2.0.4: {}
|
tailwind.config.ts
CHANGED
|
@@ -1,80 +1,89 @@
|
|
| 1 |
-
import type { Config } from
|
| 2 |
|
| 3 |
const config = {
|
| 4 |
-
darkMode: [
|
| 5 |
content: [
|
| 6 |
'./pages/**/*.{ts,tsx}',
|
| 7 |
'./components/**/*.{ts,tsx}',
|
| 8 |
'./app/**/*.{ts,tsx}',
|
| 9 |
'./src/**/*.{ts,tsx}',
|
| 10 |
-
|
| 11 |
-
prefix:
|
| 12 |
theme: {
|
| 13 |
container: {
|
| 14 |
center: true,
|
| 15 |
-
padding:
|
| 16 |
screens: {
|
| 17 |
-
|
| 18 |
},
|
| 19 |
},
|
| 20 |
extend: {
|
| 21 |
colors: {
|
| 22 |
-
border:
|
| 23 |
-
input:
|
| 24 |
-
ring:
|
| 25 |
-
background:
|
| 26 |
-
foreground:
|
| 27 |
primary: {
|
| 28 |
-
DEFAULT:
|
| 29 |
-
foreground:
|
| 30 |
},
|
| 31 |
secondary: {
|
| 32 |
-
DEFAULT:
|
| 33 |
-
foreground:
|
| 34 |
},
|
| 35 |
destructive: {
|
| 36 |
-
DEFAULT:
|
| 37 |
-
foreground:
|
| 38 |
},
|
| 39 |
muted: {
|
| 40 |
-
DEFAULT:
|
| 41 |
-
foreground:
|
| 42 |
},
|
| 43 |
accent: {
|
| 44 |
-
DEFAULT:
|
| 45 |
-
foreground:
|
| 46 |
},
|
| 47 |
popover: {
|
| 48 |
-
DEFAULT:
|
| 49 |
-
foreground:
|
| 50 |
},
|
| 51 |
card: {
|
| 52 |
-
DEFAULT:
|
| 53 |
-
foreground:
|
| 54 |
},
|
| 55 |
},
|
| 56 |
borderRadius: {
|
| 57 |
-
lg:
|
| 58 |
-
md:
|
| 59 |
-
sm:
|
| 60 |
},
|
| 61 |
keyframes: {
|
| 62 |
-
|
| 63 |
-
from: { height:
|
| 64 |
-
to: { height:
|
| 65 |
},
|
| 66 |
-
|
| 67 |
-
from: { height:
|
| 68 |
-
to: { height:
|
| 69 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
},
|
| 71 |
animation: {
|
| 72 |
-
|
| 73 |
-
|
|
|
|
| 74 |
},
|
| 75 |
},
|
| 76 |
},
|
| 77 |
-
plugins: [require(
|
| 78 |
-
} satisfies Config
|
| 79 |
|
| 80 |
-
export default config
|
|
|
|
| 1 |
+
import type { Config } from 'tailwindcss';
|
| 2 |
|
| 3 |
const config = {
|
| 4 |
+
darkMode: ['class'],
|
| 5 |
content: [
|
| 6 |
'./pages/**/*.{ts,tsx}',
|
| 7 |
'./components/**/*.{ts,tsx}',
|
| 8 |
'./app/**/*.{ts,tsx}',
|
| 9 |
'./src/**/*.{ts,tsx}',
|
| 10 |
+
],
|
| 11 |
+
prefix: '',
|
| 12 |
theme: {
|
| 13 |
container: {
|
| 14 |
center: true,
|
| 15 |
+
padding: '2rem',
|
| 16 |
screens: {
|
| 17 |
+
'2xl': '1400px',
|
| 18 |
},
|
| 19 |
},
|
| 20 |
extend: {
|
| 21 |
colors: {
|
| 22 |
+
border: 'hsl(var(--border))',
|
| 23 |
+
input: 'hsl(var(--input))',
|
| 24 |
+
ring: 'hsl(var(--ring))',
|
| 25 |
+
background: 'hsl(var(--background))',
|
| 26 |
+
foreground: 'hsl(var(--foreground))',
|
| 27 |
primary: {
|
| 28 |
+
DEFAULT: 'hsl(var(--primary))',
|
| 29 |
+
foreground: 'hsl(var(--primary-foreground))',
|
| 30 |
},
|
| 31 |
secondary: {
|
| 32 |
+
DEFAULT: 'hsl(var(--secondary))',
|
| 33 |
+
foreground: 'hsl(var(--secondary-foreground))',
|
| 34 |
},
|
| 35 |
destructive: {
|
| 36 |
+
DEFAULT: 'hsl(var(--destructive))',
|
| 37 |
+
foreground: 'hsl(var(--destructive-foreground))',
|
| 38 |
},
|
| 39 |
muted: {
|
| 40 |
+
DEFAULT: 'hsl(var(--muted))',
|
| 41 |
+
foreground: 'hsl(var(--muted-foreground))',
|
| 42 |
},
|
| 43 |
accent: {
|
| 44 |
+
DEFAULT: 'hsl(var(--accent))',
|
| 45 |
+
foreground: 'hsl(var(--accent-foreground))',
|
| 46 |
},
|
| 47 |
popover: {
|
| 48 |
+
DEFAULT: 'hsl(var(--popover))',
|
| 49 |
+
foreground: 'hsl(var(--popover-foreground))',
|
| 50 |
},
|
| 51 |
card: {
|
| 52 |
+
DEFAULT: 'hsl(var(--card))',
|
| 53 |
+
foreground: 'hsl(var(--card-foreground))',
|
| 54 |
},
|
| 55 |
},
|
| 56 |
borderRadius: {
|
| 57 |
+
lg: 'var(--radius)',
|
| 58 |
+
md: 'calc(var(--radius) - 2px)',
|
| 59 |
+
sm: 'calc(var(--radius) - 4px)',
|
| 60 |
},
|
| 61 |
keyframes: {
|
| 62 |
+
'accordion-down': {
|
| 63 |
+
from: { height: '0' },
|
| 64 |
+
to: { height: 'var(--radix-accordion-content-height)' },
|
| 65 |
},
|
| 66 |
+
'accordion-up': {
|
| 67 |
+
from: { height: 'var(--radix-accordion-content-height)' },
|
| 68 |
+
to: { height: '0' },
|
| 69 |
},
|
| 70 |
+
progress: {
|
| 71 |
+
'0%': { transform: ' translateX(0) scaleX(0)' },
|
| 72 |
+
'40%': { transform: 'translateX(0) scaleX(0.4)' },
|
| 73 |
+
'100%': { transform: 'translateX(100%) scaleX(0.5)' },
|
| 74 |
+
},
|
| 75 |
+
},
|
| 76 |
+
transformOrigin: {
|
| 77 |
+
'left-right': '0% 50%',
|
| 78 |
},
|
| 79 |
animation: {
|
| 80 |
+
'accordion-down': 'accordion-down 0.2s ease-out',
|
| 81 |
+
'accordion-up': 'accordion-up 0.2s ease-out',
|
| 82 |
+
progress: 'progress 1s infinite linear',
|
| 83 |
},
|
| 84 |
},
|
| 85 |
},
|
| 86 |
+
plugins: [require('tailwindcss-animate')],
|
| 87 |
+
} satisfies Config;
|
| 88 |
|
| 89 |
+
export default config;
|