소스 검색

feat: batch run support retry errors and decrease rate limit times (#1215)

tags/0.3.24
Joel 2 년 전
부모
커밋
c40ee7e629
No account linked to committer's email address
22개의 변경된 파일428개의 추가작업 그리고 127개의 파일을 삭제
  1. 106
    103
      web/app/components/app/text-generate/item/index.tsx
  2. 3
    0
      web/app/components/base/icons/assets/vender/line/arrows/refresh-ccw-01.svg
  3. 3
    0
      web/app/components/base/icons/assets/vender/line/files/clipboard.svg
  4. 3
    0
      web/app/components/base/icons/assets/vender/line/general/bookmark.svg
  5. 3
    0
      web/app/components/base/icons/assets/vender/line/weather/stars-02.svg
  6. 29
    0
      web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.json
  7. 16
    0
      web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.tsx
  8. 1
    0
      web/app/components/base/icons/src/vender/line/arrows/index.ts
  9. 29
    0
      web/app/components/base/icons/src/vender/line/files/Clipboard.json
  10. 16
    0
      web/app/components/base/icons/src/vender/line/files/Clipboard.tsx
  11. 1
    0
      web/app/components/base/icons/src/vender/line/files/index.ts
  12. 29
    0
      web/app/components/base/icons/src/vender/line/general/Bookmark.json
  13. 16
    0
      web/app/components/base/icons/src/vender/line/general/Bookmark.tsx
  14. 1
    0
      web/app/components/base/icons/src/vender/line/general/index.ts
  15. 29
    0
      web/app/components/base/icons/src/vender/line/weather/Stars02.json
  16. 16
    0
      web/app/components/base/icons/src/vender/line/weather/Stars02.tsx
  17. 1
    0
      web/app/components/base/icons/src/vender/line/weather/index.ts
  18. 70
    18
      web/app/components/share/text-generation/index.tsx
  19. 39
    3
      web/app/components/share/text-generation/result/index.tsx
  20. 7
    3
      web/app/components/share/text-generation/run-batch/index.tsx
  21. 5
    0
      web/i18n/lang/share-app.en.ts
  22. 5
    0
      web/i18n/lang/share-app.zh.ts

+ 106
- 103
web/app/components/app/text-generate/item/index.tsx 파일 보기

import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import type { Feedbacktype } from '@/app/components/app/chat/type' import type { Feedbacktype } from '@/app/components/app/chat/type'
import { fetchMoreLikeThis, updateFeedback } from '@/service/share' 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 const MAX_DEPTH = 3
export type IGenerationItemProps = { export type IGenerationItemProps = {
className?: string className?: string
isError: boolean
onRetry: () => void
content: string content: string
messageId?: string | null messageId?: string | null
isLoading?: boolean isLoading?: boolean
controlClearMoreLikeThis?: number controlClearMoreLikeThis?: number
} }


export const SimpleBtn = ({ className, onClick, children }: {
export const SimpleBtn = ({ className, isDisabled, onClick, children }: {
className?: string className?: string
isDisabled?: boolean
onClick?: () => void onClick?: () => void
children: React.ReactNode children: React.ReactNode
}) => ( }) => (
<div <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} {children}
</div> </div>
</svg> </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> = ({ const GenerationItem: FC<IGenerationItemProps> = ({
className, className,
isError,
onRetry,
content, content,
messageId, messageId,
isLoading, isLoading,
}, [isLoading]) }, [isLoading])


return ( 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 style={isTop
? { ? {
boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)', boxShadow: '0px 1px 2px rgba(16, 24, 40, 0.05)',
} }
<div className='flex'> <div className='flex'>
<div className='grow w-0'> <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>
</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 <SimpleBtn
isDisabled={isError || !messageId}
className={cn(isMobile && '!px-1.5', 'ml-2 space-x-1')} 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> </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>
)}
<div className='text-xs text-gray-500'>{content?.length} {t('common.unit.char')}</div>
</div>


</div> </div>
)} )}


{((childMessageId || isQuerying) && depth < 3) && ( {((childMessageId || isQuerying) && depth < 3) && (
<div className='pl-4'> <div className='pl-4'>
<GenerationItem {...childProps} />
<GenerationItem {...childProps as any} />
</div> </div>
)} )}



+ 3
- 0
web/app/components/base/icons/assets/vender/line/arrows/refresh-ccw-01.svg 파일 보기

<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>

+ 3
- 0
web/app/components/base/icons/assets/vender/line/files/clipboard.svg 파일 보기

<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>

+ 3
- 0
web/app/components/base/icons/assets/vender/line/general/bookmark.svg 파일 보기

<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>

+ 3
- 0
web/app/components/base/icons/assets/vender/line/weather/stars-02.svg 파일 보기

<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>

+ 29
- 0
web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.json 파일 보기

{
"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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/arrows/RefreshCcw01.tsx 파일 보기

// 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

+ 1
- 0
web/app/components/base/icons/src/vender/line/arrows/index.ts 파일 보기

export { default as ChevronDownDouble } from './ChevronDownDouble' export { default as ChevronDownDouble } from './ChevronDownDouble'
export { default as ChevronDown } from './ChevronDown' export { default as ChevronDown } from './ChevronDown'
export { default as ChevronRight } from './ChevronRight' export { default as ChevronRight } from './ChevronRight'
export { default as RefreshCcw01 } from './RefreshCcw01'
export { default as RefreshCw05 } from './RefreshCw05' export { default as RefreshCw05 } from './RefreshCw05'

+ 29
- 0
web/app/components/base/icons/src/vender/line/files/Clipboard.json 파일 보기

{
"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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/files/Clipboard.tsx 파일 보기

// 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
- 0
web/app/components/base/icons/src/vender/line/files/index.ts 파일 보기

export { default as Clipboard } from './Clipboard'
export { default as FilePlus02 } from './FilePlus02' export { default as FilePlus02 } from './FilePlus02'

+ 29
- 0
web/app/components/base/icons/src/vender/line/general/Bookmark.json 파일 보기

{
"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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/general/Bookmark.tsx 파일 보기

// 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
- 0
web/app/components/base/icons/src/vender/line/general/index.ts 파일 보기

export { default as AtSign } from './AtSign' export { default as AtSign } from './AtSign'
export { default as Bookmark } from './Bookmark'
export { default as Check } from './Check' export { default as Check } from './Check'
export { default as DotsHorizontal } from './DotsHorizontal' export { default as DotsHorizontal } from './DotsHorizontal'
export { default as Edit03 } from './Edit03' export { default as Edit03 } from './Edit03'

+ 29
- 0
web/app/components/base/icons/src/vender/line/weather/Stars02.json 파일 보기

{
"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"
}

+ 16
- 0
web/app/components/base/icons/src/vender/line/weather/Stars02.tsx 파일 보기

// 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

+ 1
- 0
web/app/components/base/icons/src/vender/line/weather/index.ts 파일 보기

export { default as Stars02 } from './Stars02'

+ 70
- 18
web/app/components/share/text-generation/index.tsx 파일 보기

import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import cn from 'classnames' import cn from 'classnames'
import { useBoolean, useClickAway, useGetState } from 'ahooks'
import { useBoolean, useClickAway } from 'ahooks'
import { XMarkIcon } from '@heroicons/react/24/outline' import { XMarkIcon } from '@heroicons/react/24/outline'
import TabHeader from '../../base/tab-header' import TabHeader from '../../base/tab-header'
import Button from '../../base/button' import Button from '../../base/button'
import { checkOrSetAccessToken } from '../utils' import { checkOrSetAccessToken } from '../utils'
import { AlertCircle } from '../../base/icons/src/vender/solid/alertsAndFeedback'
import s from './style.module.css' import s from './style.module.css'
import RunBatch from './run-batch' import RunBatch from './run-batch'
import ResDownload from './run-batch/res-download' import ResDownload from './run-batch/res-download'
import type { InstalledApp } from '@/models/explore' import type { InstalledApp } from '@/models/explore'
import { DEFAULT_VALUE_MAX_LEN, appDefaultIconBackground } from '@/config' import { DEFAULT_VALUE_MAX_LEN, appDefaultIconBackground } from '@/config'
import Toast from '@/app/components/base/toast' 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 { enum TaskStatus {
pending = 'pending', pending = 'pending',
running = 'running', running = 'running',
completed = 'completed', completed = 'completed',
failed = 'failed',
} }


type TaskParam = { type TaskParam = {
showResSidebar() 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 pendingTaskList = allTaskList.filter(task => task.status === TaskStatus.pending)
const noPendingTask = pendingTaskList.length === 0 const noPendingTask = pendingTaskList.length === 0
const showTaskList = allTaskList.filter(task => task.status !== TaskStatus.pending) 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 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) => { const exportRes = allTaskList.map((task) => {
if (allTaskList.length > 0 && !allTaskFinished)
return {}
const batchCompletionResLatest = getBatchCompletionRes() const batchCompletionResLatest = getBatchCompletionRes()
const res: Record<string, string> = {} const res: Record<string, string> = {}
const { inputs } = task.params const { inputs } = task.params
return false return false
} }
const headerData = data[0] const headerData = data[0]
const varLen = promptConfig?.prompt_variables.length || 0
let isMapVarName = true let isMapVarName = true
promptConfig?.prompt_variables.forEach((item, index) => { promptConfig?.prompt_variables.forEach((item, index) => {
if (!isMapVarName) if (!isMapVarName)
} }
return { return {
id: i + 1, id: i + 1,
status: i < PARALLEL_LIMIT ? TaskStatus.running : TaskStatus.pending,
status: i < GROUP_SIZE ? TaskStatus.running : TaskStatus.pending,
params: { params: {
inputs, inputs,
}, },
// eslint-disable-next-line @typescript-eslint/no-use-before-define // eslint-disable-next-line @typescript-eslint/no-use-before-define
showResSidebar() showResSidebar()
} }
const handleCompleted = (completionRes: string, taskId?: number) => {
const handleCompleted = (completionRes: string, taskId?: number, isSuccess?: boolean) => {
const allTasklistLatest = getLatestTaskList() const allTasklistLatest = getLatestTaskList()
const batchCompletionResLatest = getBatchCompletionRes() const batchCompletionResLatest = getBatchCompletionRes()
const pendingTaskList = allTasklistLatest.filter(task => task.status === TaskStatus.pending) 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) => { const newAllTaskList = allTasklistLatest.map((item) => {
if (item.id === taskId) { if (item.id === taskId) {
return { return {
...item, ...item,
status: TaskStatus.completed,
status: isSuccess ? TaskStatus.completed : TaskStatus.failed,
} }
} }
if (item.id === nextPendingTaskId) {
if (needToAddNextGroupTask && nextPendingTaskIds.includes(item.id)) {
return { return {
...item, ...item,
status: TaskStatus.running, status: TaskStatus.running,
} }
return item return item
}) })
// console.log(`end: ${newAllTaskList.map(item => item.status).join(',')}`)
setAllTaskList(newAllTaskList) setAllTaskList(newAllTaskList)
if (taskId) { if (taskId) {
setBatchCompletionRes({ setBatchCompletionRes({
isMobile={isMobile} isMobile={isMobile}
isInstalledApp={!!isInstalledApp} isInstalledApp={!!isInstalledApp}
installedAppInfo={installedAppInfo} installedAppInfo={installedAppInfo}
isError={task?.status === TaskStatus.failed}
promptConfig={promptConfig} promptConfig={promptConfig}
moreLikeThisEnabled={!!moreLikeThisConfig?.enabled} moreLikeThisEnabled={!!moreLikeThisConfig?.enabled}
inputs={isCallBatchAPI ? (task as Task).params.inputs : inputs} inputs={isCallBatchAPI ? (task as Task).params.inputs : inputs}
controlSend={controlSend} controlSend={controlSend}
controlRetry={task?.status === TaskStatus.failed ? controlRetry : 0}
controlStopResponding={controlStopResponding} controlStopResponding={controlStopResponding}
onShowRes={showResSidebar} onShowRes={showResSidebar}
handleSaveMessage={handleSaveMessage} handleSaveMessage={handleSaveMessage}
<div className='text-lg text-gray-800 font-semibold'>{t('share.generation.title')}</div> <div className='text-lg text-gray-800 font-semibold'>{t('share.generation.title')}</div>
</div> </div>
<div className='flex items-center space-x-2'> <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 <ResDownload
isMobile={isMobile} isMobile={isMobile}
values={exportRes} values={exportRes}
</div> </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 ( return (
<> <>
<RunBatch <RunBatch
vars={promptConfig.prompt_variables} vars={promptConfig.prompt_variables}
onSend={handleRunBatch} onSend={handleRunBatch}
isAllFinished={allTaskRuned}
/> />
</div> </div>



+ 39
- 3
web/app/components/share/text-generation/result/index.tsx 파일 보기

'use client' 'use client'
import type { FC } from 'react' 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 { t } from 'i18next'
import cn from 'classnames' import cn from 'classnames'
import TextGenerationRes from '@/app/components/app/text-generate/item' import TextGenerationRes from '@/app/components/app/text-generate/item'
isMobile: boolean isMobile: boolean
isInstalledApp: boolean isInstalledApp: boolean
installedAppInfo?: InstalledApp installedAppInfo?: InstalledApp
isError: boolean
promptConfig: PromptConfig | null promptConfig: PromptConfig | null
moreLikeThisEnabled: boolean moreLikeThisEnabled: boolean
inputs: Record<string, any> inputs: Record<string, any>
controlSend?: number controlSend?: number
controlRetry?: number
controlStopResponding?: number controlStopResponding?: number
onShowRes: () => void onShowRes: () => void
handleSaveMessage: (messageId: string) => void handleSaveMessage: (messageId: string) => void
isMobile, isMobile,
isInstalledApp, isInstalledApp,
installedAppInfo, installedAppInfo,
isError,
promptConfig, promptConfig,
moreLikeThisEnabled, moreLikeThisEnabled,
inputs, inputs,
controlSend, controlSend,
controlRetry,
controlStopResponding, controlStopResponding,
onShowRes, onShowRes,
handleSaveMessage, handleSaveMessage,
setResponsingFalse() setResponsingFalse()
}, [controlStopResponding]) }, [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 { notify } = Toast
const isNoData = !completionRes const isNoData = !completionRes


onShowRes() onShowRes()


setResponsingTrue() 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, { sendCompletionMessage(data, {
onData: (data: string, _isFirstMessage: boolean, { messageId }) => { onData: (data: string, _isFirstMessage: boolean, { messageId }) => {
tempMessageId = messageId tempMessageId = messageId
setCompletionRes(res.join('')) setCompletionRes(res.join(''))
}, },
onCompleted: () => { onCompleted: () => {
if (isTimeout)
return

setResponsingFalse() setResponsingFalse()
setMessageId(tempMessageId) setMessageId(tempMessageId)
onCompleted(getCompletionRes(), taskId, true) onCompleted(getCompletionRes(), taskId, true)
clearInterval(runId)
}, },
onError() { onError() {
if (isTimeout)
return

setResponsingFalse() setResponsingFalse()
onCompleted(getCompletionRes(), taskId, false) onCompleted(getCompletionRes(), taskId, false)
clearInterval(runId)
}, },
}, isInstalledApp, installedAppInfo?.id) }, isInstalledApp, installedAppInfo?.id)
} }
} }
}, [controlSend]) }, [controlSend])


useEffect(() => {
if (controlRetry)
handleSend()
}, [controlRetry])

const renderTextGenerationRes = () => ( const renderTextGenerationRes = () => (
<TextGenerationRes <TextGenerationRes
className='mt-3' className='mt-3'
isError={isError}
onRetry={handleSend}
content={completionRes} content={completionRes}
messageId={messageId} messageId={messageId}
isInWebApp isInWebApp

+ 7
- 3
web/app/components/share/text-generation/run-batch/index.tsx 파일 보기

PlayIcon, PlayIcon,
} from '@heroicons/react/24/solid' } from '@heroicons/react/24/solid'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import CSVReader from './csv-reader' import CSVReader from './csv-reader'
import CSVDownload from './csv-download' import CSVDownload from './csv-download'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
export type IRunBatchProps = { export type IRunBatchProps = {
vars: { name: string }[] vars: { name: string }[]
onSend: (data: string[][]) => void onSend: (data: string[][]) => void
isAllFinished: boolean
} }


const RunBatch: FC<IRunBatchProps> = ({ const RunBatch: FC<IRunBatchProps> = ({
vars, vars,
onSend, onSend,
isAllFinished,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()


const handleSend = () => { const handleSend = () => {
onSend(csvData) onSend(csvData)
} }
const Icon = isAllFinished ? PlayIcon : Loading02
return ( return (
<div className='pt-4'> <div className='pt-4'>
<CSVReader onParsed={handleParsed} /> <CSVReader onParsed={handleParsed} />
type="primary" type="primary"
className='mt-4 !h-8 !pl-3 !pr-4' className='mt-4 !h-8 !pl-3 !pr-4'
onClick={handleSend} 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> <span className='uppercase text-[13px]'>{t('share.generation.run')}</span>
</Button> </Button>
</div> </div>

+ 5
- 0
web/i18n/lang/share-app.en.ts 파일 보기

csvStructureTitle: 'The CSV file must conform to the following structure:', csvStructureTitle: 'The CSV file must conform to the following structure:',
downloadTemplate: 'Download the template here', downloadTemplate: 'Download the template here',
field: 'Field', field: 'Field',
batchFailed: {
info: '{{num}} failed executions',
retry: 'Retry',
outputPlaceholder: 'No output content',
},
errorMsg: { errorMsg: {
empty: 'Please input content in the uploaded file.', empty: 'Please input content in the uploaded file.',
fileStructNotMatch: 'The uploaded CSV file not match the struct.', fileStructNotMatch: 'The uploaded CSV file not match the struct.',

+ 5
- 0
web/i18n/lang/share-app.zh.ts 파일 보기

csvStructureTitle: 'CSV 文件必须符合以下结构:', csvStructureTitle: 'CSV 文件必须符合以下结构:',
downloadTemplate: '下载模板', downloadTemplate: '下载模板',
field: '', field: '',
batchFailed: {
info: '{{num}} 次运行失败',
retry: '重试',
outputPlaceholder: '无输出内容',
},
errorMsg: { errorMsg: {
empty: '上传文件的内容不能为空', empty: '上传文件的内容不能为空',
fileStructNotMatch: '上传文件的内容与结构不匹配', fileStructNotMatch: '上传文件的内容与结构不匹配',

Loading…
취소
저장