Skip to content

Commit

Permalink
build(feat): regeneration server actions
Browse files Browse the repository at this point in the history
  • Loading branch information
NemesisLW committed Jul 12, 2024
1 parent 70dc31d commit 1172676
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 44 deletions.
4 changes: 4 additions & 0 deletions components/generate-cta-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { cn } from "@/lib/utils";
import Image from "next/image";
import { Button } from "./ui/button";

// TODO: Add a loading state for the button

function GenerateButton({
className,
text = "Generate one for me",
isServerAction = false,
pending = false,
formAction,
}: GenerateButtonProps) {
return (
<Button
Expand All @@ -16,6 +19,7 @@ function GenerateButton({
)}
type={isServerAction ? "submit" : "button"}
aria-disabled={isServerAction && pending}
formAction={formAction}
>
<span className="flex items-center justify-center space-x-1 sm:space-x-2">
<Image
Expand Down
10 changes: 5 additions & 5 deletions components/pickupline-generator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@ import { useFormState } from "react-dom";
import PickupLineInputForm from "./pickupline-generator/input-form";
import OutputForm from "./pickupline-generator/output-form";

const initialState: {
message: string;
pickupLines?: string[];
} = { message: "" };
const initialState: GenerateOutputState = { message: "" };

function PickupLineGenerator() {
const [state, formAction] = useFormState(generateOutput, initialState);
Expand All @@ -28,7 +25,10 @@ function PickupLineGenerator() {
{!state.pickupLines ? (
<PickupLineInputForm formAction={formAction} />
) : (
<OutputForm pickupLines={state.pickupLines} />
<OutputForm
pickupLines={state.pickupLines}
InitialFormState={state.InitialFormState}
/>
)}
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions components/pickupline-generator/input-form.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import { Textarea } from "../ui/textarea";
import { Input } from "../ui/input";
import GenerateButton from "../generate-cta-button";
import { useFormStatus } from "react-dom";
import GenerateButton from "../generate-cta-button";
import { Input } from "../ui/input";
import { Textarea } from "../ui/textarea";

function PickupLineInputForm({
formAction,
Expand Down
38 changes: 33 additions & 5 deletions components/pickupline-generator/output-form.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
import { regenerateOutput } from "@/lib/ai/actions";
import { copyToClipboard } from "@/lib/utils";
import Image from "next/image";
import React from "react";
import { useFormState } from "react-dom";
import { toast } from "sonner";
import GenerateButton from "../generate-cta-button";
import { Card, CardContent } from "../ui/card";

function OutputForm({ pickupLines }: { pickupLines: string[] }) {
// TODO: Debug Multiple Regeneration issues

function OutputForm({ pickupLines, InitialFormState }: FormOutputProps) {
// const formActionPayload = regenerateOutput.bind(null, InitialFormState!);

const initialState: GenerateOutputState = {
message: "regenerating...",
InitialFormState: {
crushDescription: InitialFormState?.crushDescription!,
style: InitialFormState?.style!,
},
};
const [state, formAction] = useFormState(regenerateOutput, initialState);

if (state.message === "error") {
toast.error("Error regenerating pickup lines.");
console.log(state.pickupLines);
}

const currentPickupLines = state.pickupLines || pickupLines;

return (
<div className="mx-auto w-full max-w-lg space-y-4">
<h2 className="text-center text-xl text-[#A5455C] mb-4">
Copy the below pick up lines
</h2>

{pickupLines.map((line, index) => (
{currentPickupLines.map((line, index) => (
<Card key={index} className="border-2 border-[#FF2157] bg-white">
<CardContent className="py-5 px-6">
<div className="mb-2 flex items-center justify-between">
Expand All @@ -26,9 +49,14 @@ function OutputForm({ pickupLines }: { pickupLines: string[] }) {
</CardContent>
</Card>
))}
<div className="pt-4">
<GenerateButton className="w-full" text="Regenerate Pickup Line" />
</div>
<form className="pt-4">
<GenerateButton
className="w-full"
text="Regenerate Pickup Line"
formAction={formAction}
isServerAction
/>
</form>
</div>
);
}
Expand Down
91 changes: 64 additions & 27 deletions lib/ai/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import Instructor from "@instructor-ai/instructor";
import { OpenAI } from "openai";
import { FormSchema, OutputSchema } from "../schema";
import { parsePickupLineTexts } from "../utils";
import { OutputSchema } from "../schema";
import { parseFormData, parsePickupLineTexts } from "../utils";
import { promptInstructions } from "./instructions";

// Anyscale Inference for Mixtral (LLM)
Expand All @@ -24,15 +24,36 @@ const client = Instructor({
mode: "JSON_SCHEMA",
});

async function generatePickupLines(
formData: FormInputProps,
): Promise<string[]> {
// call the Mixtral API to generate the pickup lines

const PickupLines = await client.chat.completions.create({
model: "mistralai/Mixtral-8x7B-Instruct-v0.1",
messages: [
{ role: "system", content: promptInstructions },
{
role: "user",
content: `Description of my crush: ${formData.crushDescription}\nStyle of pickup lines: ${formData.style}`,
},
],
response_model: { schema: OutputSchema, name: "PickupLines" },
max_tokens: 1000,
max_retries: 3,
temperature: 1,
});

// parse the pickup line texts from the data
return parsePickupLineTexts(PickupLines);
}

export async function generateOutput(
prevState: { message: string },
prevState: GenerateOutputState,
formData: FormData,
) {
): Promise<GenerateOutputState> {
// validate the form data using the schema
const validatedInputs = FormSchema.safeParse({
crushDescription: formData.get("crushDescription"),
style: formData.get("style"),
});
const validatedInputs = parseFormData(formData);

// Return early if the form data is invalid
if (!validatedInputs.success) {
Expand All @@ -43,32 +64,48 @@ export async function generateOutput(
}

try {
// call the Mixtral API to generate the pickup lines
const PickupLines = await client.chat.completions.create({
model: "mistralai/Mixtral-8x7B-Instruct-v0.1",
messages: [
{ role: "system", content: promptInstructions },
{
role: "user",
content: `Description of my crush: ${validatedInputs.data.crushDescription}\nStyle of pickup lines: ${validatedInputs.data.style}`,
},
],
response_model: { schema: OutputSchema, name: "PickupLines" },
max_tokens: 1000,
max_retries: 3,
});

// parse the pickup line texts from the data
const listPickupLines = parsePickupLineTexts(PickupLines);
console.log("generating pickup lines...");
const listPickupLines = await generatePickupLines(validatedInputs.data);
console.log("First pickup lines generated");
console.log(listPickupLines);
return {
message: "success",
pickupLines: listPickupLines,
InitialFormState: validatedInputs.data,
};
} catch (e) {
console.log(e);
return {
message: "error generating output...",
error: e,
message: "Error generating output.",
errors: e,
};
}
}

export async function regenerateOutput(
prevState: GenerateOutputState,
formData: FormData,
): Promise<GenerateOutputState> {
try {
console.log("regenerating pickup lines...");
console.log(prevState.InitialFormState?.crushDescription);
const listPickupLines = await generatePickupLines(
prevState.InitialFormState!,
);

console.log("New pickup lines generated");
console.log(listPickupLines);
return {
message: "success",
pickupLines: listPickupLines,
};
} catch (e) {
console.error(e);
return {
message: "error",
pickupLines: prevState.pickupLines, // Keep the previous pickup lines
InitialFormState: prevState.InitialFormState,
errors: e instanceof Error ? e.message : "Unknown error occurred",
};
}
}
5 changes: 3 additions & 2 deletions lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
interface FormInputProps {
prevStyle: string;
prevCrushDescription: string;
style: string;
crushDescription: string;
}

interface GenerateButtonProps {
className?: string;
text?: string;
isServerAction?: boolean;
pending?: boolean;
formAction?: (formData: FormData) => void | ((payload: FormData) => void);
}

interface GenerateOutputProps {
Expand Down
10 changes: 8 additions & 2 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { clsx, type ClassValue } from "clsx";
import { toast } from "sonner";
import { twMerge } from "tailwind-merge";
import { OutputSchema, OutputSchemaType } from "./schema";
import { FormSchema, OutputSchema, OutputSchemaType } from "./schema";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
Expand All @@ -19,11 +19,17 @@ export const copyToClipboard = (text: string) => {
});
};

export function parseFormData(formData: FormData) {
return FormSchema.safeParse({
crushDescription: formData.get("crushDescription"),
style: formData.get("style"),
});
}

// parse the pickup line texts from the data
export function parsePickupLineTexts(data: OutputSchemaType): string[] {
// validatie using the schema
const parsedData = OutputSchema.parse(data);

const texts = parsedData.pickupLines.map((line) => line.text);

return texts;
Expand Down

0 comments on commit 1172676

Please sign in to comment.