123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- 'use client'
- import { useContext, useContextSelector } from 'use-context-selector'
- import Link from 'next/link'
- import { useCallback, useState } from 'react'
- import { useTranslation } from 'react-i18next'
- import cn from 'classnames'
- import style from '../list.module.css'
- import AppModeLabel from './AppModeLabel'
- import s from './style.module.css'
- import SettingsModal from '@/app/components/app/overview/settings'
- import type { ConfigParams } from '@/app/components/app/overview/settings'
- import type { App } from '@/types/app'
- import Confirm from '@/app/components/base/confirm'
- import { ToastContext } from '@/app/components/base/toast'
- import { deleteApp, fetchAppDetail, updateAppSiteConfig } from '@/service/apps'
- import AppIcon from '@/app/components/base/app-icon'
- import AppsContext, { useAppContext } from '@/context/app-context'
- import type { HtmlContentProps } from '@/app/components/base/popover'
- import CustomPopover from '@/app/components/base/popover'
- import Divider from '@/app/components/base/divider'
- import { asyncRunSafe } from '@/utils'
- export type AppCardProps = {
- app: App
- onRefresh?: () => void
- }
- const AppCard = ({ app, onRefresh }: AppCardProps) => {
- const { t } = useTranslation()
- const { notify } = useContext(ToastContext)
- const { isCurrentWorkspaceManager } = useAppContext()
- const mutateApps = useContextSelector(
- AppsContext,
- state => state.mutateApps,
- )
- const [showConfirmDelete, setShowConfirmDelete] = useState(false)
- const [showSettingsModal, setShowSettingsModal] = useState(false)
- const [detailState, setDetailState] = useState<{
- loading: boolean
- detail?: App
- }>({ loading: false })
- const onConfirmDelete = useCallback(async () => {
- try {
- await deleteApp(app.id)
- notify({ type: 'success', message: t('app.appDeleted') })
- if (onRefresh)
- onRefresh()
- mutateApps()
- }
- catch (e: any) {
- notify({
- type: 'error',
- message: `${t('app.appDeleteFailed')}${
- 'message' in e ? `: ${e.message}` : ''
- }`,
- })
- }
- setShowConfirmDelete(false)
- }, [app.id])
- const getAppDetail = async () => {
- setDetailState({ loading: true })
- const [err, res] = await asyncRunSafe(
- fetchAppDetail({ url: '/apps', id: app.id }),
- )
- if (!err) {
- setDetailState({ loading: false, detail: res })
- setShowSettingsModal(true)
- }
- else { setDetailState({ loading: false }) }
- }
- const onSaveSiteConfig = useCallback(
- async (params: ConfigParams) => {
- const [err] = await asyncRunSafe(
- updateAppSiteConfig({
- url: `/apps/${app.id}/site`,
- body: params,
- }),
- )
- if (!err) {
- notify({
- type: 'success',
- message: t('common.actionMsg.modifiedSuccessfully'),
- })
- if (onRefresh)
- onRefresh()
- mutateApps()
- }
- else {
- notify({
- type: 'error',
- message: t('common.actionMsg.modifiedUnsuccessfully'),
- })
- }
- },
- [app.id],
- )
- const Operations = (props: HtmlContentProps) => {
- const onClickSettings = async (e: React.MouseEvent<HTMLButtonElement>) => {
- props.onClick?.()
- e.preventDefault()
- await getAppDetail()
- }
- const onClickDelete = async (e: React.MouseEvent<HTMLDivElement>) => {
- props.onClick?.()
- e.preventDefault()
- setShowConfirmDelete(true)
- }
- return (
- <div className="w-full py-1">
- <button className={s.actionItem} onClick={onClickSettings} disabled={detailState.loading}>
- <span className={s.actionName}>{t('common.operation.settings')}</span>
- </button>
- <Divider className="!my-1" />
- <div
- className={cn(s.actionItem, s.deleteActionItem, 'group')}
- onClick={onClickDelete}
- >
- <span className={cn(s.actionName, 'group-hover:text-red-500')}>
- {t('common.operation.delete')}
- </span>
- </div>
- </div>
- )
- }
- return (
- <>
- <Link
- href={`/app/${app.id}/overview`}
- className={style.listItem}
- >
- <div className={style.listItemTitle}>
- <AppIcon
- size="small"
- icon={app.icon}
- background={app.icon_background}
- />
- <div className={style.listItemHeading}>
- <div className={style.listItemHeadingContent}>{app.name}</div>
- </div>
- {isCurrentWorkspaceManager && <CustomPopover
- htmlContent={<Operations />}
- position="br"
- trigger="click"
- btnElement={<div className={cn(s.actionIcon, s.commonIcon)} />}
- btnClassName={open =>
- cn(
- open ? '!bg-gray-100 !shadow-none' : '!bg-transparent',
- style.actionIconWrapper,
- )
- }
- className={'!w-[128px] h-fit !z-20'}
- manualClose
- />}
- </div>
- <div className={style.listItemDescription}>
- {app.model_config?.pre_prompt}
- </div>
- <div className={style.listItemFooter}>
- <AppModeLabel mode={app.mode} />
- </div>
- {showConfirmDelete && (
- <Confirm
- title={t('app.deleteAppConfirmTitle')}
- content={t('app.deleteAppConfirmContent')}
- isShow={showConfirmDelete}
- onClose={() => setShowConfirmDelete(false)}
- onConfirm={onConfirmDelete}
- onCancel={() => setShowConfirmDelete(false)}
- />
- )}
- {showSettingsModal && detailState.detail && (
- <SettingsModal
- appInfo={detailState.detail}
- isShow={showSettingsModal}
- onClose={() => setShowSettingsModal(false)}
- onSave={onSaveSiteConfig}
- />
- )}
- </Link>
- </>
- )
- }
- export default AppCard
|