* 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:
@@ -1,3 +1,4 @@
|
|||||||
|
import "dotenv/config.js";
|
||||||
import {
|
import {
|
||||||
authjsHandler,
|
authjsHandler,
|
||||||
authjsSessionMiddleware,
|
authjsSessionMiddleware,
|
||||||
|
|||||||
+8
-4
@@ -10,26 +10,30 @@
|
|||||||
"deploy": "run-s build deploy:wrangler"
|
"deploy": "run-s build deploy:wrangler"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "2.0.0-beta.2",
|
"@ai-sdk/react": "^1.2.12",
|
||||||
"@ai-sdk/react": "2.0.0-beta.3",
|
|
||||||
"@auth/core": "^0.40.0",
|
"@auth/core": "^0.40.0",
|
||||||
"@compiled/react": "^0.18.6",
|
"@compiled/react": "^0.18.6",
|
||||||
"@hono/node-server": "^1.14.4",
|
"@hono/node-server": "^1.14.4",
|
||||||
"@mantine/core": "^8.1.1",
|
"@mantine/core": "^8.1.1",
|
||||||
"@mantine/hooks": "^8.1.1",
|
"@mantine/hooks": "^8.1.1",
|
||||||
|
"@openrouter/ai-sdk-provider": "^0.7.2",
|
||||||
"@sinclair/typebox": "^0.34.37",
|
"@sinclair/typebox": "^0.34.37",
|
||||||
"@trpc/client": "^11.4.2",
|
"@trpc/client": "^11.4.2",
|
||||||
"@trpc/server": "^11.4.2",
|
"@trpc/server": "^11.4.2",
|
||||||
"@universal-middleware/core": "^0.4.8",
|
"@universal-middleware/core": "^0.4.8",
|
||||||
"@universal-middleware/hono": "^0.4.14",
|
"@universal-middleware/hono": "^0.4.14",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@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",
|
"hono": "^4.8.2",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"vike": "^0.4.235",
|
"vike": "^0.4.235",
|
||||||
"vike-cloudflare": "^0.1.7",
|
"vike-cloudflare": "^0.1.7",
|
||||||
"vike-react": "^0.6.4"
|
"vike-react": "^0.6.4",
|
||||||
|
"zod": "^3.25.67",
|
||||||
|
"zustand": "^5.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
|
|||||||
+69
-12
@@ -1,34 +1,76 @@
|
|||||||
import { JsonInput, Tabs, Textarea } from "@mantine/core";
|
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 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 = {
|
const defaultParameters = {
|
||||||
temperature: 0.5,
|
temperature: 0.5,
|
||||||
max_tokens: 100,
|
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() {
|
export default function ChatPage() {
|
||||||
const [prompt, setPrompt] = useState("");
|
const messages = useStore((state) => state.messages);
|
||||||
const [systemPrompt, setSystemPrompt] = useState(defaultSystemPrompt);
|
const message = useStore((state) => state.message);
|
||||||
const [parameters, setParameters] = useState(defaultParameters);
|
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 (
|
return (
|
||||||
<Tabs defaultValue="prompt">
|
<Tabs defaultValue="message">
|
||||||
<Tabs.List>
|
<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="system-prompt">System Prompt</Tabs.Tab>
|
||||||
<Tabs.Tab value="parameters">Parameters</Tabs.Tab>
|
<Tabs.Tab value="parameters">Parameters</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
<Tabs.Panel value="prompt">
|
<Tabs.Panel value="message">
|
||||||
|
<Messages messages={messages} />
|
||||||
<Textarea
|
<Textarea
|
||||||
resize="vertical"
|
resize="vertical"
|
||||||
placeholder="Type your message here..."
|
placeholder="Type your message here..."
|
||||||
value={prompt}
|
value={message}
|
||||||
onChange={(e) => setPrompt(e.target.value)}
|
disabled={loading}
|
||||||
onKeyDown={(e) => {
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
|
onKeyDown={async (e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
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>
|
</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
@@ -1,22 +1,40 @@
|
|||||||
import { router, publicProcedure, Validator } from "../../trpc/server";
|
import {
|
||||||
import { Type as T } from "@sinclair/typebox";
|
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({
|
export const chat = router({
|
||||||
sendMessage: publicProcedure
|
sendMessage: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
Validator(
|
(x) =>
|
||||||
T.Object({
|
x as {
|
||||||
prompt: T.String(),
|
messages: Array<UIMessage>;
|
||||||
systemPrompt: T.String(),
|
systemPrompt: string;
|
||||||
parameters: T.Object({
|
parameters: OtherParameters;
|
||||||
temperature: T.Number(),
|
},
|
||||||
max_tokens: T.Number(),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.mutation(async ({ input: { prompt, systemPrompt, parameters } }) => {
|
.query(async ({ input: { messages, systemPrompt, parameters } }) => {
|
||||||
console.log("Received new todo", { prompt, systemPrompt, parameters });
|
const response = await generateText({
|
||||||
return { prompt, systemPrompt, parameters };
|
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;
|
||||||
|
};
|
||||||
Generated
+566
-71
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
|||||||
ignoredBuiltDependencies:
|
ignoredBuiltDependencies:
|
||||||
|
- protobufjs
|
||||||
- sharp
|
- sharp
|
||||||
- workerd
|
- workerd
|
||||||
|
|
||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
- '@biomejs/biome'
|
- '@biomejs/biome'
|
||||||
- esbuild
|
- 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,
|
resHeaders,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
allowMethodOverride: true,
|
||||||
});
|
});
|
||||||
}) satisfies Get<[endpoint: string], UniversalHandler>;
|
}) satisfies Get<[endpoint: string], UniversalHandler>;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const trpc = createTRPCProxyClient<AppRouter>({
|
|||||||
links: [
|
links: [
|
||||||
httpBatchLink({
|
httpBatchLink({
|
||||||
url: "/api/trpc",
|
url: "/api/trpc",
|
||||||
|
methodOverride: "POST",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user