mirror of
http://112.124.100.131/huang.ze/ebiz-dify-ai.git
synced 2025-12-09 10:56:52 +08:00
Feat: web app dark mode (#14732)
This commit is contained in:
@@ -3,18 +3,16 @@ import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiBookmark3Line,
|
||||
RiErrorWarningFill,
|
||||
} from '@remixicon/react'
|
||||
import { useBoolean, useClickAway } from 'ahooks'
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import TabHeader from '../../base/tab-header'
|
||||
import Button from '../../base/button'
|
||||
import { checkOrSetAccessToken } from '../utils'
|
||||
import s from './style.module.css'
|
||||
import MenuDropdown from './menu-dropdown'
|
||||
import RunBatch from './run-batch'
|
||||
import ResDownload from './run-batch/res-download'
|
||||
import cn from '@/utils/classnames'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import RunOnce from '@/app/components/share/text-generation/run-once'
|
||||
import { fetchSavedMessage as doFetchSavedMessage, fetchAppInfo, fetchAppParams, removeMessage, saveMessage } from '@/service/share'
|
||||
@@ -26,6 +24,7 @@ import type {
|
||||
TextToSpeechConfig,
|
||||
} from '@/models/debug'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import { changeLanguage } from '@/i18n/i18next-config'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { userInputsFormToPromptVariables } from '@/utils/model-config'
|
||||
@@ -37,6 +36,8 @@ import Toast from '@/app/components/base/toast'
|
||||
import type { VisionFile, VisionSettings } from '@/types/app'
|
||||
import { Resolution, TransferMethod } from '@/types/app'
|
||||
import { useAppFavicon } from '@/hooks/use-app-favicon'
|
||||
import LogoSite from '@/app/components/base/logo/logo-site'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const GROUP_SIZE = 5 // to avoid RPM(Request per minute) limit. The group task finished then the next group.
|
||||
enum TaskStatus {
|
||||
@@ -72,8 +73,6 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const media = useBreakpoints()
|
||||
const isPC = media === MediaType.pc
|
||||
const isTablet = media === MediaType.tablet
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const mode = searchParams.get('mode') || 'create'
|
||||
@@ -102,6 +101,7 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
const [appId, setAppId] = useState<string>('')
|
||||
const [siteInfo, setSiteInfo] = useState<SiteInfo | null>(null)
|
||||
const [canReplaceLogo, setCanReplaceLogo] = useState<boolean>(false)
|
||||
const [customConfig, setCustomConfig] = useState<Record<string, any> | null>(null)
|
||||
const [promptConfig, setPromptConfig] = useState<PromptConfig | null>(null)
|
||||
const [moreLikeThisConfig, setMoreLikeThisConfig] = useState<MoreLikeThisConfig | null>(null)
|
||||
const [textToSpeechConfig, setTextToSpeechConfig] = useState<TextToSpeechConfig | null>(null)
|
||||
@@ -142,7 +142,7 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
setAllTaskList([]) // clear batch task running status
|
||||
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
showResSidebar()
|
||||
showResultPanel()
|
||||
}
|
||||
|
||||
const [controlRetry, setControlRetry] = useState(0)
|
||||
@@ -323,7 +323,7 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
setControlStopResponding(Date.now())
|
||||
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
showResSidebar()
|
||||
showResultPanel()
|
||||
}
|
||||
const handleCompleted = (completionRes: string, taskId?: number, isSuccess?: boolean) => {
|
||||
const allTaskListLatest = getLatestTaskList()
|
||||
@@ -388,10 +388,11 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const [appData, appParams]: any = await fetchInitData()
|
||||
const { app_id: appId, site: siteInfo, can_replace_logo } = appData
|
||||
const { app_id: appId, site: siteInfo, can_replace_logo, custom_config } = appData
|
||||
setAppId(appId)
|
||||
setSiteInfo(siteInfo as SiteInfo)
|
||||
setCanReplaceLogo(can_replace_logo)
|
||||
setCustomConfig(custom_config)
|
||||
changeLanguage(siteInfo.default_language)
|
||||
|
||||
const { user_input_form, more_like_this, file_upload, text_to_speech }: any = appParams
|
||||
@@ -431,24 +432,21 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
icon_url: siteInfo?.icon_url,
|
||||
})
|
||||
|
||||
const [isShowResSidebar, { setTrue: doShowResSidebar, setFalse: hideResSidebar }] = useBoolean(false)
|
||||
const showResSidebar = () => {
|
||||
const [isShowResultPanel, { setTrue: doShowResultPanel, setFalse: hideResultPanel }] = useBoolean(false)
|
||||
const showResultPanel = () => {
|
||||
// fix: useClickAway hideResSidebar will close sidebar
|
||||
setTimeout(() => {
|
||||
doShowResSidebar()
|
||||
doShowResultPanel()
|
||||
}, 0)
|
||||
}
|
||||
const resRef = useRef<HTMLDivElement>(null)
|
||||
useClickAway(() => {
|
||||
hideResSidebar()
|
||||
}, resRef)
|
||||
const [resultExisted, setResultExisted] = useState(false)
|
||||
|
||||
const renderRes = (task?: Task) => (<Res
|
||||
key={task?.id}
|
||||
isWorkflow={isWorkflow}
|
||||
isCallBatchAPI={isCallBatchAPI}
|
||||
isPC={isPC}
|
||||
isMobile={isMobile}
|
||||
isMobile={!isPC}
|
||||
isInstalledApp={isInstalledApp}
|
||||
installedAppInfo={installedAppInfo}
|
||||
isError={task?.status === TaskStatus.failed}
|
||||
@@ -458,7 +456,7 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
controlSend={controlSend}
|
||||
controlRetry={task?.status === TaskStatus.failed ? controlRetry : 0}
|
||||
controlStopResponding={controlStopResponding}
|
||||
onShowRes={showResSidebar}
|
||||
onShowRes={showResultPanel}
|
||||
handleSaveMessage={handleSaveMessage}
|
||||
taskId={task?.id}
|
||||
onCompleted={handleCompleted}
|
||||
@@ -466,77 +464,60 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
completionFiles={completionFiles}
|
||||
isShowTextToSpeech={!!textToSpeechConfig?.enabled}
|
||||
siteInfo={siteInfo}
|
||||
onRunStart={() => setResultExisted(true)}
|
||||
/>)
|
||||
|
||||
const renderBatchRes = () => {
|
||||
return (showTaskList.map(task => renderRes(task)))
|
||||
}
|
||||
|
||||
const resWrapClassNames = (() => {
|
||||
if (isPC)
|
||||
return 'grow h-full'
|
||||
|
||||
if (!isShowResSidebar)
|
||||
return 'none'
|
||||
|
||||
return cn('fixed z-50 inset-0', isTablet ? 'pl-[128px]' : 'pl-6')
|
||||
})()
|
||||
|
||||
const renderResWrap = (
|
||||
<div
|
||||
ref={resRef}
|
||||
className={
|
||||
cn(
|
||||
'flex flex-col h-full shrink-0',
|
||||
isPC ? 'px-10 py-8' : 'bg-gray-50',
|
||||
isTablet && 'p-6', isMobile && 'p-4')
|
||||
}
|
||||
className={cn(
|
||||
'relative flex flex-col h-full',
|
||||
!isPC && 'h-[calc(100vh_-_36px)] rounded-t-2xl shadow-lg backdrop-blur-sm',
|
||||
!isPC
|
||||
? isShowResultPanel
|
||||
? 'bg-background-default-burn'
|
||||
: 'bg-components-panel-bg border-t-[0.5px] border-divider-regular'
|
||||
: 'bg-chatbot-bg',
|
||||
)}
|
||||
>
|
||||
<>
|
||||
<div className='flex items-center justify-between shrink-0'>
|
||||
<div className='flex items-center space-x-3'>
|
||||
<div className={s.starIcon}></div>
|
||||
<div className='text-lg font-semibold text-gray-800'>{t('share.generation.title')}</div>
|
||||
</div>
|
||||
<div className='flex items-center space-x-2'>
|
||||
{allFailedTaskList.length > 0 && (
|
||||
<div className='flex items-center'>
|
||||
<RiErrorWarningFill className='w-4 h-4 text-[#D92D20]' />
|
||||
<div className='ml-1 text-[#D92D20]'>{t('share.generation.batchFailed.info', { num: allFailedTaskList.length })}</div>
|
||||
<Button
|
||||
variant='primary'
|
||||
className='ml-2'
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
{!isPC && (
|
||||
<div
|
||||
className='flex items-center justify-center cursor-pointer'
|
||||
onClick={hideResSidebar}
|
||||
>
|
||||
<XMarkIcon className='w-4 h-4 text-gray-800' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='overflow-y-auto grow'>
|
||||
{!isCallBatchAPI ? renderRes() : renderBatchRes()}
|
||||
{!noPendingTask && (
|
||||
<div className='mt-4'>
|
||||
<Loading type='area' />
|
||||
</div>
|
||||
{isCallBatchAPI && (
|
||||
<div className={cn(
|
||||
'shrink-0 px-14 pt-9 pb-2 flex items-center justify-between',
|
||||
!isPC && 'px-4 pt-3 pb-1',
|
||||
)}>
|
||||
<div className='text-text-primary system-md-semibold-uppercase'>{t('share.generation.executions', { num: allTaskList.length })}</div>
|
||||
{allSuccessTaskList.length > 0 && (
|
||||
<ResDownload
|
||||
isMobile={!isPC}
|
||||
values={exportRes}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className={cn(
|
||||
'grow flex flex-col h-0 overflow-y-auto',
|
||||
isPC && 'px-14 py-8',
|
||||
isPC && isCallBatchAPI && 'pt-0',
|
||||
!isPC && 'p-0 pb-2',
|
||||
)}>
|
||||
{!isCallBatchAPI ? renderRes() : renderBatchRes()}
|
||||
{!noPendingTask && (
|
||||
<div className='mt-4'>
|
||||
<Loading type='area' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isCallBatchAPI && allFailedTaskList.length > 0 && (
|
||||
<div className='z-10 absolute bottom-6 left-1/2 -translate-x-1/2 flex items-center gap-2 p-3 rounded-xl bg-components-panel-bg-blur backdrop-blur-sm border border-components-panel-border shadow-lg'>
|
||||
<RiErrorWarningFill className='w-4 h-4 text-text-destructive' />
|
||||
<div className='text-text-secondary system-sm-medium'>{t('share.generation.batchFailed.info', { num: allFailedTaskList.length })}</div>
|
||||
<div className='w-px h-3.5 bg-divider-regular'></div>
|
||||
<div onClick={handleRetryAllFailedTask} className='text-text-accent system-sm-semibold-uppercase cursor-pointer'>{t('share.generation.batchFailed.retry')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -548,46 +529,34 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn(
|
||||
'bg-background-default-burn',
|
||||
isPC && 'flex',
|
||||
!isPC && 'flex-col',
|
||||
isInstalledApp ? 'h-full rounded-2xl shadow-md' : 'h-screen',
|
||||
)}>
|
||||
{/* Left */}
|
||||
<div className={cn(
|
||||
isPC && 'flex',
|
||||
isInstalledApp ? s.installedApp : 'h-screen',
|
||||
'bg-gray-50',
|
||||
'shrink-0 relative flex flex-col h-full',
|
||||
isPC ? 'w-[600px] max-w-[50%]' : resultExisted ? 'h-[calc(100%_-_64px)]' : '',
|
||||
isInstalledApp && 'rounded-l-2xl',
|
||||
)}>
|
||||
{/* Left */}
|
||||
<div className={cn(
|
||||
isPC ? 'w-[600px] max-w-[50%] p-8' : 'p-4',
|
||||
isInstalledApp && 'rounded-l-2xl',
|
||||
'shrink-0 relative flex flex-col pb-10 h-full border-r border-gray-100 bg-white',
|
||||
)}>
|
||||
<div className='mb-6'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center space-x-3'>
|
||||
<AppIcon
|
||||
size="small"
|
||||
iconType={siteInfo.icon_type}
|
||||
icon={siteInfo.icon}
|
||||
background={siteInfo.icon_background || appDefaultIconBackground}
|
||||
imageUrl={siteInfo.icon_url}
|
||||
/>
|
||||
<div className='text-lg font-semibold text-gray-800'>{siteInfo.title}</div>
|
||||
</div>
|
||||
{!isPC && (
|
||||
<Button
|
||||
className='shrink-0 ml-2'
|
||||
onClick={showResSidebar}
|
||||
>
|
||||
<div className='flex items-center space-x-2 text-primary-600 text-[13px] font-medium'>
|
||||
<div className={s.starIcon}></div>
|
||||
<span>{t('share.generation.title')}</span>
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{siteInfo.description && (
|
||||
<div className='mt-2 text-xs text-gray-500'>{siteInfo.description}</div>
|
||||
)}
|
||||
{/* header */}
|
||||
<div className={cn('shrink-0 space-y-4 border-b border-divider-subtle', isPC ? 'p-8 pb-0 bg-components-panel-bg' : 'p-4 pb-0')}>
|
||||
<div className='flex items-center gap-3'>
|
||||
<AppIcon
|
||||
size={isPC ? 'large' : 'small'}
|
||||
iconType={siteInfo.icon_type}
|
||||
icon={siteInfo.icon}
|
||||
background={siteInfo.icon_background || appDefaultIconBackground}
|
||||
imageUrl={siteInfo.icon_url}
|
||||
/>
|
||||
<div className='grow text-text-secondary system-md-semibold truncate'>{siteInfo.title}</div>
|
||||
<MenuDropdown data={siteInfo} />
|
||||
</div>
|
||||
{siteInfo.description && (
|
||||
<div className='system-xs-regular text-text-tertiary'>{siteInfo.description}</div>
|
||||
)}
|
||||
<TabHeader
|
||||
items={[
|
||||
{ id: 'create', name: t('share.generation.tabs.create') },
|
||||
@@ -597,11 +566,12 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
id: 'saved',
|
||||
name: t('share.generation.tabs.saved'),
|
||||
isRight: true,
|
||||
icon: <RiBookmark3Line className='w-4 h-4' />,
|
||||
extra: savedMessages.length > 0
|
||||
? (
|
||||
<div className='ml-1 flex items-center h-5 px-1.5 rounded-md border border-gray-200 text-gray-500 text-xs font-medium'>
|
||||
<Badge className='ml-1'>
|
||||
{savedMessages.length}
|
||||
</div>
|
||||
</Badge>
|
||||
)
|
||||
: null,
|
||||
}]
|
||||
@@ -610,72 +580,89 @@ const TextGeneration: FC<IMainProps> = ({
|
||||
value={currentTab}
|
||||
onChange={setCurrentTab}
|
||||
/>
|
||||
<div className='h-20 overflow-y-auto grow'>
|
||||
<div className={cn(currentTab === 'create' ? 'block' : 'hidden')}>
|
||||
<RunOnce
|
||||
siteInfo={siteInfo}
|
||||
inputs={inputs}
|
||||
inputsRef={inputsRef}
|
||||
onInputsChange={setInputs}
|
||||
promptConfig={promptConfig}
|
||||
onSend={handleSend}
|
||||
visionConfig={visionConfig}
|
||||
onVisionFilesChange={setCompletionFiles}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn(isInBatchTab ? 'block' : 'hidden')}>
|
||||
<RunBatch
|
||||
vars={promptConfig.prompt_variables}
|
||||
onSend={handleRunBatch}
|
||||
isAllFinished={allTasksRun}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{currentTab === 'saved' && (
|
||||
<SavedItems
|
||||
className='mt-4'
|
||||
isShowTextToSpeech={textToSpeechConfig?.enabled}
|
||||
list={savedMessages}
|
||||
onRemove={handleRemoveSavedMessage}
|
||||
onStartCreateContent={() => setCurrentTab('create')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* form */}
|
||||
<div className={cn(
|
||||
'grow h-0 bg-components-panel-bg overflow-y-auto',
|
||||
isPC ? 'px-8' : 'px-4',
|
||||
!isPC && resultExisted && customConfig?.remove_webapp_brand && 'rounded-b-2xl border-b-[0.5px] border-divider-regular',
|
||||
)}>
|
||||
<div className={cn(currentTab === 'create' ? 'block' : 'hidden')}>
|
||||
<RunOnce
|
||||
siteInfo={siteInfo}
|
||||
inputs={inputs}
|
||||
inputsRef={inputsRef}
|
||||
onInputsChange={setInputs}
|
||||
promptConfig={promptConfig}
|
||||
onSend={handleSend}
|
||||
visionConfig={visionConfig}
|
||||
onVisionFilesChange={setCompletionFiles}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* copyright */}
|
||||
<div className={cn(isInBatchTab ? 'block' : 'hidden')}>
|
||||
<RunBatch
|
||||
vars={promptConfig.prompt_variables}
|
||||
onSend={handleRunBatch}
|
||||
isAllFinished={allTasksRun}
|
||||
/>
|
||||
</div>
|
||||
{currentTab === 'saved' && (
|
||||
<SavedItems
|
||||
className={cn(isPC ? 'mt-6' : 'mt-4')}
|
||||
isShowTextToSpeech={textToSpeechConfig?.enabled}
|
||||
list={savedMessages}
|
||||
onRemove={handleRemoveSavedMessage}
|
||||
onStartCreateContent={() => setCurrentTab('create')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* powered by */}
|
||||
{!customConfig?.remove_webapp_brand && (
|
||||
<div className={cn(
|
||||
isInstalledApp ? 'left-[248px]' : 'left-8',
|
||||
'fixed bottom-4 flex space-x-2 text-gray-400 font-normal text-xs',
|
||||
'shrink-0 py-3 flex items-center gap-1.5 bg-components-panel-bg',
|
||||
isPC ? 'px-8' : 'px-4',
|
||||
!isPC && resultExisted && 'rounded-b-2xl border-b-[0.5px] border-divider-regular',
|
||||
)}>
|
||||
{siteInfo.copyright && (
|
||||
<div className="">© {(new Date()).getFullYear()} {siteInfo.copyright}</div>
|
||||
<div className='text-text-tertiary system-2xs-medium-uppercase'>{t('share.chat.poweredBy')}</div>
|
||||
{customConfig?.replace_webapp_logo && (
|
||||
<img src={customConfig?.replace_webapp_logo} alt='logo' className='block w-auto h-5' />
|
||||
)}
|
||||
{siteInfo.privacy_policy && (
|
||||
<>
|
||||
{siteInfo.copyright && <div>·</div>}
|
||||
<div>{t('share.chat.privacyPolicyLeft')}
|
||||
<a
|
||||
className='text-gray-500 px-1'
|
||||
href={siteInfo.privacy_policy}
|
||||
target='_blank' rel='noopener noreferrer'>{t('share.chat.privacyPolicyMiddle')}</a>
|
||||
{t('share.chat.privacyPolicyRight')}
|
||||
</div>
|
||||
</>
|
||||
{!customConfig?.replace_webapp_logo && (
|
||||
<LogoSite className='!h-5' />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Result */}
|
||||
<div
|
||||
className={resWrapClassNames}
|
||||
style={{
|
||||
background: (!isPC && isShowResSidebar) ? 'rgba(35, 56, 118, 0.2)' : 'none',
|
||||
}}
|
||||
>
|
||||
{renderResWrap}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
{/* Result */}
|
||||
<div className={cn(
|
||||
isPC
|
||||
? 'grow h-full'
|
||||
: isShowResultPanel
|
||||
? 'fixed z-50 inset-0 bg-background-overlay backdrop-blur-sm'
|
||||
: resultExisted
|
||||
? 'relative shrink-0 h-16 pt-2.5 bg-background-default-burn overflow-hidden'
|
||||
: '',
|
||||
)}>
|
||||
{!isPC && (
|
||||
<div
|
||||
className={cn(
|
||||
isShowResultPanel
|
||||
? 'p-2 pt-6 flex items-center justify-center'
|
||||
: 'z-10 absolute top-0 left-0 w-full px-2 pt-[3px] pb-[57px] flex items-center justify-center',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (isShowResultPanel)
|
||||
hideResultPanel()
|
||||
else
|
||||
showResultPanel()
|
||||
}}
|
||||
>
|
||||
<div className='w-8 h-1 rounded bg-divider-solid cursor-grab'/>
|
||||
</div>
|
||||
)}
|
||||
{renderResWrap}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
49
web/app/components/share/text-generation/info-modal.tsx
Normal file
49
web/app/components/share/text-generation/info-modal.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import { appDefaultIconBackground } from '@/config'
|
||||
import cn from 'classnames'
|
||||
|
||||
type Props = {
|
||||
data?: SiteInfo
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const InfoModal = ({
|
||||
isShow,
|
||||
onClose,
|
||||
data,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isShow={isShow}
|
||||
onClose={onClose}
|
||||
className='!p-0 min-w-[400px] max-w-[400px]'
|
||||
closable
|
||||
>
|
||||
<div className={cn('pt-10 px-4 pb-8 flex flex-col items-center gap-4')}>
|
||||
<AppIcon
|
||||
size='xxl'
|
||||
iconType={data?.icon_type}
|
||||
icon={data?.icon}
|
||||
background={data?.icon_background || appDefaultIconBackground}
|
||||
imageUrl={data?.icon_url}
|
||||
/>
|
||||
<div className='text-text-secondary system-xl-semibold'>{data?.title}</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>
|
||||
{/* copyright */}
|
||||
{data?.copyright && (
|
||||
<div>© {(new Date()).getFullYear()} {data?.copyright}</div>
|
||||
)}
|
||||
{data?.custom_disclaimer && (
|
||||
<div className='mt-2'>{data.custom_disclaimer}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default InfoModal
|
||||
91
web/app/components/share/text-generation/menu-dropdown.tsx
Normal file
91
web/app/components/share/text-generation/menu-dropdown.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { Placement } from '@floating-ui/react'
|
||||
import {
|
||||
RiEqualizer2Line,
|
||||
} from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import InfoModal from './info-modal'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
data?: SiteInfo
|
||||
placement?: Placement
|
||||
}
|
||||
|
||||
const MenuDropdown: FC<Props> = ({
|
||||
data,
|
||||
placement,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, doSetOpen] = useState(false)
|
||||
const openRef = useRef(open)
|
||||
const setOpen = useCallback((v: boolean) => {
|
||||
doSetOpen(v)
|
||||
openRef.current = v
|
||||
}, [doSetOpen])
|
||||
|
||||
const handleTrigger = useCallback(() => {
|
||||
setOpen(!openRef.current)
|
||||
}, [setOpen])
|
||||
|
||||
const [show, setShow] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement={placement || 'bottom-end'}
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: -4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<div>
|
||||
<ActionButton size='l' className={cn(open && 'bg-state-base-hover')}>
|
||||
<RiEqualizer2Line className='w-[18px] h-[18px]' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-50'>
|
||||
<div className='w-[224px] bg-components-panel-bg-blur backdrop-blur-sm rounded-xl border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
<div className='p-1'>
|
||||
{data?.privacy_policy && (
|
||||
<a href={data.privacy_policy} target='_blank' className='flex items-center px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>
|
||||
<span className='grow'>{t('share.chat.privacyPolicyMiddle')}</span>
|
||||
</a>
|
||||
)}
|
||||
<div
|
||||
onClick={() => {
|
||||
handleTrigger()
|
||||
setShow(true)
|
||||
}}
|
||||
className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'
|
||||
>{t('common.userProfile.about')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
{show && (
|
||||
<InfoModal
|
||||
isShow={show}
|
||||
onClose={() => {
|
||||
setShow(false)
|
||||
}}
|
||||
data={data}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(MenuDropdown)
|
||||
@@ -1,22 +1,18 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import {
|
||||
RiSparklingFill,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const StarIcon = (
|
||||
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.50033 48.3337V36.667M7.50033 13.3337V1.66699M1.66699 7.50033H13.3337M1.66699 42.5003H13.3337M27.3337 4.00032L23.2872 14.521C22.6292 16.2319 22.3002 17.0873 21.7886 17.8069C21.3351 18.4446 20.7779 19.0018 20.1402 19.4552C19.4206 19.9669 18.5652 20.2959 16.8543 20.9539L6.33366 25.0003L16.8543 29.0467C18.5652 29.7048 19.4206 30.0338 20.1402 30.5454C20.7779 30.9989 21.3351 31.5561 21.7886 32.1938C22.3002 32.9133 22.6292 33.7688 23.2872 35.4796L27.3337 46.0003L31.3801 35.4796C32.0381 33.7688 32.3671 32.9133 32.8788 32.1938C33.3322 31.5561 33.8894 30.9989 34.5271 30.5454C35.2467 30.0338 36.1021 29.7048 37.813 29.0467L48.3337 25.0003L37.813 20.9539C36.1021 20.2959 35.2467 19.9669 34.5271 19.4552C33.8894 19.0018 33.3322 18.4446 32.8788 17.8069C32.3671 17.0873 32.0381 16.2319 31.3801 14.521L27.3337 4.00032Z" stroke="#EAECF0" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
|
||||
)
|
||||
|
||||
export type INoDataProps = {}
|
||||
const NoData: FC<INoDataProps> = () => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='flex flex-col h-full w-full justify-center items-center'>
|
||||
{StarIcon}
|
||||
<RiSparklingFill className='w-12 h-12 text-text-empty-state-icon' />
|
||||
<div
|
||||
className='mt-3 text-gray-300 text-xs leading-3'
|
||||
className='mt-2 text-text-quaternary system-sm-regular'
|
||||
>
|
||||
{t('share.generation.noData')}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,6 @@ import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { t } from 'i18next'
|
||||
import produce from 'immer'
|
||||
import cn from '@/utils/classnames'
|
||||
import TextGenerationRes from '@/app/components/app/text-generate/item'
|
||||
import NoData from '@/app/components/share/text-generation/no-data'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
@@ -13,7 +12,6 @@ import type { FeedbackType } from '@/app/components/base/chat/chat/type'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import type { PromptConfig } from '@/models/debug'
|
||||
import type { InstalledApp } from '@/models/explore'
|
||||
import type { ModerationService } from '@/models/common'
|
||||
import { TransferMethod, type VisionFile, type VisionSettings } from '@/types/app'
|
||||
import { NodeRunningStatus, WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import type { WorkflowProcess } from '@/app/components/base/chat/types'
|
||||
@@ -24,7 +22,7 @@ import {
|
||||
getFilesInLogs,
|
||||
} from '@/app/components/base/file-uploader/utils'
|
||||
|
||||
export interface IResultProps {
|
||||
export type IResultProps = {
|
||||
isWorkflow: boolean
|
||||
isCallBatchAPI: boolean
|
||||
isPC: boolean
|
||||
@@ -43,11 +41,10 @@ export interface IResultProps {
|
||||
handleSaveMessage: (messageId: string) => void
|
||||
taskId?: number
|
||||
onCompleted: (completionRes: string, taskId?: number, success?: boolean) => void
|
||||
enableModeration?: boolean
|
||||
moderationService?: (text: string) => ReturnType<ModerationService>
|
||||
visionConfig: VisionSettings
|
||||
completionFiles: VisionFile[]
|
||||
siteInfo: SiteInfo | null
|
||||
onRunStart: () => void
|
||||
}
|
||||
|
||||
const Result: FC<IResultProps> = ({
|
||||
@@ -72,6 +69,7 @@ const Result: FC<IResultProps> = ({
|
||||
visionConfig,
|
||||
completionFiles,
|
||||
siteInfo,
|
||||
onRunStart,
|
||||
}) => {
|
||||
const [isResponding, { setTrue: setRespondingTrue, setFalse: setRespondingFalse }] = useBoolean(false)
|
||||
useEffect(() => {
|
||||
@@ -183,8 +181,10 @@ const Result: FC<IResultProps> = ({
|
||||
let res: string[] = []
|
||||
let tempMessageId = ''
|
||||
|
||||
if (!isPC)
|
||||
if (!isPC) {
|
||||
onShowRes()
|
||||
onRunStart()
|
||||
}
|
||||
|
||||
setRespondingTrue()
|
||||
let isEnd = false
|
||||
@@ -375,7 +375,6 @@ const Result: FC<IResultProps> = ({
|
||||
<TextGenerationRes
|
||||
isWorkflow={isWorkflow}
|
||||
workflowProcessData={workflowProcessData}
|
||||
className='mt-3'
|
||||
isError={isError}
|
||||
onRetry={handleSend}
|
||||
content={completionRes}
|
||||
@@ -398,7 +397,7 @@ const Result: FC<IResultProps> = ({
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={cn(isNoData && !isCallBatchAPI && 'h-full')}>
|
||||
<>
|
||||
{!isCallBatchAPI && !isWorkflow && (
|
||||
(isResponding && !completionRes)
|
||||
? (
|
||||
@@ -414,25 +413,19 @@ const Result: FC<IResultProps> = ({
|
||||
</>
|
||||
)
|
||||
)}
|
||||
{
|
||||
!isCallBatchAPI && isWorkflow && (
|
||||
(isResponding && !workflowProcessData)
|
||||
? (
|
||||
<div className='flex h-full w-full justify-center items-center'>
|
||||
<Loading type='area' />
|
||||
</div>
|
||||
)
|
||||
: !workflowProcessData
|
||||
? <NoData />
|
||||
: renderTextGenerationRes()
|
||||
)
|
||||
}
|
||||
{isCallBatchAPI && (
|
||||
<div className='mt-2'>
|
||||
{renderTextGenerationRes()}
|
||||
</div>
|
||||
{!isCallBatchAPI && isWorkflow && (
|
||||
(isResponding && !workflowProcessData)
|
||||
? (
|
||||
<div className='flex h-full w-full justify-center items-center'>
|
||||
<Loading type='area' />
|
||||
</div>
|
||||
)
|
||||
: !workflowProcessData
|
||||
? <NoData />
|
||||
: renderTextGenerationRes()
|
||||
)}
|
||||
</div>
|
||||
{isCallBatchAPI && renderTextGenerationRes()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(Result)
|
||||
|
||||
@@ -27,17 +27,17 @@ const CSVDownload: FC<ICSVDownloadProps> = ({
|
||||
|
||||
return (
|
||||
<div className='mt-6'>
|
||||
<div className='text-sm text-gray-900 font-medium'>{t('share.generation.csvStructureTitle')}</div>
|
||||
<div className='system-sm-medium text-text-primary'>{t('share.generation.csvStructureTitle')}</div>
|
||||
<div className='mt-2 max-h-[500px] overflow-auto'>
|
||||
<table className='w-full border-separate border-spacing-0 border border-gray-200 rounded-lg text-xs'>
|
||||
<thead className='text-gray-500'>
|
||||
<table className='table-fixed w-full border-separate border-spacing-0 border border-divider-regular rounded-lg text-xs'>
|
||||
<thead className='text-text-tertiary'>
|
||||
<tr>
|
||||
{addQueryContentVars.map((item, i) => (
|
||||
<td key={i} className='h-9 pl-4 border-b border-gray-200'>{item.name}</td>
|
||||
<td key={i} className='h-9 pl-3 pr-2 border-b border-divider-regular'>{item.name}</td>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className='text-gray-300'>
|
||||
<tbody className='text-text-secondary'>
|
||||
<tr>
|
||||
{addQueryContentVars.map((item, i) => (
|
||||
<td key={i} className='h-9 pl-4'>{item.name} {t('share.generation.field')}</td>
|
||||
@@ -58,7 +58,7 @@ const CSVDownload: FC<ICSVDownloadProps> = ({
|
||||
template,
|
||||
]}
|
||||
>
|
||||
<div className='flex items-center h-[18px] space-x-1 text-[#155EEF] text-xs font-medium'>
|
||||
<div className='flex items-center h-[18px] space-x-1 text-text-accent system-xs-medium'>
|
||||
<DownloadIcon className='w-3 h-3' />
|
||||
<span>{t('share.generation.downloadTemplate')}</span>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
useCSVReader,
|
||||
} from 'react-papaparse'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import s from './style.module.css'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Csv as CSVIcon } from '@/app/components/base/icons/src/public/files'
|
||||
|
||||
@@ -41,7 +40,11 @@ const CSVReader: FC<Props> = ({
|
||||
<>
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={cn(s.zone, zoneHover && s.zoneHover, acceptedFile ? 'px-6' : 'justify-center border-dashed text-gray-500')}
|
||||
className={cn(
|
||||
'flex items-center h-20 rounded-xl bg-components-dropzone-bg border border-dashed border-components-dropzone-border system-sm-regular',
|
||||
acceptedFile && 'px-6 bg-components-panel-on-panel-item-bg border-solid border-components-panel-border hover:bg-components-panel-on-panel-item-bg-hover hover:border-components-panel-bg-blur',
|
||||
zoneHover && 'bg-components-dropzone-bg-accent border border-components-dropzone-border-accent',
|
||||
)}
|
||||
>
|
||||
{
|
||||
acceptedFile
|
||||
@@ -49,15 +52,15 @@ const CSVReader: FC<Props> = ({
|
||||
<div className='w-full flex items-center space-x-2'>
|
||||
<CSVIcon className="shrink-0" />
|
||||
<div className='flex w-0 grow'>
|
||||
<span className='max-w-[calc(100%_-_30px)] text-ellipsis whitespace-nowrap overflow-hidden text-gray-800'>{acceptedFile.name.replace(/.csv$/, '')}</span>
|
||||
<span className='shrink-0 text-gray-500'>.csv</span>
|
||||
<span className='max-w-[calc(100%_-_30px)] truncate text-text-secondary'>{acceptedFile.name.replace(/.csv$/, '')}</span>
|
||||
<span className='shrink-0 text-text-tertiary'>.csv</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='flex items-center justify-center space-x-2'>
|
||||
<div className='w-full flex items-center justify-center space-x-2'>
|
||||
<CSVIcon className="shrink-0" />
|
||||
<div className='text-gray-500'>{t('share.generation.csvUploadTitle')}<span className='text-primary-400'>{t('share.generation.browse')}</span></div>
|
||||
<div className='text-text-tertiary'>{t('share.generation.csvUploadTitle')}<span className='text-text-accent cursor-pointer'>{t('share.generation.browse')}</span></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
.zone {
|
||||
@apply flex items-center h-20 rounded-xl bg-gray-50 border border-gray-200 cursor-pointer text-sm font-normal;
|
||||
}
|
||||
|
||||
.zoneHover {
|
||||
@apply border-solid bg-gray-100;
|
||||
}
|
||||
|
||||
.info {
|
||||
@apply text-gray-800 text-sm;
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import {
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/solid'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiLoader2Line,
|
||||
RiPlayLargeLine,
|
||||
} from '@remixicon/react'
|
||||
import CSVReader from './csv-reader'
|
||||
import CSVDownload from './csv-download'
|
||||
import cn from '@/utils/classnames'
|
||||
import Button from '@/app/components/base/button'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import cn from '@/utils/classnames'
|
||||
export type IRunBatchProps = {
|
||||
vars: { name: string }[]
|
||||
onSend: (data: string[][]) => void
|
||||
@@ -24,6 +23,8 @@ const RunBatch: FC<IRunBatchProps> = ({
|
||||
isAllFinished,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const media = useBreakpoints()
|
||||
const isPC = media === MediaType.pc
|
||||
|
||||
const [csvData, setCsvData] = React.useState<string[][]>([])
|
||||
const [isParsed, setIsParsed] = React.useState(false)
|
||||
@@ -36,16 +37,15 @@ const RunBatch: FC<IRunBatchProps> = ({
|
||||
const handleSend = () => {
|
||||
onSend(csvData)
|
||||
}
|
||||
const Icon = isAllFinished ? PlayIcon : RiLoader2Line
|
||||
const Icon = isAllFinished ? RiPlayLargeLine : RiLoader2Line
|
||||
return (
|
||||
<div className='pt-4'>
|
||||
<CSVReader onParsed={handleParsed} />
|
||||
<CSVDownload vars={vars} />
|
||||
<div className='mt-4 h-[1px] bg-gray-100'></div>
|
||||
<div className='flex justify-end'>
|
||||
<Button
|
||||
variant="primary"
|
||||
className='mt-4 pl-3 pr-4'
|
||||
className={cn('mt-4 pl-3 pr-4', !isPC && 'grow')}
|
||||
onClick={handleSend}
|
||||
disabled={!isParsed || !isAllFinished}
|
||||
>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { RiDownloadLine } from '@remixicon/react'
|
||||
import {
|
||||
useCSVDownloader,
|
||||
} from 'react-papaparse'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import cn from '@/utils/classnames'
|
||||
import { Download02 as DownloadIcon } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type IResDownloadProps = {
|
||||
isMobile: boolean
|
||||
values: Record<string, string>[]
|
||||
@@ -31,10 +33,17 @@ const ResDownload: FC<IResDownloadProps> = ({
|
||||
}}
|
||||
data={values}
|
||||
>
|
||||
<Button className={cn('space-x-2 bg-white', isMobile ? '!p-0 !w-8 justify-center' : '')}>
|
||||
<DownloadIcon className='w-4 h-4 text-[#155EEF]' />
|
||||
{!isMobile && <span className='text-[#155EEF]'>{t('common.operation.download')}</span>}
|
||||
</Button>
|
||||
{isMobile && (
|
||||
<ActionButton>
|
||||
<RiDownloadLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<Button className={cn('space-x-1')}>
|
||||
<RiDownloadLine className='w-4 h-4' />
|
||||
<span>{t('common.operation.download')}</span>
|
||||
</Button>
|
||||
)}
|
||||
</CSVDownloader>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,18 +2,21 @@ import type { FC, FormEvent } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/solid'
|
||||
RiPlayLargeLine,
|
||||
} from '@remixicon/react'
|
||||
import Select from '@/app/components/base/select'
|
||||
import type { SiteInfo } from '@/models/share'
|
||||
import type { PromptConfig } from '@/models/debug'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import Input from '@/app/components/base/input'
|
||||
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
|
||||
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
|
||||
import type { VisionFile, VisionSettings } from '@/types/app'
|
||||
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
|
||||
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type IRunOnceProps = {
|
||||
siteInfo: SiteInfo
|
||||
@@ -35,6 +38,8 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
onVisionFilesChange,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const media = useBreakpoints()
|
||||
const isPC = media === MediaType.pc
|
||||
|
||||
const onClear = () => {
|
||||
const newInputs: Record<string, any> = {}
|
||||
@@ -61,8 +66,8 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
<form onSubmit={onSubmit}>
|
||||
{promptConfig.prompt_variables.map(item => (
|
||||
<div className='w-full mt-4' key={item.key}>
|
||||
<label className='text-gray-900 text-sm font-medium'>{item.name}</label>
|
||||
<div className='mt-2'>
|
||||
<label className='h-6 flex items-center text-text-secondary system-md-semibold'>{item.name}</label>
|
||||
<div className='mt-1'>
|
||||
{item.type === 'select' && (
|
||||
<Select
|
||||
className='w-full'
|
||||
@@ -70,13 +75,11 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
onSelect={(i) => { handleInputsChange({ ...inputsRef.current, [item.key]: i.value }) }}
|
||||
items={(item.options || []).map(i => ({ name: i, value: i }))}
|
||||
allowSearch={false}
|
||||
bgClassName='bg-gray-50'
|
||||
/>
|
||||
)}
|
||||
{item.type === 'string' && (
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
className="block w-full p-2 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 sm:text-xs focus:ring-blue-500 focus:border-blue-500 "
|
||||
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
|
||||
value={inputs[item.key]}
|
||||
onChange={(e) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
|
||||
@@ -92,9 +95,8 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
/>
|
||||
)}
|
||||
{item.type === 'number' && (
|
||||
<input
|
||||
<Input
|
||||
type="number"
|
||||
className="block w-full p-2 text-gray-900 border border-gray-300 rounded-lg bg-gray-50 sm:text-xs focus:ring-blue-500 focus:border-blue-500 "
|
||||
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
|
||||
value={inputs[item.key]}
|
||||
onChange={(e) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
|
||||
@@ -124,8 +126,8 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
{
|
||||
visionConfig?.enabled && (
|
||||
<div className="w-full mt-4">
|
||||
<div className="text-gray-900 text-sm font-medium">{t('common.imageUploader.imageUpload')}</div>
|
||||
<div className='mt-2'>
|
||||
<div className="h-6 flex items-center text-text-secondary system-md-semibold">{t('common.imageUploader.imageUpload')}</div>
|
||||
<div className='mt-1'>
|
||||
<TextGenerationImageUploader
|
||||
settings={visionConfig}
|
||||
onFilesChange={files => onVisionFilesChange(files.filter(file => file.progress !== -1).map(fileItem => ({
|
||||
@@ -139,11 +141,8 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{promptConfig.prompt_variables.length > 0 && (
|
||||
<div className='mt-4 h-[1px] bg-gray-100'></div>
|
||||
)}
|
||||
<div className='w-full mt-4'>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className='w-full mt-6 mb-3'>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Button
|
||||
onClick={onClear}
|
||||
disabled={false}
|
||||
@@ -151,11 +150,12 @@ const RunOnce: FC<IRunOnceProps> = ({
|
||||
<span className='text-[13px]'>{t('common.operation.clear')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
className={cn(!isPC && 'grow')}
|
||||
type='submit'
|
||||
variant="primary"
|
||||
disabled={false}
|
||||
>
|
||||
<PlayIcon className="shrink-0 w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<RiPlayLargeLine className="shrink-0 w-4 h-4 mr-1" aria-hidden="true" />
|
||||
<span className='text-[13px]'>{t('share.generation.run')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
.installedApp {
|
||||
height: 100%;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
}
|
||||
|
||||
.starIcon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url(./icons/star.svg) center center no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
Reference in New Issue
Block a user