'use client' import React, { FC, useEffect, useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import cn from 'classnames' import { useBoolean, useClickAway } from 'ahooks' import { useContext } from 'use-context-selector' 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 { fetchAppInfo, fetchAppParams, sendCompletionMessage, updateFeedback, saveMessage, fetchSavedMessage as doFetchSavedMessage, removeMessage } from '@/service/share' import type { SiteInfo } from '@/models/share' import type { PromptConfig, MoreLikeThisConfig, SavedMessage } from '@/models/debug' import Toast from '@/app/components/base/toast' import { 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 TabHeader from '../../base/tab-header' import { XMarkIcon } from '@heroicons/react/24/outline' import s from './style.module.css' import Button from '../../base/button' import { App } from '@/types/app' import { InstalledApp } from '@/models/explore' 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 [moreLikeThisConifg, setMoreLikeThisConifg] = 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) { 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) setMoreLikeThisConifg(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