From 008fbe82355d4a001c7530186266c163ce874958 Mon Sep 17 00:00:00 2001 From: BYND Date: Thu, 21 May 2026 12:37:18 +0300 Subject: [PATCH] Add query param new chat flow --- src/app/(general)/_components/chat/index.tsx | 6 +++ src/app/(general)/_contexts/chat-context.tsx | 47 ++++++++++++++++++-- src/app/(general)/login/page.tsx | 18 +++++++- src/app/(general)/new/page.tsx | 44 ++++++++++++++++++ src/app/(general)/page.tsx | 24 +++++++++- 5 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 src/app/(general)/new/page.tsx diff --git a/src/app/(general)/_components/chat/index.tsx b/src/app/(general)/_components/chat/index.tsx index e617820a..0c00e402 100644 --- a/src/app/(general)/_components/chat/index.tsx +++ b/src/app/(general)/_components/chat/index.tsx @@ -17,6 +17,8 @@ interface Props { isReadonly: boolean; isNew: boolean; workbench?: Workbench; + initialInput?: string; + autoSubmit?: boolean; } export const Chat = async ({ @@ -25,6 +27,8 @@ export const Chat = async ({ isReadonly, isNew, workbench, + initialInput, + autoSubmit = false, }: Props) => { const initialMessages = isNew ? [] @@ -113,6 +117,8 @@ export const Chat = async ({ autoResume={!isNew} workbench={workbench} initialPreferences={initialPreferences} + initialInput={initialInput} + autoSubmit={autoSubmit} > ; }; + initialInput?: string; + autoSubmit?: boolean; } export function ChatProvider({ @@ -103,15 +106,21 @@ export function ChatProvider({ autoResume, workbench, initialPreferences, + initialInput, + autoSubmit = false, }: ChatProviderProps) { const utils = api.useUtils(); + const initialChatModel = + initialPreferences?.selectedChatModel ?? DEFAULT_CHAT_MODEL; + const initialUseNativeSearch = + initialChatModel.capabilities?.includes( + LanguageModelCapability.WebSearch, + ) === true || initialPreferences?.useNativeSearch === true; const [selectedChatModel, setSelectedChatModelState] = - useState( - initialPreferences?.selectedChatModel ?? DEFAULT_CHAT_MODEL, - ); + useState(initialChatModel); const [useNativeSearch, setUseNativeSearchState] = useState( - initialPreferences?.useNativeSearch ?? false, + initialUseNativeSearch, ); const [imageGenerationModel, setImageGenerationModelState] = useState< ImageModel | undefined @@ -177,6 +186,8 @@ export function ChatProvider({ }); const [hasInvalidated, setHasInvalidated] = useState(false); const [streamStopped, setStreamStopped] = useState(false); + const hasAutoSubmitted = useRef(false); + const initialPrompt = initialInput?.trim() ?? ""; // Wrapper functions that also save to cookies const setSelectedChatModel = (model: LanguageModel) => { @@ -222,6 +233,7 @@ export function ChatProvider({ } = useChat({ id, initialMessages, + initialInput: initialPrompt, experimental_throttle: 100, sendExtraMessageFields: true, generateId: generateUUID, @@ -282,6 +294,33 @@ export function ChatProvider({ onStreamError, }); + useEffect(() => { + if ( + !autoSubmit || + !initialPrompt || + hasAutoSubmitted.current || + status !== "ready" + ) { + return; + } + + hasAutoSubmitted.current = true; + setStreamStopped(false); + + if (workbench) { + window.history.replaceState({}, "", `/workbench/${workbench.id}/${id}`); + } else { + window.history.replaceState({}, "", `/${id}`); + } + + void append({ + role: "user", + content: initialPrompt, + }); + window.localStorage.removeItem("input"); + queueMicrotask(() => setInput("")); + }, [append, autoSubmit, id, initialPrompt, setInput, status, workbench]); + const handleSubmit: UseChatHelpers["handleSubmit"] = ( event, chatRequestOptions, diff --git a/src/app/(general)/login/page.tsx b/src/app/(general)/login/page.tsx index be1eb8ed..80cba040 100644 --- a/src/app/(general)/login/page.tsx +++ b/src/app/(general)/login/page.tsx @@ -4,11 +4,25 @@ import { LoginForm } from "./login-form"; import { auth } from "@/server/auth"; import { redirect } from "next/navigation"; -export default async function LoginPage() { +type SearchParams = Record; + +const getSearchParam = ( + searchParams: SearchParams | undefined, + key: string, +) => { + const value = searchParams?.[key]; + return Array.isArray(value) ? value[0] : value; +}; + +export default async function LoginPage(props: { + searchParams: Promise; +}) { + const searchParams = await props.searchParams; + const redirectTo = getSearchParam(searchParams, "redirect"); const session = await auth(); if (session) { - redirect("/"); + redirect(redirectTo ?? "/"); } const mappedProviders = providers.map((provider) => ({ diff --git a/src/app/(general)/new/page.tsx b/src/app/(general)/new/page.tsx new file mode 100644 index 00000000..f439da65 --- /dev/null +++ b/src/app/(general)/new/page.tsx @@ -0,0 +1,44 @@ +import { redirect } from "next/navigation"; + +import { Chat } from "@/app/(general)/_components/chat"; +import { auth } from "@/server/auth"; +import { generateUUID } from "@/lib/utils"; + +type SearchParams = Record; + +const getSearchParam = ( + searchParams: SearchParams | undefined, + key: string, +) => { + const value = searchParams?.[key]; + return Array.isArray(value) ? value[0] : value; +}; + +export default async function Page(props: { + searchParams: Promise; +}) { + const searchParams = await props.searchParams; + const query = getSearchParam(searchParams, "q")?.trim(); + const session = await auth(); + const redirectPath = query + ? `/new?${new URLSearchParams({ q: query })}` + : "/new"; + + if (!session) { + redirect(`/login?${new URLSearchParams({ redirect: redirectPath })}`); + } + + const id = generateUUID(); + + return ( + + ); +} diff --git a/src/app/(general)/page.tsx b/src/app/(general)/page.tsx index 35d96f90..49c7be57 100644 --- a/src/app/(general)/page.tsx +++ b/src/app/(general)/page.tsx @@ -4,11 +4,31 @@ import LandingPage from "./_components/landing-page"; import { auth } from "@/server/auth"; import { generateUUID } from "@/lib/utils"; +import { redirect } from "next/navigation"; -export default async function Page() { +type SearchParams = Record; + +const getSearchParam = ( + searchParams: SearchParams | undefined, + key: string, +) => { + const value = searchParams?.[key]; + return Array.isArray(value) ? value[0] : value; +}; + +export default async function Page(props: { + searchParams: Promise; +}) { + const searchParams = await props.searchParams; + const query = getSearchParam(searchParams, "q")?.trim(); const session = await auth(); if (!session) { + if (query) { + const redirectPath = `/?${new URLSearchParams({ q: query })}`; + redirect(`/login?${new URLSearchParams({ redirect: redirectPath })}`); + } + return ; } @@ -21,6 +41,8 @@ export default async function Page() { initialVisibilityType="private" isReadonly={false} isNew={true} + initialInput={query} + autoSubmit={!!query} /> ); }