This commit is contained in:
吉鑫升 committed 2026-06-08 18:02:14 +08:00
commit fb692a0555
30 files changed
+3618 -6

No files matched your search

@@ -0,0 +1,158 @@
"use client";
import { BookMarked } from "lucide-react";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useKnowledgeActions } from "@/core/knowledge/hooks";
import { useThread } from "./messages/context";
import { Tooltip } from "./tooltip";
export function KnowledgeSaveTrigger({ threadId }: { threadId: string }) {
const { thread } = useThread();
const navigate = useNavigate();
const { ingestThread } = useKnowledgeActions();
const [open, setOpen] = useState(false);
const [title, setTitle] = useState("");
const [tags, setTags] = useState("");
const [status, setStatus] = useState<"approved" | "draft">("approved");
const [mode, setMode] = useState<"summary" | "full">("summary");
const [includeSources, setIncludeSources] = useState(true);
if (thread.messages.length === 0) {
return null;
}
const handleSave = async () => {
try {
const result = await ingestThread.mutateAsync({
threadId,
payload: {
title: title.trim() || null,
tags: tags
.split(/[,,\s]+/)
.map((t) => t.trim())
.filter(Boolean),
status,
mode,
include_sources: includeSources,
},
});
toast.success("已保存到知识库", {
action: {
label: "查看",
onClick: () =>
navigate(`/page/workspace/knowledge/${result.note.id}`),
},
});
setOpen(false);
setTitle("");
setTags("");
} catch (error) {
toast.error(error instanceof Error ? error.message : "保存失败");
}
};
return (
<>
<Tooltip content="保存到知识库">
<Button
className="text-muted-foreground hover:text-foreground"
variant="ghost"
onClick={() => setOpen(true)}
>
<BookMarked />
保存
</Button>
</Tooltip>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>保存到知识库</DialogTitle>
<DialogDescription>
将本次对话沉淀为 obsidian-wiki 兼容的知识笔记。
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-3">
<Input
placeholder="标题(留空自动生成)"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Input
placeholder="标签,逗号分隔"
value={tags}
onChange={(e) => setTags(e.target.value)}
/>
<div className="flex items-center gap-2">
<Select
value={status}
onValueChange={(v) => setStatus(v as "approved" | "draft")}
>
<SelectTrigger className="flex-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="approved">正式</SelectItem>
<SelectItem value="draft">草稿</SelectItem>
</SelectContent>
</Select>
<Select
value={mode}
onValueChange={(v) => setMode(v as "summary" | "full")}
>
<SelectTrigger className="flex-1">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="summary">摘要</SelectItem>
<SelectItem value="full">完整</SelectItem>
</SelectContent>
</Select>
</div>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={includeSources}
onChange={(e) => setIncludeSources(e.target.checked)}
className="size-4"
/>
包含搜索 / 工具来源
</label>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
取消
</Button>
<Button
onClick={() => void handleSave()}
disabled={ingestThread.isPending}
>
{ingestThread.isPending ? "保存中..." : "保存"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}