This commit is contained in:
bwnyasse
2026-01-10 21:22:35 -05:00
parent 90455d9181
commit d43a14ee0c
116 changed files with 41323 additions and 19 deletions

View File

@@ -0,0 +1,38 @@
const ApiResponse = ({ response, error, loading }) => {
if (loading) {
return (
<div className="mt-4">
<h3 className="text-lg font-semibold text-slate-800 mb-2">Response</h3>
<div className="bg-slate-100 border border-slate-200 rounded-lg p-4">
<p className="text-slate-500">Loading...</p>
</div>
</div>
);
}
if (error) {
return (
<div className="mt-4">
<h3 className="text-lg font-semibold text-red-800 mb-2">Error</h3>
<pre className="bg-red-50 border border-red-200 text-red-700 rounded-lg p-4 text-sm whitespace-pre-wrap">
{JSON.stringify(error, null, 2)}
</pre>
</div>
);
}
if (response) {
return (
<div className="mt-4">
<h3 className="text-lg font-semibold text-slate-800 mb-2">Response</h3>
<pre className="bg-slate-100 border border-slate-200 rounded-lg p-4 text-sm whitespace-pre-wrap">
{JSON.stringify(response, null, 2)}
</pre>
</div>
);
}
return null;
};
export default ApiResponse;

View File

