mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 09:28:04 +08:00
merge main
This commit is contained in:
@ -25,6 +25,7 @@ import { fetchAppDetail, fetchAppSSO } from '@/service/apps'
|
||||
import AppContext, { useAppContext } from '@/context/app-context'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import type { App } from '@/types/app'
|
||||
|
||||
export type IAppDetailLayoutProps = {
|
||||
children: React.ReactNode
|
||||
@ -41,12 +42,14 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
const pathname = usePathname()
|
||||
const media = useBreakpoints()
|
||||
const isMobile = media === MediaType.mobile
|
||||
const { isCurrentWorkspaceEditor } = useAppContext()
|
||||
const { isCurrentWorkspaceEditor, isLoadingCurrentWorkspace } = useAppContext()
|
||||
const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({
|
||||
appDetail: state.appDetail,
|
||||
setAppDetail: state.setAppDetail,
|
||||
setAppSiderbarExpand: state.setAppSiderbarExpand,
|
||||
})))
|
||||
const [isLoadingAppDetail, setIsLoadingAppDetail] = useState(false)
|
||||
const [appDetailRes, setAppDetailRes] = useState<App | null>(null)
|
||||
const [navigation, setNavigation] = useState<Array<{
|
||||
name: string
|
||||
href: string
|
||||
@ -107,33 +110,43 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
setAppDetail()
|
||||
setIsLoadingAppDetail(true)
|
||||
fetchAppDetail({ url: '/apps', id: appId }).then((res) => {
|
||||
// redirection
|
||||
const canIEditApp = isCurrentWorkspaceEditor
|
||||
if (!canIEditApp && (pathname.endsWith('configuration') || pathname.endsWith('workflow') || pathname.endsWith('logs'))) {
|
||||
router.replace(`/app/${appId}/overview`)
|
||||
return
|
||||
}
|
||||
if ((res.mode === 'workflow' || res.mode === 'advanced-chat') && (pathname).endsWith('configuration')) {
|
||||
router.replace(`/app/${appId}/workflow`)
|
||||
}
|
||||
else if ((res.mode !== 'workflow' && res.mode !== 'advanced-chat') && (pathname).endsWith('workflow')) {
|
||||
router.replace(`/app/${appId}/configuration`)
|
||||
}
|
||||
else {
|
||||
setAppDetail({ ...res, enable_sso: false })
|
||||
setNavigation(getNavigations(appId, isCurrentWorkspaceEditor, res.mode))
|
||||
if (systemFeatures.enable_web_sso_switch_component && canIEditApp) {
|
||||
fetchAppSSO({ appId }).then((ssoRes) => {
|
||||
setAppDetail({ ...res, enable_sso: ssoRes.enabled })
|
||||
})
|
||||
}
|
||||
}
|
||||
setAppDetailRes(res)
|
||||
}).catch((e: any) => {
|
||||
if (e.status === 404)
|
||||
router.replace('/apps')
|
||||
}).finally(() => {
|
||||
setIsLoadingAppDetail(false)
|
||||
})
|
||||
}, [appId, isCurrentWorkspaceEditor, systemFeatures, getNavigations, pathname, router, setAppDetail])
|
||||
}, [appId, router, setAppDetail])
|
||||
|
||||
useEffect(() => {
|
||||
if (!appDetailRes || isLoadingCurrentWorkspace || isLoadingAppDetail)
|
||||
return
|
||||
const res = appDetailRes
|
||||
// redirection
|
||||
const canIEditApp = isCurrentWorkspaceEditor
|
||||
if (!canIEditApp && (pathname.endsWith('configuration') || pathname.endsWith('workflow') || pathname.endsWith('logs'))) {
|
||||
router.replace(`/app/${appId}/overview`)
|
||||
return
|
||||
}
|
||||
if ((res.mode === 'workflow' || res.mode === 'advanced-chat') && (pathname).endsWith('configuration')) {
|
||||
router.replace(`/app/${appId}/workflow`)
|
||||
}
|
||||
else if ((res.mode !== 'workflow' && res.mode !== 'advanced-chat') && (pathname).endsWith('workflow')) {
|
||||
router.replace(`/app/${appId}/configuration`)
|
||||
}
|
||||
else {
|
||||
setAppDetail({ ...res, enable_sso: false })
|
||||
setNavigation(getNavigations(appId, isCurrentWorkspaceEditor, res.mode))
|
||||
if (systemFeatures.enable_web_sso_switch_component && canIEditApp) {
|
||||
fetchAppSSO({ appId }).then((ssoRes) => {
|
||||
setAppDetail({ ...res, enable_sso: ssoRes.enabled })
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [appDetailRes, appId, getNavigations, isCurrentWorkspaceEditor, isLoadingAppDetail, isLoadingCurrentWorkspace, pathname, router, setAppDetail, systemFeatures.enable_web_sso_switch_component])
|
||||
|
||||
useUnmount(() => {
|
||||
setAppDetail()
|
||||
|
||||
@ -18,10 +18,10 @@ import { IS_CE_EDITION } from '@/config'
|
||||
import Input from '@/app/components/base/input'
|
||||
|
||||
const titleClassName = `
|
||||
text-sm font-medium text-gray-900
|
||||
system-sm-semibold text-text-secondary
|
||||
`
|
||||
const descriptionClassName = `
|
||||
mt-1 text-xs font-normal text-gray-500
|
||||
mt-1 body-xs-regular text-text-tertiary
|
||||
`
|
||||
|
||||
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||
@ -122,7 +122,7 @@ export default function AccountPage() {
|
||||
<div className='mr-3'>
|
||||
<AppIcon size='tiny' />
|
||||
</div>
|
||||
<div className='mt-[3px] text-xs font-medium text-gray-700 leading-[18px]'>{item.name}</div>
|
||||
<div className='mt-[3px] system-sm-medium text-text-secondary'>{item.name}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -130,7 +130,7 @@ export default function AccountPage() {
|
||||
return (
|
||||
<>
|
||||
<div className='pt-2 pb-3'>
|
||||
<h4 className='title-2xl-semi-bold text-primary'>{t('common.account.myAccount')}</h4>
|
||||
<h4 className='title-2xl-semi-bold text-text-primary'>{t('common.account.myAccount')}</h4>
|
||||
</div>
|
||||
<div className='mb-8 p-6 rounded-xl flex items-center bg-gradient-to-r from-background-gradient-bg-fill-chat-bg-2 to-background-gradient-bg-fill-chat-bg-1'>
|
||||
<Avatar name={userProfile.name} size={64} />
|
||||
@ -142,10 +142,10 @@ export default function AccountPage() {
|
||||
<div className='mb-8'>
|
||||
<div className={titleClassName}>{t('common.account.name')}</div>
|
||||
<div className='flex items-center justify-between gap-2 w-full mt-2'>
|
||||
<div className='flex-1 bg-gray-100 rounded-md p-2 system-sm-regular text-components-input-text-filled '>
|
||||
<div className='flex-1 bg-components-input-bg-normal rounded-lg p-2 system-sm-regular text-components-input-text-filled '>
|
||||
<span className='pl-1'>{userProfile.name}</span>
|
||||
</div>
|
||||
<div className=' bg-gray-100 rounded-md py-2 px-3 cursor-pointer system-sm-medium text-components-input-text-filled' onClick={handleEditName}>
|
||||
<div className='bg-components-button-tertiary-bg rounded-lg py-2 px-3 cursor-pointer system-sm-medium text-components-button-tertiary-text' onClick={handleEditName}>
|
||||
{t('common.operation.edit')}
|
||||
</div>
|
||||
</div>
|
||||
@ -153,7 +153,7 @@ export default function AccountPage() {
|
||||
<div className='mb-8'>
|
||||
<div className={titleClassName}>{t('common.account.email')}</div>
|
||||
<div className='flex items-center justify-between gap-2 w-full mt-2'>
|
||||
<div className='flex-1 bg-gray-100 rounded-md p-2 system-sm-regular text-components-input-text-filled '>
|
||||
<div className='flex-1 bg-components-input-bg-normal rounded-lg p-2 system-sm-regular text-components-input-text-filled '>
|
||||
<span className='pl-1'>{userProfile.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -162,14 +162,14 @@ export default function AccountPage() {
|
||||
systemFeatures.enable_email_password_login && (
|
||||
<div className='mb-8 flex justify-between gap-2'>
|
||||
<div>
|
||||
<div className='mb-1 text-sm font-medium text-gray-900'>{t('common.account.password')}</div>
|
||||
<div className='mb-2 text-xs text-gray-500'>{t('common.account.passwordTip')}</div>
|
||||
<div className='mb-1 system-sm-semibold text-text-secondary'>{t('common.account.password')}</div>
|
||||
<div className='mb-2 body-xs-regular text-text-tertiary'>{t('common.account.passwordTip')}</div>
|
||||
</div>
|
||||
<Button onClick={() => setEditPasswordModalVisible(true)}>{userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className='mb-6 border-[0.5px] border-gray-100' />
|
||||
<div className='mb-6 border-[1px] border-divider-subtle' />
|
||||
<div className='mb-8'>
|
||||
<div className={titleClassName}>{t('common.account.langGeniusAccount')}</div>
|
||||
<div className={descriptionClassName}>{t('common.account.langGeniusAccountTip')}</div>
|
||||
@ -181,7 +181,7 @@ export default function AccountPage() {
|
||||
wrapperClassName='mt-2'
|
||||
/>
|
||||
)}
|
||||
{!IS_CE_EDITION && <Button className='mt-2 text-[#D92D20]' onClick={() => setShowDeleteAccountModal(true)}>{t('common.account.delete')}</Button>}
|
||||
{!IS_CE_EDITION && <Button className='mt-2 text-components-button-destructive-secondary-text' onClick={() => setShowDeleteAccountModal(true)}>{t('common.account.delete')}</Button>}
|
||||
</div>
|
||||
{
|
||||
editNameModalVisible && (
|
||||
@ -190,7 +190,7 @@ export default function AccountPage() {
|
||||
onClose={() => setEditNameModalVisible(false)}
|
||||
className={s.modal}
|
||||
>
|
||||
<div className='mb-6 text-lg font-medium text-gray-900'>{t('common.account.editName')}</div>
|
||||
<div className='mb-6 title-2xl-semi-bold text-text-primary'>{t('common.account.editName')}</div>
|
||||
<div className={titleClassName}>{t('common.account.name')}</div>
|
||||
<Input className='mt-2'
|
||||
value={editName}
|
||||
@ -219,7 +219,7 @@ export default function AccountPage() {
|
||||
}}
|
||||
className={s.modal}
|
||||
>
|
||||
<div className='mb-6 text-lg font-medium text-gray-900'>{userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}</div>
|
||||
<div className='mb-6 title-2xl-semi-bold text-text-primary'>{userProfile.is_password_set ? t('common.account.resetPassword') : t('common.account.setPassword')}</div>
|
||||
{userProfile.is_password_set && (
|
||||
<>
|
||||
<div className={titleClassName}>{t('common.account.currentPassword')}</div>
|
||||
@ -242,7 +242,7 @@ export default function AccountPage() {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className='mt-8 text-sm font-medium text-gray-900'>
|
||||
<div className='mt-8 system-sm-semibold text-text-secondary'>
|
||||
{userProfile.is_password_set ? t('common.account.newPassword') : t('common.account.password')}
|
||||
</div>
|
||||
<div className='relative mt-2'>
|
||||
@ -261,7 +261,7 @@ export default function AccountPage() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='mt-8 text-sm font-medium text-gray-900'>{t('common.account.confirmPassword')}</div>
|
||||
<div className='mt-8 system-sm-semibold text-text-secondary'>{t('common.account.confirmPassword')}</div>
|
||||
<div className='relative mt-2'>
|
||||
<Input
|
||||
type={showConfirmPassword ? 'text' : 'password'}
|
||||
@ -305,13 +305,13 @@ export default function AccountPage() {
|
||||
title={t('common.account.delete')}
|
||||
content={
|
||||
<>
|
||||
<div className='my-1 text-[#D92D20] text-sm leading-5'>
|
||||
<div className='my-1 text-text-destructive body-md-medium'>
|
||||
{t('common.account.deleteTip')}
|
||||
</div>
|
||||
<div className='mt-3 text-sm leading-5'>
|
||||
<span>{t('common.account.deleteConfirmTip')}</span>
|
||||
<a
|
||||
className='text-primary-600 cursor'
|
||||
className='text-text-accent cursor'
|
||||
href={`mailto:support@dify.ai?subject=Delete Account Request&body=Delete Account: ${userProfile.email}`}
|
||||
target='_blank'
|
||||
rel='noreferrer noopener'
|
||||
@ -323,7 +323,7 @@ export default function AccountPage() {
|
||||
support@dify.ai
|
||||
</a>
|
||||
</div>
|
||||
<div className='my-2 px-3 py-2 rounded-lg bg-gray-100 text-sm font-medium leading-5 text-gray-800'>{`${t('common.account.delete')}: ${userProfile.email}`}</div>
|
||||
<div className='my-2 px-3 py-2 rounded-lg bg-components-input-bg-active border border-components-input-border-active system-sm-regular text-components-input-text-filled'>{`${t('common.account.delete')}: ${userProfile.email}`}</div>
|
||||
</>
|
||||
}
|
||||
confirmText={t('common.operation.ok') as string}
|
||||
|
||||
@ -40,9 +40,9 @@ export default function AppSelector() {
|
||||
className={`
|
||||
inline-flex items-center
|
||||
rounded-[20px] p-1x text-sm
|
||||
text-gray-700 hover:bg-gray-200
|
||||
text-text-primary
|
||||
mobile:px-1
|
||||
${open && 'bg-gray-200'}
|
||||
${open && 'bg-components-panel-bg-blur'}
|
||||
`}
|
||||
>
|
||||
<Avatar name={userProfile.name} size={32} />
|
||||
@ -60,7 +60,7 @@ export default function AppSelector() {
|
||||
<Menu.Items
|
||||
className="
|
||||
absolute -right-2 -top-1 w-60 max-w-80
|
||||
divide-y divide-gray-100 origin-top-right rounded-lg bg-white
|
||||
divide-y divide-divider-subtle origin-top-right rounded-lg bg-components-panel-bg-blur
|
||||
shadow-lg
|
||||
"
|
||||
>
|
||||
@ -78,10 +78,10 @@ export default function AppSelector() {
|
||||
<Menu.Item>
|
||||
<div className='p-1' onClick={() => handleLogout()}>
|
||||
<div
|
||||
className='flex items-center justify-start h-9 px-3 rounded-lg cursor-pointer group hover:bg-gray-50'
|
||||
className='flex items-center justify-start h-9 px-3 rounded-lg cursor-pointer group hover:bg-state-base-hover'
|
||||
>
|
||||
<LogOut01 className='w-4 h-4 text-gray-500 flex mr-1' />
|
||||
<div className='font-normal text-[14px] text-gray-700'>{t('common.userProfile.logout')}</div>
|
||||
<LogOut01 className='w-4 h-4 text-text-tertiary flex mr-1' />
|
||||
<div className='font-normal text-[14px] text-text-secondary'>{t('common.userProfile.logout')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
|
||||
@ -21,7 +21,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
|
||||
<HeaderWrapper>
|
||||
<Header />
|
||||
</HeaderWrapper>
|
||||
<div className='relative flex flex-col overflow-y-auto bg-white shrink-0 h-0 grow'>
|
||||
<div className='relative flex flex-col overflow-y-auto bg-components-panel-bg shrink-0 h-0 grow'>
|
||||
{children}
|
||||
</div>
|
||||
</ModalContextProvider>
|
||||
|
||||
@ -25,10 +25,10 @@ const AppCard = ({
|
||||
<div className='relative shrink-0'>
|
||||
<AppIcon
|
||||
size='large'
|
||||
iconType={app.app.icon_type}
|
||||
icon={app.app.icon}
|
||||
background={app.app.icon_background}
|
||||
imageUrl={app.app.icon_url}
|
||||
iconType={appBasicInfo.icon_type}
|
||||
icon={appBasicInfo.icon}
|
||||
background={appBasicInfo.icon_background}
|
||||
imageUrl={appBasicInfo.icon_url}
|
||||
/>
|
||||
<AppTypeIcon wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 rounded-[4px] border border-divider-regular outline outline-components-panel-on-panel-item-bg'
|
||||
className='w-3 h-3' type={appBasicInfo.mode} />
|
||||
|
||||
@ -16,6 +16,7 @@ import { createContext, useContext } from 'use-context-selector'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ChatItemInTree } from '../../base/chat/types'
|
||||
import Indicator from '../../header/indicator'
|
||||
import VarPanel from './var-panel'
|
||||
import type { FeedbackFunc, FeedbackType, IChatItem, SubmitAnnotationFunc } from '@/app/components/base/chat/chat/type'
|
||||
import type { Annotation, ChatConversationGeneralDetail, ChatConversationsResponse, ChatMessage, ChatMessagesRequest, CompletionConversationGeneralDetail, CompletionConversationsResponse, LogAnnotation } from '@/models/log'
|
||||
@ -58,6 +59,12 @@ type IDrawerContext = {
|
||||
appDetail?: App
|
||||
}
|
||||
|
||||
type StatusCount = {
|
||||
success: number
|
||||
failed: number
|
||||
partial_success: number
|
||||
}
|
||||
|
||||
const DrawerContext = createContext<IDrawerContext>({} as IDrawerContext)
|
||||
|
||||
/**
|
||||
@ -72,6 +79,33 @@ const HandThumbIconWithCount: FC<{ count: number; iconType: 'up' | 'down' }> = (
|
||||
</div>
|
||||
}
|
||||
|
||||
const statusTdRender = (statusCount: StatusCount) => {
|
||||
if (statusCount.partial_success + statusCount.failed === 0) {
|
||||
return (
|
||||
<div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'>
|
||||
<Indicator color={'green'} />
|
||||
<span className='text-util-colors-green-green-600'>Success</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
else if (statusCount.failed === 0) {
|
||||
return (
|
||||
<div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'>
|
||||
<Indicator color={'green'} />
|
||||
<span className='text-util-colors-green-green-600'>Partial Success</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'>
|
||||
<Indicator color={'red'} />
|
||||
<span className='text-util-colors-red-red-600'>{statusCount.failed} {`${statusCount.failed > 1 ? 'Failures' : 'Failure'}`}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const getFormattedChatList = (messages: ChatMessage[], conversationId: string, timezone: string, format: string) => {
|
||||
const newChatList: IChatItem[] = []
|
||||
messages.forEach((item: ChatMessage) => {
|
||||
@ -497,8 +531,8 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Text App Conversation Detail Component
|
||||
*/
|
||||
* Text App Conversation Detail Component
|
||||
*/
|
||||
const CompletionConversationDetailComp: FC<{ appId?: string; conversationId?: string }> = ({ appId, conversationId }) => {
|
||||
// Text Generator App Session Details Including Message List
|
||||
const detailParams = ({ url: `/apps/${appId}/completion-conversations/${conversationId}` })
|
||||
@ -543,8 +577,8 @@ const CompletionConversationDetailComp: FC<{ appId?: string; conversationId?: st
|
||||
}
|
||||
|
||||
/**
|
||||
* Chat App Conversation Detail Component
|
||||
*/
|
||||
* Chat App Conversation Detail Component
|
||||
*/
|
||||
const ChatConversationDetailComp: FC<{ appId?: string; conversationId?: string }> = ({ appId, conversationId }) => {
|
||||
const detailParams = { url: `/apps/${appId}/chat-conversations/${conversationId}` }
|
||||
const { data: conversationDetail } = useSWR(() => (appId && conversationId) ? detailParams : null, fetchChatConversationDetail)
|
||||
@ -586,8 +620,8 @@ const ChatConversationDetailComp: FC<{ appId?: string; conversationId?: string }
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversation list component including basic information
|
||||
*/
|
||||
* Conversation list component including basic information
|
||||
*/
|
||||
const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh }) => {
|
||||
const { t } = useTranslation()
|
||||
const { formatTime } = useTimestamp()
|
||||
@ -598,6 +632,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
||||
const [showDrawer, setShowDrawer] = useState<boolean>(false) // Whether to display the chat details drawer
|
||||
const [currentConversation, setCurrentConversation] = useState<ChatConversationGeneralDetail | CompletionConversationGeneralDetail | undefined>() // Currently selected conversation
|
||||
const isChatMode = appDetail.mode !== 'completion' // Whether the app is a chat app
|
||||
const isChatflow = appDetail.mode === 'advanced-chat' // Whether the app is a chatflow app
|
||||
const { setShowPromptLogModal, setShowAgentLogModal } = useAppStore(useShallow(state => ({
|
||||
setShowPromptLogModal: state.setShowPromptLogModal,
|
||||
setShowAgentLogModal: state.setShowAgentLogModal,
|
||||
@ -640,6 +675,7 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
||||
<td className='pl-2 pr-1 w-5 rounded-l-lg bg-background-section-burn whitespace-nowrap'></td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{isChatMode ? t('appLog.table.header.summary') : t('appLog.table.header.input')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.endUser')}</td>
|
||||
{isChatflow && <td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.status')}</td>}
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{isChatMode ? t('appLog.table.header.messageCount') : t('appLog.table.header.output')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.userRate')}</td>
|
||||
<td className='pl-3 py-1.5 bg-background-section-burn whitespace-nowrap'>{t('appLog.table.header.adminRate')}</td>
|
||||
@ -670,6 +706,9 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
|
||||
{renderTdValue(leftValue || t('appLog.table.empty.noChat'), !leftValue, isChatMode && log.annotated)}
|
||||
</td>
|
||||
<td className='p-3 pr-2'>{renderTdValue(endUser || defaultValue, !endUser)}</td>
|
||||
{isChatflow && <td className='p-3 pr-2 w-[160px]' style={{ maxWidth: isChatMode ? 300 : 200 }}>
|
||||
{statusTdRender(log.status_count)}
|
||||
</td>}
|
||||
<td className='p-3 pr-2' style={{ maxWidth: isChatMode ? 100 : 200 }}>
|
||||
{renderTdValue(rightValue === 0 ? 0 : (rightValue || t('appLog.table.empty.noOutput')), !rightValue, !isChatMode && !!log.annotation?.content, log.annotation)}
|
||||
</td>
|
||||
|
||||
@ -63,6 +63,14 @@ const WorkflowAppLogList: FC<ILogs> = ({ logs, appDetail, onRefresh }) => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (status === 'partial-succeeded') {
|
||||
return (
|
||||
<div className='inline-flex items-center gap-1 system-xs-semibold-uppercase'>
|
||||
<Indicator color={'green'} />
|
||||
<span className='text-util-colors-green-green-600'>Partial Success</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const onCloseDrawer = () => {
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
import type { FC } from 'react'
|
||||
import { init } from 'emoji-mart'
|
||||
import data from '@emoji-mart/data'
|
||||
import Image from 'next/image'
|
||||
import { cva } from 'class-variance-authority'
|
||||
import type { AppIconType } from '@/types/app'
|
||||
import classNames from '@/utils/classnames'
|
||||
@ -62,7 +61,8 @@ const AppIcon: FC<AppIconProps> = ({
|
||||
onClick={onClick}
|
||||
>
|
||||
{isValidImageIcon
|
||||
? <Image src={imageUrl} className="w-full h-full" alt="app icon" />
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
? <img src={imageUrl} className="w-full h-full" alt="app icon" />
|
||||
: (innerIcon || ((icon && icon !== '') ? <em-emoji id={icon} /> : <em-emoji id='🤖' />))
|
||||
}
|
||||
</span>
|
||||
|
||||
@ -64,6 +64,12 @@ const WorkflowProcessItem = ({
|
||||
setShowMessageLogModal(true)
|
||||
}, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal])
|
||||
|
||||
const showRetryDetail = useCallback(() => {
|
||||
setCurrentLogItem(item)
|
||||
setCurrentLogModalActiveTab('TRACING')
|
||||
setShowMessageLogModal(true)
|
||||
}, [item, setCurrentLogItem, setCurrentLogModalActiveTab, setShowMessageLogModal])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@ -105,6 +111,7 @@ const WorkflowProcessItem = ({
|
||||
<TracingPanel
|
||||
list={data.tracing}
|
||||
onShowIterationDetail={showIterationDetail}
|
||||
onShowRetryDetail={showRetryDetail}
|
||||
hideNodeInfo={hideInfo}
|
||||
hideNodeProcessDetail={hideProcessDetail}
|
||||
/>
|
||||
|
||||
@ -28,6 +28,7 @@ export type InputProps = {
|
||||
destructive?: boolean
|
||||
wrapperClassName?: string
|
||||
styleCss?: CSSProperties
|
||||
unit?: string
|
||||
} & React.InputHTMLAttributes<HTMLInputElement> & VariantProps<typeof inputVariants>
|
||||
|
||||
const Input = ({
|
||||
@ -43,6 +44,7 @@ const Input = ({
|
||||
value,
|
||||
placeholder,
|
||||
onChange,
|
||||
unit,
|
||||
...props
|
||||
}: InputProps) => {
|
||||
const { t } = useTranslation()
|
||||
@ -80,6 +82,13 @@ const Input = ({
|
||||
{destructive && (
|
||||
<RiErrorWarningLine className='absolute right-2 top-1/2 -translate-y-1/2 w-4 h-4 text-text-destructive-secondary' />
|
||||
)}
|
||||
{
|
||||
unit && (
|
||||
<div className='absolute right-2 top-1/2 -translate-y-1/2 system-sm-regular text-text-tertiary'>
|
||||
{unit}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import { Fragment } from 'react'
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import classNames from '@/utils/classnames'
|
||||
// https://headlessui.com/react/dialog
|
||||
|
||||
@ -39,7 +39,7 @@ export default function Modal({
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-25" />
|
||||
<div className="fixed inset-0 bg-background-overlay-fullscreen" />
|
||||
</Transition.Child>
|
||||
|
||||
<div
|
||||
@ -66,16 +66,16 @@ export default function Modal({
|
||||
)}>
|
||||
{title && <Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium leading-6 text-text-primary"
|
||||
className="title-2xl-semi-bold text-text-primary"
|
||||
>
|
||||
{title}
|
||||
</Dialog.Title>}
|
||||
{description && <Dialog.Description className='text-text-tertiary text-xs font-normal mt-2'>
|
||||
{description && <Dialog.Description className='text-text-secondary body-md-regular mt-2'>
|
||||
{description}
|
||||
</Dialog.Description>}
|
||||
{closable
|
||||
&& <div className='absolute z-10 top-6 right-6 w-5 h-5 rounded-2xl flex items-center justify-center hover:cursor-pointer hover:bg-components-panel-on-panel-item-bg-hover'>
|
||||
<XMarkIcon className='w-4 h-4 text-text-tertiary' onClick={
|
||||
&& <div className='absolute z-10 top-6 right-6 w-5 h-5 rounded-2xl flex items-center justify-center hover:cursor-pointer hover:bg-state-base-hover'>
|
||||
<RiCloseLine className='w-4 h-4 text-text-tertiary' onClick={
|
||||
(e) => {
|
||||
e.stopPropagation()
|
||||
onClose()
|
||||
|
||||
@ -23,6 +23,7 @@ const SearchInput: FC<SearchInputProps> = ({
|
||||
const { t } = useTranslation()
|
||||
const [focus, setFocus] = useState<boolean>(false)
|
||||
const isComposing = useRef<boolean>(false)
|
||||
const [internalValue, setInternalValue] = useState<string>(value)
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
@ -45,16 +46,18 @@ const SearchInput: FC<SearchInputProps> = ({
|
||||
white && '!bg-white hover:!bg-white group-hover:!bg-white placeholder:!text-gray-400',
|
||||
)}
|
||||
placeholder={placeholder || t('common.operation.search')!}
|
||||
value={value}
|
||||
value={internalValue}
|
||||
onChange={(e) => {
|
||||
setInternalValue(e.target.value)
|
||||
if (!isComposing.current)
|
||||
onChange(e.target.value)
|
||||
}}
|
||||
onCompositionStart={() => {
|
||||
isComposing.current = true
|
||||
}}
|
||||
onCompositionEnd={() => {
|
||||
onCompositionEnd={(e) => {
|
||||
isComposing.current = false
|
||||
onChange(e.data)
|
||||
}}
|
||||
onFocus={() => setFocus(true)}
|
||||
onBlur={() => setFocus(false)}
|
||||
@ -63,7 +66,10 @@ const SearchInput: FC<SearchInputProps> = ({
|
||||
{value && (
|
||||
<div
|
||||
className='shrink-0 flex items-center justify-center w-4 h-4 cursor-pointer group/clear'
|
||||
onClick={() => onChange('')}
|
||||
onClick={() => {
|
||||
onChange('')
|
||||
setInternalValue('')
|
||||
}}
|
||||
>
|
||||
<XCircle className='w-3.5 h-3.5 text-gray-400 group-hover/clear:text-gray-600' />
|
||||
</div>
|
||||
|
||||
@ -4,6 +4,7 @@ import React, { Fragment, useEffect, useState } from 'react'
|
||||
import { Combobox, Listbox, Transition } from '@headlessui/react'
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import Badge from '../badge/index'
|
||||
import { RiCheckLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classNames from '@/utils/classnames'
|
||||
import {
|
||||
@ -153,7 +154,7 @@ const Select: FC<ISelectProps> = ({
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700',
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
||||
<RiCheckLine className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
@ -211,7 +212,7 @@ const SimpleSelect: FC<ISelectProps> = ({
|
||||
<div className={classNames('group/simple-select relative h-9', wrapperClassName)}>
|
||||
{renderTrigger && <Listbox.Button className='w-full'>{renderTrigger(selectedItem)}</Listbox.Button>}
|
||||
{!renderTrigger && (
|
||||
<Listbox.Button className={classNames(`flex items-center w-full h-full rounded-lg border-0 bg-gray-100 pl-3 pr-10 sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-gray-200 group-hover/simple-select:bg-state-base-hover-alt ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`, className)}>
|
||||
<Listbox.Button className={classNames(`flex items-center w-full h-full rounded-lg border-0 bg-components-input-bg-normal pl-3 pr-10 sm:text-sm sm:leading-6 focus-visible:outline-none focus-visible:bg-state-base-hover-alt group-hover/simple-select:bg-state-base-hover-alt ${disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`, className)}>
|
||||
<span className={classNames('block truncate text-left system-sm-regular text-components-input-text-filled', !selectedItem?.name && 'text-components-input-text-placeholder')}>{selectedItem?.name ?? localPlaceholder}</span>
|
||||
<span className="absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
{(selectedItem && !notClearable)
|
||||
@ -244,13 +245,13 @@ const SimpleSelect: FC<ISelectProps> = ({
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
|
||||
<Listbox.Options className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg border-gray-200 border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}>
|
||||
<Listbox.Options className={classNames('absolute z-10 mt-1 px-1 max-h-60 w-full overflow-auto rounded-md bg-components-panel-bg-blur py-1 text-base shadow-lg border-components-panel-border border-[0.5px] focus:outline-none sm:text-sm', optionWrapClassName)}>
|
||||
{items.map((item: Item) => (
|
||||
<Listbox.Option
|
||||
key={item.value}
|
||||
className={({ active }) =>
|
||||
classNames(
|
||||
`relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-gray-100 text-gray-700 ${active ? 'bg-gray-100' : ''}`,
|
||||
'relative cursor-pointer select-none py-2 pl-3 pr-9 rounded-lg hover:bg-state-base-hover text-text-secondary',
|
||||
optionClassName,
|
||||
)
|
||||
}
|
||||
@ -266,10 +267,10 @@ const SimpleSelect: FC<ISelectProps> = ({
|
||||
{selected && !hideChecked && (
|
||||
<span
|
||||
className={classNames(
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4 text-gray-700',
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4 text-text-accent',
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
||||
<RiCheckLine className="h-4 w-4" aria-hidden="true" />
|
||||
</span>
|
||||
)}
|
||||
</>)}
|
||||
@ -376,7 +377,7 @@ const PortalSelect: FC<PortalSelectProps> = ({
|
||||
)}
|
||||
</span>
|
||||
{!hideChecked && item.value === value && (
|
||||
<CheckIcon className='shrink-0 h-4 w-4 text-text-accent' />
|
||||
<RiCheckLine className='shrink-0 h-4 w-4 text-text-accent' />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@ -346,6 +346,9 @@ The text generation application offers non-session support and is ideal for tran
|
||||
<Property name='user' type='string' key='user'>
|
||||
User identifier, defined by the developer's rules, must be unique within the application.
|
||||
</Property>
|
||||
<Property name='content' type='string' key='content'>
|
||||
The specific content of message feedback.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
@ -353,7 +356,7 @@ The text generation application offers non-session support and is ideal for tran
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123",\n "content": "message feedback information"\n}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
|
||||
@ -361,7 +364,8 @@ The text generation application offers non-session support and is ideal for tran
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"rating": "like",
|
||||
"user": "abc-123"
|
||||
"user": "abc-123",
|
||||
"content": "message feedback information"
|
||||
}'
|
||||
```
|
||||
|
||||
|
||||
@ -345,6 +345,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<Property name='user' type='string' key='user'>
|
||||
開発者のルールで定義されたユーザー識別子。アプリケーション内で一意である必要があります。
|
||||
</Property>
|
||||
<Property name='content' type='string' key='content'>
|
||||
メッセージのフィードバックです。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### レスポンス
|
||||
@ -352,7 +355,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123",\n "content": "message feedback information"\n}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
|
||||
@ -360,7 +363,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"rating": "like",
|
||||
"user": "abc-123"
|
||||
"user": "abc-123",
|
||||
"content": "message feedback information"
|
||||
}'
|
||||
```
|
||||
|
||||
|
||||
@ -320,6 +320,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<Property name='user' type='string' key='user'>
|
||||
用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
|
||||
</Property>
|
||||
<Property name='content' type='string' key='content'>
|
||||
消息反馈的具体信息。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
@ -327,7 +330,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123",\n "content": "message feedback information"\n}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
|
||||
@ -335,7 +338,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"rating": "like",
|
||||
"user": "abc-123"
|
||||
"user": "abc-123",
|
||||
"content": "message feedback information"
|
||||
}'
|
||||
```
|
||||
|
||||
|
||||
@ -71,6 +71,7 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
- `image` ('JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG')
|
||||
- `audio` ('MP3', 'M4A', 'WAV', 'WEBM', 'AMR')
|
||||
- `video` ('MP4', 'MOV', 'MPEG', 'MPGA')
|
||||
- `custom` (Other file types)
|
||||
- `transfer_method` (string) Transfer method, `remote_url` for image URL / `local_file` for file upload
|
||||
- `url` (string) Image URL (when the transfer method is `remote_url`)
|
||||
- `upload_file_id` (string) Uploaded file ID, which must be obtained by uploading through the File Upload API in advance (when the transfer method is `local_file`)
|
||||
@ -444,6 +445,9 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
<Property name='user' type='string' key='user'>
|
||||
User identifier, defined by the developer's rules, must be unique within the application.
|
||||
</Property>
|
||||
<Property name='content' type='string' key='content'>
|
||||
The specific content of message feedback.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
@ -451,7 +455,7 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123",\n "content": "message feedback information"\n}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
|
||||
@ -459,7 +463,8 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"rating": "like",
|
||||
"user": "abc-123"
|
||||
"user": "abc-123",
|
||||
"content": "message feedback information"
|
||||
}'
|
||||
```
|
||||
|
||||
@ -675,7 +680,7 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20'`}>
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20' \\\n --header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20' \
|
||||
|
||||
@ -71,6 +71,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- `image` ('JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG')
|
||||
- `audio` ('MP3', 'M4A', 'WAV', 'WEBM', 'AMR')
|
||||
- `video` ('MP4', 'MOV', 'MPEG', 'MPGA')
|
||||
- `custom` (他のファイルタイプ)
|
||||
- `transfer_method` (string) 転送方法、画像URLの場合は`remote_url` / ファイルアップロードの場合は`local_file`
|
||||
- `url` (string) 画像URL(転送方法が`remote_url`の場合)
|
||||
- `upload_file_id` (string) アップロードされたファイルID、事前にファイルアップロードAPIを通じて取得する必要があります(転送方法が`local_file`の場合)
|
||||
@ -444,6 +445,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<Property name='user' type='string' key='user'>
|
||||
ユーザー識別子、開発者のルールによって定義され、アプリケーション内で一意でなければなりません。
|
||||
</Property>
|
||||
<Property name='content' type='string' key='content'>
|
||||
メッセージのフィードバックです。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### 応答
|
||||
@ -451,7 +455,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="リクエスト" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
|
||||
<CodeGroup title="リクエスト" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123",\n "content": "message feedback information"\n}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
|
||||
@ -459,7 +463,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"rating": "like",
|
||||
"user": "abc-123"
|
||||
"user": "abc-123",
|
||||
"content": "message feedback information"
|
||||
}'
|
||||
```
|
||||
|
||||
@ -674,7 +679,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="リクエスト" tag="GET" label="/conversations" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20'`}>
|
||||
<CodeGroup title="リクエスト" tag="GET" label="/conversations" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20' \\\n --header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20' \
|
||||
|
||||
@ -3,7 +3,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
|
||||
# 工作流编排对话型应用 API
|
||||
|
||||
对话应用支持会话持久化,可将之前的聊天记录作为上下进行回答,可适用于聊天/客服 AI 等。
|
||||
对话应用支持会话持久化,可将之前的聊天记录作为上下文进行回答,可适用于聊天/客服 AI 等。
|
||||
|
||||
<div>
|
||||
### 基础 URL
|
||||
@ -68,6 +68,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
- `image` 具体类型包含:'JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG'
|
||||
- `audio` 具体类型包含:'MP3', 'M4A', 'WAV', 'WEBM', 'AMR'
|
||||
- `video` 具体类型包含:'MP4', 'MOV', 'MPEG', 'MPGA'
|
||||
- `custom` 具体类型包含:其他文件类型
|
||||
- `transfer_method` (string) 传递方式:
|
||||
- `remote_url`: 图片地址。
|
||||
- `local_file`: 上传文件。
|
||||
@ -450,6 +451,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<Property name='user' type='string' key='user'>
|
||||
用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
|
||||
</Property>
|
||||
<Property name='content' type='string' key='content'>
|
||||
消息反馈的具体信息。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
@ -457,7 +461,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123",\n "content": "message feedback information"\n}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
|
||||
@ -465,7 +469,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"rating": "like",
|
||||
"user": "abc-123"
|
||||
"user": "abc-123",
|
||||
"content": "message feedback information"
|
||||
}'
|
||||
```
|
||||
|
||||
|
||||
@ -408,6 +408,9 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
<Property name='user' type='string' key='user'>
|
||||
User identifier, defined by the developer's rules, must be unique within the application.
|
||||
</Property>
|
||||
<Property name='content' type='string' key='content'>
|
||||
The specific content of message feedback.
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
@ -415,7 +418,7 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123",\n "content": "message feedback information"\n}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
|
||||
@ -423,7 +426,8 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"rating": "like",
|
||||
"user": "abc-123"
|
||||
"user": "abc-123",
|
||||
"content": "message feedback information"
|
||||
}'
|
||||
```
|
||||
|
||||
@ -709,7 +713,7 @@ Chat applications support session persistence, allowing previous chat history to
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20'`}>
|
||||
<CodeGroup title="Request" tag="GET" label="/conversations" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20' \\\n --header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20' \
|
||||
|
||||
@ -408,6 +408,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
<Property name='user' type='string' key='user'>
|
||||
ユーザー識別子、開発者のルールで定義され、アプリケーション内で一意でなければなりません。
|
||||
</Property>
|
||||
<Property name='content' type='string' key='content'>
|
||||
メッセージのフィードバックです。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### 応答
|
||||
@ -415,7 +418,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="リクエスト" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
|
||||
<CodeGroup title="リクエスト" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n --header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123",\n "content": "message feedback information"\n}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
|
||||
@ -423,7 +426,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"rating": "like",
|
||||
"user": "abc-123"
|
||||
"user": "abc-123",
|
||||
"content": "message feedback information"
|
||||
}'
|
||||
```
|
||||
|
||||
@ -708,7 +712,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="リクエスト" tag="GET" label="/conversations" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20'`}>
|
||||
<CodeGroup title="リクエスト" tag="GET" label="/conversations" targetCode={`curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20' \\\n --header 'Authorization: Bearer {api_key}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X GET '${props.appDetail.api_base_url}/conversations?user=abc-123&last_id=&limit=20' \
|
||||
|
||||
@ -3,7 +3,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
|
||||
# 对话型应用 API
|
||||
|
||||
对话应用支持会话持久化,可将之前的聊天记录作为上下进行回答,可适用于聊天/客服 AI 等。
|
||||
对话应用支持会话持久化,可将之前的聊天记录作为上下文进行回答,可适用于聊天/客服 AI 等。
|
||||
|
||||
<div>
|
||||
### 基础 URL
|
||||
@ -423,6 +423,9 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
<Property name='user' type='string' key='user'>
|
||||
用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
|
||||
</Property>
|
||||
<Property name='content' type='string' key='content'>
|
||||
消息反馈的具体信息。
|
||||
</Property>
|
||||
</Properties>
|
||||
|
||||
### Response
|
||||
@ -430,7 +433,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
</Col>
|
||||
<Col sticky>
|
||||
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123"\n}'`}>
|
||||
<CodeGroup title="Request" tag="POST" label="/messages/:message_id/feedbacks" targetCode={`curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks \\\n--header 'Authorization: Bearer {api_key}' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n "rating": "like",\n "user": "abc-123",\n "content": "message feedback information"\n}'`}>
|
||||
|
||||
```bash {{ title: 'cURL' }}
|
||||
curl -X POST '${props.appDetail.api_base_url}/messages/:message_id/feedbacks' \
|
||||
@ -438,7 +441,8 @@ import { Row, Col, Properties, Property, Heading, SubProperty } from '../md.tsx'
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"rating": "like",
|
||||
"user": "abc-123"
|
||||
"user": "abc-123",
|
||||
"content": "message feedback information"
|
||||
}'
|
||||
```
|
||||
|
||||
|
||||
@ -60,6 +60,7 @@ Workflow applications offers non-session support and is ideal for translation, a
|
||||
- `image` ('JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG')
|
||||
- `audio` ('MP3', 'M4A', 'WAV', 'WEBM', 'AMR')
|
||||
- `video` ('MP4', 'MOV', 'MPEG', 'MPGA')
|
||||
- `custom` (Other file types)
|
||||
- `transfer_method` (string) Transfer method, `remote_url` for image URL / `local_file` for file upload
|
||||
- `url` (string) Image URL (when the transfer method is `remote_url`)
|
||||
- `upload_file_id` (string) Uploaded file ID, which must be obtained by uploading through the File Upload API in advance (when the transfer method is `local_file`)
|
||||
|
||||
@ -60,6 +60,7 @@ import { Row, Col, Properties, Property, Heading, SubProperty, Paragraph } from
|
||||
- `image` ('JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG')
|
||||
- `audio` ('MP3', 'M4A', 'WAV', 'WEBM', 'AMR')
|
||||
- `video` ('MP4', 'MOV', 'MPEG', 'MPGA')
|
||||
- `custom` (他のファイルタイプ)
|
||||
- `transfer_method` (string) 転送方法、画像URLの場合は`remote_url` / ファイルアップロードの場合は`local_file`
|
||||
- `url` (string) 画像URL(転送方法が`remote_url`の場合)
|
||||
- `upload_file_id` (string) アップロードされたファイルID、事前にファイルアップロードAPIを通じて取得する必要があります(転送方法が`local_file`の場合)
|
||||
|
||||
@ -58,6 +58,7 @@ Workflow 应用无会话支持,适合用于翻译/文章写作/总结 AI 等
|
||||
- `image` 具体类型包含:'JPG', 'JPEG', 'PNG', 'GIF', 'WEBP', 'SVG'
|
||||
- `audio` 具体类型包含:'MP3', 'M4A', 'WAV', 'WEBM', 'AMR'
|
||||
- `video` 具体类型包含:'MP4', 'MOV', 'MPEG', 'MPGA'
|
||||
- `custom` 具体类型包含:其他文件类型
|
||||
- `transfer_method` (string) 传递方式,`remote_url` 图片地址 / `local_file` 上传文件
|
||||
- `url` (string) 图片地址(仅当传递方式为 `remote_url` 时)
|
||||
- `upload_file_id` (string) (string) 上传文件 ID(仅当传递方式为 `local_file` 时)
|
||||
|
||||
@ -28,10 +28,10 @@ const AppCard = ({
|
||||
<div className='relative shrink-0'>
|
||||
<AppIcon
|
||||
size='large'
|
||||
iconType={app.app.icon_type}
|
||||
icon={app.app.icon}
|
||||
background={app.app.icon_background}
|
||||
imageUrl={app.app.icon_url}
|
||||
iconType={appBasicInfo.icon_type}
|
||||
icon={appBasicInfo.icon}
|
||||
background={appBasicInfo.icon_background}
|
||||
imageUrl={appBasicInfo.icon_url}
|
||||
/>
|
||||
<span className='absolute bottom-[-3px] right-[-3px] w-4 h-4 p-0.5 bg-white rounded border-[0.5px] border-[rgba(0,0,0,0.02)] shadow-sm'>
|
||||
{appBasicInfo.mode === 'advanced-chat' && (
|
||||
|
||||
@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import { RiArrowDownSLine, RiLogoutBoxRLine } from '@remixicon/react'
|
||||
import Link from 'next/link'
|
||||
import { Menu, Transition } from '@headlessui/react'
|
||||
import Indicator from '../indicator'
|
||||
@ -15,11 +15,11 @@ import Avatar from '@/app/components/base/avatar'
|
||||
import { logout } from '@/service/common'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { LogOut01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { LanguagesSupported } from '@/i18n/language'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import WorkplaceSelector from './workplace-selector'
|
||||
|
||||
export type IAppSelector = {
|
||||
isMobile: boolean
|
||||
@ -27,8 +27,8 @@ export type IAppSelector = {
|
||||
|
||||
export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
const itemClassName = `
|
||||
flex items-center w-full h-9 px-3 text-gray-700 text-[14px]
|
||||
rounded-lg font-normal hover:bg-gray-50 cursor-pointer
|
||||
flex items-center w-full h-9 px-3 text-text-secondary system-md-regular
|
||||
rounded-lg hover:bg-state-base-hover cursor-pointer
|
||||
`
|
||||
const router = useRouter()
|
||||
const [aboutVisible, setAboutVisible] = useState(false)
|
||||
@ -88,7 +88,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
<Menu.Items
|
||||
className="
|
||||
absolute right-0 mt-1.5 w-60 max-w-80
|
||||
divide-y divide-gray-100 origin-top-right rounded-lg bg-white
|
||||
divide-y divide-divider-subtle origin-top-right rounded-lg bg-components-panel-bg-blur
|
||||
shadow-lg
|
||||
"
|
||||
>
|
||||
@ -96,11 +96,15 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
<div className='flex flex-nowrap items-center px-4 py-[13px]'>
|
||||
<Avatar name={userProfile.name} size={36} className='mr-3' />
|
||||
<div className='grow'>
|
||||
<div className='leading-5 font-normal text-[14px] text-gray-800 break-all'>{userProfile.name}</div>
|
||||
<div className='leading-[18px] text-xs font-normal text-gray-500 break-all'>{userProfile.email}</div>
|
||||
<div className='system-md-medium text-text-primary break-all'>{userProfile.name}</div>
|
||||
<div className='system-xs-regular text-text-tertiary break-all'>{userProfile.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<div className='px-1 py-1'>
|
||||
<div className='mt-2 px-3 text-xs font-medium text-text-tertiary'>{t('common.userProfile.workspace')}</div>
|
||||
<WorkplaceSelector />
|
||||
</div>
|
||||
<div className="px-1 py-1">
|
||||
<Menu.Item>
|
||||
<Link
|
||||
@ -108,7 +112,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
href='/account'
|
||||
target='_self' rel='noopener noreferrer'>
|
||||
<div>{t('common.account.account')}</div>
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
@ -122,7 +126,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
href={mailToSupport(userProfile.email, plan.type, langeniusVersionInfo.current_version)}
|
||||
target='_blank' rel='noopener noreferrer'>
|
||||
<div>{t('common.userProfile.emailSupport')}</div>
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||
</a>
|
||||
</Menu.Item>}
|
||||
<Menu.Item>
|
||||
@ -131,7 +135,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
href='https://github.com/langgenius/dify/discussions/categories/feedbacks'
|
||||
target='_blank' rel='noopener noreferrer'>
|
||||
<div>{t('common.userProfile.communityFeedback')}</div>
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
@ -140,7 +144,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
href='https://discord.gg/5AEfbxcd9k'
|
||||
target='_blank' rel='noopener noreferrer'>
|
||||
<div>{t('common.userProfile.community')}</div>
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
@ -151,7 +155,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
}
|
||||
target='_blank' rel='noopener noreferrer'>
|
||||
<div>{t('common.userProfile.helpCenter')}</div>
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
@ -160,7 +164,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
href='https://roadmap.dify.ai'
|
||||
target='_blank' rel='noopener noreferrer'>
|
||||
<div>{t('common.userProfile.roadmap')}</div>
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
|
||||
<ArrowUpRight className='hidden w-[14px] h-[14px] text-text-tertiary group-hover:flex' />
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
{
|
||||
@ -169,7 +173,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
<div className={classNames(itemClassName, 'justify-between')} onClick={() => setAboutVisible(true)}>
|
||||
<div>{t('common.userProfile.about')}</div>
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-2 text-xs font-normal text-gray-500'>{langeniusVersionInfo.current_version}</div>
|
||||
<div className='mr-2 system-xs-regular text-text-tertiary'>{langeniusVersionInfo.current_version}</div>
|
||||
<Indicator color={langeniusVersionInfo.current_version === langeniusVersionInfo.latest_version ? 'green' : 'orange'} />
|
||||
</div>
|
||||
</div>
|
||||
@ -180,10 +184,10 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
<Menu.Item>
|
||||
<div className='p-1' onClick={() => handleLogout()}>
|
||||
<div
|
||||
className='flex items-center justify-between h-9 px-3 rounded-lg cursor-pointer group hover:bg-gray-50'
|
||||
className='flex items-center justify-between h-9 px-3 rounded-lg cursor-pointer group hover:bg-state-base-hover'
|
||||
>
|
||||
<div className='font-normal text-[14px] text-gray-700'>{t('common.userProfile.logout')}</div>
|
||||
<LogOut01 className='hidden w-[14px] h-[14px] text-gray-500 group-hover:flex' />
|
||||
<div className='system-md-regular text-text-secondary'>{t('common.userProfile.logout')}</div>
|
||||
<RiLogoutBoxRLine className='hidden w-4 h-4 text-text-tertiary group-hover:flex' />
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
|
||||
@ -25,18 +25,18 @@ const Collapse = ({
|
||||
const toggle = () => setOpen(!open)
|
||||
|
||||
return (
|
||||
<div className={classNames('border border-gray-200 bg-gray-50 rounded-lg', wrapperClassName)}>
|
||||
<div className='flex items-center justify-between leading-[18px] px-3 py-2 text-xs font-medium text-gray-800 cursor-pointer' onClick={toggle}>
|
||||
<div className={classNames('bg-background-section-burn rounded-xl', wrapperClassName)}>
|
||||
<div className='flex items-center justify-between leading-[18px] px-3 py-2 text-xs font-medium text-text-secondary cursor-pointer' onClick={toggle}>
|
||||
{title}
|
||||
{
|
||||
open
|
||||
? <ChevronDownIcon className='w-3 h-3 text-gray-400' />
|
||||
: <ChevronRightIcon className='w-3 h-3 text-gray-400' />
|
||||
? <ChevronDownIcon className='w-3 h-3 text-components-button-tertiary-text' />
|
||||
: <ChevronRightIcon className='w-3 h-3 text-components-button-tertiary-text' />
|
||||
}
|
||||
</div>
|
||||
{
|
||||
open && (
|
||||
<div className='py-2 border-t border-t-gray-100'>
|
||||
<div className='py-1 mb-1 mx-1 border-t border-divider-subtle rounded-lg bg-components-panel-on-panel-item-bg'>
|
||||
{
|
||||
items.map(item => (
|
||||
<div key={item.key} onClick={() => onSelect && onSelect(item)}>
|
||||
|
||||
@ -12,7 +12,6 @@ export default function DataSourcePage() {
|
||||
|
||||
return (
|
||||
<div className='mb-8'>
|
||||
<div className='mb-2 text-sm font-medium text-gray-900'>{t('common.dataSource.add')}</div>
|
||||
<DataSourceNotion workspaces={notionWorkspaces} />
|
||||
<DataSourceWebsite provider={DataSourceProvider.jinaReader} />
|
||||
<DataSourceWebsite provider={DataSourceProvider.fireCrawl} />
|
||||
|
||||
@ -44,22 +44,22 @@ const ConfigItem: FC<Props> = ({
|
||||
const onChangeAuthorizedPage = notionActions?.onChangeAuthorizedPage || function () { }
|
||||
|
||||
return (
|
||||
<div className={cn(s['workspace-item'], 'flex items-center mb-1 py-1 pr-1 bg-white rounded-lg')} key={payload.id}>
|
||||
<div className={cn(s['workspace-item'], 'flex items-center mb-1 py-1 pr-1 bg-components-panel-on-panel-item-bg rounded-lg')} key={payload.id}>
|
||||
<payload.logo className='ml-3 mr-1.5' />
|
||||
<div className='grow py-[7px] leading-[18px] text-[13px] font-medium text-gray-700 truncate' title={payload.name}>{payload.name}</div>
|
||||
<div className='grow py-[7px] system-sm-medium text-text-secondary truncate' title={payload.name}>{payload.name}</div>
|
||||
{
|
||||
payload.isActive
|
||||
? <Indicator className='shrink-0 mr-[6px]' />
|
||||
? <Indicator className='shrink-0 mr-[6px]' color='green' />
|
||||
: <Indicator className='shrink-0 mr-[6px]' color='yellow' />
|
||||
}
|
||||
<div className='shrink-0 mr-3 text-xs font-medium uppercase'>
|
||||
<div className={`shrink-0 mr-3 text-xs font-medium uppercase ${payload.isActive ? 'text-util-colors-green-green-600' : 'text-util-colors-warning-warning-600'}`}>
|
||||
{
|
||||
payload.isActive
|
||||
? t(isNotion ? 'common.dataSource.notion.connected' : 'common.dataSource.website.active')
|
||||
: t(isNotion ? 'common.dataSource.notion.disconnected' : 'common.dataSource.website.inactive')
|
||||
}
|
||||
</div>
|
||||
<div className='mr-2 w-[1px] h-3 bg-gray-100' />
|
||||
<div className='mr-2 w-[1px] h-3 bg-divider-regular' />
|
||||
{isNotion && (
|
||||
<Operate payload={{
|
||||
id: payload.id,
|
||||
@ -70,8 +70,8 @@ const ConfigItem: FC<Props> = ({
|
||||
|
||||
{
|
||||
isWebsite && !readOnly && (
|
||||
<div className='p-2 text-gray-500 cursor-pointer rounded-md hover:bg-black/5' onClick={onRemove} >
|
||||
<RiDeleteBinLine className='w-4 h-4 ' />
|
||||
<div className='p-2 text-text-tertiary cursor-pointer rounded-md hover:bg-black/5' onClick={onRemove} >
|
||||
<RiDeleteBinLine className='w-4 h-4' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PlusIcon } from '@heroicons/react/24/solid'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import type { ConfigItemType } from './config-item'
|
||||
import ConfigItem from './config-item'
|
||||
|
||||
@ -41,12 +41,12 @@ const Panel: FC<Props> = ({
|
||||
const isWebsite = type === DataSourceType.website
|
||||
|
||||
return (
|
||||
<div className='mb-2 border-[0.5px] border-gray-200 bg-gray-50 rounded-xl'>
|
||||
<div className='mb-2 bg-background-section-burn rounded-xl'>
|
||||
<div className='flex items-center px-3 py-[9px]'>
|
||||
<div className={cn(s[`${type}-icon`], 'w-8 h-8 mr-3 border border-gray-100 rounded-lg')} />
|
||||
<div className={cn(s[`${type}-icon`], 'w-8 h-8 mr-3 border border-divider-subtle rounded-lg bg-background-default')} />
|
||||
<div className='grow'>
|
||||
<div className='flex items-center h-5'>
|
||||
<div className='text-sm font-medium text-gray-800'>{t(`common.dataSource.${type}.title`)}</div>
|
||||
<div className='text-sm font-medium text-text-primary'>{t(`common.dataSource.${type}.title`)}</div>
|
||||
{isWebsite && (
|
||||
<div className='ml-1 leading-[18px] px-1.5 rounded-md bg-white border border-gray-100 text-xs font-medium text-gray-700'>
|
||||
<span className='text-gray-500'>{t('common.dataSource.website.with')}</span> { provider === DataSourceProvider.fireCrawl ? '🔥 Firecrawl' : 'Jina Reader'}
|
||||
@ -55,7 +55,7 @@ const Panel: FC<Props> = ({
|
||||
</div>
|
||||
{
|
||||
!isConfigured && (
|
||||
<div className='leading-5 text-xs text-gray-500'>
|
||||
<div className='system-xs-medium text-text-tertiary'>
|
||||
{t(`common.dataSource.${type}.description`)}
|
||||
</div>
|
||||
)
|
||||
@ -81,13 +81,13 @@ const Panel: FC<Props> = ({
|
||||
<>
|
||||
{isSupportList && <div
|
||||
className={
|
||||
`flex items-center px-3 py-1 min-h-7 bg-white border-[0.5px] border-gray-200 text-xs font-medium text-primary-600 rounded-md
|
||||
`flex items-center px-3 py-1 min-h-7 bg-components-button-secondary-bg border-[0.5px] border-components-button-secondary-border system-sm-medium text-components-button-secondary-accent-text rounded-md
|
||||
${!readOnly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
|
||||
}
|
||||
onClick={onConfigure}
|
||||
>
|
||||
<PlusIcon className='w-[14px] h-[14px] mr-[5px]' />
|
||||
{t('common.dataSource.notion.addWorkspace')}
|
||||
<RiAddLine className='w-4 h-4 text-components-button-secondary-accent-text mr-[5px]' />
|
||||
{t('common.dataSource.connect')}
|
||||
</div>}
|
||||
</>
|
||||
)
|
||||
@ -98,8 +98,8 @@ const Panel: FC<Props> = ({
|
||||
{isWebsite && !isConfigured && (
|
||||
<div
|
||||
className={
|
||||
`flex items-center ml-3 px-3 h-7 bg-white border border-gray-200
|
||||
rounded-md text-xs font-medium text-gray-700
|
||||
`flex items-center ml-3 px-3 h-7 bg-components-button-secondary-bg border-[0.5px] border-components-button-secondary-border
|
||||
rounded-md text-xs font-medium text-components-button-secondary-accent-text
|
||||
${!readOnly ? 'cursor-pointer' : 'grayscale opacity-50 cursor-default'}`
|
||||
}
|
||||
onClick={!readOnly ? onConfigure : undefined}
|
||||
@ -113,10 +113,10 @@ const Panel: FC<Props> = ({
|
||||
isConfigured && (
|
||||
<>
|
||||
<div className='flex items-center px-3 h-[18px]'>
|
||||
<div className='text-xs font-medium text-gray-500'>
|
||||
<div className='system-xs-medium text-text-tertiary'>
|
||||
{isNotion ? t('common.dataSource.notion.connectedWorkspace') : t('common.dataSource.website.configuredCrawlers')}
|
||||
</div>
|
||||
<div className='grow ml-3 border-t border-t-gray-100' />
|
||||
<div className='grow ml-3 border-t border-t-divider-subtle' />
|
||||
</div>
|
||||
<div className='px-3 pt-2 pb-3'>
|
||||
{
|
||||
|
||||
@ -148,24 +148,25 @@ export default function AccountSetting({
|
||||
show
|
||||
onClose={onCancel}
|
||||
>
|
||||
<div className='mx-auto max-w-[1048px] h-[100vh] flex'>
|
||||
<div className='w-[44px] sm:w-[224px] pl-4 pr-6 border-r border-divider-burn flex flex-col'>
|
||||
<div className='mt-6 mb-8 px-3 py-2 text-text-primary title-2xl-semi-bold'>{t('common.userProfile.settings')}</div>
|
||||
<div className='flex'>
|
||||
<div className='w-[44px] sm:w-[200px] px-[1px] py-4 sm:p-4 border border-divider-burn shrink-0 sm:shrink-1 flex flex-col items-center sm:items-start'>
|
||||
<div className='mb-8 ml-0 sm:ml-2 sm:text-base title-2xl-semi-bold text-text-primary'>{t('common.userProfile.settings')}</div>
|
||||
<div className='w-full'>
|
||||
{
|
||||
menuItems.map(menuItem => (
|
||||
<div key={menuItem.key} className='mb-2'>
|
||||
{!isCurrentWorkspaceDatasetOperator && (
|
||||
<div className='py-2 pl-3 pb-1 mb-0.5 text-text-tertiary system-xs-medium-uppercase'>{menuItem.name}</div>
|
||||
<div className='px-2 mb-[6px] sm:text-xs system-xs-medium-uppercase text-text-tertiary'>{menuItem.name}</div>
|
||||
)}
|
||||
<div>
|
||||
{
|
||||
menuItem.items.map(item => (
|
||||
<div
|
||||
key={item.key}
|
||||
className={cn(
|
||||
'flex items-center mb-0.5 p-1 pl-3 h-[37px] text-sm cursor-pointer rounded-lg',
|
||||
activeMenu === item.key ? 'bg-state-base-active text-components-menu-item-text-active system-sm-semibold' : 'text-components-menu-item-text system-sm-medium')}
|
||||
className={`
|
||||
flex items-center h-[37px] mb-[2px] text-sm cursor-pointer rounded-lg
|
||||
${activeMenu === item.key ? 'system-sm-semibold text-components-menu-item-text-active bg-state-base-active' : 'system-sm-medium text-components-menu-item-text'}
|
||||
`}
|
||||
title={item.name}
|
||||
onClick={() => setActiveMenu(item.key)}
|
||||
>
|
||||
@ -180,17 +181,19 @@ export default function AccountSetting({
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className='relative flex w-[824px]'>
|
||||
<div className='absolute top-6 -right-11 flex flex-col items-center z-[9999]'>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
size='large'
|
||||
className='px-2'
|
||||
onClick={onCancel}
|
||||
>
|
||||
<RiCloseLine className='w-5 h-5' />
|
||||
</Button>
|
||||
<div className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>ESC</div>
|
||||
<div ref={scrollRef} className='relative w-[824px] h-[720px] pb-4 overflow-y-auto'>
|
||||
<div className={cn('sticky top-0 px-6 py-4 flex items-center h-14 mb-4 bg-components-panel-bg title-2xl-semi-bold text-text-primary z-20', scrolled && scrolledClassName)}>
|
||||
<div className='shrink-0'>{activeItem?.name}</div>
|
||||
{
|
||||
activeItem?.description && (
|
||||
<div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div>
|
||||
)
|
||||
}
|
||||
<div className='grow flex justify-end'>
|
||||
<div className='z-[10] flex items-center justify-center -mr-4 p-2 cursor-pointer rounded-[10px] hover:bg-components-button-tertiary-bg' onClick={onCancel}>
|
||||
<RiCloseLine className='w-5 h-5 text-components-button-tertiary-text' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={scrollRef} className='w-full pb-4 bg-components-panel-bg overflow-y-auto'>
|
||||
<div className={cn('sticky top-0 mx-8 pt-[27px] pb-2 mb-[18px] flex items-center bg-components-panel-bg z-20', scrolled && 'border-b border-divider-regular')}>
|
||||
|
||||
@ -13,7 +13,7 @@ import { timezones } from '@/utils/timezone'
|
||||
import { languages } from '@/i18n/language'
|
||||
|
||||
const titleClassName = `
|
||||
mb-2 text-sm font-medium text-gray-900
|
||||
mb-2 system-sm-semibold text-text-secondary
|
||||
`
|
||||
|
||||
export default function LanguagePage() {
|
||||
|
||||
@ -85,32 +85,32 @@ const MembersPage = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className='overflow-visible lg:overflow-visible'>
|
||||
<div className='flex items-center py-[7px] border-b border-gray-200 min-w-[480px]'>
|
||||
<div className='grow px-3 text-xs font-medium text-gray-500'>{t('common.members.name')}</div>
|
||||
<div className='shrink-0 w-[104px] text-xs font-medium text-gray-500'>{t('common.members.lastActive')}</div>
|
||||
<div className='shrink-0 w-[96px] px-3 text-xs font-medium text-gray-500'>{t('common.members.role')}</div>
|
||||
<div className='flex items-center py-[7px] border-b border-divider-regular min-w-[480px]'>
|
||||
<div className='grow px-3 system-xs-medium-uppercase text-text-tertiary'>{t('common.members.name')}</div>
|
||||
<div className='shrink-0 w-[104px] system-xs-medium-uppercase text-text-tertiary'>{t('common.members.lastActive')}</div>
|
||||
<div className='shrink-0 w-[96px] px-3 system-xs-medium-uppercase text-text-tertiary'>{t('common.members.role')}</div>
|
||||
</div>
|
||||
<div className='min-w-[480px] relative'>
|
||||
{
|
||||
accounts.map(account => (
|
||||
<div key={account.id} className='flex border-b border-gray-100'>
|
||||
<div key={account.id} className='flex border-b border-divider-subtle'>
|
||||
<div className='grow flex items-center py-2 px-3'>
|
||||
<Avatar size={24} className='mr-2' name={account.name} />
|
||||
<div className=''>
|
||||
<div className='text-[13px] font-medium text-gray-700 leading-[18px]'>
|
||||
<div className='text-text-secondary system-sm-medium'>
|
||||
{account.name}
|
||||
{account.status === 'pending' && <span className='ml-1 text-xs text-[#DC6803]'>{t('common.members.pending')}</span>}
|
||||
{userProfile.email === account.email && <span className='text-xs text-gray-500'>{t('common.members.you')}</span>}
|
||||
{account.status === 'pending' && <span className='ml-1 system-xs-regular text-[#DC6803]'>{t('common.members.pending')}</span>}
|
||||
{userProfile.email === account.email && <span className='system-xs-regular text-text-tertiary'>{t('common.members.you')}</span>}
|
||||
</div>
|
||||
<div className='text-xs text-gray-500 leading-[18px]'>{account.email}</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>{account.email}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='shrink-0 flex items-center w-[104px] py-2 text-[13px] text-gray-700'>{dayjs(Number((account.last_active_at || account.created_at)) * 1000).locale(locale === 'zh-Hans' ? 'zh-cn' : 'en').fromNow()}</div>
|
||||
<div className='shrink-0 flex items-center w-[104px] py-2 system-xs-regular text-text-secondary'>{dayjs(Number((account.last_active_at || account.created_at)) * 1000).locale(locale === 'zh-Hans' ? 'zh-cn' : 'en').fromNow()}</div>
|
||||
<div className='shrink-0 w-[96px] flex items-center'>
|
||||
{
|
||||
((isCurrentWorkspaceOwner && account.role !== 'owner') || (isCurrentWorkspaceManager && !['owner', 'admin'].includes(account.role)))
|
||||
? <Operation member={account} operatorRole={currentWorkspace.role} onOperate={mutate} />
|
||||
: <div className='px-3 text-[13px] text-gray-700'>{RoleMap[account.role] || RoleMap.normal}</div>
|
||||
: <div className='px-3 system-xs-regular text-text-secondary'>{RoleMap[account.role] || RoleMap.normal}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -11,7 +11,7 @@ const HeaderWrapper = ({
|
||||
children,
|
||||
}: HeaderWrapperProps) => {
|
||||
const pathname = usePathname()
|
||||
const isBordered = ['/apps', '/datasets', '/datasets/create', '/tools', '/account'].includes(pathname)
|
||||
const isBordered = ['/apps', '/datasets', '/datasets/create', '/tools'].includes(pathname)
|
||||
|
||||
return (
|
||||
<div className={classNames(
|
||||
|
||||
@ -52,7 +52,7 @@ const Header = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedSegment])
|
||||
return (
|
||||
<div className='flex flex-1 items-center justify-between pr-3'>
|
||||
<div className='flex flex-1 items-center justify-between px-4 bg-background-body'>
|
||||
<div className='flex items-center'>
|
||||
{isMobile && <div
|
||||
className='flex items-center justify-center h-8 w-8 cursor-pointer'
|
||||
|
||||
@ -17,20 +17,20 @@ type ColorMap = {
|
||||
}
|
||||
|
||||
const BACKGROUND_MAP: ColorMap = {
|
||||
green: 'bg-[#31C48D]',
|
||||
orange: 'bg-[#FF5A1F]',
|
||||
red: 'bg-[#F04438]',
|
||||
blue: 'bg-[#36BFFA]',
|
||||
green: 'bg-components-badge-status-light-success-bg',
|
||||
orange: 'bg-components-badge-status-light-warning-bg',
|
||||
red: 'bg-components-badge-status-light-error-bg',
|
||||
blue: 'bg-components-badge-status-light-normal-bg',
|
||||
yellow: 'bg-[#FDB022]',
|
||||
gray: 'bg-[#D0D5DD]',
|
||||
gray: 'bg-components-badge-status-light-disabled-bg',
|
||||
}
|
||||
const BORDER_MAP: ColorMap = {
|
||||
green: 'border-[#0E9F6E]',
|
||||
orange: 'border-[#D03801]',
|
||||
red: 'border-[#D92D20]',
|
||||
blue: 'border-[#0BA5EC]',
|
||||
green: 'border-components-badge-status-light-success-border-inner',
|
||||
orange: 'border-components-badge-status-light-warning-border-inner',
|
||||
red: 'border-components-badge-status-light-error-border-inner',
|
||||
blue: 'border-components-badge-status-light-normal-border-inner',
|
||||
yellow: 'border-[#F79009]',
|
||||
gray: 'border-[#98A2B3]',
|
||||
gray: 'border-components-badge-status-light-disabled-border-inner',
|
||||
}
|
||||
const SHADOW_MAP: ColorMap = {
|
||||
green: 'shadow-[0_0_5px_-3px_rgba(14,159,110,0.1),0.5px_0.5px_3px_rgba(14,159,110,0.3),inset_1.5px_1.5px_0px_rgba(255,255,255,0.2)]',
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
'use client'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const Empty = () => {
|
||||
const { t } = useTranslation()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center'>
|
||||
<div className="shrink-0 w-[163px] h-[149px] bg-cover bg-no-repeat bg-[url('~@/app/components/tools/add-tool-modal/empty.png')]"></div>
|
||||
<div className='mb-1 text-[13px] font-medium text-text-secondary leading-[18px]'>{t('tools.addToolModal.emptyTitle')}</div>
|
||||
<div className='text-[13px] text-text-tertiary leading-[18px]'>{t('tools.addToolModal.emptyTip')}</div>
|
||||
<div className='mb-1 text-[13px] font-medium text-text-primary leading-[18px]'>
|
||||
{t(`tools.addToolModal.${searchParams.get('category') === 'workflow' ? 'emptyTitle' : 'emptyTitleCustom'}`)}
|
||||
</div>
|
||||
<div className='text-[13px] text-text-tertiary leading-[18px]'>
|
||||
{t(`tools.addToolModal.${searchParams.get('category') === 'workflow' ? 'emptyTip' : 'emptyTipCustom'}`)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ const Blocks = ({
|
||||
>
|
||||
{
|
||||
classification !== '-' && !!list.length && (
|
||||
<div className='flex items-start px-3 h-[22px] text-xs font-medium text-gray-500'>
|
||||
<div className='flex items-start px-3 h-[22px] text-xs font-medium text-text-tertiary'>
|
||||
{t(`workflow.tabs.${classification}`)}
|
||||
</div>
|
||||
)
|
||||
@ -68,7 +68,7 @@ const Blocks = ({
|
||||
<Tooltip
|
||||
key={block.type}
|
||||
position='right'
|
||||
popupClassName='!p-0 !px-3 !py-2.5 !w-[200px] !leading-[18px] !text-xs !text-gray-700 !border-[0.5px] !border-black/5 !rounded-xl !shadow-lg'
|
||||
popupClassName='w-[200px]'
|
||||
popupContent={(
|
||||
<div>
|
||||
<BlockIcon
|
||||
@ -76,21 +76,21 @@ const Blocks = ({
|
||||
className='mb-2'
|
||||
type={block.type}
|
||||
/>
|
||||
<div className='mb-1 text-sm leading-5 text-gray-900'>{block.title}</div>
|
||||
<div className='text-xs text-gray-700 leading-[18px]'>{nodesExtraData[block.type].about}</div>
|
||||
<div className='mb-1 system-md-medium text-text-primary'>{block.title}</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>{nodesExtraData[block.type].about}</div>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div
|
||||
key={block.type}
|
||||
className='flex items-center px-3 w-full h-8 rounded-lg hover:bg-gray-50 cursor-pointer'
|
||||
className='flex items-center px-3 w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer'
|
||||
onClick={() => onSelect(block.type)}
|
||||
>
|
||||
<BlockIcon
|
||||
className='mr-2 shrink-0'
|
||||
type={block.type}
|
||||
/>
|
||||
<div className='text-sm text-gray-900'>{block.title}</div>
|
||||
<div className='text-sm text-text-secondary'>{block.title}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
))
|
||||
@ -103,7 +103,7 @@ const Blocks = ({
|
||||
<div className='p-1'>
|
||||
{
|
||||
isEmpty && (
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div>
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
|
||||
@ -27,6 +27,7 @@ import SearchBox from '@/app/components/plugins/marketplace/search-box'
|
||||
import {
|
||||
Plus02,
|
||||
} from '@/app/components/base/icons/src/vender/line/general'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
type NodeSelectorProps = {
|
||||
open?: boolean
|
||||
@ -117,12 +118,12 @@ const NodeSelector: FC<NodeSelectorProps> = ({
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center
|
||||
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10
|
||||
w-4 h-4 rounded-full bg-components-button-primary-bg text-text-primary-on-surface hover:bg-components-button-primary-bg-hover cursor-pointer z-10
|
||||
${triggerClassName?.(open)}
|
||||
`}
|
||||
style={triggerStyle}
|
||||
>
|
||||
<Plus02 className='w-2.5 h-2.5 text-white' />
|
||||
<Plus02 className='w-2.5 h-2.5' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -36,16 +36,16 @@ const Tabs: FC<TabsProps> = ({
|
||||
<div onClick={e => e.stopPropagation()}>
|
||||
{
|
||||
!noBlocks && (
|
||||
<div className='flex items-center px-3 border-b-[0.5px] border-b-black/5'>
|
||||
<div className='flex items-center px-3 border-b-[0.5px] border-divider-subtle'>
|
||||
{
|
||||
tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={cn(
|
||||
'relative mr-4 h-[34px] text-[13px] leading-[34px] font-medium cursor-pointer',
|
||||
'relative mr-4 pt-1 pb-2 system-sm-medium cursor-pointer',
|
||||
activeTab === tab.key
|
||||
? 'text-gray-700 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-primary-600'
|
||||
: 'text-gray-500',
|
||||
? 'text-text-primary after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-full after:bg-util-colors-blue-brand-blue-brand-600'
|
||||
: 'text-text-tertiary',
|
||||
)}
|
||||
onClick={() => onActiveTabChange(tab.key)}
|
||||
>
|
||||
|
||||
@ -78,7 +78,7 @@ const Blocks = ({
|
||||
<div className='p-1 max-w-[320px]'>
|
||||
{
|
||||
!tools.length && !showWorkflowEmpty && (
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-gray-500'>{t('workflow.tabs.noResult')}</div>
|
||||
<div className='flex items-center px-3 h-[22px] text-xs font-medium text-text-tertiary'>{t('workflow.tabs.noResult')}</div>
|
||||
)
|
||||
}
|
||||
{!tools.length && showWorkflowEmpty && (
|
||||
|
||||
@ -506,3 +506,5 @@ export const WORKFLOW_DATA_UPDATE = 'WORKFLOW_DATA_UPDATE'
|
||||
export const CUSTOM_NODE = 'custom'
|
||||
export const CUSTOM_EDGE = 'custom'
|
||||
export const DSL_EXPORT_CHECK = 'DSL_EXPORT_CHECK'
|
||||
export const DEFAULT_RETRY_MAX = 3
|
||||
export const DEFAULT_RETRY_INTERVAL = 100
|
||||
|
||||
@ -13,7 +13,7 @@ const EditingTitle = () => {
|
||||
const isSyncingWorkflowDraft = useStore(s => s.isSyncingWorkflowDraft)
|
||||
|
||||
return (
|
||||
<div className='flex items-center h-[18px] text-xs text-gray-500'>
|
||||
<div className='flex items-center h-[18px] system-xs-regular text-text-tertiary'>
|
||||
{
|
||||
!!draftUpdatedAt && (
|
||||
<>
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
} from '../hooks'
|
||||
import AppPublisher from '../../app/app-publisher'
|
||||
import { ToastContext } from '../../base/toast'
|
||||
import Divider from '../../base/divider'
|
||||
import RunAndHistory from './run-and-history'
|
||||
import EditingTitle from './editing-title'
|
||||
import RunningTitle from './running-title'
|
||||
@ -144,15 +145,12 @@ const Header: FC = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='absolute top-0 left-0 z-10 flex items-center justify-between w-full px-3 h-14'
|
||||
style={{
|
||||
background: 'linear-gradient(180deg, #F9FAFB 0%, rgba(249, 250, 251, 0.00) 100%)',
|
||||
}}
|
||||
className='absolute top-0 left-0 z-10 flex items-center justify-between w-full px-3 h-14 bg-mask-top2bottom-gray-50-to-transparent'
|
||||
>
|
||||
<div>
|
||||
{
|
||||
appSidebarExpand === 'collapse' && (
|
||||
<div className='text-xs font-medium text-gray-700'>{appDetail?.name}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{appDetail?.name}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
@ -171,7 +169,7 @@ const Header: FC = () => {
|
||||
{/* <GlobalVariableButton disabled={nodesReadOnly} /> */}
|
||||
{isChatMode && <ChatVariableButton disabled={nodesReadOnly} />}
|
||||
<EnvButton disabled={nodesReadOnly} />
|
||||
<div className='w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-auto' />
|
||||
<RunAndHistory />
|
||||
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
|
||||
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
|
||||
@ -196,12 +194,11 @@ const Header: FC = () => {
|
||||
}
|
||||
{
|
||||
viewHistory && (
|
||||
<div className='flex items-center'>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<ViewHistory withText />
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-auto' />
|
||||
<Button
|
||||
variant='primary'
|
||||
className='mr-2'
|
||||
onClick={handleGoBackToEdit}
|
||||
>
|
||||
<ArrowNarrowLeft className='w-4 h-4 mr-1' />
|
||||
@ -212,14 +209,13 @@ const Header: FC = () => {
|
||||
}
|
||||
{
|
||||
restoring && (
|
||||
<div className='flex items-center'>
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Button className='text-components-button-secondary-text' onClick={handleShowFeatures}>
|
||||
<RiApps2AddLine className='w-4 h-4 mr-1 text-components-button-secondary-text' />
|
||||
{t('workflow.common.features')}
|
||||
</Button>
|
||||
<div className='mx-2 w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-auto' />
|
||||
<Button
|
||||
className='mr-2'
|
||||
onClick={handleCancelRestore}
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
|
||||
@ -7,8 +7,10 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import TipPopup from '../operator/tip-popup'
|
||||
import { useWorkflowHistoryStore } from '../workflow-history-store'
|
||||
import Divider from '../../base/divider'
|
||||
import { useNodesReadOnly } from '@/app/components/workflow/hooks'
|
||||
import ViewWorkflowHistory from '@/app/components/workflow/header/view-workflow-history'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
export type UndoRedoProps = { handleUndo: () => void; handleRedo: () => void }
|
||||
const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => {
|
||||
@ -29,36 +31,35 @@ const UndoRedo: FC<UndoRedoProps> = ({ handleUndo, handleRedo }) => {
|
||||
const { nodesReadOnly } = useNodesReadOnly()
|
||||
|
||||
return (
|
||||
<div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'>
|
||||
<div className='flex items-center space-x-0.5 p-0.5 backdrop-blur-[5px] rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-lg'>
|
||||
<TipPopup title={t('workflow.common.undo')!} shortcuts={['ctrl', 'z']}>
|
||||
<div
|
||||
data-tooltip-id='workflow.undo'
|
||||
className={`
|
||||
flex items-center px-1.5 w-8 h-8 rounded-md text-[13px] font-medium
|
||||
hover:bg-black/5 hover:text-gray-700 cursor-pointer select-none
|
||||
${(nodesReadOnly || buttonsDisabled.undo) && 'hover:bg-transparent opacity-50 !cursor-not-allowed'}
|
||||
`}
|
||||
className={
|
||||
classNames('flex items-center px-1.5 w-8 h-8 rounded-md system-sm-medium text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer select-none',
|
||||
(nodesReadOnly || buttonsDisabled.undo)
|
||||
&& 'hover:bg-transparent text-text-disabled hover:text-text-disabled cursor-not-allowed')}
|
||||
onClick={() => !nodesReadOnly && !buttonsDisabled.undo && handleUndo()}
|
||||
>
|
||||
<RiArrowGoBackLine className='h-4 w-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
</TipPopup >
|
||||
<TipPopup title={t('workflow.common.redo')!} shortcuts={['ctrl', 'y']}>
|
||||
<div
|
||||
data-tooltip-id='workflow.redo'
|
||||
className={`
|
||||
flex items-center px-1.5 w-8 h-8 rounded-md text-[13px] font-medium
|
||||
hover:bg-black/5 hover:text-gray-700 cursor-pointer select-none
|
||||
${(nodesReadOnly || buttonsDisabled.redo) && 'hover:bg-transparent opacity-50 !cursor-not-allowed'}
|
||||
`}
|
||||
className={
|
||||
classNames('flex items-center px-1.5 w-8 h-8 rounded-md system-sm-medium text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer select-none',
|
||||
(nodesReadOnly || buttonsDisabled.redo)
|
||||
&& 'hover:bg-transparent text-text-disabled hover:text-text-disabled cursor-not-allowed',
|
||||
)}
|
||||
onClick={() => !nodesReadOnly && !buttonsDisabled.redo && handleRedo()}
|
||||
>
|
||||
<RiArrowGoForwardFill className='h-4 w-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
<div className="mx-[3px] w-[1px] h-3.5 bg-gray-200"></div>
|
||||
<Divider type='vertical' className="h-3.5 mx-0.5" />
|
||||
<ViewWorkflowHistory />
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
} from '../hooks'
|
||||
import TipPopup from '../operator/tip-popup'
|
||||
import type { WorkflowHistoryState } from '../workflow-history-store'
|
||||
import Divider from '../../base/divider'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
@ -24,6 +25,7 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import classNames from '@/utils/classnames'
|
||||
|
||||
type ChangeHistoryEntry = {
|
||||
label: string
|
||||
@ -47,7 +49,7 @@ const ViewWorkflowHistory = () => {
|
||||
setCurrentLogItem: state.setCurrentLogItem,
|
||||
setShowMessageLogModal: state.setShowMessageLogModal,
|
||||
})))
|
||||
const reactflowStore = useStoreApi()
|
||||
const reactFlowStore = useStoreApi()
|
||||
const { store, getHistoryLabel } = useWorkflowHistory()
|
||||
|
||||
const { pastStates, futureStates, undo, redo, clear } = store.temporal.getState()
|
||||
@ -59,7 +61,7 @@ const ViewWorkflowHistory = () => {
|
||||
}, [clear])
|
||||
|
||||
const handleSetState = useCallback(({ index }: ChangeHistoryEntry) => {
|
||||
const { setEdges, setNodes } = reactflowStore.getState()
|
||||
const { setEdges, setNodes } = reactFlowStore.getState()
|
||||
const diff = currentHistoryStateIndex + index
|
||||
if (diff === 0)
|
||||
return
|
||||
@ -75,7 +77,7 @@ const ViewWorkflowHistory = () => {
|
||||
|
||||
setEdges(edges)
|
||||
setNodes(nodes)
|
||||
}, [currentHistoryStateIndex, reactflowStore, redo, store, undo])
|
||||
}, [currentHistoryStateIndex, reactFlowStore, redo, store, undo])
|
||||
|
||||
const calculateStepLabel = useCallback((index: number) => {
|
||||
if (!index)
|
||||
@ -83,8 +85,7 @@ const ViewWorkflowHistory = () => {
|
||||
|
||||
const count = index < 0 ? index * -1 : index
|
||||
return `${index > 0 ? t('workflow.changeHistory.stepForward', { count }) : t('workflow.changeHistory.stepBackward', { count })}`
|
||||
}
|
||||
, [t])
|
||||
}, [t])
|
||||
|
||||
const calculateChangeList: ChangeHistoryList = useMemo(() => {
|
||||
const filterList = (list: any, startIndex = 0, reverse = false) => list.map((state: Partial<WorkflowHistoryState>, index: number) => {
|
||||
@ -125,10 +126,11 @@ const ViewWorkflowHistory = () => {
|
||||
title={t('workflow.changeHistory.title')}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center w-8 h-8 rounded-md hover:bg-black/5 cursor-pointer
|
||||
${open && 'bg-primary-50'} ${nodesReadOnly && 'bg-primary-50 opacity-50 !cursor-not-allowed'}
|
||||
`}
|
||||
className={
|
||||
classNames('flex items-center justify-center w-8 h-8 rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer',
|
||||
open && 'bg-state-accent-active text-text-accent',
|
||||
nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (nodesReadOnly)
|
||||
return
|
||||
@ -136,16 +138,16 @@ const ViewWorkflowHistory = () => {
|
||||
setShowMessageLogModal(false)
|
||||
}}
|
||||
>
|
||||
<RiHistoryLine className={`w-4 h-4 hover:bg-black/5 hover:text-gray-700 ${open ? 'text-primary-600' : 'text-gray-500'}`} />
|
||||
<RiHistoryLine className='w-4 h-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[12]'>
|
||||
<div
|
||||
className='flex flex-col ml-2 min-w-[240px] max-w-[360px] bg-white border-[0.5px] border-gray-200 shadow-xl rounded-xl overflow-y-auto'
|
||||
className='flex flex-col ml-2 min-w-[240px] max-w-[360px] bg-components-panel-bg-blur backdrop-blur-[5px] border-[0.5px] border-components-panel-border shadow-xl rounded-xl overflow-y-auto'
|
||||
>
|
||||
<div className='sticky top-0 bg-white flex items-center justify-between px-4 pt-3 text-base font-semibold text-gray-900'>
|
||||
<div className='grow'>{t('workflow.changeHistory.title')}</div>
|
||||
<div className='sticky top-0 flex items-center justify-between px-4 pt-3'>
|
||||
<div className='grow text-text-secondary system-mg-regular'>{t('workflow.changeHistory.title')}</div>
|
||||
<div
|
||||
className='shrink-0 flex items-center justify-center w-6 h-6 cursor-pointer'
|
||||
onClick={() => {
|
||||
@ -154,7 +156,7 @@ const ViewWorkflowHistory = () => {
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-500' />
|
||||
<RiCloseLine className='w-4 h-4 text-text-secondary' />
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
@ -168,8 +170,8 @@ const ViewWorkflowHistory = () => {
|
||||
{
|
||||
!calculateChangeList.statesCount && (
|
||||
<div className='py-12'>
|
||||
<RiHistoryLine className='mx-auto mb-2 w-8 h-8 text-gray-300' />
|
||||
<div className='text-center text-[13px] text-gray-400'>
|
||||
<RiHistoryLine className='mx-auto mb-2 w-8 h-8 text-text-tertiary' />
|
||||
<div className='text-center text-[13px] text-text-tertiary'>
|
||||
{t('workflow.changeHistory.placeholder')}
|
||||
</div>
|
||||
</div>
|
||||
@ -181,8 +183,8 @@ const ViewWorkflowHistory = () => {
|
||||
<div
|
||||
key={item?.index}
|
||||
className={cn(
|
||||
'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer',
|
||||
item?.index === currentHistoryStateIndex && 'bg-primary-50',
|
||||
'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-state-base-hover text-text-secondary cursor-pointer',
|
||||
item?.index === currentHistoryStateIndex && 'bg-state-base-hover',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleSetState(item)
|
||||
@ -192,8 +194,7 @@ const ViewWorkflowHistory = () => {
|
||||
<div>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center text-[13px] font-medium leading-[18px]',
|
||||
item?.index === currentHistoryStateIndex && 'text-primary-600',
|
||||
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
|
||||
)}
|
||||
>
|
||||
{item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)}{item?.index === currentHistoryStateIndex && t('workflow.changeHistory.currentState')})
|
||||
@ -207,8 +208,8 @@ const ViewWorkflowHistory = () => {
|
||||
<div
|
||||
key={item?.index}
|
||||
className={cn(
|
||||
'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-primary-50 cursor-pointer',
|
||||
item?.index === calculateChangeList.statesCount - 1 && 'bg-primary-50',
|
||||
'flex mb-0.5 px-2 py-[7px] rounded-lg hover:bg-state-base-hover cursor-pointer',
|
||||
item?.index === calculateChangeList.statesCount - 1 && 'bg-state-base-hover',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleSetState(item)
|
||||
@ -218,8 +219,7 @@ const ViewWorkflowHistory = () => {
|
||||
<div>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center text-[13px] font-medium leading-[18px]',
|
||||
item?.index === calculateChangeList.statesCount - 1 && 'text-primary-600',
|
||||
'flex items-center text-[13px] font-medium leading-[18px] text-text-secondary',
|
||||
)}
|
||||
>
|
||||
{item?.label || t('workflow.changeHistory.sessionStart')} ({calculateStepLabel(item?.index)})
|
||||
@ -234,12 +234,12 @@ const ViewWorkflowHistory = () => {
|
||||
}
|
||||
{
|
||||
!!calculateChangeList.statesCount && (
|
||||
<>
|
||||
<div className="h-[1px] bg-gray-100" />
|
||||
<div className='px-0.5'>
|
||||
<Divider className='m-0' />
|
||||
<div
|
||||
className={cn(
|
||||
'flex my-0.5 px-2 py-[7px] rounded-lg cursor-pointer',
|
||||
'hover:bg-red-50 hover:text-red-600',
|
||||
'flex my-0.5 px-2 py-[7px] rounded-lg text-text-secondary cursor-pointer',
|
||||
'hover:bg-state-base-hover',
|
||||
)}
|
||||
onClick={() => {
|
||||
handleClearHistory()
|
||||
@ -256,12 +256,12 @@ const ViewWorkflowHistory = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className="px-3 w-[240px] py-2 text-xs text-gray-500" >
|
||||
<div className="px-3 w-[240px] py-2 text-xs text-text-tertiary" >
|
||||
<div className="flex items-center mb-1 h-[22px] font-medium uppercase">{t('workflow.changeHistory.hint')}</div>
|
||||
<div className="mb-1 text-gray-700 leading-[18px]">{t('workflow.changeHistory.hintText')}</div>
|
||||
<div className="mb-1 text-text-tertiary leading-[18px]">{t('workflow.changeHistory.hintText')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
getFilesInLogs,
|
||||
} from '@/app/components/base/file-uploader/utils'
|
||||
import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
export const useWorkflowRun = () => {
|
||||
const store = useStoreApi()
|
||||
@ -114,6 +115,7 @@ export const useWorkflowRun = () => {
|
||||
onIterationStart,
|
||||
onIterationNext,
|
||||
onIterationFinish,
|
||||
onNodeRetry,
|
||||
onError,
|
||||
...restCallback
|
||||
} = callback || {}
|
||||
@ -440,10 +442,13 @@ export const useWorkflowRun = () => {
|
||||
})
|
||||
if (currentIndex > -1 && draft.tracing) {
|
||||
draft.tracing[currentIndex] = {
|
||||
...data,
|
||||
...(draft.tracing[currentIndex].extras
|
||||
? { extras: draft.tracing[currentIndex].extras }
|
||||
: {}),
|
||||
...data,
|
||||
...(draft.tracing[currentIndex].retryDetail
|
||||
? { retryDetail: draft.tracing[currentIndex].retryDetail }
|
||||
: {}),
|
||||
} as any
|
||||
}
|
||||
}))
|
||||
@ -616,6 +621,41 @@ export const useWorkflowRun = () => {
|
||||
if (onIterationFinish)
|
||||
onIterationFinish(params)
|
||||
},
|
||||
onNodeRetry: (params) => {
|
||||
const { data } = params
|
||||
const {
|
||||
workflowRunningData,
|
||||
setWorkflowRunningData,
|
||||
} = workflowStore.getState()
|
||||
const {
|
||||
getNodes,
|
||||
setNodes,
|
||||
} = store.getState()
|
||||
|
||||
const nodes = getNodes()
|
||||
setWorkflowRunningData(produce(workflowRunningData!, (draft) => {
|
||||
const tracing = draft.tracing!
|
||||
const currentRetryNodeIndex = tracing.findIndex(trace => trace.node_id === data.node_id)
|
||||
|
||||
if (currentRetryNodeIndex > -1) {
|
||||
const currentRetryNode = tracing[currentRetryNodeIndex]
|
||||
if (currentRetryNode.retryDetail)
|
||||
draft.tracing![currentRetryNodeIndex].retryDetail!.push(data as NodeTracing)
|
||||
|
||||
else
|
||||
draft.tracing![currentRetryNodeIndex].retryDetail = [data as NodeTracing]
|
||||
}
|
||||
}))
|
||||
const newNodes = produce(nodes, (draft) => {
|
||||
const currentNode = draft.find(node => node.id === data.node_id)!
|
||||
|
||||
currentNode.data._retryIndex = data.retry_index
|
||||
})
|
||||
setNodes(newNodes)
|
||||
|
||||
if (onNodeRetry)
|
||||
onNodeRetry(params)
|
||||
},
|
||||
onParallelBranchStarted: (params) => {
|
||||
// console.log(params, 'parallel start')
|
||||
},
|
||||
|
||||
@ -57,6 +57,7 @@ import {
|
||||
import I18n from '@/context/i18n'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
|
||||
import { useWorkflowConfig } from '@/service/use-workflow'
|
||||
|
||||
export const useIsChatMode = () => {
|
||||
const appDetail = useAppStore(s => s.appDetail)
|
||||
@ -69,7 +70,9 @@ export const useWorkflow = () => {
|
||||
const { locale } = useContext(I18n)
|
||||
const store = useStoreApi()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const appId = useStore(s => s.appId)
|
||||
const nodesExtraData = useNodesExtraData()
|
||||
const { data: workflowConfig } = useWorkflowConfig(appId)
|
||||
const setPanelWidth = useCallback((width: number) => {
|
||||
localStorage.setItem('workflow-node-panel-width', `${width}`)
|
||||
workflowStore.setState({ panelWidth: width })
|
||||
@ -336,15 +339,15 @@ export const useWorkflow = () => {
|
||||
for (let i = 0; i < parallelList.length; i++) {
|
||||
const parallel = parallelList[i]
|
||||
|
||||
if (parallel.depth > PARALLEL_DEPTH_LIMIT) {
|
||||
if (parallel.depth > (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT)) {
|
||||
const { setShowTips } = workflowStore.getState()
|
||||
setShowTips(t('workflow.common.parallelTip.depthLimit', { num: PARALLEL_DEPTH_LIMIT }))
|
||||
setShowTips(t('workflow.common.parallelTip.depthLimit', { num: (workflowConfig?.parallel_depth_limit || PARALLEL_DEPTH_LIMIT) }))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}, [t, workflowStore])
|
||||
}, [t, workflowStore, workflowConfig?.parallel_depth_limit])
|
||||
|
||||
const isValidConnection = useCallback(({ source, sourceHandle, target }: Connection) => {
|
||||
const {
|
||||
|
||||
@ -180,7 +180,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
return () => {
|
||||
handleSyncWorkflowDraft(true, true)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const { handleRefreshWorkflowDraft } = useWorkflowUpdate()
|
||||
@ -281,7 +281,7 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
<div
|
||||
id='workflow-container'
|
||||
className={`
|
||||
relative w-full min-w-[960px] h-full bg-[#F0F2F7]
|
||||
relative w-full min-w-[960px] h-full
|
||||
${workflowReadOnly && 'workflow-panel-animation'}
|
||||
${nodeAnimation && 'workflow-node-animation'}
|
||||
`}
|
||||
@ -373,7 +373,8 @@ const Workflow: FC<WorkflowProps> = memo(({
|
||||
<Background
|
||||
gap={[14, 14]}
|
||||
size={2}
|
||||
color='#E4E5E7'
|
||||
className="bg-workflow-canvas-workflow-bg"
|
||||
color='var(--color-workflow-canvas-workflow-dot-color)'
|
||||
/>
|
||||
</ReactFlow>
|
||||
</div>
|
||||
@ -403,7 +404,7 @@ const WorkflowWrap = memo(() => {
|
||||
|
||||
if (!data || isLoading) {
|
||||
return (
|
||||
<div className='flex justify-center items-center relative w-full h-full bg-[#F0F2F7]'>
|
||||
<div className='flex justify-center items-center relative w-full h-full'>
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -17,17 +17,25 @@ import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import RetryResultPanel from '@/app/components/workflow/run/retry-result-panel'
|
||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
|
||||
const i18nPrefix = 'workflow.singleRun'
|
||||
|
||||
type BeforeRunFormProps = {
|
||||
nodeName: string
|
||||
nodeType?: BlockEnum
|
||||
toolIcon?: string | Emoji
|
||||
onHide: () => void
|
||||
onRun: (submitData: Record<string, any>) => void
|
||||
onStop: () => void
|
||||
runningStatus: NodeRunningStatus
|
||||
result?: JSX.Element
|
||||
forms: FormProps[]
|
||||
retryDetails?: NodeTracing[]
|
||||
onRetryDetailBack?: any
|
||||
}
|
||||
|
||||
function formatValue(value: string | any, type: InputVarType) {
|
||||
@ -50,12 +58,16 @@ function formatValue(value: string | any, type: InputVarType) {
|
||||
}
|
||||
const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||
nodeName,
|
||||
nodeType,
|
||||
toolIcon,
|
||||
onHide,
|
||||
onRun,
|
||||
onStop,
|
||||
runningStatus,
|
||||
result,
|
||||
forms,
|
||||
retryDetails,
|
||||
onRetryDetailBack = () => { },
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -122,48 +134,69 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||
<div className='text-base font-semibold text-gray-900 truncate'>
|
||||
{t(`${i18nPrefix}.testRun`)} {nodeName}
|
||||
</div>
|
||||
<div className='ml-2 shrink-0 p-1 cursor-pointer' onClick={onHide}>
|
||||
<div className='ml-2 shrink-0 p-1 cursor-pointer' onClick={() => {
|
||||
onHide()
|
||||
}}>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-500 ' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<div className='mt-3 px-4 space-y-4'>
|
||||
{forms.map((form, index) => (
|
||||
<div key={index}>
|
||||
<Form
|
||||
key={index}
|
||||
className={cn(index < forms.length - 1 && 'mb-4')}
|
||||
{...form}
|
||||
/>
|
||||
{index < forms.length - 1 && <Split />}
|
||||
{
|
||||
retryDetails?.length && (
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<RetryResultPanel
|
||||
list={retryDetails.map((item, index) => ({
|
||||
...item,
|
||||
title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`,
|
||||
node_type: nodeType!,
|
||||
extras: {
|
||||
icon: toolIcon!,
|
||||
},
|
||||
}))}
|
||||
onBack={onRetryDetailBack}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!retryDetails?.length && (
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<div className='mt-3 px-4 space-y-4'>
|
||||
{forms.map((form, index) => (
|
||||
<div key={index}>
|
||||
<Form
|
||||
key={index}
|
||||
className={cn(index < forms.length - 1 && 'mb-4')}
|
||||
{...form}
|
||||
/>
|
||||
{index < forms.length - 1 && <Split />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
||||
{isRunning && (
|
||||
<div
|
||||
className='p-2 rounded-lg border border-gray-200 bg-white shadow-xs cursor-pointer'
|
||||
onClick={onStop}
|
||||
>
|
||||
<StopCircle className='w-4 h-4 text-gray-500' />
|
||||
<div className='mt-4 flex justify-between space-x-2 px-4' >
|
||||
{isRunning && (
|
||||
<div
|
||||
className='p-2 rounded-lg border border-gray-200 bg-white shadow-xs cursor-pointer'
|
||||
onClick={onStop}
|
||||
>
|
||||
<StopCircle className='w-4 h-4 text-gray-500' />
|
||||
</div>
|
||||
)}
|
||||
<Button disabled={!isFileLoaded || isRunning} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
|
||||
{isRunning && <RiLoader2Line className='animate-spin w-4 h-4 text-white' />}
|
||||
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<Button disabled={!isFileLoaded || isRunning} variant='primary' className='w-0 grow space-x-2' onClick={handleRun}>
|
||||
{isRunning && <RiLoader2Line className='animate-spin w-4 h-4 text-white' />}
|
||||
<div>{t(`${i18nPrefix}.${isRunning ? 'running' : 'startRun'}`)}</div>
|
||||
</Button>
|
||||
</div>
|
||||
{isRunning && (
|
||||
<ResultPanel status='running' showSteps={false} />
|
||||
)}
|
||||
{isFinished && (
|
||||
<>
|
||||
{result}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isRunning && (
|
||||
<ResultPanel status='running' showSteps={false} />
|
||||
)}
|
||||
{isFinished && (
|
||||
<>
|
||||
{result}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -14,7 +14,6 @@ import type {
|
||||
CommonNodeType,
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type ErrorHandleProps = Pick<Node, 'id' | 'data'>
|
||||
@ -45,7 +44,6 @@ const ErrorHandle = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Split />
|
||||
<div className='py-4'>
|
||||
<Collapse
|
||||
disabled={!error_strategy}
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type { WorkflowRetryConfig } from './types'
|
||||
import {
|
||||
useNodeDataUpdate,
|
||||
} from '@/app/components/workflow/hooks'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
export const useRetryConfig = (
|
||||
id: string,
|
||||
) => {
|
||||
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||
|
||||
const handleRetryConfigChange = useCallback((value?: WorkflowRetryConfig) => {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data: {
|
||||
retry_config: value,
|
||||
},
|
||||
})
|
||||
}, [id, handleNodeDataUpdateWithSyncDraft])
|
||||
|
||||
return {
|
||||
handleRetryConfigChange,
|
||||
}
|
||||
}
|
||||
|
||||
export const useRetryDetailShowInSingleRun = () => {
|
||||
const [retryDetails, setRetryDetails] = useState<NodeTracing[] | undefined>()
|
||||
|
||||
const handleRetryDetailsChange = useCallback((details: NodeTracing[] | undefined) => {
|
||||
setRetryDetails(details)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
retryDetails,
|
||||
handleRetryDetailsChange,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiAlertFill,
|
||||
RiCheckboxCircleFill,
|
||||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { NodeRunningStatus } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type RetryOnNodeProps = Pick<Node, 'id' | 'data'>
|
||||
const RetryOnNode = ({
|
||||
data,
|
||||
}: RetryOnNodeProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { retry_config } = data
|
||||
const showSelectedBorder = data.selected || data._isBundled || data._isEntering
|
||||
const {
|
||||
isRunning,
|
||||
isSuccessful,
|
||||
isException,
|
||||
isFailed,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
isRunning: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder,
|
||||
isSuccessful: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder,
|
||||
isFailed: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder,
|
||||
isException: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder,
|
||||
}
|
||||
}, [data._runningStatus, showSelectedBorder])
|
||||
const showDefault = !isRunning && !isSuccessful && !isException && !isFailed
|
||||
|
||||
if (!retry_config)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='px-3'>
|
||||
<div className={cn(
|
||||
'flex items-center justify-between px-[5px] py-1 bg-workflow-block-parma-bg border-[0.5px] border-transparent rounded-md system-xs-medium-uppercase text-text-tertiary',
|
||||
isRunning && 'bg-state-accent-hover border-state-accent-active text-text-accent',
|
||||
isSuccessful && 'bg-state-success-hover border-state-success-active text-text-success',
|
||||
(isException || isFailed) && 'bg-state-warning-hover border-state-warning-active text-text-warning',
|
||||
)}>
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
showDefault && (
|
||||
t('workflow.nodes.common.retry.retryTimes', { times: retry_config.max_retries })
|
||||
)
|
||||
}
|
||||
{
|
||||
isRunning && (
|
||||
<>
|
||||
<RiLoader2Line className='animate-spin mr-1 w-3.5 h-3.5' />
|
||||
{t('workflow.nodes.common.retry.retrying')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
isSuccessful && (
|
||||
<>
|
||||
<RiCheckboxCircleFill className='mr-1 w-3.5 h-3.5' />
|
||||
{t('workflow.nodes.common.retry.retrySuccessful')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
(isFailed || isException) && (
|
||||
<>
|
||||
<RiAlertFill className='mr-1 w-3.5 h-3.5' />
|
||||
{t('workflow.nodes.common.retry.retryFailed')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
!showDefault && (
|
||||
<div>
|
||||
{data._retryIndex}/{data.retry_config?.max_retries}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RetryOnNode
|
||||
@ -0,0 +1,117 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRetryConfig } from './hooks'
|
||||
import s from './style.module.css'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import Input from '@/app/components/base/input'
|
||||
import type {
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
|
||||
type RetryOnPanelProps = Pick<Node, 'id' | 'data'>
|
||||
const RetryOnPanel = ({
|
||||
id,
|
||||
data,
|
||||
}: RetryOnPanelProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleRetryConfigChange } = useRetryConfig(id)
|
||||
const { retry_config } = data
|
||||
|
||||
const handleRetryEnabledChange = (value: boolean) => {
|
||||
handleRetryConfigChange({
|
||||
retry_enabled: value,
|
||||
max_retries: retry_config?.max_retries || 3,
|
||||
retry_interval: retry_config?.retry_interval || 1000,
|
||||
})
|
||||
}
|
||||
|
||||
const handleMaxRetriesChange = (value: number) => {
|
||||
if (value > 10)
|
||||
value = 10
|
||||
else if (value < 1)
|
||||
value = 1
|
||||
handleRetryConfigChange({
|
||||
retry_enabled: true,
|
||||
max_retries: value,
|
||||
retry_interval: retry_config?.retry_interval || 1000,
|
||||
})
|
||||
}
|
||||
|
||||
const handleRetryIntervalChange = (value: number) => {
|
||||
if (value > 5000)
|
||||
value = 5000
|
||||
else if (value < 100)
|
||||
value = 100
|
||||
handleRetryConfigChange({
|
||||
retry_enabled: true,
|
||||
max_retries: retry_config?.max_retries || 3,
|
||||
retry_interval: value,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='pt-2'>
|
||||
<div className='flex items-center justify-between px-4 py-2 h-10'>
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-0.5 system-sm-semibold-uppercase text-text-secondary'>{t('workflow.nodes.common.retry.retryOnFailure')}</div>
|
||||
</div>
|
||||
<Switch
|
||||
defaultValue={retry_config?.retry_enabled}
|
||||
onChange={v => handleRetryEnabledChange(v)}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
retry_config?.retry_enabled && (
|
||||
<div className='px-4 pb-2'>
|
||||
<div className='flex items-center mb-1 w-full'>
|
||||
<div className='grow mr-2 system-xs-medium-uppercase'>{t('workflow.nodes.common.retry.maxRetries')}</div>
|
||||
<Slider
|
||||
className='mr-3 w-[108px]'
|
||||
value={retry_config?.max_retries || 3}
|
||||
onChange={handleMaxRetriesChange}
|
||||
min={1}
|
||||
max={10}
|
||||
/>
|
||||
<Input
|
||||
type='number'
|
||||
wrapperClassName='w-[80px]'
|
||||
value={retry_config?.max_retries || 3}
|
||||
onChange={e => handleMaxRetriesChange(e.target.value as any)}
|
||||
min={1}
|
||||
max={10}
|
||||
unit={t('workflow.nodes.common.retry.times') || ''}
|
||||
className={s.input}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<div className='grow mr-2 system-xs-medium-uppercase'>{t('workflow.nodes.common.retry.retryInterval')}</div>
|
||||
<Slider
|
||||
className='mr-3 w-[108px]'
|
||||
value={retry_config?.retry_interval || 1000}
|
||||
onChange={handleRetryIntervalChange}
|
||||
min={100}
|
||||
max={5000}
|
||||
/>
|
||||
<Input
|
||||
type='number'
|
||||
wrapperClassName='w-[80px]'
|
||||
value={retry_config?.retry_interval || 1000}
|
||||
onChange={e => handleRetryIntervalChange(e.target.value as any)}
|
||||
min={100}
|
||||
max={5000}
|
||||
unit={t('workflow.nodes.common.retry.ms') || ''}
|
||||
className={s.input}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<Split className='mx-4 mt-2' />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default RetryOnPanel
|
||||
@ -0,0 +1,5 @@
|
||||
.input::-webkit-inner-spin-button,
|
||||
.input::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
export type WorkflowRetryConfig = {
|
||||
max_retries: number
|
||||
retry_interval: number
|
||||
retry_enabled: boolean
|
||||
}
|
||||
@ -25,7 +25,10 @@ import {
|
||||
useNodesReadOnly,
|
||||
useToolIcon,
|
||||
} from '../../hooks'
|
||||
import { hasErrorHandleNode } from '../../utils'
|
||||
import {
|
||||
hasErrorHandleNode,
|
||||
hasRetryNode,
|
||||
} from '../../utils'
|
||||
import { useNodeIterationInteractions } from '../iteration/use-interactions'
|
||||
import type { IterationNodeType } from '../iteration/types'
|
||||
import {
|
||||
@ -35,6 +38,7 @@ import {
|
||||
import NodeResizer from './components/node-resizer'
|
||||
import NodeControl from './components/node-control'
|
||||
import ErrorHandleOnNode from './components/error-handle/error-handle-on-node'
|
||||
import RetryOnNode from './components/retry/retry-on-node'
|
||||
import AddVariablePopupWithPosition from './components/add-variable-popup-with-position'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
@ -237,6 +241,14 @@ const BaseNode: FC<BaseNodeProps> = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasRetryNode(data.type) && (
|
||||
<RetryOnNode
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasErrorHandleNode(data.type) && (
|
||||
<ErrorHandleOnNode
|
||||
|
||||
@ -21,9 +21,11 @@ import {
|
||||
TitleInput,
|
||||
} from './components/title-description-input'
|
||||
import ErrorHandleOnPanel from './components/error-handle/error-handle-on-panel'
|
||||
import RetryOnPanel from './components/retry/retry-on-panel'
|
||||
import { useResizePanel } from './hooks/use-resize-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import {
|
||||
WorkflowHistoryEvent,
|
||||
useAvailableBlocks,
|
||||
@ -38,6 +40,7 @@ import {
|
||||
import {
|
||||
canRunBySingle,
|
||||
hasErrorHandleNode,
|
||||
hasRetryNode,
|
||||
} from '@/app/components/workflow/utils'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
@ -168,6 +171,15 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
<div>
|
||||
{cloneElement(children, { id, data })}
|
||||
</div>
|
||||
<Split />
|
||||
{
|
||||
hasRetryNode(data.type) && (
|
||||
<RetryOnPanel
|
||||
id={id}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasErrorHandleNode(data.type) && (
|
||||
<ErrorHandleOnPanel
|
||||
|
||||
@ -2,7 +2,10 @@ import { BlockEnum } from '../../types'
|
||||
import type { NodeDefault } from '../../types'
|
||||
import { AuthorizationType, BodyType, Method } from './types'
|
||||
import type { BodyPayload, HttpNodeType } from './types'
|
||||
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/constants'
|
||||
import {
|
||||
ALL_CHAT_AVAILABLE_BLOCKS,
|
||||
ALL_COMPLETION_AVAILABLE_BLOCKS,
|
||||
} from '@/app/components/workflow/constants'
|
||||
|
||||
const nodeDefault: NodeDefault<HttpNodeType> = {
|
||||
defaultValue: {
|
||||
@ -24,6 +27,11 @@ const nodeDefault: NodeDefault<HttpNodeType> = {
|
||||
max_read_timeout: 0,
|
||||
max_write_timeout: 0,
|
||||
},
|
||||
retry_config: {
|
||||
retry_enabled: true,
|
||||
max_retries: 3,
|
||||
retry_interval: 100,
|
||||
},
|
||||
},
|
||||
getAvailablePrevNodes(isChatMode: boolean) {
|
||||
const nodes = isChatMode
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import useConfig from './use-config'
|
||||
import ApiInput from './components/api-input'
|
||||
@ -18,6 +18,7 @@ import { FileArrow01 } from '@/app/components/base/icons/src/vender/line/files'
|
||||
import type { NodePanelProps } from '@/app/components/workflow/types'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.http'
|
||||
|
||||
@ -60,6 +61,10 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
hideCurlPanel,
|
||||
handleCurlImport,
|
||||
} = useConfig(id, data)
|
||||
const {
|
||||
retryDetails,
|
||||
handleRetryDetailsChange,
|
||||
} = useRetryDetailShowInSingleRun()
|
||||
// To prevent prompt editor in body not update data.
|
||||
if (!isDataReady)
|
||||
return null
|
||||
@ -181,6 +186,7 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
{isShowSingleRun && (
|
||||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
nodeType={inputs.type}
|
||||
onHide={hideSingleRun}
|
||||
forms={[
|
||||
{
|
||||
@ -192,7 +198,9 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
runningStatus={runningStatus}
|
||||
onRun={handleRun}
|
||||
onStop={handleStop}
|
||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
||||
retryDetails={retryDetails}
|
||||
onRetryDetailBack={handleRetryDetailsChange}
|
||||
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
|
||||
/>
|
||||
)}
|
||||
{(isShowCurlPanel && !readOnly) && (
|
||||
@ -207,4 +215,4 @@ const Panel: FC<NodePanelProps<HttpNodeType>> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Panel)
|
||||
export default memo(Panel)
|
||||
|
||||
@ -129,9 +129,6 @@ export const getMultipleRetrievalConfig = (
|
||||
reranking_enable: ((allInternal && allEconomic) || allExternal) ? reranking_enable : true,
|
||||
}
|
||||
|
||||
if (!rerankModelIsValid)
|
||||
result.reranking_model = undefined
|
||||
|
||||
const setDefaultWeights = () => {
|
||||
result.weights = {
|
||||
vector_setting: {
|
||||
@ -198,7 +195,6 @@ export const getMultipleRetrievalConfig = (
|
||||
setDefaultWeights()
|
||||
}
|
||||
}
|
||||
|
||||
if (reranking_mode === RerankingModeEnum.RerankingModel && !rerankModelIsValid && shouldSetWeightDefaultValue) {
|
||||
result.reranking_mode = RerankingModeEnum.WeightedScore
|
||||
setDefaultWeights()
|
||||
|
||||
@ -19,6 +19,7 @@ import type { Props as FormProps } from '@/app/components/workflow/nodes/_base/c
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Editor from '@/app/components/workflow/nodes/_base/components/prompt/editor'
|
||||
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.llm'
|
||||
|
||||
@ -69,6 +70,10 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
runResult,
|
||||
filterJinjia2InputVar,
|
||||
} = useConfig(id, data)
|
||||
const {
|
||||
retryDetails,
|
||||
handleRetryDetailsChange,
|
||||
} = useRetryDetailShowInSingleRun()
|
||||
|
||||
const model = inputs.model
|
||||
|
||||
@ -282,12 +287,15 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
{isShowSingleRun && (
|
||||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
nodeType={inputs.type}
|
||||
onHide={hideSingleRun}
|
||||
forms={singleRunForms}
|
||||
runningStatus={runningStatus}
|
||||
onRun={handleRun}
|
||||
onStop={handleStop}
|
||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
||||
retryDetails={retryDetails}
|
||||
onRetryDetailBack={handleRetryDetailsChange}
|
||||
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -207,7 +207,7 @@ const InputVarList: FC<Props> = ({
|
||||
readonly={readOnly}
|
||||
isShowNodeName
|
||||
nodeId={nodeId}
|
||||
value={varInput?.type === VarKindType.constant ? (varInput?.value || '') : (varInput?.value || [])}
|
||||
value={varInput?.type === VarKindType.constant ? (varInput?.value ?? '') : (varInput?.value ?? [])}
|
||||
onChange={handleNotMixedTypeChange(variable)}
|
||||
onOpen={handleOpen(index)}
|
||||
defaultVarKindType={varInput?.type || (isNumber ? VarKindType.constant : VarKindType.variable)}
|
||||
|
||||
@ -14,6 +14,8 @@ import Loading from '@/app/components/base/loading'
|
||||
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
|
||||
import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars'
|
||||
import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import { useRetryDetailShowInSingleRun } from '@/app/components/workflow/nodes/_base/components/retry/hooks'
|
||||
import { useToolIcon } from '@/app/components/workflow/hooks'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.tool'
|
||||
|
||||
@ -48,6 +50,11 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
||||
handleStop,
|
||||
runResult,
|
||||
} = useConfig(id, data)
|
||||
const toolIcon = useToolIcon(data)
|
||||
const {
|
||||
retryDetails,
|
||||
handleRetryDetailsChange,
|
||||
} = useRetryDetailShowInSingleRun()
|
||||
|
||||
if (isLoading) {
|
||||
return <div className='flex h-[200px] items-center justify-center'>
|
||||
@ -143,12 +150,16 @@ const Panel: FC<NodePanelProps<ToolNodeType>> = ({
|
||||
{isShowSingleRun && (
|
||||
<BeforeRunForm
|
||||
nodeName={inputs.title}
|
||||
nodeType={inputs.type}
|
||||
toolIcon={toolIcon}
|
||||
onHide={hideSingleRun}
|
||||
forms={singleRunForms}
|
||||
runningStatus={runningStatus}
|
||||
onRun={handleRun}
|
||||
onStop={handleStop}
|
||||
result={<ResultPanel {...runResult} showSteps={false} />}
|
||||
retryDetails={retryDetails}
|
||||
onRetryDetailBack={handleRetryDetailsChange}
|
||||
result={<ResultPanel {...runResult} showSteps={false} onShowRetryDetail={handleRetryDetailsChange} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -78,9 +78,9 @@ const AddBlock = ({
|
||||
title={t('workflow.common.addBlock')}
|
||||
>
|
||||
<div className={cn(
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
open && '!bg-black/5',
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary cursor-pointer',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
open && 'bg-state-accent-active text-text-accent',
|
||||
)}>
|
||||
<RiAddCircleFill className='w-4 h-4' />
|
||||
</div>
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
ControlMode,
|
||||
} from '../types'
|
||||
import { useStore } from '../store'
|
||||
import Divider from '../../base/divider'
|
||||
import AddBlock from './add-block'
|
||||
import TipPopup from './tip-popup'
|
||||
import { useOperator } from './hooks'
|
||||
@ -43,26 +44,26 @@ const Control = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-gray-100 bg-white shadow-lg text-gray-500'>
|
||||
<div className='flex items-center p-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg shadow-lg text-text-tertiary'>
|
||||
<AddBlock />
|
||||
<TipPopup title={t('workflow.nodes.note.addNote')}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center ml-[1px] w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
'flex items-center justify-center ml-[1px] w-8 h-8 rounded-lg hover:bg-state-base-hover hover:text-text-secondary cursor-pointer',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
)}
|
||||
onClick={addNote}
|
||||
>
|
||||
<RiStickyNoteAddLine className='w-4 h-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
<div className='mx-[3px] w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-0.5' />
|
||||
<TipPopup title={t('workflow.common.pointerMode')} shortcuts={['v']}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center mr-[1px] w-8 h-8 rounded-lg cursor-pointer',
|
||||
controlMode === ControlMode.Pointer ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
controlMode === ControlMode.Pointer ? 'bg-state-accent-active text-text-accent' : 'hover:bg-state-base-hover hover:text-text-secondary',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
)}
|
||||
onClick={handleModePointer}
|
||||
>
|
||||
@ -73,20 +74,20 @@ const Control = () => {
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer',
|
||||
controlMode === ControlMode.Hand ? 'bg-primary-50 text-primary-600' : 'hover:bg-black/5 hover:text-gray-700',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
controlMode === ControlMode.Hand ? 'bg-state-accent-active text-text-accent' : 'hover:bg-state-base-hover hover:text-text-secondary',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
)}
|
||||
onClick={handleModeHand}
|
||||
>
|
||||
<RiHand className='w-4 h-4' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
<div className='mx-[3px] w-[1px] h-3.5 bg-gray-200'></div>
|
||||
<Divider type='vertical' className='h-3.5 mx-0.5' />
|
||||
<TipPopup title={t('workflow.panel.organizeBlocks')} shortcuts={['ctrl', 'o']}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
|
||||
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
|
||||
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-state-base-hover hover:text-text-secondary cursor-pointer',
|
||||
`${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
|
||||
)}
|
||||
onClick={handleLayout}
|
||||
>
|
||||
|
||||
@ -17,7 +17,9 @@ const Operator = ({ handleUndo, handleRedo }: OperatorProps) => {
|
||||
width: 102,
|
||||
height: 72,
|
||||
}}
|
||||
className='!absolute !left-4 !bottom-14 z-[9] !m-0 !w-[102px] !h-[72px] !border-[0.5px] !border-black/8 !rounded-lg !shadow-lg'
|
||||
maskColor='var(--color-shadow-shadow-5)'
|
||||
className='!absolute !left-4 !bottom-14 z-[9] !m-0 !w-[102px] !h-[72px] !border-[0.5px] !border-divider-subtle
|
||||
!rounded-lg !shadow-md !shadow-shadow-shadow-5 !bg-workflow-minimap-bg'
|
||||
/>
|
||||
<div className='flex items-center mt-1 gap-2 absolute left-4 bottom-4 z-[9]'>
|
||||
<ZoomInOut />
|
||||
|
||||
@ -15,12 +15,12 @@ const TipPopup = ({
|
||||
return (
|
||||
<Tooltip
|
||||
offset={4}
|
||||
popupClassName='!p-0 !bg-gray-25'
|
||||
popupClassName='p-0 bg-transparent'
|
||||
popupContent={
|
||||
<div className='flex items-center gap-1 px-2 h-6 text-xs font-medium text-gray-700 rounded-lg border-[0.5px] border-black/5'>
|
||||
{title}
|
||||
<div className='flex items-center gap-1 p-1.5 backdrop-blur-[5px] shadow-lg rounded-lg border-[0.5px] border-components-panel-border bg-components-tooltip-bg'>
|
||||
<span className='system-xs-medium text-text-secondary'>{title}</span>
|
||||
{
|
||||
shortcuts && <ShortcutsName keys={shortcuts} className='!text-[11px]' />
|
||||
shortcuts && <ShortcutsName keys={shortcuts} />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -18,10 +18,9 @@ import {
|
||||
useNodesSyncDraft,
|
||||
useWorkflowReadOnly,
|
||||
} from '../hooks'
|
||||
import {
|
||||
getKeyboardKeyNameBySystem,
|
||||
} from '../utils'
|
||||
|
||||
import ShortcutsName from '../shortcuts-name'
|
||||
import Divider from '../../base/divider'
|
||||
import TipPopup from './tip-popup'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
@ -132,53 +131,54 @@ const ZoomInOut: FC = () => {
|
||||
>
|
||||
<PortalToFollowElemTrigger asChild onClick={handleTrigger}>
|
||||
<div className={`
|
||||
p-0.5 h-9 cursor-pointer text-[13px] text-gray-500 font-medium rounded-lg bg-white shadow-lg border-[0.5px] border-gray-100
|
||||
p-0.5 h-9 cursor-pointer text-[13px] backdrop-blur-[5px] rounded-lg
|
||||
bg-components-actionbar-bg shadow-lg border-[0.5px] border-components-actionbar-border
|
||||
hover:bg-state-base-hover
|
||||
${workflowReadOnly && '!cursor-not-allowed opacity-50'}
|
||||
`}>
|
||||
<div className={cn(
|
||||
'flex items-center justify-between w-[98px] h-8 hover:bg-gray-50 rounded-lg',
|
||||
open && 'bg-gray-50',
|
||||
'flex items-center justify-between w-[98px] h-8 rounded-lg',
|
||||
)}>
|
||||
<TipPopup
|
||||
title={t('workflow.operator.zoomOut')}
|
||||
shortcuts={['ctrl', '-']}
|
||||
>
|
||||
<div
|
||||
className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer hover:bg-black/5'
|
||||
className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
zoomOut()
|
||||
}}
|
||||
>
|
||||
<RiZoomOutLine className='w-4 h-4' />
|
||||
<RiZoomOutLine className='w-4 h-4 text-text-tertiary hover:text-text-secondary' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
<div className='w-[34px]'>{parseFloat(`${zoom * 100}`).toFixed(0)}%</div>
|
||||
<div className={cn('w-[34px] system-sm-medium text-text-tertiary hover:text-text-secondary')}>{parseFloat(`${zoom * 100}`).toFixed(0)}%</div>
|
||||
<TipPopup
|
||||
title={t('workflow.operator.zoomIn')}
|
||||
shortcuts={['ctrl', '+']}
|
||||
>
|
||||
<div
|
||||
className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer hover:bg-black/5'
|
||||
className='flex items-center justify-center w-8 h-8 rounded-lg cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
zoomIn()
|
||||
}}
|
||||
>
|
||||
<RiZoomInLine className='w-4 h-4' />
|
||||
<RiZoomInLine className='w-4 h-4 text-text-tertiary hover:text-text-secondary' />
|
||||
</div>
|
||||
</TipPopup>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-10'>
|
||||
<div className='w-[145px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg'>
|
||||
<div className='w-[145px] backdrop-blur-[5px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
|
||||
{
|
||||
ZOOM_IN_OUT_OPTIONS.map((options, i) => (
|
||||
<Fragment key={i}>
|
||||
{
|
||||
i !== 0 && (
|
||||
<div className='h-[1px] bg-gray-100' />
|
||||
<Divider className='m-0' />
|
||||
)
|
||||
}
|
||||
<div className='p-1'>
|
||||
@ -186,25 +186,27 @@ const ZoomInOut: FC = () => {
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option.key}
|
||||
className='flex items-center justify-between px-3 h-8 rounded-lg hover:bg-gray-50 cursor-pointer text-sm text-gray-700'
|
||||
className='flex items-center justify-between space-x-1 py-1.5 pl-3 pr-2 h-8 rounded-lg hover:bg-state-base-hover cursor-pointer system-md-regular text-text-secondary'
|
||||
onClick={() => handleZoom(option.key)}
|
||||
>
|
||||
{option.text}
|
||||
{
|
||||
option.key === ZoomType.zoomToFit && (
|
||||
<ShortcutsName keys={[`${getKeyboardKeyNameBySystem('ctrl')}`, '1']} />
|
||||
)
|
||||
}
|
||||
{
|
||||
option.key === ZoomType.zoomTo50 && (
|
||||
<ShortcutsName keys={['shift', '5']} />
|
||||
)
|
||||
}
|
||||
{
|
||||
option.key === ZoomType.zoomTo100 && (
|
||||
<ShortcutsName keys={['shift', '1']} />
|
||||
)
|
||||
}
|
||||
<span>{option.text}</span>
|
||||
<div className='flex items-center space-x-0.5'>
|
||||
{
|
||||
option.key === ZoomType.zoomToFit && (
|
||||
<ShortcutsName keys={['ctrl', '1']} />
|
||||
)
|
||||
}
|
||||
{
|
||||
option.key === ZoomType.zoomTo50 && (
|
||||
<ShortcutsName keys={['shift', '5']} />
|
||||
)
|
||||
}
|
||||
{
|
||||
option.key === ZoomType.zoomTo100 && (
|
||||
<ShortcutsName keys={['shift', '1']} />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useClickAway } from 'ahooks'
|
||||
import Divider from '../base/divider'
|
||||
import ShortcutsName from './shortcuts-name'
|
||||
import { useStore } from './store'
|
||||
import {
|
||||
@ -41,7 +42,7 @@ const PanelContextmenu = () => {
|
||||
const renderTrigger = () => {
|
||||
return (
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
>
|
||||
{t('workflow.common.addBlock')}
|
||||
</div>
|
||||
@ -53,7 +54,7 @@ const PanelContextmenu = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='absolute w-[200px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-xl z-[9]'
|
||||
className='absolute w-[200px] rounded-lg border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg z-[9]'
|
||||
style={{
|
||||
left: panelMenu.left,
|
||||
top: panelMenu.top,
|
||||
@ -69,7 +70,7 @@ const PanelContextmenu = () => {
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleAddNote()
|
||||
@ -79,7 +80,7 @@ const PanelContextmenu = () => {
|
||||
{t('workflow.nodes.note.addNote')}
|
||||
</div>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
handleStartWorkflowRun()
|
||||
handlePaneContextmenuCancel()
|
||||
@ -89,12 +90,12 @@ const PanelContextmenu = () => {
|
||||
<ShortcutsName keys={['alt', 'r']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[1px] bg-gray-100'></div>
|
||||
<Divider className='m-0' />
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer',
|
||||
!clipboardElements.length ? 'opacity-50 cursor-not-allowed' : 'hover:bg-gray-50',
|
||||
'flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer',
|
||||
!clipboardElements.length ? 'opacity-50 cursor-not-allowed' : 'hover:bg-state-base-hover',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (clipboardElements.length) {
|
||||
@ -107,16 +108,16 @@ const PanelContextmenu = () => {
|
||||
<ShortcutsName keys={['ctrl', 'v']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[1px] bg-gray-100'></div>
|
||||
<Divider className='m-0' />
|
||||
<div className='p-1'>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => exportCheck()}
|
||||
>
|
||||
{t('app.export')}
|
||||
</div>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
className='flex items-center justify-between px-3 h-8 text-sm text-text-secondary rounded-lg cursor-pointer hover:bg-state-base-hover'
|
||||
onClick={() => setShowImportDSLModal(true)}
|
||||
>
|
||||
{t('workflow.common.importDSL')}
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
getProcessedFilesFromResponse,
|
||||
} from '@/app/components/base/file-uploader/utils'
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
type GetAbortController = (abortController: AbortController) => void
|
||||
interface SendCallback {
|
||||
@ -381,6 +382,28 @@ export const useChat = (
|
||||
}
|
||||
}))
|
||||
},
|
||||
onNodeRetry: ({ data }) => {
|
||||
if (data.iteration_id)
|
||||
return
|
||||
|
||||
const currentIndex = responseItem.workflowProcess!.tracing!.findIndex((item) => {
|
||||
if (!item.execution_metadata?.parallel_id)
|
||||
return item.node_id === data.node_id
|
||||
return item.node_id === data.node_id && (item.execution_metadata?.parallel_id === data.execution_metadata?.parallel_id || item.parallel_id === data.execution_metadata?.parallel_id)
|
||||
})
|
||||
if (responseItem.workflowProcess!.tracing[currentIndex].retryDetail)
|
||||
responseItem.workflowProcess!.tracing[currentIndex].retryDetail?.push(data as NodeTracing)
|
||||
else
|
||||
responseItem.workflowProcess!.tracing[currentIndex].retryDetail = [data as NodeTracing]
|
||||
|
||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||
const currentIndex = draft.findIndex(item => item.id === responseItem.id)
|
||||
draft[currentIndex] = {
|
||||
...draft[currentIndex],
|
||||
...responseItem,
|
||||
}
|
||||
}))
|
||||
},
|
||||
onNodeFinished: ({ data }) => {
|
||||
if (data.iteration_id)
|
||||
return
|
||||
@ -394,6 +417,9 @@ export const useChat = (
|
||||
...(responseItem.workflowProcess!.tracing[currentIndex]?.extras
|
||||
? { extras: responseItem.workflowProcess!.tracing[currentIndex].extras }
|
||||
: {}),
|
||||
...(responseItem.workflowProcess!.tracing[currentIndex]?.retryDetail
|
||||
? { retryDetail: responseItem.workflowProcess!.tracing[currentIndex].retryDetail }
|
||||
: {}),
|
||||
...data,
|
||||
} as any
|
||||
handleUpdateChatList(produce(chatListRef.current, (draft) => {
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
import { SimpleBtn } from '../../app/text-generate/item'
|
||||
import Toast from '../../base/toast'
|
||||
import IterationResultPanel from '../run/iteration-result-panel'
|
||||
import RetryResultPanel from '../run/retry-result-panel'
|
||||
import InputsPanel from './inputs-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@ -53,11 +54,16 @@ const WorkflowPreview = () => {
|
||||
}, [workflowRunningData])
|
||||
|
||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
||||
const [retryRunResult, setRetryRunResult] = useState<NodeTracing[]>([])
|
||||
const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({})
|
||||
const [isShowIterationDetail, {
|
||||
setTrue: doShowIterationDetail,
|
||||
setFalse: doHideIterationDetail,
|
||||
}] = useBoolean(false)
|
||||
const [isShowRetryDetail, {
|
||||
setTrue: doShowRetryDetail,
|
||||
setFalse: doHideRetryDetail,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterationDurationMap: IterationDurationMap) => {
|
||||
setIterDurationMap(iterationDurationMap)
|
||||
@ -65,6 +71,11 @@ const WorkflowPreview = () => {
|
||||
doShowIterationDetail()
|
||||
}, [doShowIterationDetail])
|
||||
|
||||
const handleRetryDetail = useCallback((detail: NodeTracing[]) => {
|
||||
setRetryRunResult(detail)
|
||||
doShowRetryDetail()
|
||||
}, [doShowRetryDetail])
|
||||
|
||||
if (isShowIterationDetail) {
|
||||
return (
|
||||
<div className={`
|
||||
@ -201,11 +212,12 @@ const WorkflowPreview = () => {
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
{currentTab === 'TRACING' && (
|
||||
{currentTab === 'TRACING' && !isShowRetryDetail && (
|
||||
<TracingPanel
|
||||
className='bg-background-section-burn'
|
||||
list={workflowRunningData?.tracing || []}
|
||||
onShowIterationDetail={handleShowIterationDetail}
|
||||
onShowRetryDetail={handleRetryDetail}
|
||||
/>
|
||||
)}
|
||||
{currentTab === 'TRACING' && !workflowRunningData?.tracing?.length && (
|
||||
@ -213,7 +225,14 @@ const WorkflowPreview = () => {
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{
|
||||
currentTab === 'TRACING' && isShowRetryDetail && (
|
||||
<RetryResultPanel
|
||||
list={retryRunResult}
|
||||
onBack={doHideRetryDetail}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -9,6 +9,7 @@ import OutputPanel from './output-panel'
|
||||
import ResultPanel from './result-panel'
|
||||
import TracingPanel from './tracing-panel'
|
||||
import IterationResultPanel from './iteration-result-panel'
|
||||
import RetryResultPanel from './retry-result-panel'
|
||||
import cn from '@/utils/classnames'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@ -107,6 +108,18 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
const processNonIterationNode = (item: NodeTracing) => {
|
||||
const { execution_metadata } = item
|
||||
if (!execution_metadata?.iteration_id) {
|
||||
if (item.status === 'retry') {
|
||||
const retryNode = result.find(node => node.node_id === item.node_id)
|
||||
|
||||
if (retryNode) {
|
||||
if (retryNode?.retryDetail)
|
||||
retryNode.retryDetail.push(item)
|
||||
else
|
||||
retryNode.retryDetail = [item]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
result.push(item)
|
||||
return
|
||||
}
|
||||
@ -181,10 +194,15 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
|
||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
||||
const [iterDurationMap, setIterDurationMap] = useState<IterationDurationMap>({})
|
||||
const [retryRunResult, setRetryRunResult] = useState<NodeTracing[]>([])
|
||||
const [isShowIterationDetail, {
|
||||
setTrue: doShowIterationDetail,
|
||||
setFalse: doHideIterationDetail,
|
||||
}] = useBoolean(false)
|
||||
const [isShowRetryDetail, {
|
||||
setTrue: doShowRetryDetail,
|
||||
setFalse: doHideRetryDetail,
|
||||
}] = useBoolean(false)
|
||||
|
||||
const handleShowIterationDetail = useCallback((detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => {
|
||||
setIterationRunResult(detail)
|
||||
@ -192,6 +210,11 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
setIterDurationMap(iterDurationMap)
|
||||
}, [doShowIterationDetail, setIterationRunResult, setIterDurationMap])
|
||||
|
||||
const handleShowRetryDetail = useCallback((detail: NodeTracing[]) => {
|
||||
setRetryRunResult(detail)
|
||||
doShowRetryDetail()
|
||||
}, [doShowRetryDetail, setRetryRunResult])
|
||||
|
||||
if (isShowIterationDetail) {
|
||||
return (
|
||||
<div className='grow relative flex flex-col'>
|
||||
@ -261,13 +284,22 @@ const RunPanel: FC<RunProps> = ({ hideResult, activeTab = 'RESULT', runID, getRe
|
||||
exceptionCounts={runDetail.exceptions_count}
|
||||
/>
|
||||
)}
|
||||
{!loading && currentTab === 'TRACING' && (
|
||||
{!loading && currentTab === 'TRACING' && !isShowRetryDetail && (
|
||||
<TracingPanel
|
||||
className='bg-background-section-burn'
|
||||
list={list}
|
||||
onShowIterationDetail={handleShowIterationDetail}
|
||||
onShowRetryDetail={handleShowRetryDetail}
|
||||
/>
|
||||
)}
|
||||
{
|
||||
!loading && currentTab === 'TRACING' && isShowRetryDetail && (
|
||||
<RetryResultPanel
|
||||
list={retryRunResult}
|
||||
onBack={doHideRetryDetail}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
RiCheckboxCircleFill,
|
||||
RiErrorWarningLine,
|
||||
RiLoader2Line,
|
||||
RiRestartFill,
|
||||
} from '@remixicon/react'
|
||||
import BlockIcon from '../block-icon'
|
||||
import { BlockEnum } from '../types'
|
||||
@ -20,6 +21,7 @@ import Button from '@/app/components/base/button'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
|
||||
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
|
||||
import { hasRetryNode } from '@/app/components/workflow/utils'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@ -28,8 +30,10 @@ type Props = {
|
||||
hideInfo?: boolean
|
||||
hideProcessDetail?: boolean
|
||||
onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void
|
||||
onShowRetryDetail?: (detail: NodeTracing[]) => void
|
||||
notShowIterationNav?: boolean
|
||||
justShowIterationNavArrow?: boolean
|
||||
justShowRetryNavArrow?: boolean
|
||||
}
|
||||
|
||||
const NodePanel: FC<Props> = ({
|
||||
@ -39,6 +43,7 @@ const NodePanel: FC<Props> = ({
|
||||
hideInfo = false,
|
||||
hideProcessDetail,
|
||||
onShowIterationDetail,
|
||||
onShowRetryDetail,
|
||||
notShowIterationNav,
|
||||
justShowIterationNavArrow,
|
||||
}) => {
|
||||
@ -88,11 +93,17 @@ const NodePanel: FC<Props> = ({
|
||||
}, [nodeInfo.expand, setCollapseState])
|
||||
|
||||
const isIterationNode = nodeInfo.node_type === BlockEnum.Iteration
|
||||
const isRetryNode = hasRetryNode(nodeInfo.node_type) && nodeInfo.retryDetail
|
||||
const handleOnShowIterationDetail = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
onShowIterationDetail?.(nodeInfo.details || [], nodeInfo?.iterDurationMap || nodeInfo.execution_metadata?.iteration_duration_map || {})
|
||||
}
|
||||
const handleOnShowRetryDetail = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
onShowRetryDetail?.(nodeInfo.retryDetail || [])
|
||||
}
|
||||
return (
|
||||
<div className={cn('px-2 py-1', className)}>
|
||||
<div className='group transition-all bg-background-default border border-components-panel-border rounded-[10px] shadow-xs hover:shadow-md'>
|
||||
@ -169,6 +180,19 @@ const NodePanel: FC<Props> = ({
|
||||
<Split className='mt-2' />
|
||||
</div>
|
||||
)}
|
||||
{isRetryNode && (
|
||||
<Button
|
||||
className='flex items-center justify-between mb-1 w-full'
|
||||
variant='tertiary'
|
||||
onClick={handleOnShowRetryDetail}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<RiRestartFill className='mr-0.5 w-4 h-4 text-components-button-tertiary-text flex-shrink-0' />
|
||||
{t('workflow.nodes.common.retry.retries', { num: nodeInfo.retryDetail?.length })}
|
||||
</div>
|
||||
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' />
|
||||
</Button>
|
||||
)}
|
||||
<div className={cn('mb-1', hideInfo && '!px-2 !py-0.5')}>
|
||||
{(nodeInfo.status === 'stopped') && (
|
||||
<StatusContainer status='stopped'>
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowRightSLine,
|
||||
RiRestartFill,
|
||||
} from '@remixicon/react'
|
||||
import StatusPanel from './status'
|
||||
import MetaData from './meta'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import ErrorHandleTip from '@/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
interface ResultPanelProps {
|
||||
inputs?: string
|
||||
@ -22,6 +28,8 @@ interface ResultPanelProps {
|
||||
showSteps?: boolean
|
||||
exceptionCounts?: number
|
||||
execution_metadata?: any
|
||||
retry_events?: NodeTracing[]
|
||||
onShowRetryDetail?: (retries: NodeTracing[]) => void
|
||||
}
|
||||
|
||||
const ResultPanel: FC<ResultPanelProps> = ({
|
||||
@ -38,8 +46,11 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
showSteps,
|
||||
exceptionCounts,
|
||||
execution_metadata,
|
||||
retry_events,
|
||||
onShowRetryDetail,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='bg-components-panel-bg py-2'>
|
||||
<div className='px-4 py-2'>
|
||||
@ -51,6 +62,23 @@ const ResultPanel: FC<ResultPanelProps> = ({
|
||||
exceptionCounts={exceptionCounts}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
retry_events?.length && onShowRetryDetail && (
|
||||
<div className='px-4'>
|
||||
<Button
|
||||
className='flex items-center justify-between w-full'
|
||||
variant='tertiary'
|
||||
onClick={() => onShowRetryDetail(retry_events)}
|
||||
>
|
||||
<div className='flex items-center'>
|
||||
<RiRestartFill className='mr-0.5 w-4 h-4 text-components-button-tertiary-text flex-shrink-0' />
|
||||
{t('workflow.nodes.common.retry.retries', { num: retry_events?.length })}
|
||||
</div>
|
||||
<RiArrowRightSLine className='w-4 h-4 text-components-button-tertiary-text flex-shrink-0' />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className='px-4 py-2 flex flex-col gap-2'>
|
||||
<CodeEditor
|
||||
readOnly
|
||||
|
||||
46
web/app/components/workflow/run/retry-result-panel.tsx
Normal file
46
web/app/components/workflow/run/retry-result-panel.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import { memo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowLeftLine,
|
||||
} from '@remixicon/react'
|
||||
import TracingPanel from './tracing-panel'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
|
||||
type Props = {
|
||||
list: NodeTracing[]
|
||||
onBack: () => void
|
||||
}
|
||||
|
||||
const RetryResultPanel: FC<Props> = ({
|
||||
list,
|
||||
onBack,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className='flex items-center px-4 h-8 text-text-accent-secondary bg-components-panel-bg system-sm-medium cursor-pointer'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
onBack()
|
||||
}}
|
||||
>
|
||||
<RiArrowLeftLine className='mr-1 w-4 h-4' />
|
||||
{t('workflow.singleRun.back')}
|
||||
</div>
|
||||
<TracingPanel
|
||||
list={list.map((item, index) => ({
|
||||
...item,
|
||||
title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`,
|
||||
}))}
|
||||
className='bg-background-section-burn'
|
||||
/>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
export default memo(RetryResultPanel)
|
||||
@ -21,6 +21,7 @@ import type { IterationDurationMap, NodeTracing } from '@/types/workflow'
|
||||
type TracingPanelProps = {
|
||||
list: NodeTracing[]
|
||||
onShowIterationDetail?: (detail: NodeTracing[][], iterDurationMap: IterationDurationMap) => void
|
||||
onShowRetryDetail?: (detail: NodeTracing[]) => void
|
||||
className?: string
|
||||
hideNodeInfo?: boolean
|
||||
hideNodeProcessDetail?: boolean
|
||||
@ -160,6 +161,7 @@ function buildLogTree(nodes: NodeTracing[], t: (key: string) => string): Tracing
|
||||
const TracingPanel: FC<TracingPanelProps> = ({
|
||||
list,
|
||||
onShowIterationDetail,
|
||||
onShowRetryDetail,
|
||||
className,
|
||||
hideNodeInfo = false,
|
||||
hideNodeProcessDetail = false,
|
||||
@ -251,7 +253,9 @@ const TracingPanel: FC<TracingPanelProps> = ({
|
||||
<NodePanel
|
||||
nodeInfo={node.data!}
|
||||
onShowIterationDetail={onShowIterationDetail}
|
||||
onShowRetryDetail={onShowRetryDetail}
|
||||
justShowIterationNavArrow={true}
|
||||
justShowRetryNavArrow={true}
|
||||
hideInfo={hideNodeInfo}
|
||||
hideProcessDetail={hideNodeProcessDetail}
|
||||
/>
|
||||
|
||||
@ -12,14 +12,14 @@ const ShortcutsName = ({
|
||||
}: ShortcutsNameProps) => {
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex items-center gap-0.5 h-4 text-xs text-gray-400',
|
||||
'flex items-center gap-0.5',
|
||||
className,
|
||||
)}>
|
||||
{
|
||||
keys.map(key => (
|
||||
<div
|
||||
key={key}
|
||||
className='capitalize'
|
||||
className='w-4 h-4 flex items-center justify-center bg-components-kbd-bg-gray rounded-[4px] system-kbd capitalize'
|
||||
>
|
||||
{getKeyboardKeyNameBySystem(key)}
|
||||
</div>
|
||||
|
||||
@ -19,4 +19,6 @@
|
||||
|
||||
#workflow-container .react-flow__node-custom-note {
|
||||
z-index: -1000 !important;
|
||||
}
|
||||
}
|
||||
|
||||
#workflow-container .react-flow {}
|
||||
@ -13,6 +13,7 @@ import type {
|
||||
DefaultValueForm,
|
||||
ErrorHandleTypeEnum,
|
||||
} from '@/app/components/workflow/nodes/_base/components/error-handle/types'
|
||||
import type { WorkflowRetryConfig } from '@/app/components/workflow/nodes/_base/components/retry/types'
|
||||
|
||||
export enum BlockEnum {
|
||||
Start = 'start',
|
||||
@ -68,6 +69,7 @@ export type CommonNodeType<T = {}> = {
|
||||
_iterationIndex?: number
|
||||
_inParallelHovering?: boolean
|
||||
_waitingRun?: boolean
|
||||
_retryIndex?: number
|
||||
isInIteration?: boolean
|
||||
iteration_id?: string
|
||||
selected?: boolean
|
||||
@ -77,6 +79,7 @@ export type CommonNodeType<T = {}> = {
|
||||
width?: number
|
||||
height?: number
|
||||
error_strategy?: ErrorHandleTypeEnum
|
||||
retry_config?: WorkflowRetryConfig
|
||||
default_value?: DefaultValueForm[]
|
||||
} & T & Partial<Pick<ToolDefaultValue, 'provider_id' | 'provider_type' | 'provider_name' | 'tool_name'>>
|
||||
|
||||
@ -293,6 +296,7 @@ export enum NodeRunningStatus {
|
||||
Succeeded = 'succeeded',
|
||||
Failed = 'failed',
|
||||
Exception = 'exception',
|
||||
Retry = 'retry',
|
||||
}
|
||||
|
||||
export type OnNodeAdd = (
|
||||
|
||||
@ -26,6 +26,8 @@ import {
|
||||
} from './types'
|
||||
import {
|
||||
CUSTOM_NODE,
|
||||
DEFAULT_RETRY_INTERVAL,
|
||||
DEFAULT_RETRY_MAX,
|
||||
ITERATION_CHILDREN_Z_INDEX,
|
||||
ITERATION_NODE_Z_INDEX,
|
||||
NODE_WIDTH_X_OFFSET,
|
||||
@ -292,6 +294,13 @@ export const initialNodes = (originNodes: Node[], originEdges: Edge[]) => {
|
||||
|
||||
if (node.data.type === BlockEnum.ParameterExtractor)
|
||||
(node as any).data.model.provider = correctProvider((node as any).data.model.provider)
|
||||
if (node.data.type === BlockEnum.HttpRequest && !node.data.retry_config) {
|
||||
node.data.retry_config = {
|
||||
retry_enabled: true,
|
||||
max_retries: DEFAULT_RETRY_MAX,
|
||||
retry_interval: DEFAULT_RETRY_INTERVAL,
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
})
|
||||
@ -563,6 +572,7 @@ export const isMac = () => {
|
||||
const specialKeysNameMap: Record<string, string | undefined> = {
|
||||
ctrl: '⌘',
|
||||
alt: '⌥',
|
||||
shift: '⇧',
|
||||
}
|
||||
|
||||
export const getKeyboardKeyNameBySystem = (key: string) => {
|
||||
@ -810,3 +820,7 @@ export const isExceptionVariable = (variable: string, nodeType?: BlockEnum) => {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export const hasRetryNode = (nodeType?: BlockEnum) => {
|
||||
return nodeType === BlockEnum.LLM || nodeType === BlockEnum.Tool || nodeType === BlockEnum.HttpRequest || nodeType === BlockEnum.Code
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ const LocaleLayout = ({
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
</head>
|
||||
<body
|
||||
className="h-full select-auto"
|
||||
className="h-full select-auto color-scheme"
|
||||
data-api-prefix={process.env.NEXT_PUBLIC_API_PREFIX}
|
||||
data-pubic-api-prefix={process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX}
|
||||
data-marketplace-api-prefix={process.env.NEXT_PUBLIC_MARKETPLACE_API_PREFIX}
|
||||
|
||||
@ -12,12 +12,13 @@ import I18NContext from '@/context/i18n'
|
||||
|
||||
type MailAndPasswordAuthProps = {
|
||||
isInvite: boolean
|
||||
isEmailSetup: boolean
|
||||
allowRegistration: boolean
|
||||
}
|
||||
|
||||
const passwordRegex = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||
|
||||
export default function MailAndPasswordAuth({ isInvite, allowRegistration }: MailAndPasswordAuthProps) {
|
||||
export default function MailAndPasswordAuth({ isInvite, isEmailSetup, allowRegistration }: MailAndPasswordAuthProps) {
|
||||
const { t } = useTranslation()
|
||||
const { locale } = useContext(I18NContext)
|
||||
const router = useRouter()
|
||||
@ -124,7 +125,12 @@ export default function MailAndPasswordAuth({ isInvite, allowRegistration }: Mai
|
||||
<div className='mb-3'>
|
||||
<label htmlFor="password" className="my-2 flex items-center justify-between">
|
||||
<span className='system-md-semibold text-text-secondary'>{t('login.password')}</span>
|
||||
<Link href={`/reset-password?${searchParams.toString()}`} className='system-xs-regular text-components-button-secondary-accent-text'>
|
||||
<Link
|
||||
href={`/reset-password?${searchParams.toString()}`}
|
||||
className={`system-xs-regular ${isEmailSetup ? 'text-components-button-secondary-accent-text' : 'text-components-button-secondary-accent-text-disabled pointer-events-none'}`}
|
||||
tabIndex={isEmailSetup ? 0 : -1}
|
||||
aria-disabled={!isEmailSetup}
|
||||
>
|
||||
{t('login.forget')}
|
||||
</Link>
|
||||
</label>
|
||||
|
||||
@ -163,7 +163,7 @@ const NormalForm = () => {
|
||||
</div>}
|
||||
</>}
|
||||
{systemFeatures.enable_email_password_login && authType === 'password' && <>
|
||||
<MailAndPasswordAuth isInvite={isInviteLink} allowRegistration={systemFeatures.is_allow_register} />
|
||||
<MailAndPasswordAuth isInvite={isInviteLink} isEmailSetup={systemFeatures.is_email_setup} allowRegistration={systemFeatures.is_allow_register} />
|
||||
{systemFeatures.enable_email_code_login && <div className='cursor-pointer py-1 text-center' onClick={() => { updateAuthType('code') }}>
|
||||
<span className='system-xs-medium text-components-button-secondary-accent-text'>{t('login.useVerificationCode')}</span>
|
||||
</div>}
|
||||
|
||||
@ -7,6 +7,14 @@
|
||||
@import "../../themes/manual-light.css";
|
||||
@import "../../themes/manual-dark.css";
|
||||
|
||||
html {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
html[data-changing-theme] * {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user