Restore Ultracite + fix sidebar (#1233)
This commit is contained in:
parent
8fbfc253fa
commit
947ed094a6
177 changed files with 6908 additions and 8306 deletions
|
|
@ -1,84 +1,84 @@
|
|||
'use server';
|
||||
"use server";
|
||||
|
||||
import { z } from 'zod';
|
||||
import { z } from "zod";
|
||||
|
||||
import { createUser, getUser } from '@/lib/db/queries';
|
||||
import { createUser, getUser } from "@/lib/db/queries";
|
||||
|
||||
import { signIn } from './auth';
|
||||
import { signIn } from "./auth";
|
||||
|
||||
const authFormSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(6),
|
||||
});
|
||||
|
||||
export interface LoginActionState {
|
||||
status: 'idle' | 'in_progress' | 'success' | 'failed' | 'invalid_data';
|
||||
}
|
||||
export type LoginActionState = {
|
||||
status: "idle" | "in_progress" | "success" | "failed" | "invalid_data";
|
||||
};
|
||||
|
||||
export const login = async (
|
||||
_: LoginActionState,
|
||||
formData: FormData,
|
||||
formData: FormData
|
||||
): Promise<LoginActionState> => {
|
||||
try {
|
||||
const validatedData = authFormSchema.parse({
|
||||
email: formData.get('email'),
|
||||
password: formData.get('password'),
|
||||
email: formData.get("email"),
|
||||
password: formData.get("password"),
|
||||
});
|
||||
|
||||
await signIn('credentials', {
|
||||
await signIn("credentials", {
|
||||
email: validatedData.email,
|
||||
password: validatedData.password,
|
||||
redirect: false,
|
||||
});
|
||||
|
||||
return { status: 'success' };
|
||||
return { status: "success" };
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return { status: 'invalid_data' };
|
||||
return { status: "invalid_data" };
|
||||
}
|
||||
|
||||
return { status: 'failed' };
|
||||
return { status: "failed" };
|
||||
}
|
||||
};
|
||||
|
||||
export interface RegisterActionState {
|
||||
export type RegisterActionState = {
|
||||
status:
|
||||
| 'idle'
|
||||
| 'in_progress'
|
||||
| 'success'
|
||||
| 'failed'
|
||||
| 'user_exists'
|
||||
| 'invalid_data';
|
||||
}
|
||||
| "idle"
|
||||
| "in_progress"
|
||||
| "success"
|
||||
| "failed"
|
||||
| "user_exists"
|
||||
| "invalid_data";
|
||||
};
|
||||
|
||||
export const register = async (
|
||||
_: RegisterActionState,
|
||||
formData: FormData,
|
||||
formData: FormData
|
||||
): Promise<RegisterActionState> => {
|
||||
try {
|
||||
const validatedData = authFormSchema.parse({
|
||||
email: formData.get('email'),
|
||||
password: formData.get('password'),
|
||||
email: formData.get("email"),
|
||||
password: formData.get("password"),
|
||||
});
|
||||
|
||||
const [user] = await getUser(validatedData.email);
|
||||
|
||||
if (user) {
|
||||
return { status: 'user_exists' } as RegisterActionState;
|
||||
return { status: "user_exists" } as RegisterActionState;
|
||||
}
|
||||
await createUser(validatedData.email, validatedData.password);
|
||||
await signIn('credentials', {
|
||||
await signIn("credentials", {
|
||||
email: validatedData.email,
|
||||
password: validatedData.password,
|
||||
redirect: false,
|
||||
});
|
||||
|
||||
return { status: 'success' };
|
||||
return { status: "success" };
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return { status: 'invalid_data' };
|
||||
return { status: "invalid_data" };
|
||||
}
|
||||
|
||||
return { status: 'failed' };
|
||||
return { status: "failed" };
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export { GET, POST } from '@/app/(auth)/auth';
|
||||
// biome-ignore lint/performance/noBarrelFile: "Required"
|
||||
export { GET, POST } from "@/app/(auth)/auth";
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { signIn } from '@/app/(auth)/auth';
|
||||
import { isDevelopmentEnvironment } from '@/lib/constants';
|
||||
import { getToken } from 'next-auth/jwt';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { NextResponse } from "next/server";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { signIn } from "@/app/(auth)/auth";
|
||||
import { isDevelopmentEnvironment } from "@/lib/constants";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const redirectUrl = searchParams.get('redirectUrl') || '/';
|
||||
const redirectUrl = searchParams.get("redirectUrl") || "/";
|
||||
|
||||
const token = await getToken({
|
||||
req: request,
|
||||
|
|
@ -14,8 +14,8 @@ export async function GET(request: Request) {
|
|||
});
|
||||
|
||||
if (token) {
|
||||
return NextResponse.redirect(new URL('/', request.url));
|
||||
return NextResponse.redirect(new URL("/", request.url));
|
||||
}
|
||||
|
||||
return signIn('guest', { redirect: true, redirectTo: redirectUrl });
|
||||
return signIn("guest", { redirect: true, redirectTo: redirectUrl });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { NextAuthConfig } from 'next-auth';
|
||||
import type { NextAuthConfig } from "next-auth";
|
||||
|
||||
export const authConfig = {
|
||||
pages: {
|
||||
signIn: '/login',
|
||||
newUser: '/',
|
||||
signIn: "/login",
|
||||
newUser: "/",
|
||||
},
|
||||
providers: [
|
||||
// added later in auth.ts since it requires bcrypt which is only compatible with Node.js
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
import { compare } from 'bcrypt-ts';
|
||||
import NextAuth, { type DefaultSession } from 'next-auth';
|
||||
import Credentials from 'next-auth/providers/credentials';
|
||||
import { createGuestUser, getUser } from '@/lib/db/queries';
|
||||
import { authConfig } from './auth.config';
|
||||
import { DUMMY_PASSWORD } from '@/lib/constants';
|
||||
import type { DefaultJWT } from 'next-auth/jwt';
|
||||
import { compare } from "bcrypt-ts";
|
||||
import NextAuth, { type DefaultSession } from "next-auth";
|
||||
import type { DefaultJWT } from "next-auth/jwt";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { DUMMY_PASSWORD } from "@/lib/constants";
|
||||
import { createGuestUser, getUser } from "@/lib/db/queries";
|
||||
import { authConfig } from "./auth.config";
|
||||
|
||||
export type UserType = 'guest' | 'regular';
|
||||
export type UserType = "guest" | "regular";
|
||||
|
||||
declare module 'next-auth' {
|
||||
declare module "next-auth" {
|
||||
interface Session extends DefaultSession {
|
||||
user: {
|
||||
id: string;
|
||||
type: UserType;
|
||||
} & DefaultSession['user'];
|
||||
} & DefaultSession["user"];
|
||||
}
|
||||
|
||||
// biome-ignore lint/nursery/useConsistentTypeDefinitions: "Required"
|
||||
interface User {
|
||||
id?: string;
|
||||
email?: string | null;
|
||||
|
|
@ -23,7 +24,7 @@ declare module 'next-auth' {
|
|||
}
|
||||
}
|
||||
|
||||
declare module 'next-auth/jwt' {
|
||||
declare module "next-auth/jwt" {
|
||||
interface JWT extends DefaultJWT {
|
||||
id: string;
|
||||
type: UserType;
|
||||
|
|
@ -57,22 +58,24 @@ export const {
|
|||
|
||||
const passwordsMatch = await compare(password, user.password);
|
||||
|
||||
if (!passwordsMatch) return null;
|
||||
if (!passwordsMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { ...user, type: 'regular' };
|
||||
return { ...user, type: "regular" };
|
||||
},
|
||||
}),
|
||||
Credentials({
|
||||
id: 'guest',
|
||||
id: "guest",
|
||||
credentials: {},
|
||||
async authorize() {
|
||||
const [guestUser] = await createGuestUser();
|
||||
return { ...guestUser, type: 'guest' };
|
||||
return { ...guestUser, type: "guest" };
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user }) {
|
||||
jwt({ token, user }) {
|
||||
if (user) {
|
||||
token.id = user.id as string;
|
||||
token.type = user.type;
|
||||
|
|
@ -80,7 +83,7 @@ export const {
|
|||
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
session({ session, token }) {
|
||||
if (session.user) {
|
||||
session.user.id = token.id;
|
||||
session.user.type = token.type;
|
||||
|
|
|
|||
|
|
@ -1,52 +1,51 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useActionState, useEffect, useState } from 'react';
|
||||
import { toast } from '@/components/toast';
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useActionState, useEffect, useState } from "react";
|
||||
|
||||
import { AuthForm } from '@/components/auth-form';
|
||||
import { SubmitButton } from '@/components/submit-button';
|
||||
|
||||
import { login, type LoginActionState } from '../actions';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import { AuthForm } from "@/components/auth-form";
|
||||
import { SubmitButton } from "@/components/submit-button";
|
||||
import { toast } from "@/components/toast";
|
||||
import { type LoginActionState, login } from "../actions";
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
const [email, setEmail] = useState("");
|
||||
const [isSuccessful, setIsSuccessful] = useState(false);
|
||||
|
||||
const [state, formAction] = useActionState<LoginActionState, FormData>(
|
||||
login,
|
||||
{
|
||||
status: 'idle',
|
||||
},
|
||||
status: "idle",
|
||||
}
|
||||
);
|
||||
|
||||
const { update: updateSession } = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.status === 'failed') {
|
||||
if (state.status === "failed") {
|
||||
toast({
|
||||
type: 'error',
|
||||
description: 'Invalid credentials!',
|
||||
type: "error",
|
||||
description: "Invalid credentials!",
|
||||
});
|
||||
} else if (state.status === 'invalid_data') {
|
||||
} else if (state.status === "invalid_data") {
|
||||
toast({
|
||||
type: 'error',
|
||||
description: 'Failed validating your submission!',
|
||||
type: "error",
|
||||
description: "Failed validating your submission!",
|
||||
});
|
||||
} else if (state.status === 'success') {
|
||||
} else if (state.status === "success") {
|
||||
setIsSuccessful(true);
|
||||
updateSession();
|
||||
router.refresh();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [state.status]);
|
||||
}, [state.status, router.refresh, updateSession]);
|
||||
|
||||
const handleSubmit = (formData: FormData) => {
|
||||
setEmail(formData.get('email') as string);
|
||||
setEmail(formData.get("email") as string);
|
||||
formAction(formData);
|
||||
};
|
||||
|
||||
|
|
@ -64,12 +63,12 @@ export default function Page() {
|
|||
<p className="mt-4 text-center text-gray-600 text-sm dark:text-zinc-400">
|
||||
{"Don't have an account? "}
|
||||
<Link
|
||||
href="/register"
|
||||
className="font-semibold text-gray-800 hover:underline dark:text-zinc-200"
|
||||
href="/register"
|
||||
>
|
||||
Sign up
|
||||
</Link>
|
||||
{' for free.'}
|
||||
{" for free."}
|
||||
</p>
|
||||
</AuthForm>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,53 +1,51 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useActionState, useEffect, useState } from 'react';
|
||||
|
||||
import { AuthForm } from '@/components/auth-form';
|
||||
import { SubmitButton } from '@/components/submit-button';
|
||||
|
||||
import { register, type RegisterActionState } from '../actions';
|
||||
import { toast } from '@/components/toast';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useActionState, useEffect, useState } from "react";
|
||||
import { AuthForm } from "@/components/auth-form";
|
||||
import { SubmitButton } from "@/components/submit-button";
|
||||
import { toast } from "@/components/toast";
|
||||
import { type RegisterActionState, register } from "../actions";
|
||||
|
||||
export default function Page() {
|
||||
const router = useRouter();
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
const [email, setEmail] = useState("");
|
||||
const [isSuccessful, setIsSuccessful] = useState(false);
|
||||
|
||||
const [state, formAction] = useActionState<RegisterActionState, FormData>(
|
||||
register,
|
||||
{
|
||||
status: 'idle',
|
||||
},
|
||||
status: "idle",
|
||||
}
|
||||
);
|
||||
|
||||
const { update: updateSession } = useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (state.status === 'user_exists') {
|
||||
toast({ type: 'error', description: 'Account already exists!' });
|
||||
} else if (state.status === 'failed') {
|
||||
toast({ type: 'error', description: 'Failed to create account!' });
|
||||
} else if (state.status === 'invalid_data') {
|
||||
if (state.status === "user_exists") {
|
||||
toast({ type: "error", description: "Account already exists!" });
|
||||
} else if (state.status === "failed") {
|
||||
toast({ type: "error", description: "Failed to create account!" });
|
||||
} else if (state.status === "invalid_data") {
|
||||
toast({
|
||||
type: 'error',
|
||||
description: 'Failed validating your submission!',
|
||||
type: "error",
|
||||
description: "Failed validating your submission!",
|
||||
});
|
||||
} else if (state.status === 'success') {
|
||||
toast({ type: 'success', description: 'Account created successfully!' });
|
||||
} else if (state.status === "success") {
|
||||
toast({ type: "success", description: "Account created successfully!" });
|
||||
|
||||
setIsSuccessful(true);
|
||||
updateSession();
|
||||
router.refresh();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [state.status]);
|
||||
}, [state.status, router.refresh, updateSession]);
|
||||
|
||||
const handleSubmit = (formData: FormData) => {
|
||||
setEmail(formData.get('email') as string);
|
||||
setEmail(formData.get("email") as string);
|
||||
formAction(formData);
|
||||
};
|
||||
|
||||
|
|
@ -63,14 +61,14 @@ export default function Page() {
|
|||
<AuthForm action={handleSubmit} defaultEmail={email}>
|
||||
<SubmitButton isSuccessful={isSuccessful}>Sign Up</SubmitButton>
|
||||
<p className="mt-4 text-center text-gray-600 text-sm dark:text-zinc-400">
|
||||
{'Already have an account? '}
|
||||
{"Already have an account? "}
|
||||
<Link
|
||||
href="/login"
|
||||
className="font-semibold text-gray-800 hover:underline dark:text-zinc-200"
|
||||
href="/login"
|
||||
>
|
||||
Sign in
|
||||
</Link>
|
||||
{' instead.'}
|
||||
{" instead."}
|
||||
</p>
|
||||
</AuthForm>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
'use server';
|
||||
"use server";
|
||||
|
||||
import { generateText, type UIMessage } from 'ai';
|
||||
import { cookies } from 'next/headers';
|
||||
import { generateText, type UIMessage } from "ai";
|
||||
import { cookies } from "next/headers";
|
||||
import type { VisibilityType } from "@/components/visibility-selector";
|
||||
import { myProvider } from "@/lib/ai/providers";
|
||||
import {
|
||||
deleteMessagesByChatIdAfterTimestamp,
|
||||
getMessageById,
|
||||
updateChatVisiblityById,
|
||||
} from '@/lib/db/queries';
|
||||
import type { VisibilityType } from '@/components/visibility-selector';
|
||||
import { myProvider } from '@/lib/ai/providers';
|
||||
} from "@/lib/db/queries";
|
||||
|
||||
export async function saveChatModelAsCookie(model: string) {
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set('chat-model', model);
|
||||
cookieStore.set("chat-model", model);
|
||||
}
|
||||
|
||||
export async function generateTitleFromUserMessage({
|
||||
|
|
@ -21,7 +21,7 @@ export async function generateTitleFromUserMessage({
|
|||
message: UIMessage;
|
||||
}) {
|
||||
const { text: title } = await generateText({
|
||||
model: myProvider.languageModel('title-model'),
|
||||
model: myProvider.languageModel("title-model"),
|
||||
system: `\n
|
||||
- you will generate a short title based on the first message a user begins a conversation with
|
||||
- ensure it is not more than 80 characters long
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { auth } from '@/app/(auth)/auth';
|
||||
import { createUIMessageStream, JsonToSseTransformStream } from "ai";
|
||||
import { differenceInSeconds } from "date-fns";
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import {
|
||||
getChatById,
|
||||
getMessagesByChatId,
|
||||
getStreamIdsByChatId,
|
||||
} from '@/lib/db/queries';
|
||||
import type { Chat } from '@/lib/db/schema';
|
||||
import { ChatSDKError } from '@/lib/errors';
|
||||
import type { ChatMessage } from '@/lib/types';
|
||||
import { createUIMessageStream, JsonToSseTransformStream } from 'ai';
|
||||
import { getStreamContext } from '../../route';
|
||||
import { differenceInSeconds } from 'date-fns';
|
||||
} from "@/lib/db/queries";
|
||||
import type { Chat } from "@/lib/db/schema";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
import { getStreamContext } from "../../route";
|
||||
|
||||
export async function GET(
|
||||
_: Request,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const { id: chatId } = await params;
|
||||
|
||||
|
|
@ -25,13 +25,13 @@ export async function GET(
|
|||
}
|
||||
|
||||
if (!chatId) {
|
||||
return new ChatSDKError('bad_request:api').toResponse();
|
||||
return new ChatSDKError("bad_request:api").toResponse();
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('unauthorized:chat').toResponse();
|
||||
return new ChatSDKError("unauthorized:chat").toResponse();
|
||||
}
|
||||
|
||||
let chat: Chat | null;
|
||||
|
|
@ -39,35 +39,36 @@ export async function GET(
|
|||
try {
|
||||
chat = await getChatById({ id: chatId });
|
||||
} catch {
|
||||
return new ChatSDKError('not_found:chat').toResponse();
|
||||
return new ChatSDKError("not_found:chat").toResponse();
|
||||
}
|
||||
|
||||
if (!chat) {
|
||||
return new ChatSDKError('not_found:chat').toResponse();
|
||||
return new ChatSDKError("not_found:chat").toResponse();
|
||||
}
|
||||
|
||||
if (chat.visibility === 'private' && chat.userId !== session.user.id) {
|
||||
return new ChatSDKError('forbidden:chat').toResponse();
|
||||
if (chat.visibility === "private" && chat.userId !== session.user.id) {
|
||||
return new ChatSDKError("forbidden:chat").toResponse();
|
||||
}
|
||||
|
||||
const streamIds = await getStreamIdsByChatId({ chatId });
|
||||
|
||||
if (!streamIds.length) {
|
||||
return new ChatSDKError('not_found:stream').toResponse();
|
||||
return new ChatSDKError("not_found:stream").toResponse();
|
||||
}
|
||||
|
||||
const recentStreamId = streamIds.at(-1);
|
||||
|
||||
if (!recentStreamId) {
|
||||
return new ChatSDKError('not_found:stream').toResponse();
|
||||
return new ChatSDKError("not_found:stream").toResponse();
|
||||
}
|
||||
|
||||
const emptyDataStream = createUIMessageStream<ChatMessage>({
|
||||
// biome-ignore lint/suspicious/noEmptyBlockStatements: "Needs to exist"
|
||||
execute: () => {},
|
||||
});
|
||||
|
||||
const stream = await streamContext.resumableStream(recentStreamId, () =>
|
||||
emptyDataStream.pipeThrough(new JsonToSseTransformStream()),
|
||||
emptyDataStream.pipeThrough(new JsonToSseTransformStream())
|
||||
);
|
||||
|
||||
/*
|
||||
|
|
@ -82,7 +83,7 @@ export async function GET(
|
|||
return new Response(emptyDataStream, { status: 200 });
|
||||
}
|
||||
|
||||
if (mostRecentMessage.role !== 'assistant') {
|
||||
if (mostRecentMessage.role !== "assistant") {
|
||||
return new Response(emptyDataStream, { status: 200 });
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +96,7 @@ export async function GET(
|
|||
const restoredStream = createUIMessageStream<ChatMessage>({
|
||||
execute: ({ writer }) => {
|
||||
writer.write({
|
||||
type: 'data-appendMessage',
|
||||
type: "data-appendMessage",
|
||||
data: JSON.stringify(mostRecentMessage),
|
||||
transient: true,
|
||||
});
|
||||
|
|
@ -104,7 +105,7 @@ export async function GET(
|
|||
|
||||
return new Response(
|
||||
restoredStream.pipeThrough(new JsonToSseTransformStream()),
|
||||
{ status: 200 },
|
||||
{ status: 200 }
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { geolocation } from "@vercel/functions";
|
||||
import {
|
||||
convertToModelMessages,
|
||||
createUIMessageStream,
|
||||
|
|
@ -5,9 +6,27 @@ import {
|
|||
smoothStream,
|
||||
stepCountIs,
|
||||
streamText,
|
||||
} from 'ai';
|
||||
import { auth, type UserType } from '@/app/(auth)/auth';
|
||||
import { type RequestHints, systemPrompt } from '@/lib/ai/prompts';
|
||||
} from "ai";
|
||||
import { unstable_cache as cache } from "next/cache";
|
||||
import { after } from "next/server";
|
||||
import {
|
||||
createResumableStreamContext,
|
||||
type ResumableStreamContext,
|
||||
} from "resumable-stream";
|
||||
import type { ModelCatalog } from "tokenlens/core";
|
||||
import { fetchModels } from "tokenlens/fetch";
|
||||
import { getUsage } from "tokenlens/helpers";
|
||||
import { auth, type UserType } from "@/app/(auth)/auth";
|
||||
import type { VisibilityType } from "@/components/visibility-selector";
|
||||
import { entitlementsByUserType } from "@/lib/ai/entitlements";
|
||||
import type { ChatModel } from "@/lib/ai/models";
|
||||
import { type RequestHints, systemPrompt } from "@/lib/ai/prompts";
|
||||
import { myProvider } from "@/lib/ai/providers";
|
||||
import { createDocument } from "@/lib/ai/tools/create-document";
|
||||
import { getWeather } from "@/lib/ai/tools/get-weather";
|
||||
import { requestSuggestions } from "@/lib/ai/tools/request-suggestions";
|
||||
import { updateDocument } from "@/lib/ai/tools/update-document";
|
||||
import { isProductionEnvironment } from "@/lib/constants";
|
||||
import {
|
||||
createStreamId,
|
||||
deleteChatById,
|
||||
|
|
@ -16,33 +35,14 @@ import {
|
|||
getMessagesByChatId,
|
||||
saveChat,
|
||||
saveMessages,
|
||||
} from '@/lib/db/queries';
|
||||
import { updateChatLastContextById } from '@/lib/db/queries';
|
||||
import { convertToUIMessages, generateUUID } from '@/lib/utils';
|
||||
import { generateTitleFromUserMessage } from '../../actions';
|
||||
import { createDocument } from '@/lib/ai/tools/create-document';
|
||||
import { updateDocument } from '@/lib/ai/tools/update-document';
|
||||
import { requestSuggestions } from '@/lib/ai/tools/request-suggestions';
|
||||
import { getWeather } from '@/lib/ai/tools/get-weather';
|
||||
import { isProductionEnvironment } from '@/lib/constants';
|
||||
import { myProvider } from '@/lib/ai/providers';
|
||||
import { entitlementsByUserType } from '@/lib/ai/entitlements';
|
||||
import { postRequestBodySchema, type PostRequestBody } from './schema';
|
||||
import { geolocation } from '@vercel/functions';
|
||||
import {
|
||||
createResumableStreamContext,
|
||||
type ResumableStreamContext,
|
||||
} from 'resumable-stream';
|
||||
import { after } from 'next/server';
|
||||
import { ChatSDKError } from '@/lib/errors';
|
||||
import type { ChatMessage } from '@/lib/types';
|
||||
import type { ChatModel } from '@/lib/ai/models';
|
||||
import type { VisibilityType } from '@/components/visibility-selector';
|
||||
import { unstable_cache as cache } from 'next/cache';
|
||||
import { fetchModels } from 'tokenlens/fetch';
|
||||
import { getUsage } from 'tokenlens/helpers';
|
||||
import type { ModelCatalog } from 'tokenlens/core';
|
||||
import type { AppUsage } from '@/lib/usage';
|
||||
updateChatLastContextById,
|
||||
} from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
import type { ChatMessage } from "@/lib/types";
|
||||
import type { AppUsage } from "@/lib/usage";
|
||||
import { convertToUIMessages, generateUUID } from "@/lib/utils";
|
||||
import { generateTitleFromUserMessage } from "../../actions";
|
||||
import { type PostRequestBody, postRequestBodySchema } from "./schema";
|
||||
|
||||
export const maxDuration = 60;
|
||||
|
||||
|
|
@ -54,14 +54,14 @@ const getTokenlensCatalog = cache(
|
|||
return await fetchModels();
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'TokenLens: catalog fetch failed, using default catalog',
|
||||
err,
|
||||
"TokenLens: catalog fetch failed, using default catalog",
|
||||
err
|
||||
);
|
||||
return undefined; // tokenlens helpers will fall back to defaultCatalog
|
||||
return; // tokenlens helpers will fall back to defaultCatalog
|
||||
}
|
||||
},
|
||||
['tokenlens-catalog'],
|
||||
{ revalidate: 24 * 60 * 60 }, // 24 hours
|
||||
["tokenlens-catalog"],
|
||||
{ revalidate: 24 * 60 * 60 } // 24 hours
|
||||
);
|
||||
|
||||
export function getStreamContext() {
|
||||
|
|
@ -71,9 +71,9 @@ export function getStreamContext() {
|
|||
waitUntil: after,
|
||||
});
|
||||
} catch (error: any) {
|
||||
if (error.message.includes('REDIS_URL')) {
|
||||
if (error.message.includes("REDIS_URL")) {
|
||||
console.log(
|
||||
' > Resumable streams are disabled due to missing REDIS_URL',
|
||||
" > Resumable streams are disabled due to missing REDIS_URL"
|
||||
);
|
||||
} else {
|
||||
console.error(error);
|
||||
|
|
@ -91,7 +91,7 @@ export async function POST(request: Request) {
|
|||
const json = await request.json();
|
||||
requestBody = postRequestBodySchema.parse(json);
|
||||
} catch (_) {
|
||||
return new ChatSDKError('bad_request:api').toResponse();
|
||||
return new ChatSDKError("bad_request:api").toResponse();
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -103,14 +103,14 @@ export async function POST(request: Request) {
|
|||
}: {
|
||||
id: string;
|
||||
message: ChatMessage;
|
||||
selectedChatModel: ChatModel['id'];
|
||||
selectedChatModel: ChatModel["id"];
|
||||
selectedVisibilityType: VisibilityType;
|
||||
} = requestBody;
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('unauthorized:chat').toResponse();
|
||||
return new ChatSDKError("unauthorized:chat").toResponse();
|
||||
}
|
||||
|
||||
const userType: UserType = session.user.type;
|
||||
|
|
@ -121,12 +121,16 @@ export async function POST(request: Request) {
|
|||
});
|
||||
|
||||
if (messageCount > entitlementsByUserType[userType].maxMessagesPerDay) {
|
||||
return new ChatSDKError('rate_limit:chat').toResponse();
|
||||
return new ChatSDKError("rate_limit:chat").toResponse();
|
||||
}
|
||||
|
||||
const chat = await getChatById({ id });
|
||||
|
||||
if (!chat) {
|
||||
if (chat) {
|
||||
if (chat.userId !== session.user.id) {
|
||||
return new ChatSDKError("forbidden:chat").toResponse();
|
||||
}
|
||||
} else {
|
||||
const title = await generateTitleFromUserMessage({
|
||||
message,
|
||||
});
|
||||
|
|
@ -137,10 +141,6 @@ export async function POST(request: Request) {
|
|||
title,
|
||||
visibility: selectedVisibilityType,
|
||||
});
|
||||
} else {
|
||||
if (chat.userId !== session.user.id) {
|
||||
return new ChatSDKError('forbidden:chat').toResponse();
|
||||
}
|
||||
}
|
||||
|
||||
const messagesFromDb = await getMessagesByChatId({ id });
|
||||
|
|
@ -160,7 +160,7 @@ export async function POST(request: Request) {
|
|||
{
|
||||
chatId: id,
|
||||
id: message.id,
|
||||
role: 'user',
|
||||
role: "user",
|
||||
parts: message.parts,
|
||||
attachments: [],
|
||||
createdAt: new Date(),
|
||||
|
|
@ -181,15 +181,15 @@ export async function POST(request: Request) {
|
|||
messages: convertToModelMessages(uiMessages),
|
||||
stopWhen: stepCountIs(5),
|
||||
experimental_activeTools:
|
||||
selectedChatModel === 'chat-model-reasoning'
|
||||
selectedChatModel === "chat-model-reasoning"
|
||||
? []
|
||||
: [
|
||||
'getWeather',
|
||||
'createDocument',
|
||||
'updateDocument',
|
||||
'requestSuggestions',
|
||||
"getWeather",
|
||||
"createDocument",
|
||||
"updateDocument",
|
||||
"requestSuggestions",
|
||||
],
|
||||
experimental_transform: smoothStream({ chunking: 'word' }),
|
||||
experimental_transform: smoothStream({ chunking: "word" }),
|
||||
tools: {
|
||||
getWeather,
|
||||
createDocument: createDocument({ session, dataStream }),
|
||||
|
|
@ -201,7 +201,7 @@ export async function POST(request: Request) {
|
|||
},
|
||||
experimental_telemetry: {
|
||||
isEnabled: isProductionEnvironment,
|
||||
functionId: 'stream-text',
|
||||
functionId: "stream-text",
|
||||
},
|
||||
onFinish: async ({ usage }) => {
|
||||
try {
|
||||
|
|
@ -210,23 +210,29 @@ export async function POST(request: Request) {
|
|||
myProvider.languageModel(selectedChatModel).modelId;
|
||||
if (!modelId) {
|
||||
finalMergedUsage = usage;
|
||||
dataStream.write({ type: 'data-usage', data: finalMergedUsage });
|
||||
dataStream.write({
|
||||
type: "data-usage",
|
||||
data: finalMergedUsage,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!providers) {
|
||||
finalMergedUsage = usage;
|
||||
dataStream.write({ type: 'data-usage', data: finalMergedUsage });
|
||||
dataStream.write({
|
||||
type: "data-usage",
|
||||
data: finalMergedUsage,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = getUsage({ modelId, usage, providers });
|
||||
finalMergedUsage = { ...usage, ...summary, modelId } as AppUsage;
|
||||
dataStream.write({ type: 'data-usage', data: finalMergedUsage });
|
||||
dataStream.write({ type: "data-usage", data: finalMergedUsage });
|
||||
} catch (err) {
|
||||
console.warn('TokenLens enrichment failed', err);
|
||||
console.warn("TokenLens enrichment failed", err);
|
||||
finalMergedUsage = usage;
|
||||
dataStream.write({ type: 'data-usage', data: finalMergedUsage });
|
||||
dataStream.write({ type: "data-usage", data: finalMergedUsage });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -236,16 +242,16 @@ export async function POST(request: Request) {
|
|||
dataStream.merge(
|
||||
result.toUIMessageStream({
|
||||
sendReasoning: true,
|
||||
}),
|
||||
})
|
||||
);
|
||||
},
|
||||
generateId: generateUUID,
|
||||
onFinish: async ({ messages }) => {
|
||||
await saveMessages({
|
||||
messages: messages.map((message) => ({
|
||||
id: message.id,
|
||||
role: message.role,
|
||||
parts: message.parts,
|
||||
messages: messages.map((currentMessage) => ({
|
||||
id: currentMessage.id,
|
||||
role: currentMessage.role,
|
||||
parts: currentMessage.parts,
|
||||
createdAt: new Date(),
|
||||
attachments: [],
|
||||
chatId: id,
|
||||
|
|
@ -259,12 +265,12 @@ export async function POST(request: Request) {
|
|||
context: finalMergedUsage,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('Unable to persist last usage for chat', id, err);
|
||||
console.warn("Unable to persist last usage for chat", id, err);
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
return 'Oops, an error occurred!';
|
||||
return "Oops, an error occurred!";
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -273,12 +279,12 @@ export async function POST(request: Request) {
|
|||
if (streamContext) {
|
||||
return new Response(
|
||||
await streamContext.resumableStream(streamId, () =>
|
||||
stream.pipeThrough(new JsonToSseTransformStream()),
|
||||
),
|
||||
stream.pipeThrough(new JsonToSseTransformStream())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return new Response(stream.pipeThrough(new JsonToSseTransformStream()));
|
||||
}
|
||||
|
||||
return new Response(stream.pipeThrough(new JsonToSseTransformStream()));
|
||||
} catch (error) {
|
||||
if (error instanceof ChatSDKError) {
|
||||
return error.toResponse();
|
||||
|
|
@ -288,35 +294,35 @@ export async function POST(request: Request) {
|
|||
if (
|
||||
error instanceof Error &&
|
||||
error.message?.includes(
|
||||
'AI Gateway requires a valid credit card on file to service requests',
|
||||
"AI Gateway requires a valid credit card on file to service requests"
|
||||
)
|
||||
) {
|
||||
return new ChatSDKError('bad_request:activate_gateway').toResponse();
|
||||
return new ChatSDKError("bad_request:activate_gateway").toResponse();
|
||||
}
|
||||
|
||||
console.error('Unhandled error in chat API:', error);
|
||||
return new ChatSDKError('offline:chat').toResponse();
|
||||
console.error("Unhandled error in chat API:", error);
|
||||
return new ChatSDKError("offline:chat").toResponse();
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const id = searchParams.get('id');
|
||||
const id = searchParams.get("id");
|
||||
|
||||
if (!id) {
|
||||
return new ChatSDKError('bad_request:api').toResponse();
|
||||
return new ChatSDKError("bad_request:api").toResponse();
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('unauthorized:chat').toResponse();
|
||||
return new ChatSDKError("unauthorized:chat").toResponse();
|
||||
}
|
||||
|
||||
const chat = await getChatById({ id });
|
||||
|
||||
if (chat?.userId !== session.user.id) {
|
||||
return new ChatSDKError('forbidden:chat').toResponse();
|
||||
return new ChatSDKError("forbidden:chat").toResponse();
|
||||
}
|
||||
|
||||
const deletedChat = await deleteChatById({ id });
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { z } from 'zod';
|
||||
import { z } from "zod";
|
||||
|
||||
const textPartSchema = z.object({
|
||||
type: z.enum(['text']),
|
||||
type: z.enum(["text"]),
|
||||
text: z.string().min(1).max(2000),
|
||||
});
|
||||
|
||||
const filePartSchema = z.object({
|
||||
type: z.enum(['file']),
|
||||
mediaType: z.enum(['image/jpeg', 'image/png']),
|
||||
type: z.enum(["file"]),
|
||||
mediaType: z.enum(["image/jpeg", "image/png"]),
|
||||
name: z.string().min(1).max(100),
|
||||
url: z.string().url(),
|
||||
});
|
||||
|
|
@ -18,11 +18,11 @@ export const postRequestBodySchema = z.object({
|
|||
id: z.string().uuid(),
|
||||
message: z.object({
|
||||
id: z.string().uuid(),
|
||||
role: z.enum(['user']),
|
||||
role: z.enum(["user"]),
|
||||
parts: z.array(partSchema),
|
||||
}),
|
||||
selectedChatModel: z.enum(['chat-model', 'chat-model-reasoning']),
|
||||
selectedVisibilityType: z.enum(['public', 'private']),
|
||||
selectedChatModel: z.enum(["chat-model", "chat-model-reasoning"]),
|
||||
selectedVisibilityType: z.enum(["public", "private"]),
|
||||
});
|
||||
|
||||
export type PostRequestBody = z.infer<typeof postRequestBodySchema>;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
import { auth } from '@/app/(auth)/auth';
|
||||
import type { ArtifactKind } from '@/components/artifact';
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import type { ArtifactKind } from "@/components/artifact";
|
||||
import {
|
||||
deleteDocumentsByIdAfterTimestamp,
|
||||
getDocumentsById,
|
||||
saveDocument,
|
||||
} from '@/lib/db/queries';
|
||||
import { ChatSDKError } from '@/lib/errors';
|
||||
} from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const id = searchParams.get('id');
|
||||
const id = searchParams.get("id");
|
||||
|
||||
if (!id) {
|
||||
return new ChatSDKError(
|
||||
'bad_request:api',
|
||||
'Parameter id is missing',
|
||||
"bad_request:api",
|
||||
"Parameter id is missing"
|
||||
).toResponse();
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('unauthorized:document').toResponse();
|
||||
return new ChatSDKError("unauthorized:document").toResponse();
|
||||
}
|
||||
|
||||
const documents = await getDocumentsById({ id });
|
||||
|
|
@ -29,11 +29,11 @@ export async function GET(request: Request) {
|
|||
const [document] = documents;
|
||||
|
||||
if (!document) {
|
||||
return new ChatSDKError('not_found:document').toResponse();
|
||||
return new ChatSDKError("not_found:document").toResponse();
|
||||
}
|
||||
|
||||
if (document.userId !== session.user.id) {
|
||||
return new ChatSDKError('forbidden:document').toResponse();
|
||||
return new ChatSDKError("forbidden:document").toResponse();
|
||||
}
|
||||
|
||||
return Response.json(documents, { status: 200 });
|
||||
|
|
@ -41,19 +41,19 @@ export async function GET(request: Request) {
|
|||
|
||||
export async function POST(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const id = searchParams.get('id');
|
||||
const id = searchParams.get("id");
|
||||
|
||||
if (!id) {
|
||||
return new ChatSDKError(
|
||||
'bad_request:api',
|
||||
'Parameter id is required.',
|
||||
"bad_request:api",
|
||||
"Parameter id is required."
|
||||
).toResponse();
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('not_found:document').toResponse();
|
||||
return new ChatSDKError("not_found:document").toResponse();
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -66,10 +66,10 @@ export async function POST(request: Request) {
|
|||
const documents = await getDocumentsById({ id });
|
||||
|
||||
if (documents.length > 0) {
|
||||
const [document] = documents;
|
||||
const [doc] = documents;
|
||||
|
||||
if (document.userId !== session.user.id) {
|
||||
return new ChatSDKError('forbidden:document').toResponse();
|
||||
if (doc.userId !== session.user.id) {
|
||||
return new ChatSDKError("forbidden:document").toResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,27 +86,27 @@ export async function POST(request: Request) {
|
|||
|
||||
export async function DELETE(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const id = searchParams.get('id');
|
||||
const timestamp = searchParams.get('timestamp');
|
||||
const id = searchParams.get("id");
|
||||
const timestamp = searchParams.get("timestamp");
|
||||
|
||||
if (!id) {
|
||||
return new ChatSDKError(
|
||||
'bad_request:api',
|
||||
'Parameter id is required.',
|
||||
"bad_request:api",
|
||||
"Parameter id is required."
|
||||
).toResponse();
|
||||
}
|
||||
|
||||
if (!timestamp) {
|
||||
return new ChatSDKError(
|
||||
'bad_request:api',
|
||||
'Parameter timestamp is required.',
|
||||
"bad_request:api",
|
||||
"Parameter timestamp is required."
|
||||
).toResponse();
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('unauthorized:document').toResponse();
|
||||
return new ChatSDKError("unauthorized:document").toResponse();
|
||||
}
|
||||
|
||||
const documents = await getDocumentsById({ id });
|
||||
|
|
@ -114,7 +114,7 @@ export async function DELETE(request: Request) {
|
|||
const [document] = documents;
|
||||
|
||||
if (document.userId !== session.user.id) {
|
||||
return new ChatSDKError('forbidden:document').toResponse();
|
||||
return new ChatSDKError("forbidden:document").toResponse();
|
||||
}
|
||||
|
||||
const documentsDeleted = await deleteDocumentsByIdAfterTimestamp({
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { put } from '@vercel/blob';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
import { put } from "@vercel/blob";
|
||||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { auth } from '@/app/(auth)/auth';
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
|
||||
// Use Blob instead of File since File is not available in Node.js environment
|
||||
const FileSchema = z.object({
|
||||
file: z
|
||||
.instanceof(Blob)
|
||||
.refine((file) => file.size <= 5 * 1024 * 1024, {
|
||||
message: 'File size should be less than 5MB',
|
||||
message: "File size should be less than 5MB",
|
||||
})
|
||||
// Update the file type based on the kind of files you want to accept
|
||||
.refine((file) => ['image/jpeg', 'image/png'].includes(file.type), {
|
||||
message: 'File type should be JPEG or PNG',
|
||||
.refine((file) => ["image/jpeg", "image/png"].includes(file.type), {
|
||||
message: "File type should be JPEG or PNG",
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
@ -21,19 +21,19 @@ export async function POST(request: Request) {
|
|||
const session = await auth();
|
||||
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (request.body === null) {
|
||||
return new Response('Request body is empty', { status: 400 });
|
||||
return new Response("Request body is empty", { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const file = formData.get('file') as Blob;
|
||||
const file = formData.get("file") as Blob;
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json({ error: 'No file uploaded' }, { status: 400 });
|
||||
return NextResponse.json({ error: "No file uploaded" }, { status: 400 });
|
||||
}
|
||||
|
||||
const validatedFile = FileSchema.safeParse({ file });
|
||||
|
|
@ -41,28 +41,28 @@ export async function POST(request: Request) {
|
|||
if (!validatedFile.success) {
|
||||
const errorMessage = validatedFile.error.errors
|
||||
.map((error) => error.message)
|
||||
.join(', ');
|
||||
.join(", ");
|
||||
|
||||
return NextResponse.json({ error: errorMessage }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get filename from formData since Blob doesn't have name property
|
||||
const filename = (formData.get('file') as File).name;
|
||||
const filename = (formData.get("file") as File).name;
|
||||
const fileBuffer = await file.arrayBuffer();
|
||||
|
||||
try {
|
||||
const data = await put(`${filename}`, fileBuffer, {
|
||||
access: 'public',
|
||||
access: "public",
|
||||
});
|
||||
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Upload failed' }, { status: 500 });
|
||||
} catch (_error) {
|
||||
return NextResponse.json({ error: "Upload failed" }, { status: 500 });
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to process request' },
|
||||
{ status: 500 },
|
||||
{ error: "Failed to process request" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import { auth } from '@/app/(auth)/auth';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { getChatsByUserId } from '@/lib/db/queries';
|
||||
import { ChatSDKError } from '@/lib/errors';
|
||||
import type { NextRequest } from "next/server";
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { getChatsByUserId } from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = request.nextUrl;
|
||||
|
||||
const limit = Number.parseInt(searchParams.get('limit') || '10');
|
||||
const startingAfter = searchParams.get('starting_after');
|
||||
const endingBefore = searchParams.get('ending_before');
|
||||
const limit = Number.parseInt(searchParams.get("limit") || "10", 10);
|
||||
const startingAfter = searchParams.get("starting_after");
|
||||
const endingBefore = searchParams.get("ending_before");
|
||||
|
||||
if (startingAfter && endingBefore) {
|
||||
return new ChatSDKError(
|
||||
'bad_request:api',
|
||||
'Only one of starting_after or ending_before can be provided.',
|
||||
"bad_request:api",
|
||||
"Only one of starting_after or ending_before can be provided."
|
||||
).toResponse();
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('unauthorized:chat').toResponse();
|
||||
return new ChatSDKError("unauthorized:chat").toResponse();
|
||||
}
|
||||
|
||||
const chats = await getChatsByUserId({
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
import { auth } from '@/app/(auth)/auth';
|
||||
import { getSuggestionsByDocumentId } from '@/lib/db/queries';
|
||||
import { ChatSDKError } from '@/lib/errors';
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { getSuggestionsByDocumentId } from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const documentId = searchParams.get('documentId');
|
||||
const documentId = searchParams.get("documentId");
|
||||
|
||||
if (!documentId) {
|
||||
return new ChatSDKError(
|
||||
'bad_request:api',
|
||||
'Parameter documentId is required.',
|
||||
"bad_request:api",
|
||||
"Parameter documentId is required."
|
||||
).toResponse();
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('unauthorized:suggestions').toResponse();
|
||||
return new ChatSDKError("unauthorized:suggestions").toResponse();
|
||||
}
|
||||
|
||||
const suggestions = await getSuggestionsByDocumentId({
|
||||
|
|
@ -30,7 +30,7 @@ export async function GET(request: Request) {
|
|||
}
|
||||
|
||||
if (suggestion.userId !== session.user.id) {
|
||||
return new ChatSDKError('forbidden:api').toResponse();
|
||||
return new ChatSDKError("forbidden:api").toResponse();
|
||||
}
|
||||
|
||||
return Response.json(suggestions, { status: 200 });
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
import { auth } from '@/app/(auth)/auth';
|
||||
import { getChatById, getVotesByChatId, voteMessage } from '@/lib/db/queries';
|
||||
import { ChatSDKError } from '@/lib/errors';
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { getChatById, getVotesByChatId, voteMessage } from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const chatId = searchParams.get('chatId');
|
||||
const chatId = searchParams.get("chatId");
|
||||
|
||||
if (!chatId) {
|
||||
return new ChatSDKError(
|
||||
'bad_request:api',
|
||||
'Parameter chatId is required.',
|
||||
"bad_request:api",
|
||||
"Parameter chatId is required."
|
||||
).toResponse();
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('unauthorized:vote').toResponse();
|
||||
return new ChatSDKError("unauthorized:vote").toResponse();
|
||||
}
|
||||
|
||||
const chat = await getChatById({ id: chatId });
|
||||
|
||||
if (!chat) {
|
||||
return new ChatSDKError('not_found:chat').toResponse();
|
||||
return new ChatSDKError("not_found:chat").toResponse();
|
||||
}
|
||||
|
||||
if (chat.userId !== session.user.id) {
|
||||
return new ChatSDKError('forbidden:vote').toResponse();
|
||||
return new ChatSDKError("forbidden:vote").toResponse();
|
||||
}
|
||||
|
||||
const votes = await getVotesByChatId({ id: chatId });
|
||||
|
|
@ -39,37 +39,37 @@ export async function PATCH(request: Request) {
|
|||
chatId,
|
||||
messageId,
|
||||
type,
|
||||
}: { chatId: string; messageId: string; type: 'up' | 'down' } =
|
||||
}: { chatId: string; messageId: string; type: "up" | "down" } =
|
||||
await request.json();
|
||||
|
||||
if (!chatId || !messageId || !type) {
|
||||
return new ChatSDKError(
|
||||
'bad_request:api',
|
||||
'Parameters chatId, messageId, and type are required.',
|
||||
"bad_request:api",
|
||||
"Parameters chatId, messageId, and type are required."
|
||||
).toResponse();
|
||||
}
|
||||
|
||||
const session = await auth();
|
||||
|
||||
if (!session?.user) {
|
||||
return new ChatSDKError('unauthorized:vote').toResponse();
|
||||
return new ChatSDKError("unauthorized:vote").toResponse();
|
||||
}
|
||||
|
||||
const chat = await getChatById({ id: chatId });
|
||||
|
||||
if (!chat) {
|
||||
return new ChatSDKError('not_found:vote').toResponse();
|
||||
return new ChatSDKError("not_found:vote").toResponse();
|
||||
}
|
||||
|
||||
if (chat.userId !== session.user.id) {
|
||||
return new ChatSDKError('forbidden:vote').toResponse();
|
||||
return new ChatSDKError("forbidden:vote").toResponse();
|
||||
}
|
||||
|
||||
await voteMessage({
|
||||
chatId,
|
||||
messageId,
|
||||
type: type,
|
||||
type,
|
||||
});
|
||||
|
||||
return new Response('Message voted', { status: 200 });
|
||||
return new Response("Message voted", { status: 200 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { cookies } from 'next/headers';
|
||||
import { notFound, redirect } from 'next/navigation';
|
||||
import { cookies } from "next/headers";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
|
||||
import { auth } from '@/app/(auth)/auth';
|
||||
import { Chat } from '@/components/chat';
|
||||
import { getChatById, getMessagesByChatId } from '@/lib/db/queries';
|
||||
import { DataStreamHandler } from '@/components/data-stream-handler';
|
||||
import { DEFAULT_CHAT_MODEL } from '@/lib/ai/models';
|
||||
import { convertToUIMessages } from '@/lib/utils';
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
import { Chat } from "@/components/chat";
|
||||
import { DataStreamHandler } from "@/components/data-stream-handler";
|
||||
import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models";
|
||||
import { getChatById, getMessagesByChatId } from "@/lib/db/queries";
|
||||
import { convertToUIMessages } from "@/lib/utils";
|
||||
|
||||
export default async function Page(props: { params: Promise<{ id: string }> }) {
|
||||
const params = await props.params;
|
||||
|
|
@ -20,10 +20,10 @@ export default async function Page(props: { params: Promise<{ id: string }> }) {
|
|||
const session = await auth();
|
||||
|
||||
if (!session) {
|
||||
redirect('/api/auth/guest');
|
||||
redirect("/api/auth/guest");
|
||||
}
|
||||
|
||||
if (chat.visibility === 'private') {
|
||||
if (chat.visibility === "private") {
|
||||
if (!session.user) {
|
||||
return notFound();
|
||||
}
|
||||
|
|
@ -40,20 +40,19 @@ export default async function Page(props: { params: Promise<{ id: string }> }) {
|
|||
const uiMessages = convertToUIMessages(messagesFromDb);
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const chatModelFromCookie = cookieStore.get('chat-model');
|
||||
const chatModelFromCookie = cookieStore.get("chat-model");
|
||||
|
||||
if (!chatModelFromCookie) {
|
||||
return (
|
||||
<>
|
||||
<Chat
|
||||
autoResume={true}
|
||||
id={chat.id}
|
||||
initialMessages={uiMessages}
|
||||
initialChatModel={DEFAULT_CHAT_MODEL}
|
||||
initialLastContext={chat.lastContext ?? undefined}
|
||||
initialMessages={uiMessages}
|
||||
initialVisibilityType={chat.visibility}
|
||||
isReadonly={session?.user?.id !== chat.userId}
|
||||
session={session}
|
||||
autoResume={true}
|
||||
initialLastContext={chat.lastContext ?? undefined}
|
||||
/>
|
||||
<DataStreamHandler />
|
||||
</>
|
||||
|
|
@ -63,14 +62,13 @@ export default async function Page(props: { params: Promise<{ id: string }> }) {
|
|||
return (
|
||||
<>
|
||||
<Chat
|
||||
autoResume={true}
|
||||
id={chat.id}
|
||||
initialMessages={uiMessages}
|
||||
initialChatModel={chatModelFromCookie.value}
|
||||
initialLastContext={chat.lastContext ?? undefined}
|
||||
initialMessages={uiMessages}
|
||||
initialVisibilityType={chat.visibility}
|
||||
isReadonly={session?.user?.id !== chat.userId}
|
||||
session={session}
|
||||
autoResume={true}
|
||||
initialLastContext={chat.lastContext ?? undefined}
|
||||
/>
|
||||
<DataStreamHandler />
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { cookies } from 'next/headers';
|
||||
|
||||
import { AppSidebar } from '@/components/app-sidebar';
|
||||
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
|
||||
import { auth } from '../(auth)/auth';
|
||||
import Script from 'next/script';
|
||||
import { DataStreamProvider } from '@/components/data-stream-provider';
|
||||
import { cookies } from "next/headers";
|
||||
import Script from "next/script";
|
||||
import { AppSidebar } from "@/components/app-sidebar";
|
||||
import { DataStreamProvider } from "@/components/data-stream-provider";
|
||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
||||
import { auth } from "../(auth)/auth";
|
||||
|
||||
export const experimental_ppr = true;
|
||||
|
||||
|
|
@ -14,7 +13,7 @@ export default async function Layout({
|
|||
children: React.ReactNode;
|
||||
}) {
|
||||
const [session, cookieStore] = await Promise.all([auth(), cookies()]);
|
||||
const isCollapsed = cookieStore.get('sidebar:state')?.value !== 'true';
|
||||
const isCollapsed = cookieStore.get("sidebar_state")?.value !== "true";
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,36 +1,34 @@
|
|||
import { cookies } from 'next/headers';
|
||||
|
||||
import { Chat } from '@/components/chat';
|
||||
import { DEFAULT_CHAT_MODEL } from '@/lib/ai/models';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
import { DataStreamHandler } from '@/components/data-stream-handler';
|
||||
import { auth } from '../(auth)/auth';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Chat } from "@/components/chat";
|
||||
import { DataStreamHandler } from "@/components/data-stream-handler";
|
||||
import { DEFAULT_CHAT_MODEL } from "@/lib/ai/models";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
import { auth } from "../(auth)/auth";
|
||||
|
||||
export default async function Page() {
|
||||
const session = await auth();
|
||||
|
||||
if (!session) {
|
||||
redirect('/api/auth/guest');
|
||||
redirect("/api/auth/guest");
|
||||
}
|
||||
|
||||
const id = generateUUID();
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const modelIdFromCookie = cookieStore.get('chat-model');
|
||||
const modelIdFromCookie = cookieStore.get("chat-model");
|
||||
|
||||
if (!modelIdFromCookie) {
|
||||
return (
|
||||
<>
|
||||
<Chat
|
||||
key={id}
|
||||
autoResume={false}
|
||||
id={id}
|
||||
initialMessages={[]}
|
||||
initialChatModel={DEFAULT_CHAT_MODEL}
|
||||
initialMessages={[]}
|
||||
initialVisibilityType="private"
|
||||
isReadonly={false}
|
||||
session={session}
|
||||
autoResume={false}
|
||||
key={id}
|
||||
/>
|
||||
<DataStreamHandler />
|
||||
</>
|
||||
|
|
@ -40,14 +38,13 @@ export default async function Page() {
|
|||
return (
|
||||
<>
|
||||
<Chat
|
||||
key={id}
|
||||
autoResume={false}
|
||||
id={id}
|
||||
initialMessages={[]}
|
||||
initialChatModel={modelIdFromCookie.value}
|
||||
initialMessages={[]}
|
||||
initialVisibilityType="private"
|
||||
isReadonly={false}
|
||||
session={session}
|
||||
autoResume={false}
|
||||
key={id}
|
||||
/>
|
||||
<DataStreamHandler />
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
/* set base path to root directory to include utility classes from app, components, pages, etc. */
|
||||
@import "tailwindcss" source("..");
|
||||
@import "tailwindcss";
|
||||
|
||||
/* include utility classes in streamdown */
|
||||
@source '../node_modules/streamdown/dist/index.js';
|
||||
|
|
@ -47,6 +46,7 @@
|
|||
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
||||
/* border radius unit */
|
||||
--radius: 0.5rem;
|
||||
--sidebar: hsl(0 0% 98%);
|
||||
}
|
||||
|
||||
/* define design tokens (dark mode) */
|
||||
|
|
@ -83,6 +83,7 @@
|
|||
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
|
||||
--sidebar-border: hsl(240 3.7% 15.9%);
|
||||
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
||||
--sidebar: hsl(240 5.9% 10%);
|
||||
}
|
||||
|
||||
/* define theme */
|
||||
|
|
@ -294,3 +295,23 @@
|
|||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--border) transparent;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { Toaster } from 'sonner';
|
||||
import type { Metadata } from 'next';
|
||||
import { Geist, Geist_Mono } from 'next/font/google';
|
||||
import { ThemeProvider } from '@/components/theme-provider';
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Toaster } from "sonner";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
|
||||
import './globals.css';
|
||||
import { SessionProvider } from 'next-auth/react';
|
||||
import "./globals.css";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://chat.vercel.ai'),
|
||||
title: 'Next.js Chatbot Template',
|
||||
description: 'Next.js chatbot template using the AI SDK.',
|
||||
metadataBase: new URL("https://chat.vercel.ai"),
|
||||
title: "Next.js Chatbot Template",
|
||||
description: "Next.js chatbot template using the AI SDK.",
|
||||
};
|
||||
|
||||
export const viewport = {
|
||||
|
|
@ -17,19 +17,19 @@ export const viewport = {
|
|||
};
|
||||
|
||||
const geist = Geist({
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
variable: '--font-geist',
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-geist",
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
variable: '--font-geist-mono',
|
||||
subsets: ["latin"],
|
||||
display: "swap",
|
||||
variable: "--font-geist-mono",
|
||||
});
|
||||
|
||||
const LIGHT_THEME_COLOR = 'hsl(0 0% 100%)';
|
||||
const DARK_THEME_COLOR = 'hsl(240deg 10% 3.92%)';
|
||||
const LIGHT_THEME_COLOR = "hsl(0 0% 100%)";
|
||||
const DARK_THEME_COLOR = "hsl(240deg 10% 3.92%)";
|
||||
const THEME_COLOR_SCRIPT = `\
|
||||
(function() {
|
||||
var html = document.documentElement;
|
||||
|
|
@ -48,23 +48,24 @@ const THEME_COLOR_SCRIPT = `\
|
|||
updateThemeColor();
|
||||
})();`;
|
||||
|
||||
export default async function RootLayout({
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={`${geist.variable} ${geistMono.variable}`}
|
||||
// `next-themes` injects an extra classname to the body element to avoid
|
||||
// visual flicker before hydration. Hence the `suppressHydrationWarning`
|
||||
// prop is necessary to avoid the React hydration mismatch warning.
|
||||
// https://github.com/pacocoursey/next-themes?tab=readme-ov-file#with-app
|
||||
lang="en"
|
||||
suppressHydrationWarning
|
||||
className={`${geist.variable} ${geistMono.variable}`}
|
||||
>
|
||||
<head>
|
||||
<script
|
||||
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: THEME_COLOR_SCRIPT,
|
||||
}}
|
||||
|
|
@ -74,8 +75,8 @@ export default async function RootLayout({
|
|||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
enableSystem
|
||||
>
|
||||
<Toaster position="top-center" />
|
||||
<SessionProvider>{children}</SessionProvider>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue