improve ui consistency with immer; implement addConversation
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import "@mantine/core/styles.css";
|
import "@mantine/core/styles.css";
|
||||||
|
import { navigate } from "vike/client/router";
|
||||||
import {
|
import {
|
||||||
AppShell,
|
AppShell,
|
||||||
Burger,
|
Burger,
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
IconCircle,
|
IconCircle,
|
||||||
IconCircleFilled,
|
IconCircleFilled,
|
||||||
IconTrashFilled,
|
IconTrashFilled,
|
||||||
|
IconPlus,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { useDisclosure } from "@mantine/hooks";
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
import theme from "./theme.js";
|
import theme from "./theme.js";
|
||||||
@@ -36,7 +38,7 @@ export default function LayoutDefault({
|
|||||||
const setConversations = useStore((state) => state.setConversations);
|
const setConversations = useStore((state) => state.setConversations);
|
||||||
const addConversation = useStore((state) => state.addConversation);
|
const addConversation = useStore((state) => state.addConversation);
|
||||||
const removeConversation = useStore((state) => state.removeConversation);
|
const removeConversation = useStore((state) => state.removeConversation);
|
||||||
const conversationId = useStore((state) => state.conversationId);
|
const conversationId = useStore((state) => state.selectedConversationId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
trpc.chat.listConversations.query().then((res) => {
|
trpc.chat.listConversations.query().then((res) => {
|
||||||
@@ -96,11 +98,25 @@ export default function LayoutDefault({
|
|||||||
label="Chats"
|
label="Chats"
|
||||||
leftSection={<IconActivity size={16} stroke={1.5} />}
|
leftSection={<IconActivity size={16} stroke={1.5} />}
|
||||||
rightSection={
|
rightSection={
|
||||||
<IconChevronRight
|
<>
|
||||||
size={12}
|
<IconPlus
|
||||||
stroke={1.5}
|
size={16}
|
||||||
className="mantine-rotate-rtl"
|
stroke={1.5}
|
||||||
/>
|
className="border-on-hover"
|
||||||
|
onClick={() => {
|
||||||
|
trpc.chat.createConversation.mutate().then((res) => {
|
||||||
|
if (!res?.id) return;
|
||||||
|
addConversation(res);
|
||||||
|
navigate(`/chat/${res.id}`);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconChevronRight
|
||||||
|
size={12}
|
||||||
|
stroke={1.5}
|
||||||
|
className="mantine-rotate-rtl"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
active={urlPathname.startsWith("/chat")}
|
active={urlPathname.startsWith("/chat")}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"ai": "^4.3.16",
|
"ai": "^4.3.16",
|
||||||
"dotenv": "^17.0.0",
|
"dotenv": "^17.0.0",
|
||||||
"hono": "^4.8.2",
|
"hono": "^4.8.2",
|
||||||
|
"immer": "^10.1.1",
|
||||||
"kysely": "^0.28.2",
|
"kysely": "^0.28.2",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ import type { ConversationsId } from "../../../database/generated/public/Convers
|
|||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
const pageContext = usePageContext();
|
const pageContext = usePageContext();
|
||||||
const conversationId = Number(pageContext.routeParams.id) as ConversationsId;
|
const conversationId = Number(pageContext.routeParams.id) as ConversationsId;
|
||||||
const conversationTitle = useStore((state) => state.conversationTitle);
|
const conversationTitle = useStore(
|
||||||
|
(state) => state.conversations.find((c) => c.id === conversationId)?.title,
|
||||||
|
);
|
||||||
const messages = useStore((state) => state.messages);
|
const messages = useStore((state) => state.messages);
|
||||||
const message = useStore((state) => state.message);
|
const message = useStore((state) => state.message);
|
||||||
const systemPrompt = useStore((state) => state.systemPrompt);
|
const systemPrompt = useStore((state) => state.systemPrompt);
|
||||||
@@ -53,7 +55,7 @@ export default function ChatPage() {
|
|||||||
<span>Conversation #{conversationId} - </span>
|
<span>Conversation #{conversationId} - </span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={conversationTitle}
|
value={conversationTitle || ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setConversationTitle(e.target.value);
|
setConversationTitle(e.target.value);
|
||||||
}}
|
}}
|
||||||
@@ -88,7 +90,7 @@ export default function ChatPage() {
|
|||||||
];
|
];
|
||||||
setMessages(messagesWithNewUserMessage);
|
setMessages(messagesWithNewUserMessage);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await trpc.chat.sendMessage.query({
|
const response = await trpc.chat.sendMessage.mutate({
|
||||||
messages: messagesWithNewUserMessage,
|
messages: messagesWithNewUserMessage,
|
||||||
systemPrompt,
|
systemPrompt,
|
||||||
parameters,
|
parameters,
|
||||||
|
|||||||
+1
-1
@@ -84,7 +84,7 @@ export const chat = router({
|
|||||||
parameters: OtherParameters;
|
parameters: OtherParameters;
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.query(async ({ input: { messages, systemPrompt, parameters } }) => {
|
.mutation(async ({ input: { messages, systemPrompt, parameters } }) => {
|
||||||
const response = await generateText({
|
const response = await generateText({
|
||||||
model: openrouter("mistralai/mistral-nemo"),
|
model: openrouter("mistralai/mistral-nemo"),
|
||||||
messages: [
|
messages: [
|
||||||
|
|||||||
Generated
+11
-2
@@ -62,6 +62,9 @@ importers:
|
|||||||
hono:
|
hono:
|
||||||
specifier: ^4.8.2
|
specifier: ^4.8.2
|
||||||
version: 4.8.3
|
version: 4.8.3
|
||||||
|
immer:
|
||||||
|
specifier: ^10.1.1
|
||||||
|
version: 10.1.1
|
||||||
kysely:
|
kysely:
|
||||||
specifier: ^0.28.2
|
specifier: ^0.28.2
|
||||||
version: 0.28.2
|
version: 0.28.2
|
||||||
@@ -88,7 +91,7 @@ importers:
|
|||||||
version: 3.25.67
|
version: 3.25.67
|
||||||
zustand:
|
zustand:
|
||||||
specifier: ^5.0.6
|
specifier: ^5.0.6
|
||||||
version: 5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
|
version: 5.0.6(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: 1.9.4
|
specifier: 1.9.4
|
||||||
@@ -1652,6 +1655,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-jYZ6ZtfWjzBdh8H/0CIFfCBHaFL75k+KMzaM177hrWWm2TWL39YMYaJgB74uK/niRc866NMlH9B8uCvIo284WQ==}
|
resolution: {integrity: sha512-jYZ6ZtfWjzBdh8H/0CIFfCBHaFL75k+KMzaM177hrWWm2TWL39YMYaJgB74uK/niRc866NMlH9B8uCvIo284WQ==}
|
||||||
engines: {node: '>=16.9.0'}
|
engines: {node: '>=16.9.0'}
|
||||||
|
|
||||||
|
immer@10.1.1:
|
||||||
|
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
|
||||||
|
|
||||||
inherits@2.0.4:
|
inherits@2.0.4:
|
||||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
|
||||||
@@ -4202,6 +4208,8 @@ snapshots:
|
|||||||
|
|
||||||
hono@4.8.3: {}
|
hono@4.8.3: {}
|
||||||
|
|
||||||
|
immer@10.1.1: {}
|
||||||
|
|
||||||
inherits@2.0.4: {}
|
inherits@2.0.4: {}
|
||||||
|
|
||||||
interpret@2.2.0: {}
|
interpret@2.2.0: {}
|
||||||
@@ -5320,8 +5328,9 @@ snapshots:
|
|||||||
|
|
||||||
zod@3.25.67: {}
|
zod@3.25.67: {}
|
||||||
|
|
||||||
zustand@5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)):
|
zustand@5.0.6(@types/react@19.1.8)(immer@10.1.1)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.8
|
'@types/react': 19.1.8
|
||||||
|
immer: 10.1.1
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
use-sync-external-store: 1.5.0(react@19.1.0)
|
use-sync-external-store: 1.5.0(react@19.1.0)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import type { OtherParameters, Store } from "./types.js";
|
import type { OtherParameters, Store } from "./types.js";
|
||||||
import type { ConversationsId } from "./database/generated/public/Conversations.js";
|
import type { ConversationsId } from "./database/generated/public/Conversations.js";
|
||||||
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
|
||||||
export 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.`;
|
export 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.`;
|
||||||
export const defaultParameters = {
|
export const defaultParameters = {
|
||||||
@@ -8,31 +9,65 @@ export const defaultParameters = {
|
|||||||
max_tokens: 100,
|
max_tokens: 100,
|
||||||
} as OtherParameters;
|
} as OtherParameters;
|
||||||
|
|
||||||
export const useStore = create<Store>()((set) => ({
|
export const useStore = create<Store>()(
|
||||||
conversationId: 0 as ConversationsId,
|
immer((set, get) => ({
|
||||||
conversationTitle: "",
|
selectedConversationId: 0 as ConversationsId,
|
||||||
conversations: [],
|
conversations: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
message: "",
|
message: "",
|
||||||
systemPrompt: defaultSystemPrompt,
|
systemPrompt: defaultSystemPrompt,
|
||||||
parameters: defaultParameters,
|
parameters: defaultParameters,
|
||||||
loading: false,
|
loading: false,
|
||||||
setConversationId: (conversationId) => set({ conversationId }),
|
setConversationId: (conversationId) =>
|
||||||
setConversationTitle: (conversationTitle) => set({ conversationTitle }),
|
set((stateDraft) => {
|
||||||
setConversations: (conversations) => set({ conversations }),
|
stateDraft.selectedConversationId = conversationId;
|
||||||
addConversation: (conversation) =>
|
}),
|
||||||
set((state) => ({
|
setConversationTitle: (conversationTitle) =>
|
||||||
conversations: [...state.conversations, conversation],
|
set((stateDraft) => {
|
||||||
})),
|
const conversation = stateDraft.conversations.find(
|
||||||
removeConversation: (conversationId) =>
|
(c) => c.id === stateDraft.selectedConversationId,
|
||||||
set((state) => ({
|
);
|
||||||
conversations: state.conversations.filter(
|
if (conversation) {
|
||||||
(conversation) => conversation.id !== conversationId,
|
conversation.title = conversationTitle;
|
||||||
),
|
}
|
||||||
})),
|
}),
|
||||||
setMessages: (messages) => set({ messages }),
|
setConversations: (conversations) =>
|
||||||
setMessage: (message) => set({ message }),
|
set((stateDraft) => {
|
||||||
setSystemPrompt: (systemPrompt) => set({ systemPrompt }),
|
stateDraft.conversations = conversations;
|
||||||
setParameters: (parameters) => set({ parameters }),
|
}),
|
||||||
setLoading: (loading) => set({ loading }),
|
addConversation: (conversation) =>
|
||||||
}));
|
set((stateDraft) => {
|
||||||
|
stateDraft.conversations.push(conversation);
|
||||||
|
}),
|
||||||
|
removeConversation: (conversationId) =>
|
||||||
|
set((stateDraft) => {
|
||||||
|
stateDraft.conversations.splice(
|
||||||
|
stateDraft.conversations.findIndex(
|
||||||
|
(conversation) => conversation.id === conversationId,
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
setMessages: (messages) =>
|
||||||
|
set((stateDraft) => {
|
||||||
|
//@ts-ignore
|
||||||
|
stateDraft.messages = messages;
|
||||||
|
}),
|
||||||
|
setMessage: (message) =>
|
||||||
|
set((stateDraft) => {
|
||||||
|
stateDraft.message = message;
|
||||||
|
}),
|
||||||
|
setSystemPrompt: (systemPrompt) =>
|
||||||
|
set((stateDraft) => {
|
||||||
|
stateDraft.systemPrompt = systemPrompt;
|
||||||
|
}),
|
||||||
|
setParameters: (parameters) =>
|
||||||
|
set((stateDraft) => {
|
||||||
|
stateDraft.parameters = parameters;
|
||||||
|
}),
|
||||||
|
setLoading: (loading) =>
|
||||||
|
set((stateDraft) => {
|
||||||
|
stateDraft.loading = loading;
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ export type ConversationUI = Conversations & {};
|
|||||||
export type Store = {
|
export type Store = {
|
||||||
/** This is a string because Milvus sends it as a string, and the value
|
/** This is a string because Milvus sends it as a string, and the value
|
||||||
* overflows the JS integer anyway. */
|
* overflows the JS integer anyway. */
|
||||||
conversationId: ConversationsId;
|
selectedConversationId: ConversationsId;
|
||||||
conversationTitle: string;
|
|
||||||
conversations: Array<ConversationUI>;
|
conversations: Array<ConversationUI>;
|
||||||
messages: Array<UIMessage>;
|
messages: Array<UIMessage>;
|
||||||
message: string;
|
message: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user