Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/(general)/_components/chat/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ interface Props {
isReadonly: boolean;
isNew: boolean;
workbench?: Workbench;
initialInput?: string;
autoSubmit?: boolean;
}

export const Chat = async ({
Expand All @@ -25,6 +27,8 @@ export const Chat = async ({
isReadonly,
isNew,
workbench,
initialInput,
autoSubmit = false,
}: Props) => {
const initialMessages = isNew
? []
Expand Down Expand Up @@ -113,6 +117,8 @@ export const Chat = async ({
autoResume={!isNew}
workbench={workbench}
initialPreferences={initialPreferences}
initialInput={initialInput}
autoSubmit={autoSubmit}
>
<ChatContent
id={id}
Expand Down
47 changes: 43 additions & 4 deletions src/app/(general)/_contexts/chat-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
createContext,
useContext,
useEffect,
useRef,
useState,
useCallback,
} from "react";
Expand Down Expand Up @@ -93,6 +94,8 @@ interface ChatProviderProps {
useNativeSearch?: boolean;
toolkits?: Array<PersistedToolkit>;
};
initialInput?: string;
autoSubmit?: boolean;
}

export function ChatProvider({
Expand All @@ -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<LanguageModel>(
initialPreferences?.selectedChatModel ?? DEFAULT_CHAT_MODEL,
);
useState<LanguageModel>(initialChatModel);
const [useNativeSearch, setUseNativeSearchState] = useState(
initialPreferences?.useNativeSearch ?? false,
initialUseNativeSearch,
);
const [imageGenerationModel, setImageGenerationModelState] = useState<
ImageModel | undefined
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -222,6 +233,7 @@ export function ChatProvider({
} = useChat({
id,
initialMessages,
initialInput: initialPrompt,
experimental_throttle: 100,
sendExtraMessageFields: true,
generateId: generateUUID,
Expand Down Expand Up @@ -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,
Expand Down
18 changes: 16 additions & 2 deletions src/app/(general)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string | string[] | undefined>;

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<SearchParams>;
}) {
const searchParams = await props.searchParams;
const redirectTo = getSearchParam(searchParams, "redirect");
const session = await auth();

if (session) {
redirect("/");
redirect(redirectTo ?? "/");
}

const mappedProviders = providers.map((provider) => ({
Expand Down
44 changes: 44 additions & 0 deletions src/app/(general)/new/page.tsx
Original file line number Diff line number Diff line change
@@ -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<string, string | string[] | undefined>;

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<SearchParams>;
}) {
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 (
<Chat
key={id}
id={id}
initialVisibilityType="private"
isReadonly={false}
isNew={true}
initialInput={query}
autoSubmit={!!query}
/>
);
}
24 changes: 23 additions & 1 deletion src/app/(general)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string | string[] | undefined>;

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<SearchParams>;
}) {
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 <LandingPage />;
}

Expand All @@ -21,6 +41,8 @@ export default async function Page() {
initialVisibilityType="private"
isReadonly={false}
isNew={true}
initialInput={query}
autoSubmit={!!query}
/>
);
}