mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-06 05:55:28 +08:00
feat(minapps): add proper region/language tags for all mini apps (#12636)
### What this PR does 为所有小程序添加了正确的地区(supportedRegions)和语言(locales)标签,并优化了过滤逻辑。 ### Before this PR - 部分小程序缺少 supportedRegions 和 locales 标签 - 用户手动固定的地区限制小程序在切换地区后会消失 ### After this PR - 所有小程序都配置了正确的 supportedRegions 和 locales 标签 - 用户主动固定的小程序不再受地区/语言过滤影响,始终显示 ### Why we need it 1. 地区过滤: Global 用户不应该看到仅中国可用的小程序 2. 语言过滤: 用户应该看到支持其界面语言的小程序 3. 用户体验: 用户固定的小程序代表明确意图,不应被过滤 ### Special notes for your reviewer 主要修改文件: - src/renderer/src/config/minapps.ts: 添加/修正小程序标签 - src/renderer/src/hooks/useMinapps.ts: 优化过滤逻辑,pinned apps 不受过滤影响 ### Release note feat(minapps): 已添加所有小程序地区/语言标签,固定小程序不再受地区过滤影响 --------- Co-authored-by: SuYao <sy20010504@gmail.com> Co-authored-by: Phantom <eurfelux@gmail.com>
This commit is contained in:
@@ -42,6 +42,7 @@ export enum IpcChannel {
|
||||
App_SetFullScreen = 'app:set-full-screen',
|
||||
App_IsFullScreen = 'app:is-full-screen',
|
||||
App_GetSystemFonts = 'app:get-system-fonts',
|
||||
App_GetIpCountry = 'app:get-ip-country',
|
||||
APP_CrashRenderProcess = 'app:crash-render-process',
|
||||
|
||||
App_MacIsProcessTrusted = 'app:mac-is-process-trusted',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { loggerService } from '@logger'
|
||||
import { isLinux, isMac, isPortable, isWin } from '@main/constant'
|
||||
import { generateSignature } from '@main/integration/cherryai'
|
||||
import anthropicService from '@main/services/AnthropicService'
|
||||
import { getIpCountry } from '@main/utils/ipService'
|
||||
import {
|
||||
autoDiscoverGitBash,
|
||||
checkGitAvailable,
|
||||
@@ -307,6 +308,11 @@ export async function registerIpc(mainWindow: BrowserWindow, app: Electron.App)
|
||||
}
|
||||
})
|
||||
|
||||
// Get IP Country
|
||||
ipcMain.handle(IpcChannel.App_GetIpCountry, async () => {
|
||||
return getIpCountry()
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any, isNotify: boolean = false) => {
|
||||
configManager.set(key, value, isNotify)
|
||||
})
|
||||
|
||||
@@ -146,6 +146,7 @@ const api = {
|
||||
setFullScreen: (value: boolean): Promise<void> => ipcRenderer.invoke(IpcChannel.App_SetFullScreen, value),
|
||||
isFullScreen: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_IsFullScreen),
|
||||
getSystemFonts: (): Promise<string[]> => ipcRenderer.invoke(IpcChannel.App_GetSystemFonts),
|
||||
getIpCountry: (): Promise<string> => ipcRenderer.invoke(IpcChannel.App_GetIpCountry),
|
||||
mockCrashRenderProcess: () => ipcRenderer.invoke(IpcChannel.APP_CrashRenderProcess),
|
||||
mac: {
|
||||
isProcessTrusted: (): Promise<boolean> => ipcRenderer.invoke(IpcChannel.App_MacIsProcessTrusted),
|
||||
|
||||
@@ -34,6 +34,8 @@ const MinApp: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
|
||||
const navigate = useNavigate()
|
||||
const isPinned = pinned.some((p) => p.id === app.id)
|
||||
const isVisible = minapps.some((m) => m.id === app.id)
|
||||
// Pinned apps should always be visible regardless of region/locale filtering
|
||||
const shouldShow = isVisible || isPinned
|
||||
const isActive = minappShow && currentMinappId === app.id
|
||||
const isOpened = openedKeepAliveMinapps.some((item) => item.id === app.id)
|
||||
const { isTopNavbar } = useNavbarPosition()
|
||||
@@ -107,7 +109,7 @@ const MinApp: FC<Props> = ({ app, onClick, size = 60, isLast }) => {
|
||||
: [])
|
||||
]
|
||||
|
||||
if (!isVisible) {
|
||||
if (!shouldShow) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,8 @@ const loadCustomMiniApp = async (): Promise<MinAppType[]> => {
|
||||
...app,
|
||||
type: 'Custom',
|
||||
logo: app.logo && app.logo !== '' ? app.logo : ApplicationLogo,
|
||||
addTime: app.addTime || now
|
||||
addTime: app.addTime || now,
|
||||
supportedRegions: ['CN', 'Global'] // Custom mini apps should always be visible for all regions
|
||||
}))
|
||||
} catch (error) {
|
||||
logger.error('Failed to load custom mini apps:', error as Error)
|
||||
@@ -94,111 +95,118 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
|
||||
name: 'ChatGPT',
|
||||
url: 'https://chatgpt.com/',
|
||||
logo: OpenAiProviderLogo,
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'gemini',
|
||||
name: 'Gemini',
|
||||
url: 'https://gemini.google.com/',
|
||||
logo: GeminiAppLogo,
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'silicon',
|
||||
name: 'SiliconFlow',
|
||||
url: 'https://cloud.siliconflow.cn/playground/chat',
|
||||
logo: SiliconFlowProviderLogo
|
||||
logo: SiliconFlowProviderLogo,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'deepseek',
|
||||
name: 'DeepSeek',
|
||||
url: 'https://chat.deepseek.com/',
|
||||
logo: DeepSeekProviderLogo
|
||||
logo: DeepSeekProviderLogo,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'yi',
|
||||
name: 'Wanzhi',
|
||||
nameKey: 'minapps.wanzhi',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
url: 'https://www.wanzhi.com/',
|
||||
logo: WanZhiAppLogo,
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'zhipu',
|
||||
name: 'ChatGLM',
|
||||
nameKey: 'minapps.chatglm',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
url: 'https://chatglm.cn/main/alltoolsdetail',
|
||||
logo: ZhipuProviderLogo,
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'moonshot',
|
||||
name: 'Kimi',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
url: 'https://kimi.moonshot.cn/',
|
||||
logo: KimiAppLogo
|
||||
logo: KimiAppLogo,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'baichuan',
|
||||
name: 'Baichuan',
|
||||
nameKey: 'minapps.baichuan',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
url: 'https://ying.baichuan-ai.com/chat',
|
||||
logo: BaicuanAppLogo
|
||||
logo: BaicuanAppLogo,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'dashscope',
|
||||
name: 'Qwen',
|
||||
nameKey: 'minapps.qwen',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
url: 'https://www.qianwen.com',
|
||||
logo: QwenModelLogo
|
||||
logo: QwenModelLogo,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'stepfun',
|
||||
name: 'Stepfun',
|
||||
nameKey: 'minapps.stepfun',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
url: 'https://stepfun.com',
|
||||
logo: StepfunAppLogo,
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'doubao',
|
||||
name: 'Doubao',
|
||||
nameKey: 'minapps.doubao',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
url: 'https://www.doubao.com/chat/',
|
||||
logo: DoubaoAppLogo
|
||||
logo: DoubaoAppLogo,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'cici',
|
||||
name: 'Cici',
|
||||
url: 'https://www.cici.com/chat/',
|
||||
logo: CiciAppLogo
|
||||
logo: CiciAppLogo,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'minimax',
|
||||
name: 'Hailuo',
|
||||
nameKey: 'minapps.hailuo',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
url: 'https://chat.minimaxi.com/',
|
||||
logo: HailuoModelLogo,
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'groq',
|
||||
name: 'Groq',
|
||||
url: 'https://chat.groq.com/',
|
||||
logo: GroqProviderLogo
|
||||
logo: GroqProviderLogo,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'anthropic',
|
||||
name: 'Claude',
|
||||
url: 'https://claude.ai/',
|
||||
logo: ClaudeAppLogo
|
||||
logo: ClaudeAppLogo,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'google',
|
||||
@@ -208,116 +216,123 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
|
||||
bodered: true,
|
||||
style: {
|
||||
padding: 5
|
||||
}
|
||||
},
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'baidu-ai-chat',
|
||||
name: 'Wenxin',
|
||||
nameKey: 'minapps.wenxin',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: BaiduAiAppLogo,
|
||||
url: 'https://yiyan.baidu.com/'
|
||||
url: 'https://yiyan.baidu.com/',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: '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,
|
||||
style: {
|
||||
padding: 5
|
||||
}
|
||||
},
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'tencent-yuanbao',
|
||||
name: 'Tencent Yuanbao',
|
||||
nameKey: 'minapps.tencent-yuanbao',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: TencentYuanbaoAppLogo,
|
||||
url: 'https://yuanbao.tencent.com/chat',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'sensetime-chat',
|
||||
name: 'Sensechat',
|
||||
nameKey: 'minapps.sensechat',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: SensetimeAppLogo,
|
||||
url: 'https://chat.sensetime.com/wb/chat',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'spark-desk',
|
||||
name: 'SparkDesk',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: SparkDeskAppLogo,
|
||||
url: 'https://xinghuo.xfyun.cn/desk'
|
||||
url: 'https://xinghuo.xfyun.cn/desk',
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'metaso',
|
||||
name: 'Metaso',
|
||||
nameKey: 'minapps.metaso',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: MetasoAppLogo,
|
||||
url: 'https://metaso.cn/'
|
||||
url: 'https://metaso.cn/',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'poe',
|
||||
name: 'Poe',
|
||||
logo: PoeAppLogo,
|
||||
url: 'https://poe.com'
|
||||
url: 'https://poe.com',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'perplexity',
|
||||
name: 'Perplexity',
|
||||
logo: PerplexityAppLogo,
|
||||
url: 'https://www.perplexity.ai/'
|
||||
url: 'https://www.perplexity.ai/',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'devv',
|
||||
name: 'DEVV_',
|
||||
logo: DevvAppLogo,
|
||||
url: 'https://devv.ai/'
|
||||
url: 'https://devv.ai/',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'tiangong-ai',
|
||||
name: 'Tiangong AI',
|
||||
nameKey: 'minapps.tiangong-ai',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: TiangongAiLogo,
|
||||
url: 'https://www.tiangong.cn/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'Felo',
|
||||
name: 'Felo',
|
||||
logo: FeloAppLogo,
|
||||
url: 'https://felo.ai/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'duckduckgo',
|
||||
name: 'DuckDuckGo',
|
||||
logo: DuckDuckGoAppLogo,
|
||||
url: 'https://duck.ai'
|
||||
url: 'https://duck.ai',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'bolt',
|
||||
name: 'bolt',
|
||||
logo: BoltAppLogo,
|
||||
url: 'https://bolt.new/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'nm',
|
||||
name: 'Nami AI',
|
||||
nameKey: 'minapps.nami-ai',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: NamiAiLogo,
|
||||
url: 'https://bot.n.cn/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'thinkany',
|
||||
@@ -327,82 +342,92 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
|
||||
bodered: true,
|
||||
style: {
|
||||
padding: 5
|
||||
}
|
||||
},
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'github-copilot',
|
||||
name: 'GitHub Copilot',
|
||||
logo: GithubCopilotLogo,
|
||||
url: 'https://github.com/copilot'
|
||||
url: 'https://github.com/copilot',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'genspark',
|
||||
name: 'Genspark',
|
||||
logo: GensparkLogo,
|
||||
url: 'https://www.genspark.ai/'
|
||||
url: 'https://www.genspark.ai/',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'grok',
|
||||
name: 'Grok',
|
||||
logo: GrokAppLogo,
|
||||
url: 'https://grok.com',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'grok-x',
|
||||
name: 'Grok / X',
|
||||
logo: GrokXAppLogo,
|
||||
url: 'https://x.com/i/grok',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'qwenlm',
|
||||
name: 'QwenChat',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: QwenlmAppLogo,
|
||||
url: 'https://chat.qwen.ai'
|
||||
url: 'https://chat.qwen.ai',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'flowith',
|
||||
name: 'Flowith',
|
||||
logo: FlowithAppLogo,
|
||||
url: 'https://www.flowith.io/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: '3mintop',
|
||||
name: '3MinTop',
|
||||
logo: ThreeMinTopAppLogo,
|
||||
url: 'https://3min.top',
|
||||
bodered: false
|
||||
bodered: false,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'aistudio',
|
||||
name: 'AI Studio',
|
||||
logo: AIStudioLogo,
|
||||
url: 'https://aistudio.google.com/'
|
||||
url: 'https://aistudio.google.com/',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'xiaoyi',
|
||||
name: 'Xiaoyi',
|
||||
nameKey: 'minapps.xiaoyi',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: XiaoYiAppLogo,
|
||||
url: 'https://xiaoyi.huawei.com/chat/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'notebooklm',
|
||||
name: 'NotebookLM',
|
||||
logo: NotebookLMAppLogo,
|
||||
url: 'https://notebooklm.google.com/'
|
||||
url: 'https://notebooklm.google.com/',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'coze',
|
||||
name: 'Coze',
|
||||
logo: CozeAppLogo,
|
||||
url: 'https://www.coze.com/space',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'dify',
|
||||
@@ -412,68 +437,74 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
|
||||
bodered: true,
|
||||
style: {
|
||||
padding: 5
|
||||
}
|
||||
},
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'wpslingxi',
|
||||
name: 'WPS AI',
|
||||
nameKey: 'minapps.wps-copilot',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: WPSLingXiLogo,
|
||||
url: 'https://copilot.wps.cn/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'lechat',
|
||||
name: 'LeChat',
|
||||
logo: LeChatLogo,
|
||||
url: 'https://chat.mistral.ai/chat',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'abacus',
|
||||
name: 'Abacus',
|
||||
logo: AbacusLogo,
|
||||
url: 'https://apps.abacus.ai/chatllm',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'lambdachat',
|
||||
name: 'Lambda Chat',
|
||||
logo: LambdaChatLogo,
|
||||
url: 'https://lambda.chat/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'monica',
|
||||
name: 'Monica',
|
||||
logo: MonicaLogo,
|
||||
url: 'https://monica.im/home/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'you',
|
||||
name: 'You',
|
||||
logo: YouLogo,
|
||||
url: 'https://you.com/'
|
||||
url: 'https://you.com/',
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'zhihu',
|
||||
name: 'Zhihu Zhida',
|
||||
nameKey: 'minapps.zhihu',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: ZhihuAppLogo,
|
||||
url: 'https://zhida.zhihu.com/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN']
|
||||
},
|
||||
{
|
||||
id: 'dangbei',
|
||||
name: 'Dangbei AI',
|
||||
nameKey: 'minapps.dangbei',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: DangbeiLogo,
|
||||
url: 'https://ai.dangbei.com/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: `zai`,
|
||||
@@ -483,7 +514,8 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
|
||||
bodered: true,
|
||||
style: {
|
||||
padding: 10
|
||||
}
|
||||
},
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'n8n',
|
||||
@@ -493,27 +525,28 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
|
||||
bodered: true,
|
||||
style: {
|
||||
padding: 5
|
||||
}
|
||||
},
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'longcat',
|
||||
name: 'LongCat',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
logo: LongCatAppLogo,
|
||||
url: 'https://longcat.chat/',
|
||||
bodered: true
|
||||
bodered: true,
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'ling',
|
||||
name: 'Ant Ling',
|
||||
nameKey: 'minapps.ant-ling',
|
||||
locales: ['zh-CN', 'zh-TW'],
|
||||
url: 'https://ling.tbox.cn/chat',
|
||||
logo: LingAppLogo,
|
||||
bodered: true,
|
||||
style: {
|
||||
padding: 6
|
||||
}
|
||||
},
|
||||
supportedRegions: ['CN', 'Global']
|
||||
},
|
||||
{
|
||||
id: 'huggingchat',
|
||||
@@ -523,7 +556,8 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [
|
||||
bodered: true,
|
||||
style: {
|
||||
padding: 6
|
||||
}
|
||||
},
|
||||
supportedRegions: ['CN', 'Global']
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -2,42 +2,101 @@ 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 { LanguageVarious, MinAppType } from '@renderer/types'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { setDetectedRegion } from '@renderer/store/runtime'
|
||||
import type { MinAppRegion, MinAppType } from '@renderer/types'
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
|
||||
/**
|
||||
* Data Flow Design:
|
||||
*
|
||||
* PRINCIPLE: Locale filtering is a VIEW concern, not a DATA concern.
|
||||
* PRINCIPLE: Region 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
|
||||
* - Redux stores ALL apps (including region-restricted ones) to preserve user preferences
|
||||
* - allMinApps is the template data source containing region definitions
|
||||
* - This hook applies region filtering only when READING for UI display
|
||||
* - When WRITING, 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)
|
||||
/**
|
||||
* Check if app should be visible for the given region.
|
||||
*
|
||||
* Region-based visibility rules:
|
||||
* 1. CN users see everything
|
||||
* 2. Global users: only show apps with supportedRegions including 'Global'
|
||||
* (apps without supportedRegions field are treated as CN-only)
|
||||
*/
|
||||
const isVisibleForRegion = (app: MinAppType, region: MinAppRegion): boolean => {
|
||||
// CN users see everything
|
||||
if (region === 'CN') return true
|
||||
|
||||
// Global users: check if app supports international
|
||||
// If no supportedRegions field, treat as CN-only (hidden from Global users)
|
||||
if (!app.supportedRegions || app.supportedRegions.length === 0) {
|
||||
return false
|
||||
}
|
||||
return app.supportedRegions.includes('Global')
|
||||
}
|
||||
|
||||
// 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))
|
||||
// Filter apps by region
|
||||
const filterByRegion = (apps: MinAppType[], region: MinAppRegion): MinAppType[] => {
|
||||
return apps.filter((app) => isVisibleForRegion(app, region))
|
||||
}
|
||||
|
||||
// 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))
|
||||
// Get region-hidden apps from allMinApps for the current region
|
||||
const getRegionHiddenApps = (region: MinAppRegion): MinAppType[] => {
|
||||
return allMinApps.filter((app) => !isVisibleForRegion(app, region))
|
||||
}
|
||||
|
||||
// Module-level promise to ensure only one IP detection request is made
|
||||
let regionDetectionPromise: Promise<MinAppRegion> | null = null
|
||||
|
||||
// Detect user region via IPC call to main process (cached at module level)
|
||||
const detectUserRegion = async (): Promise<MinAppRegion> => {
|
||||
// Return existing promise if detection is already in progress
|
||||
if (regionDetectionPromise) {
|
||||
return regionDetectionPromise
|
||||
}
|
||||
|
||||
regionDetectionPromise = (async () => {
|
||||
try {
|
||||
const country = await window.api.getIpCountry()
|
||||
return country.toUpperCase() === 'CN' ? 'CN' : 'Global'
|
||||
} catch {
|
||||
// If detection fails, assume CN to show all apps (conservative approach)
|
||||
return 'CN'
|
||||
}
|
||||
})()
|
||||
|
||||
return regionDetectionPromise
|
||||
}
|
||||
|
||||
export const useMinapps = () => {
|
||||
const { enabled, disabled, pinned } = useAppSelector((state: RootState) => state.minapps)
|
||||
const language = useAppSelector((state: RootState) => state.settings.language)
|
||||
const minAppRegionSetting = useAppSelector((state: RootState) => state.settings.minAppRegion)
|
||||
const detectedRegion = useAppSelector((state: RootState) => state.runtime.detectedRegion)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
// Track if this hook instance has initiated detection to avoid duplicate requests
|
||||
const hasInitiatedDetection = useRef(false)
|
||||
|
||||
// Compute effective region: use cached detection result or manual setting
|
||||
const effectiveRegion: MinAppRegion = minAppRegionSetting === 'auto' ? (detectedRegion ?? 'CN') : minAppRegionSetting
|
||||
|
||||
// Only detect region once globally when in 'auto' mode and not yet detected
|
||||
useEffect(() => {
|
||||
const initRegion = async () => {
|
||||
// Skip if not in auto mode, already detected, or this instance already initiated
|
||||
if (minAppRegionSetting !== 'auto' || detectedRegion !== null || hasInitiatedDetection.current) {
|
||||
return
|
||||
}
|
||||
|
||||
hasInitiatedDetection.current = true
|
||||
const detected = await detectUserRegion()
|
||||
dispatch(setDetectedRegion(detected))
|
||||
}
|
||||
initRegion()
|
||||
}, [minAppRegionSetting, detectedRegion, dispatch])
|
||||
|
||||
const mapApps = useCallback(
|
||||
(apps: MinAppType[]) => apps.map((app) => allMinApps.find((item) => item.id === app.id) || app),
|
||||
[]
|
||||
@@ -54,30 +113,39 @@ export const useMinapps = () => {
|
||||
[mapApps]
|
||||
)
|
||||
|
||||
// READ: Get apps filtered by locale for UI display
|
||||
// READ: Get apps filtered by region 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])
|
||||
return filterByRegion(withoutDisabled, effectiveRegion)
|
||||
}, [enabled, disabled, effectiveRegion, getAllApps])
|
||||
|
||||
const disabledApps = useMemo(() => filterByLocale(mapApps(disabled), language), [disabled, language, mapApps])
|
||||
const pinnedApps = useMemo(() => filterByLocale(mapApps(pinned), language), [pinned, language, mapApps])
|
||||
const disabledApps = useMemo(
|
||||
() => filterByRegion(mapApps(disabled), effectiveRegion),
|
||||
[disabled, effectiveRegion, mapApps]
|
||||
)
|
||||
// Pinned apps are always visible regardless of region/language
|
||||
// User explicitly pinned apps should not be hidden
|
||||
const pinnedApps = useMemo(() => mapApps(pinned), [pinned, mapApps])
|
||||
|
||||
// Get hidden apps for preserving user preferences when writing
|
||||
const getHiddenApps = useCallback((region: MinAppRegion) => {
|
||||
const regionHidden = getRegionHiddenApps(region)
|
||||
const hiddenIds = new Set(regionHidden.map((app) => app.id))
|
||||
return hiddenIds
|
||||
}, [])
|
||||
|
||||
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 hiddenIds = getHiddenApps(effectiveRegion)
|
||||
const preservedHidden = enabled.filter((app) => hiddenIds.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 toAppend = preservedHidden.filter((app) => !visibleIds.has(app.id))
|
||||
const merged = [...withoutDisabled, ...toAppend]
|
||||
|
||||
const existingIds = new Set(merged.map((app) => app.id))
|
||||
@@ -85,37 +153,35 @@ export const useMinapps = () => {
|
||||
|
||||
dispatch(setMinApps([...merged, ...missingApps]))
|
||||
},
|
||||
[dispatch, enabled, disabled, language]
|
||||
[dispatch, enabled, disabled, effectiveRegion, getHiddenApps]
|
||||
)
|
||||
|
||||
// WRITE: Update disabled apps, preserving locale-hidden disabled apps
|
||||
// WRITE: Update disabled apps, preserving 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 hiddenIds = getHiddenApps(effectiveRegion)
|
||||
const preservedHidden = disabled.filter((app) => hiddenIds.has(app.id))
|
||||
|
||||
const visibleIds = new Set(visibleDisabledApps.map((app) => app.id))
|
||||
const toAppend = preservedLocaleHidden.filter((app) => !visibleIds.has(app.id))
|
||||
const toAppend = preservedHidden.filter((app) => !visibleIds.has(app.id))
|
||||
|
||||
dispatch(setDisabledMinApps([...visibleDisabledApps, ...toAppend]))
|
||||
},
|
||||
[dispatch, disabled, language]
|
||||
[dispatch, disabled, effectiveRegion, getHiddenApps]
|
||||
)
|
||||
|
||||
// WRITE: Update pinned apps, preserving locale-hidden pinned apps
|
||||
// WRITE: Update pinned apps, preserving 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 hiddenIds = getHiddenApps(effectiveRegion)
|
||||
const preservedHidden = pinned.filter((app) => hiddenIds.has(app.id))
|
||||
|
||||
const visibleIds = new Set(visiblePinnedApps.map((app) => app.id))
|
||||
const toAppend = preservedLocaleHidden.filter((app) => !visibleIds.has(app.id))
|
||||
const toAppend = preservedHidden.filter((app) => !visibleIds.has(app.id))
|
||||
|
||||
dispatch(setPinnedMinApps([...visiblePinnedApps, ...toAppend]))
|
||||
},
|
||||
[dispatch, pinned, language]
|
||||
[dispatch, pinned, effectiveRegion, getHiddenApps]
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "Open new-window links in browser"
|
||||
},
|
||||
"region": {
|
||||
"auto": "Auto detect",
|
||||
"cn": "China",
|
||||
"description": "Filtering unsupported mini-programs based on the region",
|
||||
"global": "Global",
|
||||
"title": "Mini Program filter"
|
||||
},
|
||||
"reset_tooltip": "Reset to default",
|
||||
"sidebar_description": "Show active mini apps in the sidebar",
|
||||
"sidebar_title": "Sidebar Active Mini Apps Display",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "在浏览器中打开新窗口链接"
|
||||
},
|
||||
"region": {
|
||||
"auto": "自动检测",
|
||||
"cn": "中国",
|
||||
"description": "根据所在地区过滤不支持的小程序",
|
||||
"global": "全球",
|
||||
"title": "小程序区域筛选"
|
||||
},
|
||||
"reset_tooltip": "重置为默认值",
|
||||
"sidebar_description": "设置侧边栏是否显示活跃的小程序",
|
||||
"sidebar_title": "侧边栏活跃小程序显示设置",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "在瀏覽器中開啟新視窗連結"
|
||||
},
|
||||
"region": {
|
||||
"auto": "自動檢測",
|
||||
"cn": "中國",
|
||||
"description": "根據所在地區篩選不支援的小程式",
|
||||
"global": "全球",
|
||||
"title": "小程式區域篩選"
|
||||
},
|
||||
"reset_tooltip": "重設為預設值",
|
||||
"sidebar_description": "設定側邊欄是否顯示活躍的小程式",
|
||||
"sidebar_title": "側邊欄活躍小程式顯示設定",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "Neue Fensterlinks im Browser öffnen"
|
||||
},
|
||||
"region": {
|
||||
"auto": "Automatische Erkennung",
|
||||
"cn": "China",
|
||||
"description": "Nach Region gefilterte Mini-Programme werden nicht unterstützt",
|
||||
"global": "global",
|
||||
"title": "Bereichsfilterung für Mini-Programme"
|
||||
},
|
||||
"reset_tooltip": "Zurücksetzen auf Standardwert",
|
||||
"sidebar_description": "Festlegen ob Seitenleiste aktive Mini-Apps anzeigt",
|
||||
"sidebar_title": "Einstellungen für aktive Mini-Apps in Seitenleiste",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "Άνοιγμα νέου παραθύρου σύνδεσης στον περιηγητή"
|
||||
},
|
||||
"region": {
|
||||
"auto": "Αυτόματη ανίχνευση",
|
||||
"cn": "Κίνα",
|
||||
"description": "Η φιλτράρισμα των μικροπρογραμμάτων που δεν υποστηρίζονται βάσει της περιοχής σας",
|
||||
"global": "παγκοσμίως",
|
||||
"title": "Φίλτραρισμα περιοχής για μικρές εφαρμογές"
|
||||
},
|
||||
"reset_tooltip": "Επαναφορά στις προεπιλεγμένες τιμές",
|
||||
"sidebar_description": "Καθορίστε εάν το ενεργό μικροπρόγραμμα θα εμφανίζεται στην πλευρική γραμμή",
|
||||
"sidebar_title": "Ρυθμίσεις Εμφάνισης Ενεργού Μικροπρογράμματος στην Πλευρική Γραμμή",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "Abrir enlace en nueva ventana del navegador"
|
||||
},
|
||||
"region": {
|
||||
"auto": "Detección automática",
|
||||
"cn": "China",
|
||||
"description": "Los pequeños programas que no son compatibles se filtran según la región.",
|
||||
"global": "global",
|
||||
"title": "Filtro de área de la miniaplicación"
|
||||
},
|
||||
"reset_tooltip": "Restablecer a los valores predeterminados",
|
||||
"sidebar_description": "Configura si se muestra o no en la barra lateral la miniaplicación activa",
|
||||
"sidebar_title": "Visualización de miniaplicaciones activas en la barra lateral",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "Ouvrir un nouveau lien dans une fenêtre du navigateur"
|
||||
},
|
||||
"region": {
|
||||
"auto": "Détection automatique",
|
||||
"cn": "Chine",
|
||||
"description": "La filtration des mini-programmes non pris en charge selon la région n'est pas disponible",
|
||||
"global": "mondial",
|
||||
"title": "Filtrage par zone de la mini-application"
|
||||
},
|
||||
"reset_tooltip": "Réinitialiser aux valeurs par défaut",
|
||||
"sidebar_description": "Définir si les applications actives doivent s'afficher dans la barre latérale",
|
||||
"sidebar_title": "Affichage des applications actives dans la barre latérale",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "新視窗のリンクをブラウザで開く"
|
||||
},
|
||||
"region": {
|
||||
"auto": "自動検出",
|
||||
"cn": "中国",
|
||||
"description": "地域に基づいてサポートされていないミニアプリをフィルタリングします",
|
||||
"global": "世界中",
|
||||
"title": "ミニアプリ地域絞り込み"
|
||||
},
|
||||
"reset_tooltip": "デフォルト値にリセット",
|
||||
"sidebar_description": "サイドバーにアクティブなミニアプリを表示するかどうかを設定します",
|
||||
"sidebar_title": "サイドバーのアクティブなミニアプリ表示",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "Abrir link em nova janela do navegador"
|
||||
},
|
||||
"region": {
|
||||
"auto": "Detecção automática",
|
||||
"cn": "China",
|
||||
"description": "A filtragem de mini programas não suportados com base na região não é suportada",
|
||||
"global": "global",
|
||||
"title": "Filtro de área do mini programa"
|
||||
},
|
||||
"reset_tooltip": "Redefinir para os valores padrão",
|
||||
"sidebar_description": "Defina se os mini aplicativos ativos serão exibidos na barra lateral",
|
||||
"sidebar_title": "Exibição de Mini Aplicativos Ativos na Barra Lateral",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "Deschide în browser linkurile care deschid ferestre noi"
|
||||
},
|
||||
"region": {
|
||||
"auto": "Detectare automată",
|
||||
"cn": "China",
|
||||
"description": "Filtrarea aplicațiilor mini care nu sunt acceptate în funcție de regiunea în care vă aflați",
|
||||
"global": "global",
|
||||
"title": "Filtru zonă aplicație miniatură"
|
||||
},
|
||||
"reset_tooltip": "Resetează la implicit",
|
||||
"sidebar_description": "Afișează mini-aplicațiile active în bara laterală",
|
||||
"sidebar_title": "Afișare mini-aplicații active în bara laterală",
|
||||
|
||||
@@ -4651,6 +4651,13 @@
|
||||
"open_link_external": {
|
||||
"title": "Открывать новые окна в браузере"
|
||||
},
|
||||
"region": {
|
||||
"auto": "Автоматическое определение",
|
||||
"cn": "Китай",
|
||||
"description": "Фильтрация неподдерживаемых мини-программ в зависимости от региона",
|
||||
"global": "Глобально",
|
||||
"title": "Фильтрация по регионам в мини-программе"
|
||||
},
|
||||
"reset_tooltip": "Сбросить до значения по умолчанию",
|
||||
"sidebar_description": "Настройка отображения активных мини-приложений в боковой панели",
|
||||
"sidebar_title": "Отображение активных мини-приложений в боковой панели",
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { UndoOutlined } from '@ant-design/icons' // 导入重置图标
|
||||
import { InfoCircleOutlined, UndoOutlined } from '@ant-design/icons' // 导入重置图标和Info图标
|
||||
import Selector from '@renderer/components/Selector'
|
||||
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'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import type { RootState } from '@renderer/store'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import {
|
||||
setMaxKeepAliveMinapps,
|
||||
setMinAppRegion,
|
||||
setMinappsOpenLinkExternal,
|
||||
setShowOpenedMinappsInSidebar
|
||||
} from '@renderer/store/settings'
|
||||
import { Button, message, Slider, Switch, Tooltip } from 'antd'
|
||||
import type { MinAppRegionFilter } from '@renderer/types'
|
||||
import { Button, Flex, message, Slider, Switch, Tooltip } from 'antd'
|
||||
import type { FC } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -20,6 +24,25 @@ import MiniAppIconsManager from './MiniAppIconsManager'
|
||||
// 默认小程序缓存数量
|
||||
const DEFAULT_MAX_KEEPALIVE = 3
|
||||
|
||||
// Region selector component with defensive default value
|
||||
const RegionSelector: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const dispatch = useAppDispatch()
|
||||
const minAppRegion = useAppSelector((state: RootState) => state.settings.minAppRegion) ?? 'auto'
|
||||
|
||||
const onMinAppRegionChange = (value: MinAppRegionFilter) => {
|
||||
dispatch(setMinAppRegion(value))
|
||||
}
|
||||
|
||||
const minAppRegionOptions: { value: MinAppRegionFilter; label: string }[] = [
|
||||
{ value: 'auto', label: t('settings.miniapps.region.auto') },
|
||||
{ value: 'CN', label: t('settings.miniapps.region.cn') },
|
||||
{ value: 'Global', label: t('settings.miniapps.region.global') }
|
||||
]
|
||||
|
||||
return <Selector size={14} value={minAppRegion} onChange={onMinAppRegionChange} options={minAppRegionOptions} />
|
||||
}
|
||||
|
||||
const MiniAppSettings: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const dispatch = useAppDispatch()
|
||||
@@ -94,6 +117,17 @@ const MiniAppSettings: FC = () => {
|
||||
/>
|
||||
</BorderedContainer>
|
||||
<SettingDivider />
|
||||
{/* 小程序地区设置 */}
|
||||
<SettingRow style={{ height: 40, alignItems: 'center' }}>
|
||||
<Flex align="center" gap={4}>
|
||||
<SettingRowTitle>{t('settings.miniapps.region.title')}</SettingRowTitle>
|
||||
<Tooltip title={t('settings.miniapps.region.description')} placement="right">
|
||||
<InfoCircleOutlined style={{ cursor: 'pointer' }} />
|
||||
</Tooltip>
|
||||
</Flex>
|
||||
<RegionSelector />
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ height: 40, alignItems: 'center' }}>
|
||||
<SettingLabelGroup>
|
||||
<SettingRowTitle>{t('settings.miniapps.open_link_external.title')}</SettingRowTitle>
|
||||
|
||||
@@ -3185,6 +3185,8 @@ const migrateConfig = {
|
||||
assistant.defaultModel = qwen3Next80BModel
|
||||
}
|
||||
})
|
||||
// Initialize mini app region filter setting
|
||||
state.settings.minAppRegion ??= 'auto'
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 194 error', error as Error)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit'
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
import { AppLogo, UserAvatar } from '@renderer/config/env'
|
||||
import type { MinAppType, Topic, WebSearchStatus } from '@renderer/types'
|
||||
import type { MinAppRegion, MinAppType, Topic, WebSearchStatus } from '@renderer/types'
|
||||
import type { UpdateInfo } from 'builder-util-runtime'
|
||||
|
||||
export interface ChatState {
|
||||
@@ -73,6 +73,8 @@ export interface RuntimeState {
|
||||
export: ExportState
|
||||
chat: ChatState
|
||||
websearch: WebSearchState
|
||||
/** Detected region from IP lookup (not persisted, re-detected on each app start) */
|
||||
detectedRegion: MinAppRegion | null
|
||||
}
|
||||
|
||||
export interface ExportState {
|
||||
@@ -115,7 +117,8 @@ const initialState: RuntimeState = {
|
||||
},
|
||||
websearch: {
|
||||
activeSearches: {}
|
||||
}
|
||||
},
|
||||
detectedRegion: null
|
||||
}
|
||||
|
||||
const runtimeSlice = createSlice({
|
||||
@@ -205,6 +208,9 @@ const runtimeSlice = createSlice({
|
||||
setSessionWaitingAction: (state, action: PayloadAction<{ id: string; value: boolean }>) => {
|
||||
const { id, value } = action.payload
|
||||
state.chat.sessionWaiting[id] = value
|
||||
},
|
||||
setDetectedRegion: (state, action: PayloadAction<MinAppRegion | null>) => {
|
||||
state.detectedRegion = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -235,7 +241,9 @@ export const {
|
||||
setSessionWaitingAction,
|
||||
// WebSearch related actions
|
||||
setActiveSearches,
|
||||
setWebSearchStatus
|
||||
setWebSearchStatus,
|
||||
// Region detection
|
||||
setDetectedRegion
|
||||
} = runtimeSlice.actions
|
||||
|
||||
export default runtimeSlice.reducer
|
||||
|
||||
@@ -25,6 +25,7 @@ import type {
|
||||
CodeStyleVarious,
|
||||
LanguageVarious,
|
||||
MathEngine,
|
||||
MinAppRegionFilter,
|
||||
OpenAIServiceTier,
|
||||
PaintingProvider,
|
||||
S3Config,
|
||||
@@ -191,6 +192,8 @@ export interface SettingsState {
|
||||
maxKeepAliveMinapps: number
|
||||
showOpenedMinappsInSidebar: boolean
|
||||
minappsOpenLinkExternal: boolean
|
||||
/** Mini app region filter: 'auto' (detect from IP), 'CN', or 'Global' */
|
||||
minAppRegion: MinAppRegionFilter
|
||||
// 隐私设置
|
||||
enableDataCollection: boolean
|
||||
enableSpellCheck: boolean
|
||||
@@ -376,6 +379,7 @@ export const initialState: SettingsState = {
|
||||
maxKeepAliveMinapps: 3,
|
||||
showOpenedMinappsInSidebar: true,
|
||||
minappsOpenLinkExternal: false,
|
||||
minAppRegion: 'auto',
|
||||
enableDataCollection: false,
|
||||
enableSpellCheck: false,
|
||||
spellCheckLanguages: [],
|
||||
@@ -798,6 +802,9 @@ const settingsSlice = createSlice({
|
||||
setMinappsOpenLinkExternal: (state, action: PayloadAction<boolean>) => {
|
||||
state.minappsOpenLinkExternal = action.payload
|
||||
},
|
||||
setMinAppRegion: (state, action: PayloadAction<MinAppRegionFilter>) => {
|
||||
state.minAppRegion = action.payload
|
||||
},
|
||||
setEnableDataCollection: (state, action: PayloadAction<boolean>) => {
|
||||
state.enableDataCollection = action.payload
|
||||
},
|
||||
@@ -997,6 +1004,7 @@ export const {
|
||||
setMaxKeepAliveMinapps,
|
||||
setShowOpenedMinappsInSidebar,
|
||||
setMinappsOpenLinkExternal,
|
||||
setMinAppRegion,
|
||||
setEnableDataCollection,
|
||||
setEnableSpellCheck,
|
||||
setSpellCheckLanguages,
|
||||
|
||||
@@ -507,8 +507,8 @@ export type MinAppType = {
|
||||
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[]
|
||||
/** Regions where this app is available. If includes 'Global', shown to international users. */
|
||||
supportedRegions?: MinAppRegion[]
|
||||
logo?: string
|
||||
url: string
|
||||
// FIXME: It should be `bordered`
|
||||
@@ -519,6 +519,11 @@ export type MinAppType = {
|
||||
type?: 'Custom' | 'Default' // Added the 'type' property
|
||||
}
|
||||
|
||||
/** Region types for miniapps visibility */
|
||||
export type MinAppRegion = 'CN' | 'Global'
|
||||
|
||||
export type MinAppRegionFilter = 'auto' | MinAppRegion
|
||||
|
||||
export enum ThemeMode {
|
||||
light = 'light',
|
||||
dark = 'dark',
|
||||
|
||||
Reference in New Issue
Block a user