'use client' import type { FC } from 'react' import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { RiClipboardLine, } from '@remixicon/react' import copy from 'copy-to-clipboard' import { useParams } from 'next/navigation' import { HandThumbDownIcon, HandThumbUpIcon } from '@heroicons/react/24/outline' import { useBoolean } from 'ahooks' import { HashtagIcon } from '@heroicons/react/24/solid' import ResultTab from './result-tab' import cn from '@/utils/classnames' import { Markdown } from '@/app/components/base/markdown' import Loading from '@/app/components/base/loading' import Toast from '@/app/components/base/toast' import AudioBtn from '@/app/components/base/audio-btn' import type { Feedbacktype } from '@/app/components/base/chat/chat/type' import { fetchMoreLikeThis, updateFeedback } from '@/service/share' import { File02 } from '@/app/components/base/icons/src/vender/line/files' import { Bookmark } from '@/app/components/base/icons/src/vender/line/general' import { Stars02 } from '@/app/components/base/icons/src/vender/line/weather' import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows' import { fetchTextGenerationMessge } from '@/service/debug' import AnnotationCtrlBtn from '@/app/components/app/configuration/toolbox/annotation/annotation-ctrl-btn' import EditReplyModal from '@/app/components/app/annotation/edit-annotation-modal' import { useStore as useAppStore } from '@/app/components/app/store' import WorkflowProcessItem from '@/app/components/base/chat/chat/answer/workflow-process' import type { WorkflowProcess } from '@/app/components/base/chat/types' import type { SiteInfo } from '@/models/share' import { useChatContext } from '@/app/components/base/chat/chat/context' const MAX_DEPTH = 3 export type IGenerationItemProps = { isWorkflow?: boolean workflowProcessData?: WorkflowProcess className?: string isError: boolean onRetry: () => void content: any messageId?: string | null conversationId?: string isLoading?: boolean isResponding?: boolean isInWebApp?: boolean moreLikeThis?: boolean depth?: number feedback?: Feedbacktype onFeedback?: (feedback: Feedbacktype) => void onSave?: (messageId: string) => void isMobile?: boolean isInstalledApp: boolean installedAppId?: string taskId?: string controlClearMoreLikeThis?: number supportFeedback?: boolean supportAnnotation?: boolean isShowTextToSpeech?: boolean appId?: string varList?: { label: string; value: string | number | object }[] innerClassName?: string contentClassName?: string footerClassName?: string hideProcessDetail?: boolean siteInfo: SiteInfo | null } export const SimpleBtn = ({ className, isDisabled, onClick, children }: { className?: string isDisabled?: boolean onClick?: () => void children: React.ReactNode }) => (
!isDisabled && onClick?.()} > {children}
) export const copyIcon = ( ) const GenerationItem: FC = ({ isWorkflow, workflowProcessData, className, isError, onRetry, content, messageId, isLoading, isResponding, moreLikeThis, isInWebApp = false, feedback, onFeedback, onSave, depth = 1, isMobile, isInstalledApp, installedAppId, taskId, controlClearMoreLikeThis, supportFeedback, supportAnnotation, isShowTextToSpeech, appId, varList, innerClassName, contentClassName, hideProcessDetail, siteInfo, }) => { const { t } = useTranslation() const params = useParams() const isTop = depth === 1 const ref = useRef(null) const [completionRes, setCompletionRes] = useState('') const [childMessageId, setChildMessageId] = useState(null) const hasChild = !!childMessageId const [childFeedback, setChildFeedback] = useState({ rating: null, }) const { config, } = useChatContext() const setCurrentLogItem = useAppStore(s => s.setCurrentLogItem) const setShowPromptLogModal = useAppStore(s => s.setShowPromptLogModal) const handleFeedback = async (childFeedback: Feedbacktype) => { await updateFeedback({ url: `/messages/${childMessageId}/feedbacks`, body: { rating: childFeedback.rating } }, isInstalledApp, installedAppId) setChildFeedback(childFeedback) } const [isShowReplyModal, setIsShowReplyModal] = useState(false) const question = (varList && varList?.length > 0) ? varList?.map(({ label, value }) => `${label}:${value}`).join('&') : '' const [isQuerying, { setTrue: startQuerying, setFalse: stopQuerying }] = useBoolean(false) const childProps = { isInWebApp: true, content: completionRes, messageId: childMessageId, depth: depth + 1, moreLikeThis: true, onFeedback: handleFeedback, isLoading: isQuerying, feedback: childFeedback, onSave, isShowTextToSpeech, isMobile, isInstalledApp, installedAppId, controlClearMoreLikeThis, isWorkflow, siteInfo, } const handleMoreLikeThis = async () => { if (isQuerying || !messageId) { Toast.notify({ type: 'warning', message: t('appDebug.errorMessage.waitForResponse') }) return } startQuerying() const res: any = await fetchMoreLikeThis(messageId as string, isInstalledApp, installedAppId) setCompletionRes(res.answer) setChildFeedback({ rating: null, }) setChildMessageId(res.id) stopQuerying() } const mainStyle = (() => { const res: React.CSSProperties = !isTop ? { background: depth % 2 === 0 ? 'linear-gradient(90.07deg, #F9FAFB 0.05%, rgba(249, 250, 251, 0) 99.93%)' : '#fff', } : {} if (hasChild) res.boxShadow = '0px 1px 2px rgba(16, 24, 40, 0.05)' return res })() useEffect(() => { if (controlClearMoreLikeThis) { setChildMessageId(null) setCompletionRes('') } }, [controlClearMoreLikeThis]) // regeneration clear child useEffect(() => { if (isLoading) setChildMessageId(null) }, [isLoading]) const handleOpenLogModal = async () => { const data = await fetchTextGenerationMessge({ appId: params.appId as string, messageId: messageId!, }) const logItem = { ...data, log: [ ...data.message, ...(data.message[data.message.length - 1].role !== 'assistant' ? [ { role: 'assistant', text: data.answer, files: data.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [], }, ] : []), ], } setCurrentLogItem(logItem) setShowPromptLogModal(true) } const ratingContent = ( <> {!isWorkflow && !isError && messageId && !feedback?.rating && ( <>
{ onFeedback?.({ rating: 'like', }) }} className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
{ onFeedback?.({ rating: 'dislike', }) }} className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
)} {!isWorkflow && !isError && messageId && feedback?.rating === 'like' && (
{ onFeedback?.({ rating: null, }) }} className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-primary-600 border border-primary-200 bg-primary-100 hover:border-primary-300 hover:bg-primary-200'>
)} {!isWorkflow && !isError && messageId && feedback?.rating === 'dislike' && (
{ onFeedback?.({ rating: null, }) }} className='flex w-7 h-7 items-center justify-center rounded-md cursor-pointer !text-red-600 border border-red-200 bg-red-100 hover:border-red-300 hover:bg-red-200'>
)} ) const [currentTab, setCurrentTab] = useState('DETAIL') return (
{isLoading ? (
) : (
{(isTop && taskId) && (
{taskId}
) }
{siteInfo && siteInfo.show_workflow_steps && workflowProcessData && ( )} {workflowProcessData && !isError && ( )} {isError && (
{t('share.generation.batchFailed.outputPlaceholder')}
)} {!workflowProcessData && !isError && (typeof content === 'string') && ( )}
{ !isInWebApp && !isInstalledApp && !isResponding && ( {!isMobile &&
{t('common.operation.log')}
}
) } {(currentTab === 'RESULT' || !isWorkflow) && ( { const copyContent = isWorkflow ? workflowProcessData?.resultText : content if (typeof copyContent === 'string') copy(copyContent) else copy(JSON.stringify(copyContent)) Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) }}> {!isMobile &&
{t('common.operation.copy')}
}
)} {isInWebApp && ( <> {!isWorkflow && ( { onSave?.(messageId as string) }} > {!isMobile &&
{t('common.operation.save')}
}
)} {(moreLikeThis && depth < MAX_DEPTH) && ( {!isMobile &&
{t('appDebug.feature.moreLikeThis.title')}
}
)} {isError && ( {!isMobile &&
{t('share.generation.batchFailed.retry')}
}
)} {!isError && messageId && !isWorkflow && (
)} {ratingContent} )} {supportAnnotation && ( <>
{ }} onEdit={() => setIsShowReplyModal(true)} onRemoved={() => { }} /> )} setIsShowReplyModal(false)} query={question} answer={content} onAdded={() => { }} onEdited={() => { }} createdAt={0} onRemove={() => { }} onlyEditResponse /> {supportFeedback && (
{ratingContent}
)} {isShowTextToSpeech && ( <>
)}
{!workflowProcessData && (
{content?.length} {t('common.unit.char')}
)}
)} {((childMessageId || isQuerying) && depth < 3) && (
)}
) } export default React.memo(GenerationItem)