refactor: remove assistant chat archiving

This commit is contained in:
ajnart committed 2026-08-14 11:32:10 +02:00
1 parent 26a9752206
commit d37a2ba963
12 files changed
+8 -79

No files matched your search

+1 -1
View File
@@ -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 });
},
-10
View File
@@ -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
-1
View File
@@ -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(),
},
-1
View File
@@ -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(),
},
-1
View File
@@ -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())`),
-4
View File
@@ -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",