mirror of
https://github.com/borbann-platform/backend-api.git
synced 2025-12-18 12:14:05 +01:00
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:
parent
8b730e14ae
commit
a687636b07
@ -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"
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
128
frontend/components/models/model-card.tsx
Normal file
128
frontend/components/models/model-card.tsx
Normal 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>
|
||||
// );
|
||||
// }
|
||||
@ -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
|
||||
|
||||
110
frontend/lib/api/pipelines/index.ts
Normal file
110
frontend/lib/api/pipelines/index.ts
Normal 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;
|
||||
}
|
||||
64
frontend/lib/api/pipelines/types.ts
Normal file
64
frontend/lib/api/pipelines/types.ts
Normal 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[];
|
||||
}
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user