mirror of
https://github.com/mfakbar127/Claude-Samurai
synced 2026-06-21 13:57:42 +00:00
ux improvement
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "cc-mate",
|
"name": "claude-samurai",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
+113
-81
@@ -42,68 +42,86 @@ export function Layout() {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isProjectsRoute = location.pathname.startsWith("/projects");
|
const isProjectsRoute = location.pathname.startsWith("/projects");
|
||||||
|
|
||||||
const navLinks = [
|
const navSections = [
|
||||||
{
|
{
|
||||||
to: "/",
|
id: "core",
|
||||||
icon: FileJsonIcon,
|
label: t("navigation.section.core", { defaultValue: "Core" }),
|
||||||
label: t("navigation.configurations"),
|
items: [
|
||||||
|
{
|
||||||
|
to: "/",
|
||||||
|
icon: FileJsonIcon,
|
||||||
|
label: t("navigation.configurations"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/projects",
|
||||||
|
icon: FolderIcon,
|
||||||
|
label: t("navigation.projects"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/mcp",
|
||||||
|
icon: CpuIcon,
|
||||||
|
label: t("navigation.mcp"),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/projects",
|
id: "automation",
|
||||||
icon: FolderIcon,
|
label: t("navigation.section.automation", { defaultValue: "Automation" }),
|
||||||
label: t("navigation.projects"),
|
items: [
|
||||||
|
{
|
||||||
|
to: "/hooks",
|
||||||
|
icon: CpuIcon,
|
||||||
|
label: t("hooks.title"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/commands",
|
||||||
|
icon: TerminalIcon,
|
||||||
|
label: t("navigation.commands"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/skills",
|
||||||
|
icon: SparklesIcon,
|
||||||
|
label: t("navigation.skills"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/agents",
|
||||||
|
icon: BotIcon,
|
||||||
|
label: "Agents",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: "/mcp",
|
id: "system",
|
||||||
icon: CpuIcon,
|
label: t("navigation.section.system", { defaultValue: "System" }),
|
||||||
label: t("navigation.mcp"),
|
items: [
|
||||||
|
{
|
||||||
|
to: "/memory",
|
||||||
|
icon: BrainIcon,
|
||||||
|
label: t("navigation.memory"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/plugins",
|
||||||
|
icon: PackageIcon,
|
||||||
|
label: t("navigation.plugins"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/notification",
|
||||||
|
icon: BellIcon,
|
||||||
|
label: t("navigation.notifications"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/usage",
|
||||||
|
icon: ActivityIcon,
|
||||||
|
label: t("navigation.usage"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: "/settings",
|
||||||
|
icon: SettingsIcon,
|
||||||
|
label: t("navigation.settings"),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
] as const;
|
||||||
to: "/hooks",
|
|
||||||
icon: CpuIcon,
|
|
||||||
label: t("hooks.title"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/agents",
|
|
||||||
icon: BotIcon,
|
|
||||||
label: "Agents",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/memory",
|
|
||||||
icon: BrainIcon,
|
|
||||||
label: t("navigation.memory"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/commands",
|
|
||||||
icon: TerminalIcon,
|
|
||||||
label: t("navigation.commands"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/skills",
|
|
||||||
icon: SparklesIcon,
|
|
||||||
label: t("navigation.skills"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/plugins",
|
|
||||||
icon: PackageIcon,
|
|
||||||
label: t("navigation.plugins"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/notification",
|
|
||||||
icon: BellIcon,
|
|
||||||
label: t("navigation.notifications"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/usage",
|
|
||||||
icon: ActivityIcon,
|
|
||||||
label: t("navigation.usage"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/settings",
|
|
||||||
icon: SettingsIcon,
|
|
||||||
label: t("navigation.settings"),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background flex flex-col">
|
<div className="min-h-screen bg-background flex flex-col">
|
||||||
@@ -113,37 +131,51 @@ export function Layout() {
|
|||||||
<div className="flex flex-1 overflow-hidden ">
|
<div className="flex flex-1 overflow-hidden ">
|
||||||
<nav
|
<nav
|
||||||
className="w-[200px] bg-background border-r flex flex-col"
|
className="w-[200px] bg-background border-r flex flex-col"
|
||||||
|
aria-label={t("navigation.primary", {
|
||||||
|
defaultValue: "Primary navigation",
|
||||||
|
})}
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
>
|
>
|
||||||
<MacosDragRegionSpacer className="h-10" />
|
<MacosDragRegionSpacer className="h-10" />
|
||||||
<div
|
<div className="flex flex-col flex-1 justify-between">
|
||||||
className="flex flex-col flex-1 justify-between"
|
<div className="px-3 pt-3 space-y-4">
|
||||||
data-tauri-drag-region
|
{navSections.map((section) => (
|
||||||
>
|
<div key={section.id} className="space-y-2">
|
||||||
<ul className="px-3 pt-3 space-y-2">
|
<p
|
||||||
{navLinks.map((link) => (
|
className="px-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground/80"
|
||||||
<li key={link.to}>
|
role="heading"
|
||||||
<NavLink
|
aria-level={2}
|
||||||
to={link.to}
|
|
||||||
className={({ isActive }) =>
|
|
||||||
cn(
|
|
||||||
"flex items-center gap-2 px-3 py-2 rounded-xl cursor-default select-none ",
|
|
||||||
{
|
|
||||||
"bg-primary text-primary-foreground": isActive,
|
|
||||||
"hover:bg-accent hover:text-accent-foreground":
|
|
||||||
!isActive,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<link.icon size={14} />
|
{section.label}
|
||||||
{link.label}
|
</p>
|
||||||
</NavLink>
|
<ul className="space-y-1">
|
||||||
</li>
|
{section.items.map((link) => (
|
||||||
|
<li key={link.to}>
|
||||||
|
<NavLink
|
||||||
|
to={link.to}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
cn(
|
||||||
|
"flex items-center gap-2 px-3 py-2 rounded-xl cursor-default select-none text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
||||||
|
{
|
||||||
|
"bg-primary text-primary-foreground":
|
||||||
|
isActive,
|
||||||
|
"hover:bg-accent hover:text-accent-foreground":
|
||||||
|
!isActive,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<link.icon size={14} />
|
||||||
|
{link.label}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2 px-3 pb-3 pt-4 border-t">
|
||||||
<UpdateButton />
|
<UpdateButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+320
-119
@@ -5,17 +5,29 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { GLMDialog } from "@/components/GLMBanner";
|
import { GLMDialog } from "@/components/GLMBanner";
|
||||||
import { KimiDialog } from "@/components/KimiDialog";
|
import { KimiDialog } from "@/components/KimiDialog";
|
||||||
import { MiniMaxDialog } from "@/components/MiniMaxDialog";
|
import { MiniMaxDialog } from "@/components/MiniMaxDialog";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ButtonGroup } from "@/components/ui/button-group";
|
import { ButtonGroup } from "@/components/ui/button-group";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import {
|
import {
|
||||||
useCreateConfig,
|
useCreateConfig,
|
||||||
|
useDeleteConfig,
|
||||||
useResetToOriginalConfig,
|
useResetToOriginalConfig,
|
||||||
useSetCurrentConfig,
|
useSetCurrentConfig,
|
||||||
useStores,
|
useStores,
|
||||||
@@ -23,11 +35,9 @@ import {
|
|||||||
|
|
||||||
export function ConfigSwitcherPage() {
|
export function ConfigSwitcherPage() {
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<section>
|
||||||
<section>
|
<ConfigStores />
|
||||||
<ConfigStores />
|
</section>
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +49,10 @@ function ConfigStores() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const isOriginalConfigActive = !stores.some((store) => store.using);
|
const isOriginalConfigActive = !stores.some((store) => store.using);
|
||||||
|
const activeStore = stores.find((store) => store.using) ?? null;
|
||||||
|
const otherStores = activeStore
|
||||||
|
? stores.filter((store) => store.id !== activeStore.id)
|
||||||
|
: stores;
|
||||||
|
|
||||||
const handleStoreClick = (storeId: string, isCurrentStore: boolean) => {
|
const handleStoreClick = (storeId: string, isCurrentStore: boolean) => {
|
||||||
if (!isCurrentStore) {
|
if (!isCurrentStore) {
|
||||||
@@ -53,94 +67,106 @@ function ConfigStores() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createStoreMutation = useCreateConfig();
|
const createStoreMutation = useCreateConfig();
|
||||||
|
const deleteStoreMutation = useDeleteConfig();
|
||||||
|
|
||||||
const onCreateStore = async () => {
|
const onCreateStore = async () => {
|
||||||
const store = await createStoreMutation.mutateAsync({
|
const store = await createStoreMutation.mutateAsync({
|
||||||
title: t("configSwitcher.newConfig"),
|
title: t("configSwitcher.newConfig"),
|
||||||
settings: {},
|
settings: {},
|
||||||
});
|
});
|
||||||
navigate(`/edit/${store.id}`);
|
if (store) {
|
||||||
|
navigate(`/edit/${store.id}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (stores.length === 0) {
|
if (stores.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="flex h-[calc(100vh-64px)] items-center justify-center px-6">
|
||||||
className="flex justify-center items-center h-screen"
|
<div className="flex max-w-md flex-col items-center gap-3 text-center">
|
||||||
data-tauri-drag-region
|
<h1 className="text-xl font-semibold">
|
||||||
>
|
{t("configSwitcher.emptyTitle", {
|
||||||
<div className="flex flex-col items-center gap-2">
|
defaultValue: "No configurations yet",
|
||||||
<Button variant="ghost" onClick={onCreateStore} className="">
|
})}
|
||||||
<PlusIcon size={14} />
|
</h1>
|
||||||
{t("configSwitcher.createConfig")}
|
<p className="text-sm text-muted-foreground">
|
||||||
</Button>
|
|
||||||
|
|
||||||
<p className="text-sm text-muted-foreground" data-tauri-drag-region>
|
|
||||||
{t("configSwitcher.description")}
|
{t("configSwitcher.description")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-4 space-y-2">
|
<ButtonGroup className="mt-2">
|
||||||
<GLMDialog
|
<Button
|
||||||
trigger={
|
onClick={onCreateStore}
|
||||||
|
size="sm"
|
||||||
|
disabled={createStoreMutation.isPending}
|
||||||
|
>
|
||||||
|
<PlusIcon size={14} />
|
||||||
|
{t("configSwitcher.createConfig")}
|
||||||
|
</Button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="outline"
|
||||||
className="text-muted-foreground text-sm"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
|
disabled={createStoreMutation.isPending}
|
||||||
>
|
>
|
||||||
<ZAI />
|
<EllipsisVerticalIcon size={14} />
|
||||||
{t("glm.useZhipuGlm")}
|
|
||||||
</Button>
|
</Button>
|
||||||
}
|
</DropdownMenuTrigger>
|
||||||
/>
|
<DropdownMenuContent align="end">
|
||||||
<MiniMaxDialog
|
<GLMDialog
|
||||||
trigger={
|
trigger={
|
||||||
<Button
|
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
||||||
variant="ghost"
|
<ZAI />
|
||||||
className="text-muted-foreground text-sm"
|
{t("glm.useZhipuGlm")}
|
||||||
size="sm"
|
</DropdownMenuItem>
|
||||||
>
|
}
|
||||||
<Minimax />
|
/>
|
||||||
{t("minimax.useMiniMax")}
|
<MiniMaxDialog
|
||||||
</Button>
|
trigger={
|
||||||
}
|
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
||||||
/>
|
<Minimax />
|
||||||
<KimiDialog
|
{t("minimax.useMiniMax")}
|
||||||
trigger={
|
</DropdownMenuItem>
|
||||||
<Button
|
}
|
||||||
variant="ghost"
|
/>
|
||||||
className="text-muted-foreground text-sm"
|
<KimiDialog
|
||||||
size="sm"
|
trigger={
|
||||||
>
|
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
|
||||||
<Kimi />
|
<Kimi />
|
||||||
{t("kimi.useKimi")}
|
{t("kimi.useKimi")}
|
||||||
</Button>
|
</DropdownMenuItem>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<p className="mt-4 text-xs text-muted-foreground">
|
||||||
|
{t("configSwitcher.emptyHelper", {
|
||||||
|
defaultValue:
|
||||||
|
"Start from a template like Zhipu GLM, MiniMax, or Kimi, or create a config from scratch.",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="space-y-6 p-4">
|
||||||
<div
|
<header className="flex items-start justify-between gap-4 border-b pb-3">
|
||||||
className="flex items-center p-3 border-b px-3 justify-between sticky top-0 bg-background z-10"
|
<div>
|
||||||
data-tauri-drag-region
|
<h1 className="text-lg font-semibold">
|
||||||
>
|
|
||||||
<div data-tauri-drag-region>
|
|
||||||
<h3 className="font-bold" data-tauri-drag-region>
|
|
||||||
{t("configSwitcher.title")}
|
{t("configSwitcher.title")}
|
||||||
</h3>
|
</h1>
|
||||||
<p className="text-sm text-muted-foreground" data-tauri-drag-region>
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
{t("configSwitcher.description")}
|
{t("configSwitcher.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
|
||||||
onClick={onCreateStore}
|
onClick={onCreateStore}
|
||||||
className="text-muted-foreground"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
|
disabled={createStoreMutation.isPending}
|
||||||
>
|
>
|
||||||
<PlusIcon size={14} />
|
<PlusIcon size={14} />
|
||||||
{t("configSwitcher.createConfig")}
|
{t("configSwitcher.createConfig")}
|
||||||
@@ -149,8 +175,8 @@ function ConfigStores() {
|
|||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="text-muted-foreground"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
|
disabled={createStoreMutation.isPending}
|
||||||
>
|
>
|
||||||
<EllipsisVerticalIcon size={14} />
|
<EllipsisVerticalIcon size={14} />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -183,71 +209,246 @@ function ConfigStores() {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</div>
|
</header>
|
||||||
|
|
||||||
{/* <GLMBanner className="mx-4 mt-4" /> */}
|
<section className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div className="grid grid-cols-3 lg:grid-cols-4 gap-3 p-4">
|
<h2 className="text-sm font-medium">
|
||||||
{/* Fixed Claude Original Config Item */}
|
{t("configSwitcher.activeConfigHeading", {
|
||||||
<div
|
defaultValue: "Active configuration",
|
||||||
role="button"
|
})}
|
||||||
onClick={handleOriginalConfigClick}
|
</h2>
|
||||||
className={cn(
|
<div className="flex items-center gap-2">
|
||||||
"border rounded-xl p-3 h-[100px] flex flex-col justify-between transition-colors",
|
<Badge variant="success">
|
||||||
{
|
{t("configSwitcher.activeBadge", { defaultValue: "Active" })}
|
||||||
"bg-primary/10 border-primary border-2": isOriginalConfigActive,
|
</Badge>
|
||||||
},
|
<Button
|
||||||
)}
|
variant="ghost"
|
||||||
>
|
size="sm"
|
||||||
<div>
|
className="h-7 px-2 text-xs text-muted-foreground hover:text-foreground"
|
||||||
<div>{t("configSwitcher.originalConfig")}</div>
|
onClick={() => navigate("/settings")}
|
||||||
<div className="text-xs text-muted-foreground mt-1">
|
>
|
||||||
{t("configSwitcher.originalConfigDescription")}
|
{t("configSwitcher.viewHistory", {
|
||||||
</div>
|
defaultValue: "Backups & history",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{stores.map((store) => {
|
<div className="rounded-xl border bg-card p-4">
|
||||||
const isCurrentStore = store.using;
|
{activeStore ? (
|
||||||
return (
|
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||||
<div
|
<div className="space-y-1">
|
||||||
role="button"
|
<div className="flex items-center gap-2">
|
||||||
key={store.id}
|
<h3 className="font-medium">{activeStore.title}</h3>
|
||||||
onClick={() => handleStoreClick(store.id, isCurrentStore)}
|
<Badge
|
||||||
className={cn(
|
variant="secondary"
|
||||||
"border rounded-xl p-3 h-[100px] flex flex-col justify-between transition-colors disabled:opacity-50",
|
className="text-[11px] font-medium uppercase"
|
||||||
{
|
|
||||||
"bg-primary/10 border-primary border-2": isCurrentStore,
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div>{store.title}</div>
|
|
||||||
{store.settings.env?.ANTHROPIC_BASE_URL && (
|
|
||||||
<div
|
|
||||||
className="text-xs text-muted-foreground mt-1 truncate "
|
|
||||||
title={store.settings.env.ANTHROPIC_BASE_URL}
|
|
||||||
>
|
>
|
||||||
{store.settings.env.ANTHROPIC_BASE_URL}
|
{t("configSwitcher.tagDailyDriver", {
|
||||||
</div>
|
defaultValue: "Daily driver",
|
||||||
|
})}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
{activeStore.settings.env?.ANTHROPIC_BASE_URL && (
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{activeStore.settings.env.ANTHROPIC_BASE_URL}
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
<div className="flex justify-end">
|
<Button
|
||||||
<button
|
variant="outline"
|
||||||
className="hover:bg-primary/10 rounded-lg p-2 hover:text-primary"
|
size="sm"
|
||||||
onClick={(e) => {
|
onClick={() => navigate(`/edit/${activeStore.id}`)}
|
||||||
e.stopPropagation();
|
|
||||||
navigate(`/edit/${store.id}`);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<PencilLineIcon className="text-muted-foreground" size={14} />
|
<PencilLineIcon size={14} className="mr-1" />
|
||||||
</button>
|
{t("configSwitcher.edit", { defaultValue: "Edit" })}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
) : (
|
||||||
})}
|
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
||||||
</div>
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h3 className="font-medium">
|
||||||
|
{t("configSwitcher.originalConfig", {
|
||||||
|
defaultValue: "Original Claude config",
|
||||||
|
})}
|
||||||
|
</h3>
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="text-[11px] font-medium uppercase"
|
||||||
|
>
|
||||||
|
{t("configSwitcher.tagDefault", {
|
||||||
|
defaultValue: "Default",
|
||||||
|
})}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t("configSwitcher.originalConfigDescription")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleOriginalConfigClick}
|
||||||
|
disabled={
|
||||||
|
isOriginalConfigActive || resetToOriginalMutation.isPending
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("configSwitcher.restoreOriginal", {
|
||||||
|
defaultValue: "Restore original",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h2 className="text-sm font-medium">
|
||||||
|
{t("configSwitcher.otherConfigsHeading", {
|
||||||
|
defaultValue: "Other configurations",
|
||||||
|
})}
|
||||||
|
</h2>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{t("configSwitcher.otherConfigsHelper", {
|
||||||
|
defaultValue:
|
||||||
|
"Use these for experiments, different providers, or project-specific setups.",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{otherStores.length === 0 ? (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("configSwitcher.noOtherConfigs", {
|
||||||
|
defaultValue: "You only have the active configuration right now.",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{otherStores.map((store) => (
|
||||||
|
<div
|
||||||
|
key={store.id}
|
||||||
|
className="flex flex-col justify-between rounded-xl border bg-card p-3 transition-colors hover:border-primary/60"
|
||||||
|
>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<h3 className="truncate text-sm font-medium">
|
||||||
|
{store.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
{store.settings.env?.ANTHROPIC_BASE_URL && (
|
||||||
|
<p
|
||||||
|
className="truncate text-xs text-muted-foreground"
|
||||||
|
title={store.settings.env.ANTHROPIC_BASE_URL}
|
||||||
|
>
|
||||||
|
{store.settings.env.ANTHROPIC_BASE_URL}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-3 flex items-center justify-between gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 px-2 text-xs"
|
||||||
|
onClick={() => navigate(`/edit/${store.id}`)}
|
||||||
|
>
|
||||||
|
<PencilLineIcon size={12} className="mr-1" />
|
||||||
|
{t("configSwitcher.edit", { defaultValue: "Edit" })}
|
||||||
|
</Button>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 px-2 text-xs"
|
||||||
|
onClick={() =>
|
||||||
|
handleStoreClick(store.id, store.using)
|
||||||
|
}
|
||||||
|
disabled={setCurrentStoreMutation.isPending}
|
||||||
|
>
|
||||||
|
{t("configSwitcher.activate", {
|
||||||
|
defaultValue: "Activate",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 px-2 text-xs"
|
||||||
|
onClick={() =>
|
||||||
|
createStoreMutation.mutate({
|
||||||
|
title: `${store.title} (copy)`,
|
||||||
|
settings: store.settings,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={createStoreMutation.isPending}
|
||||||
|
>
|
||||||
|
{t("configSwitcher.duplicate", {
|
||||||
|
defaultValue: "Duplicate",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 px-2 text-xs"
|
||||||
|
disabled={deleteStoreMutation.isPending}
|
||||||
|
>
|
||||||
|
{t("configSwitcher.delete", {
|
||||||
|
defaultValue: "Delete",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
{t("configSwitcher.deleteConfirmTitle", {
|
||||||
|
defaultValue: "Delete configuration?",
|
||||||
|
})}
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{t(
|
||||||
|
"configSwitcher.deleteConfirmDescription",
|
||||||
|
{
|
||||||
|
defaultValue:
|
||||||
|
"This will permanently remove this configuration from Claude Samurai. This action cannot be undone.",
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel disabled={deleteStoreMutation.isPending}>
|
||||||
|
{t("common.cancel", {
|
||||||
|
defaultValue: "Cancel",
|
||||||
|
})}
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() =>
|
||||||
|
deleteStoreMutation.mutate({
|
||||||
|
storeId: store.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
disabled={deleteStoreMutation.isPending}
|
||||||
|
>
|
||||||
|
{t("configSwitcher.deleteConfirmAction", {
|
||||||
|
defaultValue: "Delete",
|
||||||
|
})}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user