Compare commits

...

3 Commits

  1. 2
      src/app/api/generate/providers/popiserver.ts
  2. 5
      src/app/api/popi/media/upload/route.ts
  3. 8
      src/components/composer/GenerationComposer.tsx
  4. 9
      src/store/execution/nanoBananaExecutor.ts
  5. 2
      src/utils/modelSchemaDefaults.ts

2
src/app/api/generate/providers/popiserver.ts

@ -564,7 +564,7 @@ function extractOutputFromTask(task: PopiTask, mediaType: string): GenerationOut
// payloads. Only use it as a defensive fallback for older responses that do // payloads. Only use it as a defensive fallback for older responses that do
// not put generated images under resultList. // not put generated images under resultList.
const images = [ const images = [
...new Set(resultImages.length > 0 ? resultImages : collectStrings(task.images)), ...new Set(resultImages.length > 0 ? resultImages : collectStrings(task.images ?? [])),
]; ];
return images.length > 0 return images.length > 0
? { success: true, outputs: images.map((url) => ({ type: "image", data: "", url })) } ? { success: true, outputs: images.map((url) => ({ type: "image", data: "", url })) }

5
src/app/api/popi/media/upload/route.ts

@ -65,6 +65,10 @@ function mediaResourceUrl(media: PopiMedia): string | null {
return media.url || media.originUrl || media.path || media.originPath || null; return media.url || media.originUrl || media.path || media.originPath || null;
} }
function isPopiMedia(value: PopiMediaUploadData): value is PopiMedia {
return "url" in value || "originUrl" in value || "path" in value || "originPath" in value;
}
function uploadedMediaUrls(data: PopiMediaUploadData | undefined): string[] { function uploadedMediaUrls(data: PopiMediaUploadData | undefined): string[] {
if (!data) return []; if (!data) return [];
if (Array.isArray(data)) { if (Array.isArray(data)) {
@ -81,6 +85,7 @@ function uploadedMediaUrls(data: PopiMediaUploadData | undefined): string[] {
if (list) { if (list) {
return list.map(mediaResourceUrl).filter((url): url is string => Boolean(url)); return list.map(mediaResourceUrl).filter((url): url is string => Boolean(url));
} }
if (!isPopiMedia(data)) return [];
const single = mediaResourceUrl(data); const single = mediaResourceUrl(data);
return single ? [single] : []; return single ? [single] : [];
} }

8
src/components/composer/GenerationComposer.tsx

@ -989,12 +989,13 @@ export function GenerationComposer() {
return current; return current;
} }
const currentCapabilities = current.selectedModel?.capabilities ?? [];
const next = { const next = {
...current, ...current,
selectedModel: { selectedModel: {
...resolvedModel, ...resolvedModel,
capabilities: current.selectedModel.capabilities.length > 0 capabilities: currentCapabilities.length > 0
? current.selectedModel.capabilities ? currentCapabilities
: resolvedModel.capabilities, : resolvedModel.capabilities,
}, },
}; };
@ -1013,10 +1014,11 @@ export function GenerationComposer() {
nodeModel && nodeModel &&
selectedModelNeedsPopiserverMetadataLookup(nodeModel) selectedModelNeedsPopiserverMetadataLookup(nodeModel)
) { ) {
const nodeModelCapabilities = nodeModel.capabilities ?? [];
updateNodeData(activeContext.node.id, { updateNodeData(activeContext.node.id, {
selectedModel: { selectedModel: {
...resolvedModel, ...resolvedModel,
capabilities: nodeModel.capabilities.length > 0 ? nodeModel.capabilities : resolvedModel.capabilities, capabilities: nodeModelCapabilities.length > 0 ? nodeModelCapabilities : resolvedModel.capabilities,
}, },
}); });
} }

9
src/store/execution/nanoBananaExecutor.ts

@ -7,6 +7,7 @@
import type { import type {
NanoBananaNodeData, NanoBananaNodeData,
Resolution,
SelectedModel, SelectedModel,
} from "@/types"; } from "@/types";
import { calculateGenerationCost } from "@/utils/costCalculator"; import { calculateGenerationCost } from "@/utils/costCalculator";
@ -46,6 +47,12 @@ export interface NanoBananaOptions {
useStoredFallback?: boolean; useStoredFallback?: boolean;
} }
const GEMINI_RESOLUTIONS: Resolution[] = ["512", "1K", "2K", "4K"];
function normalizeGeminiResolution(value: string): Resolution {
return GEMINI_RESOLUTIONS.includes(value as Resolution) ? (value as Resolution) : "2K";
}
function isProviderOverloadError(error: unknown): boolean { function isProviderOverloadError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);
return /provider is temporarily overloaded|system\s+disk\s+overloaded/i.test(message); return /provider is temporarily overloaded|system\s+disk\s+overloaded/i.test(message);
@ -376,7 +383,7 @@ export async function executeNanoBanana(
if (modelToUse.provider === "fal" && modelToUse.pricing) { if (modelToUse.provider === "fal" && modelToUse.pricing) {
addIncurredCost(modelToUse.pricing.amount); addIncurredCost(modelToUse.pricing.amount);
} else if (modelToUse.provider === "gemini") { } else if (modelToUse.provider === "gemini") {
const generationCost = calculateGenerationCost(nodeData.model, nodeConfig.resolution); const generationCost = calculateGenerationCost(nodeData.model, normalizeGeminiResolution(nodeConfig.resolution));
addIncurredCost(generationCost); addIncurredCost(generationCost);
} }

2
src/utils/modelSchemaDefaults.ts

@ -57,7 +57,7 @@ export function buildImageDefaultsFromSchema(schema: ModelParameter[]): {
return { return {
...(hasValue(ratioValue) ? { aspectRatio: String(ratioValue) as AspectRatio } : {}), ...(hasValue(ratioValue) ? { aspectRatio: String(ratioValue) as AspectRatio } : {}),
...(hasValue(resolutionValue) ? { resolution: String(resolutionValue) } : {}), ...(hasValue(resolutionValue) ? { resolution: String(resolutionValue) } : {}),
...(Number.isInteger(imageCountValue) && imageCountValue > 0 ...(typeof imageCountValue === "number" && Number.isInteger(imageCountValue) && imageCountValue > 0
? { imageCount: imageCountValue as ImageGenerationCount } ? { imageCount: imageCountValue as ImageGenerationCount }
: {}), : {}),
parameters, parameters,

Loading…
Cancel
Save