- {/* name & icon */}
-
-
-
{t(`${prefixSettings}.webName`, { ns: 'appOverview' })}
-
+ {/* name & icon */}
+
+
+
{t(`${prefixSettings}.webName`, { ns: 'appOverview' })}
+
+
+
{ setShowAppIconPicker(true) }}
+ className="mt-2 cursor-pointer"
+ iconType={appIcon.type}
+ icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
+ background={appIcon.type === 'image' ? undefined : appIcon.background}
+ imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
/>
-
{ setShowAppIconPicker(true) }}
- className="mt-2 cursor-pointer"
- iconType={appIcon.type}
- icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
- background={appIcon.type === 'image' ? undefined : appIcon.background}
- imageUrl={appIcon.type === 'image' ? appIcon.url : undefined}
- />
-
- {/* description */}
-
-
{t(`${prefixSettings}.webDesc`, { ns: 'appOverview' })}
-
-
- {/* answer icon */}
- {isChat && (
+ {/* description */}
+
+
{t(`${prefixSettings}.webDesc`, { ns: 'appOverview' })}
+
+
+ {/* answer icon */}
+ {isChat && (
+
+
+
{t('answerIcon.title', { ns: 'app' })}
+
setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })}
+ />
+
+
{t('answerIcon.description', { ns: 'app' })}
+
+ )}
+ {/* language */}
+
+
{t(`${prefixSettings}.language`, { ns: 'appOverview' })}
+
+
+ {/* theme color */}
+ {isChat && (
+
+
+
{t(`${prefixSettings}.chatColorTheme`, { ns: 'appOverview' })}
+
{t(`${prefixSettings}.chatColorThemeDesc`, { ns: 'appOverview' })}
+
+
+
+
+
{t(`${prefixSettings}.chatColorThemeInverted`, { ns: 'appOverview' })}
+
setInputInfo({ ...inputInfo, chatColorThemeInverted: v })}>
+
+
+
+ )}
+ {/* workflow detail */}
-
{t('answerIcon.title', { ns: 'app' })}
+
{t(`${prefixSettings}.workflow.subTitle`, { ns: 'appOverview' })}
setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })}
+ disabled={!(appInfo.mode === AppModeEnum.WORKFLOW || appInfo.mode === AppModeEnum.ADVANCED_CHAT)}
+ checked={inputInfo.show_workflow_steps}
+ onCheckedChange={v => setInputInfo({ ...inputInfo, show_workflow_steps: v })}
/>
-
{t('answerIcon.description', { ns: 'app' })}
+
{t(`${prefixSettings}.workflow.showDesc`, { ns: 'appOverview' })}
- )}
- {/* language */}
-
-
{t(`${prefixSettings}.language`, { ns: 'appOverview' })}
-
-
- {/* theme color */}
- {isChat && (
-
-
-
{t(`${prefixSettings}.chatColorTheme`, { ns: 'appOverview' })}
-
{t(`${prefixSettings}.chatColorThemeDesc`, { ns: 'appOverview' })}
-
-
-
-
-
{t(`${prefixSettings}.chatColorThemeInverted`, { ns: 'appOverview' })}
-
setInputInfo({ ...inputInfo, chatColorThemeInverted: v })}>
+ {/* more settings switch */}
+
+ {!isShowMore && (
+
setIsShowMore(true)}>
+
+
{t(`${prefixSettings}.more.entry`, { ns: 'appOverview' })}
+
+ {t(`${prefixSettings}.more.copyRightPlaceholder`, { ns: 'appOverview' })}
+ {' '}
+ &
+ {' '}
+ {t(`${prefixSettings}.more.privacyPolicyPlaceholder`, { ns: 'appOverview' })}
+
+
-
- )}
- {/* workflow detail */}
-
-
-
{t(`${prefixSettings}.workflow.subTitle`, { ns: 'appOverview' })}
-
setInputInfo({ ...inputInfo, show_workflow_steps: v })}
+ )}
+ {/* more settings */}
+ {isShowMore && (
+ <>
+ {/* copyright */}
+
+
+
+
{t(`${prefixSettings}.more.copyright`, { ns: 'appOverview' })}
+ {/* upgrade button */}
+ {enableBilling && isFreePlan && (
+
+
+
+
+
+ {t('upgradeBtn.encourageShort', { ns: 'billing' })}
+
+
+
+
+ )}
+
+ {webappCopyrightEnabled
+ ? (
+
setInputInfo({ ...inputInfo, copyrightSwitchValue: v })}
+ />
+ )
+ : (
+
+
+ setInputInfo({ ...inputInfo, copyrightSwitchValue: v })}
+ />
+
+ )}
+ />
+
+ {t(`${prefixSettings}.more.copyrightTooltip`, { ns: 'appOverview' })}
+
+
+ )}
+
+ {t(`${prefixSettings}.more.copyrightTip`, { ns: 'appOverview' })}
+ {inputInfo.copyrightSwitchValue && (
+
+ )}
+
+ {/* privacy policy */}
+
+
{t(`${prefixSettings}.more.privacyPolicy`, { ns: 'appOverview' })}
+
+ }}
+ />
+
+
+
+ {/* custom disclaimer */}
+
+
{t(`${prefixSettings}.more.customDisclaimer`, { ns: 'appOverview' })}
+
{t(`${prefixSettings}.more.customDisclaimerTip`, { ns: 'appOverview' })}
+
+
+ >
+ )}
+
+ {/* footer */}
+
+
+
+
+ {showAppIconPicker && (
+
e.stopPropagation()}>
+
{
+ setAppIcon(payload)
+ setShowAppIconPicker(false)
+ }}
+ onClose={() => {
+ setAppIcon(createAppIcon(appInfo))
+ setShowAppIconPicker(false)
+ }}
/>
-
{t(`${prefixSettings}.workflow.showDesc`, { ns: 'appOverview' })}
-
- {/* more settings switch */}
-
- {!isShowMore && (
-
setIsShowMore(true)}>
-
-
{t(`${prefixSettings}.more.entry`, { ns: 'appOverview' })}
-
- {t(`${prefixSettings}.more.copyRightPlaceholder`, { ns: 'appOverview' })}
- {' '}
- &
- {' '}
- {t(`${prefixSettings}.more.privacyPolicyPlaceholder`, { ns: 'appOverview' })}
-
-
-
-
)}
- {/* more settings */}
- {isShowMore && (
- <>
- {/* copyright */}
-
-
-
-
{t(`${prefixSettings}.more.copyright`, { ns: 'appOverview' })}
- {/* upgrade button */}
- {enableBilling && isFreePlan && (
-
-
-
-
-
- {t('upgradeBtn.encourageShort', { ns: 'billing' })}
-
-
-
-
- )}
-
-
{t(`${prefixSettings}.more.copyrightTooltip`, { ns: 'appOverview' })}
- }
- asChild={false}
- >
-
setInputInfo({ ...inputInfo, copyrightSwitchValue: v })}
- />
-
-
-
{t(`${prefixSettings}.more.copyrightTip`, { ns: 'appOverview' })}
- {inputInfo.copyrightSwitchValue && (
-
- )}
-
- {/* privacy policy */}
-
-
{t(`${prefixSettings}.more.privacyPolicy`, { ns: 'appOverview' })}
-
- }}
- />
-
-
-
- {/* custom disclaimer */}
-
-
{t(`${prefixSettings}.more.customDisclaimer`, { ns: 'appOverview' })}
-
{t(`${prefixSettings}.more.customDisclaimerTip`, { ns: 'appOverview' })}
-
-
- >
- )}
-
- {/* footer */}
-
-
-
-
- {showAppIconPicker && (
-
e.stopPropagation()}>
-
{
- setAppIcon(payload)
- setShowAppIconPicker(false)
- }}
- onClose={() => {
- setAppIcon(icon_type === 'image'
- ? { type: 'image', url: icon_url!, fileId: icon }
- : { type: 'emoji', icon, background: icon_background! })
- setShowAppIconPicker(false)
- }}
- />
-
- )}
-
+
+
>
)
}
diff --git a/web/app/components/apps/app-card.tsx b/web/app/components/apps/app-card.tsx
index 80aab3ce4d..458c7578c7 100644
--- a/web/app/components/apps/app-card.tsx
+++ b/web/app/components/apps/app-card.tsx
@@ -24,15 +24,19 @@ import {
DropdownMenuTrigger,
} from '@langgenius/dify-ui/dropdown-menu'
import { toast } from '@langgenius/dify-ui/toast'
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@langgenius/dify-ui/tooltip'
import { useSuspenseQuery } from '@tanstack/react-query'
import * as React from 'react'
-import { useCallback, useEffect, useId, useMemo, useState } from 'react'
+import { useCallback, useId, useMemo, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { AppTypeIcon } from '@/app/components/app/type-selector'
import AppIcon from '@/app/components/base/app-icon'
import Input from '@/app/components/base/input'
import TagSelector from '@/app/components/base/tag-management/selector'
-import Tooltip from '@/app/components/base/tooltip'
import { UserAvatarList } from '@/app/components/base/user-avatar-list'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
@@ -229,8 +233,9 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
setShowConfirmDelete(false)
setConfirmDeleteInput('')
}
- catch (e: any) {
- toast.error(`${t('appDeleteFailed', { ns: 'app' })}${'message' in e ? `: ${e.message}` : ''}`)
+ catch (e) {
+ const message = e instanceof Error ? e.message : ''
+ toast.error(`${t('appDeleteFailed', { ns: 'app' })}${message ? `: ${message}` : ''}`)
}
}, [app.id, mutateDeleteApp, onPlanInfoChanged, t])
@@ -313,8 +318,8 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
if (onRefresh)
onRefresh()
}
- catch (e: any) {
- toast.error(e.message || t('editFailed', { ns: 'app' }))
+ catch (e) {
+ toast.error(e instanceof Error ? e.message : t('editFailed', { ns: 'app' }))
}
}, [app.id, onRefresh, t])
@@ -391,10 +396,18 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
const shouldShowAccessControlOption = systemFeatures.webapp_auth.enabled && isCurrentWorkspaceEditor
const operationsMenuWidthClassName = shouldShowSwitchOption ? 'w-[256px]' : 'w-[216px]'
- const [tags, setTags] = useState
(app.tags)
- useEffect(() => {
- setTags(app.tags)
- }, [app.tags])
+ const appTagsKey = useMemo(() => app.tags.map(tag => tag.id).join(','), [app.tags])
+ const [tagState, setTagState] = useState<{ key: string, tags: Tag[] }>(() => ({
+ key: appTagsKey,
+ tags: app.tags,
+ }))
+ const tags = tagState.key === appTagsKey ? tagState.tags : app.tags
+ const handleTagsUpdate = useCallback((nextTags: Tag[]) => {
+ setTagState({
+ key: appTagsKey,
+ tags: nextTags,
+ })
+ }, [appTagsKey])
const EditTimeText = useMemo(() => {
const timeText = formatTime({
@@ -454,23 +467,39 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
)}
{app.access_mode === AccessMode.PUBLIC && (
-
-
+
+ }
+ />
+ {t('accessItemsDescription.anyone', { ns: 'app' })}
)}
{app.access_mode === AccessMode.SPECIFIC_GROUPS_MEMBERS && (
-
-
+
+ }
+ />
+ {t('accessItemsDescription.specific', { ns: 'app' })}
)}
{app.access_mode === AccessMode.ORGANIZATION && (
-
-
+
+ }
+ />
+ {t('accessItemsDescription.organization', { ns: 'app' })}
)}
{app.access_mode === AccessMode.EXTERNAL_MEMBERS && (
-
-
+
+ }
+ />
+ {t('accessItemsDescription.external', { ns: 'app' })}
)}
@@ -501,7 +530,7 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {
targetID={app.id}
value={tags.map(tag => tag.id)}
selectedTags={tags}
- onCacheUpdate={setTags}
+ onCacheUpdate={handleTagsUpdate}
onChange={onRefresh}
/>
@@ -532,42 +561,40 @@ const AppCard = ({ app, onlineUsers = [], onRefresh }: AppCardProps) => {