feat(minapps): add locale-based filtering and i18n support (#12572)

* feat(i18n): update i18n name immediately

* feat(minapps): hide Chinese apps for non-Chinese locale users

- Add `locales` field to MinAppType for locale-based visibility
- Add locale filtering in useMinapps hook
- Mark 23 Chinese domestic apps with locales: ['zh-CN', 'zh-TW']

Apps hidden for non-Chinese users include: Wanzhi, ChatGLM, Kimi,
Baichuan, Qwen, Stepfun, Doubao, Hailuo, Wenxin, Baidu AI Search,
Tencent Yuanbao, Sensechat, SparkDesk, Metaso, Tiangong AI, Nami AI,
Xiaoyi, WPS AI, Zhihu, Dangbei AI, Ant Ling, LongCat, QwenChat

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: minapp logic

* fix: review comments

* fix: update MinimalToolbar to use allMinApps

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
SuYao 2026-01-25 09:31:16 +08:00 committed by GitHub
parent 5c013cdb63
commit f9f550cecd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 226 additions and 75 deletions

View File

@ -1,4 +1,4 @@
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import type { MinAppType } from '@renderer/types'
import type { FC } from 'react'
@ -10,10 +10,10 @@ interface Props {
}
const MinAppIcon: FC<Props> = ({ app, size = 48, style, sidebar = false }) => {
// First try to find in DEFAULT_MIN_APPS for predefined styling
const _app = DEFAULT_MIN_APPS.find((item) => item.id === app.id)
// First try to find in allMinApps for predefined styling
const _app = allMinApps.find((item) => item.id === app.id)
// If found in DEFAULT_MIN_APPS, use predefined styling
// If found in allMinApps, use predefined styling
if (_app) {
return (
<img
@ -34,7 +34,7 @@ const MinAppIcon: FC<Props> = ({ app, size = 48, style, sidebar = false }) => {
)
}
// If not found in DEFAULT_MIN_APPS but app has logo, use it (for temporary apps)
// If not found in allMinApps but app has logo, use it (for temporary apps)
if (app.logo) {
return (
<img

View File

@ -4,7 +4,7 @@ import { describe, expect, it, vi } from 'vitest'
import MinAppIcon from '../MinAppIcon'
vi.mock('@renderer/config/minapps', () => ({
DEFAULT_MIN_APPS: [
allMinApps: [
{
id: 'test-app-1',
name: 'Test App 1',
@ -52,7 +52,7 @@ describe('MinAppIcon', () => {
})
})
it('should return null when app is not found in DEFAULT_MIN_APPS', () => {
it('should return null when app is not found in allMinApps', () => {
const unknownApp = {
id: 'unknown-app',
name: 'Unknown App',

View File

@ -1,7 +1,7 @@
import { loggerService } from '@logger'
import MinAppIcon from '@renderer/components/Icons/MinAppIcon'
import IndicatorLight from '@renderer/components/IndicatorLight'
import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps } from '@renderer/config/minapps'
import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateAllMinApps } from '@renderer/config/minapps'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { useMinapps } from '@renderer/hooks/useMinapps'
import { useRuntime } from '@renderer/hooks/useRuntime'
@ -93,7 +93,7 @@ const MinApp: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
await window.api.file.writeWithId('custom-minapps.json', JSON.stringify(updatedApps, null, 2))
window.toast.success(t('settings.miniapps.custom.remove_success'))
const reloadedApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())]
updateDefaultMinApps(reloadedApps)
updateAllMinApps(reloadedApps)
updateMinapps(minapps.filter((item) => item.id !== app.id))
updatePinnedMinapps(pinned.filter((item) => item.id !== app.id))
updateDisabledMinapps(disabled.filter((item) => item.id !== app.id))
@ -122,7 +122,7 @@ const MinApp: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
</StyledIndicator>
)}
</IconContainer>
<AppTitle>{isLast ? t('settings.miniapps.custom.title') : app.name}</AppTitle>
<AppTitle>{isLast ? t('settings.miniapps.custom.title') : app.nameKey ? t(app.nameKey) : app.name}</AppTitle>
</Container>
</Dropdown>
)

View File

@ -13,7 +13,7 @@ import {
import { loggerService } from '@logger'
import WindowControls from '@renderer/components/WindowControls'
import { isDev, isLinux, isMac, isWin } from '@renderer/config/constant'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import { useBridge } from '@renderer/hooks/useBridge'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { useMinapps } from '@renderer/hooks/useMinapps'
@ -246,7 +246,7 @@ const MinappPopupContainer: React.FC = () => {
(acc, app) => ({
...acc,
[app.id]: {
canPinned: DEFAULT_MIN_APPS.some((item) => item.id === app.id),
canPinned: allMinApps.some((item) => item.id === app.id),
isPinned: pinned.some((item) => item.id === app.id),
canOpenExternalLink: app.url.startsWith('http://') || app.url.startsWith('https://')
}

View File

@ -3,7 +3,7 @@ import { loggerService } from '@logger'
import { Sortable, useDndReorder } from '@renderer/components/dnd'
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
import { isLinux, isMac } from '@renderer/config/constant'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useFullscreen } from '@renderer/hooks/useFullscreen'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
@ -58,7 +58,7 @@ const getTabIcon = (
// Check if it's a minapp tab (format: apps:appId)
if (tabId.startsWith('apps:')) {
const appId = tabId.replace('apps:', '')
let app = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId)
let app = [...allMinApps, ...minapps].find((app) => app.id === appId)
// If not found in permanent apps, search in temporary apps cache
// The cache stores apps opened via openSmartMinapp() for top navbar mode
@ -140,7 +140,7 @@ const TabsContainer: React.FC<TabsContainerProps> = ({ children }) => {
// Check if it's a minapp tab
if (tabId.startsWith('apps:')) {
const appId = tabId.replace('apps:', '')
let app = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId)
let app = [...allMinApps, ...minapps].find((app) => app.id === appId)
// If not found in permanent apps, search in temporary apps cache
// This ensures temporary MinApps display proper titles while being used

View File

@ -56,7 +56,6 @@ import GroqProviderLogo from '@renderer/assets/images/providers/groq.png?url'
import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png?url'
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png?url'
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png?url'
import i18n from '@renderer/i18n'
import type { MinAppType } from '@renderer/types'
const logger = loggerService.withContext('Config:minapps')
@ -118,14 +117,18 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'yi',
name: i18n.t('minapps.wanzhi'),
name: 'Wanzhi',
nameKey: 'minapps.wanzhi',
locales: ['zh-CN', 'zh-TW'],
url: 'https://www.wanzhi.com/',
logo: WanZhiAppLogo,
bodered: true
},
{
id: 'zhipu',
name: i18n.t('minapps.chatglm'),
name: 'ChatGLM',
nameKey: 'minapps.chatglm',
locales: ['zh-CN', 'zh-TW'],
url: 'https://chatglm.cn/main/alltoolsdetail',
logo: ZhipuProviderLogo,
bodered: true
@ -133,31 +136,40 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
{
id: 'moonshot',
name: 'Kimi',
locales: ['zh-CN', 'zh-TW'],
url: 'https://kimi.moonshot.cn/',
logo: KimiAppLogo
},
{
id: 'baichuan',
name: i18n.t('minapps.baichuan'),
name: 'Baichuan',
nameKey: 'minapps.baichuan',
locales: ['zh-CN', 'zh-TW'],
url: 'https://ying.baichuan-ai.com/chat',
logo: BaicuanAppLogo
},
{
id: 'dashscope',
name: i18n.t('minapps.qwen'),
name: 'Qwen',
nameKey: 'minapps.qwen',
locales: ['zh-CN', 'zh-TW'],
url: 'https://www.qianwen.com',
logo: QwenModelLogo
},
{
id: 'stepfun',
name: i18n.t('minapps.stepfun'),
name: 'Stepfun',
nameKey: 'minapps.stepfun',
locales: ['zh-CN', 'zh-TW'],
url: 'https://stepfun.com',
logo: StepfunAppLogo,
bodered: true
},
{
id: 'doubao',
name: i18n.t('minapps.doubao'),
name: 'Doubao',
nameKey: 'minapps.doubao',
locales: ['zh-CN', 'zh-TW'],
url: 'https://www.doubao.com/chat/',
logo: DoubaoAppLogo
},
@ -169,7 +181,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'minimax',
name: i18n.t('minapps.hailuo'),
name: 'Hailuo',
nameKey: 'minapps.hailuo',
locales: ['zh-CN', 'zh-TW'],
url: 'https://chat.minimaxi.com/',
logo: HailuoModelLogo,
bodered: true
@ -198,13 +212,17 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'baidu-ai-chat',
name: i18n.t('minapps.wenxin'),
name: 'Wenxin',
nameKey: 'minapps.wenxin',
locales: ['zh-CN', 'zh-TW'],
logo: BaiduAiAppLogo,
url: 'https://yiyan.baidu.com/'
},
{
id: 'baidu-ai-search',
name: i18n.t('minapps.baidu-ai-search'),
name: 'Baidu AI Search',
nameKey: 'minapps.baidu-ai-search',
locales: ['zh-CN', 'zh-TW'],
logo: BaiduAiSearchLogo,
url: 'https://chat.baidu.com/',
bodered: true,
@ -214,14 +232,18 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'tencent-yuanbao',
name: i18n.t('minapps.tencent-yuanbao'),
name: 'Tencent Yuanbao',
nameKey: 'minapps.tencent-yuanbao',
locales: ['zh-CN', 'zh-TW'],
logo: TencentYuanbaoAppLogo,
url: 'https://yuanbao.tencent.com/chat',
bodered: true
},
{
id: 'sensetime-chat',
name: i18n.t('minapps.sensechat'),
name: 'Sensechat',
nameKey: 'minapps.sensechat',
locales: ['zh-CN', 'zh-TW'],
logo: SensetimeAppLogo,
url: 'https://chat.sensetime.com/wb/chat',
bodered: true
@ -229,12 +251,15 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
{
id: 'spark-desk',
name: 'SparkDesk',
locales: ['zh-CN', 'zh-TW'],
logo: SparkDeskAppLogo,
url: 'https://xinghuo.xfyun.cn/desk'
},
{
id: 'metaso',
name: i18n.t('minapps.metaso'),
name: 'Metaso',
nameKey: 'minapps.metaso',
locales: ['zh-CN', 'zh-TW'],
logo: MetasoAppLogo,
url: 'https://metaso.cn/'
},
@ -258,7 +283,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'tiangong-ai',
name: i18n.t('minapps.tiangong-ai'),
name: 'Tiangong AI',
nameKey: 'minapps.tiangong-ai',
locales: ['zh-CN', 'zh-TW'],
logo: TiangongAiLogo,
url: 'https://www.tiangong.cn/',
bodered: true
@ -285,7 +312,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'nm',
name: i18n.t('minapps.nami-ai'),
name: 'Nami AI',
nameKey: 'minapps.nami-ai',
locales: ['zh-CN', 'zh-TW'],
logo: NamiAiLogo,
url: 'https://bot.n.cn/',
bodered: true
@ -329,6 +358,7 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
{
id: 'qwenlm',
name: 'QwenChat',
locales: ['zh-CN', 'zh-TW'],
logo: QwenlmAppLogo,
url: 'https://chat.qwen.ai'
},
@ -354,7 +384,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'xiaoyi',
name: i18n.t('minapps.xiaoyi'),
name: 'Xiaoyi',
nameKey: 'minapps.xiaoyi',
locales: ['zh-CN', 'zh-TW'],
logo: XiaoYiAppLogo,
url: 'https://xiaoyi.huawei.com/chat/',
bodered: true
@ -384,7 +416,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'wpslingxi',
name: i18n.t('minapps.wps-copilot'),
name: 'WPS AI',
nameKey: 'minapps.wps-copilot',
locales: ['zh-CN', 'zh-TW'],
logo: WPSLingXiLogo,
url: 'https://copilot.wps.cn/',
bodered: true
@ -425,14 +459,18 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
},
{
id: 'zhihu',
name: i18n.t('minapps.zhihu'),
name: 'Zhihu Zhida',
nameKey: 'minapps.zhihu',
locales: ['zh-CN', 'zh-TW'],
logo: ZhihuAppLogo,
url: 'https://zhida.zhihu.com/',
bodered: true
},
{
id: 'dangbei',
name: i18n.t('minapps.dangbei'),
name: 'Dangbei AI',
nameKey: 'minapps.dangbei',
locales: ['zh-CN', 'zh-TW'],
logo: DangbeiLogo,
url: 'https://ai.dangbei.com/',
bodered: true
@ -460,13 +498,16 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
{
id: 'longcat',
name: 'LongCat',
locales: ['zh-CN', 'zh-TW'],
logo: LongCatAppLogo,
url: 'https://longcat.chat/',
bodered: true
},
{
id: 'ling',
name: i18n.t('minapps.ant-ling'),
name: 'Ant Ling',
nameKey: 'minapps.ant-ling',
locales: ['zh-CN', 'zh-TW'],
url: 'https://ling.tbox.cn/chat',
logo: LingAppLogo,
bodered: true,
@ -486,11 +527,11 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
}
]
// 加载自定义小应用并合并到默认应用中
let DEFAULT_MIN_APPS = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())]
// All mini apps: built-in defaults + custom apps loaded from user config
let allMinApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())]
function updateDefaultMinApps(param) {
DEFAULT_MIN_APPS = param
function updateAllMinApps(apps: MinAppType[]) {
allMinApps = apps
}
export { DEFAULT_MIN_APPS, loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps }
export { allMinApps, loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateAllMinApps }

View File

@ -1,4 +1,4 @@
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' // 使用设置中的值
import NavigationService from '@renderer/services/NavigationService'
@ -120,10 +120,10 @@ export const useMinappPopup = () => {
[openMinapp]
)
/** Open a minapp by id (look up the minapp in DEFAULT_MIN_APPS) */
/** Open a minapp by id (look up the minapp in allMinApps) */
const openMinappById = useCallback(
(id: string, keepAlive: boolean = false) => {
const app = DEFAULT_MIN_APPS.find((app) => app?.id === id)
const app = allMinApps.find((app) => app?.id === id)
if (app) {
openMinapp(app, keepAlive)
}

View File

@ -1,25 +1,129 @@
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import type { RootState } from '@renderer/store'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setDisabledMinApps, setMinApps, setPinnedMinApps } from '@renderer/store/minapps'
import type { MinAppType } from '@renderer/types'
import type { LanguageVarious, MinAppType } from '@renderer/types'
import { useCallback, useMemo } from 'react'
/**
* Data Flow Design:
*
* PRINCIPLE: Locale filtering is a VIEW concern, not a DATA concern.
*
* - Redux stores ALL apps (including locale-restricted ones) to preserve user preferences
* - allMinApps is the template data source containing locale definitions
* - This hook applies locale filtering only when READING for UI display
* - When WRITING, locale-hidden apps are merged back to prevent data loss
*/
// Check if app should be visible for the given locale
const isVisibleForLocale = (app: MinAppType, language: LanguageVarious): boolean => {
if (!app.locales) return true
return app.locales.includes(language)
}
// Filter apps by locale - only show apps that match current language
const filterByLocale = (apps: MinAppType[], language: LanguageVarious): MinAppType[] => {
return apps.filter((app) => isVisibleForLocale(app, language))
}
// Get locale-hidden apps from allMinApps for the current language
// This uses allMinApps as source of truth for locale definitions
const getLocaleHiddenApps = (language: LanguageVarious): MinAppType[] => {
return allMinApps.filter((app) => !isVisibleForLocale(app, language))
}
export const useMinapps = () => {
const { enabled, disabled, pinned } = useAppSelector((state: RootState) => state.minapps)
const language = useAppSelector((state: RootState) => state.settings.language)
const dispatch = useAppDispatch()
const mapApps = useCallback(
(apps: MinAppType[]) => apps.map((app) => allMinApps.find((item) => item.id === app.id) || app),
[]
)
const getAllApps = useCallback(
(apps: MinAppType[], disabledApps: MinAppType[]) => {
const mappedApps = mapApps(apps)
const existingIds = new Set(mappedApps.map((app) => app.id))
const disabledIds = new Set(disabledApps.map((app) => app.id))
const missingApps = allMinApps.filter((app) => !existingIds.has(app.id) && !disabledIds.has(app.id))
return [...mappedApps, ...missingApps]
},
[mapApps]
)
// READ: Get apps filtered by locale for UI display
const minapps = useMemo(() => {
const allApps = getAllApps(enabled, disabled)
const disabledIds = new Set(disabled.map((app) => app.id))
const withoutDisabled = allApps.filter((app) => !disabledIds.has(app.id))
return filterByLocale(withoutDisabled, language)
}, [enabled, disabled, language, getAllApps])
const disabledApps = useMemo(() => filterByLocale(mapApps(disabled), language), [disabled, language, mapApps])
const pinnedApps = useMemo(() => filterByLocale(mapApps(pinned), language), [pinned, language, mapApps])
const updateMinapps = useCallback(
(visibleApps: MinAppType[]) => {
const disabledIds = new Set(disabled.map((app) => app.id))
const withoutDisabled = visibleApps.filter((app) => !disabledIds.has(app.id))
const localeHiddenApps = getLocaleHiddenApps(language)
const localeHiddenIds = new Set(localeHiddenApps.map((app) => app.id))
const preservedLocaleHidden = enabled.filter((app) => localeHiddenIds.has(app.id) && !disabledIds.has(app.id))
const visibleIds = new Set(withoutDisabled.map((app) => app.id))
const toAppend = preservedLocaleHidden.filter((app) => !visibleIds.has(app.id))
const merged = [...withoutDisabled, ...toAppend]
const existingIds = new Set(merged.map((app) => app.id))
const missingApps = allMinApps.filter((app) => !existingIds.has(app.id) && !disabledIds.has(app.id))
dispatch(setMinApps([...merged, ...missingApps]))
},
[dispatch, enabled, disabled, language]
)
// WRITE: Update disabled apps, preserving locale-hidden disabled apps
const updateDisabledMinapps = useCallback(
(visibleDisabledApps: MinAppType[]) => {
const localeHiddenApps = getLocaleHiddenApps(language)
const localeHiddenIds = new Set(localeHiddenApps.map((app) => app.id))
const preservedLocaleHidden = disabled.filter((app) => localeHiddenIds.has(app.id))
const visibleIds = new Set(visibleDisabledApps.map((app) => app.id))
const toAppend = preservedLocaleHidden.filter((app) => !visibleIds.has(app.id))
dispatch(setDisabledMinApps([...visibleDisabledApps, ...toAppend]))
},
[dispatch, disabled, language]
)
// WRITE: Update pinned apps, preserving locale-hidden pinned apps
const updatePinnedMinapps = useCallback(
(visiblePinnedApps: MinAppType[]) => {
const localeHiddenApps = getLocaleHiddenApps(language)
const localeHiddenIds = new Set(localeHiddenApps.map((app) => app.id))
const preservedLocaleHidden = pinned.filter((app) => localeHiddenIds.has(app.id))
const visibleIds = new Set(visiblePinnedApps.map((app) => app.id))
const toAppend = preservedLocaleHidden.filter((app) => !visibleIds.has(app.id))
dispatch(setPinnedMinApps([...visiblePinnedApps, ...toAppend]))
},
[dispatch, pinned, language]
)
return {
minapps: enabled.map((app) => DEFAULT_MIN_APPS.find((item) => item.id === app.id) || app),
disabled: disabled.map((app) => DEFAULT_MIN_APPS.find((item) => item.id === app.id) || app),
pinned: pinned.map((app) => DEFAULT_MIN_APPS.find((item) => item.id === app.id) || app),
updateMinapps: (minapps: MinAppType[]) => {
dispatch(setMinApps(minapps))
},
updateDisabledMinapps: (minapps: MinAppType[]) => {
dispatch(setDisabledMinApps(minapps))
},
updatePinnedMinapps: (minapps: MinAppType[]) => {
dispatch(setPinnedMinApps(minapps))
}
minapps,
disabled: disabledApps,
pinned: pinnedApps,
updateMinapps,
updateDisabledMinapps,
updatePinnedMinapps
}
}

View File

@ -1,5 +1,5 @@
import { loggerService } from '@logger'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { useMinapps } from '@renderer/hooks/useMinapps'
import { useNavbarPosition } from '@renderer/hooks/useSettings'
@ -51,7 +51,7 @@ const MinAppPage: FC = () => {
if (!appId) return null
// First try to find in default and custom mini-apps
let foundApp = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId)
let foundApp = [...allMinApps, ...minapps].find((app) => app.id === appId)
// If not found and we have cache, try to find in cache (for temporary apps)
if (!foundApp && minAppsCache) {

View File

@ -1,7 +1,7 @@
import { CloseOutlined } from '@ant-design/icons'
import type { DraggableProvided, DroppableProvided, DropResult } from '@hello-pangea/dnd'
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import { useMinapps } from '@renderer/hooks/useMinapps'
import { getMiniappsStatusLabel } from '@renderer/i18n/label'
import type { MinAppType } from '@renderer/types'
@ -91,7 +91,9 @@ const MiniAppIconsManager: FC<MiniAppManagerProps> = ({
)
const renderProgramItem = (program: MinAppType, provided: DraggableProvided, listType: ListType) => {
const { name, logo } = DEFAULT_MIN_APPS.find((app) => app.id === program.id) || { name: program.name, logo: '' }
const appData = allMinApps.find((app) => app.id === program.id)
const name = appData?.nameKey ? t(appData.nameKey) : appData?.name || program.name
const logo = appData?.logo || ''
return (
<ProgramItem ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>

View File

@ -1,5 +1,5 @@
import { UndoOutlined } from '@ant-design/icons' // 导入重置图标
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import { useMinapps } from '@renderer/hooks/useMinapps'
import { useSettings } from '@renderer/hooks/useSettings'
import { SettingDescription, SettingDivider, SettingRowTitle, SettingTitle } from '@renderer/pages/settings'
@ -32,9 +32,9 @@ const MiniAppSettings: FC = () => {
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null)
const handleResetMinApps = useCallback(() => {
setVisibleMiniApps(DEFAULT_MIN_APPS)
setVisibleMiniApps(allMinApps)
setDisabledMiniApps([])
updateMinapps(DEFAULT_MIN_APPS)
updateMinapps(allMinApps)
updateDisabledMinapps([])
}, [updateDisabledMinapps, updateMinapps])

View File

@ -1,6 +1,6 @@
import { PlusOutlined, UploadOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps } from '@renderer/config/minapps'
import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateAllMinApps } from '@renderer/config/minapps'
import { useMinapps } from '@renderer/hooks/useMinapps'
import type { MinAppType } from '@renderer/types'
import { Button, Form, Input, Modal, Radio, Upload } from 'antd'
@ -60,7 +60,7 @@ const NewAppButton: FC<Props> = ({ size = 60 }) => {
form.resetFields()
setFileList([])
const reloadedApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())]
updateDefaultMinApps(reloadedApps)
updateAllMinApps(reloadedApps)
updateMinapps([...minapps, newApp])
} catch (error) {
window.toast.error(t('settings.miniapps.custom.save_error'))

View File

@ -10,7 +10,7 @@ import {
} from '@ant-design/icons'
import { loggerService } from '@logger'
import { isDev } from '@renderer/config/constant'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import { useMinapps } from '@renderer/hooks/useMinapps'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store'
@ -50,7 +50,7 @@ const MinimalToolbar: FC<Props> = ({ app, webviewRef, currentUrl, onReload, onOp
const navigate = useNavigate()
const [canGoBack, setCanGoBack] = useState(false)
const [canGoForward, setCanGoForward] = useState(false)
const canPinned = DEFAULT_MIN_APPS.some((item) => item.id === app.id)
const canPinned = allMinApps.some((item) => item.id === app.id)
const isPinned = pinned.some((item) => item.id === app.id)
const canOpenExternalLink = app.url.startsWith('http://') || app.url.startsWith('https://')

View File

@ -22,7 +22,7 @@ import {
DEFAULT_TEMPERATURE,
isMac
} from '@renderer/config/constant'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import {
glm45FlashModel,
isFunctionCallingModel,
@ -99,7 +99,7 @@ function removeMiniAppFromState(state: RootState, id: string) {
function addMiniApp(state: RootState, id: string) {
if (state.minapps) {
const app = DEFAULT_MIN_APPS.find((app) => app.id === id)
const app = allMinApps.find((app) => app.id === id)
if (app) {
if (!state.minapps.enabled.find((app) => app.id === id)) {
state.minapps.enabled.push(app)
@ -1076,7 +1076,7 @@ const migrateConfig = {
if (state.minapps) {
appIds.forEach((id) => {
const app = DEFAULT_MIN_APPS.find((app) => app.id === id)
const app = allMinApps.find((app) => app.id === id)
if (app) {
state.minapps.enabled.push(app)
}

View File

@ -16,7 +16,7 @@
*/
import type { PayloadAction } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
import { allMinApps } from '@renderer/config/minapps'
import type { MinAppType } from '@renderer/types'
export interface MinAppsState {
@ -26,7 +26,7 @@ export interface MinAppsState {
}
const initialState: MinAppsState = {
enabled: DEFAULT_MIN_APPS,
enabled: allMinApps,
disabled: [],
pinned: []
}

View File

@ -459,6 +459,10 @@ export interface PaintingsState {
export type MinAppType = {
id: string
name: string
/** i18n key for translatable names */
nameKey?: string
/** Locale codes where this app should be visible (e.g., ['zh-CN', 'zh-TW']) */
locales?: LanguageVarious[]
logo?: string
url: string
// FIXME: It should be `bordered`

View File

@ -9,9 +9,9 @@ import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest'
vi.mock('@renderer/config/minapps', () => {
return {
ORIGIN_DEFAULT_MIN_APPS: [],
DEFAULT_MIN_APPS: [],
allMinApps: [],
loadCustomMiniApp: async () => [],
updateDefaultMinApps: vi.fn()
updateAllMinApps: vi.fn()
}
})