Commit
·
4c17c06
1
Parent(s):
bf8e54b
added custom user instructions in the creative modifier
Browse files
frontend/app/creative/modify/page.tsx
CHANGED
|
@@ -39,6 +39,7 @@ export default function CreativeModifyPage() {
|
|
| 39 |
const [concept, setConcept] = useState("");
|
| 40 |
const [mode, setMode] = useState<ModificationMode>("modify");
|
| 41 |
const [imageModel, setImageModel] = useState<string | null>(null);
|
|
|
|
| 42 |
|
| 43 |
// Result state
|
| 44 |
const [result, setResult] = useState<ModifiedImageResult | null>(null);
|
|
@@ -104,6 +105,7 @@ export default function CreativeModifyPage() {
|
|
| 104 |
concept: concept.trim() || undefined,
|
| 105 |
mode,
|
| 106 |
image_model: imageModel,
|
|
|
|
| 107 |
});
|
| 108 |
|
| 109 |
if (response.status !== "success" || !response.image) {
|
|
@@ -118,7 +120,7 @@ export default function CreativeModifyPage() {
|
|
| 118 |
} finally {
|
| 119 |
setIsLoading(false);
|
| 120 |
}
|
| 121 |
-
}, [originalImageUrl, analysis, angle, concept, mode, imageModel]);
|
| 122 |
|
| 123 |
// Reset to start over
|
| 124 |
const handleStartOver = useCallback(() => {
|
|
@@ -130,6 +132,7 @@ export default function CreativeModifyPage() {
|
|
| 130 |
setConcept("");
|
| 131 |
setMode("modify");
|
| 132 |
setImageModel(null);
|
|
|
|
| 133 |
setResult(null);
|
| 134 |
setGeneratedPrompt(null);
|
| 135 |
setError(null);
|
|
@@ -352,10 +355,12 @@ export default function CreativeModifyPage() {
|
|
| 352 |
concept={concept}
|
| 353 |
mode={mode}
|
| 354 |
imageModel={imageModel}
|
|
|
|
| 355 |
onAngleChange={setAngle}
|
| 356 |
onConceptChange={setConcept}
|
| 357 |
onModeChange={setMode}
|
| 358 |
onImageModelChange={setImageModel}
|
|
|
|
| 359 |
onSubmit={handleModify}
|
| 360 |
isLoading={isLoading}
|
| 361 |
/>
|
|
|
|
| 39 |
const [concept, setConcept] = useState("");
|
| 40 |
const [mode, setMode] = useState<ModificationMode>("modify");
|
| 41 |
const [imageModel, setImageModel] = useState<string | null>(null);
|
| 42 |
+
const [userPrompt, setUserPrompt] = useState("");
|
| 43 |
|
| 44 |
// Result state
|
| 45 |
const [result, setResult] = useState<ModifiedImageResult | null>(null);
|
|
|
|
| 105 |
concept: concept.trim() || undefined,
|
| 106 |
mode,
|
| 107 |
image_model: imageModel,
|
| 108 |
+
user_prompt: userPrompt.trim() || undefined,
|
| 109 |
});
|
| 110 |
|
| 111 |
if (response.status !== "success" || !response.image) {
|
|
|
|
| 120 |
} finally {
|
| 121 |
setIsLoading(false);
|
| 122 |
}
|
| 123 |
+
}, [originalImageUrl, analysis, angle, concept, mode, imageModel, userPrompt]);
|
| 124 |
|
| 125 |
// Reset to start over
|
| 126 |
const handleStartOver = useCallback(() => {
|
|
|
|
| 132 |
setConcept("");
|
| 133 |
setMode("modify");
|
| 134 |
setImageModel(null);
|
| 135 |
+
setUserPrompt("");
|
| 136 |
setResult(null);
|
| 137 |
setGeneratedPrompt(null);
|
| 138 |
setError(null);
|
|
|
|
| 355 |
concept={concept}
|
| 356 |
mode={mode}
|
| 357 |
imageModel={imageModel}
|
| 358 |
+
userPrompt={userPrompt}
|
| 359 |
onAngleChange={setAngle}
|
| 360 |
onConceptChange={setConcept}
|
| 361 |
onModeChange={setMode}
|
| 362 |
onImageModelChange={setImageModel}
|
| 363 |
+
onUserPromptChange={setUserPrompt}
|
| 364 |
onSubmit={handleModify}
|
| 365 |
isLoading={isLoading}
|
| 366 |
/>
|
frontend/components/creative/ModificationForm.tsx
CHANGED
|
@@ -26,10 +26,12 @@ interface ModificationFormProps {
|
|
| 26 |
concept: string;
|
| 27 |
mode: ModificationMode;
|
| 28 |
imageModel: string | null;
|
|
|
|
| 29 |
onAngleChange: (value: string) => void;
|
| 30 |
onConceptChange: (value: string) => void;
|
| 31 |
onModeChange: (mode: ModificationMode) => void;
|
| 32 |
onImageModelChange: (model: string | null) => void;
|
|
|
|
| 33 |
onSubmit: () => void;
|
| 34 |
isLoading: boolean;
|
| 35 |
}
|
|
@@ -41,10 +43,12 @@ export const ModificationForm: React.FC<ModificationFormProps> = ({
|
|
| 41 |
concept,
|
| 42 |
mode,
|
| 43 |
imageModel,
|
|
|
|
| 44 |
onAngleChange,
|
| 45 |
onConceptChange,
|
| 46 |
onModeChange,
|
| 47 |
onImageModelChange,
|
|
|
|
| 48 |
onSubmit,
|
| 49 |
isLoading,
|
| 50 |
}) => {
|
|
@@ -99,7 +103,7 @@ export const ModificationForm: React.FC<ModificationFormProps> = ({
|
|
| 99 |
fetchOptions();
|
| 100 |
}, []);
|
| 101 |
|
| 102 |
-
const isValid = angle.trim() || concept.trim();
|
| 103 |
|
| 104 |
const groupedAngles = angleOptions.reduce((acc, angle) => {
|
| 105 |
if (!acc[angle.category]) acc[angle.category] = [];
|
|
@@ -314,6 +318,26 @@ export const ModificationForm: React.FC<ModificationFormProps> = ({
|
|
| 314 |
</div>
|
| 315 |
</div>
|
| 316 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
{/* Image Model Selection */}
|
| 318 |
<div className="space-y-4 pt-4 border-t border-gray-100">
|
| 319 |
<label className="text-sm font-bold text-gray-700 uppercase tracking-wider block">
|
|
@@ -360,12 +384,12 @@ export const ModificationForm: React.FC<ModificationFormProps> = ({
|
|
| 360 |
</span>
|
| 361 |
</button>
|
| 362 |
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
</div>
|
| 370 |
</CardContent>
|
| 371 |
</Card>
|
|
|
|
| 26 |
concept: string;
|
| 27 |
mode: ModificationMode;
|
| 28 |
imageModel: string | null;
|
| 29 |
+
userPrompt: string;
|
| 30 |
onAngleChange: (value: string) => void;
|
| 31 |
onConceptChange: (value: string) => void;
|
| 32 |
onModeChange: (mode: ModificationMode) => void;
|
| 33 |
onImageModelChange: (model: string | null) => void;
|
| 34 |
+
onUserPromptChange: (value: string) => void;
|
| 35 |
onSubmit: () => void;
|
| 36 |
isLoading: boolean;
|
| 37 |
}
|
|
|
|
| 43 |
concept,
|
| 44 |
mode,
|
| 45 |
imageModel,
|
| 46 |
+
userPrompt,
|
| 47 |
onAngleChange,
|
| 48 |
onConceptChange,
|
| 49 |
onModeChange,
|
| 50 |
onImageModelChange,
|
| 51 |
+
onUserPromptChange,
|
| 52 |
onSubmit,
|
| 53 |
isLoading,
|
| 54 |
}) => {
|
|
|
|
| 103 |
fetchOptions();
|
| 104 |
}, []);
|
| 105 |
|
| 106 |
+
const isValid = angle.trim() || concept.trim() || userPrompt.trim();
|
| 107 |
|
| 108 |
const groupedAngles = angleOptions.reduce((acc, angle) => {
|
| 109 |
if (!acc[angle.category]) acc[angle.category] = [];
|
|
|
|
| 318 |
</div>
|
| 319 |
</div>
|
| 320 |
|
| 321 |
+
{/* User Prompt (Optional) */}
|
| 322 |
+
<div className="space-y-4 pt-4 border-t border-gray-100">
|
| 323 |
+
<div className="flex items-center gap-2">
|
| 324 |
+
<Lightbulb className="w-4 h-4 text-purple-500" />
|
| 325 |
+
<label className="text-sm font-bold text-gray-700 uppercase tracking-wider">
|
| 326 |
+
Custom Instructions (Optional)
|
| 327 |
+
</label>
|
| 328 |
+
</div>
|
| 329 |
+
<textarea
|
| 330 |
+
value={userPrompt}
|
| 331 |
+
onChange={(e) => onUserPromptChange(e.target.value)}
|
| 332 |
+
placeholder="e.g., Make the image more vibrant, add a sense of urgency, change the background to blue..."
|
| 333 |
+
className="w-full px-4 py-3 rounded-xl border-2 border-gray-100 bg-gray-50/50 hover:bg-white focus:bg-white focus:border-purple-500 focus:ring-4 focus:ring-purple-500/10 transition-all font-medium text-gray-900 min-h-[100px] resize-y"
|
| 334 |
+
/>
|
| 335 |
+
<p className="text-xs text-gray-500 flex items-center gap-1.5 px-1">
|
| 336 |
+
<Info className="w-3 h-3" />
|
| 337 |
+
Provide specific instructions for how you want the image modified.
|
| 338 |
+
</p>
|
| 339 |
+
</div>
|
| 340 |
+
|
| 341 |
{/* Image Model Selection */}
|
| 342 |
<div className="space-y-4 pt-4 border-t border-gray-100">
|
| 343 |
<label className="text-sm font-bold text-gray-700 uppercase tracking-wider block">
|
|
|
|
| 384 |
</span>
|
| 385 |
</button>
|
| 386 |
|
| 387 |
+
{!isValid && (
|
| 388 |
+
<div className="mt-4 flex items-center justify-center gap-2 text-amber-600 animate-pulse">
|
| 389 |
+
<Info className="w-4 h-4" />
|
| 390 |
+
<p className="text-sm font-bold">Please provide at least one: angle, concept, or custom instructions</p>
|
| 391 |
+
</div>
|
| 392 |
+
)}
|
| 393 |
</div>
|
| 394 |
</CardContent>
|
| 395 |
</Card>
|
frontend/lib/api/endpoints.ts
CHANGED
|
@@ -329,6 +329,7 @@ export const modifyCreative = async (params: {
|
|
| 329 |
concept?: string;
|
| 330 |
mode: ModificationMode;
|
| 331 |
image_model?: string | null;
|
|
|
|
| 332 |
}): Promise<CreativeModifyResponse> => {
|
| 333 |
const response = await apiClient.post<CreativeModifyResponse>(
|
| 334 |
"/api/creative/modify",
|
|
|
|
| 329 |
concept?: string;
|
| 330 |
mode: ModificationMode;
|
| 331 |
image_model?: string | null;
|
| 332 |
+
user_prompt?: string;
|
| 333 |
}): Promise<CreativeModifyResponse> => {
|
| 334 |
const response = await apiClient.post<CreativeModifyResponse>(
|
| 335 |
"/api/creative/modify",
|
main.py
CHANGED
|
@@ -1967,6 +1967,10 @@ class CreativeModifyRequest(BaseModel):
|
|
| 1967 |
default=None,
|
| 1968 |
description="Image generation model to use"
|
| 1969 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1970 |
|
| 1971 |
|
| 1972 |
class ModifiedImageResult(BaseModel):
|
|
@@ -2207,6 +2211,7 @@ async def modify_creative(
|
|
| 2207 |
user_concept=request.concept,
|
| 2208 |
mode=request.mode,
|
| 2209 |
image_model=request.image_model,
|
|
|
|
| 2210 |
)
|
| 2211 |
|
| 2212 |
if result["status"] != "success":
|
|
|
|
| 1967 |
default=None,
|
| 1968 |
description="Image generation model to use"
|
| 1969 |
)
|
| 1970 |
+
user_prompt: Optional[str] = Field(
|
| 1971 |
+
default=None,
|
| 1972 |
+
description="Optional custom user prompt/instructions for modification"
|
| 1973 |
+
)
|
| 1974 |
|
| 1975 |
|
| 1976 |
class ModifiedImageResult(BaseModel):
|
|
|
|
| 2211 |
user_concept=request.concept,
|
| 2212 |
mode=request.mode,
|
| 2213 |
image_model=request.image_model,
|
| 2214 |
+
user_prompt=request.user_prompt,
|
| 2215 |
)
|
| 2216 |
|
| 2217 |
if result["status"] != "success":
|
services/creative_modifier.py
CHANGED
|
@@ -231,6 +231,7 @@ Be specific and detailed in your analysis. If you cannot determine something wit
|
|
| 231 |
user_angle: Optional[str] = None,
|
| 232 |
user_concept: Optional[str] = None,
|
| 233 |
mode: str = "modify",
|
|
|
|
| 234 |
) -> str:
|
| 235 |
"""
|
| 236 |
Generate a prompt for modifying the creative based on analysis and user input.
|
|
@@ -240,6 +241,7 @@ Be specific and detailed in your analysis. If you cannot determine something wit
|
|
| 240 |
user_angle: User-provided angle to apply
|
| 241 |
user_concept: User-provided concept to apply
|
| 242 |
mode: "modify" for image-to-image, "inspired" for new generation
|
|
|
|
| 243 |
|
| 244 |
Returns:
|
| 245 |
Generated prompt string
|
|
@@ -247,6 +249,12 @@ Be specific and detailed in your analysis. If you cannot determine something wit
|
|
| 247 |
logger.info(f"Generating modification prompt (mode: {mode})")
|
| 248 |
logger.info(f"User angle: {user_angle}")
|
| 249 |
logger.info(f"User concept: {user_concept}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
|
| 251 |
system_prompt = """You are an expert advertising creative director with 20+ years experience.
|
| 252 |
Your task is to create seamless, organic modifications that enhance existing creatives without appearing forced.
|
|
@@ -399,6 +407,7 @@ Be specific and detailed in your analysis. If you cannot determine something wit
|
|
| 399 |
user_concept: Optional[str] = None,
|
| 400 |
mode: str = "modify",
|
| 401 |
image_model: Optional[str] = None,
|
|
|
|
| 402 |
width: int = 1024,
|
| 403 |
height: int = 1024,
|
| 404 |
) -> Dict[str, Any]:
|
|
@@ -412,6 +421,7 @@ Be specific and detailed in your analysis. If you cannot determine something wit
|
|
| 412 |
user_concept: User-provided concept
|
| 413 |
mode: "modify" for image-to-image, "inspired" for new generation
|
| 414 |
image_model: Model to use for generation
|
|
|
|
| 415 |
width: Output width
|
| 416 |
height: Output height
|
| 417 |
|
|
@@ -425,6 +435,7 @@ Be specific and detailed in your analysis. If you cannot determine something wit
|
|
| 425 |
logger.info(f"Image URL: {image_url}")
|
| 426 |
logger.info(f"User angle: {user_angle}")
|
| 427 |
logger.info(f"User concept: {user_concept}")
|
|
|
|
| 428 |
logger.info(f"Image model: {image_model}")
|
| 429 |
logger.info("=" * 80)
|
| 430 |
|
|
@@ -442,6 +453,7 @@ Be specific and detailed in your analysis. If you cannot determine something wit
|
|
| 442 |
user_angle=user_angle,
|
| 443 |
user_concept=user_concept,
|
| 444 |
mode=mode,
|
|
|
|
| 445 |
)
|
| 446 |
result["prompt"] = prompt
|
| 447 |
logger.info(f"✓ Generated prompt: {prompt}")
|
|
|
|
| 231 |
user_angle: Optional[str] = None,
|
| 232 |
user_concept: Optional[str] = None,
|
| 233 |
mode: str = "modify",
|
| 234 |
+
user_prompt: Optional[str] = None,
|
| 235 |
) -> str:
|
| 236 |
"""
|
| 237 |
Generate a prompt for modifying the creative based on analysis and user input.
|
|
|
|
| 241 |
user_angle: User-provided angle to apply
|
| 242 |
user_concept: User-provided concept to apply
|
| 243 |
mode: "modify" for image-to-image, "inspired" for new generation
|
| 244 |
+
user_prompt: Custom user instructions for modification
|
| 245 |
|
| 246 |
Returns:
|
| 247 |
Generated prompt string
|
|
|
|
| 249 |
logger.info(f"Generating modification prompt (mode: {mode})")
|
| 250 |
logger.info(f"User angle: {user_angle}")
|
| 251 |
logger.info(f"User concept: {user_concept}")
|
| 252 |
+
logger.info(f"User prompt: {user_prompt}")
|
| 253 |
+
|
| 254 |
+
# If user provided a custom prompt, use it directly
|
| 255 |
+
if user_prompt and user_prompt.strip():
|
| 256 |
+
logger.info("Using custom user prompt directly")
|
| 257 |
+
return user_prompt.strip()
|
| 258 |
|
| 259 |
system_prompt = """You are an expert advertising creative director with 20+ years experience.
|
| 260 |
Your task is to create seamless, organic modifications that enhance existing creatives without appearing forced.
|
|
|
|
| 407 |
user_concept: Optional[str] = None,
|
| 408 |
mode: str = "modify",
|
| 409 |
image_model: Optional[str] = None,
|
| 410 |
+
user_prompt: Optional[str] = None,
|
| 411 |
width: int = 1024,
|
| 412 |
height: int = 1024,
|
| 413 |
) -> Dict[str, Any]:
|
|
|
|
| 421 |
user_concept: User-provided concept
|
| 422 |
mode: "modify" for image-to-image, "inspired" for new generation
|
| 423 |
image_model: Model to use for generation
|
| 424 |
+
user_prompt: Custom user instructions for modification
|
| 425 |
width: Output width
|
| 426 |
height: Output height
|
| 427 |
|
|
|
|
| 435 |
logger.info(f"Image URL: {image_url}")
|
| 436 |
logger.info(f"User angle: {user_angle}")
|
| 437 |
logger.info(f"User concept: {user_concept}")
|
| 438 |
+
logger.info(f"User prompt: {user_prompt}")
|
| 439 |
logger.info(f"Image model: {image_model}")
|
| 440 |
logger.info("=" * 80)
|
| 441 |
|
|
|
|
| 453 |
user_angle=user_angle,
|
| 454 |
user_concept=user_concept,
|
| 455 |
mode=mode,
|
| 456 |
+
user_prompt=user_prompt,
|
| 457 |
)
|
| 458 |
result["prompt"] = prompt
|
| 459 |
logger.info(f"✓ Generated prompt: {prompt}")
|