Move to clerk (#14)

This commit is contained in:
Jared Palmer 2023-06-15 08:42:23 -04:00 committed by GitHub
commit 385b31d42c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 780 additions and 313 deletions

View file

@ -4,6 +4,7 @@ import { revalidatePath } from 'next/cache'
import { kv } from '@vercel/kv' import { kv } from '@vercel/kv'
import { type Chat } from '@/lib/types' import { type Chat } from '@/lib/types'
import { currentUser } from '@clerk/nextjs'
export async function getChats(userId?: string | null) { export async function getChats(userId?: string | null) {
if (!userId) { if (!userId) {
@ -12,7 +13,9 @@ export async function getChats(userId?: string | null) {
try { try {
const pipeline = kv.pipeline() const pipeline = kv.pipeline()
const chats: string[] = await kv.zrange(`user:chat:${userId}`, 0, -1) const chats: string[] = await kv.zrange(`user:chat:${userId}`, 0, -1, {
rev: true
})
for (const chat of chats) { for (const chat of chats) {
pipeline.hgetall(chat) pipeline.hgetall(chat)
@ -40,25 +43,20 @@ export async function getChat(id: string, userId: string) {
return chat return chat
} }
export async function removeChat({ export async function removeChat({ id, path }: { id: string; path: string }) {
id, const user = await currentUser()
path,
userId if (!user) {
}: { throw new Error('Unauthorized')
id: string }
userId: string
path: string
}) {
// @todo next-auth@v5 doesn't work in server actions yet
// const session = await auth();
const uid = await kv.hget<string>(`chat:${id}`, 'userId') const uid = await kv.hget<string>(`chat:${id}`, 'userId')
if (uid !== userId) { if (uid !== user.id) {
throw new Error('Unauthorized') throw new Error('Unauthorized')
} }
await kv.del(`chat:${id}`) await kv.del(`chat:${id}`)
await kv.zrem(`user:chat:${userId}`, `chat:${id}`) await kv.zrem(`user:chat:${user.id}`, `chat:${id}`)
revalidatePath('/') revalidatePath('/')
revalidatePath('/chat/[id]') revalidatePath(path)
} }

View file

@ -1,2 +0,0 @@
export { GET, POST } from '@/auth'
// export const runtime = 'edge'

View file

@ -1,11 +1,11 @@
import { auth } from '@/auth'
import { kv } from '@vercel/kv' import { kv } from '@vercel/kv'
import { OpenAIStream, StreamingTextResponse } from 'ai-connector' import { OpenAIStream, StreamingTextResponse } from 'ai-connector'
import { Configuration, OpenAIApi } from 'openai-edge' import { Configuration, OpenAIApi } from 'openai-edge'
import { nanoid } from '@/lib/utils' import { nanoid } from '@/lib/utils'
import { currentUser } from '@clerk/nextjs'
// export const runtime = 'edge' export const runtime = 'edge'
const configuration = new Configuration({ const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY apiKey: process.env.OPENAI_API_KEY
@ -17,13 +17,15 @@ if (!process.env.OPENAI_API_KEY) {
throw new Error('Missing env var from OpenAI') throw new Error('Missing env var from OpenAI')
} }
export const POST = auth(async function POST(req: Request) { export async function POST(req: Request) {
const user = await currentUser()
if (user == null) {
return new Response('Unauthorized', { status: 401 })
}
const json = await req.json() const json = await req.json()
// @ts-ignore const { messages } = json
const messages = json.messages.map((m: any) => ({
content: m.content,
role: m.role
}))
const res = await openai.createChatCompletion({ const res = await openai.createChatCompletion({
model: 'gpt-3.5-turbo', model: 'gpt-3.5-turbo',
messages, messages,
@ -35,14 +37,10 @@ export const POST = auth(async function POST(req: Request) {
stream: true stream: true
}) })
const stream = await OpenAIStream(res, { const stream = OpenAIStream(res, {
async onCompletion(completion) { async onCompletion(completion) {
// @ts-ignore
if (req.auth?.user?.email == null) {
return
}
const title = json.messages[0].content.substring(0, 100) const title = json.messages[0].content.substring(0, 100)
const userId = (req as any).auth?.user?.email const userId = user.id
const id = json.id ?? nanoid() const id = json.id ?? nanoid()
const createdAt = Date.now() const createdAt = Date.now()
const payload = { const payload = {
@ -67,4 +65,4 @@ export const POST = auth(async function POST(req: Request) {
}) })
return new StreamingTextResponse(stream) return new StreamingTextResponse(stream)
}) }

View file

@ -1,5 +1,5 @@
import { type Metadata } from 'next' import { type Metadata } from 'next'
import { auth } from '@/auth' import { auth } from '@clerk/nextjs'
import { Chat } from '@/components/chat' import { Chat } from '@/components/chat'
import { getChat } from '@/app/actions' import { getChat } from '@/app/actions'
@ -16,16 +16,16 @@ export interface ChatPageProps {
export async function generateMetadata({ export async function generateMetadata({
params params
}: ChatPageProps): Promise<Metadata> { }: ChatPageProps): Promise<Metadata> {
const session = await auth() const { user } = await auth()
const chat = await getChat(params.id, session?.user?.email ?? '') const chat = await getChat(params.id, user?.id ?? '')
return { return {
title: chat?.title.slice(0, 50) ?? 'Chat' title: chat?.title.slice(0, 50) ?? 'Chat'
} }
} }
export default async function ChatPage({ params }: ChatPageProps) { export default async function ChatPage({ params }: ChatPageProps) {
const session = await auth() const { user } = await auth()
const chat = await getChat(params.id, session?.user?.email ?? '') const chat = await getChat(params.id, user?.id ?? '')
return <Chat id={chat.id} initialMessages={chat.messages} /> return <Chat id={chat.id} initialMessages={chat.messages} />
} }

View file

@ -1,11 +1,13 @@
import { Metadata } from 'next' import { Metadata } from 'next'
import { ClerkProvider } from '@clerk/nextjs'
import '@/app/globals.css' import '@/app/globals.css'
import { fontMono, fontSans } from '@/lib/fonts' import { fontMono, fontSans } from '@/lib/fonts'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { Header } from '@/components/header'
import { TailwindIndicator } from '@/components/tailwind-indicator' import { TailwindIndicator } from '@/components/tailwind-indicator'
import { ThemeProvider } from '@/components/theme-provider' import { ThemeProvider } from '@/components/theme-provider'
import { Header } from '@/components/header'
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {
@ -30,6 +32,7 @@ interface RootLayoutProps {
export default function RootLayout({ children }: RootLayoutProps) { export default function RootLayout({ children }: RootLayoutProps) {
return ( return (
<ClerkProvider>
<html lang="en" suppressHydrationWarning> <html lang="en" suppressHydrationWarning>
<head /> <head />
<body <body
@ -49,5 +52,6 @@ export default function RootLayout({ children }: RootLayoutProps) {
</ThemeProvider> </ThemeProvider>
</body> </body>
</html> </html>
</ClerkProvider>
) )
} }

View file

@ -1,8 +1,11 @@
import { nanoid } from '@/lib/utils'
import { Chat } from '@/components/chat' import { Chat } from '@/components/chat'
// export const runtime = 'edge' // export const runtime = 'edge'
export const preferredRegion = 'home' export const preferredRegion = 'home'
export default async function IndexPage() { export default function IndexPage() {
return <Chat /> const id = nanoid()
return <Chat id={id} />
} }

View file

@ -0,0 +1,42 @@
import { ClerkLoaded, ClerkLoading, SignIn } from '@clerk/nextjs'
import { cn } from '@/lib/utils'
import { ExternalLink } from '@/components/external-link'
import { buttonVariants } from '@/components/ui/button'
import { IconSpinner } from '@/components/ui/icons'
export default function SignInPage() {
return (
<div className="flex h-full min-h-screen flex-col items-center justify-center">
<div className="space-y-4">
<ClerkLoading>
<div className="flex items-center text-sm text-muted-foreground">
<IconSpinner className="mr-2 h-5 w-5" /> Loading...
</div>
</ClerkLoading>
<ClerkLoaded>
<SignIn
appearance={{
elements: {
card: 'shadow rounded-lg border border-border',
formFieldInput:
'flex h-9 w-full rounded-md px-3 py-2 text-sm shadow-sm',
formButtonPrimary: 'normal-case',
footerAction: 'text-sm',
footerActionLink: 'font-medium'
}
}}
/>
<p className="hidden px-2 text-center text-xs leading-normal text-muted-foreground sm:block">
Open source AI chatbot app built with{' '}
<ExternalLink href="https://nextjs.org">Next.js</ExternalLink> and{' '}
<ExternalLink href="https://vercel.com/storage/kv">
Vercel KV
</ExternalLink>
.
</p>
</ClerkLoaded>
</div>
</div>
)
}

View file

@ -0,0 +1,43 @@
import { ClerkLoaded, ClerkLoading, SignUp } from '@clerk/nextjs'
import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button'
import { ExternalLink } from '@/components/external-link'
import { IconSpinner } from '@/components/ui/icons'
export default function Page() {
return (
<div className="flex h-full min-h-screen flex-col items-center justify-center">
<div className="space-y-4">
<ClerkLoading>
<div className="flex items-center text-sm text-muted-foreground">
<IconSpinner className="mr-2 h-5 w-5" /> Loading...
</div>
</ClerkLoading>
<ClerkLoaded>
<SignUp
appearance={{
elements: {
card: 'shadow rounded-lg border border-border',
formFieldInput:
'flex h-9 w-full rounded-md px-3 py-2 text-sm shadow-sm',
formButtonPrimary: 'normal-case',
footerAction: 'text-sm',
footerActionLink: 'font-medium hover:underline',
identityPreview: 'rounded-md'
}
}}
/>
<p className="hidden px-2 text-center text-xs leading-normal text-muted-foreground sm:block">
Open source AI chatbot app built with{' '}
<ExternalLink href="https://nextjs.org">Next.js</ExternalLink> and{' '}
<ExternalLink href="https://vercel.com/storage/kv">
Vercel KV
</ExternalLink>
.
</p>
</ClerkLoaded>
</div>
</div>
)
}

27
auth.ts
View file

@ -1,27 +0,0 @@
import { NextResponse } from 'next/server'
import NextAuth from '@auth/nextjs'
import GitHub from '@auth/nextjs/providers/github'
export const {
handlers: { GET, POST },
auth,
CSRF_experimental
} = NextAuth({
// @ts-ignore
providers: [GitHub],
session: { strategy: 'jwt' },
async authorized({ request, auth }: any) {
const url = request.nextUrl
if (request.method === 'POST') {
const { authToken } = (await request.json()) ?? {}
// If the request has a valid auth token, it is authorized
const valid = true
if (valid) return true
return NextResponse.json('Invalid auth token', { status: 401 })
}
// Logged in users are authenticated, otherwise redirect to login page
return !!auth
}
})

View file

@ -3,26 +3,12 @@
import * as React from 'react' import * as React from 'react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useAtBottom } from '@/lib/hooks/use-at-bottom'
import { Button, type ButtonProps } from '@/components/ui/button' import { Button, type ButtonProps } from '@/components/ui/button'
import { IconArrowDown } from '@/components/ui/icons' import { IconArrowDown } from '@/components/ui/icons'
export function ButtonScrollToBottom({ className, ...props }: ButtonProps) { export function ButtonScrollToBottom({ className, ...props }: ButtonProps) {
const [isAtBottom, setIsAtBottom] = React.useState(false) const isAtBottom = useAtBottom()
React.useEffect(() => {
const handleScroll = () => {
setIsAtBottom(
window.innerHeight + window.scrollY > document.body.offsetHeight - 100
)
}
window.addEventListener('scroll', handleScroll, { passive: true })
handleScroll()
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [])
return ( return (
<Button <Button

View file

@ -1,5 +1,3 @@
'use client'
import { type Message } from 'ai-connector' import { type Message } from 'ai-connector'
import { Separator } from '@/components/ui/separator' import { Separator } from '@/components/ui/separator'

View file

@ -20,7 +20,7 @@ export function ChatMessageActions({
return ( return (
<div <div
className={cn( className={cn(
'flex items-center justify-end w-full md:absolute md:-top-2 md:-right-10 md:opacity-0 group-hover:opacity-100 transition-opacity', 'flex items-center justify-end transition-opacity group-hover:opacity-100 md:absolute md:-right-10 md:-top-2 md:opacity-0',
className className
)} )}
{...props} {...props}

View file

@ -15,7 +15,7 @@ export interface ChatMessageProps {
export function ChatMessage({ message, ...props }: ChatMessageProps) { export function ChatMessage({ message, ...props }: ChatMessageProps) {
return ( return (
<div <div
className={cn('relative mb-4 flex items-start group md:-ml-12')} className={cn('group relative mb-4 flex items-start md:-ml-12')}
{...props} {...props}
> >
<div <div
@ -28,10 +28,9 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
> >
{message.role === 'user' ? <IconUser /> : <IconOpenAI />} {message.role === 'user' ? <IconUser /> : <IconOpenAI />}
</div> </div>
<div className="ml-4 px-1 space-y-2 flex-1"> <div className="ml-4 flex-1 space-y-2 overflow-hidden px-1">
<div>
<MemoizedReactMarkdown <MemoizedReactMarkdown
className="prose leading-6 dark:prose-invert prose-p:leading-[1.8rem] prose-pre:rounded-md prose-pre:bg-[#282c34]" className="prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0"
remarkPlugins={[remarkGfm, remarkMath]} remarkPlugins={[remarkGfm, remarkMath]}
components={{ components={{
p({ children }) { p({ children }) {
@ -41,9 +40,7 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
if (children.length) { if (children.length) {
if (children[0] == '▍') { if (children[0] == '▍') {
return ( return (
<span className="mt-1 animate-pulse cursor-default"> <span className="mt-1 animate-pulse cursor-default"></span>
</span>
) )
} }
@ -52,24 +49,27 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
const match = /language-(\w+)/.exec(className || '') const match = /language-(\w+)/.exec(className || '')
return !inline ? ( if (inline) {
return (
<code className={className} {...props}>
{children}
</code>
)
}
return (
<CodeBlock <CodeBlock
key={Math.random()} key={Math.random()}
language={(match && match[1]) || ''} language={(match && match[1]) || ''}
value={String(children).replace(/\n$/, '')} value={String(children).replace(/\n$/, '')}
{...props} {...props}
/> />
) : (
<code className={className} {...props}>
{children}
</code>
) )
} }
}} }}
> >
{message.content} {message.content}
</MemoizedReactMarkdown> </MemoizedReactMarkdown>
</div>
<ChatMessageActions message={message} /> <ChatMessageActions message={message} />
</div> </div>
</div> </div>

View file

@ -1,4 +1,4 @@
import { type UseChatHelpers } from 'ai-connector' import { type UseChatHelpers } from 'ai-connector/react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { ExternalLink } from '@/components/external-link' import { ExternalLink } from '@/components/external-link'
@ -21,6 +21,7 @@ export interface ChatPanelProps
} }
export function ChatPanel({ export function ChatPanel({
id,
isLoading, isLoading,
stop, stop,
append, append,
@ -58,8 +59,9 @@ export function ChatPanel({
</div> </div>
<div className="space-y-4 border-t bg-background px-4 py-2 shadow-lg sm:rounded-t-xl sm:border md:py-4"> <div className="space-y-4 border-t bg-background px-4 py-2 shadow-lg sm:rounded-t-xl sm:border md:py-4">
<PromptForm <PromptForm
onSubmit={value => { onSubmit={async value => {
append({ await append({
id,
content: value, content: value,
role: 'user' role: 'user'
}) })

View file

@ -0,0 +1,29 @@
'use client'
import * as React from 'react'
import { useInView } from 'react-intersection-observer'
import { useAtBottom } from '@/lib/hooks/use-at-bottom'
interface ChatScrollAnchorProps {
trackVisibility?: boolean
}
export function ChatScrollAnchor({ trackVisibility }: ChatScrollAnchorProps) {
const isAtBottom = useAtBottom()
const { ref, entry, inView } = useInView({
trackVisibility,
delay: 100,
rootMargin: '0px 0px -150px 0px'
})
React.useEffect(() => {
if (isAtBottom && trackVisibility && !inView) {
entry?.target.scrollIntoView({
block: 'start'
})
}
}, [inView, entry, isAtBottom, trackVisibility])
return <div ref={ref} className="h-px w-full" />
}

View file

@ -1,11 +1,12 @@
'use client' 'use client'
import { useChat, type Message } from 'ai-connector' import { useChat, type Message } from 'ai-connector/react'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { ChatList } from '@/components/chat-list' import { ChatList } from '@/components/chat-list'
import { ChatPanel } from '@/components/chat-panel' import { ChatPanel } from '@/components/chat-panel'
import { EmptyScreen } from '@/components/empty-screen' import { EmptyScreen } from '@/components/empty-screen'
import { ChatScrollAnchor } from '@/components/chat-scroll-anchor'
export interface ChatProps extends React.ComponentProps<'div'> { export interface ChatProps extends React.ComponentProps<'div'> {
initialMessages?: Message[] initialMessages?: Message[]
@ -16,14 +17,20 @@ export function Chat({ id, initialMessages, className }: ChatProps) {
const { messages, append, reload, stop, isLoading, input, setInput } = const { messages, append, reload, stop, isLoading, input, setInput } =
useChat({ useChat({
initialMessages, initialMessages,
id,
body: {
id id
}
}) })
return ( return (
<> <>
<div className={cn('pb-[200px] pt-4 md:pt-10', className)}> <div className={cn('pb-[200px] pt-4 md:pt-10', className)}>
{messages.length ? ( {messages.length ? (
<>
<ChatList messages={messages} /> <ChatList messages={messages} />
<ChatScrollAnchor trackVisibility={isLoading} />
</>
) : ( ) : (
<EmptyScreen setInput={setInput} /> <EmptyScreen setInput={setInput} />
)} )}

View file

@ -1,8 +1,8 @@
import { cn } from '@/lib/utils' import { UseChatHelpers } from 'ai-connector/react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { ExternalLink } from '@/components/external-link' import { ExternalLink } from '@/components/external-link'
import { IconArrowRight } from '@/components/ui/icons' import { IconArrowRight } from '@/components/ui/icons'
import { UseChatHelpers } from 'ai-connector'
const exampleMessages = [ const exampleMessages = [
{ {

View file

@ -1,29 +1,47 @@
import { Suspense } from 'react' import { Suspense } from 'react'
import { auth } from '@/auth'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { buttonVariants } from '@/components/ui/button' import { buttonVariants } from '@/components/ui/button'
import { Sidebar } from '@/components/sidebar' import { Sidebar } from '@/components/sidebar'
import { UserMenu } from '@/components/user-menu'
import { SidebarList } from '@/components/sidebar-list' import { SidebarList } from '@/components/sidebar-list'
import { IconGitHub, IconSeparator, IconVercel } from '@/components/ui/icons' import { IconGitHub, IconSeparator, IconVercel } from '@/components/ui/icons'
import { UserButton, currentUser } from '@clerk/nextjs'
export async function Header() { export async function Header() {
const session = await auth() const user = await currentUser()
return ( return (
<header className="sticky top-0 z-50 flex h-16 w-full shrink-0 items-center justify-between border-b bg-gradient-to-b from-background/10 via-background/50 to-background/80 px-4 backdrop-blur-xl"> <header className="sticky top-0 z-50 flex h-16 w-full shrink-0 items-center justify-between border-b bg-gradient-to-b from-background/10 via-background/50 to-background/80 px-4 backdrop-blur-xl">
<div className="flex items-center"> <div className="flex items-center">
{/* @ts-ignore */} {/* @ts-ignore */}
<Sidebar session={session}> <Sidebar user={user?.id}>
<Suspense fallback={<div className="flex-1 overflow-auto" />}> <Suspense fallback={<div className="flex-1 overflow-auto" />}>
{/* @ts-ignore */} {/* @ts-ignore */}
<SidebarList session={session} /> <SidebarList userId={user?.id} />
</Suspense> </Suspense>
</Sidebar> </Sidebar>
<div className="flex items-center"> <div className="flex items-center">
<IconSeparator className="h-6 w-6 text-muted-foreground/50" /> <IconSeparator className="h-6 w-6 text-muted-foreground/50" />
<UserMenu session={session} /> <UserButton
showName
appearance={{
elements: {
avatarBox: 'w-6 h-6 rounded-full overflow-hidden',
userButtonBox: 'flex-row-reverse',
userButtonOuterIdentifier: 'text-primary',
userButtonPopoverCard:
'shadow-lg rounded-lg p-0 border border-border w-[200px] dark:bg-zinc-950 dark:text-zinc-50',
userButtonPopoverFooter:
'p-4 border-t border-border [&>*]:dark:text-zinc-600',
userPreview: 'p-4 border-b border-border m-0',
userButtonPopoverActionButton: 'px-1 gap-1',
userButtonPopoverActionButtonText:
'text-sm tracking-normal dark:text-zinc-400',
userButtonPopoverActionButtonIcon:
'h-4 w-4 text-muted-foreground'
}
}}
/>
</div> </div>
</div> </div>
<div className="flex items-center justify-end space-x-2"> <div className="flex items-center justify-end space-x-2">
@ -34,7 +52,7 @@ export async function Header() {
className={cn(buttonVariants({ variant: 'outline' }))} className={cn(buttonVariants({ variant: 'outline' }))}
> >
<IconGitHub /> <IconGitHub />
<span className="hidden md:flex ml-2">GitHub</span> <span className="ml-2 hidden md:flex">GitHub</span>
</a> </a>
<a <a
href="https://github.com/vercel/nextjs-ai-chatbot/" href="https://github.com/vercel/nextjs-ai-chatbot/"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -1,11 +1,11 @@
'use client' 'use client'
import * as React from 'react' import * as React from 'react'
import { signIn } from '@auth/nextjs/client'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { Button, type ButtonProps } from '@/components/ui/button' import { Button, type ButtonProps } from '@/components/ui/button'
import { IconGitHub, IconSpinner } from '@/components/ui/icons' import { IconGitHub, IconSpinner } from '@/components/ui/icons'
import { useClerk } from '@clerk/nextjs'
interface LoginButtonProps extends ButtonProps { interface LoginButtonProps extends ButtonProps {
text?: string text?: string
@ -17,13 +17,14 @@ export function LoginButton({
...props ...props
}: LoginButtonProps) { }: LoginButtonProps) {
const [isLoading, setIsLoading] = React.useState(false) const [isLoading, setIsLoading] = React.useState(false)
const clerk = useClerk()
return ( return (
<Button <Button
variant="outline" variant="outline"
onClick={() => { onClick={() => {
setIsLoading(true) setIsLoading(true)
signIn('github') clerk.openSignIn({})
}} }}
disabled={isLoading} disabled={isLoading}
className={cn(className)} className={cn(className)}

View file

@ -1,6 +1,7 @@
import * as React from 'react' import * as React from 'react'
import Link from 'next/link' import Link from 'next/link'
import Textarea from 'react-textarea-autosize' import Textarea from 'react-textarea-autosize'
import { UseChatHelpers } from 'ai-connector/react'
import { useEnterSubmit } from '@/lib/hooks/use-enter-submit' import { useEnterSubmit } from '@/lib/hooks/use-enter-submit'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@ -12,7 +13,6 @@ import {
TooltipTrigger TooltipTrigger
} from '@/components/ui/tooltip' } from '@/components/ui/tooltip'
import { IconArrowElbow, IconPlus } from '@/components/ui/icons' import { IconArrowElbow, IconPlus } from '@/components/ui/icons'
import { UseChatHelpers } from 'ai-connector'
export interface PromptProps export interface PromptProps
extends Pick<UseChatHelpers, 'input' | 'setInput'> { extends Pick<UseChatHelpers, 'input' | 'setInput'> {

View file

@ -1,7 +1,6 @@
'use client' 'use client'
import * as React from 'react' import * as React from 'react'
import { useTransition } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
@ -17,25 +16,21 @@ import {
AlertDialogTitle AlertDialogTitle
} from '@/components/ui/alert-dialog' } from '@/components/ui/alert-dialog'
import { Button, buttonVariants } from '@/components/ui/button' import { Button, buttonVariants } from '@/components/ui/button'
import { removeChat } from '@/app/actions'
import { IconMessage, IconSpinner, IconTrash } from '@/components/ui/icons' import { IconMessage, IconSpinner, IconTrash } from '@/components/ui/icons'
export function SidebarItem({ interface SidebarItemProps {
title,
href,
id,
userId
}: {
title: string title: string
href: string href: string
id: string id: string
userId: string removeChat: (args: { id: string; path: string }) => Promise<void>
}) { }
export function SidebarItem({ title, href, id, removeChat }: SidebarItemProps) {
const [open, setIsOpen] = React.useState(false) const [open, setIsOpen] = React.useState(false)
const pathname = usePathname() const pathname = usePathname()
const router = useRouter() const router = useRouter()
const [isPending, startTransition] = React.useTransition()
const isActive = pathname === href const isActive = pathname === href
const [isPending, startTransition] = useTransition()
if (!id) return null if (!id) return null
@ -85,7 +80,7 @@ export function SidebarItem({
onClick={event => { onClick={event => {
event.preventDefault() event.preventDefault()
startTransition(async () => { startTransition(async () => {
await removeChat({ id, userId, path: href }) await removeChat({ id, path: href })
setIsOpen(false) setIsOpen(false)
router.push('/') router.push('/')
}) })

View file

@ -1,41 +1,25 @@
import { getChats } from '@/app/actions' import { getChats, removeChat } from '@/app/actions'
import { Session } from '@auth/core/types'
import { SidebarItem } from '@/components/sidebar-item' import { SidebarItem } from '@/components/sidebar-item'
import { LoginButton } from '@/components/login-button'
export interface SidebarListProps { export interface SidebarListProps {
session?: Session userId?: string
} }
export async function SidebarList({ session }: SidebarListProps) { export async function SidebarList({ userId }: SidebarListProps) {
if (!session?.user?.email) { const chats = await getChats(userId)
return (
<div className="flex-1 p-6 flex text-sm items-center flex-col space-y-4">
<div className="space-y-1 text-center">
<p className="font-medium">You are not logged in!</p>
<p className="text-muted-foreground">
Please login for chat history.
</p>
</div>
<LoginButton />
</div>
)
}
const chats = await getChats(session.user.email)
return ( return (
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{chats?.length ? ( {chats?.length ? (
<div className="px-2 space-y-2"> <div className="space-y-2 px-2">
{chats.map(chat => ( {chats.map(chat => (
<SidebarItem <SidebarItem
key={chat.id} key={chat.id}
title={chat.title} title={chat.title}
userId={session?.user?.email ?? ''}
href={`/chat/${chat.id}`} href={`/chat/${chat.id}`}
id={chat.id} id={chat.id}
removeChat={removeChat}
/> />
))} ))}
</div> </div>
@ -47,5 +31,3 @@ export async function SidebarList({ session }: SidebarListProps) {
</div> </div>
) )
} }
SidebarList.displayName = 'SidebarList'

View file

@ -1,8 +1,6 @@
'use client' 'use client'
import * as React from 'react' import * as React from 'react'
import { signOut } from '@auth/nextjs/client'
import { type Session } from '@auth/nextjs/types'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { import {
@ -14,13 +12,16 @@ import {
} from '@/components/ui/sheet' } from '@/components/ui/sheet'
import { ThemeToggle } from '@/components/theme-toggle' import { ThemeToggle } from '@/components/theme-toggle'
import { IconSidebar } from '@/components/ui/icons' import { IconSidebar } from '@/components/ui/icons'
import { useClerk } from '@clerk/nextjs'
export interface SidebarProps { export interface SidebarProps {
session?: Session userId?: string
children?: React.ReactNode children?: React.ReactNode
} }
export function Sidebar({ session, children }: SidebarProps) { export function Sidebar({ userId, children }: SidebarProps) {
const { signOut } = useClerk()
return ( return (
<Sheet> <Sheet>
<SheetTrigger asChild> <SheetTrigger asChild>
@ -36,7 +37,7 @@ export function Sidebar({ session, children }: SidebarProps) {
{children} {children}
<div className="flex items-center p-4"> <div className="flex items-center p-4">
<ThemeToggle /> <ThemeToggle />
{session?.user && ( {userId && (
<Button <Button
variant="ghost" variant="ghost"
onClick={() => signOut()} onClick={() => signOut()}

View file

@ -6,6 +6,7 @@ import { coldarkDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard' import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
import { IconCheck, IconCopy, IconDownload } from '@/components/ui/icons' import { IconCheck, IconCopy, IconDownload } from '@/components/ui/icons'
import { Button } from '@/components/ui/button'
interface Props { interface Props {
language: string language: string
@ -51,8 +52,10 @@ export const generateRandomString = (length: number, lowercase = false) => {
} }
return lowercase ? result.toLowerCase() : result return lowercase ? result.toLowerCase() : result
} }
const CodeBlock: FC<Props> = memo(({ language, value }) => { const CodeBlock: FC<Props> = memo(({ language, value }) => {
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }) const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
const downloadAsFile = () => { const downloadAsFile = () => {
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return return
@ -65,7 +68,7 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
const fileName = window.prompt('Enter file name' || '', suggestedFileName) const fileName = window.prompt('Enter file name' || '', suggestedFileName)
if (!fileName) { if (!fileName) {
// user pressed cancel on prompt // User pressed cancel on prompt.
return return
} }
@ -80,37 +83,42 @@ const CodeBlock: FC<Props> = memo(({ language, value }) => {
document.body.removeChild(link) document.body.removeChild(link)
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
} }
return (
<div className="codeblock relative w-full font-sans text-[16px]">
<div className="flex w-full items-center justify-between px-4 py-1.5">
<span className="text-xs lowercase text-white">{language}</span>
<div className="flex items-center"> return (
<button <div className="codeblock relative w-full bg-zinc-950 font-sans">
type="button" <div className="flex w-full items-center justify-between bg-zinc-800 px-6 py-2 pr-4 text-zinc-100">
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-white" <span className="text-xs lowercase">{language}</span>
<div className="flex items-center space-x-1">
<Button
variant="ghost"
className="hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
onClick={downloadAsFile}
size="icon"
>
<IconDownload />
<span className="sr-only">Download</span>
</Button>
<Button
variant="ghost"
size="icon"
className="text-xs hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
onClick={() => copyToClipboard(value)} onClick={() => copyToClipboard(value)}
> >
{isCopied ? <IconCheck /> : <IconCopy />} {isCopied ? <IconCheck /> : <IconCopy />}
{isCopied ? 'Copied!' : 'Copy code'} <span className="sr-only">Copy code</span>
</button> </Button>
<button
type="button"
className="flex items-center rounded bg-none p-1 text-xs text-white"
onClick={downloadAsFile}
>
<IconDownload />
</button>
</div> </div>
</div> </div>
<SyntaxHighlighter <SyntaxHighlighter
language={language} language={language}
style={coldarkDark} style={coldarkDark}
PreTag="div"
showLineNumbers
customStyle={{ customStyle={{
margin: 0, margin: 0,
width: '100%', width: '100%',
background: 'transparent' background: 'transparent',
padding: '1.5rem 1rem'
}} }}
codeTagProps={{ codeTagProps={{
style: { style: {

View file

@ -232,7 +232,7 @@ function IconSpinner({ className, ...props }: React.ComponentProps<'svg'>) {
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256" viewBox="0 0 256 256"
fill="currentColor" fill="currentColor"
className={cn('h-4 w-4', className)} className={cn('h-4 w-4 animate-spin', className)}
{...props} {...props}
> >
<path d="M232 128a104 104 0 0 1-208 0c0-41 23.81-78.36 60.66-95.27a8 8 0 0 1 6.68 14.54C60.15 61.59 40 93.27 40 128a88 88 0 0 0 176 0c0-34.73-20.15-66.41-51.34-80.73a8 8 0 0 1 6.68-14.54C208.19 49.64 232 87 232 128Z" /> <path d="M232 128a104 104 0 0 1-208 0c0-41 23.81-78.36 60.66-95.27a8 8 0 0 1 6.68 14.54C60.15 61.59 40 93.27 40 128a88 88 0 0 0 176 0c0-34.73-20.15-66.41-51.34-80.73a8 8 0 0 1 6.68-14.54C208.19 49.64 232 87 232 128Z" />
@ -394,7 +394,28 @@ function IconClose({ className, ...props }: React.ComponentProps<'svg'>) {
) )
} }
function IconEdit({ className, ...props }: React.ComponentProps<'svg'>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className={cn('h-4 w-4', className)}
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
/>
</svg>
)
}
export { export {
IconEdit,
IconNextChat, IconNextChat,
IconOpenAI, IconOpenAI,
IconVercel, IconVercel,

View file

@ -1,24 +0,0 @@
'use client'
import * as React from 'react'
import { type Session } from '@auth/nextjs/types'
import { LoginButton } from '@/components/login-button'
export interface UserMenuProps {
session?: Session
}
export function UserMenu({ session }: UserMenuProps) {
if (!session?.user) {
return (
<LoginButton variant="ghost" className="[&_svg]:hidden" text="Login" />
)
}
return (
<p className="px-2 text-sm font-medium truncate w-[100px] sm:w-auto">
{session.user.name}
</p>
)
}

View file

@ -0,0 +1,23 @@
import * as React from 'react'
export function useAtBottom(offset = 0) {
const [isAtBottom, setIsAtBottom] = React.useState(false)
React.useEffect(() => {
const handleScroll = () => {
setIsAtBottom(
window.innerHeight + window.scrollY >=
document.body.offsetHeight - offset
)
}
window.addEventListener('scroll', handleScroll, { passive: true })
handleScroll()
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [offset])
return isAtBottom
}

View file

@ -1 +1,8 @@
export { auth as middleware } from './auth' import { authMiddleware } from '@clerk/nextjs'
// @see https://clerk.dev
export default authMiddleware()
export const config = {
matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
}

View file

@ -14,8 +14,7 @@
"format:check": "prettier --check \"{app,lib,components}**/*.{ts,tsx,mdx}\" --cache" "format:check": "prettier --check \"{app,lib,components}**/*.{ts,tsx,mdx}\" --cache"
}, },
"dependencies": { "dependencies": {
"@auth/core": "0.0.0-manual.527fff6c", "@clerk/nextjs": "^4.21.1",
"@auth/nextjs": "0.0.0-manual.030e8328",
"@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-alert-dialog": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-separator": "^1.0.3",
@ -23,7 +22,7 @@
"@radix-ui/react-tooltip": "^1.0.6", "@radix-ui/react-tooltip": "^1.0.6",
"@vercel/analytics": "^1.0.0", "@vercel/analytics": "^1.0.0",
"@vercel/kv": "^0.2.1", "@vercel/kv": "^0.2.1",
"ai-connector": "^0.0.5", "ai-connector": "^0.0.12",
"body-scroll-lock": "4.0.0-beta.0", "body-scroll-lock": "4.0.0-beta.0",
"class-variance-authority": "^0.4.0", "class-variance-authority": "^0.4.0",
"clsx": "^1.2.1", "clsx": "^1.2.1",
@ -35,6 +34,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-intersection-observer": "^9.4.4",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"react-textarea-autosize": "^8.4.1", "react-textarea-autosize": "^8.4.1",

484
pnpm-lock.yaml generated
View file

@ -8,12 +8,9 @@ overrides:
'@auth/core': 0.0.0-manual.527fff6c '@auth/core': 0.0.0-manual.527fff6c
dependencies: dependencies:
'@auth/core': '@clerk/nextjs':
specifier: 0.0.0-manual.527fff6c specifier: ^4.21.1
version: 0.0.0-manual.527fff6c version: 4.21.1(next@13.4.5-canary.12)(react-dom@18.2.0)(react@18.2.0)
'@auth/nextjs':
specifier: 0.0.0-manual.030e8328
version: 0.0.0-manual.030e8328(next@13.4.5-canary.12)(react@18.2.0)
'@radix-ui/react-alert-dialog': '@radix-ui/react-alert-dialog':
specifier: ^1.0.4 specifier: ^1.0.4
version: 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) version: 1.0.4(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)
@ -36,8 +33,8 @@ dependencies:
specifier: ^0.2.1 specifier: ^0.2.1
version: 0.2.1 version: 0.2.1
ai-connector: ai-connector:
specifier: ^0.0.5 specifier: ^0.0.12
version: 0.0.5(react@18.2.0) version: 0.0.12(react@18.2.0)(svelte@3.59.1)
body-scroll-lock: body-scroll-lock:
specifier: 4.0.0-beta.0 specifier: 4.0.0-beta.0
version: 4.0.0-beta.0 version: 4.0.0-beta.0
@ -71,6 +68,9 @@ dependencies:
react-hot-toast: react-hot-toast:
specifier: ^2.4.1 specifier: ^2.4.1
version: 2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0) version: 2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0)
react-intersection-observer:
specifier: ^9.4.4
version: 9.4.4(react@18.2.0)
react-markdown: react-markdown:
specifier: ^8.0.7 specifier: ^8.0.7
version: 8.0.7(@types/react@18.2.6)(react@18.2.0) version: 8.0.7(@types/react@18.2.6)(react@18.2.0)
@ -153,41 +153,90 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/@auth/core@0.0.0-manual.527fff6c:
resolution: {integrity: sha512-NYWuH+6VAAMV05juyarbMm6rCDUGvS5vPFsE+V13NIfbvbwbEZPj3JfcHVv7vMDrUrPtJ4EhTh00Svejp1Moqg==}
peerDependencies:
nodemailer: ^6.8.0
peerDependenciesMeta:
nodemailer:
optional: true
dependencies:
'@panva/hkdf': 1.1.1
cookie: 0.5.0
jose: 4.14.4
oauth4webapi: 2.3.0
preact: 10.11.3
preact-render-to-string: 5.2.3(preact@10.11.3)
dev: false
/@auth/nextjs@0.0.0-manual.030e8328(next@13.4.5-canary.12)(react@18.2.0):
resolution: {integrity: sha512-AAtUl9EVCKqLRneDAq6KfUprWmsBRzsBoQ+8ytz/Nfgs8Uax5CWvCJs2CsnYNk04bXSB4L7eyMJmc1zTzZKaHg==}
peerDependencies:
next: ^13.4.2
react: ^18.2.0
dependencies:
'@auth/core': 0.0.0-manual.527fff6c
next: 13.4.5-canary.12(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0
transitivePeerDependencies:
- nodemailer
dev: false
/@babel/runtime@7.21.5: /@babel/runtime@7.21.5:
resolution: {integrity: sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==} resolution: {integrity: sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
regenerator-runtime: 0.13.11 regenerator-runtime: 0.13.11
/@clerk/backend@0.23.0:
resolution: {integrity: sha512-2s8tRAL9nB+aQQUofrFCDGlz2Kv7pP+lR5CvQdMKTL9nPTlUBcvYFinjO3s6q3A7dwSk81erLKwYEXZEKA8R1w==}
engines: {node: '>=14'}
dependencies:
'@clerk/types': 3.42.0
'@peculiar/webcrypto': 1.4.1
'@types/node': 16.18.6
deepmerge: 4.2.2
node-fetch-native: 1.0.1
snakecase-keys: 5.4.4
tslib: 2.4.1
dev: false
/@clerk/clerk-react@4.20.0(react@18.2.0):
resolution: {integrity: sha512-3KiDycTHv+KzqS/If1un9dzJ5nrV32VPPVmNntjjZIWJDO7aJGy5v0+LiatSKMQbXGMVxuOmtZKHe3X7xkg+6g==}
engines: {node: '>=14'}
peerDependencies:
react: '>=16'
dependencies:
'@clerk/shared': 0.19.0(react@18.2.0)
'@clerk/types': 3.42.0
react: 18.2.0
swr: 1.3.0(react@18.2.0)
tslib: 2.4.1
dev: false
/@clerk/clerk-sdk-node@4.10.6:
resolution: {integrity: sha512-62nk65yRBXvuOv28eTAq3Gs/9b/+S3gQAVmyDO/67QqGETw+d5TAEUvFrOtLg/EyP7tpGQKthP8xeH2bii7OIg==}
engines: {node: '>=14'}
dependencies:
'@clerk/backend': 0.23.0
'@clerk/types': 3.42.0
'@types/cookies': 0.7.7
'@types/express': 4.17.14
'@types/node-fetch': 2.6.2
camelcase-keys: 6.2.2
cookie: 0.5.0
snakecase-keys: 3.2.1
tslib: 2.4.1
dev: false
/@clerk/nextjs@4.21.1(next@13.4.5-canary.12)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-Kkx8rp48jm0kWrFhMtFUTolitA4PVd2OEjxanhfmzlIhYcnH2ji/8UnH8U/dG3BvFcSeRwM1QO9wM4RsdXIKQg==}
engines: {node: '>=14'}
peerDependencies:
next: '>=10'
react: ^17.0.2 || ^18.0.0-0
react-dom: ^17.0.2 || ^18.0.0-0
dependencies:
'@clerk/backend': 0.23.0
'@clerk/clerk-react': 4.20.0(react@18.2.0)
'@clerk/clerk-sdk-node': 4.10.6
'@clerk/types': 3.42.0
next: 13.4.5-canary.12(react-dom@18.2.0)(react@18.2.0)
path-to-regexp: 6.2.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
tslib: 2.4.1
dev: false
/@clerk/shared@0.19.0(react@18.2.0):
resolution: {integrity: sha512-yTm3XZBVvRhnUY4ax6SiNc/vZ4/lfqgUl+DLBxRKaJtsWcijKEddOCmFUcSgCS0wu1QcyvwNZ4PUYkVq9UWcxg==}
peerDependencies:
react: '>=16'
dependencies:
glob-to-regexp: 0.4.1
js-cookie: 3.0.1
react: 18.2.0
swr: 1.3.0(react@18.2.0)
dev: false
/@clerk/types@3.42.0:
resolution: {integrity: sha512-wEiBJFT3/Itzmu3jQfHK4zCMTqiE4fUheB1fpYx8JE2q498wzjTEHpglgxl88c0tudtWomUKqXIMJnBBEVVZBg==}
engines: {node: '>=14'}
dependencies:
csstype: 3.1.1
dev: false
/@esbuild/android-arm@0.15.18: /@esbuild/android-arm@0.15.18:
resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -430,8 +479,30 @@ packages:
fastq: 1.15.0 fastq: 1.15.0
dev: true dev: true
/@panva/hkdf@1.1.1: /@peculiar/asn1-schema@2.3.6:
resolution: {integrity: sha512-dhPeilub1NuIG0X5Kvhh9lH4iW3ZsHlnzwgwbOlgwQ2wG1IqFzsgHqmKPk3WzsdWAeaxKJxgM0+W433RmN45GA==} resolution: {integrity: sha512-izNRxPoaeJeg/AyH8hER6s+H7p4itk+03QCa4sbxI3lNdseQYCuxzgsuNK8bTXChtLTjpJz6NmXKA73qLa3rCA==}
dependencies:
asn1js: 3.0.5
pvtsutils: 1.3.2
tslib: 2.5.0
dev: false
/@peculiar/json-schema@1.1.12:
resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==}
engines: {node: '>=8.0.0'}
dependencies:
tslib: 2.5.0
dev: false
/@peculiar/webcrypto@1.4.1:
resolution: {integrity: sha512-eK4C6WTNYxoI7JOabMoZICiyqRRtJB220bh0Mbj5RwRycleZf9BPyZoxsTvpP0FpmVS2aS13NKOuh5/tN3sIRw==}
engines: {node: '>=10.12.0'}
dependencies:
'@peculiar/asn1-schema': 2.3.6
'@peculiar/json-schema': 1.1.12
pvtsutils: 1.3.2
tslib: 2.5.0
webcrypto-core: 1.7.7
dev: false dev: false
/@pkgr/utils@2.4.0: /@pkgr/utils@2.4.0:
@ -937,12 +1008,52 @@ packages:
tailwindcss: 3.3.2 tailwindcss: 3.3.2
dev: true dev: true
/@types/body-parser@1.19.2:
resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
dependencies:
'@types/connect': 3.4.35
'@types/node': 17.0.45
dev: false
/@types/connect@3.4.35:
resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
dependencies:
'@types/node': 17.0.45
dev: false
/@types/cookies@0.7.7:
resolution: {integrity: sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==}
dependencies:
'@types/connect': 3.4.35
'@types/express': 4.17.14
'@types/keygrip': 1.0.2
'@types/node': 17.0.45
dev: false
/@types/debug@4.1.7: /@types/debug@4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies: dependencies:
'@types/ms': 0.7.31 '@types/ms': 0.7.31
dev: false dev: false
/@types/express-serve-static-core@4.17.35:
resolution: {integrity: sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==}
dependencies:
'@types/node': 17.0.45
'@types/qs': 6.9.7
'@types/range-parser': 1.2.4
'@types/send': 0.17.1
dev: false
/@types/express@4.17.14:
resolution: {integrity: sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==}
dependencies:
'@types/body-parser': 1.19.2
'@types/express-serve-static-core': 4.17.35
'@types/qs': 6.9.7
'@types/serve-static': 1.15.1
dev: false
/@types/hast@2.3.4: /@types/hast@2.3.4:
resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==} resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
dependencies: dependencies:
@ -957,23 +1068,53 @@ packages:
resolution: {integrity: sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw==} resolution: {integrity: sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw==}
dev: false dev: false
/@types/keygrip@1.0.2:
resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==}
dev: false
/@types/mdast@3.0.11: /@types/mdast@3.0.11:
resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==} resolution: {integrity: sha512-Y/uImid8aAwrEA24/1tcRZwpxX3pIFTSilcNDKSPn+Y2iDywSEachzRuvgAYYLR3wpGXAsMbv5lvKLDZLeYPAw==}
dependencies: dependencies:
'@types/unist': 2.0.6 '@types/unist': 2.0.6
dev: false dev: false
/@types/mime@1.3.2:
resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==}
dev: false
/@types/mime@3.0.1:
resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
dev: false
/@types/ms@0.7.31: /@types/ms@0.7.31:
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
dev: false dev: false
/@types/node-fetch@2.6.2:
resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==}
dependencies:
'@types/node': 17.0.45
form-data: 3.0.1
dev: false
/@types/node@16.18.6:
resolution: {integrity: sha512-vmYJF0REqDyyU0gviezF/KHq/fYaUbFhkcNbQCuPGFQj6VTbXuHZoxs/Y7mutWe73C8AC6l9fFu8mSYiBAqkGA==}
dev: false
/@types/node@17.0.45: /@types/node@17.0.45:
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
dev: true
/@types/prop-types@15.7.5: /@types/prop-types@15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
/@types/qs@6.9.7:
resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
dev: false
/@types/range-parser@1.2.4:
resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==}
dev: false
/@types/react-dom@18.2.4: /@types/react-dom@18.2.4:
resolution: {integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==} resolution: {integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==}
dependencies: dependencies:
@ -995,6 +1136,20 @@ packages:
/@types/scheduler@0.16.3: /@types/scheduler@0.16.3:
resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
/@types/send@0.17.1:
resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==}
dependencies:
'@types/mime': 1.3.2
'@types/node': 17.0.45
dev: false
/@types/serve-static@1.15.1:
resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
dependencies:
'@types/mime': 3.0.1
'@types/node': 17.0.45
dev: false
/@types/unist@2.0.6: /@types/unist@2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false dev: false
@ -1096,15 +1251,23 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/ai-connector@0.0.5(react@18.2.0): /ai-connector@0.0.12(react@18.2.0)(svelte@3.59.1):
resolution: {integrity: sha512-byHDAVQt3zlQ61uEBDKlOlY3uX2O+ahNVV1bnnHhnv4fNIlu+NWefxlRUqAxVM80tlCrS/+aXA8AlpkIZsa1tg==} resolution: {integrity: sha512-sUnGgl3w7dd+j1TQSH9SifQv6eoei7f7kqOgnY2OhOVOoUyQ1vJeum7jkO38OAEQ8hU0vA0yYsR3G4TkcsMFEg==}
engines: {node: '>=14.6'} engines: {node: '>=14.6'}
peerDependencies: peerDependencies:
react: ^18.0.0 react: ^18.0.0
svelte: ^3.29.0
peerDependenciesMeta:
react:
optional: true
svelte:
optional: true
dependencies: dependencies:
eventsource-parser: 1.0.0 eventsource-parser: 1.0.0
nanoid: 3.3.6 nanoid: 3.3.6
react: 18.2.0 react: 18.2.0
sswr: 1.10.0(svelte@3.59.1)
svelte: 3.59.1
swr: 2.1.5(react@18.2.0) swr: 2.1.5(react@18.2.0)
dev: false dev: false
@ -1215,10 +1378,23 @@ packages:
get-intrinsic: 1.2.0 get-intrinsic: 1.2.0
dev: true dev: true
/asn1js@3.0.5:
resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==}
engines: {node: '>=12.0.0'}
dependencies:
pvtsutils: 1.3.2
pvutils: 1.1.3
tslib: 2.5.0
dev: false
/ast-types-flow@0.0.7: /ast-types-flow@0.0.7:
resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==}
dev: true dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: false
/autoprefixer@10.4.14(postcss@8.4.23): /autoprefixer@10.4.14(postcss@8.4.23):
resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@ -1342,6 +1518,20 @@ packages:
engines: {node: '>= 6'} engines: {node: '>= 6'}
dev: true dev: true
/camelcase-keys@6.2.2:
resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}
engines: {node: '>=8'}
dependencies:
camelcase: 5.3.1
map-obj: 4.3.0
quick-lru: 4.0.1
dev: false
/camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'}
dev: false
/camelcase@7.0.1: /camelcase@7.0.1:
resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
@ -1440,6 +1630,13 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true dev: true
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: false
/comma-separated-tokens@1.0.8: /comma-separated-tokens@1.0.8:
resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==}
dev: false dev: false
@ -1487,6 +1684,10 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/csstype@3.1.1:
resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
dev: false
/csstype@3.1.2: /csstype@3.1.2:
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
@ -1556,6 +1757,11 @@ packages:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dev: true dev: true
/deepmerge@4.2.2:
resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
engines: {node: '>=0.10.0'}
dev: false
/default-browser-id@3.0.0: /default-browser-id@3.0.0:
resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -1587,6 +1793,11 @@ packages:
object-keys: 1.1.1 object-keys: 1.1.1
dev: true dev: true
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: false
/dequal@2.0.3: /dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -1636,6 +1847,13 @@ packages:
esutils: 2.0.3 esutils: 2.0.3
dev: true dev: true
/dot-case@3.0.4:
resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
dependencies:
no-case: 3.0.4
tslib: 2.5.0
dev: false
/dotenv@16.0.3: /dotenv@16.0.3:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -2472,6 +2690,15 @@ packages:
is-callable: 1.2.7 is-callable: 1.2.7
dev: true dev: true
/form-data@3.0.1:
resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: false
/format@0.2.2: /format@0.2.2:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'} engines: {node: '>=0.4.x'}
@ -3036,8 +3263,9 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/jose@4.14.4: /js-cookie@3.0.1:
resolution: {integrity: sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==} resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==}
engines: {node: '>=12'}
dev: false dev: false
/js-sdsl@4.4.0: /js-sdsl@4.4.0:
@ -3158,6 +3386,12 @@ packages:
dependencies: dependencies:
js-tokens: 4.0.0 js-tokens: 4.0.0
/lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
dependencies:
tslib: 2.5.0
dev: false
/lowlight@1.20.0: /lowlight@1.20.0:
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
dependencies: dependencies:
@ -3178,6 +3412,11 @@ packages:
es5-ext: 0.10.62 es5-ext: 0.10.62
dev: true dev: true
/map-obj@4.3.0:
resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
engines: {node: '>=8'}
dev: false
/markdown-table@3.0.3: /markdown-table@3.0.3:
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
dev: false dev: false
@ -3611,6 +3850,18 @@ packages:
picomatch: 2.3.1 picomatch: 2.3.1
dev: true dev: true
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/mimic-fn@2.1.0: /mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -3739,6 +3990,17 @@ packages:
- babel-plugin-macros - babel-plugin-macros
dev: false dev: false
/no-case@3.0.4:
resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
dependencies:
lower-case: 2.0.2
tslib: 2.5.0
dev: false
/node-fetch-native@1.0.1:
resolution: {integrity: sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg==}
dev: false
/node-fetch@2.6.11: /node-fetch@2.6.11:
resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==} resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==}
engines: {node: 4.x || >=6.0.0} engines: {node: 4.x || >=6.0.0}
@ -3779,10 +4041,6 @@ packages:
path-key: 4.0.0 path-key: 4.0.0
dev: true dev: true
/oauth4webapi@2.3.0:
resolution: {integrity: sha512-JGkb5doGrwzVDuHwgrR4nHJayzN4h59VCed6EW8Tql6iHDfZIabCJvg6wtbn5q6pyB2hZruI3b77Nudvq7NmvA==}
dev: false
/object-assign@4.1.1: /object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -3956,6 +4214,10 @@ packages:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true dev: true
/path-to-regexp@6.2.1:
resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
dev: false
/path-type@4.0.0: /path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -4066,19 +4328,6 @@ packages:
source-map-js: 1.0.2 source-map-js: 1.0.2
dev: true dev: true
/preact-render-to-string@5.2.3(preact@10.11.3):
resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==}
peerDependencies:
preact: '>=10'
dependencies:
preact: 10.11.3
pretty-format: 3.8.0
dev: false
/preact@10.11.3:
resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==}
dev: false
/prelude-ls@1.2.1: /prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -4090,10 +4339,6 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/pretty-format@3.8.0:
resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==}
dev: false
/prismjs@1.27.0: /prismjs@1.27.0:
resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -4126,10 +4371,26 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/pvtsutils@1.3.2:
resolution: {integrity: sha512-+Ipe2iNUyrZz+8K/2IOo+kKikdtfhRKzNpQbruF2URmqPtoqAs8g3xS7TJvFF2GcPXjh7DkqMnpVveRFq4PgEQ==}
dependencies:
tslib: 2.5.0
dev: false
/pvutils@1.1.3:
resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
engines: {node: '>=6.0.0'}
dev: false
/queue-microtask@1.2.3: /queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true dev: true
/quick-lru@4.0.1:
resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==}
engines: {node: '>=8'}
dev: false
/react-dom@18.2.0(react@18.2.0): /react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies: peerDependencies:
@ -4154,6 +4415,14 @@ packages:
- csstype - csstype
dev: false dev: false
/react-intersection-observer@9.4.4(react@18.2.0):
resolution: {integrity: sha512-KQr8ezJscRSQZR0/TjogG4mZttCzdPZj8fhDzAK+xrPYOVvFjabRNy+4EY3UhS3fJaJyIedGjA8y4yKX+nc08w==}
peerDependencies:
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
dev: false
/react-is@16.13.1: /react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@ -4466,6 +4735,30 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
/snake-case@3.0.4:
resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
dependencies:
dot-case: 3.0.4
tslib: 2.5.0
dev: false
/snakecase-keys@3.2.1:
resolution: {integrity: sha512-CjU5pyRfwOtaOITYv5C8DzpZ8XA/ieRsDpr93HI2r6e3YInC6moZpSQbmUtg8cTk58tq2x3jcG2gv+p1IZGmMA==}
engines: {node: '>=8'}
dependencies:
map-obj: 4.3.0
to-snake-case: 1.0.0
dev: false
/snakecase-keys@5.4.4:
resolution: {integrity: sha512-YTywJG93yxwHLgrYLZjlC75moVEX04LZM4FHfihjHe1FCXm+QaLOFfSf535aXOAd0ArVQMWUAe8ZPm4VtWyXaA==}
engines: {node: '>=12'}
dependencies:
map-obj: 4.3.0
snake-case: 3.0.4
type-fest: 2.19.0
dev: false
/source-map-js@1.0.2: /source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -4478,6 +4771,15 @@ packages:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
dev: false dev: false
/sswr@1.10.0(svelte@3.59.1):
resolution: {integrity: sha512-nLWAJSQy3h8t7rrbTXanRyVHuQPj4PwKIVGe4IMlxJFdhyaxnN/JGACnvQKGDeWiTGYIZIx/jRuUsPEF0867Pg==}
peerDependencies:
svelte: ^3.29.0
dependencies:
svelte: 3.59.1
swrev: 3.0.0
dev: false
/stop-iteration-iterator@1.0.0: /stop-iteration-iterator@1.0.0:
resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -4604,6 +4906,19 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dev: true dev: true
/svelte@3.59.1:
resolution: {integrity: sha512-pKj8fEBmqf6mq3/NfrB9SLtcJcUvjYSWyePlfCqN9gujLB25RitWK8PvFzlwim6hD/We35KbPlRteuA6rnPGcQ==}
engines: {node: '>= 8'}
dev: false
/swr@1.3.0(react@18.2.0):
resolution: {integrity: sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
dev: false
/swr@2.1.5(react@18.2.0): /swr@2.1.5(react@18.2.0):
resolution: {integrity: sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw==} resolution: {integrity: sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw==}
peerDependencies: peerDependencies:
@ -4613,6 +4928,10 @@ packages:
use-sync-external-store: 1.2.0(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0)
dev: false dev: false
/swrev@3.0.0:
resolution: {integrity: sha512-QJuZiptdOmbDY45pECBRVEgnoBlOKjeT2MWVz04wKHpWX15hM3P7EjcIbHDg5yLoPCMQ7to3349MEE+l9QF5HA==}
dev: false
/synckit@0.8.5: /synckit@0.8.5:
resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
@ -4703,6 +5022,10 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
/to-no-case@1.0.2:
resolution: {integrity: sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==}
dev: false
/to-regex-range@5.0.1: /to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
@ -4710,6 +5033,18 @@ packages:
is-number: 7.0.0 is-number: 7.0.0
dev: true dev: true
/to-snake-case@1.0.0:
resolution: {integrity: sha512-joRpzBAk1Bhi2eGEYBjukEWHOe/IvclOkiJl3DtA91jV6NwQ3MwXA4FHYeqk8BNp/D8bmi9tcNbRu/SozP0jbQ==}
dependencies:
to-space-case: 1.0.0
dev: false
/to-space-case@1.0.0:
resolution: {integrity: sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==}
dependencies:
to-no-case: 1.0.2
dev: false
/tr46@0.0.3: /tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
dev: false dev: false
@ -4739,6 +5074,10 @@ packages:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true dev: true
/tslib@2.4.1:
resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==}
dev: false
/tslib@2.5.0: /tslib@2.5.0:
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
@ -4764,6 +5103,11 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
dev: false
/type@1.2.0: /type@1.2.0:
resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==}
dev: true dev: true
@ -4978,6 +5322,16 @@ packages:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
dev: false dev: false
/webcrypto-core@1.7.7:
resolution: {integrity: sha512-7FjigXNsBfopEj+5DV2nhNpfic2vumtjjgPmeDKk45z+MJwXKKfhPB7118Pfzrmh4jqOMST6Ch37iPAHoImg5g==}
dependencies:
'@peculiar/asn1-schema': 2.3.6
'@peculiar/json-schema': 1.1.12
asn1js: 3.0.5
pvtsutils: 1.3.2
tslib: 2.5.0
dev: false
/webidl-conversions@3.0.1: /webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
dev: false dev: false

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB