| | |
| | | import { useCallback, useEffect, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { RiMoreFill } from '@remixicon/react' |
| | | import s from './style.module.css' |
| | | import cn from '@/utils/classnames' |
| | | import type { App } from '@/types/app' |
| | | import Confirm from '@/app/components/base/confirm' |
| | | import Toast, { ToastContext } from '@/app/components/base/toast' |
| | |
| | | import type { HtmlContentProps } from '@/app/components/base/popover' |
| | | import CustomPopover from '@/app/components/base/popover' |
| | | import Divider from '@/app/components/base/divider' |
| | | import { WEB_PREFIX } from '@/config' |
| | | import { getRedirection } from '@/utils/app-redirection' |
| | | import { useProviderContext } from '@/context/provider-context' |
| | | import { NEED_REFRESH_APP_LIST_KEY } from '@/config' |
| | |
| | | import { fetchWorkflowDraft } from '@/service/workflow' |
| | | import { fetchInstalledAppList } from '@/service/explore' |
| | | import { AppTypeIcon } from '@/app/components/app/type-selector' |
| | | import cn from '@/utils/classnames' |
| | | |
| | | export type AppCardProps = { |
| | | app: App |
| | |
| | | }) |
| | | } |
| | | setShowConfirmDelete(false) |
| | | // eslint-disable-next-line react-hooks/exhaustive-deps |
| | | }, [app.id]) |
| | | |
| | | const onEdit: CreateAppModalProps['onConfirm'] = useCallback(async ({ |
| | |
| | | onRefresh() |
| | | mutateApps() |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.editFailed') }) |
| | | } |
| | | }, [app.id, mutateApps, notify, onRefresh, t]) |
| | |
| | | onPlanInfoChanged() |
| | | getRedirection(isCurrentWorkspaceEditor, newApp, push) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.newApp.appCreateFailed') }) |
| | | } |
| | | } |
| | |
| | | a.download = `${app.name}.yml` |
| | | a.click() |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.exportFailed') }) |
| | | } |
| | | } |
| | |
| | | } |
| | | setSecretEnvList(list) |
| | | } |
| | | catch { |
| | | catch (e) { |
| | | notify({ type: 'error', message: t('app.exportFailed') }) |
| | | } |
| | | } |
| | |
| | | try { |
| | | const { installed_apps }: any = await fetchInstalledAppList(app.id) || {} |
| | | if (installed_apps?.length > 0) |
| | | window.open(`${WEB_PREFIX}/explore/installed/${installed_apps[0].id}`, '_blank') |
| | | window.open(`/explore/installed/${installed_apps[0].id}`, '_blank') |
| | | else |
| | | throw new Error('No app found in Explore') |
| | | } |
| | |
| | | } |
| | | return ( |
| | | <div className="relative w-full py-1" onMouseLeave={onMouseLeave}> |
| | | <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickSettings}> |
| | | <span className='system-sm-regular text-text-secondary'>{t('app.editApp')}</span> |
| | | <button className={s.actionItem} onClick={onClickSettings}> |
| | | <span className={s.actionName}>{t('app.editApp')}</span> |
| | | </button> |
| | | <Divider className="!my-1" /> |
| | | <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickDuplicate}> |
| | | <span className='system-sm-regular text-text-secondary'>{t('app.duplicate')}</span> |
| | | <button className={s.actionItem} onClick={onClickDuplicate}> |
| | | <span className={s.actionName}>{t('app.duplicate')}</span> |
| | | </button> |
| | | <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickExport}> |
| | | <span className='system-sm-regular text-text-secondary'>{t('app.export')}</span> |
| | | <button className={s.actionItem} onClick={onClickExport}> |
| | | <span className={s.actionName}>{t('app.export')}</span> |
| | | </button> |
| | | {(app.mode === 'completion' || app.mode === 'chat') && ( |
| | | <> |
| | | <Divider className="!my-1" /> |
| | | <div |
| | | className='mx-1 flex h-9 cursor-pointer items-center rounded-lg px-3 py-2 hover:bg-state-base-hover' |
| | | className='h-9 py-2 px-3 mx-1 flex items-center hover:bg-gray-50 rounded-lg cursor-pointer' |
| | | onClick={onClickSwitch} |
| | | > |
| | | <span className='text-sm leading-5 text-text-secondary'>{t('app.switch')}</span> |
| | | <span className='text-gray-700 text-sm leading-5'>{t('app.switch')}</span> |
| | | </div> |
| | | </> |
| | | )} |
| | | <Divider className="!my-1" /> |
| | | <button className='mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-base-hover' onClick={onClickInstalledApp}> |
| | | <span className='system-sm-regular text-text-secondary'>{t('app.openInExplore')}</span> |
| | | <button className={s.actionItem} onClick={onClickInstalledApp}> |
| | | <span className={s.actionName}>{t('app.openInExplore')}</span> |
| | | </button> |
| | | <Divider className="!my-1" /> |
| | | <div |
| | | className='group mx-1 flex h-8 w-[calc(100%_-_8px)] cursor-pointer items-center gap-2 rounded-lg px-3 py-[6px] hover:bg-state-destructive-hover' |
| | | className={cn(s.actionItem, s.deleteActionItem, 'group')} |
| | | onClick={onClickDelete} |
| | | > |
| | | <span className='system-sm-regular text-text-secondary group-hover:text-text-destructive'> |
| | | <span className={cn(s.actionName, 'group-hover:text-red-500')}> |
| | | {t('common.operation.delete')} |
| | | </span> |
| | | </div> |
| | |
| | | e.preventDefault() |
| | | getRedirection(isCurrentWorkspaceEditor, app, push) |
| | | }} |
| | | className='group relative col-span-1 inline-flex h-[160px] cursor-pointer flex-col rounded-xl border-[1px] border-solid border-components-card-border bg-components-card-bg shadow-sm transition-all duration-200 ease-in-out hover:shadow-lg' |
| | | className='relative h-[160px] group col-span-1 bg-components-card-bg border-[1px] border-solid border-components-card-border rounded-xl shadow-sm inline-flex flex-col transition-all duration-200 ease-in-out cursor-pointer hover:shadow-lg' |
| | | > |
| | | <div className='flex h-[66px] shrink-0 grow-0 items-center gap-3 px-[14px] pb-3 pt-[14px]'> |
| | | <div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'> |
| | | <div className='relative shrink-0'> |
| | | <AppIcon |
| | | size="large" |
| | |
| | | background={app.icon_background} |
| | | imageUrl={app.icon_url} |
| | | /> |
| | | <AppTypeIcon type={app.mode} wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm' className='h-3 w-3' /> |
| | | <AppTypeIcon type={app.mode} wrapperClassName='absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm' className='w-3 h-3' /> |
| | | </div> |
| | | <div className='w-0 grow py-[1px]'> |
| | | <div className='flex items-center text-sm font-semibold leading-5 text-text-secondary'> |
| | | <div className='grow w-0 py-[1px]'> |
| | | <div className='flex items-center text-sm leading-5 font-semibold text-text-secondary'> |
| | | <div className='truncate' title={app.name}>{app.name}</div> |
| | | </div> |
| | | <div className='flex items-center text-[10px] font-medium leading-[18px] text-text-tertiary'> |
| | | <div className='flex items-center text-[10px] leading-[18px] text-text-tertiary font-medium'> |
| | | {app.mode === 'advanced-chat' && <div className='truncate'>{t('app.types.advanced').toUpperCase()}</div>} |
| | | {app.mode === 'chat' && <div className='truncate'>{t('app.types.chatbot').toUpperCase()}</div>} |
| | | {app.mode === 'agent-chat' && <div className='truncate'>{t('app.types.agent').toUpperCase()}</div>} |
| | |
| | | </div> |
| | | </div> |
| | | <div className={cn( |
| | | 'absolute bottom-1 left-0 right-0 h-[42px] shrink-0 items-center pb-[6px] pl-[14px] pr-[6px] pt-1', |
| | | 'absolute bottom-1 left-0 right-0 items-center shrink-0 pt-1 pl-[14px] pr-[6px] pb-[6px] h-[42px]', |
| | | tags.length ? 'flex' : '!hidden group-hover:!flex', |
| | | )}> |
| | | {isCurrentWorkspaceEditor && ( |
| | | <> |
| | | <div className={cn('flex w-0 grow items-center gap-1')} onClick={(e) => { |
| | | <div className={cn('grow flex items-center gap-1 w-0')} onClick={(e) => { |
| | | e.stopPropagation() |
| | | e.preventDefault() |
| | | }}> |
| | | <div className={cn( |
| | | 'mr-[41px] w-full grow group-hover:!mr-0 group-hover:!block', |
| | | 'group-hover:!block group-hover:!mr-0 mr-[41px] grow w-full', |
| | | tags.length ? '!block' : '!hidden', |
| | | )}> |
| | | <TagSelector |
| | |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div className='mx-1 !hidden h-[14px] w-[1px] shrink-0 group-hover:!flex' /> |
| | | <div className='!hidden shrink-0 group-hover:!flex'> |
| | | <div className='!hidden group-hover:!flex shrink-0 mx-1 w-[1px] h-[14px]' /> |
| | | <div className='!hidden group-hover:!flex shrink-0'> |
| | | <CustomPopover |
| | | htmlContent={<Operations />} |
| | | position="br" |
| | | trigger="click" |
| | | btnElement={ |
| | | <div |
| | | className='flex h-8 w-8 cursor-pointer items-center justify-center rounded-md' |
| | | className='flex items-center justify-center w-8 h-8 cursor-pointer rounded-md' |
| | | > |
| | | <RiMoreFill className='h-4 w-4 text-text-tertiary' /> |
| | | <RiMoreFill className='w-4 h-4 text-text-tertiary' /> |
| | | </div> |
| | | } |
| | | btnClassName={open => |
| | | cn( |
| | | open ? '!bg-black/5 !shadow-none' : '!bg-transparent', |
| | | 'h-8 w-8 rounded-md border-none !p-2 hover:!bg-black/5', |
| | | 'h-8 w-8 !p-2 rounded-md border-none hover:!bg-black/5', |
| | | ) |
| | | } |
| | | popupClassName={ |
| | |
| | | ? '!w-[256px] translate-x-[-224px]' |
| | | : '!w-[160px] translate-x-[-128px]' |
| | | } |
| | | className={'!z-20 h-fit'} |
| | | className={'h-fit !z-20'} |
| | | /> |
| | | </div> |
| | | </> |