fix: imports

This commit is contained in:
Austin Pickett 2026-04-19 18:52:04 -04:00
parent 60fd4b7d16
commit 823b6d08ed
11 changed files with 2127 additions and 146 deletions

View file

@ -15,7 +15,7 @@ import {
Code,
Zap,
} from "lucide-react";
import { H2 } from "@nous-research/ui/ui/components/typography/h2";
import { H2 } from "@nous-research/ui";
import { api } from "@/lib/api";
import type { SkillInfo, ToolsetInfo } from "@/lib/api";
import { useToast } from "@/hooks/useToast";
@ -47,7 +47,10 @@ const CATEGORY_LABELS: Record<string, string> = {
ui: "UI",
};
function prettyCategory(raw: string | null | undefined, generalLabel: string): string {
function prettyCategory(
raw: string | null | undefined,
generalLabel: string,
): string {
if (!raw) return generalLabel;
if (CATEGORY_LABELS[raw]) return CATEGORY_LABELS[raw];
return raw
@ -56,7 +59,10 @@ function prettyCategory(raw: string | null | undefined, generalLabel: string): s
.join(" ");
}
const TOOLSET_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {
const TOOLSET_ICONS: Record<
string,
React.ComponentType<{ className?: string }>
> = {
computer: Cpu,
web: Globe,
security: Shield,
@ -68,7 +74,9 @@ const TOOLSET_ICONS: Record<string, React.ComponentType<{ className?: string }>>
automation: Zap,
};
function toolsetIcon(name: string): React.ComponentType<{ className?: string }> {
function toolsetIcon(
name: string,
): React.ComponentType<{ className?: string }> {
const lower = name.toLowerCase();
for (const [key, icon] of Object.entries(TOOLSET_ICONS)) {
if (lower.includes(key)) return icon;
@ -108,12 +116,12 @@ export default function SkillsPage() {
await api.toggleSkill(skill.name, !skill.enabled);
setSkills((prev) =>
prev.map((s) =>
s.name === skill.name ? { ...s, enabled: !s.enabled } : s
)
s.name === skill.name ? { ...s, enabled: !s.enabled } : s,
),
);
showToast(
`${skill.name} ${skill.enabled ? t.common.disabled : t.common.enabled}`,
"success"
"success",
);
} catch {
showToast(`${t.common.failedToToggle} ${skill.name}`, "error");
@ -136,16 +144,19 @@ export default function SkillsPage() {
(s) =>
s.name.toLowerCase().includes(lowerSearch) ||
s.description.toLowerCase().includes(lowerSearch) ||
(s.category ?? "").toLowerCase().includes(lowerSearch)
(s.category ?? "").toLowerCase().includes(lowerSearch),
);
}, [skills, isSearching, lowerSearch]);
const activeSkills = useMemo(() => {
if (isSearching) return [];
if (!activeCategory) return [...skills].sort((a, b) => a.name.localeCompare(b.name));
if (!activeCategory)
return [...skills].sort((a, b) => a.name.localeCompare(b.name));
return skills
.filter((s) =>
activeCategory === "__none__" ? !s.category : s.category === activeCategory
activeCategory === "__none__"
? !s.category
: s.category === activeCategory,
)
.sort((a, b) => a.name.localeCompare(b.name));
}, [skills, activeCategory, isSearching]);
@ -162,7 +173,11 @@ export default function SkillsPage() {
if (b[0] === "__none__") return 1;
return a[0].localeCompare(b[0]);
})
.map(([key, count]) => ({ key, name: prettyCategory(key === "__none__" ? null : key, t.common.general), count }));
.map(([key, count]) => ({
key,
name: prettyCategory(key === "__none__" ? null : key, t.common.general),
count,
}));
}, [skills, t]);
const enabledCount = skills.filter((s) => s.enabled).length;
@ -173,7 +188,7 @@ export default function SkillsPage() {
!search ||
ts.name.toLowerCase().includes(lowerSearch) ||
ts.label.toLowerCase().includes(lowerSearch) ||
ts.description.toLowerCase().includes(lowerSearch)
ts.description.toLowerCase().includes(lowerSearch),
);
}, [toolsets, search, lowerSearch]);
@ -196,13 +211,18 @@ export default function SkillsPage() {
<Package className="h-5 w-5 text-muted-foreground" />
<H2 variant="sm">{t.skills.title}</H2>
<span className="text-xs text-muted-foreground">
{t.skills.enabledOf.replace("{enabled}", String(enabledCount)).replace("{total}", String(skills.length))}
{t.skills.enabledOf
.replace("{enabled}", String(enabledCount))
.replace("{total}", String(skills.length))}
</span>
</div>
</div>
{/* ═══════════════ Sidebar + Content ═══════════════ */}
<div className="flex flex-col sm:flex-row gap-4" style={{ minHeight: "calc(100vh - 180px)" }}>
<div
className="flex flex-col sm:flex-row gap-4"
style={{ minHeight: "calc(100vh - 180px)" }}
>
{/* ---- Sidebar ---- */}
<div className="sm:w-52 sm:shrink-0">
<div className="sm:sticky sm:top-[72px] flex flex-col gap-1">
@ -230,7 +250,11 @@ export default function SkillsPage() {
<div className="flex sm:flex-col gap-1 overflow-x-auto sm:overflow-x-visible scrollbar-none pb-1 sm:pb-0">
<button
type="button"
onClick={() => { setView("skills"); setActiveCategory(null); setSearch(""); }}
onClick={() => {
setView("skills");
setActiveCategory(null);
setSearch("");
}}
className={`group flex items-center gap-2 px-2.5 py-1.5 text-left text-xs transition-colors cursor-pointer ${
view === "skills" && !isSearching
? "bg-primary/10 text-primary font-medium"
@ -238,35 +262,48 @@ export default function SkillsPage() {
}`}
>
<Package className="h-3.5 w-3.5 shrink-0" />
<span className="flex-1 truncate">{t.skills.all} ({skills.length})</span>
{view === "skills" && !isSearching && <ChevronRight className="h-3 w-3 text-primary/50 shrink-0" />}
<span className="flex-1 truncate">
{t.skills.all} ({skills.length})
</span>
{view === "skills" && !isSearching && (
<ChevronRight className="h-3 w-3 text-primary/50 shrink-0" />
)}
</button>
{/* Skill categories (nested under All Skills) */}
{view === "skills" && !isSearching && allCategories.map(({ key, name, count }) => {
const isActive = activeCategory === key;
return (
<button
key={key}
type="button"
onClick={() => setActiveCategory(activeCategory === key ? null : key)}
className={`group flex items-center gap-2 px-2.5 py-1 pl-7 text-left text-[11px] transition-colors cursor-pointer ${
isActive
? "text-primary font-medium"
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
}`}
>
<span className="flex-1 truncate">{name}</span>
<span className={`text-[10px] tabular-nums ${isActive ? "text-primary/60" : "text-muted-foreground/50"}`}>
{count}
</span>
</button>
);
})}
{view === "skills" &&
!isSearching &&
allCategories.map(({ key, name, count }) => {
const isActive = activeCategory === key;
return (
<button
key={key}
type="button"
onClick={() =>
setActiveCategory(activeCategory === key ? null : key)
}
className={`group flex items-center gap-2 px-2.5 py-1 pl-7 text-left text-[11px] transition-colors cursor-pointer ${
isActive
? "text-primary font-medium"
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
}`}
>
<span className="flex-1 truncate">{name}</span>
<span
className={`text-[10px] tabular-nums ${isActive ? "text-primary/60" : "text-muted-foreground/50"}`}
>
{count}
</span>
</button>
);
})}
<button
type="button"
onClick={() => { setView("toolsets"); setSearch(""); }}
onClick={() => {
setView("toolsets");
setSearch("");
}}
className={`group flex items-center gap-2 px-2.5 py-1.5 text-left text-xs transition-colors cursor-pointer ${
view === "toolsets"
? "bg-primary/10 text-primary font-medium"
@ -274,8 +311,12 @@ export default function SkillsPage() {
}`}
>
<Wrench className="h-3.5 w-3.5 shrink-0" />
<span className="flex-1 truncate">{t.skills.toolsets} ({toolsets.length})</span>
{view === "toolsets" && <ChevronRight className="h-3 w-3 text-primary/50 shrink-0" />}
<span className="flex-1 truncate">
{t.skills.toolsets} ({toolsets.length})
</span>
{view === "toolsets" && (
<ChevronRight className="h-3 w-3 text-primary/50 shrink-0" />
)}
</button>
</div>
</div>
@ -293,7 +334,12 @@ export default function SkillsPage() {
{t.skills.title}
</CardTitle>
<Badge variant="secondary" className="text-[10px]">
{t.skills.resultCount.replace("{count}", String(searchMatchedSkills.length)).replace("{s}", searchMatchedSkills.length !== 1 ? "s" : "")}
{t.skills.resultCount
.replace("{count}", String(searchMatchedSkills.length))
.replace(
"{s}",
searchMatchedSkills.length !== 1 ? "s" : "",
)}
</Badge>
</div>
</CardHeader>
@ -325,18 +371,26 @@ export default function SkillsPage() {
<CardTitle className="text-sm flex items-center gap-2">
<Package className="h-4 w-4" />
{activeCategory
? prettyCategory(activeCategory === "__none__" ? null : activeCategory, t.common.general)
? prettyCategory(
activeCategory === "__none__" ? null : activeCategory,
t.common.general,
)
: t.skills.all}
</CardTitle>
<Badge variant="secondary" className="text-[10px]">
{activeSkills.length} {t.skills.skillCount.replace("{count}", String(activeSkills.length)).replace("{s}", activeSkills.length !== 1 ? "s" : "")}
{activeSkills.length}{" "}
{t.skills.skillCount
.replace("{count}", String(activeSkills.length))
.replace("{s}", activeSkills.length !== 1 ? "s" : "")}
</Badge>
</div>
</CardHeader>
<CardContent className="px-4 pb-4">
{activeSkills.length === 0 ? (
<p className="text-sm text-muted-foreground text-center py-8">
{skills.length === 0 ? t.skills.noSkills : t.skills.noSkillsMatch}
{skills.length === 0
? t.skills.noSkills
: t.skills.noSkillsMatch}
</p>
) : (
<div className="grid gap-1">
@ -366,7 +420,9 @@ export default function SkillsPage() {
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{filteredToolsets.map((ts) => {
const TsIcon = toolsetIcon(ts.name);
const labelText = ts.label.replace(/^[\p{Emoji}\s]+/u, "").trim() || ts.name;
const labelText =
ts.label.replace(/^[\p{Emoji}\s]+/u, "").trim() ||
ts.name;
return (
<Card key={ts.name} className="relative">
@ -375,12 +431,16 @@ export default function SkillsPage() {
<TsIcon className="h-5 w-5 text-muted-foreground shrink-0 mt-0.5" />
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className="font-medium text-sm">{labelText}</span>
<span className="font-medium text-sm">
{labelText}
</span>
<Badge
variant={ts.enabled ? "success" : "outline"}
className="text-[10px]"
>
{ts.enabled ? t.common.active : t.common.inactive}
{ts.enabled
? t.common.active
: t.common.inactive}
</Badge>
</div>
<p className="text-xs text-muted-foreground mb-2">
@ -406,7 +466,12 @@ export default function SkillsPage() {
)}
{ts.tools.length === 0 && (
<span className="text-[10px] text-muted-foreground/60">
{ts.enabled ? t.skills.toolsetLabel.replace("{name}", ts.name) : t.skills.disabledForCli}
{ts.enabled
? t.skills.toolsetLabel.replace(
"{name}",
ts.name,
)
: t.skills.disabledForCli}
</span>
)}
</div>