feat(image-generation): add aspect ratio and image size configuration

This commit is contained in:
zihanjian
2025-12-02 15:29:54 +08:00
parent 5782b8df3f
commit 549db2aac6
4 changed files with 91 additions and 16 deletions

View File

@@ -13,6 +13,10 @@ export default function App() {
const [historicalActivities, setHistoricalActivities] = useState<
Record<string, ProcessedEvent[]>
>({});
const [imageConfig, setImageConfig] = useState({
aspectRatio: "16:9",
imageSize: "1K",
});
const scrollAreaRef = useRef<HTMLDivElement>(null);
const hasFinalizeEventOccurredRef = useRef(false);
const [error, setError] = useState<string | null>(null);
@@ -105,7 +109,9 @@ export default function App() {
submittedInputValue: string,
effort: string,
model: string,
language: string
language: string,
aspectRatio: string,
imageSize: string
) => {
if (!submittedInputValue.trim()) return;
setProcessedEventsTimeline([]);
@@ -140,12 +146,15 @@ export default function App() {
id: Date.now().toString(),
},
];
setImageConfig({ aspectRatio, imageSize });
thread.submit({
messages: newMessages,
initial_search_query_count: initial_search_query_count,
max_research_loops: max_research_loops,
reasoning_model: model,
language,
aspect_ratio: aspectRatio,
image_size: imageSize,
});
},
[thread]
@@ -188,6 +197,8 @@ export default function App() {
onCancel={handleCancel}
liveActivityEvents={processedEventsTimeline}
historicalActivities={historicalActivities}
aspectRatio={imageConfig.aspectRatio}
imageSize={imageConfig.imageSize}
/>
)}
</main>

View File

