diff --git a/apps/database-new/app/NewThreadInput.tsx b/apps/database-new/app/NewThreadInput.tsx new file mode 100644 index 00000000000..ca63cbc3c60 --- /dev/null +++ b/apps/database-new/app/NewThreadInput.tsx @@ -0,0 +1,81 @@ +'use client' +import { ChatInputAtom } from '@/components/Chat/ChatInput' +import { CHAT_EXAMPLES } from '@/data/chat-examples' +import { useMutation } from '@tanstack/react-query' +import { ExternalLink } from 'lucide-react' +import { useRouter } from 'next/navigation' +import { useState } from 'react' +import { cn } from 'ui' + +interface ChatInputParams { + userID: string | undefined +} + +const suggestions = CHAT_EXAMPLES + +const NewThreadInput = ({ userID }: ChatInputParams) => { + const router = useRouter() + const [value, setValue] = useState('') + + const { mutate, isPending, isSuccess } = useMutation({ + mutationFn: async (prompt: string) => { + const body = { prompt, userID } // Include userID in the body object + + const response = await fetch('/api/ai/sql/threads/create', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) + const result = await response.json() + return result + }, + onSuccess(data) { + const url = `/${data.threadId}/${data.runId}` + router.push(url) + }, + }) + + return ( + <> +
+ { + if (value.length > 0) mutate(value) + }} + autoFocus + value={value} + rows={1} + contentEditable + aria-expanded={false} + placeholder="e.g Create a Telegram-like chat application" + disabled={isPending || isSuccess} + loading={isPending || isSuccess} + onChange={(e) => setValue(e.target.value)} + /> +
+
+ {suggestions.map((suggestion, idx) => ( + + ))} +
+ + ) +} + +export default NewThreadInput diff --git a/apps/database-new/components/Chat/BottomMarker.tsx b/apps/database-new/app/[threadId]/BottomMarker.tsx similarity index 100% rename from apps/database-new/components/Chat/BottomMarker.tsx rename to apps/database-new/app/[threadId]/BottomMarker.tsx diff --git a/apps/database-new/components/Chat/Chat.tsx b/apps/database-new/app/[threadId]/Chat.tsx similarity index 88% rename from apps/database-new/components/Chat/Chat.tsx rename to apps/database-new/app/[threadId]/Chat.tsx index d0a44e2a695..0f802dae565 100644 --- a/apps/database-new/components/Chat/Chat.tsx +++ b/apps/database-new/app/[threadId]/Chat.tsx @@ -10,6 +10,7 @@ import { useMessagesQuery } from '@/data/messages-query' import { AssistantMessage, UserMessage } from '@/lib/types' import BottomMarker from './BottomMarker' import UserChat from './UserChat' +import { ChatInputAtom } from '../../components/Chat/ChatInput' export const Chat = () => { const router = useRouter() @@ -94,30 +95,22 @@ export const Chat = () => { )}
- } onChange={(v) => setValue(v.target.value)} - onKeyDown={(e) => { - if (e.code === 'Enter' && value.length > 0) { + handleSubmit={() => { + if (value && value.length > 0) { mutate(value) setInputEntered(true) } }} - actions={ - loading || inputEntered ? ( -
- -
- ) : null - } />
diff --git a/apps/database-new/components/Chat/UserChat.tsx b/apps/database-new/app/[threadId]/UserChat.tsx similarity index 100% rename from apps/database-new/components/Chat/UserChat.tsx rename to apps/database-new/app/[threadId]/UserChat.tsx diff --git a/apps/database-new/app/[threadId]/layout.tsx b/apps/database-new/app/[threadId]/layout.tsx index a16244fc4c6..85b76e12e95 100644 --- a/apps/database-new/app/[threadId]/layout.tsx +++ b/apps/database-new/app/[threadId]/layout.tsx @@ -1,5 +1,5 @@ 'use client' -import { Chat } from '@/components/Chat/Chat' +import { Chat } from '@/app/[threadId]/Chat' import SaveSchemaDropdown from '@/components/Header/SaveSchemaDropdown' import ToggleCodeEditorButton from '@/components/Header/ToggleCodeEditorButton' @@ -7,7 +7,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { return (
-
+
diff --git a/apps/database-new/app/new/page.tsx b/apps/database-new/app/page.tsx similarity index 85% rename from apps/database-new/app/new/page.tsx rename to apps/database-new/app/page.tsx index 7744aee341d..598ff665fc6 100644 --- a/apps/database-new/app/new/page.tsx +++ b/apps/database-new/app/page.tsx @@ -1,7 +1,7 @@ -import type { Metadata } from 'next' -import ChatInput from '@/components/Chat/ChatInput' import { createClient } from '@/lib/supabase/server' +import type { Metadata } from 'next' import { cookies } from 'next/headers' +import NewThreadInput from './NewThreadInput' export const metadata: Metadata = { title: 'database.design | Create', @@ -17,7 +17,7 @@ const NewThread = async () => { return (
- +
) } diff --git a/apps/database-new/app/profile/EmptyState.tsx b/apps/database-new/app/profile/EmptyState.tsx index 8532c728c3a..c799079a9e7 100644 --- a/apps/database-new/app/profile/EmptyState.tsx +++ b/apps/database-new/app/profile/EmptyState.tsx @@ -7,7 +7,7 @@ const EmptyState = () => {

No conversations created yet

) diff --git a/apps/database-new/components/Chat/ChatInput.tsx b/apps/database-new/components/Chat/ChatInput.tsx index 3d6cb4774a1..6ba663f5eac 100644 --- a/apps/database-new/components/Chat/ChatInput.tsx +++ b/apps/database-new/components/Chat/ChatInput.tsx @@ -1,111 +1,85 @@ -'use client' -import { useMutation } from '@tanstack/react-query' import { Loader2 } from 'lucide-react' -import { useRouter } from 'next/navigation' -import { useState } from 'react' +import { createRef, forwardRef, useEffect } from 'react' +import { TextArea_Shadcn_, cn } from 'ui' -import { Input } from 'ui' - -interface ChatInputParams { - userID: string | undefined +export interface TextAreaProps extends React.TextareaHTMLAttributes { + loading: boolean + handleSubmit: () => void } -const suggestions = [ - { label: 'Twitter clone', prompt: 'Create a twitter clone' }, - { - label: 'Chat application', - prompt: - 'Create a chat application that supports sending messages either through channels or directly between users', - }, - { - label: 'User management', - prompt: 'Create a simple user management schema that supports role based access control', - }, - { label: 'To do list', prompt: 'Create a simple schema for me to track a to do list' }, -] +const ChatInputAtom = ({ className, loading, handleSubmit, ...props }: TextAreaProps) => { + const ref = createRef() -const ChatInput = ({ userID }: ChatInputParams) => { - const router = useRouter() - const [value, setValue] = useState('') + useEffect(() => { + if (!props.value && ref && ref.current) { + ref.current.style.height = '40px' + } else if (ref && ref.current) { + console.log('ref.current.scrollHeight', ref.current.scrollHeight) + const newHeight = ref.current.scrollHeight + 'px' + console.log('new height', newHeight) + ref.current.style.height = newHeight + } + }, [props.value, ref]) - const { mutate, isPending, isSuccess } = useMutation({ - mutationFn: async (prompt: string) => { - const body = { prompt, userID } // Include userID in the body object + useEffect(() => { + ref?.current?.focus() + }, [props.value, ref]) - const response = await fetch('/api/ai/sql/threads/create', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }) - const result = await response.json() - return result - }, - onSuccess(data) { - const url = `/${data.threadId}/${data.runId}` - router.push(url) - }, - }) + const handleKeyDown = (event: React.KeyboardEvent) => { + // Check if the pressed key is "Enter" (key code 13) + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault() + // Manually submit the form + // value is handled by parent component + handleSubmit() + } + } return ( - <> -
- database -
- design -
- +
+ { - if (e.code === 'Enter') { - if (value.length > 0) mutate(value) - } - }} - onChange={(e) => setValue(e.target.value)} - actions={ - isPending || isSuccess ? ( -
- -
- ) : ( -
- - - -
- ) + className={ + 'transition-all text-sm pl-12 pr-10 rounded-[18px] resize-none box-border leading-6' } + ref={ref} + spellCheck={false} + onKeyDown={handleKeyDown} + {...props} /> - -
- {suggestions.map((suggestion, idx) => ( - - ))} + + + +
+ )}
- + ) } -export default ChatInput +ChatInputAtom.displayName = 'ChatInputAtom' + +export { ChatInputAtom } diff --git a/apps/database-new/components/Header/Header.tsx b/apps/database-new/components/Header/Header.tsx index f82d4db5194..2a7ebf92683 100644 --- a/apps/database-new/components/Header/Header.tsx +++ b/apps/database-new/components/Header/Header.tsx @@ -21,7 +21,7 @@ const Header = async () => {
database -
+
design
diff --git a/apps/database-new/components/Header/HeaderActions.tsx b/apps/database-new/components/Header/HeaderActions.tsx index 91e29ade946..d08a064f420 100644 --- a/apps/database-new/components/Header/HeaderActions.tsx +++ b/apps/database-new/components/Header/HeaderActions.tsx @@ -44,7 +44,7 @@ const HeaderActions = ({ user }: HeaderActionsProps) => { )} {currentUser ? ( diff --git a/apps/database-new/data/chat-examples.ts b/apps/database-new/data/chat-examples.ts new file mode 100644 index 00000000000..c664fbf0288 --- /dev/null +++ b/apps/database-new/data/chat-examples.ts @@ -0,0 +1,15 @@ +const CHAT_EXAMPLES = [ + { label: 'Twitter clone', prompt: 'Create a twitter clone' }, + { + label: 'Chat application', + prompt: + 'Create a chat application that supports sending messages either through channels or directly between users', + }, + { + label: 'User management', + prompt: 'Create a simple user management schema that supports role based access control', + }, + { label: 'To do list', prompt: 'Create a simple schema for me to track a to do list' }, +] + +export { CHAT_EXAMPLES } diff --git a/apps/database-new/next.config.js b/apps/database-new/next.config.js index f45d966e2c4..81fdd3746e6 100644 --- a/apps/database-new/next.config.js +++ b/apps/database-new/next.config.js @@ -19,13 +19,6 @@ const nextConfig = { }) return config }, - redirects: () => [ - { - source: '/', - destination: '/new', - permanent: true, - }, - ], } module.exports = nextConfig diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 47f7bff4cbd..fe9e64292ed 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -144,6 +144,7 @@ export { } from './src/components/shadcn/ui/accordion' export { Input as Input_Shadcn_ } from './src/components/shadcn/ui/input' +export { TextArea as TextArea_Shadcn_ } from './src/components/shadcn/ui/text-area' export { Label as Label_Shadcn_ } from './src/components/shadcn/ui/label' export * from './src/components/shadcn/ui/switch' export { Checkbox as Checkbox_Shadcn_ } from './src/components/shadcn/ui/checkbox' diff --git a/packages/ui/src/components/shadcn/ui/text-area.tsx b/packages/ui/src/components/shadcn/ui/text-area.tsx new file mode 100644 index 00000000000..5cf730a6ed7 --- /dev/null +++ b/packages/ui/src/components/shadcn/ui/text-area.tsx @@ -0,0 +1,28 @@ +import * as React from 'react' + +import { cn } from '@ui/lib/utils' + +export interface TextAreaProps extends React.TextareaHTMLAttributes {} + +const customClasses = ['bg-control'] + +const TextArea = React.forwardRef( + ({ className, ...props }, ref) => { + return ( +