diff --git a/src/apps/draw/DrawCreate.tsx b/src/apps/draw/DrawCreate.tsx index 36bdedd22..1ad7cdf20 100644 --- a/src/apps/draw/DrawCreate.tsx +++ b/src/apps/draw/DrawCreate.tsx @@ -213,33 +213,33 @@ export function DrawCreate(props: { }} > + {/* Draw history (last 50) */} + + {/**/} - {/* */} - {/* {prompts.map((prompt, _index) => {*/} - {/* return (*/} - {/* */} - {/* );*/} - {/* })}*/} - {/* */} - + {/* {prompts.map((prompt, _index) => {*/} + {/* return (*/} + {/* */} + {/* );*/} + {/* })}*/} + {/**/} {/* Fallbac*/} diff --git a/src/apps/draw/create/ButtonPromptFromIdea.tsx b/src/apps/draw/create/ButtonPromptFromIdea.tsx index ceeb0129f..c6a8a4393 100644 --- a/src/apps/draw/create/ButtonPromptFromIdea.tsx +++ b/src/apps/draw/create/ButtonPromptFromIdea.tsx @@ -28,24 +28,26 @@ export function ButtonPromptFromIdea(props: { return props.isMobile ? null : ( - + + + diff --git a/src/apps/draw/create/PromptComposer.tsx b/src/apps/draw/create/PromptComposer.tsx index 98b88d4fa..1e1653b2c 100644 --- a/src/apps/draw/create/PromptComposer.tsx +++ b/src/apps/draw/create/PromptComposer.tsx @@ -14,12 +14,13 @@ import NumbersRoundedIcon from '@mui/icons-material/NumbersRounded'; import RemoveIcon from '@mui/icons-material/Remove'; import StopOutlinedIcon from '@mui/icons-material/StopOutlined'; +import { imaginePromptFromText } from '~/modules/aifn/imagine/imaginePromptFromText'; + import { animationEnterBelow } from '~/common/util/animUtils'; import { lineHeightTextareaMd } from '~/common/app.theme'; import { useUIPreferencesStore } from '~/common/state/store-ui'; import { ButtonPromptFromIdea } from './ButtonPromptFromIdea'; -import { ButtonPromptFromX } from './ButtonPromptFromX'; import { useDrawIdeas } from './useDrawIdeas'; @@ -51,10 +52,11 @@ export function PromptComposer(props: { const [nextPrompt, setNextPrompt] = React.useState(''); const [tempCount, setTempCount] = React.useState(1); const [tempRepeat, setTempRepeat] = React.useState(1); + const [isSimpleEnhancing, setIsSimpleEnhancing] = React.useState(false); const [showMobileRepeat, setShowMobileRepeat] = React.useState(false); // external state - const { currentIdea, nextRandomIdea } = useDrawIdeas(); + const { currentIdea, nextRandomIdea, clearCurrentIdea } = useDrawIdeas(); const enterIsNewline = useUIPreferencesStore(state => state.enterIsNewline); @@ -77,6 +79,7 @@ export function PromptComposer(props: { const handlePromptEnqueue = React.useCallback(() => { setNextPrompt(''); + clearCurrentIdea(); if (nonEmptyPrompt?.trim()) { onPromptEnqueue([{ uuid: uuidv4(), @@ -84,7 +87,7 @@ export function PromptComposer(props: { _repeatCount: isRepeatShown ? tempRepeat : 1, }]); } - }, [isRepeatShown, nonEmptyPrompt, onPromptEnqueue, tempRepeat]); + }, [clearCurrentIdea, isRepeatShown, nonEmptyPrompt, onPromptEnqueue, tempRepeat]); // Type... @@ -109,108 +112,114 @@ export function PromptComposer(props: { // Ideas - const handleIdeaUse = React.useCallback(() => { currentIdeaPrompt && setNextPrompt(currentIdeaPrompt); }, [currentIdeaPrompt]); // PromptFx + const handleSimpleEnhance = React.useCallback(async () => { + if (nonEmptyPrompt?.trim()) { + setIsSimpleEnhancing(true); + const improvedPrompt = await imaginePromptFromText(nonEmptyPrompt, null).catch(console.error); + if (improvedPrompt) + setNextPrompt(improvedPrompt); + setIsSimpleEnhancing(false); + } + }, [nonEmptyPrompt]); - const textEnrichComponents = React.useMemo(() => { + const textEnrichComponents = React.useMemo(() => ( + { - alert('Not implemented yet'); - }; + // layout + display: 'flex', flexFlow: 'row wrap', alignItems: 'center', gap: 1, - return ( - // PromptFx Buttons - - // layout - display: 'flex', flexFlow: 'row wrap', alignItems: 'center', gap: 1, + {/* Change / Use idea */} + {/*{props.isMobile && (*/} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + {/*)}*/} - // Buttons (tagged by class) - [`& .${promptButtonClass}`]: { - '--Button-gap': '1.2rem', - transition: 'background-color 0.2s, color 0.2s', - minWidth: 100, - }, - }}> + {/* PromptFx */} + - {/* Change / Use idea */} - {/*{props.isMobile && (*/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/* */} - {/*)}*/} + {/*}*/} + {/* onClick={handleClickMissing}*/} + {/* sx={{ borderRadius: 'sm' }}*/} + {/*>*/} + {/* Restyle*/} + {/**/} - {/* PromptFx */} - - - {/*}*/} - {/* onClick={handleClickMissing}*/} - {/* sx={{ borderRadius: 'sm' }}*/} - {/*>*/} - {/* Restyle*/} - {/**/} - - - {tempCount > 1 && setTempCount(count => count - 1)}> - - } - {tempCount > 1 && <> - - - - - - - - } - setTempCount(count => count + 1)}> - + + {tempCount > 1 && setTempCount(count => count - 1)}> + + } + {tempCount > 1 && <> + + - + + + + + } + setTempCount(count => count + 1)}> + + + - - {/* Char counter */} - {/**/} - {/* {!!nonEmptyPrompt?.length && nonEmptyPrompt.length.toLocaleString()}*/} - {/**/} - - ); - }, [tempCount, userHasText]); + {/* Char counter */} + {/**/} + {/* {!!nonEmptyPrompt?.length && nonEmptyPrompt.length.toLocaleString()}*/} + {/**/} + + ), [handleSimpleEnhance, isSimpleEnhancing, tempCount, userHasText]); return ( @@ -235,9 +244,9 @@ export function PromptComposer(props: { - - - + {/**/} + {/* */} + {/**/} {/**/} {/* */} {/**/} @@ -250,7 +259,7 @@ export function PromptComposer(props: { - + {/**/} {/**/} @@ -268,7 +277,7 @@ export function PromptComposer(props: { value={nextPrompt} onChange={handleTextareaTextChange} onKeyDown={handleTextareaKeyDown} - startDecorator={textEnrichComponents} + endDecorator={textEnrichComponents} slotProps={{ textarea: { enterKeyHint: enterIsNewline ? 'enter' : 'send', diff --git a/src/apps/draw/create/useDrawIdeas.tsx b/src/apps/draw/create/useDrawIdeas.tsx index 3423e0953..2010653e0 100644 --- a/src/apps/draw/create/useDrawIdeas.tsx +++ b/src/apps/draw/create/useDrawIdeas.tsx @@ -52,5 +52,9 @@ export function useDrawIdeas() { }); }, []); - return { allIdeas, currentIdea, nextRandomIdea }; + const clearCurrentIdea = React.useCallback(() => { + setCurrentIdea(null); + }, []); + + return { allIdeas, currentIdea, nextRandomIdea, clearCurrentIdea }; } \ No newline at end of file diff --git a/src/modules/aifn/imagine/imaginePromptFromText.ts b/src/modules/aifn/imagine/imaginePromptFromText.ts index ce3034afa..4ae839e5d 100644 --- a/src/modules/aifn/imagine/imaginePromptFromText.ts +++ b/src/modules/aifn/imagine/imaginePromptFromText.ts @@ -1,24 +1,33 @@ -import { getFastLLMId } from '~/modules/llms/store-llms'; +import { getChatLLMId } from '~/modules/llms/store-llms'; import { llmChatGenerateOrThrow, VChatMessageIn } from '~/modules/llms/llm.client'; const simpleImagineSystemPrompt = - `As an AI art prompt writer, create captivating prompts using adjectives, nouns, and artistic references that a non-technical person can understand. + `As an AI image generation prompt writer, create captivating but clear and simple prompts using adjectives, nouns, and artistic references that a non-technical person can understand. Craft creative, coherent and descriptive captions to guide the AI in generating visually striking artwork. -Provide output as a lowercase prompt and nothing else.`; +Follow best practices such as beginning with 'A [photo of, drawing of, ...] {subject} ...', using objective words that are unambiguous to visualize. +Write a minimum of 20-30 words prompt and up to the size of the input. +Provide output a single image generation prompt and nothing else.`; /** * Creates a caption for a drawing or photo given some description - used to elevate the quality of the imaging */ -export async function imaginePromptFromText(messageText: string, contextRef: string): Promise { - const fastLLMId = getFastLLMId(); - if (!fastLLMId) return null; +export async function imaginePromptFromText(messageText: string, contextRef: string | null): Promise { + // we used the fast LLM, but let's just converge to the chat LLM here + const chatLLMId = getChatLLMId(); + if (!chatLLMId) return null; + + // truncate the messageText to full words and up to 1000 characters + const lastSpace = messageText.slice(0, 1000).lastIndexOf(' '); + messageText = messageText.slice(0, lastSpace > 0 ? lastSpace : 1000); + if (!/[.!?]$/.test(messageText)) messageText += '.'; + try { const instructions: VChatMessageIn[] = [ { role: 'system', content: simpleImagineSystemPrompt }, - { role: 'user', content: 'Write a prompt, based on the following input.\n\n```\n' + messageText.slice(0, 1000) + '\n```\n' }, + { role: 'user', content: 'Write a minimum of 20-30 words prompt and up to the size of the input, based on the INPUT below.\n\nINPUT:\n' + messageText }, ]; - const chatResponse = await llmChatGenerateOrThrow(fastLLMId, instructions, 'draw-expand-prompt', contextRef, null, null); + const chatResponse = await llmChatGenerateOrThrow(chatLLMId, instructions, 'draw-expand-prompt', contextRef, null, null); return chatResponse.content?.trim() ?? null; } catch (error: any) { console.error('imaginePromptFromText: fetch request error:', error);