Compare commits

..

No commits in common. '88adc158991d79cbc14c033638999bf530f9381e' and 'edfaabeb0df702f081e22bad494649480f3e8d9c' have entirely different histories.

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

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

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

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

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

@ -8,13 +8,17 @@ import {
import GoogleProvider from "@auth/core/providers/google";
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.)
import {
type Get,
type UniversalHandler,
type UniversalMiddleware,
env as getEnv,
import type {
Get,
UniversalHandler,
UniversalMiddleware,
} from "@universal-middleware/core";
import { env } from "./env.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) {
/**
@ -29,114 +33,111 @@ if (!globalThis.crypto) {
});
}
const authjsConfig = (env: Record<string, string>) =>
({
basePath: "/api/auth",
// trustHost: Boolean(
// env.AUTH_TRUST_HOST ?? env.VERCEL ?? env.NODE_ENV !== "production"
// ),
trustHost: true,
// TODO: Replace secret {@see https://authjs.dev/reference/core#secret}
secret: env.AUTHJS_SECRET,
providers: [
// TODO: Choose and implement providers
// CredentialsProvider({
// name: "Credentials",
// credentials: {
// username: { label: "Username", type: "text", placeholder: "jsmith" },
// password: { label: "Password", type: "password" },
// },
// async authorize() {
// // Add logic here to look up the user from the credentials supplied
// const user = {
// id: "019900bb-61b3-7333-b760-b27784dfe33b",
// name: "J Smith",
// email: "jsmith@example.com",
// };
const authjsConfig = {
basePath: "/api/auth",
trustHost: Boolean(
env.AUTH_TRUST_HOST ?? env.VERCEL ?? env.NODE_ENV !== "production"
),
// TODO: Replace secret {@see https://authjs.dev/reference/core#secret}
secret: "buginoo",
providers: [
// TODO: Choose and implement providers
// CredentialsProvider({
// name: "Credentials",
// credentials: {
// username: { label: "Username", type: "text", placeholder: "jsmith" },
// password: { label: "Password", type: "password" },
// },
// async authorize() {
// // Add logic here to look up the user from the credentials supplied
// const user = {
// id: "019900bb-61b3-7333-b760-b27784dfe33b",
// name: "J Smith",
// email: "jsmith@example.com",
// };
// // Any object returned will be saved in `user` property of the JWT
// // If you return null then an error will be displayed advising the user to check their details.
// // You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
// return user ?? null;
// },
// }),
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
}),
],
callbacks: {
async signIn({ user, account, profile }) {
if (typeof user?.email !== "string") return false;
//@ts-ignore
const dbClient = await getDbClient(env.POSTGRES_CONNECTION_STRING);
let userFromDb = await dbClient
.selectFrom("users")
.selectAll()
.where("email", "=", user.email)
.executeTakeFirst();
if (!userFromDb) {
userFromDb = (
await dbClient
.insertInto("users")
.values({
email: user.email,
username: user.email,
password: null,
createdAt: null,
lastLogin: null,
})
.returningAll()
.execute()
)[0];
}
console.log("signIn", user, account, profile);
return true;
},
jwt: async ({ token }) => {
if (typeof token?.email !== "string") return token;
//@ts-ignore
const dbClient = await getDbClient(env.POSTGRES_CONNECTION_STRING);
let userFromDb = await dbClient
.selectFrom("users")
.selectAll()
.where("email", "=", token.email || "")
.executeTakeFirst();
/** TODO: the following should never happen, because the account in
* created in the `isgnIn` step; but I don't know what error to throw here
* if for some reason there is no such account.*/
if (!userFromDb) {
userFromDb = (
await dbClient
.insertInto("users")
.values({
email: token.email,
username: token.email,
password: null,
createdAt: null,
lastLogin: null,
})
.returningAll()
.execute()
)[0];
}
return {
...token,
id: userFromDb?.id || "",
};
},
session: ({ token, session }) => {
return {
...session,
user: {
...session.user,
id: token.id as string,
},
jwt: token,
};
},
// // Any object returned will be saved in `user` property of the JWT
// // If you return null then an error will be displayed advising the user to check their details.
// // You can also Reject this callback with an Error thus the user will be sent to the error page with the error message as a query parameter
// return user ?? null;
// },
// }),
GoogleProvider({
clientId:
"697711350664-t6237s5n3ttjd1npp1qif1aupptkr0va.apps.googleusercontent.com",
clientSecret: "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",
}),
],
callbacks: {
async signIn({ user, account, profile }) {
if (typeof user?.email !== "string") return false;
const dbClient = await getDbClient(POSTGRES_CONNECTION_STRING);
let userFromDb = await dbClient
.selectFrom("users")
.selectAll()
.where("email", "=", user.email)
.executeTakeFirst();
if (!userFromDb) {
userFromDb = (
await dbClient
.insertInto("users")
.values({
email: user.email,
username: user.email,
password: null,
createdAt: null,
lastLogin: null,
})
.returningAll()
.execute()
)[0];
}
console.log("signIn", user, account, profile);
return true;
},
jwt: async ({ token }) => {
if (typeof token?.email !== "string") return token;
const dbClient = await getDbClient(POSTGRES_CONNECTION_STRING);
let userFromDb = await dbClient
.selectFrom("users")
.selectAll()
.where("email", "=", token.email || "")
.executeTakeFirst();
/** TODO: the following should never happen, because the account in
* created in the `isgnIn` step; but I don't know what error to throw here
* if for some reason there is no such account.*/
if (!userFromDb) {
userFromDb = (
await dbClient
.insertInto("users")
.values({
email: token.email,
username: token.email,
password: null,
createdAt: null,
lastLogin: null,
})
.returningAll()
.execute()
)[0];
}
return {
...token,
id: userFromDb?.id || "",
};
},
session: ({ token, session }) => {
return {
...session,
user: {
...session.user,
id: token.id as string,
},
jwt: token,
};
},
} satisfies Omit<AuthConfig, "raw">);
},
} satisfies Omit<AuthConfig, "raw">;
/**
* Retrieve Auth.js session from Request
@ -174,15 +175,11 @@ export async function getSession(
* @link {@see https://authjs.dev/getting-started/session-management/get-session}
**/
export const authjsSessionMiddleware: Get<[], UniversalMiddleware> =
() => async (request, context, runtime) => {
const env = getEnv(runtime);
() => async (request, context) => {
try {
return {
...context,
session: await getSession(
request,
authjsConfig(env as Record<string, string>)
),
session: await getSession(request, authjsConfig),
};
} catch (error) {
console.debug("authjsSessionMiddleware:", error);
@ -197,7 +194,6 @@ export const authjsSessionMiddleware: Get<[], UniversalMiddleware> =
* Auth.js route
* @link {@see https://authjs.dev/getting-started/installation}
**/
export const authjsHandler = (() => async (request, context, runtime) => {
const env = getEnv(runtime);
return Auth(request, authjsConfig(env as Record<string, string>));
export const authjsHandler = (() => async (request) => {
return Auth(request, authjsConfig);
}) satisfies Get<[], UniversalHandler>;

@ -1,3 +1,14 @@
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 = "z-ai/glm-4.5-air";
// export const MODEL_NAME = "openai/gpt-5-mini";

@ -7,7 +7,7 @@ import {
} from "@universal-middleware/core";
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { getDbClient } from "../database/postgres";
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { getOpenrouter } from "./provider.js";
import { env as processEnv } from "./env.js";
import { getToken } from "@auth/core/jwt";
@ -22,22 +22,10 @@ export const trpcHandler = ((endpoint) => (request, context, runtime) => {
(env.POSTGRES_CONNECTION_STRING ||
processEnv.POSTGRES_CONNECTION_STRING) as string
);
const openrouter = createOpenRouter({
apiKey: (env.OPENROUTER_API_KEY ||
processEnv.OPENROUTER_API_KEY) as string,
});
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",
});
const openrouter = getOpenrouter(
(env.OPENROUTER_API_KEY || processEnv.OPENROUTER_API_KEY) as string
);
const jwt = await getToken({ req: request, secret: "buginoo" });
return {
...context,
...runtime,

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

@ -1,6 +1,8 @@
import type { TSchema } from "@sinclair/typebox";
import { TypeCompiler } from "@sinclair/typebox/compiler";
import { initTRPC, TRPCError } from "@trpc/server";
import type { getDbClient } from "../../database/postgres";
import type { createOpenRouter } from "@openrouter/ai-sdk-provider";
import type { getOpenrouter } from "@server/provider.js";
import type { JWT } from "@auth/core/jwt";
/**
@ -11,7 +13,7 @@ const t = initTRPC
.context<
object & {
dbClient: ReturnType<typeof getDbClient>;
openrouter: ReturnType<typeof createOpenRouter>;
openrouter: ReturnType<typeof getOpenrouter>;
jwt?: JWT | null;
}
>()
@ -51,4 +53,22 @@ 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;

@ -5,15 +5,4 @@ compatibility_flags = [ "nodejs_compat" ]
[vars]
MILVUS_ADDRESS = "in03-639fdba4bcde098.serverless.gcp-us-west1.cloud.zilliz.com"
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"
MILVUS_USERNAME = "db_639fdba4bcde098"
Loading…
Cancel
Save