diff --git a/apps/web/app/deploy/[databaseId]/page.tsx b/apps/web/app/deploy/[databaseId]/page.tsx index bf058366..193bcabd 100644 --- a/apps/web/app/deploy/[databaseId]/page.tsx +++ b/apps/web/app/deploy/[databaseId]/page.tsx @@ -4,12 +4,10 @@ import { useMutation } from '@tanstack/react-query' import { useParams, useRouter, useSearchParams } from 'next/navigation' import { useEffect } from 'react' import { useApp } from '~/components/app-provider' -import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui/dialog' import { createClient } from '~/utils/supabase/client' -import { Loader2 } from 'lucide-react' -import { ParticlesBackground } from '~/components/particles-background' import { getOauthUrl } from '~/lib/util' import { SupabaseIcon } from '~/components/supabase-icon' +import LineAnimation from '~/components/lines' class IntegrationRevokedError extends Error { constructor() { @@ -24,7 +22,7 @@ export default function Page() { const { liveShare } = useApp() const searchParams = useSearchParams() - const { mutate: deploy, error } = useMutation({ + const { mutate: deploy } = useMutation({ mutationFn: async () => { // make the database available to the deployment worker const localDatabaseUrl = await liveShare.start(params.databaseId) @@ -108,31 +106,24 @@ export default function Page() { }, [deploy]) return ( - - - - - - - Deploying your database - -
- -
+
+
+
+

+ + Deploying your database +

+
-

Your database is being deployed. This process typically takes a few minutes.

Please keep this page open to ensure successful deployment.

