'use client' import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import cn from 'classnames' import { useBoolean, useClickAway } from 'ahooks' import { XMarkIcon } from '@heroicons/react/24/outline' import TabHeader from '../../base/tab-header' import Button from '../../base/button' import s from './style.module.css' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import ConfigScence from '@/app/components/share/text-generation/config-scence' import NoData from '@/app/components/share/text-generation/no-data' // import History from '@/app/components/share/text-generation/history' import { fetchSavedMessage as doFetchSavedMessage, fetchAppInfo, fetchAppParams, removeMessage, saveMessage, sendCompletionMessage, updateFeedback } from '@/service/share' import type { SiteInfo } from '@/models/share' import type { MoreLikeThisConfig, PromptConfig, SavedMessage } from '@/models/debug' import Toast from '@/app/components/base/toast' import AppIcon from '@/app/components/base/app-icon' import type { Feedbacktype } from '@/app/components/app/chat' import { changeLanguage } from '@/i18n/i18next-config' import Loading from '@/app/components/base/loading' import { userInputsFormToPromptVariables } from '@/utils/model-config' import TextGenerationRes from '@/app/components/app/text-generate/item' import SavedItems from '@/app/components/app/text-generate/saved-items' import type { InstalledApp } from '@/models/explore' import { appDefaultIconBackground } from '@/config' export type IMainProps = { isInstalledApp?: boolean installedAppInfo?: InstalledApp } const TextGeneration: FC = ({ isInstalledApp = false, installedAppInfo, }) => { const { t } = useTranslation() const media = useBreakpoints() const isPC = media === MediaType.pc const isTablet = media === MediaType.tablet const isMoble = media === MediaType.mobile const [currTab, setCurrTab] = useState('create') const [inputs, setInputs] = useState>({}) const [appId, setAppId] = useState('') const [siteInfo, setSiteInfo] = useState(null) const [promptConfig, setPromptConfig] = useState(null) const [moreLikeThisConfig, setMoreLikeThisConfig] = useState(null) const [isResponsing, { setTrue: setResponsingTrue, setFalse: setResponsingFalse }] = useBoolean(false) const [query, setQuery] = useState('') const [completionRes, setCompletionRes] = useState('') const { notify } = Toast const isNoData = !completionRes const [messageId, setMessageId] = useState(null) const [feedback, setFeedback] = useState({ rating: null, }) const handleFeedback = async (feedback: Feedbacktype) => { await updateFeedback({ url: `/messages/${messageId}/feedbacks`, body: { rating: feedback.rating } }, isInstalledApp, installedAppInfo?.id) setFeedback(feedback) } const [savedMessages, setSavedMessages] = useState([]) const fetchSavedMessage = async () => { const res: any = await doFetchSavedMessage(isInstalledApp, installedAppInfo?.id) setSavedMessages(res.data) } useEffect(() => { fetchSavedMessage() }, []) const handleSaveMessage = async (messageId: string) => { await saveMessage(messageId, isInstalledApp, installedAppInfo?.id) notify({ type: 'success', message: t('common.api.saved') }) fetchSavedMessage() } const handleRemoveSavedMessage = async (messageId: string) => { await removeMessage(messageId, isInstalledApp, installedAppInfo?.id) notify({ type: 'success', message: t('common.api.remove') }) fetchSavedMessage() } const logError = (message: string) => { notify({ type: 'error', message }) } const checkCanSend = () => { const prompt_variables = promptConfig?.prompt_variables if (!prompt_variables || prompt_variables?.length === 0) return true let hasEmptyInput = false const requiredVars = prompt_variables?.filter(({ key, name, required }) => { const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null) return res }) || [] // compatible with old version requiredVars.forEach(({ key }) => { if (hasEmptyInput) return if (!inputs[key]) hasEmptyInput = true }) if (hasEmptyInput) { logError(t('appDebug.errorMessage.valueOfVarRequired')) return false } return !hasEmptyInput } const handleSend = async () => { if (isResponsing) { notify({ type: 'info', message: t('appDebug.errorMessage.waitForResponse') }) return false } if (!checkCanSend()) return if (!query) { logError(t('appDebug.errorMessage.queryRequired')) return false } const data = { inputs, query, } setMessageId(null) setFeedback({ rating: null, }) setCompletionRes('') const res: string[] = [] let tempMessageId = '' if (!isPC) // eslint-disable-next-line @typescript-eslint/no-use-before-define showResSidebar() setResponsingTrue() sendCompletionMessage(data, { onData: (data: string, _isFirstMessage: boolean, { messageId }: any) => { tempMessageId = messageId res.push(data) setCompletionRes(res.join('')) }, onCompleted: () => { setResponsingFalse() setMessageId(tempMessageId) }, onError() { setResponsingFalse() }, }, isInstalledApp, installedAppInfo?.id) } const fetchInitData = () => { return Promise.all([isInstalledApp ? { app_id: installedAppInfo?.id, site: { title: installedAppInfo?.app.name, prompt_public: false, copyright: '', }, plan: 'basic', } : fetchAppInfo(), fetchAppParams(isInstalledApp, installedAppInfo?.id)]) } useEffect(() => { (async () => { const [appData, appParams]: any = await fetchInitData() const { app_id: appId, site: siteInfo } = appData setAppId(appId) setSiteInfo(siteInfo as SiteInfo) changeLanguage(siteInfo.default_language) const { user_input_form, more_like_this }: any = appParams const prompt_variables = userInputsFormToPromptVariables(user_input_form) setPromptConfig({ prompt_template: '', // placeholder for feture prompt_variables, } as PromptConfig) setMoreLikeThisConfig(more_like_this) })() }, []) // Can Use metadata(https://beta.nextjs.org/docs/api-reference/metadata) to set title. But it only works in server side client. useEffect(() => { if (siteInfo?.title) document.title = `${siteInfo.title} - Powered by Dify` }, [siteInfo?.title]) const [isShowResSidebar, { setTrue: showResSidebar, setFalse: hideResSidebar }] = useBoolean(false) const resRef = useRef(null) useClickAway(() => { hideResSidebar() }, resRef) const renderRes = (
<>
{t('share.generation.title')}
{!isPC && (
)}
{(isResponsing && !completionRes) ? (
) : ( <> {isNoData ? : ( ) } )}
) if (!appId || !siteInfo || !promptConfig) return return ( <>
{/* Left */}
{siteInfo.title}
{!isPC && ( )}
{siteInfo.description && (
{siteInfo.description}
)}
0 ? (
{savedMessages.length}
) : null, }, ]} value={currTab} onChange={setCurrTab} />
{currTab === 'create' && ( )} {currTab === 'saved' && ( setCurrTab('create')} /> )}
{/* copyright */}
© {siteInfo.copyright || siteInfo.title} {(new Date()).getFullYear()}
{siteInfo.privacy_policy && ( <>
·
{t('share.chat.privacyPolicyLeft')} {t('share.chat.privacyPolicyMiddle')} {t('share.chat.privacyPolicyRight')}
)}
{/* Result */} {isPC && (
{renderRes}
)} {(!isPC && isShowResSidebar) && (
{renderRes}
)}
) } export default TextGeneration