| | |
| | | 'use client' |
| | | import type { HTMLProps } from 'react' |
| | | import React, { useMemo, useState } from 'react' |
| | | import { |
| | | Cog8ToothIcon, |
| | | DocumentTextIcon, |
| | | PaintBrushIcon, |
| | | RocketLaunchIcon, |
| | | } from '@heroicons/react/24/outline' |
| | | import { usePathname, useRouter } from 'next/navigation' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { |
| | | RiBookOpenLine, |
| | | RiEqualizer2Line, |
| | | RiExternalLinkLine, |
| | | RiPaintBrushLine, |
| | | RiWindowLine, |
| | | } from '@remixicon/react' |
| | | import SettingsModal from './settings' |
| | | import EmbeddedModal from './embedded' |
| | | import CustomizeModal from './customize' |
| | |
| | | import AppBasic from '@/app/components/app-sidebar/basic' |
| | | import { asyncRunSafe, randomString } from '@/utils' |
| | | import Button from '@/app/components/base/button' |
| | | import Tag from '@/app/components/base/tag' |
| | | import Switch from '@/app/components/base/switch' |
| | | import Divider from '@/app/components/base/divider' |
| | | import CopyFeedback from '@/app/components/base/copy-feedback' |
| | |
| | | import type { AppDetailResponse } from '@/models/app' |
| | | import { useAppContext } from '@/context/app-context' |
| | | import type { AppSSO } from '@/types/app' |
| | | import Indicator from '@/app/components/header/indicator' |
| | | |
| | | export type IAppCardProps = { |
| | | className?: string |
| | | appInfo: AppDetailResponse & Partial<AppSSO> |
| | | isInPanel?: boolean |
| | | cardType?: 'api' | 'webapp' |
| | | customBgColor?: string |
| | | onChangeStatus: (val: boolean) => Promise<void> |
| | |
| | | onGenerateCode?: () => Promise<void> |
| | | } |
| | | |
| | | const EmbedIcon = ({ className = '' }: HTMLProps<HTMLDivElement>) => { |
| | | return <div className={`${style.codeBrowserIcon} ${className}`}></div> |
| | | } |
| | | |
| | | function AppCard({ |
| | | appInfo, |
| | | isInPanel, |
| | | cardType = 'webapp', |
| | | customBgColor, |
| | | onChangeStatus, |
| | |
| | | const OPERATIONS_MAP = useMemo(() => { |
| | | const operationsMap = { |
| | | webapp: [ |
| | | { opName: t('appOverview.overview.appInfo.launch'), opIcon: RiExternalLinkLine }, |
| | | { opName: t('appOverview.overview.appInfo.preview'), opIcon: RocketLaunchIcon }, |
| | | { opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: PaintBrushIcon }, |
| | | ] as { opName: string; opIcon: any }[], |
| | | api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: RiBookOpenLine }], |
| | | api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: DocumentTextIcon }], |
| | | app: [], |
| | | } |
| | | if (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: RiWindowLine }) |
| | | |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: RiPaintBrushLine }) |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: EmbedIcon }) |
| | | |
| | | if (isCurrentWorkspaceEditor) |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: RiEqualizer2Line }) |
| | | operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: Cog8ToothIcon }) |
| | | |
| | | return operationsMap |
| | | }, [isCurrentWorkspaceEditor, appInfo, t]) |
| | |
| | | const appUrl = `${app_base_url}/${appMode}/${access_token}` |
| | | const apiUrl = appInfo?.api_base_url |
| | | |
| | | let bgColor = 'bg-primary-50 bg-opacity-40' |
| | | if (cardType === 'api') |
| | | bgColor = 'bg-purple-50' |
| | | |
| | | const genClickFuncByName = (opName: string) => { |
| | | switch (opName) { |
| | | case t('appOverview.overview.appInfo.launch'): |
| | | case t('appOverview.overview.appInfo.preview'): |
| | | return () => { |
| | | window.open(appUrl, '_blank') |
| | | } |
| | |
| | | return ( |
| | | <div |
| | | className={ |
| | | `${isInPanel ? 'border-l-[0.5px] border-t' : 'border-[0.5px] shadow-xs'} w-full max-w-full rounded-xl border-effects-highlight ${className ?? ''}`} |
| | | `shadow-xs border-[0.5px] rounded-lg border-gray-200 ${className ?? ''}`} |
| | | > |
| | | <div className={`${customBgColor ?? 'bg-background-default'} rounded-xl`}> |
| | | <div className='flex w-full flex-col items-start justify-center gap-3 self-stretch border-b-[0.5px] border-divider-subtle p-3'> |
| | | <div className='flex w-full items-center gap-3 self-stretch'> |
| | | <AppBasic |
| | | iconType={cardType} |
| | | icon={appInfo.icon} |
| | | icon_background={appInfo.icon_background} |
| | | name={basicName} |
| | | type={ |
| | | isApp |
| | | ? t('appOverview.overview.appInfo.explanation') |
| | | : t('appOverview.overview.apiInfo.explanation') |
| | | } |
| | | /> |
| | | <div className='flex items-center gap-1'> |
| | | <Indicator color={runningStatus ? 'green' : 'yellow'} /> |
| | | <div className={`${runningStatus ? 'text-text-success' : 'text-text-warning'} system-xs-semibold-uppercase`}> |
| | | {runningStatus |
| | | ? t('appOverview.overview.status.running') |
| | | : t('appOverview.overview.status.disable')} |
| | | </div> |
| | | </div> |
| | | <div className={`px-6 py-5 ${customBgColor ?? bgColor} rounded-lg`}> |
| | | <div className="mb-2.5 flex flex-row items-start justify-between"> |
| | | <AppBasic |
| | | iconType={cardType} |
| | | icon={appInfo.icon} |
| | | icon_background={appInfo.icon_background} |
| | | name={basicName} |
| | | type={ |
| | | isApp |
| | | ? t('appOverview.overview.appInfo.explanation') |
| | | : t('appOverview.overview.apiInfo.explanation') |
| | | } |
| | | /> |
| | | <div className="flex flex-row items-center h-9"> |
| | | <Tag className="mr-2" color={runningStatus ? 'green' : 'yellow'}> |
| | | {runningStatus |
| | | ? t('appOverview.overview.status.running') |
| | | : t('appOverview.overview.status.disable')} |
| | | </Tag> |
| | | <Switch defaultValue={runningStatus} onChange={onChangeStatus} disabled={toggleDisabled} /> |
| | | </div> |
| | | <div className='flex flex-col items-start justify-center self-stretch'> |
| | | <div className="system-xs-medium pb-1 text-text-tertiary"> |
| | | </div> |
| | | <div className="flex flex-col justify-center py-2"> |
| | | <div className="py-1"> |
| | | <div className="pb-1 text-xs text-gray-500"> |
| | | {isApp |
| | | ? t('appOverview.overview.appInfo.accessibleAddress') |
| | | : t('appOverview.overview.apiInfo.accessibleAddress')} |
| | | </div> |
| | | <div className="inline-flex h-9 w-full items-center gap-0.5 rounded-lg bg-components-input-bg-normal p-1 pl-2"> |
| | | <div className="flex h-4 min-w-0 flex-1 items-start justify-start gap-2 px-1"> |
| | | <div className="overflow-hidden text-ellipsis whitespace-nowrap text-xs font-medium text-text-secondary"> |
| | | <div className="w-full h-9 pl-2 pr-0.5 py-0.5 bg-black bg-opacity-2 rounded-lg border border-black border-opacity-5 justify-start items-center inline-flex"> |
| | | <div className="h-4 px-2 justify-start items-start gap-2 flex flex-1 min-w-0"> |
| | | <div className="text-gray-700 text-xs font-medium text-ellipsis overflow-hidden whitespace-nowrap"> |
| | | {isApp ? appUrl : apiUrl} |
| | | </div> |
| | | </div> |
| | | <Divider type="vertical" className="!h-3.5 shrink-0 !mx-0.5" /> |
| | | {isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} selectorId={randomString(8)} className={'hover:bg-gray-200'} />} |
| | | <CopyFeedback |
| | | content={isApp ? appUrl : apiUrl} |
| | | className={'!size-6'} |
| | | className={'hover:bg-gray-200'} |
| | | /> |
| | | {isApp && <ShareQRCode content={isApp ? appUrl : apiUrl} className='z-50 !size-6 rounded-md hover:bg-state-base-hover' selectorId={randomString(8)} />} |
| | | {isApp && <Divider type="vertical" className="!mx-0.5 !h-3.5 shrink-0" />} |
| | | {/* button copy link/ button regenerate */} |
| | | {showConfirmDelete && ( |
| | | <Confirm |
| | |
| | | popupContent={t('appOverview.overview.appInfo.regenerate') || ''} |
| | | > |
| | | <div |
| | | className="h-6 w-6 cursor-pointer rounded-md hover:bg-state-base-hover" |
| | | className="w-8 h-8 ml-0.5 cursor-pointer hover:bg-gray-200 rounded-lg" |
| | | onClick={() => setShowConfirmDelete(true)} |
| | | > |
| | | <div |
| | | className={ |
| | | `h-full w-full ${style.refreshIcon} ${genLoading ? style.generateLogo : ''}`} |
| | | `w-full h-full ${style.refreshIcon} ${genLoading ? style.generateLogo : ''}`} |
| | | ></div> |
| | | </div> |
| | | </Tooltip> |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div className={'flex items-center gap-1 self-stretch p-3'}> |
| | | {!isApp && <SecretKeyButton appId={appInfo.id} />} |
| | | <div className={'pt-2 flex flex-row items-center flex-wrap gap-y-2'}> |
| | | {!isApp && <SecretKeyButton className='flex-shrink-0 !h-8 bg-white mr-2' textCls='!text-gray-700 font-medium' iconCls='stroke-[1.2px]' appId={appInfo.id} />} |
| | | {OPERATIONS_MAP[cardType].map((op) => { |
| | | const disabled |
| | | = op.opName === t('appOverview.overview.appInfo.settings.entry') |
| | |
| | | : !runningStatus |
| | | return ( |
| | | <Button |
| | | className="mr-1 min-w-[88px]" |
| | | size="small" |
| | | variant={'ghost'} |
| | | className="mr-2" |
| | | key={op.opName} |
| | | onClick={genClickFuncByName(op.opName)} |
| | | disabled={disabled} |
| | |
| | | } |
| | | popupClassName={disabled ? 'mt-[-8px]' : '!hidden'} |
| | | > |
| | | <div className="flex items-center justify-center gap-[1px]"> |
| | | <op.opIcon className="h-3.5 w-3.5" /> |
| | | <div className={`${runningStatus ? 'text-text-tertiary' : 'text-components-button-ghost-text-disabled'} system-xs-medium px-[3px]`}>{op.opName}</div> |
| | | <div className="flex flex-row items-center"> |
| | | <op.opIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" /> |
| | | <span className="text-[13px]">{op.opName}</span> |
| | | </div> |
| | | </Tooltip> |
| | | </Button> |