weige 2 months ago
parent
commit
4bbc631ac6
  1. 5
      src/app/api/llm/route.ts
  2. 16
      src/components/composer/GenerationComposer.tsx
  3. 17
      src/store/execution/generateVideoExecutor.ts
  4. 52
      src/store/execution/generationParameters.ts
  5. 7
      src/store/execution/llmGenerateExecutor.ts
  6. 40
      src/store/execution/nanoBananaExecutor.ts
  7. 1
      src/types/api.ts

5
src/app/api/llm/route.ts

@ -334,6 +334,7 @@ async function generateWithPopiserver(
aiModelId?: number,
images?: string[],
videos?: string[],
parameters?: Record<string, unknown>,
requestId?: string
): Promise<string> {
const modelId = model || "kimi-k2.6";
@ -346,6 +347,7 @@ async function generateWithPopiserver(
"Content-Type": "application/json",
},
body: JSON.stringify({
...(parameters || {}),
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content },
@ -500,6 +502,7 @@ export async function POST(request: NextRequest) {
provider,
model,
aiModelId,
parameters,
temperature = 0.7,
maxTokens = 1024
} = body;
@ -527,7 +530,7 @@ export async function POST(request: NextRequest) {
let text: string;
if (provider === "popiserver") {
text = await generateWithPopiserver(request, login!.token, prompt, model, aiModelId, images, videos, requestId);
text = await generateWithPopiserver(request, login!.token, prompt, model, aiModelId, images, videos, parameters, requestId);
} else if (provider === "newapiwg") {
const newApiWGKey = await resolveUserGatewayApiKey(request);
text = await generateWithNewApiWG(prompt, model, temperature, maxTokens, images, videos, requestId, newApiWGKey, newApiWGBaseUrl, loginToken);

16
src/components/composer/GenerationComposer.tsx

@ -217,18 +217,22 @@ function buildGatewayDimensionSelects(
});
}
function applyGatewayDimensionParameterDefaults(
function getSchemaParameterFallbackValue(parameter: ModelParameter): unknown {
return parameter.default ?? parameter.options?.[0]?.value ?? parameter.enum?.[0];
}
function applySchemaParameterDefaults(
schema: ModelParameter[] | undefined,
parameters: Record<string, unknown> | undefined
): { parameters: Record<string, unknown>; changed: boolean } {
const next = { ...(parameters ?? {}) };
let changed = false;
const nameSet = new Set(GATEWAY_DIMENSION_PARAMETER_NAMES);
for (const parameter of schema ?? []) {
if (!nameSet.has(parameter.name) || !parameter.options || parameter.options.length === 0) continue;
if (next[parameter.name] !== undefined && next[parameter.name] !== null && next[parameter.name] !== "") continue;
next[parameter.name] = parameter.options[0]?.value;
const value = getSchemaParameterFallbackValue(parameter);
if (value === undefined || value === null || value === "") continue;
next[parameter.name] = value;
changed = true;
}
@ -1569,11 +1573,11 @@ export function GenerationComposer() {
const activeParameterSchema = generationNodeData?.parameterSchema ?? [];
useEffect(() => {
if (context.mode !== "node-edit" || !context.node || activeParameterSchema.length === 0) return;
const normalized = applyGatewayDimensionParameterDefaults(activeParameterSchema, draftRef.current.parameters);
const normalized = applySchemaParameterDefaults(activeParameterSchema, draftRef.current.parameters);
if (!normalized.changed) return;
setDraft((current) => {
const currentNormalized = applyGatewayDimensionParameterDefaults(activeParameterSchema, current.parameters);
const currentNormalized = applySchemaParameterDefaults(activeParameterSchema, current.parameters);
if (!currentNormalized.changed) return current;
const next = { ...current, parameters: currentNormalized.parameters };
draftRef.current = next;

17
src/store/execution/generateVideoExecutor.ts

@ -32,6 +32,7 @@ import {
uploadTemporaryDynamicInputsForGeneration,
uploadTemporaryImagesForGeneration,
} from "@/utils/temporaryImageUpload";
import { mergeConfiguredParameters } from "./generationParameters";
export interface GenerateVideoOptions {
/** When true, falls back to stored inputImages/inputPrompt if no connections provide them. */
@ -44,14 +45,22 @@ function buildVideoParameters(
parametersOverride?: Record<string, unknown>
): Record<string, unknown> | undefined {
const parameters = { ...(parametersOverride ?? nodeData.parameters ?? {}) };
if (modelToUse.provider === "popiserver") {
return Object.keys(parameters).length > 0 ? parameters : undefined;
}
const durationSeconds = normalizeVideoDurationSeconds(nodeData.durationSeconds);
const resolution = nodeData.resolution ?? DEFAULT_VIDEO_RESOLUTION;
const soundParameterName = getVideoSoundParameterName(modelToUse);
if (modelToUse.provider === "popiserver") {
const merged = mergeConfiguredParameters(parameters, {
...(nodeData.aspectRatio ? { aspectRatio: nodeData.aspectRatio, aspect_ratio: nodeData.aspectRatio } : {}),
resolution,
duration: durationSeconds,
durationSeconds,
...(nodeData.fps !== undefined ? { fps: nodeData.fps } : {}),
...(soundParameterName ? { [soundParameterName]: nodeData.soundEnabled ?? getDefaultVideoSoundEnabled(modelToUse) } : {}),
});
return Object.keys(merged).length > 0 ? merged : undefined;
}
if (nodeData.aspectRatio) parameters.aspectRatio = nodeData.aspectRatio;
for (const name of VIDEO_RESOLUTION_PARAMETER_NAMES) delete parameters[name];
parameters.resolution = resolution;

52
src/store/execution/generationParameters.ts

@ -0,0 +1,52 @@
const IMAGE_COUNT_PARAMETER_NAMES = [
"batchSize",
"imageCount",
"numImages",
"num_images",
"numOutputs",
"num_outputs",
"count",
];
export function positiveInteger(value: unknown): number | null {
const numeric = typeof value === "number"
? value
: typeof value === "string" && value.trim().length > 0
? Number(value)
: NaN;
return Number.isInteger(numeric) && numeric > 0 ? numeric : null;
}
export function mergeConfiguredParameters(
base: Record<string, unknown> | undefined,
configured: Record<string, unknown>
): Record<string, unknown> {
const parameters = { ...(base ?? {}) };
for (const [key, value] of Object.entries(configured)) {
if (value !== undefined && value !== null) {
parameters[key] = parameters[key] ?? value;
}
}
return parameters;
}
export function resolveImageGenerationCount(
parameters: Record<string, unknown> | undefined,
nodeImageCount: number | undefined
): number {
for (const name of IMAGE_COUNT_PARAMETER_NAMES) {
const value = positiveInteger(parameters?.[name]);
if (value !== null) return value;
}
return positiveInteger(nodeImageCount) ?? 1;
}
export function mergeImageGenerationParameters(
base: Record<string, unknown> | undefined,
configured: Record<string, unknown>,
imageCount: number
): Record<string, unknown> {
const parameters = mergeConfiguredParameters(base, configured);
parameters.batchSize = positiveInteger(parameters.batchSize) ?? imageCount;
return parameters;
}

7
src/store/execution/llmGenerateExecutor.ts

@ -16,6 +16,7 @@ import { buildLlmHeaders } from "@/store/utils/buildApiHeaders";
import { isPopiProviderMode } from "@/lib/providerMode";
import { runWithFallback } from "./runWithFallback";
import type { NodeExecutionContext } from "./types";
import { mergeConfiguredParameters } from "./generationParameters";
export interface LlmGenerateOptions {
/** When true, falls back to stored inputImages/inputPrompt if no connections provide them. */
@ -105,6 +106,11 @@ export async function executeLlmGenerate(
const temperature = (parametersOverride?.temperature as number | undefined) ?? nodeData.temperature;
const maxTokens = (parametersOverride?.maxTokens as number | undefined) ?? nodeData.maxTokens;
const effectiveParameters = mergeConfiguredParameters(parametersOverride, {
temperature,
maxTokens,
max_tokens: maxTokens,
});
try {
const response = await fetch("/api/llm", {
@ -119,6 +125,7 @@ export async function executeLlmGenerate(
...(aiModelId !== undefined && { aiModelId }),
temperature,
maxTokens,
parameters: effectiveParameters,
}),
...(signal ? { signal } : {}),
});

40
src/store/execution/nanoBananaExecutor.ts

@ -34,6 +34,11 @@ import {
uploadTemporaryDynamicInputsForGeneration,
uploadTemporaryImagesForGeneration,
} from "@/utils/temporaryImageUpload";
import {
mergeConfiguredParameters,
mergeImageGenerationParameters,
resolveImageGenerationCount,
} from "./generationParameters";
export interface NanoBananaOptions {
/** When true, falls back to stored inputImages/inputPrompt if no connections provide them. */
@ -161,6 +166,25 @@ export async function executeNanoBanana(
...preparedDynamicInputs.cleanupIds,
];
const requestedImageCount = resolveImageGenerationCount(
parametersOverride ?? nodeData.parameters,
nodeData.imageCount
);
const configuredParameters = {
aspectRatio: (parametersOverride?.aspectRatio as string | undefined) ?? nodeData.aspectRatio,
aspect_ratio: (parametersOverride?.aspectRatio as string | undefined) ?? nodeData.aspectRatio,
resolution: (parametersOverride?.resolution as string | undefined) ?? nodeData.resolution,
imageSize: (parametersOverride?.resolution as string | undefined) ?? nodeData.resolution,
useGoogleSearch: (parametersOverride?.useGoogleSearch as boolean | undefined) ?? nodeData.useGoogleSearch,
useImageSearch: (parametersOverride?.useImageSearch as boolean | undefined) ?? nodeData.useImageSearch,
};
const effectiveParameters = provider === "popiserver"
? mergeImageGenerationParameters(
parametersOverride ?? nodeData.parameters,
configuredParameters,
requestedImageCount
)
: mergeConfiguredParameters(parametersOverride ?? nodeData.parameters, configuredParameters);
const requestPayload = {
images: preparedImages.images,
prompt: finalPrompt,
@ -170,7 +194,7 @@ export async function executeNanoBanana(
useGoogleSearch: (parametersOverride?.useGoogleSearch as boolean) ?? nodeData.useGoogleSearch,
useImageSearch: (parametersOverride?.useImageSearch as boolean) ?? nodeData.useImageSearch,
selectedModel: modelToUse,
parameters: parametersOverride ?? nodeData.parameters,
parameters: effectiveParameters,
dynamicInputs: preparedDynamicInputs.dynamicInputs,
};
@ -182,9 +206,6 @@ export async function executeNanoBanana(
throw new Error(errorMsg);
}
const requestedImageCount = nodeData.imageCount === 2 || nodeData.imageCount === 4
? nodeData.imageCount
: 1;
const usePopiserverBatch = provider === "popiserver" && requestedImageCount > 1;
const requestCount = usePopiserverBatch ? 1 : requestedImageCount;
const usedModelId = modelToUse.modelId || nodeData.model;
@ -198,19 +219,10 @@ export async function executeNanoBanana(
let partialError: string | null = null;
for (let requestIndex = 0; requestIndex < requestCount; requestIndex++) {
const payloadForRequest = usePopiserverBatch
? {
...requestPayload,
parameters: {
...(requestPayload.parameters || {}),
batchSize: requestedImageCount,
},
}
: requestPayload;
const response = await fetch("/api/generate", {
method: "POST",
headers,
body: JSON.stringify(payloadForRequest),
body: JSON.stringify(requestPayload),
...(signal ? { signal } : {}),
});

1
src/types/api.ts

@ -51,6 +51,7 @@ export interface LLMGenerateRequest {
aiModelId?: number;
temperature?: number;
maxTokens?: number;
parameters?: Record<string, unknown>;
}
export interface LLMGenerateResponse {

Loading…
Cancel
Save