fix assistant exports and tool calls
This commit is contained in:
1 parent
f7eed7ce8a
commit
305c9af2da
15 files changed
+756
-29
No files matched your search
@@ -114,7 +114,7 @@ import remarkDirective from "remark-directive";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { clientApi, fetchApi } from "@homarr/api/client";
|
||||
import { assistantProviderIds, assistantProviderPresets, assistantReasoningModes } from "@homarr/definitions";
|
||||
import type { AssistantProvider } from "@homarr/definitions";
|
||||
import { showErrorNotification, showSuccessNotification } from "@homarr/notifications";
|
||||
@@ -123,6 +123,11 @@ import { useScopedI18n } from "@homarr/translation/client";
|
||||
import classes from "./assistant-panel.module.css";
|
||||
import { getAssistantActivityState } from "./assistant-activity-state";
|
||||
import { useAssistantAutoApproval, useAssistantAutomaticAction } from "./assistant-auto-approval";
|
||||
import {
|
||||
buildAssistantConversationMarkdown,
|
||||
buildAssistantMessageMarkdown,
|
||||
getAssistantConversationExportFilename,
|
||||
} from "./assistant-conversation-export";
|
||||
import { AssistantDotMatrix } from "./assistant-dot-matrix";
|
||||
import { AssistantImage } from "./assistant-image";
|
||||
import { getAssistantDirectiveTranslationKey, parseAssistantDirectives } from "./assistant-directives";
|
||||
@@ -1068,6 +1073,46 @@ const BranchPicker = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const downloadAssistantMarkdown = (markdown: string, filename: string) => {
|
||||
const blobUrl = URL.createObjectURL(new Blob([markdown], { type: "text/markdown;charset=utf-8" }));
|
||||
const link = document.createElement("a");
|
||||
link.href = blobUrl;
|
||||
link.download = filename;
|
||||
link.hidden = true;
|
||||
document.body.append(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(blobUrl), 0);
|
||||
};
|
||||
|
||||
const AssistantMessageExportAction = () => {
|
||||
const t = useScopedI18n("common.assistant");
|
||||
const message = useAuiState((state) => state.message);
|
||||
|
||||
return (
|
||||
<Tooltip label={t("exportMarkdown")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
aria-label={t("exportMarkdown")}
|
||||
onClick={() => {
|
||||
const markdown = buildAssistantMessageMarkdown({
|
||||
id: message.id,
|
||||
parentId: message.parentId,
|
||||
format: "assistant-ui/runtime",
|
||||
content: message,
|
||||
createdAt: message.createdAt,
|
||||
});
|
||||
downloadAssistantMarkdown(markdown, `assistant-message-${message.id.slice(0, 8)}.md`);
|
||||
}}
|
||||
>
|
||||
<IconFileExport size={14} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const AssistantMessageActions = () => {
|
||||
const t = useScopedI18n("common.assistant");
|
||||
return (
|
||||
@@ -1096,13 +1141,7 @@ const AssistantMessageActions = () => {
|
||||
</ActionIcon>
|
||||
</ActionBarPrimitive.FeedbackNegative>
|
||||
</Tooltip>
|
||||
<Tooltip label={t("exportMarkdown")}>
|
||||
<ActionBarPrimitive.ExportMarkdown asChild>
|
||||
<ActionIcon variant="subtle" color="gray" size="sm" aria-label={t("exportMarkdown")}>
|
||||
<IconFileExport size={14} />
|
||||
</ActionIcon>
|
||||
</ActionBarPrimitive.ExportMarkdown>
|
||||
</Tooltip>
|
||||
<AssistantMessageExportAction />
|
||||
<Tooltip label={t("regenerate")}>
|
||||
<ActionBarPrimitive.Reload asChild>
|
||||
<ActionIcon variant="subtle" color="gray" size="sm" aria-label={t("regenerate")}>
|
||||
@@ -1892,6 +1931,30 @@ const HistorySelectContext = createContext<() => void>(() => undefined);
|
||||
const ThreadListItem = () => {
|
||||
const t = useScopedI18n("common.assistant");
|
||||
const onSelect = useContext(HistorySelectContext);
|
||||
const remoteId = useAuiState((state) => state.threadListItem.remoteId);
|
||||
const title = useAuiState((state) => state.threadListItem.title);
|
||||
const [exporting, setExporting] = useState(false);
|
||||
|
||||
const exportConversation = async () => {
|
||||
if (!remoteId || exporting) return;
|
||||
setExporting(true);
|
||||
try {
|
||||
const conversation = await fetchApi.assistant.getThread.query({ threadId: remoteId });
|
||||
const markdown = buildAssistantConversationMarkdown(conversation);
|
||||
downloadAssistantMarkdown(
|
||||
markdown,
|
||||
getAssistantConversationExportFilename(conversation.thread.title ?? title, remoteId),
|
||||
);
|
||||
} catch {
|
||||
showErrorNotification({
|
||||
title: t("exportConversation.failedTitle"),
|
||||
message: t("exportConversation.failedDescription"),
|
||||
});
|
||||
} finally {
|
||||
setExporting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThreadListItemPrimitive.Root className={classes.historyItem}>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
@@ -1908,6 +1971,20 @@ const ThreadListItem = () => {
|
||||
<ThreadListItemPrimitive.Title fallback={t("newConversation")} />
|
||||
</Button>
|
||||
</ThreadListItemPrimitive.Trigger>
|
||||
<Tooltip label={t("exportConversation.action")}>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
loading={exporting}
|
||||
loaderProps={{ type: "bars" }}
|
||||
disabled={!remoteId}
|
||||
aria-label={t("exportConversation.action")}
|
||||
onClick={() => void exportConversation()}
|
||||
>
|
||||
<IconFileExport size={14} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<ThreadListItemPrimitive.Delete asChild>
|
||||
<ActionIcon variant="subtle" color="red" size="sm" aria-label={t("delete")}>
|
||||
<IconTrash size={14} />
|
||||
|
||||
Reference in new issue
Block a user