remove skill categories (#9008)
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
This commit is contained in:
@@ -1,294 +0,0 @@
|
||||
import type { SkillInfo } from "../api/skills";
|
||||
|
||||
const SKILL_CATEGORY_ORDER = [
|
||||
"design",
|
||||
"engineering",
|
||||
"quality",
|
||||
"research",
|
||||
"writing",
|
||||
"integrations",
|
||||
"operations",
|
||||
"productivity",
|
||||
"general",
|
||||
] as const;
|
||||
|
||||
export type SkillCategory = (typeof SKILL_CATEGORY_ORDER)[number];
|
||||
|
||||
export interface SkillViewInfo extends SkillInfo {
|
||||
inferredCategory: SkillCategory;
|
||||
}
|
||||
|
||||
const DESIGN_SLUGS = new Set([
|
||||
"adapt",
|
||||
"animate",
|
||||
"audit",
|
||||
"bolder",
|
||||
"clarify",
|
||||
"colorize",
|
||||
"critique",
|
||||
"delight",
|
||||
"distill",
|
||||
"frontend-design",
|
||||
"harden",
|
||||
"impeccable",
|
||||
"layout",
|
||||
]);
|
||||
|
||||
const ENGINEERING_SLUGS = new Set([
|
||||
"cloning-squareup-repos",
|
||||
"create-pr",
|
||||
"plugin-creator",
|
||||
"skill-creator",
|
||||
"skill-installer",
|
||||
]);
|
||||
|
||||
const QUALITY_SLUGS = new Set([
|
||||
"code-review",
|
||||
"create-app-e2e-test",
|
||||
"edge-case-finder",
|
||||
]);
|
||||
|
||||
const RESEARCH_SLUGS = new Set([
|
||||
"agent-browser",
|
||||
"codesearch",
|
||||
"dev-guides",
|
||||
"eng-ai-chat",
|
||||
"go-link",
|
||||
"openai-docs",
|
||||
]);
|
||||
|
||||
const WRITING_SLUGS = new Set(["ceo-weekly-update"]);
|
||||
|
||||
const OPERATIONS_SLUGS = new Set([
|
||||
"check-ci",
|
||||
"datadog",
|
||||
"github:gh-address-comments",
|
||||
"github:gh-fix-ci",
|
||||
]);
|
||||
|
||||
const INTEGRATION_SLUGS = new Set([
|
||||
"excel",
|
||||
"gdrive",
|
||||
"github:github",
|
||||
"launchdarkly",
|
||||
"linear",
|
||||
"powerpoint",
|
||||
]);
|
||||
|
||||
const PRODUCTIVITY_SLUGS = new Set(["grocery-list-organizer"]);
|
||||
|
||||
const CATEGORY_KEYWORDS: Record<SkillCategory, string[]> = {
|
||||
design: [
|
||||
"accessibility",
|
||||
"animation",
|
||||
"breakpoint",
|
||||
"color",
|
||||
"copy",
|
||||
"design",
|
||||
"frontend",
|
||||
"interface",
|
||||
"layout",
|
||||
"mobile",
|
||||
"motion",
|
||||
"polish",
|
||||
"responsive",
|
||||
"spacing",
|
||||
"theme",
|
||||
"typography",
|
||||
"ui",
|
||||
"ux",
|
||||
"visual",
|
||||
],
|
||||
engineering: [
|
||||
"app",
|
||||
"build",
|
||||
"codebase",
|
||||
"create",
|
||||
"feature",
|
||||
"implement",
|
||||
"install",
|
||||
"plugin",
|
||||
"react",
|
||||
"repository",
|
||||
"rust",
|
||||
"scaffold",
|
||||
"skill",
|
||||
"typescript",
|
||||
],
|
||||
quality: [
|
||||
"bug",
|
||||
"coverage",
|
||||
"edge case",
|
||||
"lint",
|
||||
"quality",
|
||||
"regression",
|
||||
"review",
|
||||
"test",
|
||||
"verify",
|
||||
],
|
||||
research: [
|
||||
"browse",
|
||||
"discover",
|
||||
"docs",
|
||||
"documentation",
|
||||
"find",
|
||||
"guide",
|
||||
"investigate",
|
||||
"knowledge",
|
||||
"look up",
|
||||
"query",
|
||||
"read",
|
||||
"search",
|
||||
],
|
||||
writing: [
|
||||
"copy",
|
||||
"document",
|
||||
"draft",
|
||||
"edit",
|
||||
"email",
|
||||
"message",
|
||||
"rewrite",
|
||||
"summary",
|
||||
"update",
|
||||
"write",
|
||||
],
|
||||
integrations: [
|
||||
"drive",
|
||||
"excel",
|
||||
"extension",
|
||||
"github",
|
||||
"google docs",
|
||||
"google drive",
|
||||
"google sheets",
|
||||
"google slides",
|
||||
"launchdarkly",
|
||||
"linear",
|
||||
"powerpoint",
|
||||
"sheets",
|
||||
"slides",
|
||||
],
|
||||
operations: [
|
||||
"buildkite",
|
||||
"canary",
|
||||
"ci",
|
||||
"flag",
|
||||
"incident",
|
||||
"kochiku",
|
||||
"log",
|
||||
"metric",
|
||||
"monitor",
|
||||
"observability",
|
||||
"release",
|
||||
"trace",
|
||||
],
|
||||
productivity: [
|
||||
"grocery",
|
||||
"meal plan",
|
||||
"organize",
|
||||
"organizer",
|
||||
"shopping",
|
||||
"weekly update",
|
||||
],
|
||||
general: [],
|
||||
};
|
||||
|
||||
function normalizeText(value: string) {
|
||||
return value.toLowerCase().replace(/[^a-z0-9:+\s-]/g, " ");
|
||||
}
|
||||
|
||||
function keywordScore(haystack: string, keywords: string[]) {
|
||||
return keywords.reduce((score, keyword) => {
|
||||
if (!haystack.includes(keyword)) {
|
||||
return score;
|
||||
}
|
||||
|
||||
return score + (keyword.includes(" ") ? 2 : 1);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function inferCategoryFromSlug(slug: string): SkillCategory | null {
|
||||
if (DESIGN_SLUGS.has(slug)) {
|
||||
return "design";
|
||||
}
|
||||
|
||||
if (QUALITY_SLUGS.has(slug)) {
|
||||
return "quality";
|
||||
}
|
||||
|
||||
if (ENGINEERING_SLUGS.has(slug)) {
|
||||
return "engineering";
|
||||
}
|
||||
|
||||
if (RESEARCH_SLUGS.has(slug)) {
|
||||
return "research";
|
||||
}
|
||||
|
||||
if (WRITING_SLUGS.has(slug)) {
|
||||
return "writing";
|
||||
}
|
||||
|
||||
if (OPERATIONS_SLUGS.has(slug)) {
|
||||
return "operations";
|
||||
}
|
||||
|
||||
if (INTEGRATION_SLUGS.has(slug) || slug.startsWith("google-drive:")) {
|
||||
return "integrations";
|
||||
}
|
||||
|
||||
if (PRODUCTIVITY_SLUGS.has(slug)) {
|
||||
return "productivity";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function inferSkillCategory(
|
||||
skill: Pick<SkillInfo, "name" | "description" | "instructions">,
|
||||
): SkillCategory {
|
||||
const slug = skill.name.toLowerCase();
|
||||
const explicitCategory = inferCategoryFromSlug(slug);
|
||||
if (explicitCategory) {
|
||||
return explicitCategory;
|
||||
}
|
||||
|
||||
const haystack = normalizeText(
|
||||
[skill.name, skill.description, skill.instructions].join(" "),
|
||||
);
|
||||
let bestCategory: SkillCategory = "general";
|
||||
let bestScore = 0;
|
||||
|
||||
for (const category of SKILL_CATEGORY_ORDER) {
|
||||
if (category === "general") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const score = keywordScore(haystack, CATEGORY_KEYWORDS[category]);
|
||||
if (score > bestScore) {
|
||||
bestCategory = category;
|
||||
bestScore = score;
|
||||
}
|
||||
}
|
||||
|
||||
return bestScore > 0 ? bestCategory : "general";
|
||||
}
|
||||
|
||||
function withInferredSkillCategory(skill: SkillInfo): SkillViewInfo {
|
||||
return {
|
||||
...skill,
|
||||
inferredCategory: inferSkillCategory(skill),
|
||||
};
|
||||
}
|
||||
|
||||
export function withInferredSkillCategories(
|
||||
skills: SkillInfo[],
|
||||
): SkillViewInfo[] {
|
||||
return skills.map(withInferredSkillCategory);
|
||||
}
|
||||
|
||||
export function uniqueSkillCategories(
|
||||
skills: SkillViewInfo[],
|
||||
): SkillCategory[] {
|
||||
return SKILL_CATEGORY_ORDER.filter((category) =>
|
||||
skills.some((skill) => skill.inferredCategory === category),
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
import type { SkillInfo } from "../api/skills";
|
||||
import type { SkillCategory, SkillViewInfo } from "./skillCategories";
|
||||
|
||||
export type SkillsFilter = "all" | "global" | `project:${string}`;
|
||||
|
||||
export interface SkillsSection {
|
||||
id: string;
|
||||
title: string;
|
||||
skills: SkillViewInfo[];
|
||||
skills: SkillInfo[];
|
||||
}
|
||||
|
||||
// Mirrors crates/goose/src/skills/mod.rs::validate_skill_name.
|
||||
@@ -59,24 +58,19 @@ export function compareSkillsByName(a: SkillInfo, b: SkillInfo) {
|
||||
}
|
||||
|
||||
export function filterSkills(
|
||||
skills: SkillViewInfo[],
|
||||
skills: SkillInfo[],
|
||||
filters: {
|
||||
search: string;
|
||||
activeFilter: SkillsFilter;
|
||||
selectedCategories: SkillCategory[];
|
||||
},
|
||||
getCategoryLabel: (category: SkillCategory) => string,
|
||||
): SkillViewInfo[] {
|
||||
): SkillInfo[] {
|
||||
const searchTerm = filters.search.trim().toLowerCase();
|
||||
return skills.filter((skill) => {
|
||||
const matchesSearch =
|
||||
searchTerm.length === 0 ||
|
||||
skill.name.toLowerCase().includes(searchTerm) ||
|
||||
skill.description.toLowerCase().includes(searchTerm) ||
|
||||
skill.sourceLabel.toLowerCase().includes(searchTerm) ||
|
||||
getCategoryLabel(skill.inferredCategory)
|
||||
.toLowerCase()
|
||||
.includes(searchTerm);
|
||||
skill.sourceLabel.toLowerCase().includes(searchTerm);
|
||||
|
||||
const matchesFilter =
|
||||
filters.activeFilter === "all"
|
||||
@@ -87,16 +81,12 @@ export function filterSkills(
|
||||
(project) => `project:${project.id}` === filters.activeFilter,
|
||||
);
|
||||
|
||||
const matchesCategory =
|
||||
filters.selectedCategories.length === 0 ||
|
||||
filters.selectedCategories.includes(skill.inferredCategory);
|
||||
|
||||
return matchesSearch && matchesFilter && matchesCategory;
|
||||
return matchesSearch && matchesFilter;
|
||||
});
|
||||
}
|
||||
|
||||
export function groupSkills(
|
||||
filteredSkills: SkillViewInfo[],
|
||||
filteredSkills: SkillInfo[],
|
||||
activeFilter: SkillsFilter,
|
||||
projectFilters: { id: string; name: string }[],
|
||||
labels: { personalTitle: string; projectsFallback: string },
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
IconAdjustmentsHorizontal,
|
||||
IconChevronDown,
|
||||
} from "@tabler/icons-react";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/shared/ui/dropdown-menu";
|
||||
import type { SkillCategory } from "../lib/skillCategories";
|
||||
|
||||
interface SkillCategoryFilterProps {
|
||||
categories: SkillCategory[];
|
||||
selectedCategories: SkillCategory[];
|
||||
onSelectedCategoriesChange: (categories: SkillCategory[]) => void;
|
||||
}
|
||||
|
||||
export function SkillCategoryFilter({
|
||||
categories,
|
||||
selectedCategories,
|
||||
onSelectedCategoriesChange,
|
||||
}: SkillCategoryFilterProps) {
|
||||
const { t } = useTranslation(["skills"]);
|
||||
|
||||
const toggleCategory = useCallback(
|
||||
(category: SkillCategory) => {
|
||||
onSelectedCategoriesChange(
|
||||
selectedCategories.includes(category)
|
||||
? selectedCategories.filter((value) => value !== category)
|
||||
: [...selectedCategories, category],
|
||||
);
|
||||
},
|
||||
[onSelectedCategoriesChange, selectedCategories],
|
||||
);
|
||||
|
||||
const buttonLabel =
|
||||
selectedCategories.length === 0
|
||||
? t("view.categories.label")
|
||||
: selectedCategories.length === 1
|
||||
? t(`view.categories.options.${selectedCategories[0]}`)
|
||||
: t("view.categories.count", { count: selectedCategories.length });
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
size="xs"
|
||||
variant={selectedCategories.length > 0 ? "default" : "outline-flat"}
|
||||
leftIcon={<IconAdjustmentsHorizontal />}
|
||||
rightIcon={<IconChevronDown />}
|
||||
aria-label={t("view.categories.filter")}
|
||||
>
|
||||
{buttonLabel}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-56">
|
||||
<DropdownMenuLabel>{t("view.categories.label")}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
{categories.map((category) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={category}
|
||||
checked={selectedCategories.includes(category)}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
onCheckedChange={() => toggleCategory(category)}
|
||||
>
|
||||
{t(`view.categories.options.${category}`)}
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
{selectedCategories.length > 0 ? (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onSelect={(event) => {
|
||||
event.preventDefault();
|
||||
onSelectedCategoriesChange([]);
|
||||
}}
|
||||
>
|
||||
{t("view.categories.clear")}
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
) : null}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -21,10 +21,9 @@ import { PageColumns } from "@/shared/ui/page-columns";
|
||||
import { DetailPageShell, PageHeader } from "@/shared/ui/page-shell";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
import type { SkillInfo } from "../api/skills";
|
||||
import type { SkillViewInfo } from "../lib/skillCategories";
|
||||
|
||||
interface SkillDetailPageProps {
|
||||
skill: SkillViewInfo | null;
|
||||
skill: SkillInfo | null;
|
||||
onBack: () => void;
|
||||
onEdit: (skill: SkillInfo) => void;
|
||||
onReveal: (skill: SkillInfo) => void;
|
||||
@@ -180,14 +179,6 @@ export function SkillDetailPage({
|
||||
sidebar={
|
||||
<aside className="space-y-5">
|
||||
<section className="space-y-5 border-b border-border pb-5">
|
||||
<DetailField
|
||||
label={t("view.category")}
|
||||
contentAs="p"
|
||||
contentClassName="text-foreground"
|
||||
>
|
||||
{t(`view.categories.options.${skill.inferredCategory}`)}
|
||||
</DetailField>
|
||||
|
||||
<DetailField
|
||||
label={t("view.source")}
|
||||
contentClassName="space-y-1 text-foreground"
|
||||
|
||||
@@ -8,15 +8,15 @@ import {
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip";
|
||||
import { IconMessagePlus } from "@tabler/icons-react";
|
||||
import type { SkillViewInfo } from "../lib/skillCategories";
|
||||
import type { SkillInfo } from "../api/skills";
|
||||
import type { SkillsSection } from "../lib/skillsHelpers";
|
||||
|
||||
interface SkillsListSectionsProps {
|
||||
sections: SkillsSection[];
|
||||
expandedSectionIds: string[];
|
||||
onExpandedSectionIdsChange: (ids: string[]) => void;
|
||||
onSelectSkill: (skill: SkillViewInfo) => void;
|
||||
onStartChat?: (skill: SkillViewInfo) => void;
|
||||
onSelectSkill: (skill: SkillInfo) => void;
|
||||
onStartChat?: (skill: SkillInfo) => void;
|
||||
}
|
||||
|
||||
export function SkillsListSections({
|
||||
|
||||
@@ -3,8 +3,6 @@ import { cn } from "@/shared/lib/cn";
|
||||
import { SearchBar } from "@/shared/ui/SearchBar";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import { FilterRow } from "@/shared/ui/page-shell";
|
||||
import { SkillCategoryFilter } from "./SkillCategoryFilter";
|
||||
import type { SkillCategory } from "../lib/skillCategories";
|
||||
import type { SkillsFilter } from "../lib/skillsHelpers";
|
||||
|
||||
interface SkillsToolbarProps {
|
||||
@@ -13,9 +11,6 @@ interface SkillsToolbarProps {
|
||||
activeFilter: SkillsFilter;
|
||||
onActiveFilterChange: (filter: SkillsFilter) => void;
|
||||
projectFilters: { id: string; name: string }[];
|
||||
categoryFilters: SkillCategory[];
|
||||
selectedCategories: SkillCategory[];
|
||||
onSelectedCategoriesChange: (categories: SkillCategory[]) => void;
|
||||
dropHandlers?: React.HTMLAttributes<HTMLDivElement>;
|
||||
isDragOver?: boolean;
|
||||
}
|
||||
@@ -47,9 +42,6 @@ export function SkillsToolbar({
|
||||
activeFilter,
|
||||
onActiveFilterChange,
|
||||
projectFilters,
|
||||
categoryFilters,
|
||||
selectedCategories,
|
||||
onSelectedCategoriesChange,
|
||||
dropHandlers,
|
||||
isDragOver,
|
||||
}: SkillsToolbarProps) {
|
||||
@@ -94,13 +86,6 @@ export function SkillsToolbar({
|
||||
</FilterButton>
|
||||
);
|
||||
})}
|
||||
{categoryFilters.length > 0 ? (
|
||||
<SkillCategoryFilter
|
||||
categories={categoryFilters}
|
||||
selectedCategories={selectedCategories}
|
||||
onSelectedCategoriesChange={onSelectedCategoriesChange}
|
||||
/>
|
||||
) : null}
|
||||
</FilterRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -25,12 +25,6 @@ import {
|
||||
type EditingSkill,
|
||||
type SkillInfo,
|
||||
} from "../api/skills";
|
||||
import {
|
||||
uniqueSkillCategories,
|
||||
withInferredSkillCategories,
|
||||
type SkillCategory,
|
||||
type SkillViewInfo,
|
||||
} from "../lib/skillCategories";
|
||||
|
||||
interface SkillsViewProps {
|
||||
onStartChatWithSkill?: (skill: SkillInfo, projectId?: string | null) => void;
|
||||
@@ -41,21 +35,18 @@ export function SkillsView({ onStartChatWithSkill }: SkillsViewProps) {
|
||||
const projects = useProjectStore((state) => state.projects);
|
||||
const [search, setSearch] = useState("");
|
||||
const [activeFilter, setActiveFilter] = useState<SkillsFilter>("all");
|
||||
const [selectedCategories, setSelectedCategories] = useState<SkillCategory[]>(
|
||||
[],
|
||||
);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [editingSkill, setEditingSkill] = useState<EditingSkill | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [skills, setSkills] = useState<SkillViewInfo[]>([]);
|
||||
const [skills, setSkills] = useState<SkillInfo[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [deletingSkill, setDeletingSkill] = useState<SkillInfo | null>(null);
|
||||
const [activeSkillId, setActiveSkillId] = useState<string | null>(null);
|
||||
const [expandedSectionIds, setExpandedSectionIds] = useState<string[]>([]);
|
||||
const loadRequestIdRef = useRef(0);
|
||||
|
||||
const loadSkills = useCallback(async (): Promise<SkillViewInfo[]> => {
|
||||
const loadSkills = useCallback(async (): Promise<SkillInfo[]> => {
|
||||
const requestId = loadRequestIdRef.current + 1;
|
||||
loadRequestIdRef.current = requestId;
|
||||
setLoading(true);
|
||||
@@ -66,9 +57,7 @@ export function SkillsView({ onStartChatWithSkill }: SkillsViewProps) {
|
||||
if (loadRequestIdRef.current !== requestId) {
|
||||
return [];
|
||||
}
|
||||
const nextSkills = withInferredSkillCategories(
|
||||
hydrateProjectNames(result, projects),
|
||||
);
|
||||
const nextSkills = hydrateProjectNames(result, projects);
|
||||
setSkills(nextSkills);
|
||||
return nextSkills;
|
||||
} catch {
|
||||
@@ -89,10 +78,6 @@ export function SkillsView({ onStartChatWithSkill }: SkillsViewProps) {
|
||||
}, [loadSkills]);
|
||||
|
||||
const projectFilters = useMemo(() => uniqueProjectFilters(skills), [skills]);
|
||||
const categoryFilters = useMemo(
|
||||
() => uniqueSkillCategories(skills),
|
||||
[skills],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeFilter.startsWith("project:")) {
|
||||
@@ -105,20 +90,9 @@ export function SkillsView({ onStartChatWithSkill }: SkillsViewProps) {
|
||||
}
|
||||
}, [activeFilter, projectFilters]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCategories((current) =>
|
||||
current.filter((category) => categoryFilters.includes(category)),
|
||||
);
|
||||
}, [categoryFilters]);
|
||||
|
||||
const filteredSkills = useMemo(
|
||||
() =>
|
||||
filterSkills(
|
||||
skills,
|
||||
{ search, activeFilter, selectedCategories },
|
||||
(category) => t(`view.categories.options.${category}`),
|
||||
),
|
||||
[skills, search, activeFilter, selectedCategories, t],
|
||||
() => filterSkills(skills, { search, activeFilter }),
|
||||
[skills, search, activeFilter],
|
||||
);
|
||||
|
||||
const groupedSkills = useMemo(
|
||||
@@ -219,7 +193,7 @@ export function SkillsView({ onStartChatWithSkill }: SkillsViewProps) {
|
||||
handleExport,
|
||||
} = useSkillImportExport(refreshSkills);
|
||||
|
||||
const handleSelectSkill = (skill: SkillViewInfo) => {
|
||||
const handleSelectSkill = (skill: SkillInfo) => {
|
||||
setActiveSkillId(skill.id);
|
||||
};
|
||||
|
||||
@@ -288,9 +262,6 @@ export function SkillsView({ onStartChatWithSkill }: SkillsViewProps) {
|
||||
activeFilter={activeFilter}
|
||||
onActiveFilterChange={setActiveFilter}
|
||||
projectFilters={projectFilters}
|
||||
categoryFilters={categoryFilters}
|
||||
selectedCategories={selectedCategories}
|
||||
onSelectedCategoriesChange={setSelectedCategories}
|
||||
dropHandlers={dropHandlers}
|
||||
isDragOver={isDragOver}
|
||||
/>
|
||||
|
||||
@@ -244,7 +244,6 @@ describe("SkillsView", () => {
|
||||
expect(
|
||||
screen.getByText("/tmp/alpha/.goose/skills/test-writer/SKILL.md"),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText("Quality")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("starts a chat with the selected skill from the list", async () => {
|
||||
@@ -378,23 +377,6 @@ describe("SkillsView", () => {
|
||||
expect(screen.getByText("test-writer")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("filters skills by inferred category from the dropdown", async () => {
|
||||
listSkills.mockResolvedValue(mockSkills);
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<SkillsView />);
|
||||
await screen.findByText("code-review");
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "Filter by category" }),
|
||||
);
|
||||
await user.click(screen.getByRole("menuitemcheckbox", { name: "Design" }));
|
||||
|
||||
expect(screen.getByText("layout")).toBeInTheDocument();
|
||||
expect(screen.queryByText("code-review")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("test-writer")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows a delete confirmation from the detail panel", async () => {
|
||||
listSkills.mockResolvedValue(mockSkills);
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -20,24 +20,6 @@
|
||||
"deleteError": "Failed to delete skill",
|
||||
"deleteSuccess": "Deleted \"{{name}}\"",
|
||||
"deleteTitle": "Delete \"{{name}}\" permanently?",
|
||||
"category": "Category",
|
||||
"categories": {
|
||||
"clear": "Clear categories",
|
||||
"count": "{{count}} categories",
|
||||
"filter": "Filter by category",
|
||||
"label": "Categories",
|
||||
"options": {
|
||||
"design": "Design",
|
||||
"engineering": "Engineering",
|
||||
"general": "General",
|
||||
"integrations": "Integrations",
|
||||
"operations": "Operations",
|
||||
"productivity": "Productivity",
|
||||
"quality": "Quality",
|
||||
"research": "Research",
|
||||
"writing": "Writing"
|
||||
}
|
||||
},
|
||||
"description": "Use skills to add specific instructions or behaviors to any agent.",
|
||||
"detailEmptyDescription": "Select a skill to inspect its source, location, and instructions.",
|
||||
"detailEmptyTitle": "Choose a skill",
|
||||
|
||||
@@ -16,24 +16,6 @@
|
||||
},
|
||||
"view": {
|
||||
"backToSkills": "Volver a skills",
|
||||
"category": "Categoría",
|
||||
"categories": {
|
||||
"clear": "Limpiar categorías",
|
||||
"count": "{{count}} categorías",
|
||||
"filter": "Filtrar por categoría",
|
||||
"label": "Categorías",
|
||||
"options": {
|
||||
"design": "Diseño",
|
||||
"engineering": "Ingeniería",
|
||||
"general": "General",
|
||||
"integrations": "Integraciones",
|
||||
"operations": "Operaciones",
|
||||
"productivity": "Productividad",
|
||||
"quality": "Calidad",
|
||||
"research": "Investigación",
|
||||
"writing": "Redacción"
|
||||
}
|
||||
},
|
||||
"deleteDescription": "Esta habilidad y su configuración se eliminarán de forma permanente.",
|
||||
"deleteError": "Error al eliminar la skill",
|
||||
"deleteSuccess": "Skill \"{{name}}\" eliminada",
|
||||
|
||||
@@ -42,32 +42,11 @@ test.describe("Skills view", () => {
|
||||
page.getByRole("button", { name: "Back to skills" }),
|
||||
).toBeVisible();
|
||||
await expect(page.getByText("alpha").first()).toBeVisible();
|
||||
await expect(page.getByText("Quality")).toBeVisible();
|
||||
await expect(
|
||||
page.getByText("/tmp/alpha/.goose/skills/test-writer/SKILL.md"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("category filtering isolates inferred groups", async ({
|
||||
tauriMocked: page,
|
||||
}) => {
|
||||
await navigateToSkills(page);
|
||||
|
||||
await page.getByRole("button", { name: "Filter by category" }).click();
|
||||
await page.getByRole("menuitemcheckbox", { name: "Design" }).click();
|
||||
await page.keyboard.press("Escape");
|
||||
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Open layout details" }),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Open code-review details" }),
|
||||
).not.toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("button", { name: "Open test-writer details" }),
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("search filters the list", async ({ tauriMocked: page }) => {
|
||||
await navigateToSkills(page);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user