mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 11:42:29 +08:00
feat: batch run support retry errors and decrease rate limit times (#1215)
This commit is contained in:
parent
841e967d48
commit
c40ee7e629
|
@ -12,10 +12,15 @@ import Loading from '@/app/components/base/loading'
|
|||
import Toast from '@/app/components/base/toast'
|
||||
import type { Feedbacktype } from '@/app/components/app/chat/type'
|
||||
import { fetchMoreLikeThis, updateFeedback } from '@/service/share'
|
||||
|
||||
import { Clipboard } 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'
|
||||
const MAX_DEPTH = 3
|
||||
export type IGenerationItemProps = {
|
||||
className?: string
|
||||
isError: boolean
|
||||
onRetry: () => void
|
||||
content: string
|
||||
messageId?: string | null
|
||||
isLoading?: boolean
|
||||
|
@ -32,14 +37,15 @@ export type IGenerationItemProps = {
|
|||
controlClearMoreLikeThis?: number
|
||||
}
|
||||
|
||||
export const SimpleBtn = ({ className, onClick, children }: {
|
||||
export const SimpleBtn = ({ className, isDisabled, onClick, children }: {
|
||||
className?: string
|
||||
isDisabled?: boolean
|
||||
onClick?: () => void
|
||||
children: React.ReactNode
|
||||
}) => (
|
||||
<div
|
||||
className={cn(className, 'flex items-center h-7 px-3 rounded-md border border-gray-200 text-xs text-gray-700 font-medium cursor-pointer hover:shadow-sm hover:border-gray-300')}
|
||||
onClick={() => onClick?.()}
|
||||
className={cn(className, 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')}
|
||||
onClick={() => !isDisabled && onClick?.()}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -51,27 +57,10 @@ export const copyIcon = (
|
|||
</svg>
|
||||
)
|
||||
|
||||
const moreLikeThisIcon = (
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_4138_1944)">
|
||||
<path d="M2.62533 12.8337V9.91699M2.62533 4.08366V1.16699M1.16699 2.62533H4.08366M1.16699 11.3753H4.08366M7.58366 1.75033L6.57206 4.38049C6.40755 4.80821 6.32529 5.02207 6.19738 5.20196C6.08402 5.36139 5.94472 5.50069 5.78529 5.61405C5.6054 5.74196 5.39155 5.82421 4.96383 5.98872L2.33366 7.00033L4.96383 8.01193C5.39155 8.17644 5.60541 8.25869 5.78529 8.3866C5.94472 8.49996 6.08402 8.63926 6.19738 8.79869C6.32529 8.97858 6.40755 9.19244 6.57206 9.62016L7.58366 12.2503L8.59526 9.62015C8.75977 9.19244 8.84202 8.97858 8.96993 8.79869C9.0833 8.63926 9.22259 8.49996 9.38203 8.3866C9.56191 8.25869 9.77577 8.17644 10.2035 8.01193L12.8337 7.00033L10.2035 5.98872C9.77577 5.82421 9.56191 5.74196 9.38203 5.61405C9.22259 5.50069 9.0833 5.36139 8.96993 5.20196C8.84202 5.02207 8.75977 4.80821 8.59526 4.38049L7.58366 1.75033Z" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4138_1944">
|
||||
<rect width="14" height="14" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const saveIcon = (
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.0837 12.25L7.00033 9.33333L2.91699 12.25V2.91667C2.91699 2.60725 3.03991 2.3105 3.2587 2.09171C3.47749 1.87292 3.77424 1.75 4.08366 1.75H9.91699C10.2264 1.75 10.5232 1.87292 10.7419 2.09171C10.9607 2.3105 11.0837 2.60725 11.0837 2.91667V12.25Z" stroke="#344054" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const GenerationItem: FC<IGenerationItemProps> = ({
|
||||
className,
|
||||
isError,
|
||||
onRetry,
|
||||
content,
|
||||
messageId,
|
||||
isLoading,
|
||||
|
@ -162,7 +151,7 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||
}, [isLoading])
|
||||
|
||||
return (
|
||||
<div className={cn(className, isTop ? 'rounded-xl border border-gray-200 bg-white' : 'rounded-br-xl !mt-0')}
|
||||
<div className={cn(className, isTop ? `rounded-xl border ${!isError ? 'border-gray-200 bg-white' : 'border-[#FECDCA] bg-[#FEF3F2]'} ` : 'rounded-br-xl !mt-0')}
|
||||
style={isTop
|
||||
? {
|
||||
boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)',
|
||||
|
@ -186,98 +175,112 @@ const GenerationItem: FC<IGenerationItemProps> = ({
|
|||
}
|
||||
<div className='flex'>
|
||||
<div className='grow w-0'>
|
||||
<Markdown content={content} />
|
||||
{isError
|
||||
? <div className='text-gray-400 text-sm'>{t('share.generation.batchFailed.outputPlaceholder')}</div>
|
||||
: (
|
||||
<Markdown content={ content } />
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{messageId && (
|
||||
<div className='flex items-center justify-between mt-3'>
|
||||
<div className='flex items-center'>
|
||||
<SimpleBtn
|
||||
className={cn(isMobile && '!px-1.5', 'space-x-1')}
|
||||
onClick={() => {
|
||||
copy(content)
|
||||
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
|
||||
}}>
|
||||
{copyIcon}
|
||||
{!isMobile && <div>{t('common.operation.copy')}</div>}
|
||||
</SimpleBtn>
|
||||
{isInWebApp && (
|
||||
<>
|
||||
|
||||
<div className='flex items-center justify-between mt-3'>
|
||||
<div className='flex items-center'>
|
||||
<SimpleBtn
|
||||
isDisabled={isError || !messageId}
|
||||
className={cn(isMobile && '!px-1.5', 'space-x-1')}
|
||||
onClick={() => {
|
||||
copy(content)
|
||||
Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') })
|
||||
}}>
|
||||
<Clipboard className='w-3.5 h-3.5' />
|
||||
{!isMobile && <div>{t('common.operation.copy')}</div>}
|
||||
</SimpleBtn>
|
||||
{isInWebApp && (
|
||||
<>
|
||||
<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>
|
||||
{(moreLikeThis && depth < MAX_DEPTH) && (
|
||||
<SimpleBtn
|
||||
isDisabled={isError || !messageId}
|
||||
className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')}
|
||||
onClick={() => { onSave?.(messageId as string) }}
|
||||
onClick={handleMoreLikeThis}
|
||||
>
|
||||
{saveIcon}
|
||||
{!isMobile && <div>{t('common.operation.save')}</div>}
|
||||
<Stars02 className='w-3.5 h-3.5' />
|
||||
{!isMobile && <div>{t('appDebug.feature.moreLikeThis.title')}</div>}
|
||||
</SimpleBtn>)}
|
||||
{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 && <div className="mx-3 w-[1px] h-[14px] bg-gray-200"></div>}
|
||||
{!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>
|
||||
{(moreLikeThis && depth < MAX_DEPTH) && (
|
||||
<SimpleBtn
|
||||
className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')}
|
||||
onClick={handleMoreLikeThis}
|
||||
>
|
||||
{moreLikeThisIcon}
|
||||
{!isMobile && <div>{t('appDebug.feature.moreLikeThis.title')}</div>}
|
||||
</SimpleBtn>)}
|
||||
<div className="mx-3 w-[1px] h-[14px] bg-gray-200"></div>
|
||||
{!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>
|
||||
)}
|
||||
{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>
|
||||
)}
|
||||
{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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className='text-xs text-gray-500'>{content?.length} {t('common.unit.char')}</div>
|
||||
)}
|
||||
{!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>
|
||||
)}
|
||||
{!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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className='text-xs text-gray-500'>{content?.length} {t('common.unit.char')}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
{((childMessageId || isQuerying) && depth < 3) && (
|
||||
<div className='pl-4'>
|
||||
<GenerationItem {...childProps} />
|
||||
<GenerationItem {...childProps as any} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 10C2 10 4.00498 7.26822 5.63384 5.63824C7.26269 4.00827 9.5136 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.89691 21 4.43511 18.2543 3.35177 14.5M2 10V4M2 10H8" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 376 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 4C16.93 4 17.395 4 17.7765 4.10222C18.8117 4.37962 19.6204 5.18827 19.8978 6.22354C20 6.60504 20 7.07003 20 8V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V8C4 7.07003 4 6.60504 4.10222 6.22354C4.37962 5.18827 5.18827 4.37962 6.22354 4.10222C6.60504 4 7.07003 4 8 4M9.6 6H14.4C14.9601 6 15.2401 6 15.454 5.89101C15.6422 5.79513 15.7951 5.64215 15.891 5.45399C16 5.24008 16 4.96005 16 4.4V3.6C16 3.03995 16 2.75992 15.891 2.54601C15.7951 2.35785 15.6422 2.20487 15.454 2.10899C15.2401 2 14.9601 2 14.4 2H9.6C9.03995 2 8.75992 2 8.54601 2.10899C8.35785 2.20487 8.20487 2.35785 8.10899 2.54601C8 2.75992 8 3.03995 8 3.6V4.4C8 4.96005 8 5.24008 8.10899 5.45399C8.20487 5.64215 8.35785 5.79513 8.54601 5.89101C8.75992 6 9.03995 6 9.6 6Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 7.8C5 6.11984 5 5.27976 5.32698 4.63803C5.6146 4.07354 6.07354 3.6146 6.63803 3.32698C7.27976 3 8.11984 3 9.8 3H14.2C15.8802 3 16.7202 3 17.362 3.32698C17.9265 3.6146 18.3854 4.07354 18.673 4.63803C19 5.27976 19 6.11984 19 7.8V21L12 17L5 21V7.8Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 443 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.5 22V17M4.5 7V2M2 4.5H7M2 19.5H7M13 3L11.2658 7.50886C10.9838 8.24209 10.8428 8.60871 10.6235 8.91709C10.4292 9.1904 10.1904 9.42919 9.91709 9.62353C9.60871 9.8428 9.24209 9.98381 8.50886 10.2658L4 12L8.50886 13.7342C9.24209 14.0162 9.60871 14.1572 9.91709 14.3765C10.1904 14.5708 10.4292 14.8096 10.6235 15.0829C10.8428 15.3913 10.9838 15.7579 11.2658 16.4911L13 21L14.7342 16.4911C15.0162 15.7579 15.1572 15.3913 15.3765 15.0829C15.5708 14.8096 15.8096 14.5708 16.0829 14.3765C16.3913 14.1572 16.7579 14.0162 17.4911 13.7342L22 12L17.4911 10.2658C16.7579 9.98381 16.3913 9.8428 16.0829 9.62353C15.8096 9.42919 15.5708 9.1904 15.3765 8.91709C15.1572 8.60871 15.0162 8.24209 14.7342 7.50886L13 3Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 894 B |
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M2 10C2 10 4.00498 7.26822 5.63384 5.63824C7.26269 4.00827 9.5136 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.89691 21 4.43511 18.2543 3.35177 14.5M2 10V4M2 10H8",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "2",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "RefreshCcw01"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './RefreshCcw01.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'RefreshCcw01'
|
||||
|
||||
export default Icon
|
|
@ -3,4 +3,5 @@ export { default as ArrowUpRight } from './ArrowUpRight'
|
|||
export { default as ChevronDownDouble } from './ChevronDownDouble'
|
||||
export { default as ChevronDown } from './ChevronDown'
|
||||
export { default as ChevronRight } from './ChevronRight'
|
||||
export { default as RefreshCcw01 } from './RefreshCcw01'
|
||||
export { default as RefreshCw05 } from './RefreshCw05'
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M16 4C16.93 4 17.395 4 17.7765 4.10222C18.8117 4.37962 19.6204 5.18827 19.8978 6.22354C20 6.60504 20 7.07003 20 8V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V8C4 7.07003 4 6.60504 4.10222 6.22354C4.37962 5.18827 5.18827 4.37962 6.22354 4.10222C6.60504 4 7.07003 4 8 4M9.6 6H14.4C14.9601 6 15.2401 6 15.454 5.89101C15.6422 5.79513 15.7951 5.64215 15.891 5.45399C16 5.24008 16 4.96005 16 4.4V3.6C16 3.03995 16 2.75992 15.891 2.54601C15.7951 2.35785 15.6422 2.20487 15.454 2.10899C15.2401 2 14.9601 2 14.4 2H9.6C9.03995 2 8.75992 2 8.54601 2.10899C8.35785 2.20487 8.20487 2.35785 8.10899 2.54601C8 2.75992 8 3.03995 8 3.6V4.4C8 4.96005 8 5.24008 8.10899 5.45399C8.20487 5.64215 8.35785 5.79513 8.54601 5.89101C8.75992 6 9.03995 6 9.6 6Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "2",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Clipboard"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Clipboard.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Clipboard'
|
||||
|
||||
export default Icon
|
|
@ -1 +1,2 @@
|
|||
export { default as Clipboard } from './Clipboard'
|
||||
export { default as FilePlus02 } from './FilePlus02'
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M5 7.8C5 6.11984 5 5.27976 5.32698 4.63803C5.6146 4.07354 6.07354 3.6146 6.63803 3.32698C7.27976 3 8.11984 3 9.8 3H14.2C15.8802 3 16.7202 3 17.362 3.32698C17.9265 3.6146 18.3854 4.07354 18.673 4.63803C19 5.27976 19 6.11984 19 7.8V21L12 17L5 21V7.8Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "2",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Bookmark"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Bookmark.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Bookmark'
|
||||
|
||||
export default Icon
|
|
@ -1,4 +1,5 @@
|
|||
export { default as AtSign } from './AtSign'
|
||||
export { default as Bookmark } from './Bookmark'
|
||||
export { default as Check } from './Check'
|
||||
export { default as DotsHorizontal } from './DotsHorizontal'
|
||||
export { default as Edit03 } from './Edit03'
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"viewBox": "0 0 24 24",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M4.5 22V17M4.5 7V2M2 4.5H7M2 19.5H7M13 3L11.2658 7.50886C10.9838 8.24209 10.8428 8.60871 10.6235 8.91709C10.4292 9.1904 10.1904 9.42919 9.91709 9.62353C9.60871 9.8428 9.24209 9.98381 8.50886 10.2658L4 12L8.50886 13.7342C9.24209 14.0162 9.60871 14.1572 9.91709 14.3765C10.1904 14.5708 10.4292 14.8096 10.6235 15.0829C10.8428 15.3913 10.9838 15.7579 11.2658 16.4911L13 21L14.7342 16.4911C15.0162 15.7579 15.1572 15.3913 15.3765 15.0829C15.5708 14.8096 15.8096 14.5708 16.0829 14.3765C16.3913 14.1572 16.7579 14.0162 17.4911 13.7342L22 12L17.4911 10.2658C16.7579 9.98381 16.3913 9.8428 16.0829 9.62353C15.8096 9.42919 15.5708 9.1904 15.3765 8.91709C15.1572 8.60871 15.0162 8.24209 14.7342 7.50886L13 3Z",
|
||||
"stroke": "currentColor",
|
||||
"stroke-width": "2",
|
||||
"stroke-linecap": "round",
|
||||
"stroke-linejoin": "round"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Stars02"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Stars02.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'Stars02'
|
||||
|
||||
export default Icon
|
|
@ -0,0 +1 @@
|
|||
export { default as Stars02 } from './Stars02'
|
|
@ -3,11 +3,12 @@ import type { FC } from 'react'
|
|||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import { useBoolean, useClickAway, useGetState } from 'ahooks'
|
||||
import { useBoolean, useClickAway } from 'ahooks'
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import TabHeader from '../../base/tab-header'
|
||||
import Button from '../../base/button'
|
||||
import { checkOrSetAccessToken } from '../utils'
|
||||
import { AlertCircle } from '../../base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import s from './style.module.css'
|
||||
import RunBatch from './run-batch'
|
||||
import ResDownload from './run-batch/res-download'
|
||||
|
@ -25,12 +26,12 @@ import SavedItems from '@/app/components/app/text-generate/saved-items'
|
|||
import type { InstalledApp } from '@/models/explore'
|
||||
import { DEFAULT_VALUE_MAX_LEN, appDefaultIconBackground } from '@/config'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
const PARALLEL_LIMIT = 5
|
||||
const GROUP_SIZE = 5 // to avoid RPM(Request per minute) limit. The group task finished then the next group.
|
||||
enum TaskStatus {
|
||||
pending = 'pending',
|
||||
running = 'running',
|
||||
completed = 'completed',
|
||||
failed = 'failed',
|
||||
}
|
||||
|
||||
type TaskParam = {
|
||||
|
@ -99,15 +100,41 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
showResSidebar()
|
||||
}
|
||||
|
||||
const [allTaskList, setAllTaskList, getLatestTaskList] = useGetState<Task[]>([])
|
||||
const [controlRetry, setControlRetry] = useState(0)
|
||||
const handleRetryAllFailedTask = () => {
|
||||
setControlRetry(Date.now())
|
||||
}
|
||||
const [allTaskList, doSetAllTaskList] = useState<Task[]>([])
|
||||
const allTaskListRef = useRef<Task[]>([])
|
||||
const getLatestTaskList = () => allTaskListRef.current
|
||||
const setAllTaskList = (taskList: Task[]) => {
|
||||
doSetAllTaskList(taskList)
|
||||
allTaskListRef.current = taskList
|
||||
}
|
||||
const pendingTaskList = allTaskList.filter(task => task.status === TaskStatus.pending)
|
||||
const noPendingTask = pendingTaskList.length === 0
|
||||
const showTaskList = allTaskList.filter(task => task.status !== TaskStatus.pending)
|
||||
const [currGroupNum, doSetCurrGroupNum] = useState(0)
|
||||
const currGroupNumRef = useRef(0)
|
||||
const setCurrGroupNum = (num: number) => {
|
||||
doSetCurrGroupNum(num)
|
||||
currGroupNumRef.current = num
|
||||
}
|
||||
const getCurrGroupNum = () => {
|
||||
return currGroupNumRef.current
|
||||
}
|
||||
const allSuccessTaskList = allTaskList.filter(task => task.status === TaskStatus.completed)
|
||||
const allFailedTaskList = allTaskList.filter(task => task.status === TaskStatus.failed)
|
||||
const allTaskFinished = allTaskList.every(task => task.status === TaskStatus.completed)
|
||||
const [batchCompletionRes, setBatchCompletionRes, getBatchCompletionRes] = useGetState<Record<string, string>>({})
|
||||
const allTaskRuned = allTaskList.every(task => [TaskStatus.completed, TaskStatus.failed].includes(task.status))
|
||||
const [batchCompletionRes, doSetBatchCompletionRes] = useState<Record<string, string>>({})
|
||||
const batchCompletionResRef = useRef<Record<string, string>>({})
|
||||
const setBatchCompletionRes = (res: Record<string, string>) => {
|
||||
doSetBatchCompletionRes(res)
|
||||
batchCompletionResRef.current = res
|
||||
}
|
||||
const getBatchCompletionRes = () => batchCompletionResRef.current
|
||||
const exportRes = allTaskList.map((task) => {
|
||||
if (allTaskList.length > 0 && !allTaskFinished)
|
||||
return {}
|
||||
const batchCompletionResLatest = getBatchCompletionRes()
|
||||
const res: Record<string, string> = {}
|
||||
const { inputs } = task.params
|
||||
|
@ -123,7 +150,6 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
return false
|
||||
}
|
||||
const headerData = data[0]
|
||||
const varLen = promptConfig?.prompt_variables.length || 0
|
||||
let isMapVarName = true
|
||||
promptConfig?.prompt_variables.forEach((item, index) => {
|
||||
if (!isMapVarName)
|
||||
|
@ -234,7 +260,7 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
}
|
||||
return {
|
||||
id: i + 1,
|
||||
status: i < PARALLEL_LIMIT ? TaskStatus.running : TaskStatus.pending,
|
||||
status: i < GROUP_SIZE ? TaskStatus.running : TaskStatus.pending,
|
||||
params: {
|
||||
inputs,
|
||||
},
|
||||
|
@ -248,20 +274,28 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
showResSidebar()
|
||||
}
|
||||
const handleCompleted = (completionRes: string, taskId?: number) => {
|
||||
const handleCompleted = (completionRes: string, taskId?: number, isSuccess?: boolean) => {
|
||||
const allTasklistLatest = getLatestTaskList()
|
||||
const batchCompletionResLatest = getBatchCompletionRes()
|
||||
const pendingTaskList = allTasklistLatest.filter(task => task.status === TaskStatus.pending)
|
||||
const nextPendingTaskId = pendingTaskList[0]?.id
|
||||
// console.log(`start: ${allTasklistLatest.map(item => item.status).join(',')}`)
|
||||
const hadRunedTaskNum = 1 + allTasklistLatest.filter(task => [TaskStatus.completed, TaskStatus.failed].includes(task.status)).length
|
||||
const needToAddNextGroupTask = (getCurrGroupNum() !== hadRunedTaskNum) && pendingTaskList.length > 0 && (hadRunedTaskNum % GROUP_SIZE === 0 || (allTasklistLatest.length - hadRunedTaskNum < GROUP_SIZE))
|
||||
// avoid add many task at the same time
|
||||
if (needToAddNextGroupTask)
|
||||
setCurrGroupNum(hadRunedTaskNum)
|
||||
// console.group()
|
||||
// console.log(`[#${taskId}]: ${isSuccess ? 'success' : 'fail'}.currGroupNum: ${getCurrGroupNum()}.hadRunedTaskNum: ${hadRunedTaskNum}, needToAddNextGroupTask: ${needToAddNextGroupTask}`)
|
||||
// console.log([...allTasklistLatest.filter(task => [TaskStatus.completed, TaskStatus.failed].includes(task.status)).map(item => item.id), taskId].sort((a: any, b: any) => a - b).join(','))
|
||||
// console.groupEnd()
|
||||
const nextPendingTaskIds = needToAddNextGroupTask ? pendingTaskList.slice(0, GROUP_SIZE).map(item => item.id) : []
|
||||
const newAllTaskList = allTasklistLatest.map((item) => {
|
||||
if (item.id === taskId) {
|
||||
return {
|
||||
...item,
|
||||
status: TaskStatus.completed,
|
||||
status: isSuccess ? TaskStatus.completed : TaskStatus.failed,
|
||||
}
|
||||
}
|
||||
if (item.id === nextPendingTaskId) {
|
||||
if (needToAddNextGroupTask && nextPendingTaskIds.includes(item.id)) {
|
||||
return {
|
||||
...item,
|
||||
status: TaskStatus.running,
|
||||
|
@ -269,7 +303,6 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
}
|
||||
return item
|
||||
})
|
||||
// console.log(`end: ${newAllTaskList.map(item => item.status).join(',')}`)
|
||||
setAllTaskList(newAllTaskList)
|
||||
if (taskId) {
|
||||
setBatchCompletionRes({
|
||||
|
@ -333,10 +366,12 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
isMobile={isMobile}
|
||||
isInstalledApp={!!isInstalledApp}
|
||||
installedAppInfo={installedAppInfo}
|
||||
isError={task?.status === TaskStatus.failed}
|
||||
promptConfig={promptConfig}
|
||||
moreLikeThisEnabled={!!moreLikeThisConfig?.enabled}
|
||||
inputs={isCallBatchAPI ? (task as Task).params.inputs : inputs}
|
||||
controlSend={controlSend}
|
||||
controlRetry={task?.status === TaskStatus.failed ? controlRetry : 0}
|
||||
controlStopResponding={controlStopResponding}
|
||||
onShowRes={showResSidebar}
|
||||
handleSaveMessage={handleSaveMessage}
|
||||
|
@ -365,7 +400,19 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
<div className='text-lg text-gray-800 font-semibold'>{t('share.generation.title')}</div>
|
||||
</div>
|
||||
<div className='flex items-center space-x-2'>
|
||||
{allTaskList.length > 0 && allTaskFinished && (
|
||||
{allFailedTaskList.length > 0 && (
|
||||
<div className='flex items-center'>
|
||||
<AlertCircle className='w-4 h-4 text-[#D92D20]' />
|
||||
<div className='ml-1 text-[#D92D20]'>{t('share.generation.batchFailed.info', { num: allFailedTaskList.length })}</div>
|
||||
<Button
|
||||
type='primary'
|
||||
className='ml-2 !h-8 !px-3'
|
||||
onClick={handleRetryAllFailedTask}
|
||||
>{t('share.generation.batchFailed.retry')}</Button>
|
||||
<div className='mx-3 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
</div>
|
||||
)}
|
||||
{allSuccessTaskList.length > 0 && (
|
||||
<ResDownload
|
||||
isMobile={isMobile}
|
||||
values={exportRes}
|
||||
|
@ -394,8 +441,12 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
</div>
|
||||
)
|
||||
|
||||
if (!appId || !siteInfo || !promptConfig)
|
||||
return <Loading type='app' />
|
||||
if (!appId || !siteInfo || !promptConfig) {
|
||||
return (
|
||||
<div className='flex items-center h-screen'>
|
||||
<Loading type='app' />
|
||||
</div>)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -466,6 +517,7 @@ const TextGeneration: FC<IMainProps> = ({
|
|||
<RunBatch
|
||||
vars={promptConfig.prompt_variables}
|
||||
onSend={handleRunBatch}
|
||||
isAllFinished={allTaskRuned}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useBoolean, useGetState } from 'ahooks'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { t } from 'i18next'
|
||||
import cn from 'classnames'
|
||||
import TextGenerationRes from '@/app/components/app/text-generate/item'
|
||||
|
@ -18,10 +18,12 @@ export type IResultProps = {
|
|||
isMobile: boolean
|
||||
isInstalledApp: boolean
|
||||
installedAppInfo?: InstalledApp
|
||||
isError: boolean
|
||||
promptConfig: PromptConfig | null
|
||||
moreLikeThisEnabled: boolean
|
||||
inputs: Record<string, any>
|
||||
controlSend?: number
|
||||
controlRetry?: number
|
||||
controlStopResponding?: number
|
||||
onShowRes: () => void
|
||||
handleSaveMessage: (messageId: string) => void
|
||||
|
@ -35,10 +37,12 @@ const Result: FC<IResultProps> = ({
|
|||
isMobile,
|
||||
isInstalledApp,
|
||||
installedAppInfo,
|
||||
isError,
|
||||
promptConfig,
|
||||
moreLikeThisEnabled,
|
||||
inputs,
|
||||
controlSend,
|
||||
controlRetry,
|
||||
controlStopResponding,
|
||||
onShowRes,
|
||||
handleSaveMessage,
|
||||
|
@ -51,7 +55,13 @@ const Result: FC<IResultProps> = ({
|
|||
setResponsingFalse()
|
||||
}, [controlStopResponding])
|
||||
|
||||
const [completionRes, setCompletionRes, getCompletionRes] = useGetState('')
|
||||
const [completionRes, doSetCompletionRes] = useState('')
|
||||
const completionResRef = useRef('')
|
||||
const setCompletionRes = (res: string) => {
|
||||
completionResRef.current = res
|
||||
doSetCompletionRes(res)
|
||||
}
|
||||
const getCompletionRes = () => completionResRef.current
|
||||
const { notify } = Toast
|
||||
const isNoData = !completionRes
|
||||
|
||||
|
@ -124,6 +134,17 @@ const Result: FC<IResultProps> = ({
|
|||
onShowRes()
|
||||
|
||||
setResponsingTrue()
|
||||
const startTime = Date.now()
|
||||
let isTimeout = false
|
||||
const runId = setInterval(() => {
|
||||
if (Date.now() - startTime > 1000 * 60) { // 1min timeout
|
||||
clearInterval(runId)
|
||||
setResponsingFalse()
|
||||
onCompleted(getCompletionRes(), taskId, false)
|
||||
isTimeout = true
|
||||
console.log(`[#${taskId}]: timeout`)
|
||||
}
|
||||
}, 1000)
|
||||
sendCompletionMessage(data, {
|
||||
onData: (data: string, _isFirstMessage: boolean, { messageId }) => {
|
||||
tempMessageId = messageId
|
||||
|
@ -131,13 +152,21 @@ const Result: FC<IResultProps> = ({
|
|||
setCompletionRes(res.join(''))
|
||||
},
|
||||
onCompleted: () => {
|
||||
if (isTimeout)
|
||||
return
|
||||
|
||||
setResponsingFalse()
|
||||
setMessageId(tempMessageId)
|
||||
onCompleted(getCompletionRes(), taskId, true)
|
||||
clearInterval(runId)
|
||||
},
|
||||
onError() {
|
||||
if (isTimeout)
|
||||
return
|
||||
|
||||
setResponsingFalse()
|
||||
onCompleted(getCompletionRes(), taskId, false)
|
||||
clearInterval(runId)
|
||||
},
|
||||
}, isInstalledApp, installedAppInfo?.id)
|
||||
}
|
||||
|
@ -150,9 +179,16 @@ const Result: FC<IResultProps> = ({
|
|||
}
|
||||
}, [controlSend])
|
||||
|
||||
useEffect(() => {
|
||||
if (controlRetry)
|
||||
handleSend()
|
||||
}, [controlRetry])
|
||||
|
||||
const renderTextGenerationRes = () => (
|
||||
<TextGenerationRes
|
||||
className='mt-3'
|
||||
isError={isError}
|
||||
onRetry={handleSend}
|
||||
content={completionRes}
|
||||
messageId={messageId}
|
||||
isInWebApp
|
||||
|
|
|
@ -5,18 +5,21 @@ import {
|
|||
PlayIcon,
|
||||
} from '@heroicons/react/24/solid'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from 'classnames'
|
||||
import CSVReader from './csv-reader'
|
||||
import CSVDownload from './csv-download'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
export type IRunBatchProps = {
|
||||
vars: { name: string }[]
|
||||
onSend: (data: string[][]) => void
|
||||
isAllFinished: boolean
|
||||
}
|
||||
|
||||
const RunBatch: FC<IRunBatchProps> = ({
|
||||
vars,
|
||||
onSend,
|
||||
isAllFinished,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
@ -31,6 +34,7 @@ const RunBatch: FC<IRunBatchProps> = ({
|
|||
const handleSend = () => {
|
||||
onSend(csvData)
|
||||
}
|
||||
const Icon = isAllFinished ? PlayIcon : Loading02
|
||||
return (
|
||||
<div className='pt-4'>
|
||||
<CSVReader onParsed={handleParsed} />
|
||||
|
@ -41,9 +45,9 @@ const RunBatch: FC<IRunBatchProps> = ({
|
|||
type="primary"
|
||||
className='mt-4 !h-8 !pl-3 !pr-4'
|
||||
onClick={handleSend}
|
||||
disabled={!isParsed}
|
||||
disabled={!isParsed || !isAllFinished}
|
||||
>
|
||||
<PlayIcon className="shrink-0 w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<Icon className={cn(!isAllFinished && 'animate-spin', 'shrink-0 w-4 h-4 mr-1')} aria-hidden="true" />
|
||||
<span className='uppercase text-[13px]'>{t('share.generation.run')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
@ -54,6 +54,11 @@ const translation = {
|
|||
csvStructureTitle: 'The CSV file must conform to the following structure:',
|
||||
downloadTemplate: 'Download the template here',
|
||||
field: 'Field',
|
||||
batchFailed: {
|
||||
info: '{{num}} failed executions',
|
||||
retry: 'Retry',
|
||||
outputPlaceholder: 'No output content',
|
||||
},
|
||||
errorMsg: {
|
||||
empty: 'Please input content in the uploaded file.',
|
||||
fileStructNotMatch: 'The uploaded CSV file not match the struct.',
|
||||
|
|
|
@ -51,6 +51,11 @@ const translation = {
|
|||
csvStructureTitle: 'CSV 文件必须符合以下结构:',
|
||||
downloadTemplate: '下载模板',
|
||||
field: '',
|
||||
batchFailed: {
|
||||
info: '{{num}} 次运行失败',
|
||||
retry: '重试',
|
||||
outputPlaceholder: '无输出内容',
|
||||
},
|
||||
errorMsg: {
|
||||
empty: '上传文件的内容不能为空',
|
||||
fileStructNotMatch: '上传文件的内容与结构不匹配',
|
||||
|
|
Loading…
Reference in New Issue
Block a user