Enforce MAX_USER_PROMPT limit in generate-page-modal and comic-creation-form
This commit is contained in:
@@ -15,6 +15,7 @@ import { useKeyboardShortcut } from "@/hooks/use-keyboard-shortcut";
|
|||||||
import { validateFileForUpload, generateFilePreview } from "@/lib/file-utils";
|
import { validateFileForUpload, generateFilePreview } from "@/lib/file-utils";
|
||||||
import { useS3Upload } from "next-s3-upload";
|
import { useS3Upload } from "next-s3-upload";
|
||||||
import { isContentPolicyViolation } from "@/lib/utils";
|
import { isContentPolicyViolation } from "@/lib/utils";
|
||||||
|
import { MAX_SYSTEM_LENGTH, MAX_USER_PROMPT } from "@/lib/prompt";
|
||||||
|
|
||||||
interface CharacterItem {
|
interface CharacterItem {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -330,13 +331,19 @@ export function GeneratePageModal({
|
|||||||
<textarea
|
<textarea
|
||||||
autoFocus
|
autoFocus
|
||||||
value={prompt}
|
value={prompt}
|
||||||
onChange={(e) => setPrompt(e.target.value)}
|
|
||||||
|
onChange={(e) => setPrompt(e.target.value.slice(
|
||||||
|
// Only allow users to type up to MAX_SYSTEM_LENGTH characters in the prompt.
|
||||||
|
// Ensure users cannot paste or otherwise enter more text than the max.
|
||||||
|
0, MAX_USER_PROMPT))
|
||||||
|
}
|
||||||
placeholder={
|
placeholder={
|
||||||
isRedrawMode
|
isRedrawMode
|
||||||
? "Tweak the prompt to improve this page..."
|
? "Tweak the prompt to improve this page..."
|
||||||
: "Continue the story... Describe what happens next."
|
: "Continue the story... Describe what happens next."
|
||||||
}
|
}
|
||||||
disabled={isGenerating}
|
disabled={isGenerating}
|
||||||
|
maxLength={MAX_USER_PROMPT}
|
||||||
className="w-full bg-transparent border-none text-sm text-white placeholder-muted-foreground/50 focus:ring-0 focus:outline-none resize-none h-20 leading-relaxed tracking-tight"
|
className="w-full bg-transparent border-none text-sm text-white placeholder-muted-foreground/50 focus:ring-0 focus:outline-none resize-none h-20 leading-relaxed tracking-tight"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -358,11 +365,10 @@ export function GeneratePageModal({
|
|||||||
onClick={() => toggleCharacterSelection(index)}
|
onClick={() => toggleCharacterSelection(index)}
|
||||||
onDoubleClick={() => setShowPreview(imageUrl)}
|
onDoubleClick={() => setShowPreview(imageUrl)}
|
||||||
disabled={isGenerating}
|
disabled={isGenerating}
|
||||||
className={`w-10 h-10 rounded-md overflow-hidden transition-all disabled:opacity-50 disabled:cursor-not-allowed relative ${
|
className={`w-10 h-10 rounded-md overflow-hidden transition-all disabled:opacity-50 disabled:cursor-not-allowed relative ${isSelected
|
||||||
isSelected
|
? "border-2 border-indigo-500"
|
||||||
? "border-2 border-indigo-500"
|
: "border-2 border-transparent hover:border-indigo/50"
|
||||||
: "border-2 border-transparent hover:border-indigo/50"
|
}`}
|
||||||
}`}
|
|
||||||
title="Click to select/deselect, double-click to preview"
|
title="Click to select/deselect, double-click to preview"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -432,7 +438,7 @@ export function GeneratePageModal({
|
|||||||
{isRedrawMode
|
{isRedrawMode
|
||||||
? "Previous pages and characters automatically referenced."
|
? "Previous pages and characters automatically referenced."
|
||||||
: "Previous page automatically referenced. " +
|
: "Previous page automatically referenced. " +
|
||||||
`${selectedCharacterIndices.size} selected characters.`}
|
`${selectedCharacterIndices.size} selected characters.`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useKeyboardShortcut } from "@/hooks/use-keyboard-shortcut";
|
|||||||
import { useApiKey } from "@/hooks/use-api-key";
|
import { useApiKey } from "@/hooks/use-api-key";
|
||||||
import { isContentPolicyViolation } from "@/lib/utils";
|
import { isContentPolicyViolation } from "@/lib/utils";
|
||||||
import { ApiKeyModal } from "@/components/api-key-modal";
|
import { ApiKeyModal } from "@/components/api-key-modal";
|
||||||
|
import { MAX_SYSTEM_LENGTH, MAX_USER_PROMPT } from "@/lib/prompt";
|
||||||
|
|
||||||
interface ComicCreationFormProps {
|
interface ComicCreationFormProps {
|
||||||
prompt: string;
|
prompt: string;
|
||||||
@@ -328,9 +329,12 @@ export function ComicCreationForm({
|
|||||||
<textarea
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
value={prompt}
|
value={prompt}
|
||||||
onChange={(e) => setPrompt(e.target.value)}
|
onChange={(e) => setPrompt(e.target.value.slice(
|
||||||
|
0, MAX_USER_PROMPT))
|
||||||
|
}
|
||||||
placeholder="A cyberpunk detective standing in neon rain, holding a glowing datapad, moody lighting, noir style..."
|
placeholder="A cyberpunk detective standing in neon rain, holding a glowing datapad, moody lighting, noir style..."
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
maxLength={MAX_USER_PROMPT}
|
||||||
className="w-full bg-transparent border-none text-sm text-white placeholder-muted-foreground/50 focus:ring-0 focus:outline-none resize-none h-16 leading-relaxed disabled:opacity-50 disabled:cursor-not-allowed"
|
className="w-full bg-transparent border-none text-sm text-white placeholder-muted-foreground/50 focus:ring-0 focus:outline-none resize-none h-16 leading-relaxed disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -424,8 +428,8 @@ export function ComicCreationForm({
|
|||||||
setShowStyleDropdown(false);
|
setShowStyleDropdown(false);
|
||||||
}}
|
}}
|
||||||
className={`w-full text-left px-3 py-2 rounded text-xs transition-colors flex items-center justify-between ${style === styleOption.id
|
className={`w-full text-left px-3 py-2 rounded text-xs transition-colors flex items-center justify-between ${style === styleOption.id
|
||||||
? "bg-indigo/10 text-indigo"
|
? "bg-indigo/10 text-indigo"
|
||||||
: "text-muted-foreground hover:bg-white/5 hover:text-white"
|
: "text-muted-foreground hover:bg-white/5 hover:text-white"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>{styleOption.name}</span>
|
<span>{styleOption.name}</span>
|
||||||
|
|||||||
+38
-5
@@ -1,5 +1,12 @@
|
|||||||
import { COMIC_STYLES } from "./constants";
|
import { COMIC_STYLES } from "./constants";
|
||||||
|
|
||||||
|
// Together AI has a 45000 character limit for the prompt parameter
|
||||||
|
// We use 40k total to keep a larger safety margin
|
||||||
|
export const MAX_PROMPT_LENGTH = 40000;
|
||||||
|
export const MAX_SYSTEM_LENGTH = 35000; // Reserve 5,000 for user's prompt
|
||||||
|
|
||||||
|
export const MAX_USER_PROMPT = MAX_PROMPT_LENGTH - MAX_SYSTEM_LENGTH;
|
||||||
|
|
||||||
export function buildComicPrompt({
|
export function buildComicPrompt({
|
||||||
prompt,
|
prompt,
|
||||||
style,
|
style,
|
||||||
@@ -28,11 +35,37 @@ export function buildComicPrompt({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isAddPage && previousPages.length > 0) {
|
if (isAddPage && previousPages.length > 0) {
|
||||||
const storyHistory = previousPages
|
// Limit previous pages to fit within MAX_SYSTEM_LENGTH
|
||||||
.map((page, index) => `Page ${index + 1}: ${page.prompt}`)
|
// Start with most recent pages and work backwards
|
||||||
.join("\n");
|
const header = `\nSTORY CONTINUATION CONTEXT:\nThis is page ${previousPages.length + 1} of an existing comic story. Here are the recent pages for context:\n`;
|
||||||
|
const footer = `\n\nThe new page should naturally continue this story. Maintain the same characters, setting, and narrative style. Reference previous events and build upon them.\n`;
|
||||||
continuationContext = `\nSTORY CONTINUATION CONTEXT:\nThis is a continuation of an existing comic story. Here are the previous pages:\n${storyHistory}\n\nThe new page should naturally continue this story. Maintain the same characters, setting, and narrative style. Reference previous events and build upon them.\n`;
|
|
||||||
|
// Calculate available space for previous pages (reserve space for rest of system prompt)
|
||||||
|
const basePromptLength = 2500; // Approximate length of system prompt without previous pages
|
||||||
|
const availableSpace = MAX_SYSTEM_LENGTH - basePromptLength - header.length - footer.length;
|
||||||
|
|
||||||
|
const selectedPages: string[] = [];
|
||||||
|
let currentLength = 0;
|
||||||
|
|
||||||
|
// Add pages from most recent backwards until we run out of space
|
||||||
|
for (let i = previousPages.length - 1; i >= 0; i--) {
|
||||||
|
const pageEntry = `Page ${i + 1}: ${previousPages[i].prompt}`;
|
||||||
|
const entryLength = pageEntry.length + (selectedPages.length > 0 ? 1 : 0); // +1 for newline
|
||||||
|
|
||||||
|
if (currentLength + entryLength <= availableSpace) {
|
||||||
|
selectedPages.unshift(pageEntry);
|
||||||
|
currentLength += entryLength;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedPages.length > 0) {
|
||||||
|
continuationContext = header + selectedPages.join("\n") + footer;
|
||||||
|
} else {
|
||||||
|
// Fallback if no pages fit
|
||||||
|
continuationContext = `\nSTORY CONTINUATION CONTEXT:\nThis is page ${previousPages.length + 1} of an existing comic story with ${previousPages.length} previous pages. Continue the story maintaining consistency.\n`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let characterSection = "";
|
let characterSection = "";
|
||||||
|
|||||||
Reference in New Issue
Block a user