* trpc same data shape as `generateText`

* read `.env` file
* use zustand for state management
* fix peer dependency warnings by installing zod
* install milvus client
master
Avraham Sakal 4 months ago
parent e0c5329ff6
commit 4f356153bb

@ -1,3 +1,4 @@
import "dotenv/config.js";
import {
authjsHandler,
authjsSessionMiddleware,

@ -10,26 +10,30 @@
"deploy": "run-s build deploy:wrangler"
},
"dependencies": {
"@ai-sdk/openai": "2.0.0-beta.2",
"@ai-sdk/react": "2.0.0-beta.3",
"@ai-sdk/react": "^1.2.12",
"@auth/core": "^0.40.0",
"@compiled/react": "^0.18.6",
"@hono/node-server": "^1.14.4",
"@mantine/core": "^8.1.1",
"@mantine/hooks": "^8.1.1",
"@openrouter/ai-sdk-provider": "^0.7.2",
"@sinclair/typebox": "^0.34.37",
"@trpc/client": "^11.4.2",
"@trpc/server": "^11.4.2",
"@universal-middleware/core": "^0.4.8",
"@universal-middleware/hono": "^0.4.14",
"@vitejs/plugin-react": "^4.6.0",
"ai": "5.0.0-beta.3",
"@zilliz/milvus2-sdk-node": "^2.5.11",
"ai": "^4.3.16",
"dotenv": "^17.0.0",
"hono": "^4.8.2",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"vike": "^0.4.235",
"vike-cloudflare": "^0.1.7",
"vike-react": "^0.6.4"
"vike-react": "^0.6.4",
"zod": "^3.25.67",
"zustand": "^5.0.6"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",

@ -1,34 +1,76 @@
import { JsonInput, Tabs, Textarea } from "@mantine/core";
import { useState } from "react";
import { trpc } from "../../trpc/client";
import { create } from "zustand";
import type { Message as UIMessage } from "ai";
import type { OtherParameters, Store } from "./types.js";
const defaultSystemPrompt = `You are a helpful assistant that answers questions based on the provided context. If you don't know the answer, just say that you don't know, don't try to make up an answer.`;
const defaultParameters = {
temperature: 0.5,
max_tokens: 100,
};
} as OtherParameters;
const useStore = create<Store>()((set) => ({
messages: [],
message: "",
systemPrompt: defaultSystemPrompt,
parameters: defaultParameters,
loading: false,
setMessages: (messages) => set({ messages }),
setMessage: (message) => set({ message }),
setSystemPrompt: (systemPrompt) => set({ systemPrompt }),
setParameters: (parameters) => set({ parameters }),
setLoading: (loading) => set({ loading }),
}));
export default function ChatPage() {
const [prompt, setPrompt] = useState("");
const [systemPrompt, setSystemPrompt] = useState(defaultSystemPrompt);
const [parameters, setParameters] = useState(defaultParameters);
const messages = useStore((state) => state.messages);
const message = useStore((state) => state.message);
const systemPrompt = useStore((state) => state.systemPrompt);
const parameters = useStore((state) => state.parameters);
const loading = useStore((state) => state.loading);
const setMessages = useStore((state) => state.setMessages);
const setMessage = useStore((state) => state.setMessage);
const setSystemPrompt = useStore((state) => state.setSystemPrompt);
const setParameters = useStore((state) => state.setParameters);
const setLoading = useStore((state) => state.setLoading);
return (
<Tabs defaultValue="prompt">
<Tabs defaultValue="message">
<Tabs.List>
<Tabs.Tab value="prompt">Prompt</Tabs.Tab>
<Tabs.Tab value="message">Message</Tabs.Tab>
<Tabs.Tab value="system-prompt">System Prompt</Tabs.Tab>
<Tabs.Tab value="parameters">Parameters</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="prompt">
<Tabs.Panel value="message">
<Messages messages={messages} />
<Textarea
resize="vertical"
placeholder="Type your message here..."
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onKeyDown={(e) => {
value={message}
disabled={loading}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={async (e) => {
if (e.key === "Enter") {
e.preventDefault();
console.log("Sending message...");
const messagesWithNewUserMessage = [
...messages,
{ role: "user" as const, content: message } as UIMessage,
];
setMessages(messagesWithNewUserMessage);
setLoading(true);
const response = await trpc.chat.sendMessage.query({
messages: messagesWithNewUserMessage,
systemPrompt,
parameters,
});
const messagesWithAssistantMessage = [
...messagesWithNewUserMessage,
{ role: "assistant", content: response.text } as UIMessage,
];
setMessages(messagesWithAssistantMessage);
setMessage("");
setLoading(false);
}
}}
/>
@ -53,3 +95,18 @@ export default function ChatPage() {
</Tabs>
);
}
function Messages({
messages,
}: {
messages: Array<UIMessage>;
}) {
return (
<div>
{messages.map((message, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
<div key={index}>{message.content}</div>
))}
</div>
);
}

@ -1,22 +1,40 @@
import { router, publicProcedure, Validator } from "../../trpc/server";
import { Type as T } from "@sinclair/typebox";
import {
router,
publicProcedure,
// Validator
} from "../../trpc/server";
// import { Type as T } from "@sinclair/typebox";
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
import { generateText } from "ai";
import type { Message as UIMessage } from "ai";
import type { OtherParameters } from "./types.js";
import { env } from "../../server/env.js";
const openrouter = createOpenRouter({
apiKey: env.OPENROUTER_API_KEY,
});
export const chat = router({
sendMessage: publicProcedure
.input(
Validator(
T.Object({
prompt: T.String(),
systemPrompt: T.String(),
parameters: T.Object({
temperature: T.Number(),
max_tokens: T.Number(),
}),
}),
),
(x) =>
x as {
messages: Array<UIMessage>;
systemPrompt: string;
parameters: OtherParameters;
},
)
.mutation(async ({ input: { prompt, systemPrompt, parameters } }) => {
console.log("Received new todo", { prompt, systemPrompt, parameters });
return { prompt, systemPrompt, parameters };
.query(async ({ input: { messages, systemPrompt, parameters } }) => {
const response = await generateText({
model: openrouter("mistralai/mistral-nemo"),
messages: [
{ role: "system" as const, content: systemPrompt },
...messages,
],
maxSteps: 3,
tools: undefined,
...parameters,
});
return response;
}),
});

@ -0,0 +1,20 @@
import type { Message as UIMessage } from "ai";
import type { generateText } from "ai";
export type OtherParameters = Omit<
Parameters<typeof generateText>[0],
"model" | "messages" | "abortSignal"
>;
export type Store = {
messages: Array<UIMessage>;
message: string;
systemPrompt: string;
parameters: OtherParameters;
loading: boolean;
setMessages: (messages: Array<UIMessage>) => void;
setMessage: (message: string) => void;
setSystemPrompt: (systemPrompt: string) => void;
setParameters: (parameters: OtherParameters) => void;
setLoading: (loading: boolean) => void;
};

File diff suppressed because it is too large Load Diff

@ -1,7 +1,9 @@
ignoredBuiltDependencies:
- protobufjs
- sharp
- workerd
onlyBuiltDependencies:
- '@biomejs/biome'
- esbuild
- protobufjs

@ -0,0 +1,10 @@
export const env: Record<string, string | undefined> =
typeof process?.env !== "undefined"
? process.env
: import.meta && "env" in import.meta
? (
import.meta as ImportMeta & {
env: Record<string, string | undefined>;
}
).env
: {};

@ -16,5 +16,6 @@ export const trpcHandler = ((endpoint) => (request, context, runtime) => {
resHeaders,
};
},
allowMethodOverride: true,
});
}) satisfies Get<[endpoint: string], UniversalHandler>;

@ -5,6 +5,7 @@ export const trpc = createTRPCProxyClient<AppRouter>({
links: [
httpBatchLink({
url: "/api/trpc",
methodOverride: "POST",
}),
],
});

Loading…
Cancel
Save