diff --git a/src/components/FloatingActionBar.tsx b/src/components/FloatingActionBar.tsx index 8d345a42..52231c19 100644 --- a/src/components/FloatingActionBar.tsx +++ b/src/components/FloatingActionBar.tsx @@ -8,6 +8,7 @@ import { useReactFlow } from "@xyflow/react"; import { ModelSearchDialog } from "./modals/ModelSearchDialog"; import { useFTUXStore, TutorialStep } from "@/store/ftuxStore"; import { TranslationKey, useI18n } from "@/i18n"; +import { buildDefaultGenerationNodeData } from "@/utils/defaultGenerationNodeModel"; // All nodes menu categories const ALL_NODES_CATEGORIES: { labelKey: TranslationKey; nodes: { type: NodeType; labelKey: TranslationKey }[] }[] = [ @@ -183,14 +184,14 @@ function GenerateComboButton() { }; }, [isOpen]); - const handleAddNode = (type: NodeType) => { + const handleAddNode = async (type: NodeType) => { const center = getPaneCenter(); const position = screenToFlowPosition({ x: center.x + Math.random() * 100 - 50, y: center.y + Math.random() * 100 - 50, }); - addNode(type, position); + addNode(type, position, await buildDefaultGenerationNodeData(type)); setIsOpen(false); }; @@ -303,14 +304,14 @@ function AllNodesMenu() { }; }, [isOpen]); - const handleAddNode = useCallback((type: NodeType) => { + const handleAddNode = useCallback(async (type: NodeType) => { const center = getPaneCenter(); const position = screenToFlowPosition({ x: center.x + Math.random() * 100 - 50, y: center.y + Math.random() * 100 - 50, }); - addNode(type, position); + addNode(type, position, await buildDefaultGenerationNodeData(type)); setIsOpen(false); }, [addNode, screenToFlowPosition]); diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index 797b75ad..f18b99a0 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/src/components/WorkflowCanvas.tsx @@ -99,6 +99,7 @@ import { TutorialOverlay } from "./onboarding/TutorialOverlay"; import { useFTUXStore } from "@/store/ftuxStore"; import { useI18n } from "@/i18n"; import { isPopiProviderMode } from "@/lib/providerMode"; +import { buildDefaultGenerationNodeData } from "@/utils/defaultGenerationNodeModel"; const nodeTypes: NodeTypes = { imageInput: ImageInputNode, @@ -2040,7 +2041,13 @@ export function WorkflowCanvas() { glbViewer: { width: 360, height: 380 }, }; const dims = defaultDimensions[nodeType]; - addNode(nodeType, { x: centerX - dims.width / 2, y: centerY - dims.height / 2 }); + void (async () => { + addNode( + nodeType, + { x: centerX - dims.width / 2, y: centerY - dims.height / 2 }, + await buildDefaultGenerationNodeData(nodeType) + ); + })(); return; } } @@ -2321,7 +2328,7 @@ export function WorkflowCanvas() { }, []); const handleDrop = useCallback( - (event: DragEvent) => { + async (event: DragEvent) => { event.preventDefault(); setIsDragOver(false); setDropType(null); @@ -2333,7 +2340,7 @@ export function WorkflowCanvas() { x: event.clientX, y: event.clientY, }); - addNode(nodeType, position); + addNode(nodeType, position, await buildDefaultGenerationNodeData(nodeType)); return; } diff --git a/src/lib/llmModels.ts b/src/lib/llmModels.ts index 9891a97f..91635941 100644 --- a/src/lib/llmModels.ts +++ b/src/lib/llmModels.ts @@ -1,5 +1,6 @@ import type { LLMModelType, LLMProvider } from "@/types"; import { getProviderDisplayName, isPopiProviderMode } from "@/lib/providerMode"; +import { POPISERVER_DEFAULT_LLM_MODEL_ID } from "@/store/utils/defaultImageModel"; export interface LLMModelOption { value: LLMModelType; @@ -7,7 +8,7 @@ export interface LLMModelOption { } export const DEFAULT_NEWAPIWG_LLM_MODEL_ID: LLMModelType = "doubao-seed-2-0-lite-260428"; -export const DEFAULT_POPISERVER_LLM_MODEL_ID: LLMModelType = "kimi-k2.6"; +export const DEFAULT_POPISERVER_LLM_MODEL_ID: LLMModelType = POPISERVER_DEFAULT_LLM_MODEL_ID; const ALL_LLM_PROVIDERS: { value: LLMProvider; label: string }[] = [ { value: "popiserver", label: getProviderDisplayName("popiserver") }, diff --git a/src/store/utils/defaultImageModel.ts b/src/store/utils/defaultImageModel.ts index 84e917b0..e1ac75c3 100644 --- a/src/store/utils/defaultImageModel.ts +++ b/src/store/utils/defaultImageModel.ts @@ -7,12 +7,16 @@ const NEWAPIWG_DEFAULT_IMAGE_FALLBACK_MODEL_ID = "apiyi_nano_banana_2"; const NEWAPIWG_LEGACY_DEFAULT_IMAGE_MODEL_ID = "apiyi_nano_banana_2"; const NEWAPIWG_DEFAULT_VIDEO_MODEL_ID = "viduq3-turbo"; const DEFAULT_AUDIO_MODEL_ID = "elevenlabs/turbo-v2.5"; +export const POPISERVER_DEFAULT_IMAGE_MODEL_ID = "gpt-image-2-all"; +export const POPISERVER_DEFAULT_VIDEO_MODEL_ID = "viduq3-turbo"; +export const POPISERVER_DEFAULT_AUDIO_MODEL_ID = "speech-2.8-hd"; +export const POPISERVER_DEFAULT_LLM_MODEL_ID = "kimi-k2.6"; export function createPopiserverDefaultImageModel(): SelectedModel { return { provider: POPI_PROVIDER_ID, - modelId: "", - displayName: "Select model", + modelId: POPISERVER_DEFAULT_IMAGE_MODEL_ID, + displayName: POPISERVER_DEFAULT_IMAGE_MODEL_ID, capabilities: ["text-to-image", "image-to-image"], }; } @@ -20,8 +24,8 @@ export function createPopiserverDefaultImageModel(): SelectedModel { export function createPopiserverDefaultVideoModel(): SelectedModel { return { provider: POPI_PROVIDER_ID, - modelId: "", - displayName: "Select model", + modelId: POPISERVER_DEFAULT_VIDEO_MODEL_ID, + displayName: POPISERVER_DEFAULT_VIDEO_MODEL_ID, capabilities: ["text-to-video", "image-to-video"], }; } @@ -29,8 +33,8 @@ export function createPopiserverDefaultVideoModel(): SelectedModel { export function createPopiserverDefaultAudioModel(): SelectedModel { return { provider: POPI_PROVIDER_ID, - modelId: "", - displayName: "Select model", + modelId: POPISERVER_DEFAULT_AUDIO_MODEL_ID, + displayName: POPISERVER_DEFAULT_AUDIO_MODEL_ID, capabilities: ["text-to-audio"], }; } diff --git a/src/store/utils/nodeDefaults.ts b/src/store/utils/nodeDefaults.ts index b640fa76..1c719943 100644 --- a/src/store/utils/nodeDefaults.ts +++ b/src/store/utils/nodeDefaults.ts @@ -28,7 +28,7 @@ import { SelectedModel, } from "@/types"; import { loadGenerateImageDefaults, loadNodeDefaults } from "./localStorage"; -import { DEFAULT_NEWAPIWG_LLM_MODEL_ID } from "@/lib/llmModels"; +import { DEFAULT_NEWAPIWG_LLM_MODEL_ID, DEFAULT_POPISERVER_LLM_MODEL_ID } from "@/lib/llmModels"; import { createDefaultAudioModel, createNewApiWGDefaultImageModel, @@ -246,7 +246,7 @@ export const createDefaultNodeData = (type: NodeType): WorkflowNodeData => { inputVideos: [], outputText: null, provider: llmDefaults?.provider ?? (isPopiProviderMode() ? "popiserver" : "newapiwg"), - model: llmDefaults?.model ?? DEFAULT_NEWAPIWG_LLM_MODEL_ID, + model: llmDefaults?.model ?? (isPopiProviderMode() ? DEFAULT_POPISERVER_LLM_MODEL_ID : DEFAULT_NEWAPIWG_LLM_MODEL_ID), temperature: llmDefaults?.temperature ?? 0.7, maxTokens: llmDefaults?.maxTokens ?? 8192, status: "idle", diff --git a/src/utils/defaultGenerationNodeModel.ts b/src/utils/defaultGenerationNodeModel.ts new file mode 100644 index 00000000..45907f2b --- /dev/null +++ b/src/utils/defaultGenerationNodeModel.ts @@ -0,0 +1,101 @@ +import type { LLMModelType, NodeType, SelectedModel, WorkflowNodeData } from "@/types"; +import type { ProviderModel } from "@/lib/providers/types"; +import { isPopiProviderMode, POPI_PROVIDER_ID } from "@/lib/providerMode"; +import { useModelStore, type PopiModelKind } from "@/store/modelStore"; +import { loadNodeDefaults } from "@/store/utils/localStorage"; +import { + POPISERVER_DEFAULT_AUDIO_MODEL_ID, + POPISERVER_DEFAULT_IMAGE_MODEL_ID, + POPISERVER_DEFAULT_LLM_MODEL_ID, + POPISERVER_DEFAULT_VIDEO_MODEL_ID, +} from "@/store/utils/defaultImageModel"; + +type GenerationNodeType = "nanoBanana" | "generateVideo" | "generateAudio" | "llmGenerate"; + +function getKindForNodeType(type: NodeType): PopiModelKind | null { + if (type === "nanoBanana") return "image"; + if (type === "generateVideo") return "video"; + if (type === "generateAudio") return "audio"; + if (type === "llmGenerate") return "llm"; + return null; +} + +function getConfiguredPopiModelId(type: GenerationNodeType): string | null { + const defaults = loadNodeDefaults(); + if (type === "nanoBanana") { + const model = defaults.generateImage?.selectedModel; + return model?.provider === POPI_PROVIDER_ID && model.modelId ? model.modelId : null; + } + if (type === "generateVideo") { + const model = defaults.generateVideo?.selectedModel; + return model?.provider === POPI_PROVIDER_ID && model.modelId ? model.modelId : null; + } + if (type === "generateAudio") { + const model = defaults.generateAudio?.selectedModel; + return model?.provider === POPI_PROVIDER_ID && model.modelId ? model.modelId : null; + } + const llm = defaults.llm; + return llm?.provider === POPI_PROVIDER_ID && llm.model ? llm.model : null; +} + +function getCodeDefaultPopiModelId(type: GenerationNodeType): string { + if (type === "nanoBanana") return POPISERVER_DEFAULT_IMAGE_MODEL_ID; + if (type === "generateVideo") return POPISERVER_DEFAULT_VIDEO_MODEL_ID; + if (type === "generateAudio") return POPISERVER_DEFAULT_AUDIO_MODEL_ID; + return POPISERVER_DEFAULT_LLM_MODEL_ID; +} + +function matchesConfiguredModel(model: ProviderModel, configuredModelId: string): boolean { + return ( + model.id === configuredModelId || + String(model.metadata?.aiModelId ?? "") === configuredModelId || + String(model.metadata?.aiModelCode ?? "") === configuredModelId || + String(model.metadata?.aiModelCodeAlias ?? "") === configuredModelId + ); +} + +function providerModelToSelectedModel(model: ProviderModel): SelectedModel { + return { + provider: model.provider, + modelId: model.id, + displayName: model.name, + capabilities: model.capabilities, + metadata: model.metadata, + pricing: model.pricing + ? { + type: model.pricing.type, + amount: model.pricing.amount, + variants: model.pricing.variants, + } + : undefined, + }; +} + +export async function buildDefaultGenerationNodeData( + type: NodeType +): Promise | undefined> { + if (!isPopiProviderMode()) return undefined; + + const kind = getKindForNodeType(type); + if (!kind || kind === "3d") return undefined; + + const generationType = type as GenerationNodeType; + await useModelStore.getState().initializePopiModels(); + + const models = useModelStore.getState().byKind[kind]; + if (models.length === 0) return undefined; + + const preferredModelId = getConfiguredPopiModelId(generationType) ?? getCodeDefaultPopiModelId(generationType); + const model = models.find((item) => matchesConfiguredModel(item, preferredModelId)) ?? models[0]; + const selectedModel = providerModelToSelectedModel(model); + + if (generationType === "llmGenerate") { + return { + provider: POPI_PROVIDER_ID, + model: selectedModel.modelId as LLMModelType, + selectedModel, + } as Partial; + } + + return { selectedModel } as Partial; +}