feat(agents): Add GitHub agent import feature

- Implemented GitHub agent browser in CCAgents component
- Created GitHubAgentBrowser component for browsing and importing agents
- Added Rust commands for fetching and importing agents from GitHub
- Updated API layer with GitHub agent import methods
- Updated Cargo.toml with new dependencies
- Fixed Tauri configuration and capabilities
- Added dropdown menu for import options
- Implemented search and preview functionality for GitHub agents
This commit is contained in:
Vivek R
2025-06-24 00:00:18 +05:30
parent 5a29f9ae01
commit c85caa7a47
12 changed files with 1508 additions and 658 deletions

View File

@@ -17,10 +17,19 @@ import {
ArrowLeft,
History,
Download,
Upload
Upload,
Globe,
FileJson,
ChevronDown
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { api, type Agent, type AgentRunWithMetrics } from "@/lib/api";
import { save, open } from "@tauri-apps/plugin-dialog";
import { invoke } from "@tauri-apps/api/core";
@@ -31,6 +40,7 @@ import { AgentExecution } from "./AgentExecution";
import { AgentRunsList } from "./AgentRunsList";
import { AgentRunView } from "./AgentRunView";
import { RunningSessionsView } from "./RunningSessionsView";
import { GitHubAgentBrowser } from "./GitHubAgentBrowser";
interface CCAgentsProps {
/**
@@ -76,6 +86,7 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
const [activeTab, setActiveTab] = useState<"agents" | "running">("agents");
const [selectedAgent, setSelectedAgent] = useState<Agent | null>(null);
const [selectedRunId, setSelectedRunId] = useState<number | null>(null);
const [showGitHubBrowser, setShowGitHubBrowser] = useState(false);
const AGENTS_PER_PAGE = 9; // 3x3 grid
@@ -294,15 +305,29 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
</div>
</div>
<div className="flex items-center gap-2">
<Button
onClick={handleImportAgent}
size="default"
variant="outline"
className="flex items-center gap-2"
>
<Download className="h-4 w-4" />
Import
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="default"
variant="outline"
className="flex items-center gap-2"
>
<Download className="h-4 w-4" />
Import
<ChevronDown className="h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={handleImportAgent}>
<FileJson className="h-4 w-4 mr-2" />
From File
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setShowGitHubBrowser(true)}>
<Globe className="h-4 w-4 mr-2" />
From GitHub
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Button
onClick={() => setView("create")}
size="default"
@@ -535,6 +560,17 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
/>
)}
</ToastContainer>
{/* GitHub Agent Browser */}
<GitHubAgentBrowser
isOpen={showGitHubBrowser}
onClose={() => setShowGitHubBrowser(false)}
onImportSuccess={async () => {
setShowGitHubBrowser(false);
await loadAgents();
setToast({ message: "Agent imported successfully from GitHub", type: "success" });
}}
/>
</div>
);
};

View File

