feat: implement data fetching and error handling for DataPipelinePage, update PipelineCard component to use 'name' prop, and add API functions for pipeline management

This commit is contained in:
THIS ONE IS A LITTLE BIT TRICKY KRUB 2025-04-25 14:25:56 +07:00
parent 8b730e14ae
commit a687636b07
8 changed files with 498 additions and 256 deletions

View File

@ -1,13 +1,34 @@
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Plus } from "lucide-react";
import Link from "next/link";
"use client";
import PageHeader from "@/components/page-header";
import { PipelineCard } from "@/components/pipeline/card";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { listPipelines } from "@/lib/api/pipelines";
import { Pipeline } from "@/lib/api/pipelines/types";
import { Plus } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
export default function DataPipelinePage() {
const [pipelines, setPipelines] = useState<Pipeline[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchPipelines = async () => {
try {
const data = await listPipelines();
setPipelines(data);
} catch (err) {
console.error("Error fetching pipelines:", err);
setError("Failed to load pipelines");
}
};
fetchPipelines();
}, []);
return (
<div className="container mx-auto p-6">
{error && <p className="text-red-500">{error}</p>}
<PageHeader
title="Data Pipelines"
description="Manage your automated data collection pipelines"
@ -37,7 +58,7 @@ export default function DataPipelinePage() {
<TabsContent value="active" className="mt-4">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<PipelineCard
title="Property Listings"
name="Property Listings"
description="Scrapes real estate listings from multiple websites"
status="active"
lastRun="2 hours ago"
@ -48,7 +69,7 @@ export default function DataPipelinePage() {
/>
<PipelineCard
title="Rental Market Data"
name="Rental Market Data"
description="Collects rental prices and availability"
status="active"
lastRun="Yesterday"
@ -59,7 +80,7 @@ export default function DataPipelinePage() {
/>
<PipelineCard
title="Price Comparison"
name="Price Comparison"
description="Tracks property price changes over time"
status="error"
lastRun="2 days ago"
@ -74,7 +95,7 @@ export default function DataPipelinePage() {
<TabsContent value="paused" className="mt-4">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<PipelineCard
title="Commercial Properties"
name="Commercial Properties"
description="Collects data on commercial real estate"
status="paused"
lastRun="1 week ago"
@ -88,7 +109,7 @@ export default function DataPipelinePage() {
<TabsContent value="all" className="mt-4">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<PipelineCard
title="Property Listings"
name="Property Listings"
description="Scrapes real estate listings from multiple websites"
status="active"
lastRun="2 hours ago"
@ -100,7 +121,7 @@ export default function DataPipelinePage() {
{/* mock pipeline card with data */}
<PipelineCard
title="Rental Market Data"
name="Rental Market Data"
description="Collects rental prices and availability"
status="active"
lastRun="Yesterday"
@ -111,7 +132,7 @@ export default function DataPipelinePage() {
/>
<PipelineCard
title="Price Comparison"
name="Price Comparison"
description="Tracks property price changes over time"
status="error"
lastRun="2 days ago"
@ -122,7 +143,7 @@ export default function DataPipelinePage() {
/>
<PipelineCard
title="Commercial Properties"
name="Commercial Properties"
description="Collects data on commercial real estate"
status="paused"
lastRun="1 week ago"

View File

@ -8,7 +8,7 @@ import { PropertyInfoPanel } from "@/components/map/property-info-panel";
import MapWithSearch from "@/components/map/map-with-search";
import { TopNavigation } from "@/components/navigation/top-navigation";
import { Button } from "@/components/ui/button";
import { BarChart2, Filter, MapPin, MessageCircle } from "lucide-react";
import { BarChart2, Filter, MessageCircle } from "lucide-react";
import { useRef, useState } from "react";
import Draggable from "react-draggable";
@ -36,7 +36,7 @@ export default function MapsPage() {
</div>
{/* Sample Property Markers */}
<div
{/* <div
className="absolute left-1/4 top-1/3 text-primary cursor-pointer group"
onClick={handlePropertyClick}
>
@ -74,7 +74,7 @@ export default function MapsPage() {
Sold
</span>
</div>
</div>
</div> */}
</div>
{/* Top Navigation Bar */}
@ -91,7 +91,6 @@ export default function MapsPage() {
onClick={() => {
setShowAnalytics(!showAnalytics);
if (showAnalytics) {
setShowFilters(false);
setShowPropertyInfo(false);
}
}}
@ -107,7 +106,6 @@ export default function MapsPage() {
onClick={() => {
setShowFilters(!showFilters);
if (showFilters) {
setShowAnalytics(false);
setShowPropertyInfo(false);
}
}}

View File

@ -1,31 +1,32 @@
"use client";
import { useState } from "react";
// import { ModelCard } from "@/components/models/model-card";
import PageHeader from "@/components/page-header";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Badge } from "@/components/ui/badge";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Switch } from "@/components/ui/switch";
import { Progress } from "@/components/ui/progress";
import { Switch } from "@/components/ui/switch";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { useModelState } from "@/store/model-store";
import {
AlertTriangle,
BrainCircuit,
Clock,
Check,
Database,
Play,
Plus,
Settings,
Sliders,
Trash2,
AlertTriangle,
Check,
ArrowRight,
Info,
} from "lucide-react";
import Link from "next/link";
import PageHeader from "@/components/page-header";
import { useState } from "react";
import { useShallow } from "zustand/react/shallow";
export default function ModelsPage() {
const [activeTab, setActiveTab] = useState("my-models");
@ -34,85 +35,36 @@ export default function ModelsPage() {
const [isTraining, setIsTraining] = useState(false);
const [modelName, setModelName] = useState("");
const [modelDescription, setModelDescription] = useState("");
const { models } = useModelState(
useShallow((state) => ({
models: state.models,
}))
);
const dataPipelines = [
{ id: "pipeline-1", name: "Property Listings", records: 1240, lastUpdated: "2 hours ago" },
{ id: "pipeline-2", name: "Rental Market Data", records: 830, lastUpdated: "Yesterday" },
{ id: "pipeline-3", name: "Price Comparison", records: 1560, lastUpdated: "2 days ago" },
{ id: "pipeline-4", name: "Commercial Properties", records: 450, lastUpdated: "1 week ago" },
];
const models = [
{
id: "model-1",
name: "Standard ML Model v2.4",
type: "Regression",
hyperparameters: {
learningRate: "0.01",
maxDepth: "6",
numEstimators: "100",
},
dataSource: "System Base Model",
status: "active",
lastUpdated: "3 days ago",
isSystem: true,
id: "pipeline-1",
name: "Property Listings",
records: 1240,
lastUpdated: "2 hours ago",
},
{
id: "model-2",
name: "Enhanced Neural Network v1.8",
type: "Neural Network",
hyperparameters: {
layers: "4",
neurons: "128,64,32,16",
dropout: "0.2",
},
dataSource: "System Base Model",
status: "active",
id: "pipeline-2",
name: "Rental Market Data",
records: 830,
lastUpdated: "Yesterday",
},
{
id: "pipeline-3",
name: "Price Comparison",
records: 1560,
lastUpdated: "2 days ago",
},
{
id: "pipeline-4",
name: "Commercial Properties",
records: 450,
lastUpdated: "1 week ago",
isSystem: true,
},
{
id: "model-3",
name: "Geospatial Regression v3.1",
type: "Geospatial",
hyperparameters: {
spatialWeight: "0.7",
kernelType: "gaussian",
bandwidth: "adaptive",
},
dataSource: "System Base Model",
status: "active",
lastUpdated: "2 weeks ago",
isSystem: true,
},
{
id: "model-4",
name: "Time Series Forecast v2.0",
type: "Time Series",
hyperparameters: {
p: "2",
d: "1",
q: "2",
seasonal: "true",
},
dataSource: "System Base Model",
status: "active",
lastUpdated: "1 month ago",
isSystem: true,
},
{
id: "model-5",
name: "Custom Model (User #1242)",
type: "Ensemble",
hyperparameters: {
baseEstimators: "3",
votingMethod: "weighted",
weights: "0.4,0.4,0.2",
},
dataSource: "Property Listings Pipeline",
status: "active",
lastUpdated: "5 days ago",
isSystem: false,
},
];
@ -146,7 +98,11 @@ export default function ModelsPage() {
]}
/>
<Tabs defaultValue="my-models" className="mt-6" onValueChange={setActiveTab}>
<Tabs
defaultValue="my-models"
className="mt-6"
onValueChange={setActiveTab}
>
<div className="flex justify-between items-center mb-6">
<TabsList>
<TabsTrigger value="my-models">My Models</TabsTrigger>
@ -155,7 +111,10 @@ export default function ModelsPage() {
</TabsList>
{activeTab !== "train-model" && (
<Button onClick={() => setActiveTab("train-model")} className="gap-2">
<Button
onClick={() => setActiveTab("train-model")}
className="gap-2"
>
<Plus className="h-4 w-4" />
Train New Model
</Button>
@ -164,35 +123,34 @@ export default function ModelsPage() {
<TabsContent value="my-models">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{models
.filter((model) => !model.isSystem)
.map((model) => (
<ModelCard key={model.id} model={model} />
))}
{/* {models} */}
</div>
{models.filter((model) => !model.isSystem).length === 0 && (
{models && (
<Card className="border-dashed">
<CardContent className="pt-6 text-center">
<BrainCircuit className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<h3 className="text-lg font-medium mb-2">No Custom Models Yet</h3>
<h3 className="text-lg font-medium mb-2">
No Custom Models Yet
</h3>
<p className="text-sm text-muted-foreground mb-4">
Train your first custom model to get started with personalized property predictions.
Train your first custom model to get started with personalized
property predictions.
</p>
<Button onClick={() => setActiveTab("train-model")}>Train Your First Model</Button>
<Button onClick={() => setActiveTab("train-model")}>
Train Your First Model
</Button>
</CardContent>
</Card>
)}
</TabsContent>
<TabsContent value="system-models">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{models
.filter((model) => model.isSystem)
.map((model) => (
<ModelCard key={model.id} model={model} />
))}
</div>
{/* <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{models.map((model) => (
<ModelCard model={model} />
))}
</div> */}
</TabsContent>
<TabsContent value="train-model">
@ -215,7 +173,9 @@ export default function ModelsPage() {
</div>
<div className="space-y-2">
<Label htmlFor="model-description">Description (Optional)</Label>
<Label htmlFor="model-description">
Description (Optional)
</Label>
<Textarea
id="model-description"
placeholder="Describe the purpose of this model..."
@ -237,28 +197,42 @@ export default function ModelsPage() {
</div>
<div className="pt-2">
<h3 className="text-sm font-medium mb-2">Advanced Settings</h3>
<h3 className="text-sm font-medium mb-2">
Advanced Settings
</h3>
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="feature-selection">Automatic Feature Selection</Label>
<p className="text-xs text-muted-foreground">Let AI select the most relevant features</p>
<Label htmlFor="feature-selection">
Automatic Feature Selection
</Label>
<p className="text-xs text-muted-foreground">
Let AI select the most relevant features
</p>
</div>
<Switch id="feature-selection" defaultChecked />
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="hyperparameter-tuning">Hyperparameter Tuning</Label>
<p className="text-xs text-muted-foreground">Optimize model parameters automatically</p>
<Label htmlFor="hyperparameter-tuning">
Hyperparameter Tuning
</Label>
<p className="text-xs text-muted-foreground">
Optimize model parameters automatically
</p>
</div>
<Switch id="hyperparameter-tuning" defaultChecked />
</div>
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="cross-validation">Cross-Validation</Label>
<p className="text-xs text-muted-foreground">Use k-fold cross-validation</p>
<Label htmlFor="cross-validation">
Cross-Validation
</Label>
<p className="text-xs text-muted-foreground">
Use k-fold cross-validation
</p>
</div>
<Switch id="cross-validation" defaultChecked />
</div>
@ -272,7 +246,9 @@ export default function ModelsPage() {
<Card className="mb-6">
<CardHeader>
<CardTitle>Select Data Source</CardTitle>
<CardDescription>Choose a data pipeline to train your model</CardDescription>
<CardDescription>
Choose a data pipeline to train your model
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
@ -280,20 +256,26 @@ export default function ModelsPage() {
<div
key={pipeline.id}
className={`p-4 border rounded-lg cursor-pointer transition-all ${
selectedPipeline === pipeline.id ? "border-primary bg-primary/5" : "hover:border-primary/50"
selectedPipeline === pipeline.id
? "border-primary bg-primary/5"
: "hover:border-primary/50"
}`}
onClick={() => setSelectedPipeline(pipeline.id)}>
onClick={() => setSelectedPipeline(pipeline.id)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Database className="h-5 w-5 text-primary" />
<div>
<h3 className="font-medium">{pipeline.name}</h3>
<p className="text-xs text-muted-foreground">
{pipeline.records.toLocaleString()} records Updated {pipeline.lastUpdated}
{pipeline.records.toLocaleString()} records
Updated {pipeline.lastUpdated}
</p>
</div>
</div>
{selectedPipeline === pipeline.id && <Check className="h-5 w-5 text-primary" />}
{selectedPipeline === pipeline.id && (
<Check className="h-5 w-5 text-primary" />
)}
</div>
</div>
))}
@ -304,7 +286,9 @@ export default function ModelsPage() {
<Card>
<CardHeader>
<CardTitle>Training Process</CardTitle>
<CardDescription>Monitor and control the training process</CardDescription>
<CardDescription>
Monitor and control the training process
</CardDescription>
</CardHeader>
<CardContent>
{isTraining ? (
@ -335,7 +319,11 @@ export default function ModelsPage() {
</div>
{trainingProgress < 100 && (
<Button variant="outline" className="w-full" onClick={() => setIsTraining(false)}>
<Button
variant="outline"
className="w-full"
onClick={() => setIsTraining(false)}
>
Cancel Training
</Button>
)}
@ -363,7 +351,9 @@ export default function ModelsPage() {
<BrainCircuit className="h-8 w-8 text-primary" />
<div>
<h3 className="font-medium">Ready to Train</h3>
<p className="text-sm text-muted-foreground">Configure your settings and start training</p>
<p className="text-sm text-muted-foreground">
Configure your settings and start training
</p>
</div>
</div>
@ -383,13 +373,18 @@ export default function ModelsPage() {
</div>
<div className="flex gap-2">
<Button variant="outline" className="flex-1" onClick={() => setActiveTab("my-models")}>
<Button
variant="outline"
className="flex-1"
onClick={() => setActiveTab("my-models")}
>
Cancel
</Button>
<Button
className="flex-1 gap-2"
onClick={handleStartTraining}
disabled={!selectedPipeline || !modelName}>
disabled={!selectedPipeline || !modelName}
>
<Play className="h-4 w-4" />
Start Training
</Button>
@ -405,96 +400,3 @@ export default function ModelsPage() {
</div>
);
}
interface ModelCardProps {
model: {
id: string;
name: string;
type: string;
hyperparameters: {
[key: string]: string;
};
dataSource: string;
status: string;
lastUpdated: string;
isSystem: boolean;
};
}
function ModelCard({ model }: ModelCardProps) {
return (
<Card className={model.isSystem ? "border-primary/20" : ""}>
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<CardTitle className="text-lg">{model.name}</CardTitle>
<Badge variant={model.status === "active" ? "default" : "secondary"}>
{model.status === "active" ? "Active" : "Inactive"}
</Badge>
</div>
<CardDescription>{model.type} Model</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<div className="flex items-center gap-1 mb-1">
<Database className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">Data Source:</span>
</div>
{model.isSystem ? (
<div className="flex items-center gap-1 text-sm">
<Badge variant="outline" className="bg-primary/5">
System Base Model
</Badge>
<Info
className="h-4 w-4 text-muted-foreground cursor-help"
title="This is a pre-trained system model"
/>
</div>
) : (
<span className="text-sm">{model.dataSource}</span>
)}
</div>
<div>
<div className="flex items-center gap-1 mb-1">
<Sliders className="h-4 w-4 text-primary" />
<span className="text-sm font-medium">Hyperparameters:</span>
</div>
<div className="grid grid-cols-1 gap-1">
{Object.entries(model.hyperparameters).map(([key, value]) => (
<div key={key} className="flex justify-between text-xs">
<span className="text-muted-foreground">{key}:</span>
<span>{value}</span>
</div>
))}
</div>
</div>
<div className="flex items-center text-sm">
<Clock className="h-4 w-4 mr-2 text-muted-foreground" />
<span className="text-muted-foreground">Last updated:</span>
<span className="ml-1 font-medium">{model.lastUpdated}</span>
</div>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button variant="outline" size="sm" asChild>
<Link href={model.isSystem ? "/documentation/models" : "/models/details"}>View Details</Link>
</Button>
<div className="flex gap-2">
<Button variant="outline" size="icon" className="h-8 w-8 text-primary border-primary/20 hover:border-primary">
<Settings className="h-4 w-4" />
</Button>
{!model.isSystem && (
<Button variant="outline" size="icon" className="h-8 w-8 border-primary/20 hover:border-primary">
<Trash2 className="h-4 w-4" />
</Button>
)}
<Button variant="outline" size="icon" className="h-8 w-8 border-primary/20 hover:border-primary">
<ArrowRight className="h-4 w-4" />
</Button>
</div>
</CardFooter>
</Card>
);
}

View File

@ -0,0 +1,128 @@
// import {
// ArrowRight,
// Clock,
// Database,
// Info,
// Link,
// Settings,
// Sliders,
// Trash2,
// } from "lucide-react";
// import { Badge } from "../ui/badge";
// import { Button } from "../ui/button";
// import {
// Card,
// CardContent,
// CardDescription,
// CardFooter,
// CardHeader,
// CardTitle,
// } from "../ui/card";
// interface ModelCardProps {
// model: {
// id: string;
// name: string;
// type: string;
// // hyperparameters: {
// // [key: string]: string;
// // };
// // dataSource: string;
// status: string;
// // lastUpdated: string;
// // isSystem: boolean;
// };
// }
// export function ModelCard({ model }: ModelCardProps) {
// return (
// <Card>
// <CardHeader className="pb-2">
// <div className="flex justify-between items-start">
// <CardTitle className="text-lg">{model.name}</CardTitle>
// <Badge variant={model.status === "active" ? "default" : "secondary"}>
// {model.status === "active" ? "Active" : "Inactive"}
// </Badge>
// </div>
// <CardDescription>{model.type} Model</CardDescription>
// </CardHeader>
// <CardContent>
// <div className="space-y-4">
// <div>
// <div className="flex items-center gap-1 mb-1">
// <Database className="h-4 w-4 text-primary" />
// <span className="text-sm font-medium">Data Source:</span>
// </div>
// {model.isSystem ? (
// <div className="flex items-center gap-1 text-sm">
// <Badge variant="outline" className="bg-primary/5">
// System Base Model
// </Badge>
// <span title="This is a pre-trained system model">
// <Info className="h-4 w-4 text-muted-foreground cursor-help" />
// </span>
// </div>
// ) : (
// <span className="text-sm">{model.dataSource}</span>
// )}
// </div>
// <div>
// <div className="flex items-center gap-1 mb-1">
// <Sliders className="h-4 w-4 text-primary" />
// <span className="text-sm font-medium">Hyperparameters:</span>
// </div>
// <div className="grid grid-cols-1 gap-1">
// {Object.entries(model.hyperparameters).map(([key, value]) => (
// <div key={key} className="flex justify-between text-xs">
// <span className="text-muted-foreground">{key}:</span>
// <span>{value}</span>
// </div>
// ))}
// </div>
// </div>
// <div className="flex items-center text-sm">
// <Clock className="h-4 w-4 mr-2 text-muted-foreground" />
// <span className="text-muted-foreground">Last updated:</span>
// <span className="ml-1 font-medium">{model.lastUpdated}</span>
// </div>
// </div>
// </CardContent>
// <CardFooter className="flex justify-between">
// <Button variant="outline" size="sm" asChild>
// <Link
// href={model.isSystem ? "/documentation/models" : "/models/details"}
// >
// View Details
// </Link>
// </Button>
// <div className="flex gap-2">
// <Button
// variant="outline"
// size="icon"
// className="h-8 w-8 text-primary border-primary/20 hover:border-primary"
// >
// <Settings className="h-4 w-4" />
// </Button>
// {!model.isSystem && (
// <Button
// variant="outline"
// size="icon"
// className="h-8 w-8 border-primary/20 hover:border-primary"
// >
// <Trash2 className="h-4 w-4" />
// </Button>
// )}
// <Button
// variant="outline"
// size="icon"
// className="h-8 w-8 border-primary/20 hover:border-primary"
// >
// <ArrowRight className="h-4 w-4" />
// </Button>
// </div>
// </CardFooter>
// </Card>
// );
// }

View File

@ -1,26 +1,25 @@
import {
Clock,
Database,
Play,
RefreshCw,
Pause,
AlertTriangle,
Copy,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { StatusBadge } from "./badge";
import {
AlertTriangle,
Clock,
Copy,
Database,
Pause,
Play,
RefreshCw,
} from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { StatusBadge } from "./badge";
interface PipelineCardProps {
title: string;
name: string;
description: string;
status: "active" | "paused" | "error";
lastRun: string;
@ -32,7 +31,7 @@ interface PipelineCardProps {
}
export function PipelineCard({
title,
name,
description,
status,
lastRun,
@ -51,10 +50,10 @@ export function PipelineCard({
>
<CardHeader className="pb-2">
<div className="flex justify-between items-start">
<CardTitle className="text-lg">{title}</CardTitle>
<CardTitle className="text-lg">{name}</CardTitle>
<StatusBadge status={status} />
</div>
<CardDescription>{description}</CardDescription>
{/* <CardDescription>{description}</CardDescription> */}
</CardHeader>
<CardContent>
<div className="space-y-2">
@ -86,7 +85,7 @@ export function PipelineCard({
</CardContent>
<CardFooter className="flex justify-between">
<Link
href={`/data-pipeline/${title.toLowerCase().replace(/\s+/g, "-")}`}
href={`/data-pipeline/${name.toLowerCase().replace(/\s+/g, "-")}`}
>
<Button variant="outline" size="sm">
View Details

View File

@ -0,0 +1,110 @@
import { Pipeline, PipelineCreate, Run } from "./types";
// Base URL for your API
const API_BASE = process.env.NEXT_PUBLIC_API_BASE_URL;
// if (typeof window !== "undefined") {
// console.log(API_BASE);
// }
// Utility for handling fetch responses
async function handleResponse<T>(res: Response): Promise<T> {
if (!res.ok) {
const errorBody = await res.json();
throw new Error(JSON.stringify(errorBody));
}
return res.json();
}
// GET /pipelines
export async function listPipelines(): Promise<Pipeline[]> {
const res = await fetch(`${API_BASE}/pipelines`);
return handleResponse<Pipeline[]>(res);
}
// POST /pipelines
export async function createPipeline(
payload: PipelineCreate
): Promise<Pipeline> {
const res = await fetch(`${API_BASE}/pipelines`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
return handleResponse<Pipeline>(res);
}
// GET /pipelines/{pipeline_id}
export async function getPipeline(pipeline_id: string): Promise<Pipeline> {
const res = await fetch(`${API_BASE}/pipelines/${pipeline_id}`);
return handleResponse<Pipeline>(res);
}
// POST /pipelines/{pipeline_id}/run
export async function runPipeline(pipeline_id: string): Promise<Run> {
const res = await fetch(`${API_BASE}/pipelines/${pipeline_id}/run`, {
method: "POST",
});
return handleResponse<Run>(res);
}
// GET /pipelines/{pipeline_id}/runs
export async function listRuns(pipeline_id: string): Promise<Run[]> {
const res = await fetch(`${API_BASE}/pipelines/${pipeline_id}/runs`);
return handleResponse<Run[]>(res);
}
// GET /pipelines/{pipeline_id}/runs/{run_id}
export async function getRun(
pipeline_id: string,
run_id: string
): Promise<Run> {
const res = await fetch(
`${API_BASE}/pipelines/${pipeline_id}/runs/${run_id}`
);
return handleResponse<Run>(res);
}
// GET /pipelines/{pipeline_id}/runs/{run_id}/results
export async function getRunResults(
pipeline_id: string,
run_id: string
): Promise<any[]> {
const res = await fetch(
`${API_BASE}/pipelines/${pipeline_id}/runs/${run_id}/results`
);
return handleResponse<any[]>(res);
}
// GET /pipelines/{pipeline_id}/runs/{run_id}/error
export async function getRunError(
pipeline_id: string,
run_id: string
): Promise<string> {
const res = await fetch(
`${API_BASE}/pipelines/${pipeline_id}/runs/${run_id}/error`
);
return handleResponse<string>(res);
}
// SSE: /pipelines/{pipeline_id}/runs/{run_id}/logs/stream
export function streamLogs(
pipeline_id: string,
run_id: string,
onMessage: (data: string) => void,
onError?: (event: Event) => void
): EventSource {
const url = `${API_BASE}/pipelines/${pipeline_id}/runs/${run_id}/logs/stream`;
const eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
onMessage(event.data);
};
eventSource.onerror = (event) => {
if (onError) onError(event);
eventSource.close();
};
return eventSource;
}

View File

@ -0,0 +1,64 @@
// types.ts
export interface ApiConfig {
url: string;
token?: string | null;
}
export interface ApiSource {
type: "api";
config: ApiConfig;
}
export interface FileConfig {
path: string;
format?: "csv" | "json" | "sqlite";
}
export interface FileSource {
type: "file";
config: FileConfig;
}
export interface ScrapeConfig {
urls: string[];
schema_file?: string | null;
prompt?: string | null;
}
export interface ScrapeSource {
type: "scrape";
config: ScrapeConfig;
}
export type DataSource = ApiSource | FileSource | ScrapeSource;
export interface Pipeline {
id: string;
name?: string | null;
sources: DataSource[];
created_at: string;
}
export interface PipelineCreate {
name?: string | null;
sources: DataSource[];
}
export interface Run {
id: string;
pipeline_id: string;
status: "PENDING" | "RUNNING" | "COMPLETED" | "FAILED";
started_at: string;
finished_at?: string | null;
}
export interface ValidationError {
loc: (string | number)[];
msg: string;
type: string;
}
export interface HTTPValidationError {
detail: ValidationError[];
}

View File

@ -26,9 +26,18 @@ export interface SuccessResponse {
/**
* Property Data Types
*/
export type PropertyType = "Condominium" | "House" | "Townhouse" | "Land" | "Apartment" | "Other";
export type PropertyType =
| "Condominium"
| "House"
| "Townhouse"
| "Land"
| "Apartment"
| "Other";
export type OwnershipType = "Freehold" | "Leasehold";
export type FurnishingStatus = "Unfurnished" | "Partly Furnished" | "Fully Furnished";
export type FurnishingStatus =
| "Unfurnished"
| "Partly Furnished"
| "Fully Furnished";
export interface PriceHistoryEntry {
date: string; // Consider using Date object after parsing
@ -134,8 +143,19 @@ export interface DataPipeline {
/**
* Model Types
*/
export type ModelType = "Regression" | "Neural Network" | "Geospatial" | "Time Series" | "Ensemble" | "Classification";
export type ModelStatus = "active" | "inactive" | "training" | "error" | "pending";
export type ModelType =
| "Regression"
| "Neural Network"
| "Geospatial"
| "Time Series"
| "Ensemble"
| "Classification";
export type ModelStatus =
| "active"
| "inactive"
| "training"
| "error"
| "pending";
export interface Model {
id: string;