refactor: remove assistant chat archiving
This commit is contained in:
1 parent
26a9752206
commit
d37a2ba963
12 files changed
+8
-79
No files matched your search
@@ -64,7 +64,7 @@ uses. Choose OpenRouter or another provider when you prefer a provider-neutral p
|
||||
## Conversations and tools
|
||||
|
||||
Conversations and messages are stored per user in the Homarr database. The assistant stays out of the
|
||||
page layout until it is opened. Users can start, revisit, archive, and delete conversations from the
|
||||
page layout until it is opened. Users can start, revisit, and delete conversations from the
|
||||
conversation menu in the floating panel.
|
||||
|
||||
The assistant can use the same internal tool definitions as the [Homarr MCP server](./mcp).
|
||||
|
||||
@@ -142,11 +142,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
const thread = await db.query.assistantThreads.findFirst({
|
||||
where: and(
|
||||
eq(assistantThreads.id, parsed.data.id),
|
||||
eq(assistantThreads.userId, session.user.id),
|
||||
eq(assistantThreads.status, "regular"),
|
||||
),
|
||||
where: and(eq(assistantThreads.id, parsed.data.id), eq(assistantThreads.userId, session.user.id)),
|
||||
});
|
||||
if (!thread) {
|
||||
return Response.json({ error: "Conversation not found." }, { status: 404 });
|
||||
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
IconActivityHeartbeat,
|
||||
IconAlertTriangle,
|
||||
IconApps,
|
||||
IconArchive,
|
||||
IconArrowUp,
|
||||
IconCheck,
|
||||
IconChevronUp,
|
||||
@@ -44,7 +43,6 @@ import {
|
||||
IconPlayerStop,
|
||||
IconPlus,
|
||||
IconRefresh,
|
||||
IconRestore,
|
||||
IconRobot,
|
||||
IconSearch,
|
||||
IconTrash,
|
||||
@@ -237,45 +235,6 @@ const ThreadListItem = () => {
|
||||
<ThreadListItemPrimitive.Title fallback={t("newConversation")} />
|
||||
</Button>
|
||||
</ThreadListItemPrimitive.Trigger>
|
||||
<ThreadListItemPrimitive.Archive asChild>
|
||||
<ActionIcon variant="subtle" color="gray" size="sm" aria-label={t("archive")}>
|
||||
<IconArchive size={14} />
|
||||
</ActionIcon>
|
||||
</ThreadListItemPrimitive.Archive>
|
||||
<ThreadListItemPrimitive.Delete asChild>
|
||||
<ActionIcon variant="subtle" color="red" size="sm" aria-label={t("delete")}>
|
||||
<IconTrash size={14} />
|
||||
</ActionIcon>
|
||||
</ThreadListItemPrimitive.Delete>
|
||||
</Group>
|
||||
</ThreadListItemPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
||||
const ArchivedThreadListItem = () => {
|
||||
const t = useScopedI18n("common.assistant");
|
||||
const onSelect = useContext(HistorySelectContext);
|
||||
return (
|
||||
<ThreadListItemPrimitive.Root className={classes.historyItem}>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
<ThreadListItemPrimitive.Trigger asChild>
|
||||
<Button
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="compact-sm"
|
||||
justify="flex-start"
|
||||
flex={1}
|
||||
style={{ minWidth: 0 }}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<ThreadListItemPrimitive.Title fallback={t("archivedConversation")} />
|
||||
</Button>
|
||||
</ThreadListItemPrimitive.Trigger>
|
||||
<ThreadListItemPrimitive.Unarchive asChild>
|
||||
<ActionIcon variant="subtle" color="gray" size="sm" aria-label={t("restore")}>
|
||||
<IconRestore size={14} />
|
||||
</ActionIcon>
|
||||
</ThreadListItemPrimitive.Unarchive>
|
||||
<ThreadListItemPrimitive.Delete asChild>
|
||||
<ActionIcon variant="subtle" color="red" size="sm" aria-label={t("delete")}>
|
||||
<IconTrash size={14} />
|
||||
@@ -307,10 +266,6 @@ const ThreadHistory = ({ onSelect }: { onSelect: () => void }) => {
|
||||
<ScrollArea h="min(24rem, 55dvh)" type="auto" offsetScrollbars>
|
||||
<Stack gap={3}>
|
||||
<ThreadListPrimitive.Items components={{ ThreadListItem }} />
|
||||
<Text size="sm" fw={600} c="dimmed" px="xs" mt="sm">
|
||||
{t("archived")}
|
||||
</Text>
|
||||
<ThreadListPrimitive.Items archived components={{ ThreadListItem: ArchivedThreadListItem }} />
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
</Stack>
|
||||
|
||||
@@ -52,6 +52,7 @@ interface AssistantContextValue {
|
||||
}
|
||||
|
||||
const AssistantContext = createContext<AssistantContextValue | null>(null);
|
||||
const ignoreUnsupportedArchiveAction = () => Promise.resolve();
|
||||
|
||||
export const useHomarrAssistant = () => {
|
||||
const value = useContext(AssistantContext);
|
||||
@@ -69,7 +70,7 @@ const threadAdapter: RemoteThreadListAdapter = {
|
||||
return {
|
||||
threads: threads.map((thread) => ({
|
||||
remoteId: thread.id,
|
||||
status: thread.status,
|
||||
status: "regular",
|
||||
title: thread.title ?? undefined,
|
||||
lastMessageAt: thread.updatedAt,
|
||||
custom: { modelId: thread.modelId },
|
||||
@@ -84,7 +85,7 @@ const threadAdapter: RemoteThreadListAdapter = {
|
||||
const { thread } = await fetchApi.assistant.getThread.query({ threadId });
|
||||
return {
|
||||
remoteId: thread.id,
|
||||
status: thread.status,
|
||||
status: "regular",
|
||||
title: thread.title ?? undefined,
|
||||
lastMessageAt: thread.updatedAt,
|
||||
custom: { modelId: thread.modelId },
|
||||
@@ -93,12 +94,9 @@ const threadAdapter: RemoteThreadListAdapter = {
|
||||
async rename(threadId, title) {
|
||||
await fetchApi.assistant.renameThread.mutate({ threadId, title });
|
||||
},
|
||||
async archive(threadId) {
|
||||
await fetchApi.assistant.setThreadStatus.mutate({ threadId, status: "archived" });
|
||||
},
|
||||
async unarchive(threadId) {
|
||||
await fetchApi.assistant.setThreadStatus.mutate({ threadId, status: "regular" });
|
||||
},
|
||||
// assistant-ui requires these callbacks even when the product does not expose archiving.
|
||||
archive: ignoreUnsupportedArchiveAction,
|
||||
unarchive: ignoreUnsupportedArchiveAction,
|
||||
async delete(threadId) {
|
||||
await fetchApi.assistant.deleteThread.mutate({ threadId });
|
||||
},
|
||||
|
||||
@@ -407,16 +407,6 @@ export const assistantRouter = createTRPCRouter({
|
||||
.where(eq(assistantThreads.id, input.threadId));
|
||||
}),
|
||||
|
||||
setThreadStatus: protectedProcedure
|
||||
.input(z.object({ threadId: z.string().max(64), status: z.enum(["regular", "archived"]) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await ownedThreadAsync(ctx.db, input.threadId, ctx.session.user.id);
|
||||
await ctx.db
|
||||
.update(assistantThreads)
|
||||
.set({ status: input.status, updatedAt: new Date() })
|
||||
.where(eq(assistantThreads.id, input.threadId));
|
||||
}),
|
||||
|
||||
deleteThread: protectedProcedure
|
||||
.input(z.object({ threadId: z.string().max(64) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
|
||||
@@ -16,7 +16,6 @@ CREATE TABLE `assistant_thread` (
|
||||
`user_id` varchar(64) NOT NULL,
|
||||
`title` varchar(256),
|
||||
`model_id` varchar(256),
|
||||
`status` varchar(16) DEFAULT 'regular' NOT NULL,
|
||||
`created_at` timestamp DEFAULT (now()) NOT NULL,
|
||||
`updated_at` timestamp DEFAULT (now()) NOT NULL,
|
||||
CONSTRAINT `assistant_thread_id` PRIMARY KEY(`id`)
|
||||
|
||||
@@ -24,7 +24,6 @@ CREATE TABLE "assistant_thread" (
|
||||
"user_id" varchar(64) NOT NULL,
|
||||
"title" varchar(256),
|
||||
"model_id" varchar(256),
|
||||
"status" varchar(16) DEFAULT 'regular' NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
@@ -25,7 +25,6 @@ CREATE TABLE `assistant_thread` (
|
||||
`user_id` text NOT NULL,
|
||||
`title` text,
|
||||
`model_id` text,
|
||||
`status` text DEFAULT 'regular' NOT NULL,
|
||||
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
|
||||
@@ -518,7 +518,6 @@ export const assistantThreads = mysqlTable(
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
title: varchar({ length: 256 }),
|
||||
modelId: varchar({ length: 256 }),
|
||||
status: varchar({ length: 16 }).$type<"regular" | "archived">().notNull().default("regular"),
|
||||
createdAt: timestamp().notNull().defaultNow(),
|
||||
updatedAt: timestamp().notNull().defaultNow(),
|
||||
},
|
||||
|
||||
@@ -517,7 +517,6 @@ export const assistantThreads = pgTable(
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
title: varchar({ length: 256 }),
|
||||
modelId: varchar({ length: 256 }),
|
||||
status: varchar({ length: 16 }).$type<"regular" | "archived">().notNull().default("regular"),
|
||||
createdAt: timestamp().notNull().defaultNow(),
|
||||
updatedAt: timestamp().notNull().defaultNow(),
|
||||
},
|
||||
|
||||
@@ -504,7 +504,6 @@ export const assistantThreads = sqliteTable(
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
title: text(),
|
||||
modelId: text(),
|
||||
status: text().$type<"regular" | "archived">().notNull().default("regular"),
|
||||
createdAt: int({ mode: "timestamp" })
|
||||
.notNull()
|
||||
.default(sql`(unixepoch())`),
|
||||
|
||||
@@ -1999,11 +1999,7 @@
|
||||
"signIn": "Sign in to use Homarr Assistant."
|
||||
},
|
||||
"newConversation": "New conversation",
|
||||
"archivedConversation": "Archived conversation",
|
||||
"conversations": "Conversations",
|
||||
"archived": "Archived",
|
||||
"archive": "Archive",
|
||||
"restore": "Restore",
|
||||
"delete": "Delete",
|
||||
"closeHistory": "Close conversation history",
|
||||
"openHistory": "Open conversation history",
|
||||
|
||||
Reference in new issue
Block a user