Enhance AssistantPage with search functionality and UI improvements

Added a search feature to filter assistants by name, type, or ID, improving user experience in managing assistants. Updated the layout and styling for better visual appeal, including new components for badges and dropdown menus. Removed deprecated status fields from mock data to streamline the assistant list display.
This commit is contained in:
Xin Wang
2026-06-05 21:52:07 +08:00
parent 1794957bb1
commit 2676d61ccd

View File

@@ -3,23 +3,27 @@
import { import {
Bot, Bot,
Brain, Brain,
Database,
Mic, Mic,
MoreHorizontal, MoreHorizontal,
Pencil, Pencil,
Plus, Plus,
Rocket, Rocket,
Save,
Search, Search,
Sparkles, Sparkles,
Trash2, Trash2,
Volume2,
} from "lucide-react"; } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { import {
Select, Select,
SelectContent, SelectContent,
@@ -53,7 +57,6 @@ type AssistantListItem = {
id: string; id: string;
name: string; name: string;
type: AssistantType; type: AssistantType;
status: "运行中" | "草稿" | "已停用";
updatedAt: string; updatedAt: string;
}; };
@@ -62,28 +65,24 @@ const mockAssistants: AssistantListItem[] = [
id: "asst_001", id: "asst_001",
name: "政务视频咨询助手", name: "政务视频咨询助手",
type: "提示词", type: "提示词",
status: "运行中",
updatedAt: "2026-06-05 18:20", updatedAt: "2026-06-05 18:20",
}, },
{ {
id: "asst_002", id: "asst_002",
name: "热线工单辅助助手", name: "热线工单辅助助手",
type: "工作流", type: "工作流",
status: "草稿",
updatedAt: "2026-06-04 15:12", updatedAt: "2026-06-04 15:12",
}, },
{ {
id: "asst_003", id: "asst_003",
name: "Dify 知识库问答助手", name: "Dify 知识库问答助手",
type: "Dify", type: "Dify",
status: "运行中",
updatedAt: "2026-06-02 09:48", updatedAt: "2026-06-02 09:48",
}, },
{ {
id: "asst_004", id: "asst_004",
name: "FastGPT 售后咨询助手", name: "FastGPT 售后咨询助手",
type: "FastGPT", type: "FastGPT",
status: "已停用",
updatedAt: "2026-05-29 11:06", updatedAt: "2026-05-29 11:06",
}, },
]; ];
@@ -102,7 +101,20 @@ export function AssistantPage() {
enableInterrupt: true, enableInterrupt: true,
}); });
const [view, setView] = useState<"list" | "create">("list"); const [view, setView] = useState<"list" | "create">("list");
const [openMenuId, setOpenMenuId] = useState<string | null>(null); const [searchQuery, setSearchQuery] = useState("");
const filteredAssistants = mockAssistants.filter((assistant) => {
const keyword = searchQuery.trim().toLowerCase();
if (!keyword) {
return true;
}
return [assistant.name, assistant.id, assistant.type, assistant.updatedAt]
.join(" ")
.toLowerCase()
.includes(keyword);
});
function updateForm<K extends keyof AssistantForm>( function updateForm<K extends keyof AssistantForm>(
key: K, key: K,
@@ -115,59 +127,76 @@ export function AssistantPage() {
} }
if (view === "list") { if (view === "list") {
return ( return (
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-6"> <div className="mx-auto flex w-full max-w-[1180px] flex-col gap-8">
<div className="flex items-start justify-between"> <div className="flex flex-col items-start justify-between gap-5 sm:flex-row sm:gap-6">
<div> <div>
<div className="flex items-center gap-3"> <div className="caption-label text-muted-soft"></div>
<span className="h-3 w-3 rounded-full bg-blue-400 shadow-[0_0_0_4px_rgba(46,161,255,.16),0_0_14px_rgba(46,161,255,.35)]" /> <h1 className="font-display display-lg mt-3 text-ink"></h1>
<h1 className="text-3xl font-bold"></h1> <p className="mt-3 max-w-2xl text-[15px] leading-7 text-muted-foreground">
</div>
<p className="mt-2 text-sm text-muted-soft">
Dify FastGPT Dify FastGPT
</p> </p>
</div> </div>
<button <Button
size="lg"
className="w-full shrink-0 gap-2 sm:w-auto"
onClick={() => setView("create")} onClick={() => setView("create")}
className="flex h-10 items-center gap-2 rounded-xl bg-blue-500 px-4 text-sm font-semibold text-white shadow-[0_8px_24px_rgba(29,123,255,.35)]"
> >
<Plus size={16} /> <Plus size={16} />
</button> </Button>
</div> </div>
<section className="rounded-2xl border border-sidebar-border bg-sidebar p-5"> <section className="rounded-2xl border border-hairline bg-card p-6 shadow-sm">
<div className="mb-5 flex items-center justify-between gap-4"> <div className="mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div> <div>
<div className="text-lg font-semibold"></div> <div className="text-[18px] font-medium text-foreground">
<div className="mt-1 text-sm text-muted-soft">
</div>
<div className="mt-1 text-sm text-muted-foreground">
{mockAssistants.length} {mockAssistants.length}
{searchQuery.trim() && `,已筛选 ${filteredAssistants.length}`}
</div> </div>
</div> </div>
<div className="flex h-10 w-[280px] items-center gap-3 rounded-xl border border-hairline-strong bg-surface-strong/60 px-3 text-sm text-muted-soft"> <div className="relative w-full md:w-[320px]">
<Search size={16} /> <Search
<span>...</span> size={15}
className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-muted-soft"
/>
<Input
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
className="h-10 border-hairline-strong bg-background pl-9 text-sm text-foreground placeholder:text-muted-soft"
placeholder="搜索助手名称、类型或 ID..."
/>
</div> </div>
</div> </div>
<div className="overflow-hidden rounded-2xl border border-hairline-strong"> <div className="overflow-hidden rounded-xl border border-hairline">
<div className="grid grid-cols-[1fr_120px_120px_160px_120px] bg-surface-strong/70 px-5 py-3 text-xs font-medium text-muted-soft"> <div className="hidden items-center gap-4 bg-surface-strong/60 px-5 py-3 md:flex">
<div></div> <div className="caption-label flex-1 text-muted-soft">
<div></div>
<div></div> </div>
<div></div> <div className="caption-label w-[110px] text-muted-soft">
<div className="text-right"></div>
</div>
<div className="caption-label w-[150px] text-muted-soft">
</div>
<div className="caption-label w-[116px] text-right text-muted-soft">
</div>
</div> </div>
<div className="divide-y divide-hairline-strong"> <div className="divide-y divide-hairline">
{mockAssistants.map((assistant) => ( {filteredAssistants.map((assistant) => (
<div <div
key={assistant.id} key={assistant.id}
className="grid grid-cols-[1fr_120px_120px_160px_120px] items-center px-5 py-4 text-sm transition-colors hover:bg-sidebar-accent/30" className="flex flex-col gap-3 px-5 py-4 text-sm transition-colors hover:bg-surface-strong/40 md:flex-row md:items-center md:gap-4"
> >
<div> <div className="min-w-0 flex-1">
<div className="font-medium text-foreground"> <div className="truncate font-medium text-foreground">
{assistant.name} {assistant.name}
</div> </div>
<div className="mt-1 text-xs text-muted-soft"> <div className="mt-1 text-xs text-muted-soft">
@@ -175,57 +204,68 @@ export function AssistantPage() {
</div> </div>
</div> </div>
<div> <div className="md:w-[110px]">
<span className="rounded-full border border-hairline-strong bg-surface-strong px-2.5 py-1 text-xs text-muted-foreground"> <Badge
{assistant.type} variant="secondary"
</span> className="h-6 bg-surface-strong px-3 text-muted-foreground"
</div>
<div>
<span
className={[
"rounded-full px-2.5 py-1 text-xs",
assistant.status === "运行中"
? "bg-green-500/10 text-green-400"
: assistant.status === "草稿"
? "bg-yellow-500/10 text-yellow-400"
: "bg-muted/30 text-muted-foreground",
].join(" ")}
> >
{assistant.status} {assistant.type}
</span> </Badge>
</div> </div>
<div className="text-muted-soft">{assistant.updatedAt}</div> <div className="text-muted-foreground md:w-[150px]">
{assistant.updatedAt}
</div>
<div className="relative flex justify-end gap-2"> <div className="flex justify-end gap-2 md:w-[116px]">
<button className="flex h-8 items-center gap-1 rounded-lg border border-hairline-strong px-3 text-xs text-muted-foreground transition-colors hover:bg-sidebar-accent hover:text-foreground"> <Button
variant="outline"
size="sm"
className="gap-1.5 border-hairline-strong text-xs text-muted-foreground hover:text-foreground"
onClick={() => setView("create")}
>
<Pencil size={14} /> <Pencil size={14} />
</button> </Button>
<button <DropdownMenu>
onClick={() => <DropdownMenuTrigger asChild>
setOpenMenuId( <Button
openMenuId === assistant.id ? null : assistant.id, variant="outline"
) size="icon-sm"
} className="border-hairline-strong text-muted-foreground hover:text-foreground"
className="flex h-8 w-8 items-center justify-center rounded-lg border border-hairline-strong text-muted-foreground transition-colors hover:bg-sidebar-accent hover:text-foreground" aria-label={`${assistant.name} 更多操作`}
> >
<MoreHorizontal size={15} /> <MoreHorizontal size={15} />
</button> </Button>
</DropdownMenuTrigger>
{openMenuId === assistant.id && ( <DropdownMenuContent
<div className="absolute right-0 top-9 z-10 w-28 rounded-xl border border-hairline-strong bg-sidebar p-1 shadow-xl"> align="end"
<button className="flex h-9 w-full items-center gap-2 rounded-lg px-3 text-sm text-red-400 transition-colors hover:bg-red-500/10"> className="w-32 min-w-32 rounded-xl border border-hairline bg-popover p-1"
>
<DropdownMenuItem
variant="destructive"
className="rounded-lg"
>
<Trash2 size={14} /> <Trash2 size={14} />
</button> </DropdownMenuItem>
</div> </DropdownMenuContent>
)} </DropdownMenu>
</div> </div>
</div> </div>
))} ))}
{filteredAssistants.length === 0 && (
<div className="px-5 py-12 text-center">
<div className="font-medium text-foreground">
</div>
<div className="mt-2 text-sm text-muted-foreground">
</div>
</div>
)}
</div> </div>
</div> </div>
</section> </section>
@@ -254,14 +294,6 @@ export function AssistantPage() {
> >
</Button> </Button>
<Button
variant="outline"
size="lg"
className="gap-2 border-hairline-strong text-foreground hover:bg-surface-strong"
>
<Save size={16} />
稿
</Button>
<Button size="lg" className="gap-2"> <Button size="lg" className="gap-2">
<Rocket size={16} /> <Rocket size={16} />