Add Kortix Admin API Key to setup

This commit is contained in:
marko-kraemer 2025-08-06 12:31:45 -07:00
parent 1d01571500
commit 0e8c58106a
3 changed files with 51 additions and 165 deletions

View File

@ -165,6 +165,17 @@ export default function AgentsPage() {
metadata: template.metadata,
};
// Apply search filtering to each item
const matchesSearch = !marketplaceSearchQuery.trim() || (() => {
const searchLower = marketplaceSearchQuery.toLowerCase();
return item.name.toLowerCase().includes(searchLower) ||
item.description?.toLowerCase().includes(searchLower) ||
item.tags.some(tag => tag.toLowerCase().includes(searchLower)) ||
item.creator_name?.toLowerCase().includes(searchLower);
})();
if (!matchesSearch) return; // Skip items that don't match search
// Always add user's own templates to mineItems for the "mine" filter
if (user?.id === template.creator_id) {
mineItems.push(item);
@ -201,7 +212,7 @@ export default function AgentsPage() {
communityItems: sortItems(communityItems),
mineItems: sortItems(mineItems)
};
}, [marketplaceTemplates, marketplaceSortBy, user?.id]);
}, [marketplaceTemplates, marketplaceSortBy, user?.id, marketplaceSearchQuery]);
const allMarketplaceItems = useMemo(() => {
if (marketplaceFilter === 'kortix') {

View File

@ -1,163 +0,0 @@
import React from 'react';
import { Search, Filter, SortAsc, SortDesc, X, Settings, Wrench, Grid3X3, List } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, DropdownMenuCheckboxItem } from '@/components/ui/dropdown-menu';
type AgentSortOption = 'name' | 'created_at' | 'updated_at' | 'tools_count';
type SortOrder = 'asc' | 'desc';
type ViewMode = 'grid' | 'list';
interface FilterOptions {
hasDefaultAgent: boolean;
hasMcpTools: boolean;
hasAgentpressTools: boolean;
selectedTools: string[];
}
interface SearchAndFiltersProps {
searchQuery: string;
setSearchQuery: (query: string) => void;
sortBy: AgentSortOption;
setSortBy: (sort: AgentSortOption) => void;
sortOrder: SortOrder;
setSortOrder: (order: SortOrder) => void;
filters: FilterOptions;
setFilters: React.Dispatch<React.SetStateAction<FilterOptions>>;
activeFiltersCount: number;
clearFilters: () => void;
viewMode: ViewMode;
setViewMode: (mode: ViewMode) => void;
allTools: string[];
}
export const SearchAndFilters = ({
searchQuery,
setSearchQuery,
sortBy,
setSortBy,
sortOrder,
setSortOrder,
filters,
setFilters,
activeFiltersCount,
clearFilters,
viewMode,
setViewMode,
allTools
}: SearchAndFiltersProps) => {
return (
<div className="flex flex-col w-full gap-4 lg:flex-row lg:items-center lg:justify-between">
<div className="flex flex-col w-full gap-3 sm:flex-row sm:items-center flex-1">
<div className="relative flex-1 w-full border rounded-xl">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="Search agents..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
{searchQuery && (
<Button
variant="ghost"
size="sm"
className="absolute right-1 top-1/2 h-7 w-7 -translate-y-1/2 p-0"
onClick={() => setSearchQuery('')}
>
<X className="h-3 w-3" />
</Button>
)}
</div>
{/* <Select value={sortBy} onValueChange={(value: SortOption) => setSortBy(value)}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Sort by" />
</SelectTrigger>
<SelectContent>
<SelectItem value="name">Name</SelectItem>
<SelectItem value="created_at">Created Date</SelectItem>
<SelectItem value="updated_at">Updated Date</SelectItem>
<SelectItem value="tools_count">Tools Count</SelectItem>
</SelectContent>
</Select>
<Button
variant="outline"
size="sm"
onClick={() => setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')}
className="px-3"
>
{sortOrder === 'asc' ? <SortAsc className="h-4 w-4" /> : <SortDesc className="h-4 w-4" />}
</Button> */}
</div>
<div className="flex items-center gap-2">
{/* <DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="relative">
<Filter className="h-4 w-4" />
Filter
{activeFiltersCount > 0 && (
<Badge variant="secondary" className="ml-2 h-5 w-5 rounded-full p-0 text-xs">
{activeFiltersCount}
</Badge>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel>Filter by</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem
checked={filters.hasMcpTools}
onCheckedChange={(checked) =>
setFilters(prev => ({ ...prev, hasMcpTools: checked }))
}
>
<Wrench className="h-4 w-4" />
Has MCP tools
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={filters.hasAgentpressTools}
onCheckedChange={(checked) =>
setFilters(prev => ({ ...prev, hasAgentpressTools: checked }))
}
>
<Settings className="h-4 w-4" />
Has Default tools
</DropdownMenuCheckboxItem>
{activeFiltersCount > 0 && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={clearFilters}>
<X className="h-4 w-4" />
Clear filters
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu> */}
{/* <div className="flex border rounded-md">
<Button
variant={viewMode === 'grid' ? 'default' : 'ghost'}
size="sm"
onClick={() => setViewMode('grid')}
className="rounded-r-none border-r"
>
<Grid3X3 className="h-4 w-4" />
</Button>
<Button
variant={viewMode === 'list' ? 'default' : 'ghost'}
size="sm"
onClick={() => setViewMode('list')}
className="rounded-l-none"
>
<List className="h-4 w-4" />
</Button>
</div> */}
</div>
</div>
);
}

View File

@ -168,6 +168,9 @@ def load_existing_env_vars():
"PIPEDREAM_CLIENT_SECRET": backend_env.get("PIPEDREAM_CLIENT_SECRET", ""),
"PIPEDREAM_X_PD_ENVIRONMENT": backend_env.get("PIPEDREAM_X_PD_ENVIRONMENT", ""),
},
"kortix": {
"KORTIX_ADMIN_API_KEY": backend_env.get("KORTIX_ADMIN_API_KEY", ""),
},
"frontend": {
"NEXT_PUBLIC_SUPABASE_URL": frontend_env.get(
"NEXT_PUBLIC_SUPABASE_URL", ""
@ -241,6 +244,13 @@ def generate_encryption_key():
return base64.b64encode(key_bytes).decode("utf-8")
def generate_admin_api_key():
"""Generates a secure admin API key for Kortix."""
# Generate 32 random bytes and encode as hex for a readable API key
key_bytes = secrets.token_bytes(32)
return key_bytes.hex()
# --- Main Setup Class ---
class SetupWizard:
def __init__(self):
@ -263,6 +273,7 @@ class SetupWizard:
"webhook": existing_env_vars["webhook"],
"mcp": existing_env_vars["mcp"],
"pipedream": existing_env_vars["pipedream"],
"kortix": existing_env_vars["kortix"],
}
# Override with any progress data (in case user is resuming)
@ -273,7 +284,7 @@ class SetupWizard:
else:
self.env_vars[key] = value
self.total_steps = 18
self.total_steps = 19
def show_current_config(self):
"""Shows the current configuration status."""
@ -359,6 +370,12 @@ class SetupWizard:
else:
config_items.append(f"{Colors.YELLOW}{Colors.ENDC} Morph (recommended)")
# Check Kortix configuration
if self.env_vars["kortix"]["KORTIX_ADMIN_API_KEY"]:
config_items.append(f"{Colors.GREEN}{Colors.ENDC} Kortix Admin")
else:
config_items.append(f"{Colors.YELLOW}{Colors.ENDC} Kortix Admin")
if any("" in item for item in config_items):
print_info("Current configuration status:")
for item in config_items:
@ -384,6 +401,7 @@ class SetupWizard:
self.run_step(6, self.collect_morph_api_key)
self.run_step(7, self.collect_search_api_keys)
self.run_step(8, self.collect_rapidapi_keys)
self.run_step(9, self.collect_kortix_keys)
self.run_step(10, self.collect_qstash_keys)
self.run_step(11, self.collect_mcp_keys)
self.run_step(12, self.collect_pipedream_keys)
@ -893,6 +911,24 @@ class SetupWizard:
else:
print_info("Skipping RapidAPI key.")
def collect_kortix_keys(self):
"""Generates or configures the Kortix admin API key."""
print_step(9, self.total_steps, "Configuring Kortix Admin API Key")
# Check if we already have a value configured
existing_key = self.env_vars["kortix"]["KORTIX_ADMIN_API_KEY"]
if existing_key:
print_info(
f"Found existing Kortix admin API key: {mask_sensitive_value(existing_key)}"
)
print_info("Using existing admin API key.")
else:
print_info("Generating a secure admin API key for Kortix administrative functions...")
self.env_vars["kortix"]["KORTIX_ADMIN_API_KEY"] = generate_admin_api_key()
print_success("Kortix admin API key generated.")
print_success("Kortix admin configuration saved.")
def collect_qstash_keys(self):
"""Collects the required QStash configuration."""
print_step(
@ -1129,6 +1165,7 @@ class SetupWizard:
**self.env_vars["mcp"],
**self.env_vars["pipedream"],
**self.env_vars["daytona"],
**self.env_vars["kortix"],
"NEXT_PUBLIC_URL": "http://localhost:3000",
}
@ -1149,6 +1186,7 @@ class SetupWizard:
"NEXT_PUBLIC_BACKEND_URL": "http://localhost:8000/api",
"NEXT_PUBLIC_URL": "http://localhost:3000",
"NEXT_PUBLIC_ENV_MODE": "LOCAL",
"KORTIX_ADMIN_API_KEY": self.env_vars["kortix"]["KORTIX_ADMIN_API_KEY"],
}
frontend_env_content = "# Generated by Suna install script\n\n"