From a39930e8c6cc7795d469ae0932d93d2a2633d6a0 Mon Sep 17 00:00:00 2001 From: Tom Wise <79859203+tomwisecodes@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:53:49 +0000 Subject: [PATCH] chore: regeneration --- .../src/app/image-spike/[slug]/images.tsx | 155 ++++++++++++------ .../src/components/RegenerationForm.tsx | 96 +++++++++++ apps/nextjs/src/hooks/useImageSearch.ts | 67 ++++++++ packages/api/src/router/imageGen.ts | 135 ++++++++++++++- 4 files changed, 406 insertions(+), 47 deletions(-) create mode 100644 apps/nextjs/src/components/RegenerationForm.tsx diff --git a/apps/nextjs/src/app/image-spike/[slug]/images.tsx b/apps/nextjs/src/app/image-spike/[slug]/images.tsx index a70b78c63..dfa3eaaf0 100644 --- a/apps/nextjs/src/app/image-spike/[slug]/images.tsx +++ b/apps/nextjs/src/app/image-spike/[slug]/images.tsx @@ -12,6 +12,7 @@ import Link from "next/link"; import type { ImageResponse } from "types/imageTypes"; import LoadingWheel from "@/components/LoadingWheel"; +import { RegenerationForm } from "@/components/RegenerationForm"; export type Cycle = { title: string; @@ -70,8 +71,51 @@ const ImagesPage = ({ pageData }) => { }, ]); + // In your ImagesPage component, add this state + const [isRegenerating, setIsRegenerating] = useState(false); + const { bestImage, findBestImage } = useBestImage({ pageData }); - const { fetchImages, availableSources } = useImageSearch({ pageData }); + const { fetchImages, availableSources, regenerateImageWithAnalysis } = + useImageSearch({ pageData }); + + // Add this function to handle regeneration + const handleRegeneration = async (columnId: string, feedback: string) => { + const column = comparisonColumns.find((col) => col.id === columnId); + if (!column?.imageSearchBatch?.[0]) return; + + const image = column.imageSearchBatch[0]; + + updateColumn(columnId, { isLoading: true }); + + if (!image.imageSource) { + throw new Error("Image source is required to regenerate image."); + } + + try { + setIsRegenerating(true); + const regeneratedImage = await regenerateImageWithAnalysis( + image.url, + selectedImagePrompt, + feedback, + image.imageSource.toLowerCase().includes("Stability AI") + ? "stability" + : "openai", + ); + + setIsRegenerating(false); + updateColumn(columnId, { + imageSearchBatch: [regeneratedImage], + }); + } catch (error) { + setIsRegenerating(false); + console.error("Error regenerating image:", error); + updateColumn(columnId, { + error: "Failed to regenerate image", + }); + } finally { + updateColumn(columnId, { isLoading: false }); + } + }; if ( !pageData?.lessonPlan?.cycle1?.explanation?.imagePrompt || @@ -196,8 +240,8 @@ const ImagesPage = ({ pageData }) => { {promptConstructor( prompt, pageData.title, - pageData.keyStage, pageData.subject, + pageData.keyStage, pageData.lessonPlan, )}

@@ -348,58 +392,77 @@ const ImagesPage = ({ pageData }) => { {column.imageSearchBatch && (
- {column.imageSearchBatch?.map((image) => ( -
-
- {image.alt -
-
-

- License:{" "} - {image.license} -

-

- Score:{" "} - {image.appropriatenessScore} -

-

- Prompt used:{" "} - {image.imagePrompt} -

-

- Reasoning:{" "} - {image.appropriatenessReasoning} -

- {image.photographer && ( + {column.imageSearchBatch?.map((image) => { + console.log("iumage", image); + return ( +
+
+ {isRegenerating ? ( + + ) : ( + {image.alt + )} +
+ {(image.imageSource === "DAL-E" || + image.imageSource?.includes( + "Stable Diffusion", + )) && ( + + handleRegeneration(column.id, feedback) + } + imageSource={image.imageSource} + /> + )} +

- By:{" "} - {image.photographer} + License:{" "} + {image.license}

- )} - {image.title && (

- Title:{" "} - {image.title} + Score:{" "} + {image.appropriatenessScore} +

+

+ Prompt used:{" "} + {image.imagePrompt}

- )} -
-

Fetch: {image.timing.fetch.toFixed(2)}ms

- Validation: {image.timing.validation.toFixed(2)}ms + Reasoning:{" "} + {image.appropriatenessReasoning}

-

Total: {image.timing.total.toFixed(2)}ms

+ {image.photographer && ( +

+ By:{" "} + {image.photographer} +

+ )} + {image.title && ( +

+ Title:{" "} + {image.title} +

+ )} +
+

Fetch: {image.timing.fetch.toFixed(2)}ms

+

+ Validation: {image.timing.validation.toFixed(2)} + ms +

+

Total: {image.timing.total.toFixed(2)}ms

+
-
- ))} + ); + })}
)}
diff --git a/apps/nextjs/src/components/RegenerationForm.tsx b/apps/nextjs/src/components/RegenerationForm.tsx new file mode 100644 index 000000000..e3be99d28 --- /dev/null +++ b/apps/nextjs/src/components/RegenerationForm.tsx @@ -0,0 +1,96 @@ +import React, { useState } from "react"; + +import { OakIcon } from "@oaknational/oak-components"; +import * as Dialog from "@radix-ui/react-dialog"; + +import LoadingWheel from "@/components/LoadingWheel"; + +import { Icon } from "./Icon"; + +interface RegenerationFormProps { + onSubmit: (feedback: string) => Promise; + imageSource: string; +} + +export const RegenerationForm: React.FC = ({ + onSubmit, + imageSource, +}) => { + const [isOpen, setIsOpen] = useState(false); + const [feedback, setFeedback] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + try { + await onSubmit(feedback); + setIsOpen(false); + setFeedback(""); + } catch (error) { + console.error("Error regenerating image:", error); + } finally { + setIsLoading(false); + } + }; + + return ( + + + + + + + + + + Regenerate Image + + +
+
+ +