import type { TextGenerationCustomConfig } from '../types' import type { MoreLikeThisConfig, PromptConfig, SavedMessage, TextToSpeechConfig } from '@/models/debug' import type { SiteInfo } from '@/models/share' import type { VisionSettings } from '@/types/app' import { toast } from '@langgenius/dify-ui/toast' import { useSuspenseQuery } from '@tanstack/react-query' import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { getRawInputsFromUrlParams } from '@/app/components/base/chat/utils' import { useWebAppStore } from '@/context/web-app-context' import { useAppFavicon } from '@/hooks/use-app-favicon' import useDocumentTitle from '@/hooks/use-document-title' import { changeLanguage } from '@/i18n-config/client' import { AppSourceType, fetchSavedMessage as doFetchSavedMessage, removeMessage, saveMessage } from '@/service/share' import { systemFeaturesQueryOptions } from '@/service/system-features' import { Resolution, TransferMethod } from '@/types/app' import { userInputsFormToPromptVariables } from '@/utils/model-config' type UseTextGenerationAppStateOptions = { isInstalledApp: boolean isWorkflow: boolean } type ShareAppParams = { user_input_form: Parameters[0] more_like_this: MoreLikeThisConfig | null file_upload: VisionSettings & { allowed_file_upload_methods?: TransferMethod[] allowed_upload_methods?: TransferMethod[] } text_to_speech: TextToSpeechConfig | null system_parameters?: Record & { image_file_size_limit?: number } } const coerceWorkflowUrlDefault = ( promptVariable: NonNullable[number], rawValue: unknown, ) => { if (rawValue === undefined || rawValue === null) return undefined if (promptVariable.type === 'checkbox') { if (typeof rawValue === 'boolean') return rawValue const normalized = String(rawValue).toLowerCase() if (normalized === 'true') return true if (normalized === 'false') return false return undefined } if (promptVariable.type === 'number') { const numericValue = Number(rawValue) return Number.isNaN(numericValue) ? undefined : numericValue } if (typeof rawValue !== 'string') return undefined if (promptVariable.type === 'select') return promptVariable.options?.includes(rawValue) ? rawValue : undefined if (promptVariable.max_length) return rawValue.slice(0, promptVariable.max_length) return rawValue } export const useTextGenerationAppState = ({ isInstalledApp, isWorkflow }: UseTextGenerationAppStateOptions) => { const { t } = useTranslation() const appSourceType = isInstalledApp ? AppSourceType.installedApp : AppSourceType.webApp const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions()) const appData = useWebAppStore(s => s.appInfo) const appParams = useWebAppStore(s => s.appParams) const accessMode = useWebAppStore(s => s.webAppAccessMode) const [appId, setAppId] = useState('') const [siteInfo, setSiteInfo] = useState(null) const [customConfig, setCustomConfig] = useState(null) const [promptConfig, setPromptConfig] = useState(null) const [moreLikeThisConfig, setMoreLikeThisConfig] = useState(null) const [textToSpeechConfig, setTextToSpeechConfig] = useState(null) const [savedMessages, setSavedMessages] = useState([]) const [visionConfig, setVisionConfig] = useState({ enabled: false, number_limits: 2, detail: Resolution.low, transfer_methods: [TransferMethod.local_file], }) const fetchSavedMessages = useCallback(async (targetAppId = appId) => { if (!targetAppId) return const res = await doFetchSavedMessage(appSourceType, targetAppId) as { data: SavedMessage[] } setSavedMessages(res.data) }, [appId, appSourceType]) const handleSaveMessage = useCallback(async (messageId: string) => { if (!appId) return await saveMessage(messageId, appSourceType, appId) toast.success(t('api.saved', { ns: 'common' })) await fetchSavedMessages(appId) }, [appId, appSourceType, fetchSavedMessages, t]) const handleRemoveSavedMessage = useCallback(async (messageId: string) => { if (!appId) return await removeMessage(messageId, appSourceType, appId) toast.success(t('api.remove', { ns: 'common' })) await fetchSavedMessages(appId) }, [appId, appSourceType, fetchSavedMessages, t]) useEffect(() => { let cancelled = false const initialize = async () => { if (!appData || !appParams) return const { app_id: nextAppId, site, custom_config } = appData setAppId(nextAppId) setSiteInfo(site as SiteInfo) setCustomConfig((custom_config || null) as TextGenerationCustomConfig | null) await changeLanguage(site.default_language) const { user_input_form, more_like_this, file_upload, text_to_speech } = appParams as unknown as ShareAppParams const promptVariables = userInputsFormToPromptVariables(user_input_form) if (isWorkflow && !isInstalledApp) { const workflowUrlInputs = await getRawInputsFromUrlParams() promptVariables.forEach((promptVariable) => { const workflowDefault = coerceWorkflowUrlDefault(promptVariable, workflowUrlInputs[promptVariable.key]) if (workflowDefault !== undefined) promptVariable.default = workflowDefault }) } if (cancelled) return setVisionConfig({ ...file_upload, transfer_methods: file_upload?.allowed_file_upload_methods || file_upload?.allowed_upload_methods, image_file_size_limit: appParams?.system_parameters.image_file_size_limit, fileUploadConfig: appParams?.system_parameters, } as VisionSettings) setPromptConfig({ prompt_template: '', prompt_variables: promptVariables, } as PromptConfig) setMoreLikeThisConfig(more_like_this) setTextToSpeechConfig(text_to_speech) if (!isWorkflow) await fetchSavedMessages(nextAppId) } void initialize() return () => { cancelled = true } }, [appData, appParams, fetchSavedMessages, isInstalledApp, isWorkflow]) useDocumentTitle(siteInfo?.title || t('generation.title', { ns: 'share' })) useAppFavicon({ enable: !isInstalledApp, icon_type: siteInfo?.icon_type, icon: siteInfo?.icon, icon_background: siteInfo?.icon_background, icon_url: siteInfo?.icon_url, }) return { accessMode, appId, appSourceType, customConfig, fetchSavedMessages, handleRemoveSavedMessage, handleSaveMessage, moreLikeThisConfig, promptConfig, savedMessages, siteInfo, systemFeatures, textToSpeechConfig, visionConfig, setVisionConfig, } }