diff --git a/dto/openai_request.go b/dto/openai_request.go index 5727327d9a5d..02736b49a56f 100644 --- a/dto/openai_request.go +++ b/dto/openai_request.go @@ -46,7 +46,7 @@ type GeneralOpenAIRequest struct { Input any `json:"input,omitempty"` Instruction string `json:"instruction,omitempty"` Size string `json:"size,omitempty"` - Seconds *int `json:"seconds,omitempty"` + Seconds *string `json:"seconds,omitempty"` Quality *string `json:"quality,omitempty"` Functions json.RawMessage `json:"functions,omitempty"` FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` diff --git a/web/src/constants/playground.constants.js b/web/src/constants/playground.constants.js index 659450179280..3dd70cbc113d 100644 --- a/web/src/constants/playground.constants.js +++ b/web/src/constants/playground.constants.js @@ -76,6 +76,7 @@ export const DEBUG_TABS = { // ========== API 相关常量 ========== export const API_ENDPOINTS = { CHAT_COMPLETIONS: '/pg/chat/completions', + VIDEO_GENERATIONS: '/v1/video/generations', USER_MODELS: '/api/user/models', USER_GROUPS: '/api/user/self/groups', }; diff --git a/web/src/helpers/api.js b/web/src/helpers/api.js index c0317ed7b4cd..dcb8b5334e79 100644 --- a/web/src/helpers/api.js +++ b/web/src/helpers/api.js @@ -158,14 +158,12 @@ export const buildApiPayload = ( const isVideoModel = typeof inputs.model === 'string' && inputs.model.includes('video'); if (isVideoModel) { + payload.stream = false; if (inputs.videoSize) { payload.size = inputs.videoSize; } if (inputs.videoSeconds) { - const parsedSeconds = Number.parseInt(inputs.videoSeconds, 10); - if (Number.isFinite(parsedSeconds)) { - payload.seconds = parsedSeconds; - } + payload.seconds = String(inputs.videoSeconds); } if (inputs.videoQuality) { payload.quality = inputs.videoQuality; diff --git a/web/src/hooks/playground/useApiRequest.jsx b/web/src/hooks/playground/useApiRequest.jsx index 8ec50cf45d92..405eb3234207 100644 --- a/web/src/hooks/playground/useApiRequest.jsx +++ b/web/src/hooks/playground/useApiRequest.jsx @@ -40,6 +40,78 @@ export const useApiRequest = ( saveMessages, ) => { const { t } = useTranslation(); + const isVideoGenerationPayload = useCallback((payload) => { + const model = payload?.model; + return typeof model === 'string' && model.includes('video'); + }, []); + + const getTextFromMessageContent = useCallback((content) => { + if (typeof content === 'string') { + return content; + } + if (!Array.isArray(content)) { + return ''; + } + const textParts = content + .filter((item) => item?.type === 'text') + .map((item) => item?.text || '') + .filter(Boolean); + return textParts.join('\n'); + }, []); + + const getImageFromMessageContent = useCallback((content) => { + if (!Array.isArray(content)) { + return ''; + } + const imageItem = content.find((item) => item?.type === 'image_url'); + if (!imageItem) { + return ''; + } + const imageURL = imageItem.image_url; + if (typeof imageURL === 'string') { + return imageURL; + } + return imageURL?.url || ''; + }, []); + + const buildVideoRequestPayload = useCallback( + (payload) => { + const messages = Array.isArray(payload?.messages) ? payload.messages : []; + const lastUserMessage = [...messages] + .reverse() + .find((m) => m?.role === 'user'); + const prompt = getTextFromMessageContent(lastUserMessage?.content); + const image = getImageFromMessageContent(lastUserMessage?.content); + + return { + model: payload.model, + prompt, + seconds: payload.seconds, + size: payload.size, + quality: payload.quality, + ...(image ? { image } : {}), + }; + }, + [getImageFromMessageContent, getTextFromMessageContent], + ); + + const resolveEndpointAndPayload = useCallback( + (payload) => { + if (isVideoGenerationPayload(payload)) { + return { + endpoint: API_ENDPOINTS.VIDEO_GENERATIONS, + requestPayload: buildVideoRequestPayload(payload), + forceNonStream: true, + }; + } + return { + endpoint: API_ENDPOINTS.CHAT_COMPLETIONS, + requestPayload: payload, + forceNonStream: false, + }; + }, + [buildVideoRequestPayload, isVideoGenerationPayload], + ); // 处理消息自动关闭逻辑的公共函数 const applyAutoCollapseLogic = useCallback( @@ -174,9 +246,10 @@ export const useApiRequest = ( // 非流式请求 const handleNonStreamRequest = useCallback( async (payload) => { + const { endpoint, requestPayload } = resolveEndpointAndPayload(payload); setDebugData((prev) => ({ ...prev, - request: payload, + request: requestPayload, timestamp: new Date().toISOString(), response: null, sseMessages: null, // 非流式请求清除 SSE 消息 @@ -185,13 +258,13 @@ export const useApiRequest = ( setActiveDebugTab(DEBUG_TABS.REQUEST); try { - const response = await fetch(API_ENDPOINTS.CHAT_COMPLETIONS, { + const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'New-Api-User': getUserIdFromLocalStorage(), }, - body: JSON.stringify(payload), + body: JSON.stringify(requestPayload), }); if (!response.ok) { @@ -228,6 +301,38 @@ export const useApiRequest = ( })); setActiveDebugTab(DEBUG_TABS.RESPONSE); + if ( + endpoint === API_ENDPOINTS.VIDEO_GENERATIONS || + data.object === 'video' || + data.task_id + ) { + const summary = [ + `${t('视频任务已创建')}`, + `task_id: ${data.task_id || data.id || '-'}`, + `status: ${data.status || '-'}`, + `seconds: ${data.seconds || requestPayload.seconds || '-'}`, + `size: ${data.size || requestPayload.size || '-'}`, + ].join('\n'); + setMessage((prevMessage) => { + const newMessages = [...prevMessage]; + const lastMessage = newMessages[newMessages.length - 1]; + if (lastMessage?.status === MESSAGE_STATUS.LOADING) { + const autoCollapseState = applyAutoCollapseLogic( + lastMessage, + true, + ); + newMessages[newMessages.length - 1] = { + ...lastMessage, + content: summary, + status: MESSAGE_STATUS.COMPLETE, + ...autoCollapseState, + }; + } + return newMessages; + }); + return; + } + if (data.choices?.[0]) { const choice = data.choices[0]; let content = choice.message?.content || ''; @@ -285,7 +390,14 @@ export const useApiRequest = ( }); } }, - [setDebugData, setActiveDebugTab, setMessage, t, applyAutoCollapseLogic], + [ + resolveEndpointAndPayload, + setDebugData, + setActiveDebugTab, + setMessage, + t, + applyAutoCollapseLogic, + ], ); // SSE请求 @@ -500,13 +612,14 @@ export const useApiRequest = ( // 发送请求 const sendRequest = useCallback( (payload, isStream) => { - if (isStream) { + const { forceNonStream } = resolveEndpointAndPayload(payload); + if (isStream && !forceNonStream) { handleSSE(payload); } else { handleNonStreamRequest(payload); } }, - [handleSSE, handleNonStreamRequest], + [resolveEndpointAndPayload, handleSSE, handleNonStreamRequest], ); return {