@@ -0,0 +1,365 @@
import React, { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
Search,
Download,
X,
Loader2,
AlertCircle,
Eye,
Check,
Globe,
FileJson,
} from "lucide-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { api, type GitHubAgentFile, type AgentExport } from "@/lib/api";
import { AGENT_ICONS, type AgentIconName } from "./CCAgents";
import { cn } from "@/lib/utils";
interface GitHubAgentBrowserProps {
isOpen: boolean;
onClose: () => void;
onImportSuccess: () => void;
}
interface AgentPreview {
file: GitHubAgentFile;
data: AgentExport | null;
loading: boolean;
error: string | null;
}
export const GitHubAgentBrowser: React.FC<GitHubAgentBrowserProps> = ({
isOpen,
onClose,
onImportSuccess,
}) => {
const [agents, setAgents] = useState<GitHubAgentFile[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState("");
const [selectedAgent, setSelectedAgent] = useState<AgentPreview | null>(null);
const [importing, setImporting] = useState(false);
const [importedAgents, setImportedAgents] = useState<Set<string>>(new Set());
useEffect(() => {
if (isOpen) {
fetchAgents();
}
}, [isOpen]);
const fetchAgents = async () => {
try {
setLoading(true);
setError(null);
const agentFiles = await api.fetchGitHubAgents();
setAgents(agentFiles);
} catch (err) {
console.error("Failed to fetch GitHub agents:", err);
setError("Failed to fetch agents from GitHub. Please check your internet connection.");
} finally {
setLoading(false);
}
};
const handlePreviewAgent = async (file: GitHubAgentFile) => {
setSelectedAgent({
file,
data: null,
loading: true,
error: null,
});
try {
const agentData = await api.fetchGitHubAgentContent(file.download_url);
setSelectedAgent({
file,
data: agentData,
loading: false,
error: null,
});
} catch (err) {
console.error("Failed to fetch agent content:", err);
setSelectedAgent({
file,
data: null,
loading: false,
error: "Failed to load agent details",
});
}
};
const handleImportAgent = async () => {
if (!selectedAgent?.file) return;
try {
setImporting(true);
await api.importAgentFromGitHub(selectedAgent.file.download_url);
// Mark as imported
setImportedAgents(prev => new Set(prev).add(selectedAgent.file.name));
// Close preview
setSelectedAgent(null);
// Notify parent
onImportSuccess();
} catch (err) {
console.error("Failed to import agent:", err);
alert(`Failed to import agent: ${err instanceof Error ? err.message : "Unknown error"}`);
} finally {
setImporting(false);
}
};
const filteredAgents = agents.filter(agent =>
agent.name.toLowerCase().includes(searchQuery.toLowerCase())
);
const getAgentDisplayName = (fileName: string) => {
return fileName.replace(".claudia.json", "").replace(/-/g, " ")
.split(" ")
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
};
const renderIcon = (iconName: string) => {
const Icon = AGENT_ICONS[iconName as AgentIconName] || AGENT_ICONS.bot;
return <Icon className="h-8 w-8" />;
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-4xl max-h-[80vh] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Globe className="h-5 w-5" />
Import Agent from GitHub
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-hidden flex flex-col">
{/* Search Bar */}
<div className="mb-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search agents..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto">
{loading ? (
<div className="flex items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : error ? (
<div className="flex flex-col items-center justify-center h-64 text-center">
<AlertCircle className="h-12 w-12 text-destructive mb-4" />
<p className="text-sm text-muted-foreground mb-4">{error}</p>
<Button onClick={fetchAgents} variant="outline" size="sm">
Try Again
</Button>
</div>
) : filteredAgents.length === 0 ? (
<div className="flex flex-col items-center justify-center h-64 text-center">
<FileJson className="h-12 w-12 text-muted-foreground mb-4" />
<p className="text-sm text-muted-foreground">
{searchQuery ? "No agents found matching your search" : "No agents available"}
</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 pb-4">
<AnimatePresence mode="popLayout">
{filteredAgents.map((agent, index) => (
<motion.div
key={agent.sha}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.2, delay: index * 0.05 }}
>
<Card className="h-full hover:shadow-lg transition-shadow cursor-pointer"
onClick={() => handlePreviewAgent(agent)}>
<CardContent className="p-4">
<div className="flex items-start justify-between mb-3">
<h3 className="text-sm font-semibold line-clamp-2">
{getAgentDisplayName(agent.name)}
</h3>
{importedAgents.has(agent.name) && (
<Badge variant="secondary" className="ml-2">
<Check className="h-3 w-3 mr-1" />
Imported
</Badge>
)}
</div>
<p className="text-xs text-muted-foreground">
{(agent.size / 1024).toFixed(1)} KB
</p>
</CardContent>
<CardFooter className="p-4 pt-0">
<Button
size="sm"
variant="outline"
className="w-full"
onClick={(e) => {
e.stopPropagation();
handlePreviewAgent(agent);
}}
>
<Eye className="h-3 w-3 mr-2" />
Preview
</Button>
</CardFooter>
</Card>
</motion.div>
))}
</AnimatePresence>
</div>
)}
</div>
</div>
</DialogContent>
{/* Agent Preview Dialog */}
<AnimatePresence>
{selectedAgent && (
<Dialog open={!!selectedAgent} onOpenChange={() => setSelectedAgent(null)}>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-hidden flex flex-col">
<DialogHeader>
<DialogTitle className="flex items-center justify-between">
<span>Agent Preview</span>
<Button
variant="ghost"
size="icon"
onClick={() => setSelectedAgent(null)}
className="h-8 w-8"
>
<X className="h-4 w-4" />
</Button>
</DialogTitle>
</DialogHeader>
<div className="flex-1 overflow-y-auto">
{selectedAgent.loading ? (
<div className="flex items-center justify-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : selectedAgent.error ? (
<div className="flex flex-col items-center justify-center h-64 text-center">
<AlertCircle className="h-12 w-12 text-destructive mb-4" />
<p className="text-sm text-muted-foreground">{selectedAgent.error}</p>
</div>
) : selectedAgent.data ? (
<div className="space-y-4">
{/* Agent Info */}
<div className="flex items-start gap-4">
<div className="p-3 rounded-lg bg-primary/10 text-primary">
{renderIcon(selectedAgent.data.agent.icon)}
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold">
{selectedAgent.data.agent.name}
</h3>
<div className="flex items-center gap-2 mt-1">
<Badge variant="outline">{selectedAgent.data.agent.model}</Badge>
{selectedAgent.data.agent.sandbox_enabled && (
<Badge variant="secondary">Sandbox</Badge>
)}
</div>
</div>
</div>
{/* System Prompt */}
<div>
<h4 className="text-sm font-medium mb-2">System Prompt</h4>
<div className="bg-muted rounded-lg p-3 max-h-48 overflow-y-auto">
<pre className="text-xs whitespace-pre-wrap font-mono">
{selectedAgent.data.agent.system_prompt}
</pre>
</div>
</div>
{/* Default Task */}
{selectedAgent.data.agent.default_task && (
<div>
<h4 className="text-sm font-medium mb-2">Default Task</h4>
<div className="bg-muted rounded-lg p-3">
<p className="text-sm">{selectedAgent.data.agent.default_task}</p>
</div>
</div>
)}
{/* Permissions */}
<div>
<h4 className="text-sm font-medium mb-2">Permissions</h4>
<div className="flex flex-wrap gap-2">
<Badge variant={selectedAgent.data.agent.enable_file_read ? "default" : "secondary"}>
File Read: {selectedAgent.data.agent.enable_file_read ? "Yes" : "No"}
</Badge>
<Badge variant={selectedAgent.data.agent.enable_file_write ? "default" : "secondary"}>
File Write: {selectedAgent.data.agent.enable_file_write ? "Yes" : "No"}
</Badge>
<Badge variant={selectedAgent.data.agent.enable_network ? "default" : "secondary"}>
Network: {selectedAgent.data.agent.enable_network ? "Yes" : "No"}
</Badge>
</div>
</div>
{/* Metadata */}
<div className="text-xs text-muted-foreground">
<p>Version: {selectedAgent.data.version}</p>
<p>Exported: {new Date(selectedAgent.data.exported_at).toLocaleDateString()}</p>
</div>
</div>
) : null}
</div>
{/* Actions */}
{selectedAgent.data && (
<div className="flex justify-end gap-2 mt-4 pt-4 border-t">
<Button
variant="outline"
onClick={() => setSelectedAgent(null)}
>
Cancel
</Button>
<Button
onClick={handleImportAgent}
disabled={importing || importedAgents.has(selectedAgent.file.name)}
>
{importing ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Importing...
</>
) : importedAgents.has(selectedAgent.file.name) ? (
<>
<Check className="h-4 w-4 mr-2" />
Already Imported
</>
) : (
<>
<Download className="h-4 w-4 mr-2" />
Import Agent
</>
)}
</Button>
</div>
)}
</DialogContent>
</Dialog>
)}
</AnimatePresence>
</Dialog>
);
};

