mirror of
https://github.com/Zippland/NanoComic.git
synced 2026-02-06 16:12:17 +08:00
feat(image-generation): add aspect ratio and image size configuration
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user