From 77950e48c76f4a3b29d01831d43039caba29888a Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期二, 18 十一月 2025 14:12:42 +0800
Subject: [PATCH] 修改
---
app/components/app/text-generate/item/index.tsx | 449 +++++++++++++++++++++++++++++++++----------------------
1 files changed, 267 insertions(+), 182 deletions(-)
diff --git a/app/components/app/text-generate/item/index.tsx b/app/components/app/text-generate/item/index.tsx
index aa3ffa3..3e2f837 100644
--- a/app/components/app/text-generate/item/index.tsx
+++ b/app/components/app/text-generate/item/index.tsx
@@ -1,36 +1,35 @@
'use client'
import type { FC } from 'react'
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
- RiBookmark3Line,
RiClipboardLine,
- RiFileList3Line,
- RiPlayList2Line,
- RiReplay15Line,
- RiSparklingFill,
- RiSparklingLine,
- RiThumbDownLine,
- RiThumbUpLine,
} 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 AnnotationCtrlBtn from '@/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-btn'
import { fetchTextGenerationMessage } from '@/service/debug'
+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'
-import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
-import NewAudioButton from '@/app/components/base/new-audio-button'
-import cn from '@/utils/classnames'
const MAX_DEPTH = 3
@@ -57,11 +56,30 @@
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
- inSidePanel?: boolean
}
+
+export const SimpleBtn = ({ className, isDisabled, onClick, children }: {
+ className?: string
+ isDisabled?: boolean
+ onClick?: () => void
+ children: React.ReactNode
+}) => (
+ <div
+ className={cn(isDisabled ? 'border-gray-100 text-gray-300' : 'border-gray-200 text-gray-700 cursor-pointer hover:border-gray-300 hover:shadow-sm', 'flex items-center h-7 px-3 rounded-md border text-xs font-medium', className)}
+ onClick={() => !isDisabled && onClick?.()}
+ >
+ {children}
+ </div>
+)
export const copyIcon = (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -91,16 +109,22 @@
taskId,
controlClearMoreLikeThis,
supportFeedback,
+ supportAnnotation,
isShowTextToSpeech,
+ appId,
+ varList,
+ innerClassName,
+ contentClassName,
hideProcessDetail,
siteInfo,
- inSidePanel,
}) => {
const { t } = useTranslation()
const params = useParams()
const isTop = depth === 1
+ const ref = useRef(null)
const [completionRes, setCompletionRes] = useState('')
const [childMessageId, setChildMessageId] = useState<string | null>(null)
+ const hasChild = !!childMessageId
const [childFeedback, setChildFeedback] = useState<FeedbackType>({
rating: null,
})
@@ -116,6 +140,8 @@
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 = {
@@ -135,7 +161,6 @@
controlClearMoreLikeThis,
isWorkflow,
siteInfo,
- taskId,
}
const handleMoreLikeThis = async () => {
@@ -152,6 +177,19 @@
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) {
@@ -190,125 +228,123 @@
setShowPromptLogModal(true)
}
+ const ratingContent = (
+ <>
+ {!isWorkflow && !isError && messageId && !feedback?.rating && (
+ <SimpleBtn className="!px-0">
+ <>
+ <div
+ onClick={() => {
+ onFeedback?.({
+ rating: 'like',
+ })
+ }}
+ className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
+ <HandThumbUpIcon width={16} height={16} />
+ </div>
+ <div
+ onClick={() => {
+ onFeedback?.({
+ rating: 'dislike',
+ })
+ }}
+ className='flex w-6 h-6 items-center justify-center rounded-md cursor-pointer hover:bg-gray-100'>
+ <HandThumbDownIcon width={16} height={16} />
+ </div>
+ </>
+ </SimpleBtn>
+ )}
+ {!isWorkflow && !isError && messageId && feedback?.rating === 'like' && (
+ <div
+ onClick={() => {
+ 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'>
+ <HandThumbUpIcon width={16} height={16} />
+ </div>
+ )}
+ {!isWorkflow && !isError && messageId && feedback?.rating === 'dislike' && (
+ <div
+ onClick={() => {
+ 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'>
+ <HandThumbDownIcon width={16} height={16} />
+ </div>
+ )}
+ </>
+ )
+
const [currentTab, setCurrentTab] = useState<string>('DETAIL')
- const showResultTabs = !!workflowProcessData?.resultText || !!workflowProcessData?.files?.length
- const switchTab = async (tab: string) => {
- setCurrentTab(tab)
- }
- useEffect(() => {
- if (workflowProcessData?.resultText || !!workflowProcessData?.files?.length)
- switchTab('RESULT')
- else
- switchTab('DETAIL')
- }, [workflowProcessData?.files?.length, workflowProcessData?.resultText])
return (
- <>
- <div className={cn('relative', !isTop && 'mt-3', className)}>
- {isLoading && (
- <div className={cn('flex h-10 items-center', !inSidePanel && 'rounded-2xl border-t border-divider-subtle bg-chat-bubble-bg')}><Loading type='area' /></div>
- )}
- {!isLoading && (
- <>
- {/* result content */}
- <div className={cn(
- 'relative',
- !inSidePanel && 'rounded-2xl border-t border-divider-subtle bg-chat-bubble-bg',
- )}>
- {workflowProcessData && (
- <>
- <div className={cn(
- 'p-3',
- showResultTabs && 'border-b border-divider-subtle',
- )}>
- {taskId && (
- <div className={cn('system-2xs-medium-uppercase mb-2 flex items-center text-text-accent-secondary', isError && 'text-text-destructive')}>
- <RiPlayList2Line className='mr-1 h-3 w-3' />
- <span>{t('share.generation.execution')}</span>
- <span className='px-1'>路</span>
- <span>{taskId}</span>
- </div>
- )}
- {siteInfo && workflowProcessData && (
- <WorkflowProcessItem
- data={workflowProcessData}
- expand={workflowProcessData.expand}
- hideProcessDetail={hideProcessDetail}
- hideInfo={hideProcessDetail}
- readonly={!siteInfo.show_workflow_steps}
- />
- )}
- {showResultTabs && (
- <div className='flex items-center space-x-6 px-1'>
- <div
- className={cn(
- 'system-sm-semibold-uppercase cursor-pointer border-b-2 border-transparent py-3 text-text-tertiary',
- currentTab === 'RESULT' && 'border-util-colors-blue-brand-blue-brand-600 text-text-primary',
- )}
- onClick={() => switchTab('RESULT')}
- >{t('runLog.result')}</div>
- <div
- className={cn(
- 'system-sm-semibold-uppercase cursor-pointer border-b-2 border-transparent py-3 text-text-tertiary',
- currentTab === 'DETAIL' && 'border-util-colors-blue-brand-blue-brand-600 text-text-primary',
- )}
- onClick={() => switchTab('DETAIL')}
- >{t('runLog.detail')}</div>
- </div>
- )}
- </div>
- {!isError && (
- <ResultTab data={workflowProcessData} content={content} currentTab={currentTab} />
- )}
- </>
- )}
- {!workflowProcessData && taskId && (
- <div className={cn('system-2xs-medium-uppercase sticky left-0 top-0 flex w-full items-center rounded-t-2xl bg-components-actionbar-bg p-4 pb-3 text-text-accent-secondary', isError && 'text-text-destructive')}>
- <RiPlayList2Line className='mr-1 h-3 w-3' />
- <span>{t('share.generation.execution')}</span>
- <span className='px-1'>路</span>
- <span>{`${taskId}${depth > 1 ? `-${depth - 1}` : ''}`}</span>
- </div>
- )}
- {isError && (
- <div className='body-lg-regular p-4 pt-0 text-text-quaternary'>{t('share.generation.batchFailed.outputPlaceholder')}</div>
- )}
- {!workflowProcessData && !isError && (typeof content === 'string') && (
- <div className={cn('p-4', taskId && 'pt-0')}>
- <Markdown content={content} />
- </div>
- )}
- </div>
- {/* meta data */}
- <div className={cn(
- 'system-xs-regular relative mt-1 h-4 px-4 text-text-quaternary',
- isMobile && ((childMessageId || isQuerying) && depth < 3) && 'pl-10',
- )}>
- {!isWorkflow && <span>{content?.length} {t('common.unit.char')}</span>}
- {/* action buttons */}
- <div className='absolute bottom-1 right-2 flex items-center'>
- {!isInWebApp && !isInstalledApp && !isResponding && (
- <div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'>
- <ActionButton disabled={isError || !messageId} onClick={handleOpenLogModal}>
- <RiFileList3Line className='h-4 w-4' />
- {/* <div>{t('common.operation.log')}</div> */}
- </ActionButton>
- </div>
+ <div ref={ref} className={cn(isTop ? `rounded-xl border ${!isError ? 'border-gray-200 bg-chat-bubble-bg' : 'border-[#FECDCA] bg-[#FEF3F2]'} ` : 'rounded-br-xl !mt-0', className)}
+ style={isTop
+ ? {
+ boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)',
+ }
+ : {}}
+ >
+ {isLoading
+ ? (
+ <div className='flex items-center h-10'><Loading type='area' /></div>
+ )
+ : (
+ <div
+ className={cn(!isTop && 'rounded-br-xl border-l-2 border-primary-400', 'p-4', innerClassName)}
+ style={mainStyle}
+ >
+ {(isTop && taskId) && (
+ <div className='mb-2 text-gray-500 border border-gray-200 box-border flex items-center rounded-md italic text-[11px] pl-1 pr-1.5 font-medium w-fit group-hover:opacity-100'>
+ <HashtagIcon className='w-3 h-3 text-gray-400 fill-current mr-1 stroke-current stroke-1' />
+ {taskId}
+ </div>)
+ }
+ <div className={`flex ${contentClassName}`}>
+ <div className='grow w-0'>
+ {siteInfo && workflowProcessData && (
+ <WorkflowProcessItem
+ data={workflowProcessData}
+ expand={workflowProcessData.expand}
+ hideProcessDetail={hideProcessDetail}
+ hideInfo={hideProcessDetail}
+ readonly={!siteInfo.show_workflow_steps}
+ />
)}
- <div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'>
- {moreLikeThis && (
- <ActionButton state={depth === MAX_DEPTH ? ActionButtonState.Disabled : ActionButtonState.Default} disabled={depth === MAX_DEPTH} onClick={handleMoreLikeThis}>
- <RiSparklingLine className='h-4 w-4' />
- </ActionButton>
- )}
- {isShowTextToSpeech && (
- <NewAudioButton
- id={messageId!}
- voice={config?.text_to_speech?.voice}
- />
- )}
- {((currentTab === 'RESULT' && workflowProcessData?.resultText) || !isWorkflow) && (
- <ActionButton disabled={isError || !messageId} onClick={() => {
+ {workflowProcessData && !isError && (
+ <ResultTab data={workflowProcessData} content={content} currentTab={currentTab} onCurrentTabChange={setCurrentTab} />
+ )}
+ {isError && (
+ <div className='text-gray-400 text-sm'>{t('share.generation.batchFailed.outputPlaceholder')}</div>
+ )}
+ {!workflowProcessData && !isError && (typeof content === 'string') && (
+ <Markdown content={content} />
+ )}
+ </div>
+ </div>
+
+ <div className='flex items-center justify-between mt-3'>
+ <div className='flex items-center'>
+ {
+ !isInWebApp && !isInstalledApp && !isResponding && (
+ <SimpleBtn
+ isDisabled={isError || !messageId}
+ className={cn(isMobile && '!px-1.5', 'space-x-1 mr-1')}
+ onClick={handleOpenLogModal}>
+ <File02 className='w-3.5 h-3.5' />
+ {!isMobile && <div>{t('common.operation.log')}</div>}
+ </SimpleBtn>
+ )
+ }
+ {((currentTab === 'RESULT' && workflowProcessData?.resultText) || !isWorkflow) && (
+ <SimpleBtn
+ isDisabled={isError || !messageId}
+ className={cn(isMobile && '!px-1.5', 'space-x-1')}
+ onClick={() => {
const copyContent = isWorkflow ? workflowProcessData?.resultText : content
if (typeof copyContent === 'string')
copy(copyContent)
@@ -316,68 +352,117 @@
copy(JSON.stringify(copyContent))
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
}}>
- <RiClipboardLine className='h-4 w-4' />
- </ActionButton>
- )}
- {isInWebApp && isError && (
- <ActionButton onClick={onRetry}>
- <RiReplay15Line className='h-4 w-4' />
- </ActionButton>
- )}
- {isInWebApp && !isWorkflow && (
- <ActionButton disabled={isError || !messageId} onClick={() => { onSave?.(messageId as string) }}>
- <RiBookmark3Line className='h-4 w-4' />
- </ActionButton>
- )}
- </div>
- {(supportFeedback || isInWebApp) && !isWorkflow && !isError && messageId && (
- <div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'>
- {!feedback?.rating && (
- <>
- <ActionButton onClick={() => onFeedback?.({ rating: 'like' })}>
- <RiThumbUpLine className='h-4 w-4' />
- </ActionButton>
- <ActionButton onClick={() => onFeedback?.({ rating: 'dislike' })}>
- <RiThumbDownLine className='h-4 w-4' />
- </ActionButton>
- </>
+ <RiClipboardLine className='w-3.5 h-3.5' />
+ {!isMobile && <div>{t('common.operation.copy')}</div>}
+ </SimpleBtn>
+ )}
+
+ {isInWebApp && (
+ <>
+ {!isWorkflow && (
+ <SimpleBtn
+ isDisabled={isError || !messageId}
+ className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')}
+ onClick={() => { onSave?.(messageId as string) }}
+ >
+ <Bookmark className='w-3.5 h-3.5' />
+ {!isMobile && <div>{t('common.operation.save')}</div>}
+ </SimpleBtn>
)}
- {feedback?.rating === 'like' && (
- <ActionButton state={ActionButtonState.Active} onClick={() => onFeedback?.({ rating: null })}>
- <RiThumbUpLine className='h-4 w-4' />
- </ActionButton>
+ {(moreLikeThis && depth < MAX_DEPTH) && (
+ <SimpleBtn
+ isDisabled={isError || !messageId}
+ className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')}
+ onClick={handleMoreLikeThis}
+ >
+ <Stars02 className='w-3.5 h-3.5' />
+ {!isMobile && <div>{t('appDebug.feature.moreLikeThis.title')}</div>}
+ </SimpleBtn>
)}
- {feedback?.rating === 'dislike' && (
- <ActionButton state={ActionButtonState.Destructive} onClick={() => onFeedback?.({ rating: null })}>
- <RiThumbDownLine className='h-4 w-4' />
- </ActionButton>
+ {isError && (
+ <SimpleBtn
+ onClick={onRetry}
+ className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')}
+ >
+ <RefreshCcw01 className='w-3.5 h-3.5' />
+ {!isMobile && <div>{t('share.generation.batchFailed.retry')}</div>}
+ </SimpleBtn>
)}
+ {!isError && messageId && !isWorkflow && (
+ <div className="mx-3 w-[1px] h-[14px] bg-gray-200"></div>
+ )}
+ {ratingContent}
+ </>
+ )}
+
+ {supportAnnotation && (
+ <>
+ <div className='ml-2 mr-1 h-[14px] w-[1px] bg-gray-200'></div>
+ <AnnotationCtrlBtn
+ appId={appId!}
+ messageId={messageId!}
+ className='ml-1'
+ query={question}
+ answer={content}
+ // not support cache. So can not be cached
+ cached={false}
+ onAdded={() => {
+
+ }}
+ onEdit={() => setIsShowReplyModal(true)}
+ onRemoved={() => { }}
+ />
+ </>
+ )}
+
+ <EditReplyModal
+ appId={appId!}
+ messageId={messageId!}
+ isShow={isShowReplyModal}
+ onHide={() => setIsShowReplyModal(false)}
+ query={question}
+ answer={content}
+ onAdded={() => { }}
+ onEdited={() => { }}
+ createdAt={0}
+ onRemove={() => { }}
+ onlyEditResponse
+ />
+
+ {supportFeedback && (
+ <div className='ml-1'>
+ {ratingContent}
</div>
+ )}
+
+ {isShowTextToSpeech && (
+ <>
+ <div className='ml-2 mr-2 h-[14px] w-[1px] bg-gray-200'></div>
+ <AudioBtn
+ id={messageId!}
+ className={'mr-1'}
+ voice={config?.text_to_speech?.voice}
+ />
+ </>
+ )}
+ </div>
+ <div>
+ {!workflowProcessData && (
+ <div className='text-xs text-gray-500'>{content?.length} {t('common.unit.char')}</div>
)}
</div>
</div>
- {/* more like this elements */}
- {!isTop && (
- <div className={cn(
- 'absolute top-[-32px] flex h-[33px] w-4 justify-center',
- isMobile ? 'left-[17px]' : 'left-[50%] translate-x-[-50%]',
- )}>
- <div className='h-full w-0.5 bg-divider-regular'></div>
- <div className={cn(
- 'absolute left-0 flex h-4 w-4 items-center justify-center rounded-2xl border-[0.5px] border-divider-subtle bg-util-colors-blue-blue-500 shadow-xs',
- isMobile ? 'top-[3.5px]' : 'top-2',
- )}>
- <RiSparklingFill className='h-3 w-3 text-text-primary-on-surface' />
- </div>
- </div>
- )}
- </>
+
+ </div>
)}
- </div>
+
{((childMessageId || isQuerying) && depth < 3) && (
- <GenerationItem {...childProps as any} />
+ <div className='pl-4'>
+ <GenerationItem {...childProps as any} />
+ </div>
)}
- </>
+
+ </div>
)
}
export default React.memo(GenerationItem)
--
Gitblit v1.8.0