Files
AiTool/frontend/components/historical-references.tsx
丹尼尔 ad96272ab6 fix:bug
2026-03-12 19:35:06 +08:00

112 lines
3.4 KiB
TypeScript

"use client";
import { useState, useEffect, useCallback } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { listProjects, type ProjectRead } from "@/lib/api/client";
import { Copy, Search, FileText } from "lucide-react";
import { toast } from "sonner";
export function HistoricalReferences() {
const [projects, setProjects] = useState<ProjectRead[]>([]);
const [search, setSearch] = useState("");
const [loading, setLoading] = useState(true);
const load = useCallback(async () => {
setLoading(true);
try {
const data = await listProjects();
setProjects(data);
} catch (e) {
toast.error("加载历史项目失败");
setProjects([]);
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
load();
}, [load]);
const filtered = search.trim()
? projects.filter(
(p) =>
p.raw_requirement.toLowerCase().includes(search.toLowerCase()) ||
(p.ai_solution_md || "").toLowerCase().includes(search.toLowerCase())
)
: projects.slice(0, 10);
const copySnippet = (text: string, label: string) => {
if (!text) return;
navigator.clipboard.writeText(text);
toast.success(`已复制 ${label}`);
};
return (
<div className="border-t p-2">
<p className="text-xs font-medium text-muted-foreground px-2 mb-2 flex items-center gap-1">
<FileText className="h-3.5 w-3.5" />
</p>
<div className="relative mb-2">
<Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="搜索项目..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="h-8 pl-7 text-xs"
/>
</div>
<div className="max-h-48 overflow-y-auto space-y-1">
{loading ? (
<p className="text-xs text-muted-foreground px-2">...</p>
) : filtered.length === 0 ? (
<p className="text-xs text-muted-foreground px-2"></p>
) : (
filtered.map((p) => (
<div
key={p.id}
className={cn(
"rounded border bg-background/50 p-2 text-xs space-y-1"
)}
>
<p className="font-medium text-foreground line-clamp-1">
#{p.id}
</p>
<p className="text-muted-foreground line-clamp-2">
{p.raw_requirement.slice(0, 80)}
</p>
<div className="flex gap-1 pt-1">
<Button
variant="ghost"
size="sm"
className="h-6 px-1.5 text-xs"
onClick={() =>
copySnippet(p.raw_requirement, "原始需求")
}
>
<Copy className="h-3 w-3 mr-0.5" />
</Button>
<Button
variant="ghost"
size="sm"
className="h-6 px-1.5 text-xs"
onClick={() =>
copySnippet(p.ai_solution_md || "", "方案")
}
>
<Copy className="h-3 w-3 mr-0.5" />
</Button>
</div>
</div>
))
)}
</div>
</div>
);
}