View File

@@ -0,0 +1,198 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View File

@@ -171,6 +171,30 @@ export interface Agent {
updated_at: string;
}
export interface AgentExport {
version: number;
exported_at: string;
agent: {
name: string;
icon: string;
system_prompt: string;
default_task?: string;
model: string;
sandbox_enabled: boolean;
enable_file_read: boolean;
enable_file_write: boolean;
enable_network: boolean;
};
}
export interface GitHubAgentFile {
name: string;
path: string;
download_url: string;
size: number;
sha: string;
}
export interface AgentRun {
id?: number;
agent_id: number;
@@ -456,15 +480,42 @@ export const api = {
},
/**
* Gets sessions for a specific project
* @param projectId - The project ID to get sessions for
* @returns Promise resolving to an array of sessions
* Fetch list of agents from GitHub repository
* @returns Promise resolving to list of available agents on GitHub
*/
async getProjectSessions(projectId: string): Promise<Session[]> {
async fetchGitHubAgents(): Promise<GitHubAgentFile[]> {
try {
return await invoke<Session[]>("get_project_sessions", { projectId });
return await invoke<GitHubAgentFile[]>('fetch_github_agents');
} catch (error) {
console.error("Failed to get project sessions:", error);
console.error("Failed to fetch GitHub agents:", error);
throw error;
}
},
/**
* Fetch and preview a specific agent from GitHub
* @param downloadUrl - The download URL for the agent file
* @returns Promise resolving to the agent export data
*/
async fetchGitHubAgentContent(downloadUrl: string): Promise<AgentExport> {
try {
return await invoke<AgentExport>('fetch_github_agent_content', { downloadUrl });
} catch (error) {
console.error("Failed to fetch GitHub agent content:", error);
throw error;
}
},
/**
* Import an agent directly from GitHub
* @param downloadUrl - The download URL for the agent file
* @returns Promise resolving to the imported agent
*/
async importAgentFromGitHub(downloadUrl: string): Promise<Agent> {
try {
return await invoke<Agent>('import_agent_from_github', { downloadUrl });
} catch (error) {
console.error("Failed to import agent from GitHub:", error);
throw error;
}
},