mirror of
https://github.com/SkalaraAI/skalara-mvp.git
synced 2025-04-09 23:10:17 -04:00
654 lines
20 KiB
TypeScript
654 lines
20 KiB
TypeScript
"use client";
|
||
import { zodResolver } from "@hookform/resolvers/zod";
|
||
import { useForm } from "react-hook-form";
|
||
import * as z from "zod";
|
||
import { useEffect, useState } from "react";
|
||
import { OpenAIChatApi } from "llm-api";
|
||
import { completion } from "zod-gpt";
|
||
|
||
import { v4 } from "uuid";
|
||
|
||
import { Button } from "@/components/ui/button";
|
||
import {
|
||
Form,
|
||
FormControl,
|
||
FormDescription,
|
||
FormField,
|
||
FormItem,
|
||
FormLabel,
|
||
FormMessage,
|
||
} from "@/components/ui/form";
|
||
import { Input } from "@/components/ui/input";
|
||
import { Badge } from "@/components/ui/badge";
|
||
|
||
import { X } from "lucide-react";
|
||
import {
|
||
Table,
|
||
TableBody,
|
||
TableCaption,
|
||
TableCell,
|
||
TableHead,
|
||
TableHeader,
|
||
TableRow,
|
||
} from "@/components/ui/table";
|
||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||
import { set } from "date-fns";
|
||
|
||
const MIN_FEATURES_PER_PROJECT = 6;
|
||
const MAX_FEATURES_PER_PROJECT = 12;
|
||
const MIN_TASKS_PER_FEATURE = 6;
|
||
const MAX_TASKS_PER_FEATURE = 12;
|
||
const OPENAI_MODEL = "gpt-3.5-turbo-16k";
|
||
|
||
const formSchema = z.object({
|
||
name: z.string().min(2, {
|
||
message: "Project name must be at least 2 characters.",
|
||
}),
|
||
description: z.string().min(2, {
|
||
message: "Project description must be at least 2 characters.",
|
||
}),
|
||
stack: z.array(z.string()).min(1, {
|
||
message: "Project tech stack must have at least one item.",
|
||
}),
|
||
});
|
||
|
||
console.log("OPENAI_API_KEY", process.env.OPENAI_API_KEY);
|
||
|
||
const openai = new OpenAIChatApi(
|
||
{
|
||
apiKey: "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",
|
||
},
|
||
{
|
||
model: OPENAI_MODEL,
|
||
}
|
||
);
|
||
|
||
type Project = {
|
||
name: string;
|
||
description: string;
|
||
stack: string[];
|
||
features?: Feature[];
|
||
tasks?: Task[];
|
||
};
|
||
|
||
type Feature = {
|
||
name: string;
|
||
description: string;
|
||
uid: any;
|
||
tasks?: Task[];
|
||
};
|
||
|
||
type Dependency = {
|
||
uid: string;
|
||
depUid: string;
|
||
};
|
||
|
||
type Task = {
|
||
name: string;
|
||
description: string;
|
||
priority: "low" | "medium" | "high";
|
||
order: number;
|
||
uid: any;
|
||
feature: any;
|
||
};
|
||
|
||
export default function Home() {
|
||
const form = useForm<z.infer<typeof formSchema>>({
|
||
resolver: zodResolver(formSchema),
|
||
defaultValues: {
|
||
name: "",
|
||
description: "",
|
||
stack: [],
|
||
},
|
||
});
|
||
|
||
const [stackInput, setStackInput] = useState("");
|
||
const [deps, setDeps] = useState<Dependency[]>([]);
|
||
const [project, setProject] = useState<Project>();
|
||
const [features, setFeatures] = useState<Feature[]>([]);
|
||
const [tasks, setTasks] = useState<Task[]>([]);
|
||
|
||
const { setValue } = form;
|
||
|
||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||
setFeatures([]);
|
||
setTasks([]);
|
||
setProject(values);
|
||
|
||
try {
|
||
const feature_gen_prompt = `You are an AI software project manager. You use agile methodology and the best software project management techniques to plan software projects for indie developers.
|
||
|
||
I am an indie developer creating a new software project. Here are the details:
|
||
"""
|
||
Project Name: ${values.name}
|
||
Project Description: ${values.description}
|
||
Tech Stack: ${values.stack.join(", ")}
|
||
"""
|
||
|
||
Instructions: Generate a list of features for the project. Each feature should have the following:
|
||
"""
|
||
Name
|
||
Description
|
||
"""`;
|
||
console.log(`prompt: ${feature_gen_prompt}`);
|
||
const res = await completion(openai, feature_gen_prompt, {
|
||
schema: z.object({
|
||
features: z
|
||
.array(
|
||
z.object({
|
||
name: z.string().describe("The name of the feature"),
|
||
description: z
|
||
.string()
|
||
.describe("The description of the feature"),
|
||
})
|
||
)
|
||
.min(MIN_FEATURES_PER_PROJECT)
|
||
.max(MAX_FEATURES_PER_PROJECT),
|
||
}),
|
||
});
|
||
console.log("RAW RES", res.data.features);
|
||
const _features: Feature[] = res.data.features.map((feature) => {
|
||
return {
|
||
...feature,
|
||
uid: v4(),
|
||
};
|
||
});
|
||
setFeatures(_features);
|
||
generateDependencies(
|
||
values.name,
|
||
values.description,
|
||
values.stack,
|
||
_features
|
||
);
|
||
} catch (err: any) {
|
||
console.error(err);
|
||
return new Error(err);
|
||
}
|
||
}
|
||
|
||
async function generateDependencies(
|
||
p_name: string,
|
||
p_desc: string,
|
||
p_stack: string[],
|
||
_features: Feature[]
|
||
) {
|
||
try {
|
||
const dep_gen_prompt = `You are an AI software project manager. You use agile methodology and the best software project management techniques to plan software projects for indie developers.
|
||
|
||
I am an indie developer creating a new software project. Here are the details:
|
||
"""
|
||
Project Name: ${p_name}
|
||
Project Description: ${p_desc}
|
||
Tech Stack: ${p_stack.join(", ")}
|
||
"""
|
||
|
||
I have created a list of different features that I'd like to implement:
|
||
"""
|
||
${_features.map((feature) => {
|
||
return `Name: ${feature.name}, Description: ${feature.description}, ID: ${feature.uid}.\n`;
|
||
})}
|
||
"""
|
||
|
||
Instructions: For each feature, we need to figure out what other features need to be made first, and therefore need to create a list of dependencies (e.g. if you have 'user auth' and another feature that requires user auth, then that feature would have the dependency of 'user auth'). When returning features, return the feature UID and the dependencies UID's don't return anything else.`;
|
||
|
||
// TODO: CATCH CIRCLES IN DEP GRAPH
|
||
const featureUids: readonly [string, ...string[]] = _features.map(
|
||
(feature) => feature.uid.toString()
|
||
) as [string, ...string[]];
|
||
|
||
console.log(`featureUids: ${featureUids}`);
|
||
console.log(`prompt: ${dep_gen_prompt}`);
|
||
|
||
console.log(featureUids);
|
||
const res = await completion(openai, dep_gen_prompt, {
|
||
schema: z.object({
|
||
dependencies: z
|
||
.array(
|
||
z.object({
|
||
uid: z.enum(featureUids).describe("The UID of this feature"),
|
||
dependencies: z
|
||
.array(
|
||
z.object({
|
||
uid: z
|
||
.enum(featureUids)
|
||
.describe(
|
||
"The UID of the feature this feature depends on"
|
||
),
|
||
})
|
||
)
|
||
.describe("The UID of the dependencies of the feature"),
|
||
})
|
||
)
|
||
.describe("The dependencies of the features"),
|
||
}),
|
||
});
|
||
|
||
console.log("===>", res.data.dependencies);
|
||
|
||
let deps: Dependency[] = [];
|
||
|
||
// res.data.dependences: [
|
||
// {
|
||
// uid: "123",
|
||
// dependencies: [{uid: "456"}, {uid: "789"}]
|
||
// },
|
||
// {
|
||
// uid: "456",
|
||
// dependencies: [{uid: "111"}, {uid: "222"}]
|
||
// }
|
||
// ]
|
||
|
||
console.log("gathering dependencies");
|
||
res.data.dependencies.forEach((dep) => {
|
||
const feature = _features.find((feature) => feature.uid === dep.uid);
|
||
console.log(`feature: ${feature}`);
|
||
if (feature) {
|
||
dep.dependencies.forEach((d) => {
|
||
const dependency = _features.find(
|
||
(feature) => feature.uid === d.uid
|
||
);
|
||
console.log(`dependency: ${dependency}`);
|
||
if (dependency) {
|
||
deps.push({
|
||
uid: feature.uid,
|
||
depUid: dependency.uid,
|
||
});
|
||
}
|
||
});
|
||
}
|
||
});
|
||
console.log("done gathering dependencies => ", deps);
|
||
generateTasks(p_name, p_desc, p_stack, _features, deps);
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function generateTasks(
|
||
p_name: string,
|
||
p_desc: string,
|
||
p_stack: string[],
|
||
_features: Feature[],
|
||
_dependencies: Dependency[]
|
||
) {
|
||
const featurePromises = _features.map((feature) => {
|
||
const related_features: (Feature | undefined)[] = _dependencies
|
||
.filter((dep) => dep.uid === feature.uid)
|
||
.map((dep) => _features.find((f) => f.uid === dep.depUid));
|
||
|
||
return generateTask(
|
||
p_name,
|
||
p_desc,
|
||
p_stack,
|
||
related_features,
|
||
_features,
|
||
feature
|
||
);
|
||
});
|
||
const tasks: Task[][] = await Promise.all(featurePromises);
|
||
setTasks(tasks.flat());
|
||
|
||
let featuresWithTasks = [...features];
|
||
featuresWithTasks.forEach((feature) => {
|
||
feature.tasks = tasks
|
||
.flat()
|
||
.filter((task) => task.feature === feature.uid);
|
||
});
|
||
|
||
setFeatures(featuresWithTasks);
|
||
setDeps(_dependencies);
|
||
|
||
console.log("featuresWithTasks ", featuresWithTasks);
|
||
}
|
||
|
||
async function generateTask(
|
||
p_name: string,
|
||
p_desc: string,
|
||
p_stack: string[],
|
||
related_features: (Feature | undefined)[],
|
||
_features: Feature[],
|
||
_feature: Feature
|
||
): Promise<Task[]> {
|
||
try {
|
||
const deps = _features.filter((feature) => feature.uid !== _feature.uid);
|
||
|
||
const prompt = `You are an AI software project manager. You use agile methodology and the best software project management techniques to plan software projects for indie developers.
|
||
|
||
I am an indie developer creating a new software project. Here are the details:
|
||
"""
|
||
Project Name: ${p_name}
|
||
Project Description: ${p_desc}
|
||
Tech Stack: ${p_stack.join(", ")}
|
||
"""
|
||
###
|
||
I have already written the tasks for the following features:
|
||
|
||
${related_features.join(", ")}
|
||
"""
|
||
|
||
Generate tasks for the following feature in the context of the project. I have already written the tasks for the configuration of the project with the specified tech stack, so don’t generate any configuration-related tasks. Only generate tasks specific to the feature. Also, look back at the user story and the features I already wrote tasks for. Try your best to not generate any tasks that I may have already written for my other features.
|
||
|
||
Feature:
|
||
"""
|
||
Name: ${_feature.name}
|
||
Description: ${_feature.description}
|
||
ID: ${_feature.uid}
|
||
"""
|
||
|
||
Each task should have the following:
|
||
"""
|
||
Name
|
||
Description
|
||
Priority (low, medium, high)
|
||
Dependency-Based Order (numeric)
|
||
"""
|
||
`;
|
||
|
||
const res = await completion(openai, prompt, {
|
||
schema: z.object({
|
||
tasks: z
|
||
.array(
|
||
z.object({
|
||
name: z.string().describe("The task name"),
|
||
description: z.string().describe("The task description"),
|
||
priority: z
|
||
.enum(["low", "medium", "high"])
|
||
.describe("The task priority"),
|
||
order: z
|
||
.number()
|
||
.describe(
|
||
"The order in which the task should be implemented"
|
||
),
|
||
})
|
||
)
|
||
.min(MIN_TASKS_PER_FEATURE)
|
||
.max(MAX_TASKS_PER_FEATURE),
|
||
}),
|
||
});
|
||
|
||
const tasks: Task[] = res.data.tasks.map((task) => {
|
||
return {
|
||
...task,
|
||
uid: v4(),
|
||
feature: _feature.uid,
|
||
};
|
||
});
|
||
|
||
console.log("tasks", tasks);
|
||
|
||
return tasks;
|
||
} catch (err) {
|
||
console.error(err);
|
||
return [];
|
||
}
|
||
}
|
||
|
||
async function dedupeTasks() {
|
||
try {
|
||
features.forEach((feature) => {
|
||
console.log("deduping feature ", feature.name);
|
||
let _deps = deps
|
||
.filter((dep) => dep.uid === feature.uid)
|
||
.map((dep) => dep.depUid);
|
||
if (_deps.length > 0) {
|
||
feature.tasks!.forEach((task) => {
|
||
console.log("deduping task ", task.name);
|
||
let otherTasks: Task[] = [];
|
||
_deps.forEach((dep) => {
|
||
otherTasks = [
|
||
...otherTasks,
|
||
...tasks.filter((t) => t.feature === dep),
|
||
];
|
||
});
|
||
dedupeTask(task, otherTasks);
|
||
});
|
||
}
|
||
});
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function dedupeTask(targetTask: Task, otherTasks: Task[]) {
|
||
try {
|
||
const prompt = `You are an AI software project manager. You use agile methodology and the best software project management techniques to plan software projects for indie developers.
|
||
|
||
I am an indie developer creating a new software project. Here are the details:
|
||
"""
|
||
Project Name: ${project?.name}
|
||
Project Description: ${project?.description}
|
||
Tech Stack: ${project?.stack.join(", ")}
|
||
"""
|
||
We have generated a list of features which contain a list of tasks to build out those features. We need to dedupe the tasks so that we don't have any duplicate tasks. Check if this task is a duplicate of any other task in the project. If it is, then return the UID of the task that it is a duplicate of. If it is not a duplicate, then return null.
|
||
|
||
Task:
|
||
"""
|
||
Name: ${targetTask.name}
|
||
Description: ${targetTask.description}
|
||
"""
|
||
|
||
Other Tasks:
|
||
"""
|
||
${otherTasks.map((task) => {
|
||
return `Name: ${task.name}, Description: ${task.description}, ID: ${task.uid}.\n`;
|
||
})}
|
||
"""
|
||
`;
|
||
|
||
// logic to to actually complete and return a boolean
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
const keyHandler = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||
if ((e.key === "Enter" || e.key === "Tab") && stackInput !== "") {
|
||
e.preventDefault();
|
||
setValue("stack", [...form.getValues("stack"), stackInput]);
|
||
setStackInput("");
|
||
}
|
||
};
|
||
return (
|
||
<div className="p-5">
|
||
<Form {...form}>
|
||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||
<FormField
|
||
control={form.control}
|
||
name="name"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>Project Name</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder="" {...field} />
|
||
</FormControl>
|
||
<FormDescription>
|
||
This is the name of your project.
|
||
</FormDescription>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="description"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>Project Description</FormLabel>
|
||
<FormControl>
|
||
<Input placeholder="" {...field} />
|
||
</FormControl>
|
||
<FormDescription>
|
||
This is your project description.
|
||
</FormDescription>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<FormField
|
||
control={form.control}
|
||
name="stack"
|
||
render={({ field }) => (
|
||
<FormItem>
|
||
<FormLabel>Tech Stack</FormLabel>
|
||
<FormControl>
|
||
<Input
|
||
value={stackInput}
|
||
onChange={(e) => setStackInput(e.target.value)}
|
||
onKeyDown={keyHandler}
|
||
/>
|
||
</FormControl>
|
||
<FormDescription>
|
||
This is your project tech stack.
|
||
</FormDescription>
|
||
<div>
|
||
{form.getValues("stack").map((stack) => (
|
||
<Badge
|
||
key={stack}
|
||
className="mr-2 font-normal rounded-md"
|
||
variant="outline"
|
||
>
|
||
<span className="mr-1">{stack}</span>
|
||
<X
|
||
className="inline font-light text-red-500"
|
||
size={16}
|
||
onClick={() =>
|
||
setValue(
|
||
"stack",
|
||
form.getValues("stack").filter((s) => s !== stack)
|
||
)
|
||
}
|
||
/>
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
<FormMessage />
|
||
</FormItem>
|
||
)}
|
||
/>
|
||
<Button type="submit">Submit</Button>
|
||
<Button type="button" onClick={() => setDogs(form)}>
|
||
Prefill Uber Dogs
|
||
</Button>
|
||
</form>
|
||
</Form>
|
||
<h1></h1>
|
||
<Tabs defaultValue="features" className="w-75 m-auto">
|
||
<TabsList>
|
||
<TabsTrigger value="features">Features</TabsTrigger>
|
||
<TabsTrigger value="tasks">Tasks</TabsTrigger>
|
||
<TabsTrigger value="duplicates">Duplicates</TabsTrigger>
|
||
</TabsList>
|
||
<TabsContent value="features">
|
||
<Table>
|
||
<TableCaption>A list of all the generated features.</TableCaption>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>UID</TableHead>
|
||
<TableHead>Name</TableHead>
|
||
<TableHead>Description</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{features!.map((feature) => {
|
||
return (
|
||
<TableRow key={feature.uid}>
|
||
<TableCell
|
||
className="max-w-xs whitespace-nowrap"
|
||
style={{
|
||
maxWidth: "100px",
|
||
overflowX: "scroll",
|
||
overflowY: "hidden",
|
||
}}
|
||
>
|
||
{feature.uid}
|
||
</TableCell>
|
||
<TableCell>{feature.name}</TableCell>
|
||
<TableCell>{feature.description}</TableCell>
|
||
</TableRow>
|
||
);
|
||
})}
|
||
</TableBody>
|
||
</Table>
|
||
</TabsContent>
|
||
<TabsContent value="tasks">
|
||
{features.map((_feature) => {
|
||
return (
|
||
<>
|
||
<div key={_feature.uid}>
|
||
<TaskTable
|
||
feature={_feature}
|
||
tasks={tasks.filter(
|
||
(task) => task.feature === _feature.uid
|
||
)}
|
||
/>
|
||
<hr className="my-10" />
|
||
</div>
|
||
</>
|
||
);
|
||
})}
|
||
</TabsContent>
|
||
<TabsContent value="duplicates">
|
||
This is where deduping logic will be displayed.
|
||
</TabsContent>
|
||
</Tabs>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const TaskTable = (props: any) => {
|
||
return (
|
||
<Table>
|
||
<TableCaption>Tasks for the feature {props.feature.name}.</TableCaption>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>UID</TableHead>
|
||
<TableHead>Name</TableHead>
|
||
<TableHead>Description</TableHead>
|
||
<TableHead>Priority</TableHead>
|
||
<TableHead>Order</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{props.tasks.map((task: Task) => {
|
||
return (
|
||
<TableRow key={task.name}>
|
||
<TableCell
|
||
className="max-w-xs whitespace-nowrap"
|
||
style={{
|
||
maxWidth: "100px",
|
||
overflowX: "scroll",
|
||
overflowY: "hidden",
|
||
}}
|
||
>
|
||
{task.uid}
|
||
</TableCell>
|
||
<TableCell>{task.name}</TableCell>
|
||
<TableCell>{task.description}</TableCell>
|
||
<TableCell>{task.priority}</TableCell>
|
||
<TableCell>{task.order}</TableCell>
|
||
</TableRow>
|
||
);
|
||
})}
|
||
</TableBody>
|
||
</Table>
|
||
);
|
||
};
|
||
|
||
const setDogs = (form: any) => {
|
||
form.setValue("name", "Uber Dogs");
|
||
form.setValue(
|
||
"description",
|
||
"Uber Dogs is a mobile app that allows users to order dogs on demand."
|
||
);
|
||
form.setValue("stack", ["Django", "google-maps-api", "GraphQL"]);
|
||
};
|
||
|
||
/*
|
||
|
||
Electronic Medical Record System
|
||
|
||
The Shrivatsa Center for Interdimensional Healing requires a tailored open source electronic medical record (EMR) system designed with an intuitive user interface that supports the seamless management of appointments, comprehensive patient records, laboratory results, and integrated billing. This platform should prioritize security and patient confidentiality while providing real-time access to health data, facilitating efficient communication between departments, and incorporating advanced features such as appointment reminders, customizable fields for interdimensional treatment modalities, and automated billing processes that can adapt to a variety of insurance providers and self-pay scenarios. The system must be scalable and flexible to evolve with the center’s expanding interdimensional healthcare services.
|
||
|
||
*/
|