@@ -168,6 +168,8 @@ interface AiMessageBubbleProps {
mdComponents: typeof mdComponents;
handleCopy: (text: string, messageId: string) => void;
copiedMessageId: string | null;
aspectRatio?: string;
imageSize?: string;
}
// AiMessageBubble Component
@@ -180,6 +182,8 @@ const AiMessageBubble: React.FC<AiMessageBubbleProps> = ({
mdComponents,
handleCopy,
copiedMessageId,
aspectRatio,
imageSize,
}) => {
const [pageImages, setPageImages] = useState<
Record<
@@ -238,7 +242,12 @@ const AiMessageBubble: React.FC<AiMessageBubbleProps> = ({
fetch(`${backendBase}/generate_image`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt: page.detail, number_of_images: 1 }),
body: JSON.stringify({
prompt: page.detail,
number_of_images: 1,
aspect_ratio: aspectRatio || "16:9",
image_size: imageSize || "1K",
}),
})
.then(async (res) => {
if (!res.ok) throw new Error(await res.text());
@@ -352,10 +361,19 @@ interface ChatMessagesViewProps {
messages: Message[];
isLoading: boolean;
scrollAreaRef: React.RefObject<HTMLDivElement | null>;
onSubmit: (inputValue: string, effort: string, model: string, language: string) => void;
onSubmit: (
inputValue: string,
effort: string,
model: string,
language: string,
aspectRatio: string,
imageSize: string
) => void;
onCancel: () => void;
liveActivityEvents: ProcessedEvent[];
historicalActivities: Record<string, ProcessedEvent[]>;
aspectRatio?: string;
imageSize?: string;
}
export function ChatMessagesView({
@@ -366,6 +384,8 @@ export function ChatMessagesView({
onCancel,
liveActivityEvents,
historicalActivities,
aspectRatio,
imageSize,
}: ChatMessagesViewProps) {
const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null);
@@ -406,6 +426,8 @@ export function ChatMessagesView({
mdComponents={mdComponents}
handleCopy={handleCopy}
copiedMessageId={copiedMessageId}
aspectRatio={aspectRatio}
imageSize={imageSize}
/>
)}
</div>

View File

@@ -24,7 +24,9 @@ interface InputFormProps {
inputValue: string,
effort: string,
model: string,
language: string
language: string,
aspectRatio: string,
imageSize: string
) => void;
onCancel: () => void;
isLoading: boolean;
@@ -38,15 +40,17 @@ export const InputForm: React.FC<InputFormProps> = ({
hasHistory,
}) => {
const [internalInputValue, setInternalInputValue] = useState("");
const [effort, setEffort] = useState("medium");
const [effort, setEffort] = useState("low");
// Default to a current, broadly capable model
const [model, setModel] = useState("gemini-2.5-flash");
const [language, setLanguage] = useState("简体中文");
const [aspectRatio, setAspectRatio] = useState("16:9");
const [imageSize, setImageSize] = useState("1K");
const handleInternalSubmit = (e?: React.FormEvent) => {
if (e) e.preventDefault();
if (!internalInputValue.trim()) return;
onSubmit(internalInputValue, effort, model, language);
onSubmit(internalInputValue, effort, model, language, aspectRatio, imageSize);
setInternalInputValue("");
};
@@ -74,7 +78,7 @@ export const InputForm: React.FC<InputFormProps> = ({
value={internalInputValue}
onChange={(e) => setInternalInputValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Who won the Euro 2024 and scored the most goals?"
placeholder="白雪公主大战奥特曼"
className={`w-full text-neutral-100 placeholder-neutral-500 resize-none border-0 focus:outline-none focus:ring-0 outline-none focus-visible:ring-0 shadow-none
md:text-base min-h-[56px] max-h-[200px]`}
rows={1}
@@ -107,9 +111,10 @@ export const InputForm: React.FC<InputFormProps> = ({
)}
</div>
</div>
<div className="flex items-center justify-between">
<div className="flex flex-row gap-2">
<div className="flex flex-row gap-2 bg-neutral-700 border-neutral-600 text-neutral-300 focus:ring-neutral-500 rounded-xl rounded-t-sm pl-2 max-w-[100%] sm:max-w-[90%]">
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<div className="flex flex-col gap-2 w-full">
<div className="flex flex-wrap gap-2">
<div className="flex flex-row gap-2 bg-neutral-700 border-neutral-600 text-neutral-300 focus:ring-neutral-500 rounded-xl rounded-t-sm pl-2">
<div className="flex flex-row items-center text-sm">
<Brain className="h-4 w-4 mr-2" />
Effort
@@ -140,7 +145,7 @@ export const InputForm: React.FC<InputFormProps> = ({
</SelectContent>
</Select>
</div>
<div className="flex flex-row gap-2 bg-neutral-700 border-neutral-600 text-neutral-300 focus:ring-neutral-500 rounded-xl rounded-t-sm pl-2 max-w-[100%] sm:max-w-[90%]">
<div className="flex flex-row gap-2 bg-neutral-700 border-neutral-600 text-neutral-300 focus:ring-neutral-500 rounded-xl rounded-t-sm pl-2">
<div className="flex flex-row items-center text-sm ml-2">
<Cpu className="h-4 w-4 mr-2" />
Model
@@ -173,11 +178,11 @@ export const InputForm: React.FC<InputFormProps> = ({
<div className="flex items-center">
<Cpu className="h-4 w-4 mr-2 text-purple-400" /> 3 Pro Preview
</div>
</SelectItem>
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-row gap-2 bg-neutral-700 border-neutral-600 text-neutral-300 focus:ring-neutral-500 rounded-xl rounded-t-sm pl-2 max-w-[100%] sm:max-w-[90%]">
<div className="flex flex-row gap-2 bg-neutral-700 border-neutral-600 text-neutral-300 focus:ring-neutral-500 rounded-xl rounded-t-sm pl-2">
<div className="flex flex-row items-center text-sm ml-2">
<Languages className="h-4 w-4 mr-2" />
Language
@@ -207,7 +212,42 @@ export const InputForm: React.FC<InputFormProps> = ({
</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex flex-wrap gap-2">
<div className="flex flex-row gap-2 bg-neutral-700 border-neutral-600 text-neutral-300 focus:ring-neutral-500 rounded-xl rounded-t-sm pl-2">
<div className="flex flex-row items-center text-sm ml-2">
<Cpu className="h-4 w-4 mr-2" />
Aspect
</div>
<Select value={aspectRatio} onValueChange={setAspectRatio}>
<SelectTrigger className="w-[140px] bg-transparent border-none cursor-pointer">
<SelectValue placeholder="Aspect Ratio" />
</SelectTrigger>
<SelectContent className="bg-neutral-700 border-neutral-600 text-neutral-300 cursor-pointer">
<SelectItem value="16:9">16:9</SelectItem>
<SelectItem value="4:3">4:3</SelectItem>
<SelectItem value="1:1">1:1</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex flex-row gap-2 bg-neutral-700 border-neutral-600 text-neutral-300 focus:ring-neutral-500 rounded-xl rounded-t-sm pl-2">
<div className="flex flex-row items-center text-sm ml-2">
<Cpu className="h-4 w-4 mr-2" />
Size
</div>
<Select value={imageSize} onValueChange={setImageSize}>
<SelectTrigger className="w-[120px] bg-transparent border-none cursor-pointer">
<SelectValue placeholder="Image Size" />
</SelectTrigger>
<SelectContent className="bg-neutral-700 border-neutral-600 text-neutral-300 cursor-pointer">
<SelectItem value="1K">1K</SelectItem>
<SelectItem value="2K">2K</SelectItem>
<SelectItem value="4K">4K</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
{hasHistory && (
<Button

View File

@@ -5,7 +5,9 @@ interface WelcomeScreenProps {
submittedInputValue: string,
effort: string,
model: string,
language: string
language: string,
aspectRatio: string,
imageSize: string
) => void;
onCancel: () => void;
isLoading: boolean;
@@ -19,10 +21,10 @@ export const WelcomeScreen: React.FC<WelcomeScreenProps> = ({
<div className="h-full flex flex-col items-center justify-center text-center px-4 flex-1 w-full max-w-3xl mx-auto gap-4">
<div>
<h1 className="text-5xl md:text-6xl font-semibold text-neutral-100 mb-3">
Welcome.
NanoComic
</h1>
<p className="text-xl md:text-2xl text-neutral-400">
How can I help you today?
</p>
</div>
<div className="w-full mt-4">