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, aiModelId?: number,
images?: string[], images?: string[],
videos?: string[], videos?: string[],
parameters?: Record<string, unknown>,
requestId?: string requestId?: string
): Promise<string> { ): Promise<string> {
const modelId = model || "kimi-k2.6"; const modelId = model || "kimi-k2.6";
@ -346,6 +347,7 @@ async function generateWithPopiserver(
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
...(parameters || {}),
messages: [ messages: [
{ role: "system", content: "You are a helpful assistant." }, { role: "system", content: "You are a helpful assistant." },
{ role: "user", content }, { role: "user", content },
@ -500,6 +502,7 @@ export async function POST(request: NextRequest) {
provider, provider,
model, model,
aiModelId, aiModelId,
parameters,
temperature = 0.7, temperature = 0.7,
maxTokens = 1024 maxTokens = 1024
} = body; } = body;
@ -527,7 +530,7 @@ export async function POST(request: NextRequest) {
let text: string; let text: string;
if (provider === "popiserver") { 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") { } else if (provider === "newapiwg") {
const newApiWGKey = await resolveUserGatewayApiKey(request); const newApiWGKey = await resolveUserGatewayApiKey(request);
text = await generateWithNewApiWG(prompt, model, temperature, maxTokens, images, videos, requestId, newApiWGKey, newApiWGBaseUrl, loginToken); 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, schema: ModelParameter[] | undefined,
parameters: Record<string, unknown> | undefined parameters: Record<string, unknown> | undefined
): { parameters: Record<string, unknown>; changed: boolean } { ): { parameters: Record<string, unknown>; changed: boolean } {
const next = { ...(parameters ?? {}) }; const next = { ...(parameters ?? {}) };
let changed = false; let changed = false;
const nameSet = new Set(GATEWAY_DIMENSION_PARAMETER_NAMES);
for (const parameter of schema ?? []) { 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; 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; changed = true;
} }
@ -1569,11 +1573,11 @@ export function GenerationComposer() {
const activeParameterSchema = generationNodeData?.parameterSchema ?? []; const activeParameterSchema = generationNodeData?.parameterSchema ?? [];
useEffect(() => { useEffect(() => {
if (context.mode !== "node-edit" || !context.node || activeParameterSchema.length === 0) return; 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; if (!normalized.changed) return;
setDraft((current) => { setDraft((current) => {
const currentNormalized = applyGatewayDimensionParameterDefaults(activeParameterSchema, current.parameters); const currentNormalized = applySchemaParameterDefaults(activeParameterSchema, current.parameters);
if (!currentNormalized.changed) return current; if (!currentNormalized.changed) return current;
const next = { ...current, parameters: currentNormalized.parameters }; const next = { ...current, parameters: currentNormalized.parameters };
draftRef.current = next; draftRef.current = next;

17
src/store/execution/generateVideoExecutor.ts

@ -32,6 +32,7 @@ import {
uploadTemporaryDynamicInputsForGeneration, uploadTemporaryDynamicInputsForGeneration,
uploadTemporaryImagesForGeneration, uploadTemporaryImagesForGeneration,
} from "@/utils/temporaryImageUpload"; } from "@/utils/temporaryImageUpload";
import { mergeConfiguredParameters } from "./generationParameters";
export interface GenerateVideoOptions { export interface GenerateVideoOptions {
/** When true, falls back to stored inputImages/inputPrompt if no connections provide them. */ /** When true, falls back to stored inputImages/inputPrompt if no connections provide them. */
@ -44,14 +45,22 @@ function buildVideoParameters(
parametersOverride?: Record<string, unknown> parametersOverride?: Record<string, unknown>
): Record<string, unknown> | undefined { ): Record<string, unknown> | undefined {
const parameters = { ...(parametersOverride ?? nodeData.parameters ?? {}) }; const parameters = { ...(parametersOverride ?? nodeData.parameters ?? {}) };
if (modelToUse.provider === "popiserver") {
return Object.keys(parameters).length > 0 ? parameters : undefined;
}
const durationSeconds = normalizeVideoDurationSeconds(nodeData.durationSeconds); const durationSeconds = normalizeVideoDurationSeconds(nodeData.durationSeconds);
const resolution = nodeData.resolution ?? DEFAULT_VIDEO_RESOLUTION; const resolution = nodeData.resolution ?? DEFAULT_VIDEO_RESOLUTION;
const soundParameterName = getVideoSoundParameterName(modelToUse); 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; if (nodeData.aspectRatio) parameters.aspectRatio = nodeData.aspectRatio;
for (const name of VIDEO_RESOLUTION_PARAMETER_NAMES) delete parameters[name]; for (const name of VIDEO_RESOLUTION_PARAMETER_NAMES) delete parameters[name];
parameters.resolution = resolution; 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 { isPopiProviderMode } from "@/lib/providerMode";
import { runWithFallback } from "./runWithFallback"; import { runWithFallback } from "./runWithFallback";
import type { NodeExecutionContext } from "./types"; import type { NodeExecutionContext } from "./types";
import { mergeConfiguredParameters } from "./generationParameters";
export interface LlmGenerateOptions { export interface LlmGenerateOptions {
/** When true, falls back to stored inputImages/inputPrompt if no connections provide them. */ /** 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 temperature = (parametersOverride?.temperature as number | undefined) ?? nodeData.temperature;
const maxTokens = (parametersOverride?.maxTokens as number | undefined) ?? nodeData.maxTokens; const maxTokens = (parametersOverride?.maxTokens as number | undefined) ?? nodeData.maxTokens;
const effectiveParameters = mergeConfiguredParameters(parametersOverride, {
temperature,
maxTokens,
max_tokens: maxTokens,
});
try { try {
const response = await fetch("/api/llm", { const response = await fetch("/api/llm", {
@ -119,6 +125,7 @@ export async function executeLlmGenerate(
...(aiModelId !== undefined && { aiModelId }), ...(aiModelId !== undefined && { aiModelId }),
temperature, temperature,
maxTokens, maxTokens,
parameters: effectiveParameters,
}), }),
...(signal ? { signal } : {}), ...(signal ? { signal } : {}),
}); });

40
src/store/execution/nanoBananaExecutor.ts

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

1
src/types/api.ts

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

Loading…
Cancel
Save