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 {
Bot,
Brain,
Database,
Mic,
MoreHorizontal,
Pencil,
Plus,
Rocket,
Save,
Search,
Sparkles,
Trash2,
Volume2,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Switch } from "@/components/ui/switch";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Select,
SelectContent,
@@ -53,7 +57,6 @@ type AssistantListItem = {
id: string;
name: string;
type: AssistantType;
status: "运行中" | "草稿" | "已停用";
updatedAt: string;
};
@@ -62,28 +65,24 @@ const mockAssistants: AssistantListItem[] = [
id: "asst_001",
name: "政务视频咨询助手",
type: "提示词",
status: "运行中",
updatedAt: "2026-06-05 18:20",
},
{
id: "asst_002",
name: "热线工单辅助助手",
type: "工作流",
status: "草稿",
updatedAt: "2026-06-04 15:12",
},
{
id: "asst_003",
name: "Dify 知识库问答助手",
type: "Dify",
status: "运行中",
updatedAt: "2026-06-02 09:48",
},
{
id: "asst_004",
name: "FastGPT 售后咨询助手",
type: "FastGPT",
status: "已停用",
updatedAt: "2026-05-29 11:06",
},
];
@@ -102,7 +101,20 @@ export function AssistantPage() {
enableInterrupt: true,
});
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>(
key: K,
@@ -115,117 +127,145 @@ export function AssistantPage() {
}
if (view === "list") {
return (
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-6">
<div className="flex items-start justify-between">
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-8">
<div className="flex flex-col items-start justify-between gap-5 sm:flex-row sm:gap-6">
<div>
<div className="flex items-center gap-3">
<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="text-3xl font-bold"></h1>
</div>
<p className="mt-2 text-sm text-muted-soft">
<div className="caption-label text-muted-soft"></div>
<h1 className="font-display display-lg mt-3 text-ink"></h1>
<p className="mt-3 max-w-2xl text-[15px] leading-7 text-muted-foreground">
Dify FastGPT
</p>
</div>
<button
<Button
size="lg"
className="w-full shrink-0 gap-2 sm:w-auto"
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} />
</button>
</Button>
</div>
<section className="rounded-2xl border border-sidebar-border bg-sidebar p-5">
<div className="mb-5 flex items-center justify-between gap-4">
<section className="rounded-2xl border border-hairline bg-card p-6 shadow-sm">
<div className="mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<div className="text-lg font-semibold"></div>
<div className="mt-1 text-sm text-muted-soft">
<div className="text-[18px] font-medium text-foreground">
</div>
<div className="mt-1 text-sm text-muted-foreground">
{mockAssistants.length}
{searchQuery.trim() && `,已筛选 ${filteredAssistants.length}`}
</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">
<Search size={16} />
<span>...</span>
<div className="relative w-full md:w-[320px]">
<Search
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 className="overflow-hidden rounded-2xl border border-hairline-strong">
<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></div>
<div></div>
<div></div>
<div></div>
<div className="text-right"></div>
<div className="overflow-hidden rounded-xl border border-hairline">
<div className="hidden items-center gap-4 bg-surface-strong/60 px-5 py-3 md:flex">
<div className="caption-label flex-1 text-muted-soft">
</div>
<div className="caption-label w-[110px] text-muted-soft">
</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 className="divide-y divide-hairline-strong">
{mockAssistants.map((assistant) => (
<div className="divide-y divide-hairline">
{filteredAssistants.map((assistant) => (
<div
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="font-medium text-foreground">
<div className="min-w-0 flex-1">
<div className="truncate font-medium text-foreground">
{assistant.name}
</div>
<div className="mt-1 text-xs text-muted-soft">
{assistant.id}
</div>
</div>
<div>
<span className="rounded-full border border-hairline-strong bg-surface-strong px-2.5 py-1 text-xs text-muted-foreground">
{assistant.type}
</span>
</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(" ")}
<div className="md:w-[110px]">
<Badge
variant="secondary"
className="h-6 bg-surface-strong px-3 text-muted-foreground"
>
{assistant.status}
</span>
{assistant.type}
</Badge>
</div>
<div className="text-muted-soft">{assistant.updatedAt}</div>
<div className="relative flex justify-end gap-2">
<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">
<div className="text-muted-foreground md:w-[150px]">
{assistant.updatedAt}
</div>
<div className="flex justify-end gap-2 md:w-[116px]">
<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} />
</button>
<button
onClick={() =>
setOpenMenuId(
openMenuId === assistant.id ? null : assistant.id,
)
}
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"
>
<MoreHorizontal size={15} />
</button>
{openMenuId === assistant.id && (
<div className="absolute right-0 top-9 z-10 w-28 rounded-xl border border-hairline-strong bg-sidebar p-1 shadow-xl">
<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">
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon-sm"
className="border-hairline-strong text-muted-foreground hover:text-foreground"
aria-label={`${assistant.name} 更多操作`}
>
<MoreHorizontal size={15} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="w-32 min-w-32 rounded-xl border border-hairline bg-popover p-1"
>
<DropdownMenuItem
variant="destructive"
className="rounded-lg"
>
<Trash2 size={14} />
</button>
</div>
)}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</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>
</section>
@@ -254,14 +294,6 @@ export function AssistantPage() {
>
</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">
<Rocket size={16} />
@@ -495,4 +527,4 @@ function ToggleRow({
<Switch checked={checked} onCheckedChange={onChange} />
</div>
);
}
}