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"; "use client";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Plus } from "lucide-react";
import Link from "next/link";
import PageHeader from "@/components/page-header"; import PageHeader from "@/components/page-header";
import { PipelineCard } from "@/components/pipeline/card"; 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() { 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 ( return (
<div className="container mx-auto p-6"> <div className="container mx-auto p-6">
{error && <p className="text-red-500">{error}</p>}
<PageHeader <PageHeader
title="Data Pipelines" title="Data Pipelines"
description="Manage your automated data collection pipelines" description="Manage your automated data collection pipelines"
@ -37,7 +58,7 @@ export default function DataPipelinePage() {
<TabsContent value="active" className="mt-4"> <TabsContent value="active" className="mt-4">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<PipelineCard <PipelineCard
title="Property Listings" name="Property Listings"
description="Scrapes real estate listings from multiple websites" description="Scrapes real estate listings from multiple websites"
status="active" status="active"
lastRun="2 hours ago" lastRun="2 hours ago"
@ -48,7 +69,7 @@ export default function DataPipelinePage() {
/> />
<PipelineCard <PipelineCard
title="Rental Market Data" name="Rental Market Data"
description="Collects rental prices and availability" description="Collects rental prices and availability"
status="active" status="active"
lastRun="Yesterday" lastRun="Yesterday"
@ -59,7 +80,7 @@ export default function DataPipelinePage() {
/> />
<PipelineCard <PipelineCard
title="Price Comparison" name="Price Comparison"
description="Tracks property price changes over time" description="Tracks property price changes over time"
status="error" status="error"
lastRun="2 days ago" lastRun="2 days ago"
@ -74,7 +95,7 @@ export default function DataPipelinePage() {
<TabsContent value="paused" className="mt-4"> <TabsContent value="paused" className="mt-4">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<PipelineCard <PipelineCard
title="Commercial Properties" name="Commercial Properties"
description="Collects data on commercial real estate" description="Collects data on commercial real estate"
status="paused" status="paused"
lastRun="1 week ago" lastRun="1 week ago"
@ -88,7 +109,7 @@ export default function DataPipelinePage() {
<TabsContent value="all" className="mt-4"> <TabsContent value="all" className="mt-4">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<PipelineCard <PipelineCard
title="Property Listings" name="Property Listings"
description="Scrapes real estate listings from multiple websites" description="Scrapes real estate listings from multiple websites"
status="active" status="active"
lastRun="2 hours ago" lastRun="2 hours ago"
@ -100,7 +121,7 @@ export default function DataPipelinePage() {
{/* mock pipeline card with data */} {/* mock pipeline card with data */}
<PipelineCard <PipelineCard
title="Rental Market Data" name="Rental Market Data"
description="Collects rental prices and availability" description="Collects rental prices and availability"
status="active" status="active"
lastRun="Yesterday" lastRun="Yesterday"
@ -111,7 +132,7 @@ export default function DataPipelinePage() {
/> />
<PipelineCard <PipelineCard
title="Price Comparison" name="Price Comparison"
description="Tracks property price changes over time" description="Tracks property price changes over time"
status="error" status="error"
lastRun="2 days ago" lastRun="2 days ago"
@ -122,7 +143,7 @@ export default function DataPipelinePage() {
/> />
<PipelineCard <PipelineCard
title="Commercial Properties" name="Commercial Properties"
description="Collects data on commercial real estate" description="Collects data on commercial real estate"
status="paused" status="paused"
lastRun="1 week ago" 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 MapWithSearch from "@/components/map/map-with-search";
import { TopNavigation } from "@/components/navigation/top-navigation"; import { TopNavigation } from "@/components/navigation/top-navigation";
import { Button } from "@/components/ui/button"; 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 { useRef, useState } from "react";
import Draggable from "react-draggable"; import Draggable from "react-draggable";
@ -36,7 +36,7 @@ export default function MapsPage() {
</div> </div>
{/* Sample Property Markers */} {/* Sample Property Markers */}
<div {/* <div
className="absolute left-1/4 top-1/3 text-primary cursor-pointer group" className="absolute left-1/4 top-1/3 text-primary cursor-pointer group"
onClick={handlePropertyClick} onClick={handlePropertyClick}
> >
@ -74,7 +74,7 @@ export default function MapsPage() {
Sold Sold
</span> </span>
</div> </div>
</div> </div> */}
</div> </div>
{/* Top Navigation Bar */} {/* Top Navigation Bar */}
@ -91,7 +91,6 @@ export default function MapsPage() {
onClick={() => { onClick={() => {
setShowAnalytics(!showAnalytics); setShowAnalytics(!showAnalytics);
if (showAnalytics) { if (showAnalytics) {
setShowFilters(false);
setShowPropertyInfo(false); setShowPropertyInfo(false);
} }
}} }}
@ -107,7 +106,6 @@ export default function MapsPage() {
onClick={() => { onClick={() => {
setShowFilters(!showFilters); setShowFilters(!showFilters);
if (showFilters) { if (showFilters) {
setShowAnalytics(false);
setShowPropertyInfo(false); setShowPropertyInfo(false);
} }
}} }}

View File

@ -1,31 +1,32 @@
"use client"; "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 { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import {
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; Card,
import { Badge } from "@/components/ui/badge"; CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; 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 { 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 { import {
AlertTriangle,
BrainCircuit, BrainCircuit,
Clock, Check,
Database, Database,
Play, Play,
Plus, Plus,
Settings,
Sliders,
Trash2,
AlertTriangle,
Check,
ArrowRight,
Info,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import { useState } from "react";
import PageHeader from "@/components/page-header"; import { useShallow } from "zustand/react/shallow";
export default function ModelsPage() { export default function ModelsPage() {
const [activeTab, setActiveTab] = useState("my-models"); const [activeTab, setActiveTab] = useState("my-models");
@ -34,85 +35,36 @@ export default function ModelsPage() {
const [isTraining, setIsTraining] = useState(false); const [isTraining, setIsTraining] = useState(false);
const [modelName, setModelName] = useState(""); const [modelName, setModelName] = useState("");
const [modelDescription, setModelDescription] = useState(""); const [modelDescription, setModelDescription] = useState("");
const { models } = useModelState(
useShallow((state) => ({
models: state.models,
}))
);
const dataPipelines = [ 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", id: "pipeline-1",
name: "Standard ML Model v2.4", name: "Property Listings",
type: "Regression", records: 1240,
hyperparameters: { lastUpdated: "2 hours ago",
learningRate: "0.01",
maxDepth: "6",
numEstimators: "100",
},
dataSource: "System Base Model",
status: "active",
lastUpdated: "3 days ago",
isSystem: true,
}, },
{ {
id: "model-2", id: "pipeline-2",
name: "Enhanced Neural Network v1.8", name: "Rental Market Data",
type: "Neural Network", records: 830,
hyperparameters: { lastUpdated: "Yesterday",
layers: "4",
neurons: "128,64,32,16",
dropout: "0.2",
}, },
dataSource: "System Base Model", {
status: "active", id: "pipeline-3",
name: "Price Comparison",
records: 1560,
lastUpdated: "2 days ago",
},
{
id: "pipeline-4",
name: "Commercial Properties",
records: 450,
lastUpdated: "1 week ago", 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"> <div className="flex justify-between items-center mb-6">
<TabsList> <TabsList>
<TabsTrigger value="my-models">My Models</TabsTrigger> <TabsTrigger value="my-models">My Models</TabsTrigger>
@ -155,7 +111,10 @@ export default function ModelsPage() {
</TabsList> </TabsList>
{activeTab !== "train-model" && ( {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" /> <Plus className="h-4 w-4" />
Train New Model Train New Model
</Button> </Button>
@ -164,35 +123,34 @@ export default function ModelsPage() {
<TabsContent value="my-models"> <TabsContent value="my-models">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{models {/* {models} */}
.filter((model) => !model.isSystem)
.map((model) => (
<ModelCard key={model.id} model={model} />
))}
</div> </div>
{models.filter((model) => !model.isSystem).length === 0 && ( {models && (
<Card className="border-dashed"> <Card className="border-dashed">
<CardContent className="pt-6 text-center"> <CardContent className="pt-6 text-center">
<BrainCircuit className="h-12 w-12 text-muted-foreground mx-auto mb-4" /> <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"> <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> </p>
<Button onClick={() => setActiveTab("train-model")}>Train Your First Model</Button> <Button onClick={() => setActiveTab("train-model")}>
Train Your First Model
</Button>
</CardContent> </CardContent>
</Card> </Card>
)} )}
</TabsContent> </TabsContent>
<TabsContent value="system-models"> <TabsContent value="system-models">
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> {/* <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{models {models.map((model) => (
.filter((model) => model.isSystem) <ModelCard model={model} />
.map((model) => (
<ModelCard key={model.id} model={model} />
))} ))}
</div> </div> */}
</TabsContent> </TabsContent>
<TabsContent value="train-model"> <TabsContent value="train-model">
@ -215,7 +173,9 @@ export default function ModelsPage() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="model-description">Description (Optional)</Label> <Label htmlFor="model-description">
Description (Optional)
</Label>
<Textarea <Textarea
id="model-description" id="model-description"
placeholder="Describe the purpose of this model..." placeholder="Describe the purpose of this model..."
@ -237,28 +197,42 @@ export default function ModelsPage() {
</div> </div>
<div className="pt-2"> <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="space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="feature-selection">Automatic Feature Selection</Label> <Label htmlFor="feature-selection">
<p className="text-xs text-muted-foreground">Let AI select the most relevant features</p> Automatic Feature Selection
</Label>
<p className="text-xs text-muted-foreground">
Let AI select the most relevant features
</p>
</div> </div>
<Switch id="feature-selection" defaultChecked /> <Switch id="feature-selection" defaultChecked />
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="hyperparameter-tuning">Hyperparameter Tuning</Label> <Label htmlFor="hyperparameter-tuning">
<p className="text-xs text-muted-foreground">Optimize model parameters automatically</p> Hyperparameter Tuning
</Label>
<p className="text-xs text-muted-foreground">
Optimize model parameters automatically
</p>
</div> </div>
<Switch id="hyperparameter-tuning" defaultChecked /> <Switch id="hyperparameter-tuning" defaultChecked />
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="space-y-0.5"> <div className="space-y-0.5">
<Label htmlFor="cross-validation">Cross-Validation</Label> <Label htmlFor="cross-validation">
<p className="text-xs text-muted-foreground">Use k-fold cross-validation</p> Cross-Validation
</Label>
<p className="text-xs text-muted-foreground">
Use k-fold cross-validation
</p>
</div> </div>
<Switch id="cross-validation" defaultChecked /> <Switch id="cross-validation" defaultChecked />
</div> </div>
@ -272,7 +246,9 @@ export default function ModelsPage() {
<Card className="mb-6"> <Card className="mb-6">
<CardHeader> <CardHeader>
<CardTitle>Select Data Source</CardTitle> <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> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-4"> <div className="space-y-4">
@ -280,20 +256,26 @@ export default function ModelsPage() {
<div <div
key={pipeline.id} key={pipeline.id}
className={`p-4 border rounded-lg cursor-pointer transition-all ${ 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 justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Database className="h-5 w-5 text-primary" /> <Database className="h-5 w-5 text-primary" />
<div> <div>
<h3 className="font-medium">{pipeline.name}</h3> <h3 className="font-medium">{pipeline.name}</h3>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
{pipeline.records.toLocaleString()} records Updated {pipeline.lastUpdated} {pipeline.records.toLocaleString()} records
Updated {pipeline.lastUpdated}
</p> </p>
</div> </div>
</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>
</div> </div>
))} ))}
@ -304,7 +286,9 @@ export default function ModelsPage() {
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Training Process</CardTitle> <CardTitle>Training Process</CardTitle>
<CardDescription>Monitor and control the training process</CardDescription> <CardDescription>
Monitor and control the training process
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{isTraining ? ( {isTraining ? (
@ -335,7 +319,11 @@ export default function ModelsPage() {
</div> </div>
{trainingProgress < 100 && ( {trainingProgress < 100 && (
<Button variant="outline" className="w-full" onClick={() => setIsTraining(false)}> <Button
variant="outline"
className="w-full"
onClick={() => setIsTraining(false)}
>
Cancel Training Cancel Training
</Button> </Button>
)} )}
@ -363,7 +351,9 @@ export default function ModelsPage() {
<BrainCircuit className="h-8 w-8 text-primary" /> <BrainCircuit className="h-8 w-8 text-primary" />
<div> <div>
<h3 className="font-medium">Ready to Train</h3> <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>
</div> </div>
@ -383,13 +373,18 @@ export default function ModelsPage() {
</div> </div>
<div className="flex gap-2"> <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 Cancel
</Button> </Button>
<Button <Button
className="flex-1 gap-2" className="flex-1 gap-2"
onClick={handleStartTraining} onClick={handleStartTraining}
disabled={!selectedPipeline || !modelName}> disabled={!selectedPipeline || !modelName}
>
<Play className="h-4 w-4" /> <Play className="h-4 w-4" />
Start Training Start Training
</Button> </Button>
@ -405,96 +400,3 @@ export default function ModelsPage() {
</div> </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 { import { Button } from "@/components/ui/button";
Clock,
Database,
Play,
RefreshCw,
Pause,
AlertTriangle,
Copy,
} from "lucide-react";
import { import {
Card, Card,
CardContent, CardContent,
CardDescription,
CardFooter, CardFooter,
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } 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 Link from "next/link";
import { Button } from "@/components/ui/button"; import { StatusBadge } from "./badge";
interface PipelineCardProps { interface PipelineCardProps {
title: string; name: string;
description: string; description: string;
status: "active" | "paused" | "error"; status: "active" | "paused" | "error";
lastRun: string; lastRun: string;
@ -32,7 +31,7 @@ interface PipelineCardProps {
} }
export function PipelineCard({ export function PipelineCard({
title, name,
description, description,
status, status,
lastRun, lastRun,
@ -51,10 +50,10 @@ export function PipelineCard({
> >
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<div className="flex justify-between items-start"> <div className="flex justify-between items-start">
<CardTitle className="text-lg">{title}</CardTitle> <CardTitle className="text-lg">{name}</CardTitle>
<StatusBadge status={status} /> <StatusBadge status={status} />
</div> </div>
<CardDescription>{description}</CardDescription> {/* <CardDescription>{description}</CardDescription> */}
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="space-y-2"> <div className="space-y-2">
@ -86,7 +85,7 @@ export function PipelineCard({
</CardContent> </CardContent>
<CardFooter className="flex justify-between"> <CardFooter className="flex justify-between">
<Link <Link
href={`/data-pipeline/${title.toLowerCase().replace(/\s+/g, "-")}`} href={`/data-pipeline/${name.toLowerCase().replace(/\s+/g, "-")}`}
> >
<Button variant="outline" size="sm"> <Button variant="outline" size="sm">
View Details 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 * 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 OwnershipType = "Freehold" | "Leasehold";
export type FurnishingStatus = "Unfurnished" | "Partly Furnished" | "Fully Furnished"; export type FurnishingStatus =
| "Unfurnished"
| "Partly Furnished"
| "Fully Furnished";
export interface PriceHistoryEntry { export interface PriceHistoryEntry {
date: string; // Consider using Date object after parsing date: string; // Consider using Date object after parsing
@ -134,8 +143,19 @@ export interface DataPipeline {
/** /**
* Model Types * Model Types
*/ */
export type ModelType = "Regression" | "Neural Network" | "Geospatial" | "Time Series" | "Ensemble" | "Classification"; export type ModelType =
export type ModelStatus = "active" | "inactive" | "training" | "error" | "pending"; | "Regression"
| "Neural Network"
| "Geospatial"
| "Time Series"
| "Ensemble"
| "Classification";
export type ModelStatus =
| "active"
| "inactive"
| "training"
| "error"
| "pending";
export interface Model { export interface Model {
id: string; id: string;