mirror of
https://github.com/SkalaraAI/beta.git
synced 2025-04-09 15:00:20 -04:00
376 lines
12 KiB
TypeScript
376 lines
12 KiB
TypeScript
"use client";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTrigger,
|
|
DialogTitle,
|
|
DialogFooter,
|
|
} from "@/components/ui/dialog";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { Loader2, PencilRuler, Plus, Trash2 } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { useEffect, useState } from "react";
|
|
import { Feature, Task } from "@/types";
|
|
import { ScrollArea } from "./ui/scroll-area";
|
|
import FeatureCard from "./feature-card";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
export function GenerateProject({
|
|
workspaceID,
|
|
projectID,
|
|
project_name,
|
|
project_description,
|
|
tech_stack,
|
|
}: {
|
|
workspaceID: string;
|
|
projectID: string;
|
|
project_name: string;
|
|
project_description: string;
|
|
tech_stack: string[];
|
|
}) {
|
|
const [features, setFeatures] = useState<Feature[]>([]);
|
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
const [isDialogOpen, setDialogOpen] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [isAddingFeature, setIsAddingFeature] = useState(false);
|
|
const [addName, setAddName] = useState("");
|
|
const [addDescription, setAddDescription] = useState("");
|
|
const [page, setPage] = useState(0);
|
|
const router = useRouter();
|
|
|
|
async function generateFeatures() {
|
|
setLoading(true);
|
|
const res = await fetch(`/w/${workspaceID}/p/${projectID}/features/gen`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
project_name,
|
|
project_description,
|
|
tech_stack,
|
|
}),
|
|
});
|
|
setLoading(false);
|
|
setPage(1);
|
|
const data = await res.json();
|
|
console.log("CLIENT RAW RES ===>", data.features);
|
|
setFeatures(data.features);
|
|
}
|
|
|
|
async function generateTasks(features: Feature[]) {
|
|
console.log("GENERATING TASKS ===>", features);
|
|
let _tasks: Task[] = [];
|
|
|
|
features.forEach(async (feature, i) => {
|
|
const res = await fetch(`/w/${workspaceID}/p/${projectID}/tasks/gen`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
project_name,
|
|
project_description,
|
|
tech_stack,
|
|
related_features: [],
|
|
feature,
|
|
}),
|
|
});
|
|
const data = await res.json();
|
|
console.log("TASK RAW RES ===>", data.tasks);
|
|
_tasks.push(...data.tasks);
|
|
console.log("CURTASKS ===>", _tasks);
|
|
|
|
if (i === features.length - 1) {
|
|
console.log("TASKS ===>", _tasks);
|
|
setTasks(_tasks);
|
|
addTasks(_tasks);
|
|
setPage(3);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function addTasks(tasks: Task[] = []) {
|
|
console.log("ADDING TASKS ===>", tasks);
|
|
const res = await fetch(`/w/${workspaceID}/p/${projectID}/tasks/add`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(tasks),
|
|
});
|
|
|
|
const data = await res.json();
|
|
console.log("ADDTASKS RAW RES ===>", data.tasks);
|
|
}
|
|
|
|
async function addFeatures() {
|
|
const res = await fetch(`/w/${workspaceID}/p/${projectID}/features/add`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify(features),
|
|
});
|
|
const data = await res.json();
|
|
console.log("addFeatures RAW RES ===>", data.features);
|
|
setFeatures(data.features);
|
|
setPage(2);
|
|
return data.features;
|
|
}
|
|
|
|
useEffect(() => {
|
|
async function getFeatures() {
|
|
const res = await fetch(`/w/${workspaceID}/p/${projectID}/features`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
const data = await res.json();
|
|
if (data.features.length === 0) setDialogOpen(true);
|
|
console.log("CLIENT RAW RES ===>", data.features);
|
|
return data.features;
|
|
}
|
|
getFeatures().then((features) => setFeatures(features));
|
|
}, [projectID, workspaceID]);
|
|
|
|
return (
|
|
<>
|
|
<Dialog open={isDialogOpen} onOpenChange={setDialogOpen}>
|
|
{page == 0 && (
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
Welcome to <i>{project_name}</i>.
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
Now it's time to generate some features for your new
|
|
project.
|
|
</DialogDescription>
|
|
{!loading ? (
|
|
<Button
|
|
size="lg"
|
|
onClick={() => {
|
|
generateFeatures();
|
|
}}
|
|
>
|
|
Let's get started.
|
|
</Button>
|
|
) : (
|
|
<Button disabled size="lg" className="cursor-progress">
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
Generating features...
|
|
</Button>
|
|
)}
|
|
<DialogFooter>
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => setDialogOpen(false)}
|
|
size="sm"
|
|
>
|
|
Skip this for now.
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogHeader>
|
|
</DialogContent>
|
|
)}
|
|
{page == 1 && (
|
|
<DialogContent className="min-w-[800px]">
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
Here's what we've generated for you.
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
Feel free to edit, delete, or add new features.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="flex flex-col space-y-2">
|
|
<ScrollArea className="w-full h-[600px]">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{features.map((feature) => (
|
|
<FeatureCard
|
|
key={feature.uid}
|
|
feature={feature}
|
|
features={features}
|
|
setFeatures={setFeatures}
|
|
/>
|
|
))}
|
|
|
|
{!isAddingFeature ? (
|
|
<Card
|
|
className="w-[350px] h-[200px] flex justify-center items-center border-green-400 border-dashed m-auto hover:bg-green-400 transition cursor-pointer"
|
|
onClick={() => setIsAddingFeature(true)}
|
|
>
|
|
<CardContent className="p-0 flex flex-col items-center gap-1">
|
|
<p>Create a new feature</p>
|
|
<Plus />
|
|
</CardContent>
|
|
</Card>
|
|
) : (
|
|
<Card className="w-[350px] h-min-[200px] m-auto flex justify-center items-center">
|
|
<CardContent className="p-4 gap-4 flex flex-col items-center">
|
|
<input
|
|
type="text"
|
|
className="w-full border-1 border-muted-foreground rounded-md p-2"
|
|
placeholder="Feature name"
|
|
value={addName}
|
|
onChange={(e) => {
|
|
setAddName(e.target.value);
|
|
}}
|
|
/>
|
|
<textarea
|
|
className="w-full border-1 border-muted-foreground rounded-md p-2"
|
|
placeholder="Feature description"
|
|
rows={4}
|
|
value={addDescription}
|
|
onChange={(e) => {
|
|
setAddDescription(e.target.value);
|
|
}}
|
|
/>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => {
|
|
setIsAddingFeature(false);
|
|
setAddName("");
|
|
setAddDescription("");
|
|
}}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={() => {
|
|
setFeatures([
|
|
...features,
|
|
{
|
|
name: addName,
|
|
description: addDescription,
|
|
uid: uuidv4(),
|
|
},
|
|
]);
|
|
setAddName("");
|
|
setAddDescription("");
|
|
setIsAddingFeature(false);
|
|
}}
|
|
>
|
|
Create
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
</ScrollArea>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button
|
|
onClick={async () => {
|
|
setPage(2);
|
|
await addFeatures().then((features) => {
|
|
generateTasks(features);
|
|
});
|
|
}}
|
|
>
|
|
These look good, generate subtasks.
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => {
|
|
setDialogOpen(false);
|
|
addFeatures();
|
|
setPage(0);
|
|
}}
|
|
size="sm"
|
|
>
|
|
Skip this for now.
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
)}
|
|
{page == 2 && (
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Generating tasks...</DialogTitle>
|
|
</DialogHeader>
|
|
</DialogContent>
|
|
)}
|
|
{page == 3 && (
|
|
<DialogContent className="min-w-[800px]">
|
|
<DialogHeader>
|
|
<DialogTitle>Here are your generated tasks.</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="flex flex-col space-y-2">
|
|
<ScrollArea className="w-full h-[600px]">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{tasks.map((task) => (
|
|
<Card key={task.uid} className="w-[300px] m-auto">
|
|
<CardHeader>
|
|
<CardTitle>{task.name}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p>Description: {task.description}</p>
|
|
<p>Priority: {task.priority}</p>
|
|
<p>Order: {task.order}</p>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</ScrollArea>
|
|
</div>
|
|
</DialogContent>
|
|
)}
|
|
</Dialog>
|
|
|
|
<Button
|
|
onClick={() => {
|
|
setDialogOpen(true);
|
|
}}
|
|
>
|
|
Generate Features
|
|
</Button>
|
|
</>
|
|
);
|
|
}
|
|
|
|
/* <DialogContent className="min-w-[800px]">
|
|
<DialogHeader>
|
|
<DialogTitle>Features</DialogTitle>
|
|
<DialogDescription className="flex flex-col space-y-2">
|
|
<ScrollArea className="w-full h-[600px]">
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{features.map((feature) => (
|
|
<Card key={feature.name} className="w-[300px]">
|
|
<CardHeader>
|
|
<CardTitle>{feature.name}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p>Description: {feature.description}</p>
|
|
<p>ID: {feature.id}</p>
|
|
<p>Priority: {feature.priority}</p>
|
|
<p>Order: {feature.order}</p>
|
|
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</ScrollArea>
|
|
</DialogDescription>
|
|
<DialogFooter>
|
|
<Button className="w-full" onClick={generateTasks}>
|
|
Generate Tasks
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogHeader>
|
|
</DialogContent>; */
|