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,13 @@
const ApiPlaceholder = ({ title }) => {
return (
<div>
<h1 className="text-3xl font-bold text-slate-900 mb-4">{title}</h1>
<div className="bg-slate-100 border border-slate-200 rounded-lg p-8 text-center">
<p className="text-slate-500">This page is a placeholder for the "{title}" API test harness.</p>
<p className="text-slate-500 mt-2">Implementation is pending.</p>
</div>
</div>
);
};
export default ApiPlaceholder;

View File

@@ -0,0 +1,190 @@
import { useState, useMemo } from "react";
import { krowSDK } from "@/api/krowSDK";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import ApiResponse from "@/components/ApiResponse";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
const entityNames = Object.keys(krowSDK.entities).sort();
const getPrettifiedJSON = (entity, method) => {
// Basic placeholder payloads. A more advanced SDK could provide detailed examples.
const payloads = {
get: { id: "some-id" },
create: { data: { property: "value" } },
update: { id: "some-id", data: { property: "new-value" } },
delete: { id: "some-id" },
filter: { where: { property: { _eq: "value" } } },
list: {}
};
return JSON.stringify(payloads[method] || {}, null, 2);
};
const EntityTester = () => {
const [selectedEntity, setSelectedEntity] = useState(null);
const [selectedMethod, setSelectedMethod] = useState(null);
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [jsonInput, setJsonInput] = useState("");
const [jsonError, setJsonError] = useState(null);
const availableMethods = useMemo(() => {
if (!selectedEntity) return [];
return Object.keys(krowSDK.entities[selectedEntity]);
}, [selectedEntity]);
const handleEntityChange = (entity) => {
setSelectedEntity(entity);
setSelectedMethod(null);
setJsonInput("");
setJsonError(null);
setResponse(null);
setError(null);
};
const handleMethodSelect = (method) => {
setSelectedMethod(method);
setJsonInput(getPrettifiedJSON(selectedEntity, method));
setJsonError(null);
setResponse(null);
setError(null);
};
const handleJsonInputChange = (e) => {
setJsonInput(e.target.value);
try {
JSON.parse(e.target.value);
setJsonError(null);
} catch (err) {
setJsonError("Invalid JSON format");
}
};
const executeApi = async () => {
if (!selectedEntity || !selectedMethod || jsonError) return;
setLoading(true);
setResponse(null);
setError(null);
try {
const params = JSON.parse(jsonInput);
const sdkMethod = krowSDK.entities[selectedEntity][selectedMethod];
const res = await sdkMethod(params);
setResponse(res);
} catch (err) {
setError(err.response?.data || err.message);
} finally {
setLoading(false);
}
};
const renderMethodForm = () => {
if (!selectedMethod) {
return (
<div className="mt-4 p-4 text-center text-slate-500 bg-slate-50 rounded-lg">
<p>Select a method to begin.</p>
</div>
);
}
return (
<div className="space-y-4 mt-6">
<h3 className="text-lg font-semibold text-slate-800">
Parameters for <code className="bg-slate-100 p-1 rounded text-sm">/{selectedEntity}/{selectedMethod}</code>
</h3>
{/*
This is a textarea for JSON input. A more advanced implementation could
dynamically generate a form based on the expected parameters of each
SDK method, but that requires metadata about each method's signature
which is not currently available in the mock client.
*/}
<div className="space-y-2">
<Label htmlFor="params">JSON Payload</Label>
<Textarea
id="params"
name="params"
value={jsonInput}
onChange={handleJsonInputChange}
rows={8}
className={`font-mono text-sm ${jsonError ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
/>
{jsonError && <p className="text-xs text-red-600">{jsonError}</p>}
</div>
<Button onClick={executeApi} disabled={loading || !!jsonError}>
{loading ? "Executing..." : "Execute"}
</Button>
</div>
);
};
return (
<div>
<header className="mb-8">
<h1 className="text-3xl font-bold text-slate-900">Entity API Tester</h1>
<p className="text-slate-600 mt-1">
Select an entity and method, provide the required parameters in JSON format, and execute the API call.
</p>
</header>
<Card className="shadow-sm">
<CardHeader>
<CardTitle>API Configuration</CardTitle>
<CardDescription>Choose a Base44 entity and method to interact with.</CardDescription>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 items-start">
<div className="col-span-1">
<Label>Entity</Label>
<Select onValueChange={handleEntityChange} value={selectedEntity || ""}>
<SelectTrigger>
<SelectValue placeholder="Select an entity" />
</SelectTrigger>
<SelectContent>
{entityNames.map(entity => (
<SelectItem key={entity} value={entity}>{entity}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{selectedEntity && (
<div className="col-span-2">
<Label>Method</Label>
<div className="flex flex-wrap items-center gap-2 pt-2">
{availableMethods.map(method => (
<Button
key={method}
variant={selectedMethod === method ? "default" : "outline"}
size="sm"
onClick={() => handleMethodSelect(method)}
>
{method}
</Button>
))}
</div>
</div>
)}
</div>
{renderMethodForm()}
</CardContent>
</Card>
<ApiResponse response={response} error={error} loading={loading} />
</div>
);
};
export default EntityTester;

View File

@@ -0,0 +1,28 @@
import ServiceTester from "@/components/ServiceTester";
const GenerateImage = () => {
const fields = [
{
name: "prompt",
label: "Prompt",
type: "textarea",
placeholder: "Enter a prompt for the image",
},
{
name: "file",
label: "File",
type: "file",
},
];
return (
<ServiceTester
serviceName="Generate Image"
serviceDescription="Test the Generate Image service"
endpoint="/generate-image"
fields={fields}
/>
);
};
export default GenerateImage;

View File

@@ -0,0 +1,31 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
const Home = () => {
return (
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold text-slate-900">Welcome to KROW API Test Harness</h1>
<p className="text-slate-600 mt-1">
Your dedicated tool for rapid and authenticated testing of KROW backend services.
</p>
</div>
<Card>
<CardHeader>
<CardTitle>Get Started</CardTitle>
<CardDescription>
Use the sidebar navigation to select an API category and then choose a specific endpoint to test.
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-slate-700">
This tool automatically handles Firebase authentication, injecting the necessary ID tokens into your API requests.
Simply log in, select an API, provide the required parameters, and execute to see the raw JSON response.
</p>
</CardContent>
</Card>
</div>
);
};
export default Home;

View File

@@ -0,0 +1,37 @@
import { GoogleAuthProvider, signInWithPopup, setPersistence, browserLocalPersistence } from "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";
import { Navigate } from "react-router-dom";
import { auth } from "../firebase";
import { Button } from "@/components/ui/button";
const Login = () => {
const [user, loading] = useAuthState(auth);
const handleGoogleLogin = async () => {
const provider = new GoogleAuthProvider();
try {
await setPersistence(auth, browserLocalPersistence);
await signInWithPopup(auth, provider);
} catch (error) {
console.error("Error signing in with Google", error);
}
};
if (loading) {
return <div>Loading...</div>;
}
// If user is logged in, redirect to the home page
if (user) {
return <Navigate to="/" />;
}
// If no user, show the login button
return (
<div className="flex items-center justify-center h-screen">
<Button onClick={handleGoogleLogin}>Sign in with Google</Button>
</div>
);
};
export default Login;

View File

@@ -0,0 +1,49 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import ApiResponse from "@/components/ApiResponse";
import { krowSDK } from "@/api/krowSDK";
const GetMe = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const handleGetMe = async () => {
setLoading(true);
setError(null);
setResponse(null);
try {
const res = await krowSDK.auth.me();
setResponse(res);
} catch (err) {
setError(err.response?.data || err.message);
} finally {
setLoading(false);
}
};
return (
<div>
<h1 className="text-3xl font-bold text-slate-900 mb-4">Get Me</h1>
<p className="text-slate-600 mb-6">Fetches the currently authenticated user's profile.</p>
<Card className="max-w-2xl">
<CardHeader>
<CardTitle>Test `/auth/me`</CardTitle>
</CardHeader>
<CardContent>
<Button onClick={handleGetMe} disabled={loading}>
{loading ? "Loading..." : "Execute"}
</Button>
</CardContent>
</Card>
<ApiResponse response={response} error={error} loading={loading} />
</div>
);
};
// We need to re-import Card components because they are not globally available.
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
export default GetMe;

View File

@@ -0,0 +1,74 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import ApiResponse from "@/components/ApiResponse";
import { krowSDK } from "@/api/krowSDK";
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from "@/components/ui/card";
const CreateSignedUrl = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
file_uri: "gs://your-bucket/private-file.pdf",
expires_in: 3600,
});
const handleChange = (e) => {
const { id, value } = e.target;
setFormData(prev => ({ ...prev, [id]: value }));
};
const handleCreateUrl = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
setResponse(null);
try {
const params = {
...formData,
expires_in: parseInt(formData.expires_in, 10),
};
const res = await krowSDK.integrations.Core.CreateFileSignedUrl(params);
setResponse(res);
} catch (err) {
setError(err.response?.data || err.message);
} finally {
setLoading(false);
}
};
return (
<div>
<h1 className="text-3xl font-bold text-slate-900 mb-4">Create Signed URL</h1>
<p className="text-slate-600 mb-6">Tests the `integrations.Core.CreateFileSignedUrl` endpoint.</p>
<Card className="max-w-2xl">
<CardHeader>
<CardTitle>Test `/createSignedUrl`</CardTitle>
<CardDescription>Creates a temporary access URL for a private file stored in GCS.</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleCreateUrl} className="space-y-4">
<div>
<Label htmlFor="file_uri">File URI</Label>
<Input id="file_uri" value={formData.file_uri} onChange={handleChange} />
</div>
<div>
<Label htmlFor="expires_in">Expires In (seconds)</Label>
<Input id="expires_in" type="number" value={formData.expires_in} onChange={handleChange} />
</div>
<Button type="submit" disabled={loading}>
{loading ? "Creating..." : "Create Signed URL"}
</Button>
</form>
</CardContent>
</Card>
<ApiResponse response={response} error={error} loading={loading} />
</div>
);
};
export default CreateSignedUrl;

View File

@@ -0,0 +1,81 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import ApiResponse from "@/components/ApiResponse";
import { krowSDK } from "@/api/krowSDK";
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
const InvokeLLM = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
prompt: "Extract the total amount from the attached invoice.",
response_json_schema: JSON.stringify({ type: "object", properties: { total_amount: { type: "number" } } }, null, 2),
file_urls: "https://example.com/invoice.pdf",
});
const handleChange = (e) => {
const { id, value } = e.target;
setFormData(prev => ({ ...prev, [id]: value }));
};
const handleInvokeLLM = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
setResponse(null);
try {
const params = {
...formData,
response_json_schema: JSON.parse(formData.response_json_schema),
file_urls: formData.file_urls.split(',').map(url => url.trim()),
};
const res = await krowSDK.integrations.Core.InvokeLLM(params);
setResponse(res);
} catch (err) {
setError(err.response?.data || err.message);
} finally {
setLoading(false);
}
};
return (
<div>
<h1 className="text-3xl font-bold text-slate-900 mb-4">Invoke LLM</h1>
<p className="text-slate-600 mb-6">Tests the `integrations.Core.InvokeLLM` endpoint.</p>
<Card className="max-w-2xl">
<CardHeader>
<CardTitle>Test `/invokeLLM`</CardTitle>
<CardDescription>Calls a large language model (e.g., Vertex AI).</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleInvokeLLM} className="space-y-4">
<div>
<Label htmlFor="prompt">Prompt</Label>
<Textarea id="prompt" value={formData.prompt} onChange={handleChange} rows={4} />
</div>
<div>
<Label htmlFor="response_json_schema">Response JSON Schema</Label>
<Textarea id="response_json_schema" value={formData.response_json_schema} onChange={handleChange} rows={6} className="font-mono" />
</div>
<div>
<Label htmlFor="file_urls">File URLs (comma-separated)</Label>
<Input id="file_urls" value={formData.file_urls} onChange={handleChange} />
</div>
<Button type="submit" disabled={loading}>
{loading ? "Invoking..." : "Invoke LLM"}
</Button>
</form>
</CardContent>
</Card>
<ApiResponse response={response} error={error} loading={loading} />
</div>
);
};
export default InvokeLLM;

View File

@@ -0,0 +1,75 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import ApiResponse from "@/components/ApiResponse";
import { krowSDK } from "@/api/krowSDK";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
const SendEmail = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
to: "test@example.com",
subject: "Test Email from Harness",
body: "This is a test email.",
});
const handleChange = (e) => {
const { id, value } = e.target;
setFormData(prev => ({ ...prev, [id]: value }));
};
const handleSendEmail = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
setResponse(null);
try {
const res = await krowSDK.integrations.Core.SendEmail(formData);
setResponse(res);
} catch (err) {
setError(err.response?.data || err.message);
} finally {
setLoading(false);
}
};
return (
<div>
<h1 className="text-3xl font-bold text-slate-900 mb-4">Send Email</h1>
<p className="text-slate-600 mb-6">Tests the `integrations.Core.SendEmail` endpoint.</p>
<Card className="max-w-2xl">
<CardHeader>
<CardTitle>Test `/sendEmail`</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSendEmail} className="space-y-4">
<div>
<Label htmlFor="to">To</Label>
<Input id="to" type="email" value={formData.to} onChange={handleChange} />
</div>
<div>
<Label htmlFor="subject">Subject</Label>
<Input id="subject" value={formData.subject} onChange={handleChange} />
</div>
<div>
<Label htmlFor="body">Body</Label>
<Textarea id="body" value={formData.body} onChange={handleChange} rows={5} />
</div>
<Button type="submit" disabled={loading}>
{loading ? "Sending..." : "Send Email"}
</Button>
</form>
</CardContent>
</Card>
<ApiResponse response={response} error={error} loading={loading} />
</div>
);
};
export default SendEmail;

View File

@@ -0,0 +1,67 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import ApiResponse from "@/components/ApiResponse";
import { krowSDK } from "@/api/krowSDK";
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from "@/components/ui/card";
const UploadFile = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [file, setFile] = useState(null);
const handleFileChange = (e) => {
setFile(e.target.files[0]);
};
const handleUpload = async (e) => {
e.preventDefault();
if (!file) {
setError({ message: "Please select a file to upload." });
return;
}
setLoading(true);
setError(null);
setResponse(null);
try {
const res = await krowSDK.integrations.Core.UploadFile({ file });
setResponse(res);
} catch (err) {
setError(err.response?.data || err.message);
} finally {
setLoading(false);
}
};
return (
<div>
<h1 className="text-3xl font-bold text-slate-900 mb-4">Upload Public File</h1>
<p className="text-slate-600 mb-6">Tests the `integrations.Core.UploadFile` endpoint for public files.</p>
<Card className="max-w-2xl">
<CardHeader>
<CardTitle>Test `/uploadFile`</CardTitle>
<CardDescription>Handles multipart/form-data upload to GCS and returns a public URL.</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleUpload} className="space-y-4">
<div>
<Label htmlFor="file">File</Label>
<Input id="file" type="file" onChange={handleFileChange} />
</div>
<Button type="submit" disabled={loading || !file}>
{loading ? "Uploading..." : "Upload File"}
</Button>
</form>
</CardContent>
</Card>
<ApiResponse response={response} error={error} loading={loading} />
</div>
);
};
export default UploadFile;

View File

@@ -0,0 +1,67 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import ApiResponse from "@/components/ApiResponse";
import { krowSDK } from "@/api/krowSDK";
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from "@/components/ui/card";
const UploadPrivateFile = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const [file, setFile] = useState(null);
const handleFileChange = (e) => {
setFile(e.target.files[0]);
};
const handleUpload = async (e) => {
e.preventDefault();
if (!file) {
setError({ message: "Please select a file to upload." });
return;
}
setLoading(true);
setError(null);
setResponse(null);
try {
const res = await krowSDK.integrations.Core.UploadPrivateFile({ file });
setResponse(res);
} catch (err) {
setError(err.response?.data || err.message);
} finally {
setLoading(false);
}
};
return (
<div>
<h1 className="text-3xl font-bold text-slate-900 mb-4">Upload Private File</h1>
<p className="text-slate-600 mb-6">Tests the `integrations.Core.UploadPrivateFile` endpoint.</p>
<Card className="max-w-2xl">
<CardHeader>
<CardTitle>Test `/uploadPrivateFile`</CardTitle>
<CardDescription>Handles multipart/form-data upload to GCS and returns a secure gs:// URI.</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleUpload} className="space-y-4">
<div>
<Label htmlFor="file">File</Label>
<Input id="file" type="file" onChange={handleFileChange} />
</div>
<Button type="submit" disabled={loading || !file}>
{loading ? "Uploading..." : "Upload File"}
</Button>
</form>
</CardContent>
</Card>
<ApiResponse response={response} error={error} loading={loading} />
</div>
);
};
export default UploadPrivateFile;