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 : (
- }
- sx={{
- // '--Button-gap': 'auto',
- // minWidth: 100,
- justifyContent: 'flex-start',
- transition: 'background-color 0.2s, color 0.2s',
- }}>
- Idea
-
+
+ }
+ sx={{
+ // '--Button-gap': 'auto',
+ // minWidth: 100,
+ justifyContent: 'flex-start',
+ transition: 'background-color 0.2s, color 0.2s',
+ }}>
+ Idea
+
+
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 */}
+ }
+ onClick={handleSimpleEnhance}
+ sx={{
+ boxShadow: (!userHasText || isSimpleEnhancing) ? undefined : '0 6px 6px -6px rgb(var(--joy-palette-primary-darkChannel) / 40%)',
+ borderRadius: 'xs',
+ // boxShadow: 'xs'
+ }}
+ >
+ Enhance
+
- {/* Change / Use idea */}
- {/*{props.isMobile && (*/}
- {/* */}
- {/* */}
- {/* */}
- {/* */}
- {/* */}
- {/* */}
- {/* */}
- {/* */}
- {/*)}*/}
+ {/*}*/}
+ {/* onClick={handleClickMissing}*/}
+ {/* sx={{ borderRadius: 'sm' }}*/}
+ {/*>*/}
+ {/* Restyle*/}
+ {/**/}
- {/* PromptFx */}
- }
- onClick={handleClickMissing}
- sx={{ borderRadius: 'sm' }}
- >
- Enhance
-
-
- {/*}*/}
- {/* 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);