editable facts in the UI

master
Avraham Sakal 2 months ago
parent 46595649df
commit df1e263ef7

@ -11,7 +11,7 @@ import {
useMantineTheme,
} from "@mantine/core";
import { trpc } from "../../../trpc/client";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import {
defaultParameters,
defaultSystemPrompt,
@ -22,7 +22,7 @@ import { useData } from "vike-react/useData";
import type { Data } from "./+data";
import type { CommittedMessage, DraftMessage } from "../../../types";
import Markdown from "react-markdown";
import { IconTrash } from "@tabler/icons-react";
import { IconTrash, IconEdit, IconCheck, IconX } from "@tabler/icons-react";
export default function ChatPage() {
const pageContext = usePageContext();
@ -49,6 +49,27 @@ export default function ChatPage() {
const removeFactTrigger = useStore((state) => state.removeFactTrigger);
const setLoading = useStore((state) => state.setLoading);
// State for editing facts
const [editingFactId, setEditingFactId] = useState<string | null>(null);
const [editingFactContent, setEditingFactContent] = useState("");
// Handle clicking outside to cancel editing
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (editingFactId && event.target instanceof Element) {
const editingElement = event.target.closest('.editing-fact');
if (!editingElement) {
setEditingFactId(null);
}
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [editingFactId]);
const {
conversation,
messages: initialMessages,
@ -89,6 +110,16 @@ export default function ChatPage() {
await trpc.chat.facts.deleteOne.mutate({ factId });
}
async function handleUpdateFact(factId: string, content: string) {
// Update the local state first
setFacts(facts.map(fact =>
fact.id === factId ? { ...fact, content } : fact
));
// Then update the database
await trpc.chat.facts.update.mutate({ factId, content });
}
async function handleDeleteFactTrigger(factTriggerId: string) {
removeFactTrigger(factTriggerId);
await trpc.chat.factTriggers.deleteOne.mutate({ factTriggerId });
@ -193,7 +224,48 @@ export default function ChatPage() {
<List>
{facts.map((fact) => (
<List.Item key={fact.id}>
{fact.content}{" "}
{editingFactId === fact.id ? (
<Group wrap="nowrap" className="editing-fact">
<Textarea
value={editingFactContent}
onChange={(e) => setEditingFactContent(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
handleUpdateFact(fact.id, editingFactContent);
setEditingFactId(null);
} else if (e.key === "Escape") {
setEditingFactId(null);
}
}}
autoFocus
style={{ flex: 1 }}
/>
<ActionIcon
onClick={() => {
handleUpdateFact(fact.id, editingFactContent);
setEditingFactId(null);
}}
>
<IconCheck size={16} />
</ActionIcon>
<ActionIcon
onClick={() => setEditingFactId(null)}
>
<IconX size={16} />
</ActionIcon>
</Group>
) : (
<Group wrap="nowrap">
<span style={{ flex: 1 }}>{fact.content}</span>
<ActionIcon
onClick={() => {
setEditingFactId(fact.id);
setEditingFactContent(fact.content);
}}
>
<IconEdit size={16} />
</ActionIcon>
<IconTrash
size={16}
stroke={1.5}
@ -203,6 +275,8 @@ export default function ChatPage() {
handleDeleteFact(fact.id);
}}
/>
</Group>
)}
</List.Item>
))}
</List>

@ -75,6 +75,23 @@ export const facts = router({
db.write();
return { ok: true };
}),
update: publicProcedure
.input(
(x) =>
x as {
factId: string;
content: string;
},
)
.mutation(async ({ input: { factId, content } }) => {
const factIndex = db.data.facts.findIndex(
(fact) => fact.id === factId,
);
if (factIndex === -1) throw new Error("Fact not found");
db.data.facts[factIndex].content = content;
await db.write();
return { ok: true };
}),
extractFromNewMessages: publicProcedure
.input(
(x) =>

Loading…
Cancel
Save