- -
-
+ + + + ) } diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index a33d2830..b623e3bf 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -14,13 +14,13 @@ --primary-foreground: 0 0% 98%; --secondary: 0 0% 96.1%; --secondary-foreground: 0 0% 9%; - --muted: 0 0% 96.1%; - --muted-foreground: 0 0% 45.1%; - --accent: 0 0% 96.1%; + --muted: 0 0% 97.1%; + --muted-foreground: 0 0% 30.1%; + --accent: 0 0% 91.1%; --accent-foreground: 0 0% 9%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; - --border: 0 0% 89.8%; + --border: 0 0% 91.8%; --input: 0 0% 89.8%; --ring: 0 0% 3.9%; --radius: 0.5rem; @@ -29,6 +29,14 @@ --chart-3: 197 37% 24%; --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; } .dark { @@ -36,13 +44,13 @@ --foreground: 0 0% 98%; --card: 0 0% 3.9%; --card-foreground: 0 0% 98%; - --popover: 0 0% 3.9%; + --popover: 0 0% 7%; --popover-foreground: 0 0% 98%; --primary: 0 0% 98%; --primary-foreground: 0 0% 9%; --secondary: 0 0% 14.9%; --secondary-foreground: 0 0% 98%; - --muted: 0 0% 14.9%; + --muted: 0 0% 10.9%; --muted-foreground: 0 0% 63.9%; --accent: 0 0% 14.9%; --accent-foreground: 0 0% 98%; @@ -56,6 +64,14 @@ --chart-3: 30 80% 55%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; } } @@ -89,6 +105,7 @@ html, body, main { + font-size: 15px; height: 100%; width: 100%; padding: 0; diff --git a/apps/web/components/app-provider.tsx b/apps/web/components/app-provider.tsx index 557a3508..5967249f 100644 --- a/apps/web/components/app-provider.tsx +++ b/apps/web/components/app-provider.tsx @@ -45,6 +45,15 @@ export default function AppProvider({ children }: AppProps) { const [isSignInDialogOpen, setIsSignInDialogOpen] = useState(false) const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false) const [isRateLimited, setIsRateLimited] = useState(false) + const [showSidebar, setShowSidebar] = useState(false) + + useEffect(() => { + dbManager?.getDatabases().then((databases) => { + if (databases.length > 0) { + setShowSidebar(true) + } + }) + }, []) const focusRef = useRef(null) @@ -294,6 +303,8 @@ export default function AppProvider({ children }: AppProps) { pgVersion, isLegacyDomain, isLegacyDomainRedirect, + showSidebar, + setShowSidebar, }} > {children} @@ -334,6 +345,8 @@ export type AppContextValues = { setModelProviderError: (error: string | undefined) => void isLegacyDomain: boolean isLegacyDomainRedirect: boolean + showSidebar: boolean + setShowSidebar: (show: boolean) => void } export const AppContext = createContext(undefined) diff --git a/apps/web/components/byo-llm-button.tsx b/apps/web/components/byo-llm-button.tsx index d09e2072..b26f525c 100644 --- a/apps/web/components/byo-llm-button.tsx +++ b/apps/web/components/byo-llm-button.tsx @@ -1,24 +1,50 @@ import { Brain } from 'lucide-react' import { useApp } from '~/components/app-provider' import { Button } from '~/components/ui/button' +import { cn } from '~/lib/utils' +import { Tooltip, TooltipContent, TooltipTrigger } from '~/components/ui/tooltip' export type ByoLlmButtonProps = { onClick?: () => void + className?: string + size?: 'default' | 'sm' | 'lg' | 'icon' + iconOnly?: boolean } -export default function ByoLlmButton({ onClick }: ByoLlmButtonProps) { - const { setIsModelProviderDialogOpen } = useApp() +export default function ByoLlmButton({ + onClick, + className, + size = 'default', + iconOnly = false, +}: ByoLlmButtonProps) { + const { setIsModelProviderDialogOpen, modelProvider } = useApp() - return ( + const button = ( ) + + if (iconOnly) { + return ( + + {button} + Bring your own LLM + + ) + } + + return button } diff --git a/apps/web/components/chat-message.tsx b/apps/web/components/chat-message.tsx index 0f688fc1..dfb3f79c 100644 --- a/apps/web/components/chat-message.tsx +++ b/apps/web/components/chat-message.tsx @@ -12,6 +12,9 @@ import { isAutomatedUserMessage } from '~/lib/util' import { cn } from '~/lib/utils' import { CodeBlock } from './code-block' import { ToolUi } from './tools' +import { Avatar, AvatarFallback, AvatarImage } from '~/components/ui/avatar' +import { useApp } from './app-provider' +import { UserIcon } from 'lucide-react' export type ChatMessageProps = { message: Message @@ -19,6 +22,10 @@ export type ChatMessageProps = { } function ChatMessage({ message, isLast }: ChatMessageProps) { + const { user } = useApp() + const avatarUrl = user?.user_metadata.avatar_url ?? null + const username = user?.user_metadata.user_name ?? null + switch (message.role) { case 'user': if (isAutomatedUserMessage(message)) { @@ -28,7 +35,6 @@ function ChatMessage({ message, isLast }: ChatMessageProps) { return ( {message.content} @@ -50,7 +56,7 @@ function ChatMessage({ message, isLast }: ChatMessageProps) { remarkPlugins={[remarkGfm, [remarkMath, { singleDollarTextMath: false }]]} rehypePlugins={[[rehypeKatex, { output: 'html' }]]} components={{ ...markdownComponents, img: () => null }} - className="prose prose-xs text-base [&_.katex-display>.katex]:text-left" + className="prose prose-xs prose-h3:text-md [&_.katex-display>.katex]:text-left prose-ol:my-4" > {message.content} @@ -69,7 +75,7 @@ function ChatMessage({ message, isLast }: ChatMessageProps) { return ( { } const markdownComponents = { - mono: (props: any) => {props.children}, + mono: (props: any) => {props.children}, code: (props: any) => , pre: (props: any) => (
diff --git a/apps/web/components/chat.tsx b/apps/web/components/chat.tsx
index cff185f5..01172e0d 100644
--- a/apps/web/components/chat.tsx
+++ b/apps/web/components/chat.tsx
@@ -75,7 +75,7 @@ export default function Chat() {
     stopReply,
   } = useWorkspace()
 
-  const { input, setInput, handleInputChange, isLoading } = useChat({
+  const { input, setInput, isLoading } = useChat({
     id: databaseId,
     api: '/api/chat',
   })
@@ -185,10 +185,7 @@ export default function Chat() {
       }
     },
     cursorElement: (
-      
+      
          Add file to chat
       
     ),
@@ -196,6 +193,26 @@ export default function Chat() {
 
   const inputRef = useRef(null)
 
+  // Add this function to handle textarea resizing
+  const adjustTextareaHeight = useCallback(() => {
+    const textarea = inputRef.current
+    if (textarea) {
+      textarea.style.height = 'auto'
+      textarea.style.height = `${textarea.scrollHeight}px`
+    }
+  }, [])
+
+  // Update the handleInputChange to include height adjustment
+  const handleInputChange = (e: React.ChangeEvent) => {
+    setInput(e.target.value)
+    adjustTextareaHeight()
+  }
+
+  // Add useEffect to adjust height on input changes
+  useEffect(() => {
+    adjustTextareaHeight()
+  }, [input, adjustTextareaHeight])
+
   // Scroll to end when chat is first mounted
   useEffect(() => {
     scrollToEnd()
@@ -273,283 +290,271 @@ export default function Chat() {
             
             
           
-        ) : isConversationStarted ? (
-          
- - setIsMessageAnimationComplete(false)} - onAnimationComplete={() => setIsMessageAnimationComplete(true)} - initial="show" - animate="show" - > - {messages.map((message, i) => ( - - ))} - - {modelProviderError && !isLoading && ( - - -
-

Whoops!

-

- There was an error connecting to your custom model provider:{' '} - {modelProviderError} -

-

- Double check your{' '} - { - setIsModelProviderDialogOpen(true) - }} - > - API info - - . -

-
-
- )} -
- - {isRateLimited && !isLoading && ( - - -
-

Hang tight!

-

- We're seeing a lot of AI traffic from your end and need to temporarily - pause your chats to make sure our servers don't melt. -

- -

Have a quick coffee break and try again in a few minutes!

-
-
- )} -
- - {isLoading && ( - - - - - {lastMessage && - (lastMessage.role === 'user' || - (lastMessage.role === 'assistant' && !lastMessage.content)) && ( - - Working on it... - - )} - - )} - -
-
) : ( -
- {!isAuthRequired ? ( - <> - - - What would you like to create? - - - ) : ( + isConversationStarted && ( +
setIsMessageAnimationComplete(false)} + onAnimationComplete={() => setIsMessageAnimationComplete(true)} + initial="show" animate="show" > - - or - -

{ - setIsSignInDialogOpen(true) - }} - > - Why do I need to sign in? -

+ {messages.map((message, i) => ( + + ))} + + {modelProviderError && !isLoading && ( + + +
+

Whoops!

+

+ There was an error connecting to your custom model provider:{' '} + {modelProviderError}. +

+
+ +
+ )} +
+ + {isRateLimited && !isLoading && ( + + +
+

Hang tight!

+

+ We're seeing a lot of AI traffic from your end and need to + temporarily pause your chats to make sure our servers don't melt. +

+ +

Have a quick coffee break and try again in a few minutes!

+
+
+ )} +
+ + {isLoading && ( + + + + + {lastMessage && + (lastMessage.role === 'user' || + (lastMessage.role === 'assistant' && !lastMessage.content)) && ( + + Working on it... + + )} + + )} +
- )} -
+
+ ) )} {!isSticky && ( - - - + +
+ )} -
+
- {isAuthRequired && !isLoadingUser && isConversationStarted && ( - - - or - -

{ - setIsSignInDialogOpen(true) - }} - > - Why do I need to sign in? -

-
+ {!isLoadingUser && ( + <> + {isAuthRequired ? ( + + +

Sign in to create a database

+

+ We ask you to sign in to prevent API abuse. +

+
+ + +
+
+ ) : ( + !isConversationStarted && + !isLoadingMessages && + !isLoadingSchema && ( +
+
+ + What would you like to create? + +

+ Describe what you want to build and add any specific database requirements. +

+
+ + + +
+
+
+ ) + )} + )}
- {/* - * This is a hidden dummy message acting as an animation anchor - * before the real message is added to the chat. - * - * The animation starts in this element's position and moves over to - * the location of the real message after submit. - * - * It works by sharing the same `layoutId` between both message elements - * which framer motion requires to animate between them. - */} - {input && ( - - {input} - - )} -