* trpc same data shape as generateText

* read `.env` file
* use zustand for state management
* fix peer dependency warnings by installing zod
* install milvus client
This commit is contained in:
Avraham Sakal
2025-06-30 08:44:49 -04:00
parent e0c5329ff6
commit 4f356153bb
10 changed files with 711 additions and 102 deletions
+69 -12
View File
@@ -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>
);
}
+33 -15
View File
@@ -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;
}),
});
+20
View File
@@ -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;
};