diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx index 684ac0ac51..4f019b0621 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx @@ -7,31 +7,21 @@ import useSWR, { useSWRConfig } from 'swr' import AppCard from '@/app/components/app/overview/appCard' import Loading from '@/app/components/base/loading' import { ToastContext } from '@/app/components/base/toast' -import { fetchAppDetail, updateAppApiStatus, updateAppSiteAccessToken, updateAppSiteConfig, updateAppSiteStatus } from '@/service/apps' -import type { IToastProps } from '@/app/components/base/toast' +import { + fetchAppDetail, + updateAppSiteAccessToken, + updateAppSiteConfig, + updateAppSiteStatus, +} from '@/service/apps' import type { App } from '@/types/app' +import type { UpdateAppSiteCodeResponse } from '@/models/app' +import { asyncRunSafe } from '@/utils' +import { NEED_REFRESH_APP_LIST_KEY } from '@/config' export type ICardViewProps = { appId: string } -type IParams = { - url: string - body?: Record -} - -export async function asyncRunSafe(func: (val: IParams) => Promise, params: IParams, callback: (props: IToastProps) => void, dict?: any): Promise<[string?, T?]> { - try { - const res = await func(params) - callback && callback({ type: 'success', message: dict('common.actionMsg.modifiedSuccessfully') }) - return [undefined, res] - } - catch (err) { - callback && callback({ type: 'error', message: dict('common.actionMsg.modificationFailed') }) - return [(err as Error).message, undefined] - } -} - const CardView: FC = ({ appId }) => { const detailParams = { url: '/apps', id: appId } const { data: response } = useSWR(detailParams, fetchAppDetail) @@ -42,44 +32,78 @@ const CardView: FC = ({ appId }) => { if (!response) return - const onChangeSiteStatus = async (value: boolean) => { - const [err] = await asyncRunSafe(updateAppSiteStatus as any, { url: `/apps/${appId}/site-enable`, body: { enable_site: value } }, notify, t) - if (!err) + const handleError = (err: Error | null) => { + if (!err) { + notify({ + type: 'success', + message: t('common.actionMsg.modifiedSuccessfully'), + }) mutate(detailParams) + } + else { + notify({ + type: 'error', + message: t('common.actionMsg.modificationFailed'), + }) + } + } + + const onChangeSiteStatus = async (value: boolean) => { + const [err] = await asyncRunSafe( + updateAppSiteStatus({ + url: `/apps/${appId}/site-enable`, + body: { enable_site: value }, + }) as Promise, + ) + handleError(err) } const onChangeApiStatus = async (value: boolean) => { - const [err] = await asyncRunSafe(updateAppApiStatus as any, { url: `/apps/${appId}/api-enable`, body: { enable_api: value } }, notify, t) - if (!err) - mutate(detailParams) + const [err] = await asyncRunSafe( + updateAppSiteStatus({ + url: `/apps/${appId}/api-enable`, + body: { enable_api: value }, + }) as Promise, + ) + handleError(err) } const onSaveSiteConfig = async (params: any) => { - const [err] = await asyncRunSafe(updateAppSiteConfig as any, { url: `/apps/${appId}/site`, body: params }, notify, t) + const [err] = await asyncRunSafe( + updateAppSiteConfig({ + url: `/apps/${appId}/site`, + body: params, + }) as Promise, + ) if (!err) - mutate(detailParams) + localStorage.setItem(NEED_REFRESH_APP_LIST_KEY, '1') + + handleError(err) } const onGenerateCode = async () => { - const [err] = await asyncRunSafe(updateAppSiteAccessToken as any, { url: `/apps/${appId}/site/access-token-reset` }, notify, t) - if (!err) - mutate(detailParams) + const [err] = await asyncRunSafe( + updateAppSiteAccessToken({ + url: `/apps/${appId}/site/access-token-reset`, + }) as Promise, + ) + handleError(err) } return ( -
+
+ onSaveSiteConfig={onSaveSiteConfig} + /> + onChangeStatus={onChangeApiStatus} + />
) } diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx index 6570c23a95..cd55faf8e1 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/chartView.tsx @@ -46,35 +46,23 @@ export default function ChartView({ appId }: IChartViewProps) { defaultValue={7} />
-
-
- -
-
- -
+
+ +
-
-
- {isChatApp - ? ( - - ) - : ( - - )} -
-
- -
+
+ {isChatApp + ? ( + + ) + : ( + + )} +
-
-
- -
-
- -
+
+ +
) diff --git a/web/app/(commonLayout)/apps/AppCard.tsx b/web/app/(commonLayout)/apps/AppCard.tsx index 2645859bb5..c56a029be7 100644 --- a/web/app/(commonLayout)/apps/AppCard.tsx +++ b/web/app/(commonLayout)/apps/AppCard.tsx @@ -2,64 +2,165 @@ import { useContext, useContextSelector } from 'use-context-selector' import Link from 'next/link' -import type { MouseEventHandler } from 'react' 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 { App } from '@/types/app' import Confirm from '@/app/components/base/confirm' import { ToastContext } from '@/app/components/base/toast' -import { deleteApp } from '@/service/apps' +import { deleteApp, fetchAppDetail, updateAppSiteConfig } from '@/service/apps' import AppIcon from '@/app/components/base/app-icon' import AppsContext, { useAppContext } from '@/context/app-context' +import CustomPopover from '@/app/components/base/popover' +import Divider from '@/app/components/base/divider' +import { asyncRunSafe } from '@/utils' export type AppCardProps = { app: App - onDelete?: () => void + onRefresh?: () => void } -const AppCard = ({ - app, - onDelete, -}: AppCardProps) => { +const AppCard = ({ app, onRefresh }: AppCardProps) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const { isCurrentWorkspaceManager } = useAppContext() - const mutateApps = useContextSelector(AppsContext, state => state.mutateApps) + const mutateApps = useContextSelector( + AppsContext, + state => state.mutateApps, + ) const [showConfirmDelete, setShowConfirmDelete] = useState(false) - const onDeleteClick: MouseEventHandler = useCallback((e) => { - e.preventDefault() - setShowConfirmDelete(true) - }, []) + 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 (onDelete) - onDelete() + if (onRefresh) + onRefresh() mutateApps() } catch (e: any) { - notify({ type: 'error', message: `${t('app.appDeleteFailed')}${'message' in e ? `: ${e.message}` : ''}` }) + 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 }) as Promise, + ) + if (!err) { + setDetailState({ loading: false, detail: res }) + setShowSettingsModal(true) + } + else { setDetailState({ loading: false }) } + } + + const onSaveSiteConfig = useCallback( + async (params: any) => { + const [err] = await asyncRunSafe( + updateAppSiteConfig({ + url: `/apps/${app.id}/site`, + body: params, + }) as Promise, + ) + if (!err) { + notify({ + type: 'success', + message: t('common.actionMsg.modifiedSuccessfully'), + }) + if (onRefresh) + onRefresh() + mutateApps() + } + else { + notify({ + type: 'error', + message: t('common.actionMsg.modificationFailed'), + }) + } + }, + [app.id], + ) + + const Operations = (props: any) => { + const onClickSettings = async (e: any) => { + props?.onClose() + e.preventDefault() + await getAppDetail() + } + const onClickDelete = async (e: any) => { + props?.onClose() + e.preventDefault() + setShowConfirmDelete(true) + } + return ( +
+ + + +
+ + {t('common.operation.delete')} + +
+
+ ) + } + return ( <> - +
- +
{app.name}
- { isCurrentWorkspaceManager - && } + {isCurrentWorkspaceManager && } + position="br" + trigger="click" + btnElement={
} + btnClassName={open => + cn( + open ? '!bg-gray-100 !shadow-none' : '!bg-transparent', + style.actionIconWrapper, + ) + } + className={'!w-[128px] h-fit !z-20'} + />} +
+
+ {app.model_config?.pre_prompt}
-
{app.model_config?.pre_prompt}
@@ -74,6 +175,14 @@ const AppCard = ({ onCancel={() => setShowConfirmDelete(false)} /> )} + {showSettingsModal && detailState.detail && ( + setShowSettingsModal(false)} + onSave={onSaveSiteConfig} + /> + )} ) diff --git a/web/app/(commonLayout)/apps/AppModeLabel.tsx b/web/app/(commonLayout)/apps/AppModeLabel.tsx index f223c01981..d40d29a11e 100644 --- a/web/app/(commonLayout)/apps/AppModeLabel.tsx +++ b/web/app/(commonLayout)/apps/AppModeLabel.tsx @@ -2,8 +2,8 @@ import classNames from 'classnames' import { useTranslation } from 'react-i18next' -import { type AppMode } from '@/types/app' import style from '../list.module.css' +import { type AppMode } from '@/types/app' export type AppModeLabelProps = { mode: AppMode diff --git a/web/app/(commonLayout)/apps/Apps.tsx b/web/app/(commonLayout)/apps/Apps.tsx index 973afb6a3c..4ad79e8295 100644 --- a/web/app/(commonLayout)/apps/Apps.tsx +++ b/web/app/(commonLayout)/apps/Apps.tsx @@ -54,7 +54,7 @@ const Apps = () => { return (