diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx index 1d96320309..91b305dc8c 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx @@ -161,9 +161,9 @@ const AppDetailLayout: FC = (props) => { } return ( -
+
{appDetail && ( - + )}
{children} diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx index 8f3ee510b8..208078f612 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView.tsx @@ -24,9 +24,11 @@ import AppContext from '@/context/app-context' export type ICardViewProps = { appId: string + isInPanel?: boolean + className?: string } -const CardView: FC = ({ appId }) => { +const CardView: FC = ({ appId, isInPanel, className }) => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const appDetail = useAppStore(state => state.appDetail) @@ -120,10 +122,11 @@ const CardView: FC = ({ appId }) => { return return ( -
+
= ({ appId }) => {
diff --git a/web/app/(commonLayout)/datasets/ApiServer.tsx b/web/app/(commonLayout)/datasets/ApiServer.tsx index 7baa342a62..0ed2663088 100644 --- a/web/app/(commonLayout)/datasets/ApiServer.tsx +++ b/web/app/(commonLayout)/datasets/ApiServer.tsx @@ -31,8 +31,6 @@ const ApiServer: FC = ({
) diff --git a/web/app/components/app-sidebar/app-info.tsx b/web/app/components/app-sidebar/app-info.tsx index 12f9c59cd1..57eb013be7 100644 --- a/web/app/components/app-sidebar/app-info.tsx +++ b/web/app/components/app-sidebar/app-info.tsx @@ -1,18 +1,18 @@ import { useTranslation } from 'react-i18next' import { useRouter } from 'next/navigation' import { useContext, useContextSelector } from 'use-context-selector' -import { RiArrowDownSLine } from '@remixicon/react' import React, { useCallback, useState } from 'react' +import { + RiDeleteBinLine, + RiEditLine, + RiEqualizer2Line, + RiFileCopy2Line, + RiFileDownloadLine, + RiFileUploadLine, +} from '@remixicon/react' import AppIcon from '../base/app-icon' import SwitchAppModal from '../app/switch-app-modal' -import s from './style.module.css' import cn from '@/utils/classnames' -import { - PortalToFollowElem, - PortalToFollowElemContent, - PortalToFollowElemTrigger, -} from '@/app/components/base/portal-to-follow-elem' -import Divider from '@/app/components/base/divider' import Confirm from '@/app/components/base/confirm' import { useStore as useAppStore } from '@/app/components/app/store' import { ToastContext } from '@/app/components/base/toast' @@ -22,8 +22,6 @@ import { copyApp, deleteApp, exportAppConfig, updateAppInfo } from '@/service/ap import DuplicateAppModal from '@/app/components/app/duplicate-modal' import type { DuplicateAppModalProps } from '@/app/components/app/duplicate-modal' import CreateAppModal from '@/app/components/explore/create-app-modal' -import { AiText, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' -import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { getRedirection } from '@/utils/app-redirection' @@ -31,6 +29,9 @@ import UpdateDSLModal from '@/app/components/workflow/update-dsl-modal' import type { EnvironmentVariable } from '@/app/components/workflow/types' import DSLExportConfirmModal from '@/app/components/workflow/dsl-export-confirm-modal' import { fetchWorkflowDraft } from '@/service/workflow' +import ContentDialog from '@/app/components/base/content-dialog' +import Button from '@/app/components/base/button' +import CardView from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/cardView' export type IAppInfoProps = { expand: boolean @@ -47,7 +48,6 @@ const AppInfo = ({ expand }: IAppInfoProps) => { const [showEditModal, setShowEditModal] = useState(false) const [showDuplicateModal, setShowDuplicateModal] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) - const [showSwitchTip, setShowSwitchTip] = useState('') const [showSwitchModal, setShowSwitchModal] = useState(false) const [showImportDSLModal, setShowImportDSLModal] = useState(false) const [secretEnvList, setSecretEnvList] = useState([]) @@ -183,291 +183,199 @@ const AppInfo = ({ expand }: IAppInfoProps) => { return null return ( - -
- { - if (isCurrentWorkspaceEditor) - setOpen(v => !v) - }} - className='block' - > -
-
- - - {appDetail.mode === 'advanced-chat' && ( - - )} - {appDetail.mode === 'agent-chat' && ( - - )} - {appDetail.mode === 'chat' && ( - - )} - {appDetail.mode === 'completion' && ( - - )} - {appDetail.mode === 'workflow' && ( - - )} - -
- {expand && ( -
-
-
{appDetail.name}
- {isCurrentWorkspaceEditor && } -
-
- {appDetail.mode === 'advanced-chat' && ( - <> -
{t('app.types.chatbot').toUpperCase()}
-
{t('app.types.advanced').toUpperCase()}
- - )} - {appDetail.mode === 'agent-chat' && ( -
{t('app.types.agent').toUpperCase()}
- )} - {appDetail.mode === 'chat' && ( - <> -
{t('app.types.chatbot').toUpperCase()}
-
{(t('app.types.basic').toUpperCase())}
- - )} - {appDetail.mode === 'completion' && ( - <> -
{t('app.types.completion').toUpperCase()}
-
{(t('app.types.basic').toUpperCase())}
- - )} - {appDetail.mode === 'workflow' && ( -
{t('app.types.workflow').toUpperCase()}
- )} -
+
+ + setOpen(false)} + className='!p-0 flex flex-col absolute left-2 top-2 bottom-2 w-[420px] rounded-2xl' + > +
+
+ +
+
{appDetail.name}
+
{appDetail.mode === 'advanced-chat' ? t('app.types.chatbot') : appDetail.mode === 'agent-chat' ? t('app.types.agent') : appDetail.mode === 'chat' ? t('app.types.chatbot') : appDetail.mode === 'completion' ? t('app.types.completion') : t('app.types.workflow')}
- {/* description */} - {appDetail.description && ( -
{appDetail.description}
- )} - {/* operations */} - -
-
{ +
+ {/* description */} + {appDetail.description && ( +
{appDetail.description}
+ )} + {/* operations */} +
+
-
{ + }} + > + + {t('app.editApp')} + +
- {(appDetail.mode === 'completion' || appDetail.mode === 'chat') && ( - <> - -
setShowSwitchTip(appDetail.mode)} - onMouseLeave={() => setShowSwitchTip('')} - onClick={() => { - setOpen(false) - setShowSwitchModal(true) - }} - > - {t('app.switch')} -
- - )} - -
- {t('app.export')} -
- { - (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && ( -
{ - setOpen(false) - setShowImportDSLModal(true) - }}> - {t('workflow.common.importDSL')} -
- ) - } - -
{ - setOpen(false) - setShowConfirmDelete(true) - }}> - - {t('common.operation.delete')} - -
-
- {/* switch tip */} -
-
-
-
- {showSwitchTip === 'chat' ? t('app.types.advanced') : t('app.types.workflow')} - BETA -
-
{t('app.newApp.advancedFor').toLocaleUpperCase()}
-
{t('app.newApp.advancedDescription')}
-
-
+ + {t('app.duplicate')} + + + { + (appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && ( + + ) + }
- - {showSwitchModal && ( - setShowSwitchModal(false)} - onSuccess={() => setShowSwitchModal(false)} +
+
+ - )} - {showEditModal && ( - setShowEditModal(false)} - /> - )} - {showDuplicateModal && ( - setShowDuplicateModal(false)} - /> - )} - {showConfirmDelete && ( - setShowConfirmDelete(false)} - /> - )} - {showImportDSLModal && ( - setShowImportDSLModal(false)} - onBackup={exportCheck} - /> - )} - {secretEnvList.length > 0 && ( - setSecretEnvList([])} - /> - )} -
- +
+
+ +
+
+ {showSwitchModal && ( + setShowSwitchModal(false)} + onSuccess={() => setShowSwitchModal(false)} + /> + )} + {showEditModal && ( + setShowEditModal(false)} + /> + )} + {showDuplicateModal && ( + setShowDuplicateModal(false)} + /> + )} + {showConfirmDelete && ( + setShowConfirmDelete(false)} + /> + )} + {showImportDSLModal && ( + setShowImportDSLModal(false)} + onBackup={exportCheck} + /> + )} + {secretEnvList.length > 0 && ( + setSecretEnvList([])} + /> + )} +
) } diff --git a/web/app/components/app-sidebar/basic.tsx b/web/app/components/app-sidebar/basic.tsx index 51fc10721e..6600920ba7 100644 --- a/web/app/components/app-sidebar/basic.tsx +++ b/web/app/components/app-sidebar/basic.tsx @@ -58,7 +58,7 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type const { t } = useTranslation() return ( -
+
{icon && icon_background && iconType === 'app' && (
@@ -71,8 +71,10 @@ export default function AppBasic({ icon, icon_background, name, isExternal, type } {mode === 'expand' &&
-
- {name} +
+
+ {name} +
{hoverTip && }
-
{type}
{isExternal ? t('dataset.externalTag') : ''}
}
diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index 61e4bf8330..dec8499eb5 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -57,7 +57,7 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati
{iconType === 'app' && ( diff --git a/web/app/components/app/overview/appCard.tsx b/web/app/components/app/overview/appCard.tsx index f9f5c1fbff..72b9671f2d 100644 --- a/web/app/components/app/overview/appCard.tsx +++ b/web/app/components/app/overview/appCard.tsx @@ -1,14 +1,14 @@ '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' @@ -18,7 +18,6 @@ import Tooltip from '@/app/components/base/tooltip' 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' @@ -28,10 +27,12 @@ import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-butt 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 + isInPanel?: boolean cardType?: 'api' | 'webapp' customBgColor?: string onChangeStatus: (val: boolean) => Promise @@ -39,12 +40,9 @@ export type IAppCardProps = { onGenerateCode?: () => Promise } -const EmbedIcon = ({ className = '' }: HTMLProps) => { - return
-} - function AppCard({ appInfo, + isInPanel, cardType = 'webapp', customBgColor, onChangeStatus, @@ -66,17 +64,18 @@ function AppCard({ const OPERATIONS_MAP = useMemo(() => { const operationsMap = { webapp: [ - { opName: t('appOverview.overview.appInfo.preview'), opIcon: RocketLaunchIcon }, - { opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: PaintBrushIcon }, + { opName: t('appOverview.overview.appInfo.launch'), opIcon: RiExternalLinkLine }, ] as { opName: string; opIcon: any }[], - api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: DocumentTextIcon }], + api: [{ opName: t('appOverview.overview.apiInfo.doc'), opIcon: RiBookOpenLine }], app: [], } if (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') - operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: EmbedIcon }) + operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.embedded.entry'), opIcon: RiWindowLine }) + + operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.customize.entry'), opIcon: RiPaintBrushLine }) if (isCurrentWorkspaceEditor) - operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: Cog8ToothIcon }) + operationsMap.webapp.push({ opName: t('appOverview.overview.appInfo.settings.entry'), opIcon: RiEqualizer2Line }) return operationsMap }, [isCurrentWorkspaceEditor, appInfo, t]) @@ -92,13 +91,9 @@ function AppCard({ 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.preview'): + case t('appOverview.overview.appInfo.launch'): return () => { window.open(appUrl, '_blank') } @@ -135,49 +130,50 @@ function AppCard({ return (
-
-
- -
- - {runningStatus - ? t('appOverview.overview.status.running') - : t('appOverview.overview.status.disable')} - +
+
+
+ +
+ +
+ {runningStatus + ? t('appOverview.overview.status.running') + : t('appOverview.overview.status.disable')} +
+
-
-
-
-
+
+
{isApp ? t('appOverview.overview.appInfo.accessibleAddress') : t('appOverview.overview.apiInfo.accessibleAddress')}
-
-
-
+
+
+
{isApp ? appUrl : apiUrl}
- - {isApp && } + {isApp && } + {isApp && } {/* button copy link/ button regenerate */} {showConfirmDelete && (
setShowConfirmDelete(true)} >
-
- {!isApp && } +
+ {!isApp && } {OPERATIONS_MAP[cardType].map((op) => { const disabled = op.opName === t('appOverview.overview.appInfo.settings.entry') @@ -219,7 +215,9 @@ function AppCard({ : !runningStatus return ( diff --git a/web/app/components/base/content-dialog/index.tsx b/web/app/components/base/content-dialog/index.tsx new file mode 100644 index 0000000000..9e6726b1f6 --- /dev/null +++ b/web/app/components/base/content-dialog/index.tsx @@ -0,0 +1,59 @@ +import { Fragment, type ReactNode } from 'react' +import { Transition } from '@headlessui/react' +import classNames from '@/utils/classnames' + +type ContentDialogProps = { + className?: string + show: boolean + onClose?: () => void + children: ReactNode +} + +const ContentDialog = ({ + className, + show, + onClose, + children, +}: ContentDialogProps) => { + return ( + + +
+ + + +
+ {children} +
+
+ + ) +} + +export default ContentDialog diff --git a/web/app/components/base/copy-feedback/index.tsx b/web/app/components/base/copy-feedback/index.tsx index ead1eb1d18..ec6b6ddb31 100644 --- a/web/app/components/base/copy-feedback/index.tsx +++ b/web/app/components/base/copy-feedback/index.tsx @@ -35,7 +35,7 @@ const CopyFeedback = ({ content, className }: Props) => { } >
diff --git a/web/app/components/base/qrcode/index.tsx b/web/app/components/base/qrcode/index.tsx index c9323992e9..75d4831db7 100644 --- a/web/app/components/base/qrcode/index.tsx +++ b/web/app/components/base/qrcode/index.tsx @@ -1,7 +1,7 @@ 'use client' import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import QRCode from 'qrcode.react' +import { QRCodeSVG } from 'qrcode.react' import QrcodeStyle from './style.module.css' import Tooltip from '@/app/components/base/tooltip' @@ -54,20 +54,20 @@ const ShareQRCode = ({ content, selectorId, className }: Props) => { popupContent={t(`${prefixEmbedded}`) || ''} >
{isShow && (
- +
-
{t('appOverview.overview.appInfo.qrcode.scan')}
-
·
+
{t('appOverview.overview.appInfo.qrcode.scan')}
+
·
{t('appOverview.overview.appInfo.qrcode.download')}
diff --git a/web/app/components/develop/secret-key/secret-key-button.tsx b/web/app/components/develop/secret-key/secret-key-button.tsx index dab319bab4..e1845330ae 100644 --- a/web/app/components/develop/secret-key/secret-key-button.tsx +++ b/web/app/components/develop/secret-key/secret-key-button.tsx @@ -1,29 +1,31 @@ 'use client' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { RiKey2Line } from '@remixicon/react' import Button from '@/app/components/base/button' import SecretKeyModal from '@/app/components/develop/secret-key/secret-key-modal' -// import { KeyIcon } from '@heroicons/react/20/solid' type ISecretKeyButtonProps = { className?: string appId?: string - iconCls?: string textCls?: string } -const SecretKeyButton = ({ className, appId, iconCls, textCls }: ISecretKeyButtonProps) => { +const SecretKeyButton = ({ className, appId, textCls }: ISecretKeyButtonProps) => { const [isVisible, setVisible] = useState(false) const { t } = useTranslation() return ( <> - setVisible(false)} appId={appId} /> diff --git a/web/i18n/en-US/app-overview.ts b/web/i18n/en-US/app-overview.ts index 15801d1504..565805a271 100644 --- a/web/i18n/en-US/app-overview.ts +++ b/web/i18n/en-US/app-overview.ts @@ -33,6 +33,7 @@ const translation = { explanation: 'Ready-to-use AI WebApp', accessibleAddress: 'Public URL', preview: 'Preview', + launch: 'Launch', regenerate: 'Regenerate', regenerateNotice: 'Do you want to regenerate the public URL?', preUseReminder: 'Please enable WebApp before continuing.', diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index c116e080b4..018401aa56 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -27,6 +27,7 @@ const translation = { sure: 'I\'m sure', download: 'Download', delete: 'Delete', + deleteApp: 'Delete App', settings: 'Settings', setup: 'Setup', getForFree: 'Get for free', diff --git a/web/i18n/zh-Hans/app-overview.ts b/web/i18n/zh-Hans/app-overview.ts index a337058d07..6274a64f13 100644 --- a/web/i18n/zh-Hans/app-overview.ts +++ b/web/i18n/zh-Hans/app-overview.ts @@ -33,6 +33,7 @@ const translation = { explanation: '开箱即用的 AI WebApp', accessibleAddress: '公开访问 URL', preview: '预览', + launch: '启动', regenerate: '重新生成', regenerateNotice: '您是否要重新生成公开访问 URL?', preUseReminder: '使用前请先打开开关', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 53c5337cab..5ce1cb1385 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -27,6 +27,7 @@ const translation = { sure: '我确定', download: '下载', delete: '删除', + deleteApp: '删除应用', settings: '设置', setup: '设置', getForFree: '免费获取', diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 0da4968a7f..99d85bf32a 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -99,6 +99,8 @@ const config = { 'chatbot-bg': 'var(--color-chatbot-bg)', 'chat-bubble-bg': 'var(--color-chat-bubble-bg)', 'workflow-process-bg': 'var(--color-workflow-process-bg)', + 'app-detail-bg': 'var(--color-app-detail-bg)', + 'app-detail-overlay-bg': 'var(--color-app-detail-overlay-bg)', 'dataset-chunk-process-success-bg': 'var(--color-dataset-chunk-process-success-bg)', 'dataset-chunk-process-error-bg': 'var(--color-dataset-chunk-process-error-bg)', 'dataset-chunk-detail-card-hover-bg': 'var(--color-dataset-chunk-detail-card-hover-bg)', diff --git a/web/themes/manual-dark.css b/web/themes/manual-dark.css index 6d4c5f3908..052a080fa9 100644 --- a/web/themes/manual-dark.css +++ b/web/themes/manual-dark.css @@ -19,6 +19,17 @@ html[data-theme="dark"] { rgba(34, 34, 37, 0.9) -0.1%, rgba(29, 29, 32, 0.9) 98.26% ); + --color-app-detail-bg: linear-gradient( + 169deg, + #1D1D20 1.18%, + #222225 99.52% + ); + --color-app-detail-overlay-bg: linear-gradient( + 270deg, + rgba(0, 0, 0, 0.00) 0%, + rgba(24, 24, 27, 0.02) 8%, + rgba(24, 24, 27, 0.54) 100% + ); --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%); --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.30) 0%, rgba(0, 0, 0, 0.00) 100%); --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #1D1D20 0%, #222225 100%); diff --git a/web/themes/manual-light.css b/web/themes/manual-light.css index 501f9f1d1f..0566f4b1cd 100644 --- a/web/themes/manual-light.css +++ b/web/themes/manual-light.css @@ -19,6 +19,17 @@ html[data-theme="light"] { rgba(249, 250, 251, 0.9) -0.1%, rgba(242, 244, 247, 0.9) 98.26% ); + --color-app-detail-bg: linear-gradient( + 169deg, + #F2F4F7 1.18%, + #F9FAFB 99.52% + ); + --color-app-detail-overlay-bg: linear-gradient( + 270deg, + rgba(0, 0, 0, 0.00) 0%, + rgba(16, 24, 40, 0.01) 8%, + rgba(16, 24, 40, 0.18) 100% + ); --color-dataset-chunk-process-success-bg: linear-gradient(92deg, rgba(23, 178, 106, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%); --color-dataset-chunk-process-error-bg: linear-gradient(92deg, rgba(240, 68, 56, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%); --color-dataset-chunk-detail-card-hover-bg: linear-gradient(180deg, #F2F4F7 0%, #F9FAFB 100%);