@@ -0,0 +1,125 @@
import { Link, Navigate, Outlet, useLocation } from "react-router-dom";
import { useAuthState } from "react-firebase-hooks/auth";
import { auth } from "../firebase";
import { krowSDK } from "@/api/krowSDK";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { ChevronDown } from "lucide-react";
import { useState } from "react";
const navLinks = {
auth: [
{ path: "/auth/me", title: "Get Me" },
{ path: "/auth/update-me", title: "Update Me" },
],
core: [
{ path: "/core/send-email", title: "Send Email" },
{ path: "/core/invoke-llm", title: "Invoke LLM" },
{ path: "/core/upload-file", title: "Upload File" },
{ path: "/core/upload-private-file", title: "Upload Private File" },
{ path: "/core/create-signed-url", title: "Create Signed URL" },
{ path: "/core/extract-data", title: "Extract Data from File" },
{ path: "/core/generate-image", title: "Generate Image" },
],
entities: [
{ path: "/entities", title: "Entity API Tester" }
]
};
const NavSection = ({ title, links }) => {
const location = useLocation();
const [isOpen, setIsOpen] = useState(true);
const navLinkClasses = (path) =>
`flex items-center px-3 py-2 text-sm font-medium rounded-md transition-colors ${
location.pathname === path
? "bg-slate-200 text-slate-900"
: "text-slate-600 hover:bg-slate-100"
}`;
return (
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
<CollapsibleTrigger className="w-full">
<div className="flex items-center justify-between px-3 py-2">
<h2 className="text-xs font-semibold text-slate-400 uppercase tracking-wider">
{title}
</h2>
<ChevronDown className={`w-4 h-4 text-slate-400 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
</div>
</CollapsibleTrigger>
<CollapsibleContent className="space-y-1">
{links.map((api) => (
<Link key={api.path} to={api.path} className={navLinkClasses(api.path)}>
{api.title}
</Link>
))}
</CollapsibleContent>
</Collapsible>
);
}
const Layout = () => {
const [user, loading] = useAuthState(auth);
const location = useLocation();
if (loading) {
return (
<div className="flex items-center justify-center h-screen">
<div>Loading...</div>
</div>
);
}
if (!user) {
return <Navigate to="/login" />;
}
const handleLogout = () => {
krowSDK.auth.logout();
};
return (
<div className="flex h-screen bg-slate-50">
{/* Sidebar */}
<aside className="w-72 bg-white border-r border-slate-200 flex flex-col">
<div className="p-6 border-b border-slate-200">
<div className="flex items-center space-x-3">
<img src="/logo.svg" alt="Krow Logo" className="h-12 w-12" />
<div>
<h1 className="text-xl font-bold text-slate-900">KROW</h1>
<p className="text-xs text-slate-500">API Test Harness ({import.meta.env.VITE_HARNESS_ENVIRONMENT})</p>
</div>
</div>
</div>
<nav className="flex-1 overflow-y-auto p-4 space-y-2">
<NavSection title="Auth" links={navLinks.auth} />
<NavSection title="Core Integrations" links={navLinks.core} />
<NavSection title="Entities" links={navLinks.entities} />
</nav>
<div className="p-4 border-t border-slate-200">
<div className="text-sm text-slate-600 truncate mb-2" title={user.email}>{user.email}</div>
<Button onClick={handleLogout} className="w-full">
Logout
</Button>
</div>
</aside>
{/* Main Content */}
<main className="flex-1 overflow-y-auto">
<div className="p-8">
<Outlet />
</div>
</main>
</div>
);
};
export default Layout;

View File

@@ -0,0 +1,120 @@
import { useState } from "react";
import apiClient from "../api/client";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
const ServiceTester = ({ serviceName, serviceDescription, endpoint, fields }) => {
const [formData, setFormData] = useState({});
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const handleChange = (e) => {
const { name, value, files } = e.target;
if (files) {
setFormData({ ...formData, [name]: files[0] });
} else {
setFormData({ ...formData, [name]: value });
}
};
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
setResponse(null);
const data = new FormData();
for (const key in formData) {
data.append(key, formData[key]);
}
try {
const res = await apiClient.post(endpoint, data);
setResponse(res.data);
} catch (err) {
setError(err.response?.data || err.message);
} finally {
setLoading(false);
}
};
return (
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold text-slate-900">{serviceName}</h1>
<p className="text-slate-600 mt-1">{serviceDescription}</p>
</div>
<form onSubmit={handleSubmit}>
<Card>
<CardHeader>
<CardTitle>Parameters</CardTitle>
<CardDescription>
Fill in the required parameters to execute the service.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{fields.map((field) => (
<div key={field.name} className="grid w-full max-w-sm items-center gap-1.5">
<Label htmlFor={field.name}>{field.label}</Label>
{field.type === "textarea" ? (
<Textarea
id={field.name}
name={field.name}
onChange={handleChange}
placeholder={field.placeholder}
/>
) : (
<Input
id={field.name}
name={field.name}
type={field.type}
onChange={handleChange}
placeholder={field.placeholder}
/>
)}
</div>
))}
</CardContent>
<CardFooter>
<Button type="submit" disabled={loading}>
{loading ? "Executing..." : "Execute"}
</Button>
</CardFooter>
</Card>
</form>
{response && (
<div className="mt-8">
<h2 className="text-xl font-bold text-slate-900 mb-2">Response</h2>
<Card>
<CardContent className="p-4">
<pre className="text-sm overflow-x-auto bg-slate-900 text-white p-4 rounded-md">
{JSON.stringify(response, null, 2)}
</pre>
</CardContent>
</Card>
</div>
)}
{error && (
<div className="mt-8">
<h2 className="text-xl font-bold text-red-600 mb-2">Error</h2>
<Card className="border-red-500">
<CardContent className="p-4">
<pre className="text-sm overflow-x-auto bg-red-50 text-red-800 p-4 rounded-md">
{JSON.stringify(error, null, 2)}
</pre>
</CardContent>
</Card>
</div>
)}
</div>
);
};
export default ServiceTester;

View File

@@ -0,0 +1,48 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props} />
);
})
Button.displayName = "Button"
export { Button, buttonVariants }

View File

@@ -0,0 +1,50 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
{...props} />
))
Card.displayName = "Card"
const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props} />
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props} />
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props} />
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props} />
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -0,0 +1,11 @@
"use client"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
const Collapsible = CollapsiblePrimitive.Root
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
export { Collapsible, CollapsibleTrigger, CollapsibleContent }

View File

@@ -0,0 +1,19 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Input = React.forwardRef(({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props} />
);
})
Input.displayName = "Input"
export { Input }

View File

@@ -0,0 +1,16 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva } from "class-variance-authority";
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef(({ className, ...props }, ref) => (
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View File

@@ -0,0 +1,105 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover 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",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
}

View File

@@ -0,0 +1,18 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Textarea = React.forwardRef(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
ref={ref}
{...props} />
);
})
Textarea.displayName = "Textarea"
export { Textarea }