From f95f6db0e30eaa365accb341dc85c081e7c8e0ed Mon Sep 17 00:00:00 2001 From: Joel Date: Wed, 16 Aug 2023 10:31:08 +0800 Subject: [PATCH] feat: support app rename and make app card ui better (#766) Co-authored-by: Gillian97 --- .../[appId]/overview/cardView.tsx | 98 +++++++----- .../[appId]/overview/chartView.tsx | 42 ++--- web/app/(commonLayout)/apps/AppCard.tsx | 149 +++++++++++++++--- web/app/(commonLayout)/apps/AppModeLabel.tsx | 2 +- web/app/(commonLayout)/apps/Apps.tsx | 2 +- web/app/(commonLayout)/apps/NewAppDialog.tsx | 2 +- web/app/(commonLayout)/apps/style.module.css | 21 +++ .../(commonLayout)/datasets/DatasetCard.tsx | 2 +- web/app/(commonLayout)/list.module.css | 51 +++--- web/app/components/app-sidebar/basic.tsx | 8 +- web/app/components/app/chat/answer/index.tsx | 2 +- web/app/components/app/log/list.tsx | 2 +- web/app/components/app/overview/appCard.tsx | 142 +++++++++-------- web/app/components/app/overview/appChart.tsx | 3 +- .../app/overview/assets/refresh-hover.svg | 3 + .../app/overview/assets/refresh.svg | 3 + .../app/overview/settings/index.tsx | 18 ++- .../components/app/overview/share-link.tsx | 69 -------- .../overview/{style.css => style.module.css} | 13 ++ web/app/components/base/confirm-ui/index.tsx | 4 +- web/app/components/base/confirm/index.tsx | 2 +- .../components/base/copy-feedback/index.tsx | 56 +++++++ .../base/copy-feedback/style.module.css | 15 ++ web/app/components/base/popover/index.tsx | 58 +++---- .../components/datasets/documents/list.tsx | 136 ++++++++-------- .../datasets/documents/style.module.css | 20 +-- .../develop/secret-key/input-copy.tsx | 2 +- .../develop/secret-key/secret-key-button.tsx | 10 +- .../explore/item-operation/style.module.css | 6 +- .../config-view/detail/style.module.css | 4 +- .../invited-modal/invitation-link.tsx | 2 +- .../datasets/documents => }/assets/action.svg | 0 .../datasets/documents => }/assets/csv.svg | 0 .../apps => }/assets/delete.svg | 0 .../datasets/documents => }/assets/html.svg | 0 .../datasets/documents => }/assets/json.svg | 0 .../datasets/documents => }/assets/md.svg | 0 .../datasets/documents => }/assets/pdf.svg | 0 .../datasets/documents => }/assets/txt.svg | 0 .../datasets/documents => }/assets/xlsx.svg | 0 web/i18n/lang/app-debug.en.ts | 2 +- web/i18n/lang/app-debug.zh.ts | 2 +- web/i18n/lang/app-overview.en.ts | 13 +- web/i18n/lang/app-overview.zh.ts | 11 +- web/i18n/lang/app.en.ts | 4 + web/i18n/lang/app.zh.ts | 4 + web/i18n/lang/common.en.ts | 3 + web/i18n/lang/common.zh.ts | 3 + web/models/app.ts | 2 - web/models/datasets.ts | 1 + web/service/apps.ts | 7 +- web/service/demo/index.tsx | 14 +- web/utils/index.ts | 18 ++- 53 files changed, 612 insertions(+), 419 deletions(-) create mode 100644 web/app/(commonLayout)/apps/style.module.css create mode 100644 web/app/components/app/overview/assets/refresh-hover.svg create mode 100644 web/app/components/app/overview/assets/refresh.svg delete mode 100644 web/app/components/app/overview/share-link.tsx rename web/app/components/app/overview/{style.css => style.module.css} (51%) create mode 100644 web/app/components/base/copy-feedback/index.tsx create mode 100644 web/app/components/base/copy-feedback/style.module.css rename web/{app/components/datasets/documents => }/assets/action.svg (100%) rename web/{app/components/datasets/documents => }/assets/csv.svg (100%) rename web/{app/(commonLayout)/apps => }/assets/delete.svg (100%) rename web/{app/components/datasets/documents => }/assets/html.svg (100%) rename web/{app/components/datasets/documents => }/assets/json.svg (100%) rename web/{app/components/datasets/documents => }/assets/md.svg (100%) rename web/{app/components/datasets/documents => }/assets/pdf.svg (100%) rename web/{app/components/datasets/documents => }/assets/txt.svg (100%) rename web/{app/components/datasets/documents => }/assets/xlsx.svg (100%) 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 (