diff --git a/components/generate/GenerateStoryComponent.tsx b/components/generate/GenerateStoryComponent.tsx
index 02cf61efd..1f792a9b0 100644
--- a/components/generate/GenerateStoryComponent.tsx
+++ b/components/generate/GenerateStoryComponent.tsx
@@ -1,59 +1,16 @@
'use-client';
-import Image from 'next/image';
-import chatOperations from 'operations/chatOperations';
-import imageOperations from 'operations/imageOperations';
-import { useState } from 'react';
import GenerateStoryContextProvider, { IGenerateStoryContext } from './GenerateStoryContext';
+import StoryPDFViewer from 'components/pdf/StoryPDFViewer';
export default function GenerateStoryComponent() {
- const [loading, setLoading] = useState(false);
- const [data, setData] = useState();
- const [cover, setCover] = useState('');
-
- const getStory = async () => {
- setLoading(true);
- const data = await chatOperations.createStoryAsync();
- setData(data);
- setLoading(false);
- };
-
- const getCover = async () => {
- setLoading(true);
- const data = await imageOperations.createImageAsync(
- 'make a cover image of the best book in the world for children'
- );
- const coverURL = data.text[0].url;
- setCover(coverURL);
- setLoading(false);
- };
-
return (
- {({ story }: IGenerateStoryContext) => {
+ {({ story, loading }: IGenerateStoryContext) => {
return (
-
-
-
-
-
- {loading ? Loading...
: null}
-
- {cover &&
}
-
- {JSON.stringify(data)}
-
- {JSON.stringify(data)}
-
+
+ {loading ?
Loading...
: null}
+ {story &&
}
+
);
}}
diff --git a/components/generate/GenerateStoryContext.tsx b/components/generate/GenerateStoryContext.tsx
index 79b048c88..251161cd5 100644
--- a/components/generate/GenerateStoryContext.tsx
+++ b/components/generate/GenerateStoryContext.tsx
@@ -1,39 +1,55 @@
'use client';
-import { IStory } from 'operations/chatOperations';
-import { PropsWithChildren, createContext, useContext, useMemo, useState } from 'react';
+import chatOperations, { IStory } from 'operations/chatOperations';
+import {
+ PropsWithChildren,
+ createContext,
+ useCallback,
+ useContext,
+ useMemo,
+ useState
+} from 'react';
export interface IGenerateStoryContext {
story?: IStory;
- setStory: (story: IStory) => void;
- images: string[];
- setImages: (images: string[]) => void;
+ loading: boolean;
}
const GenerateStoryContext = createContext({
story: undefined,
- setStory: () => {},
- images: [],
- setImages: () => {}
+ loading: false
});
function GenerateStoryContextProvider({ children }: { children: PropsWithChildren }) {
+ const [loading, setLoading] = useState(false);
const [story, setStory] = useState();
- /*
- Note(Benson): For now images is an array of urls where each index in the array
- corresponds to the page number.
- i.e., index 0 could be title, index 1 is the first page in pages, etc.
- */
- const [images, setImages] = useState([]);
- const value = useMemo(
- () => ({ story, setStory, images, setImages }),
- [story, setStory, images, setImages]
- );
+ const value = useMemo(() => ({ story, loading }), [story, loading]);
+
+ /*
+ TODO(Benson -> Patricio): Make network call to get images
+ TODO(Benson -> Patricio): Write a helper function in this directory /generate/utils.ts
+ called mergeImages(story: IStory, images: string[]): IStory;
+ */
+ const getStoryAsync = useCallback(async () => {
+ setLoading(true);
+ const story = await chatOperations.createStoryAsync();
+ // const images = await imageOperations.getStoryImagesAsync(story);
+ setStory(story);
+ setLoading(false);
+ }, []);
return (
- {typeof children === 'function' ? children(value) : children}
+ <>
+
+ {typeof children === 'function' ? children(value) : children}
+ >
);
}
diff --git a/components/pdf/StoryPDFViewer.tsx b/components/pdf/StoryPDFViewer.tsx
new file mode 100644
index 000000000..9c66ea2a4
--- /dev/null
+++ b/components/pdf/StoryPDFViewer.tsx
@@ -0,0 +1,176 @@
+'use client';
+
+import {
+ Font,
+ Image,
+ Document as PDFDocument,
+ Page,
+ StyleSheet,
+ Text,
+ View
+} from '@react-pdf/renderer';
+import dynamic from 'next/dynamic';
+import { IStory } from 'operations/chatOperations';
+
+export const runtime = 'edge';
+
+const PDFViewerNoSSR = dynamic(() => import('@react-pdf/renderer').then((mod) => mod.PDFViewer), {
+ ssr: false
+});
+
+export default function StoryPDFViewer({ story }: { story: IStory }) {
+ return (
+
+ );
+}
+
+Font.register({
+ family: 'JosefinSans',
+ fonts: [
+ { src: '/fonts/Josefin_Sans/static/JosefinSans-Thin.ttf', fontWeight: 100 },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-ExtraLight.ttf',
+ fontWeight: 200
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-Light.ttf',
+ fontWeight: 300
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-Regular.ttf',
+ fontWeight: 400
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-Medium.ttf',
+ fontWeight: 500
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-SemiBold.ttf',
+ fontWeight: 600
+ },
+ { src: '/fonts/Josefin_Sans/static/JosefinSans-Bold.ttf', fontWeight: 700 },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-ExtraBold.ttf',
+ fontWeight: 800
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-Black.ttf',
+ fontWeight: 900
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-ThinItalic.ttf',
+ fontStyle: 'italic',
+ fontWeight: 100
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-ExtraLightItalic.ttf',
+ fontStyle: 'italic',
+ fontWeight: 200
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-LightItalic.ttf',
+ fontStyle: 'italic',
+ fontWeight: 300
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-Italic.ttf',
+ fontStyle: 'italic',
+ fontWeight: 400
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-MediumItalic.ttf',
+ fontStyle: 'italic',
+ fontWeight: 500
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-SemiBoldItalic.ttf',
+ fontStyle: 'italic',
+ fontWeight: 600
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-BoldItalic.ttf',
+ fontStyle: 'italic',
+ fontWeight: 700
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-ExtraBoldItalic.ttf',
+ fontStyle: 'italic',
+ fontWeight: 800
+ },
+ {
+ src: '/fonts/Josefin_Sans/static/JosefinSans-BlackItalic.ttf',
+ fontStyle: 'italic',
+ fontWeight: 900
+ }
+ ]
+});
+
+const imgURL =
+ 'https://cdn.discordapp.com/attachments/989274756341706822/1175024578578366534/pinturillu_sian_couple_of_men_illustration_fantasy_Charlie_Bowa_1c51f19c-d5b9-4b53-b32f-e5468912bd1d.png?ex=6569b9ea&is=655744ea&hm=8dd0e4e5653bb9f7a7330f745983035f93e1891279351efe2dd6be7657987d88&';
+
+/*
+Note(Benson): PDFTest has some hardcoded implementation of generating random sections.
+Right now for the Story viewer just render all the text at the bottom until we come up
+with a solidified way at dispersing the text throughout the page.
+*/
+
+// TODO(Benson -> Patricio): replace hardcoded images.
+const Document = ({ pages }: { pages: { text: string }[] }) => {
+ return (
+
+ {pages.map(({ text }, index) => {
+ return (
+
+
+ {text}
+
+
+
+ );
+ })}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ pdfContainer: { width: '100%', height: '100%' },
+ image: {
+ width: '100%',
+ height: '100%',
+ position: 'absolute',
+ zIndex: -1,
+ top: 0
+ },
+ page: {
+ position: 'relative',
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'flex-start'
+ },
+ section: {
+ position: 'absolute',
+ margin: 30,
+ fontFamily: 'JosefinSans',
+ /* Background properties */
+ backgroundColor: 'rgba(255, 255, 255, 0.5)' /* semi-transparent white background */,
+ /* Blur effect */
+ backdropFilter: 'blur(10px)',
+ /* Additional styling for aesthetics */
+ padding: 12,
+ borderRadius: 16,
+ boxShadow: '0 4px 8px rgba(0, 0, 0, 0.1)',
+ fontSize: 20,
+ width: '90%'
+ }
+});
diff --git a/lib/utils/usePromiseMemo.ts b/lib/utils/usePromiseMemo.ts
new file mode 100644
index 000000000..6018842b3
--- /dev/null
+++ b/lib/utils/usePromiseMemo.ts
@@ -0,0 +1,56 @@
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import dependenciesMatch from 'utils/dependenciesMatch';
+
+const usePromiseMemo = (
+ promise: () => Promise,
+ nextDeps: unknown[]
+): { results?: T; error?: E; loading: boolean; refetch: () => void } => {
+ const [results, setResults] = useState();
+ const [error, setError] = useState();
+ const [hasFinished, setHasFinished] = useState(false);
+ const dependencies = useRef(nextDeps);
+
+ const isMounted = useRef(true);
+
+ const checkIfPromiseIsStillValid = useCallback(
+ (dependenciesAtTimeOfPromise: unknown[]): boolean => {
+ return (
+ isMounted.current && dependenciesMatch(dependenciesAtTimeOfPromise, dependencies.current)
+ );
+ },
+ []
+ );
+
+ const run = useCallback(() => {
+ setHasFinished(false);
+ promise()
+ .then((r) => checkIfPromiseIsStillValid(nextDeps) && setResults(r))
+ .catch((e) => checkIfPromiseIsStillValid(nextDeps) && setError(e))
+ .finally(() => checkIfPromiseIsStillValid(nextDeps) && setHasFinished(true));
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, nextDeps);
+
+ useEffect(() => {
+ isMounted.current = true;
+ dependencies.current = nextDeps;
+ run();
+ return () => {
+ isMounted.current = false;
+ };
+ // nextDeps is already a dependency of run
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [run]);
+
+ return useMemo(
+ () => ({
+ results,
+ error,
+ loading: !hasFinished,
+ refetch: run
+ }),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [hasFinished, results]
+ );
+};
+
+export default usePromiseMemo;
diff --git a/operations/chatOperations.ts b/operations/chatOperations.ts
index b9b4936e9..85344345d 100644
--- a/operations/chatOperations.ts
+++ b/operations/chatOperations.ts
@@ -17,13 +17,20 @@ function getFunctionCallArguments(response: any) {
return JSON.parse(response.text.function_call.arguments);
}
+/*
+ TODO(Benson -> Patricio): update this typing? add images to pages? assuming images
+ and pages are 1:1? that means we should probably also have a titleImage property too then?
+ Added dummy filler.
+ */
export interface IStory {
title: string;
+ // titleImage?: string;
topic: string;
introduction: string;
narrativeStructure: string;
archetypes_characters: string;
pages: { text: string }[];
+ // pages: { text: string, image?: string }[];
}
async function createStoryAsync(
@@ -40,9 +47,13 @@ async function createStoryAsync(
content: userPrompt
}
];
- const data = await post('/api/open-ai/chat', generateRequestPayload(messages));
- // const data = await post('/api/revalidate', generateRequestPayload(messages));
- return getFunctionCallArguments(data);
+ try {
+ const data = await post('/api/open-ai/chat', generateRequestPayload(messages));
+ return getFunctionCallArguments(data);
+ } catch (e) {
+ console.error(e);
+ throw e;
+ }
}
export default { createStoryAsync };