新增用户意图确认功能
This commit is contained in:
1 parent
cf2e4bdd9c
commit
b8926f00d7
574 files changed
+97664
-97208
No files matched your search
@@ -1,135 +1,135 @@
|
||||
"use client";
|
||||
|
||||
import { SparklesIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Empty,
|
||||
EmptyContent,
|
||||
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 { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { useEnableSkill, useSkills } from "@/core/skills/hooks";
|
||||
import type { Skill } from "@/core/skills/type";
|
||||
import { env } from "@/env";
|
||||
|
||||
import { SettingsSection } from "./settings-section";
|
||||
|
||||
export function SkillSettingsPage({ onClose }: { onClose?: () => void } = {}) {
|
||||
const { t } = useI18n();
|
||||
const { skills, isLoading, error } = useSkills();
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t.settings.skills.title}
|
||||
description={t.settings.skills.description}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="text-muted-foreground text-sm">{t.common.loading}</div>
|
||||
) : error ? (
|
||||
<div>Error: {error.message}</div>
|
||||
) : (
|
||||
<SkillSettingsList skills={skills} onClose={onClose} />
|
||||
)}
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
|
||||
function SkillSettingsList({
|
||||
skills,
|
||||
onClose,
|
||||
}: {
|
||||
skills: Skill[];
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const [filter, setFilter] = useState<string>("public");
|
||||
const { mutate: enableSkill } = useEnableSkill();
|
||||
const filteredSkills = useMemo(
|
||||
() => skills.filter((skill) => skill.category === filter),
|
||||
[skills, filter],
|
||||
);
|
||||
const handleCreateSkill = () => {
|
||||
onClose?.();
|
||||
router.push("/?mode=skill");
|
||||
};
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<header className="flex justify-between">
|
||||
<div className="flex gap-2">
|
||||
<Tabs defaultValue="public" onValueChange={setFilter}>
|
||||
<TabsList variant="line">
|
||||
<TabsTrigger value="public">{t.common.public}</TabsTrigger>
|
||||
<TabsTrigger value="custom">{t.common.custom}</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div>
|
||||
<Button size="sm" onClick={handleCreateSkill}>
|
||||
<SparklesIcon className="size-4" />
|
||||
{t.settings.skills.createSkill}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
{filteredSkills.length === 0 && (
|
||||
<EmptySkill onCreateSkill={handleCreateSkill} />
|
||||
)}
|
||||
{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}
|
||||
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"}
|
||||
onCheckedChange={(checked) =>
|
||||
enableSkill({ skillName: skill.name, enabled: checked })
|
||||
}
|
||||
/>
|
||||
</ItemActions>
|
||||
</Item>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EmptySkill({ onCreateSkill }: { onCreateSkill: () => void }) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<SparklesIcon />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>{t.settings.skills.emptyTitle}</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
{t.settings.skills.emptyDescription}
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button onClick={onCreateSkill}>{t.settings.skills.emptyButton}</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
);
|
||||
}
|
||||
"use client";
|
||||
|
||||
import { SparklesIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Empty,
|
||||
EmptyContent,
|
||||
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 { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { useEnableSkill, useSkills } from "@/core/skills/hooks";
|
||||
import type { Skill } from "@/core/skills/type";
|
||||
import { env } from "@/env";
|
||||
|
||||
import { SettingsSection } from "./settings-section";
|
||||
|
||||
export function SkillSettingsPage({ onClose }: { onClose?: () => void } = {}) {
|
||||
const { t } = useI18n();
|
||||
const { skills, isLoading, error } = useSkills();
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t.settings.skills.title}
|
||||
description={t.settings.skills.description}
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="text-muted-foreground text-sm">{t.common.loading}</div>
|
||||
) : error ? (
|
||||
<div>Error: {error.message}</div>
|
||||
) : (
|
||||
<SkillSettingsList skills={skills} onClose={onClose} />
|
||||
)}
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
|
||||
function SkillSettingsList({
|
||||
skills,
|
||||
onClose,
|
||||
}: {
|
||||
skills: Skill[];
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const [filter, setFilter] = useState<string>("public");
|
||||
const { mutate: enableSkill } = useEnableSkill();
|
||||
const filteredSkills = useMemo(
|
||||
() => skills.filter((skill) => skill.category === filter),
|
||||
[skills, filter],
|
||||
);
|
||||
const handleCreateSkill = () => {
|
||||
onClose?.();
|
||||
router.push("/?mode=skill");
|
||||
};
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<header className="flex justify-between">
|
||||
<div className="flex gap-2">
|
||||
<Tabs defaultValue="public" onValueChange={setFilter}>
|
||||
<TabsList variant="line">
|
||||
<TabsTrigger value="public">{t.common.public}</TabsTrigger>
|
||||
<TabsTrigger value="custom">{t.common.custom}</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<div>
|
||||
<Button size="sm" onClick={handleCreateSkill}>
|
||||
<SparklesIcon className="size-4" />
|
||||
{t.settings.skills.createSkill}
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
{filteredSkills.length === 0 && (
|
||||
<EmptySkill onCreateSkill={handleCreateSkill} />
|
||||
)}
|
||||
{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}
|
||||
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"}
|
||||
onCheckedChange={(checked) =>
|
||||
enableSkill({ skillName: skill.name, enabled: checked })
|
||||
}
|
||||
/>
|
||||
</ItemActions>
|
||||
</Item>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function EmptySkill({ onCreateSkill }: { onCreateSkill: () => void }) {
|
||||
const { t } = useI18n();
|
||||
return (
|
||||
<Empty>
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<SparklesIcon />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>{t.settings.skills.emptyTitle}</EmptyTitle>
|
||||
<EmptyDescription>
|
||||
{t.settings.skills.emptyDescription}
|
||||
</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button onClick={onCreateSkill}>{t.settings.skills.emptyButton}</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user