diff --git a/web/app/components/app/text-generate/item/index.tsx b/web/app/components/app/text-generate/item/index.tsx index c85acdd58f..1d854f50bf 100644 --- a/web/app/components/app/text-generate/item/index.tsx +++ b/web/app/components/app/text-generate/item/index.tsx @@ -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 }) => (
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}
@@ -51,27 +57,10 @@ export const copyIcon = ( ) -const moreLikeThisIcon = ( - - - - - - - - - - -) - -const saveIcon = ( - - - -) - const GenerationItem: FC = ({ className, + isError, + onRetry, content, messageId, isLoading, @@ -162,7 +151,7 @@ const GenerationItem: FC = ({ }, [isLoading]) return ( -
= ({ }
- + {isError + ?
{t('share.generation.batchFailed.outputPlaceholder')}
+ : ( + + )} +
- {messageId && ( -
-
- { - copy(content) - Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) - }}> - {copyIcon} - {!isMobile &&
{t('common.operation.copy')}
} -
- {isInWebApp && ( - <> + +
+
+ { + copy(content) + Toast.notify({ type: 'success', message: t('common.actionMsg.copySuccessfully') }) + }}> + + {!isMobile &&
{t('common.operation.copy')}
} +
+ {isInWebApp && ( + <> + { onSave?.(messageId as string) }} + > + + {!isMobile &&
{t('common.operation.save')}
} +
+ {(moreLikeThis && depth < MAX_DEPTH) && ( { onSave?.(messageId as string) }} + onClick={handleMoreLikeThis} > - {saveIcon} - {!isMobile &&
{t('common.operation.save')}
} + + {!isMobile &&
{t('appDebug.feature.moreLikeThis.title')}
} +
)} + {isError && + + {!isMobile &&
{t('share.generation.batchFailed.retry')}
} +
} + {!isError && messageId &&
} + {!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'> + +
+
- {(moreLikeThis && depth < MAX_DEPTH) && ( - - {moreLikeThisIcon} - {!isMobile &&
{t('appDebug.feature.moreLikeThis.title')}
} -
)} -
- {!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'> - -
- -
- )} - {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'> - -
- )} - {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'> - -
- )} - - )} -
-
{content?.length} {t('common.unit.char')}
+ )} + {!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'> + +
+ )} + {!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'> + +
+ )} + + )}
- )} +
{content?.length} {t('common.unit.char')}
+
)} {((childMessageId || isQuerying) && depth < 3) && (
- +
)} diff --git a/web/app/components/base/icons/assets/vender/line/arrows/refresh-ccw-01.svg b/web/app/components/base/icons/assets/vender/line/arrows/refresh-ccw-01.svg new file mode 100644 index 0000000000..706c677c3d --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/arrows/refresh-ccw-01.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/assets/vender/line/files/clipboard.svg b/web/app/components/base/icons/assets/vender/line/files/clipboard.svg new file mode 100644 index 0000000000..8abaaa9c39 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/files/clipboard.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/assets/vender/line/general/bookmark.svg b/web/app/components/base/icons/assets/vender/line/general/bookmark.svg new file mode 100644 index 0000000000..576abcdc45 --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/general/bookmark.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/assets/vender/line/weather/stars-02.svg b/web/app/components/base/icons/assets/vender/line/weather/stars-02.svg new file mode 100644 index 0000000000..324aa94aaf --- /dev/null +++ b/web/app/components/base/icons/assets/vender/line/weather/stars-02.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.json b/web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.json new file mode 100644 index 0000000000..30033b41bd --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.json @@ -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" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.tsx b/web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.tsx new file mode 100644 index 0000000000..959233ae0b --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.tsx @@ -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, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'RefreshCcw01' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/arrows/index.ts b/web/app/components/base/icons/src/vender/line/arrows/index.ts index bea93770a6..cd55cba98e 100644 --- a/web/app/components/base/icons/src/vender/line/arrows/index.ts +++ b/web/app/components/base/icons/src/vender/line/arrows/index.ts @@ -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' diff --git a/web/app/components/base/icons/src/vender/line/files/Clipboard.json b/web/app/components/base/icons/src/vender/line/files/Clipboard.json new file mode 100644 index 0000000000..f256747558 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/files/Clipboard.json @@ -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" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/files/Clipboard.tsx b/web/app/components/base/icons/src/vender/line/files/Clipboard.tsx new file mode 100644 index 0000000000..31be579af9 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/files/Clipboard.tsx @@ -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, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Clipboard' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/files/index.ts b/web/app/components/base/icons/src/vender/line/files/index.ts index 434387d105..08eec28b98 100644 --- a/web/app/components/base/icons/src/vender/line/files/index.ts +++ b/web/app/components/base/icons/src/vender/line/files/index.ts @@ -1 +1,2 @@ +export { default as Clipboard } from './Clipboard' export { default as FilePlus02 } from './FilePlus02' diff --git a/web/app/components/base/icons/src/vender/line/general/Bookmark.json b/web/app/components/base/icons/src/vender/line/general/Bookmark.json new file mode 100644 index 0000000000..1b6e517be7 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/Bookmark.json @@ -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" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/general/Bookmark.tsx b/web/app/components/base/icons/src/vender/line/general/Bookmark.tsx new file mode 100644 index 0000000000..587a17a648 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/general/Bookmark.tsx @@ -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, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Bookmark' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/general/index.ts b/web/app/components/base/icons/src/vender/line/general/index.ts index 827893902e..248c52d0ee 100644 --- a/web/app/components/base/icons/src/vender/line/general/index.ts +++ b/web/app/components/base/icons/src/vender/line/general/index.ts @@ -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' diff --git a/web/app/components/base/icons/src/vender/line/weather/Stars02.json b/web/app/components/base/icons/src/vender/line/weather/Stars02.json new file mode 100644 index 0000000000..54f6a42ecf --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/weather/Stars02.json @@ -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" +} \ No newline at end of file diff --git a/web/app/components/base/icons/src/vender/line/weather/Stars02.tsx b/web/app/components/base/icons/src/vender/line/weather/Stars02.tsx new file mode 100644 index 0000000000..606ab51255 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/weather/Stars02.tsx @@ -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, Omit>(( + props, + ref, +) => ) + +Icon.displayName = 'Stars02' + +export default Icon diff --git a/web/app/components/base/icons/src/vender/line/weather/index.ts b/web/app/components/base/icons/src/vender/line/weather/index.ts new file mode 100644 index 0000000000..1a68bce765 --- /dev/null +++ b/web/app/components/base/icons/src/vender/line/weather/index.ts @@ -0,0 +1 @@ +export { default as Stars02 } from './Stars02' diff --git a/web/app/components/share/text-generation/index.tsx b/web/app/components/share/text-generation/index.tsx index 50cc7b8811..d476a70e26 100644 --- a/web/app/components/share/text-generation/index.tsx +++ b/web/app/components/share/text-generation/index.tsx @@ -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 = ({ showResSidebar() } - const [allTaskList, setAllTaskList, getLatestTaskList] = useGetState([]) + const [controlRetry, setControlRetry] = useState(0) + const handleRetryAllFailedTask = () => { + setControlRetry(Date.now()) + } + const [allTaskList, doSetAllTaskList] = useState([]) + const allTaskListRef = useRef([]) + 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>({}) + const allTaskRuned = allTaskList.every(task => [TaskStatus.completed, TaskStatus.failed].includes(task.status)) + const [batchCompletionRes, doSetBatchCompletionRes] = useState>({}) + const batchCompletionResRef = useRef>({}) + const setBatchCompletionRes = (res: Record) => { + 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 = {} const { inputs } = task.params @@ -123,7 +150,6 @@ const TextGeneration: FC = ({ 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 = ({ } 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 = ({ // 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 = ({ } return item }) - // console.log(`end: ${newAllTaskList.map(item => item.status).join(',')}`) setAllTaskList(newAllTaskList) if (taskId) { setBatchCompletionRes({ @@ -333,10 +366,12 @@ const TextGeneration: FC = ({ 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 = ({
{t('share.generation.title')}
- {allTaskList.length > 0 && allTaskFinished && ( + {allFailedTaskList.length > 0 && ( +
+ +
{t('share.generation.batchFailed.info', { num: allFailedTaskList.length })}
+ +
+
+ )} + {allSuccessTaskList.length > 0 && ( = ({
) - if (!appId || !siteInfo || !promptConfig) - return + if (!appId || !siteInfo || !promptConfig) { + return ( +
+ +
) + } return ( <> @@ -466,6 +517,7 @@ const TextGeneration: FC = ({ diff --git a/web/app/components/share/text-generation/result/index.tsx b/web/app/components/share/text-generation/result/index.tsx index 38dffb2229..c9386e4731 100644 --- a/web/app/components/share/text-generation/result/index.tsx +++ b/web/app/components/share/text-generation/result/index.tsx @@ -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 controlSend?: number + controlRetry?: number controlStopResponding?: number onShowRes: () => void handleSaveMessage: (messageId: string) => void @@ -35,10 +37,12 @@ const Result: FC = ({ isMobile, isInstalledApp, installedAppInfo, + isError, promptConfig, moreLikeThisEnabled, inputs, controlSend, + controlRetry, controlStopResponding, onShowRes, handleSaveMessage, @@ -51,7 +55,13 @@ const Result: FC = ({ 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 = ({ 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 = ({ 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 = ({ } }, [controlSend]) + useEffect(() => { + if (controlRetry) + handleSend() + }, [controlRetry]) + const renderTextGenerationRes = () => ( void + isAllFinished: boolean } const RunBatch: FC = ({ vars, onSend, + isAllFinished, }) => { const { t } = useTranslation() @@ -31,6 +34,7 @@ const RunBatch: FC = ({ const handleSend = () => { onSend(csvData) } + const Icon = isAllFinished ? PlayIcon : Loading02 return (
@@ -41,9 +45,9 @@ const RunBatch: FC = ({ type="primary" className='mt-4 !h-8 !pl-3 !pr-4' onClick={handleSend} - disabled={!isParsed} + disabled={!isParsed || !isAllFinished} > -
diff --git a/web/i18n/lang/share-app.en.ts b/web/i18n/lang/share-app.en.ts index fc22af5777..6b5d5dd1a3 100644 --- a/web/i18n/lang/share-app.en.ts +++ b/web/i18n/lang/share-app.en.ts @@ -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.', diff --git a/web/i18n/lang/share-app.zh.ts b/web/i18n/lang/share-app.zh.ts index 5db6bf20d9..7c21f0ed15 100644 --- a/web/i18n/lang/share-app.zh.ts +++ b/web/i18n/lang/share-app.zh.ts @@ -51,6 +51,11 @@ const translation = { csvStructureTitle: 'CSV 文件必须符合以下结构:', downloadTemplate: '下载模板', field: '', + batchFailed: { + info: '{{num}} 次运行失败', + retry: '重试', + outputPlaceholder: '无输出内容', + }, errorMsg: { empty: '上传文件的内容不能为空', fileStructNotMatch: '上传文件的内容与结构不匹配',