Compare commits

..

10 Commits

@ -68,9 +68,18 @@ function getQueryClient() {
// suspends during the initial render. This may not be needed if we // suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client // have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) browserQueryClient = makeQueryClient(); if (!browserQueryClient) browserQueryClient = makeQueryClient();
// This code is for all users
window.__TANSTACK_QUERY_CLIENT__ = browserQueryClient;
return browserQueryClient; return browserQueryClient;
} }
// This code is only for TypeScript
declare global {
interface Window {
__TANSTACK_QUERY_CLIENT__: import("@tanstack/query-core").QueryClient;
}
}
export function SignInWithGoogle() { export function SignInWithGoogle() {
const pageContext = usePageContext(); const pageContext = usePageContext();
/** This is populated using the +onCreatePageContext.server.ts hook */ /** This is populated using the +onCreatePageContext.server.ts hook */
@ -232,7 +241,9 @@ function NavLinkChat() {
const { urlPathname } = pageContext; const { urlPathname } = pageContext;
const trpc = useTRPC(); const trpc = useTRPC();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
// const const { data: conversations } = useQuery(
trpc.chat.conversations.fetchAll.queryOptions()
);
const startConversation = useMutation( const startConversation = useMutation(
trpc.chat.conversations.start.mutationOptions({ trpc.chat.conversations.start.mutationOptions({
onSuccess: () => { onSuccess: () => {
@ -286,9 +297,6 @@ function NavLinkChat() {
}) })
); );
const { data: conversations } = useQuery(
trpc.chat.conversations.fetchAll.queryOptions()
);
// const selectedConversationId = useStore( // const selectedConversationId = useStore(
// (state) => state.selectedConversationId // (state) => state.selectedConversationId
// ); // );
@ -356,7 +364,7 @@ function NavLinkChat() {
> >
{conversations?.map((conversation) => ( {conversations?.map((conversation) => (
<NavLink <NavLink
key={conversation.id} key={conversation.id + conversation.title}
href={`/chat/${conversation.id}`} href={`/chat/${conversation.id}`}
label={conversation.title} label={conversation.title}
className="hover-container" className="hover-container"

@ -19,7 +19,6 @@
"@mantine/hooks": "^8.1.1", "@mantine/hooks": "^8.1.1",
"@neondatabase/serverless": "^1.0.1", "@neondatabase/serverless": "^1.0.1",
"@openrouter/ai-sdk-provider": "^1.1.2", "@openrouter/ai-sdk-provider": "^1.1.2",
"@sinclair/typebox": "^0.34.37",
"@tabler/icons-react": "^3.34.1", "@tabler/icons-react": "^3.34.1",
"@tanstack/react-query": "^5.85.3", "@tanstack/react-query": "^5.85.3",
"@trpc/client": "^11.4.4", "@trpc/client": "^11.4.4",

@ -316,14 +316,18 @@ export default function ChatPage() {
newConversations: null, newConversations: null,
}; };
} }
const newConversations: Array<Conversation> = [
...previousConversations, /** MUST make a deep, immutable copy in order to trigger re-render of
{ * oberserving components. */
...conversation, const newConversations: Array<Conversation> = JSON.parse(
title, JSON.stringify(previousConversations)
} as Conversation, );
]; const conversationToUpdate = newConversations.find((c) => c.id === id);
queryClient.setQueryData( if (!conversationToUpdate) {
return { previousConversations, newConversations };
}
conversationToUpdate.title = title;
queryClient.setQueryData<Array<Conversation>>(
trpc.chat.conversations.fetchAll.queryKey(), trpc.chat.conversations.fetchAll.queryKey(),
newConversations newConversations
); );
@ -505,6 +509,12 @@ export default function ChatPage() {
// onChange={(e) => { // onChange={(e) => {
// setConversationTitle(e.target.value); // setConversationTitle(e.target.value);
// }} // }}
onKeyUp={(e) => {
if (e.key === "Enter") {
e.preventDefault();
e.currentTarget.blur();
}
}}
onBlur={(e) => { onBlur={(e) => {
updateConversationTitle.mutateAsync({ updateConversationTitle.mutateAsync({
id: conversationId, id: conversationId,

@ -1,7 +1,7 @@
import type { PageContextServer } from "vike/types"; import type { PageContextServer } from "vike/types";
import { createCaller } from "../../../server/trpc/chat.js"; import { createCaller } from "../../../server/trpc/chat.js";
import { getDbClient } from "../../../database/postgres.js"; import { getDbClient } from "../../../database/postgres.js";
import { getOpenrouter } from "../../../server/provider.js"; import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { env } from "../../../server/env.js"; import { env } from "../../../server/env.js";
export type Data = Awaited<ReturnType<typeof data>>; export type Data = Awaited<ReturnType<typeof data>>;
@ -9,9 +9,10 @@ export type Data = Awaited<ReturnType<typeof data>>;
export const data = async (pageContext: PageContextServer) => { export const data = async (pageContext: PageContextServer) => {
const { id } = pageContext.routeParams; const { id } = pageContext.routeParams;
const caller = createCaller({ const caller = createCaller({
openrouter: getOpenrouter( openrouter: createOpenRouter({
(pageContext.env?.OPENROUTER_API_KEY || env.OPENROUTER_API_KEY) as string apiKey: (pageContext.env?.OPENROUTER_API_KEY ||
), env.OPENROUTER_API_KEY) as string,
}),
jwt: pageContext.session?.jwt, jwt: pageContext.session?.jwt,
dbClient: getDbClient( dbClient: getDbClient(
(pageContext.env?.POSTGRES_CONNECTION_STRING || (pageContext.env?.POSTGRES_CONNECTION_STRING ||

@ -32,9 +32,6 @@ importers:
'@openrouter/ai-sdk-provider': '@openrouter/ai-sdk-provider':
specifier: ^1.1.2 specifier: ^1.1.2
version: 1.1.2(ai@5.0.9(zod@4.0.17))(zod@4.0.17) version: 1.1.2(ai@5.0.9(zod@4.0.17))(zod@4.0.17)
'@sinclair/typebox':
specifier: ^0.34.37
version: 0.34.37
'@tabler/icons-react': '@tabler/icons-react':
specifier: ^3.34.1 specifier: ^3.34.1
version: 3.34.1(react@19.1.0) version: 3.34.1(react@19.1.0)
@ -1155,9 +1152,6 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@sinclair/typebox@0.34.37':
resolution: {integrity: sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==}
'@standard-schema/spec@1.0.0': '@standard-schema/spec@1.0.0':
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
@ -3936,8 +3930,6 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.44.1': '@rollup/rollup-win32-x64-msvc@4.44.1':
optional: true optional: true
'@sinclair/typebox@0.34.37': {}
'@standard-schema/spec@1.0.0': {} '@standard-schema/spec@1.0.0': {}
'@tabler/icons-react@3.34.1(react@19.1.0)': '@tabler/icons-react@3.34.1(react@19.1.0)':

@ -8,17 +8,13 @@ import {
import GoogleProvider from "@auth/core/providers/google"; import GoogleProvider from "@auth/core/providers/google";
import type { Session } from "@auth/core/types"; import type { Session } from "@auth/core/types";
// TODO: stop using universal-middleware and directly integrate server middlewares instead and/or use vike-server https://vike.dev/server. (Bati generates boilerplates that use universal-middleware https://github.com/magne4000/universal-middleware to make Bati's internal logic easier. This is temporary and will be removed soon.) // TODO: stop using universal-middleware and directly integrate server middlewares instead and/or use vike-server https://vike.dev/server. (Bati generates boilerplates that use universal-middleware https://github.com/magne4000/universal-middleware to make Bati's internal logic easier. This is temporary and will be removed soon.)
import type { import {
Get, type Get,
UniversalHandler, type UniversalHandler,
UniversalMiddleware, type UniversalMiddleware,
env as getEnv,
} from "@universal-middleware/core"; } from "@universal-middleware/core";
import { env } from "./env.js";
import { getDbClient } from "../database/index.js"; import { getDbClient } from "../database/index.js";
import { JWT } from "@auth/core/jwt";
const POSTGRES_CONNECTION_STRING =
"postgres://neondb_owner:npg_sOVmj8vWq2zG@ep-withered-king-adiz9gpi-pooler.c-2.us-east-1.aws.neon.tech:5432/neondb?sslmode=require&channel_binding=true";
if (!globalThis.crypto) { if (!globalThis.crypto) {
/** /**
@ -33,13 +29,15 @@ if (!globalThis.crypto) {
}); });
} }
const authjsConfig = { const authjsConfig = (env: Record<string, string>) =>
({
basePath: "/api/auth", basePath: "/api/auth",
trustHost: Boolean( // trustHost: Boolean(
env.AUTH_TRUST_HOST ?? env.VERCEL ?? env.NODE_ENV !== "production" // env.AUTH_TRUST_HOST ?? env.VERCEL ?? env.NODE_ENV !== "production"
), // ),
trustHost: true,
// TODO: Replace secret {@see https://authjs.dev/reference/core#secret} // TODO: Replace secret {@see https://authjs.dev/reference/core#secret}
secret: "buginoo", secret: env.AUTHJS_SECRET,
providers: [ providers: [
// TODO: Choose and implement providers // TODO: Choose and implement providers
// CredentialsProvider({ // CredentialsProvider({
@ -63,15 +61,15 @@ const authjsConfig = {
// }, // },
// }), // }),
GoogleProvider({ GoogleProvider({
clientId: clientId: env.GOOGLE_CLIENT_ID,
"697711350664-t6237s5n3ttjd1npp1qif1aupptkr0va.apps.googleusercontent.com", clientSecret: env.GOOGLE_CLIENT_SECRET,
clientSecret: "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",
}), }),
], ],
callbacks: { callbacks: {
async signIn({ user, account, profile }) { async signIn({ user, account, profile }) {
if (typeof user?.email !== "string") return false; if (typeof user?.email !== "string") return false;
const dbClient = await getDbClient(POSTGRES_CONNECTION_STRING); //@ts-ignore
const dbClient = await getDbClient(env.POSTGRES_CONNECTION_STRING);
let userFromDb = await dbClient let userFromDb = await dbClient
.selectFrom("users") .selectFrom("users")
.selectAll() .selectAll()
@ -97,7 +95,8 @@ const authjsConfig = {
}, },
jwt: async ({ token }) => { jwt: async ({ token }) => {
if (typeof token?.email !== "string") return token; if (typeof token?.email !== "string") return token;
const dbClient = await getDbClient(POSTGRES_CONNECTION_STRING); //@ts-ignore
const dbClient = await getDbClient(env.POSTGRES_CONNECTION_STRING);
let userFromDb = await dbClient let userFromDb = await dbClient
.selectFrom("users") .selectFrom("users")
.selectAll() .selectAll()
@ -137,7 +136,7 @@ const authjsConfig = {
}; };
}, },
}, },
} satisfies Omit<AuthConfig, "raw">; } satisfies Omit<AuthConfig, "raw">);
/** /**
* Retrieve Auth.js session from Request * Retrieve Auth.js session from Request
@ -175,11 +174,15 @@ export async function getSession(
* @link {@see https://authjs.dev/getting-started/session-management/get-session} * @link {@see https://authjs.dev/getting-started/session-management/get-session}
**/ **/
export const authjsSessionMiddleware: Get<[], UniversalMiddleware> = export const authjsSessionMiddleware: Get<[], UniversalMiddleware> =
() => async (request, context) => { () => async (request, context, runtime) => {
const env = getEnv(runtime);
try { try {
return { return {
...context, ...context,
session: await getSession(request, authjsConfig), session: await getSession(
request,
authjsConfig(env as Record<string, string>)
),
}; };
} catch (error) { } catch (error) {
console.debug("authjsSessionMiddleware:", error); console.debug("authjsSessionMiddleware:", error);
@ -194,6 +197,7 @@ export const authjsSessionMiddleware: Get<[], UniversalMiddleware> =
* Auth.js route * Auth.js route
* @link {@see https://authjs.dev/getting-started/installation} * @link {@see https://authjs.dev/getting-started/installation}
**/ **/
export const authjsHandler = (() => async (request) => { export const authjsHandler = (() => async (request, context, runtime) => {
return Auth(request, authjsConfig); const env = getEnv(runtime);
return Auth(request, authjsConfig(env as Record<string, string>));
}) satisfies Get<[], UniversalHandler>; }) satisfies Get<[], UniversalHandler>;

@ -1,14 +1,3 @@
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { env } from "./env.js";
export const openrouter = createOpenRouter({
apiKey: env.OPENROUTER_API_KEY,
});
export function getOpenrouter(OPENROUTER_API_KEY: string) {
return createOpenRouter({
apiKey: OPENROUTER_API_KEY,
});
}
export const MODEL_NAME = "mistralai/mistral-nemo"; export const MODEL_NAME = "mistralai/mistral-nemo";
// export const MODEL_NAME = "z-ai/glm-4.5-air"; // export const MODEL_NAME = "z-ai/glm-4.5-air";
// export const MODEL_NAME = "openai/gpt-5-mini"; // export const MODEL_NAME = "openai/gpt-5-mini";

@ -7,7 +7,7 @@ import {
} from "@universal-middleware/core"; } from "@universal-middleware/core";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { getDbClient } from "../database/postgres"; import { getDbClient } from "../database/postgres";
import { getOpenrouter } from "./provider.js"; import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { env as processEnv } from "./env.js"; import { env as processEnv } from "./env.js";
import { getToken } from "@auth/core/jwt"; import { getToken } from "@auth/core/jwt";
@ -22,10 +22,22 @@ export const trpcHandler = ((endpoint) => (request, context, runtime) => {
(env.POSTGRES_CONNECTION_STRING || (env.POSTGRES_CONNECTION_STRING ||
processEnv.POSTGRES_CONNECTION_STRING) as string processEnv.POSTGRES_CONNECTION_STRING) as string
); );
const openrouter = getOpenrouter( const openrouter = createOpenRouter({
(env.OPENROUTER_API_KEY || processEnv.OPENROUTER_API_KEY) as string apiKey: (env.OPENROUTER_API_KEY ||
); processEnv.OPENROUTER_API_KEY) as string,
const jwt = await getToken({ req: request, secret: "buginoo" }); });
const jwt = await getToken({
req,
secret: (env.AUTHJS_SECRET || processEnv.AUTHJS_SECRET) as string,
/** Needed to specify cookie name because for some reason in production
* it wasn't reading the correct cookie but in development it was. It
* was not straightforward to fix the name of the cookie in
* `authOptions`, so I adjusted what cookie to read from here: */
cookieName:
env.NODE_ENV === "production"
? "__Secure-authjs.session-token"
: "authjs.session-token",
});
return { return {
...context, ...context,
...runtime, ...runtime,

@ -1,6 +1,6 @@
import { router, createCallerFactory, authProcedure } from "./server.js"; import { router, createCallerFactory, authProcedure } from "./server.js";
import type { DraftMessage } from "../../types.js"; import type { DraftMessage } from "../../types.js";
import { MODEL_NAME, openrouter } from "../provider.js"; import { MODEL_NAME } from "../provider.js";
import { generateObject, generateText, jsonSchema } from "ai"; import { generateObject, generateText, jsonSchema } from "ai";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { z } from "zod"; import { z } from "zod";

@ -1,8 +1,6 @@
import type { TSchema } from "@sinclair/typebox";
import { TypeCompiler } from "@sinclair/typebox/compiler";
import { initTRPC, TRPCError } from "@trpc/server"; import { initTRPC, TRPCError } from "@trpc/server";
import type { getDbClient } from "../../database/postgres"; import type { getDbClient } from "../../database/postgres";
import type { getOpenrouter } from "@server/provider.js"; import type { createOpenRouter } from "@openrouter/ai-sdk-provider";
import type { JWT } from "@auth/core/jwt"; import type { JWT } from "@auth/core/jwt";
/** /**
@ -13,7 +11,7 @@ const t = initTRPC
.context< .context<
object & { object & {
dbClient: ReturnType<typeof getDbClient>; dbClient: ReturnType<typeof getDbClient>;
openrouter: ReturnType<typeof getOpenrouter>; openrouter: ReturnType<typeof createOpenRouter>;
jwt?: JWT | null; jwt?: JWT | null;
} }
>() >()
@ -53,22 +51,4 @@ export const authProcedure = publicProcedure.use(
} }
); );
/**
* Generate a TRPC-compatible validator function given a Typebox schema.
* This was copied from [https://github.com/sinclairzx81/typebox/blob/6cfcdc02cc813af2f1be57407c771fc4fadfc34a/example/trpc/readme.md].
* @param schema A Typebox schema
* @returns A TRPC-compatible validator function
*/
export function Validator<T extends TSchema>(schema: T) {
const check = TypeCompiler.Compile(schema);
return (value: unknown) => {
if (check.Check(value)) return value;
const err = check.Errors(value).First();
throw new TRPCError({
message: `${err?.message} for ${err?.path}`,
code: "BAD_REQUEST",
});
};
}
export const createCallerFactory = t.createCallerFactory; export const createCallerFactory = t.createCallerFactory;

@ -6,3 +6,14 @@ compatibility_flags = [ "nodejs_compat" ]
[vars] [vars]
MILVUS_ADDRESS = "in03-639fdba4bcde098.serverless.gcp-us-west1.cloud.zilliz.com" MILVUS_ADDRESS = "in03-639fdba4bcde098.serverless.gcp-us-west1.cloud.zilliz.com"
MILVUS_USERNAME = "db_639fdba4bcde098" MILVUS_USERNAME = "db_639fdba4bcde098"
[env.production.vars]
MILVUS_ADDRESS = "in03-639fdba4bcde098.serverless.gcp-us-west1.cloud.zilliz.com"
MILVUS_USERNAME = "db_639fdba4bcde098"
GOOGLE_CLIENT_ID = "697711350664-t6237s5n3ttjd1npp1qif1aupptkr0va.apps.googleusercontent.com"
NODE_ENV = "production"
[env.preview.vars]
MILVUS_ADDRESS = "in03-639fdba4bcde098.serverless.gcp-us-west1.cloud.zilliz.com"
MILVUS_USERNAME = "db_639fdba4bcde098"
NODE_ENV = "development"
Loading…
Cancel
Save