|
"use client" |
|
|
|
import React from "react" |
|
import { IoMdPhonePortrait } from "react-icons/io" |
|
import { GiRollingDices } from "react-icons/gi" |
|
import { ClapImageRatio } from "@aitube/clap" |
|
|
|
import { Card, CardContent, CardHeader } from "@/components/ui/card" |
|
import { Button } from "@/components/ui/button" |
|
import { Toaster } from "@/components/ui/sonner" |
|
import { TextareaField } from "@/components/form/textarea-field" |
|
|
|
import { cn, generateRandomStory } from "@/lib/utils" |
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" |
|
import { useIsBusy, useOrientation, useQueryStringParams, useStoryPromptDraft } from "@/lib/hooks" |
|
import { BottomBar, VideoPreview } from "@/components/interface" |
|
import { MainTitle } from "@/components/interface/main-title" |
|
import { LoadClapButton } from "@/components/interface/load-clap-button" |
|
import { SaveClapButton } from "@/components/interface/save-clap-button" |
|
import { useProcessors } from "@/lib/hooks/useProcessors" |
|
import { Characters } from "@/components/interface/characters" |
|
import { useOAuth } from "@/lib/oauth/useOAuth" |
|
import { AuthWall } from "@/components/interface/auth-wall" |
|
|
|
import { defaultPrompt } from "./config" |
|
import { useStore } from "./store" |
|
|
|
export function Main() { |
|
const { storyPromptDraft, setStoryPromptDraft, promptDraftRef } = useStoryPromptDraft() |
|
const { isBusy } = useIsBusy() |
|
const { imageRatio, toggleOrientation } = useOrientation() |
|
const { handleCreateStory, handleExtendStory } = useProcessors() |
|
useQueryStringParams() |
|
|
|
const currentVideo = useStore(s => s.currentVideo) |
|
|
|
const showAuthWall = useStore(s => s.showAuthWall) |
|
const { isLoggedIn, enableOAuthWall } = useOAuth() |
|
|
|
return ( |
|
<TooltipProvider> |
|
<div className={cn( |
|
`fixed`, |
|
// `bg-zinc-800`, |
|
// old style, more "amber" |
|
// `bg-gradient-to-br from-amber-600 to-yellow-500`, |
|
|
|
// nice style! |
|
// `bg-gradient-to-br from-amber-700 to-yellow-300`, |
|
|
|
// warm orange, a bit flash but not bad, not bad at all |
|
// `bg-gradient-to-br from-orange-700 to-yellow-400`, |
|
|
|
// nice "AiTube" vibe |
|
// `bg-gradient-to-br from-red-700 to-yellow-400`, |
|
|
|
// pretty cool lime! |
|
`bg-gradient-to-br from-lime-700 to-yellow-400`, |
|
|
|
// new style, pretty "fresh" - maybe too bright? |
|
// use a dark logo for this one |
|
// `bg-gradient-to-br from-yellow-200 to-yellow-500`, |
|
|
|
// too pastel |
|
// `bg-gradient-to-br from-yellow-200 to-red-300`, |
|
|
|
// `bg-gradient-to-br from-sky-400 to-sky-300/30`, |
|
`w-screen h-full overflow-y-scroll md:overflow-hidden`, |
|
)} |
|
|
|
// this version is a bit too aggressive on mobile |
|
// style={{ boxShadow: "inset 0 0px 250px 0 rgb(0 0 0 / 60%)" }} |
|
|
|
style={{ boxShadow: "inset 0 0 10vh 0 rgb(0 0 0 / 50%)" }} |
|
> |
|
<div className="flex flex-col w-screen h-screen"> |
|
<div className=" |
|
flex flex-col md:flex-row w-full |
|
items-center md:justify-center |
|
flex-1 |
|
" |
|
> |
|
<div className={cn( |
|
`flex flex-col md:h-full md:items-center md:justify-center`, |
|
`w-full md:w-1/2`, |
|
`transition-all duration-200 ease-in-out`, |
|
`ml-0`, |
|
`pt-4 sm:pt-0`, |
|
)}> |
|
<Card className={cn( |
|
// `shadow-2xl z-30 rounded-3xl`, |
|
`shadow-none`, |
|
`w-full md:ml-[12%] md:w-[95%]`, |
|
`transition-all duration-200 ease-in-out`, |
|
`bg-transparent dark:bg-transparent`, |
|
// `backdrop-blur-2xl dark:backdrop-blur-2xl`, |
|
// `bg-amber-500 dark:bg-amber-500`, |
|
|
|
|
|
`border-transparent dark:border-transparent`, |
|
// `bg-stone-50/90 dark:bg-stone-50/90`, |
|
// `border-yellow-100 dark:border-yellow-100`, |
|
// `bg-yellow-500 dark:bg-yellow-500`, |
|
// `border-yellow-400 dark:border-yellow-400`, |
|
|
|
)}> |
|
<CardHeader> |
|
<MainTitle /> |
|
</CardHeader> |
|
<CardContent |
|
className="flex flex-col space-y-3" |
|
> |
|
|
|
{/* LEFT MENU BUTTONS + MAIN PROMPT INPUT */} |
|
<div className="flex flex-row space-x-3 w-full"> |
|
|
|
|
|
{/* |
|
|
|
TODO: To finish by Julian a bit later |
|
|
|
<div className=" |
|
flex flex-col |
|
|
|
w-32 bg-yellow-600 |
|
transition-all duration-200 ease-in-out |
|
space-y-2 md:space-y-4 |
|
"> |
|
<Input |
|
type="file" |
|
className="" |
|
onChange={async (e: React.ChangeEvent<HTMLInputElement>) => { |
|
if (e.target.files && e.target.files.length > 0) { |
|
const file = e.target.files[0]; |
|
const newImageBase64 = await fileToBase64(file) |
|
setMainCharacterImage(newImageBase64) |
|
} |
|
}} |
|
accept="image/*" |
|
/> |
|
</div> |
|
*/} |
|
|
|
{/* MAIN PROMPT INPUT */} |
|
<div className=" |
|
flex flex-col |
|
flex-1 |
|
transition-all duration-200 ease-in-out |
|
space-y-2 md:space-y-4 |
|
"> |
|
<TextareaField |
|
id="story-prompt-draft" |
|
// label="My story:" |
|
// disabled={modelState != 'ready'} |
|
onChange={(e) => { |
|
setStoryPromptDraft(e.target.value) |
|
promptDraftRef.current = e.target.value |
|
}} |
|
placeholder={defaultPrompt} |
|
inputClassName=" |
|
transition-all duration-200 ease-in-out |
|
h-32 md:h-56 lg:h-64 |
|
|
|
" |
|
disabled={isBusy} |
|
value={storyPromptDraft} |
|
/> |
|
|
|
|
|
{/* END OF MAIN PROMPT INPUT */} |
|
</div> |
|
|
|
{/* END OF LEFT MENU BUTTONS + MAIN PROMPT INPUT */} |
|
</div> |
|
|
|
{/* ACTION BAR */} |
|
|
|
<div className=" |
|
w-full |
|
flex flex-col lg:flex-row |
|
justify-between items-center |
|
space-y-3 lg:space-x-3 lg:space-y-0"> |
|
|
|
{/* |
|
<Button |
|
onClick={() => load()} |
|
disabled={isBusy} |
|
// variant="ghost" |
|
className={cn( |
|
`text-sm md:text-base lg:text-lg`, |
|
`bg-stone-800/90 text-amber-400/100 dark:bg-stone-800/90 dark:text-amber-400/100`, |
|
`font-bold`, |
|
`hover:bg-stone-800/100 hover:text-amber-300/100 dark:hover:bg-stone-800/100 dark:hover:text-amber-300/100`, |
|
storyPromptDraft ? "opacity-100" : "opacity-80" |
|
)} |
|
> |
|
<span className="mr-1">Load project</span> |
|
</Button> |
|
*/} |
|
|
|
<div className=" |
|
flex flex-row |
|
justify-between items-center |
|
space-x-3"> |
|
<LoadClapButton /> |
|
<SaveClapButton /> |
|
<Characters /> |
|
</div> |
|
|
|
<div className=" |
|
flex flex-row |
|
justify-between items-center |
|
space-x-3 |
|
select-none |
|
"> |
|
|
|
{/* RANDOMNESS SWITCH */} |
|
<Tooltip> |
|
<TooltipTrigger asChild><div className=" |
|
flex flex-row |
|
justify-between items-center |
|
cursor-pointer |
|
transition-all duration-150 ease-in-out |
|
hover:scale-110 active:scale-150 |
|
text-stone-800 |
|
hover:text-stone-950 |
|
active:text-black |
|
group |
|
" |
|
onClick={() => { |
|
const randomStory = generateRandomStory() |
|
setStoryPromptDraft(randomStory) |
|
promptDraftRef.current = randomStory |
|
}}> |
|
<div> |
|
</div> |
|
<div className=" |
|
w-6 h-8 |
|
flex flex-row items-center justify-center |
|
transition-all duration-150 ease-out |
|
group-hover:animate-swing |
|
" |
|
> |
|
<GiRollingDices size={24} /> |
|
</div> |
|
</div></TooltipTrigger> |
|
<TooltipContent side="top"> |
|
<p className="text-xs font-normal text-stone-100/90 text-center"> |
|
Generate a random prompt. |
|
</p> |
|
</TooltipContent> |
|
</Tooltip> |
|
{/* END OF RANDOMNESS SWITCH */} |
|
|
|
{/* ORIENTATION SWITCH */} |
|
<div className=" |
|
flex flex-row |
|
justify-between items-center |
|
cursor-pointer |
|
transition-all duration-150 ease-out |
|
hover:scale-110 active:scale-150 |
|
text-stone-800 |
|
hover:text-stone-950 |
|
active:text-black |
|
group |
|
" |
|
onClick={() => toggleOrientation()}> |
|
<div> |
|
</div> |
|
<div className=" |
|
w-8 h-8 |
|
flex flex-row items-center justify-center |
|
transition-all duration-150 ease-in-out |
|
group-hover:animate-swing |
|
" |
|
> |
|
<div className={cn( |
|
`transition-all duration-200 ease-in-out`, |
|
imageRatio === ClapImageRatio.LANDSCAPE ? `rotate-90` : `rotate-0` |
|
)}> |
|
<IoMdPhonePortrait size={24} /> |
|
</div> |
|
</div> |
|
</div> |
|
{/* END OF ORIENTATION SWITCH */} |
|
<Button |
|
onClick={currentVideo ? handleExtendStory : handleCreateStory} |
|
disabled={!storyPromptDraft || isBusy || !isLoggedIn} |
|
// variant="ghost" |
|
className={cn( |
|
`text-base md:text-lg lg:text-xl xl:text-2xl`, |
|
`bg-stone-800/90 text-amber-400/100 dark:bg-stone-800/90 dark:text-amber-400/100`, |
|
`font-bold`, |
|
`hover:bg-stone-800/100 hover:text-amber-300/100 dark:hover:bg-stone-800/100 dark:hover:text-amber-300/100`, |
|
storyPromptDraft ? "opacity-100" : "opacity-80" |
|
)} |
|
> |
|
<span className="mr-1.5">{ |
|
currentVideo ? 'Extend' : 'Create' |
|
}</span><span className="hidden md:inline">π</span><span className="inline md:hidden">π</span> |
|
</Button> |
|
</div> |
|
|
|
{/* END OF ACTION BAR */} |
|
</div> |
|
|
|
</CardContent> |
|
</Card> |
|
</div> |
|
<div className={cn( |
|
`flex flex-col items-center justify-center`, |
|
`flex-1 h-full`, |
|
// `transition-all duration-200 ease-in-out` |
|
|
|
`-mt-[20px] -mb-[90px] md:-mt-0 md:-mb-0`, |
|
)}> |
|
<VideoPreview /> |
|
</div> |
|
</div> |
|
<BottomBar /> |
|
</div> |
|
<Toaster /> |
|
<AuthWall show={showAuthWall} /> |
|
</div> |
|
</TooltipProvider> |
|
); |
|
} |
|
|