Files
deer-flow/frontend/src/components/workspace/settings/skill-settings-page.tsx

129 lines
3.6 KiB
TypeScript
Raw Normal View History

2026-01-20 23:43:21 +08:00
"use client";
import { SparklesIcon } from "lucide-react";
2026-01-22 00:38:20 +08:00
import { useMemo, useState } from "react";
2026-01-20 23:43:21 +08:00
2026-01-22 00:38:20 +08:00
import { Button } from "@/components/ui/button";
2026-01-20 23:43:21 +08:00
import {
Empty,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
import {
Item,
ItemActions,
ItemTitle,
ItemContent,
ItemDescription,
} from "@/components/ui/item";
import { Switch } from "@/components/ui/switch";
import { useI18n } from "@/core/i18n/hooks";
import { useEnableSkill, useSkills } from "@/core/skills/hooks";
import type { Skill } from "@/core/skills/type";
import { SettingsSection } from "./settings-section";
export function SkillSettingsPage() {
const { t } = useI18n();
const { skills, isLoading, error } = useSkills();
return (
<SettingsSection
title={t.settings.skills.title}
description={t.settings.skills.description}
>
{isLoading ? (
<div>Loading...</div>
) : error ? (
<div>Error: {error.message}</div>
) : (
<SkillSettingsList skills={skills} />
)}
</SettingsSection>
);
}
function SkillSettingsList({ skills }: { skills: Skill[] }) {
2026-01-22 00:38:20 +08:00
const { t } = useI18n();
const [filter, setFilter] = useState<"public" | "custom">("public");
2026-01-20 23:43:21 +08:00
const { mutate: enableSkill } = useEnableSkill();
2026-01-22 00:38:20 +08:00
const filteredSkills = useMemo(
() => skills.filter((skill) => skill.category === filter),
[skills, filter],
);
2026-01-20 23:43:21 +08:00
if (skills.length === 0) {
return (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<SparklesIcon />
</EmptyMedia>
<EmptyTitle>No skill yet</EmptyTitle>
<EmptyDescription>
Put your skill folders under the `/skills/custom` folder under the
root folder of DeerFlow.
</EmptyDescription>
</EmptyHeader>
</Empty>
);
}
return (
<div className="flex w-full flex-col gap-4">
2026-01-22 00:38:20 +08:00
<header className="flex gap-2">
<Button
className="rounded-xl"
size="sm"
variant={filter === "public" ? "default" : "outline"}
onClick={() => setFilter("public")}
>
{t.common.public}
</Button>
<Button
className="rounded-xl"
size="sm"
variant={filter === "custom" ? "default" : "outline"}
onClick={() => setFilter("custom")}
>
{t.common.custom}
</Button>
</header>
{filteredSkills.length === 0 && (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<SparklesIcon />
</EmptyMedia>
<EmptyTitle>No skill yet</EmptyTitle>
<EmptyDescription>
2026-01-22 11:31:23 +08:00
Put your skill folders under the `skills/{filter}` folder under
2026-01-22 00:38:20 +08:00
the root folder of DeerFlow.
</EmptyDescription>
</EmptyHeader>
</Empty>
)}
{filteredSkills.length > 0 &&
filteredSkills.map((skill) => (
<Item className="w-full" variant="outline" key={skill.name}>
<ItemContent>
<ItemTitle>
<div className="flex items-center gap-2">{skill.name}</div>
</ItemTitle>
<ItemDescription className="line-clamp-4">
{skill.description}
</ItemDescription>
</ItemContent>
<ItemActions>
<Switch
checked={skill.enabled}
onCheckedChange={(checked) =>
enableSkill({ skillName: skill.name, enabled: checked })
}
/>
</ItemActions>
</Item>
))}
2026-01-20 23:43:21 +08:00
</div>
);
}