diff --git a/src/renderer/src/components/MinApp/index.tsx b/src/renderer/src/components/MinApp/index.tsx index 285e0c1e..2df40425 100644 --- a/src/renderer/src/components/MinApp/index.tsx +++ b/src/renderer/src/components/MinApp/index.tsx @@ -202,23 +202,40 @@ const EmptyView = styled.div` export default class MinApp { static topviewId = 0 static onClose = () => {} + static isOpening = false + + static async start(app: MinAppType) { + if (this.isOpening) return + this.isOpening = true + + try { + // 先关闭现有的小程序 + await this.close() + + // 确保 webview 完全卸载 + await new Promise(resolve => setTimeout(resolve, 100)) + + store.dispatch(setMinappShow(true)) + return new Promise((resolve) => { + TopView.show( + { + resolve(v) + this.close() + }} + />, + 'MinApp' + ) + }) + } finally { + this.isOpening = false + } + } + static close() { + if (!this.isOpening) return TopView.hide('MinApp') store.dispatch(setMinappShow(false)) } - static start(app: MinAppType) { - store.dispatch(setMinappShow(true)) - return new Promise((resolve) => { - TopView.show( - { - resolve(v) - this.close() - }} - />, - 'MinApp' - ) - }) - } } diff --git a/src/renderer/src/components/app/Sidebar.tsx b/src/renderer/src/components/app/Sidebar.tsx index 418312d3..af47548b 100644 --- a/src/renderer/src/components/app/Sidebar.tsx +++ b/src/renderer/src/components/app/Sidebar.tsx @@ -1,6 +1,7 @@ import { FileSearchOutlined, FolderOutlined, PictureOutlined, TranslationOutlined } from '@ant-design/icons' import { isMac } from '@renderer/config/constant' import { isLocalAi, UserAvatar } from '@renderer/config/env' +import { getAllMinApps } from '@renderer/config/minapps' import { useTheme } from '@renderer/context/ThemeProvider' import useAvatar from '@renderer/hooks/useAvatar' import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' @@ -21,8 +22,9 @@ const Sidebar: FC = () => { const { minappShow } = useRuntime() const { t } = useTranslation() const navigate = useNavigate() - const { windowStyle, sidebarIcons } = useSettings() + const { windowStyle, sidebarIcons, miniAppIcons } = useSettings() const { theme, toggleTheme } = useTheme() + const allApps = getAllMinApps() const isRoute = (path: string): string => (pathname === path ? 'active' : '') const isRoutes = (path: string): string => (pathname.startsWith(path) ? 'active' : '') @@ -72,6 +74,27 @@ const Sidebar: FC = () => { }) } + const renderPinnedApps = () => { + if (!miniAppIcons?.pinned) return null + const pinnedApps = allApps.filter((app) => miniAppIcons.pinned.includes(app.id)) + return pinnedApps.map((app) => ( + + + MinApp.start(app)}> + + + + + )) + } + return ( { }}> - {renderMainMenus()} + + + {renderMainMenus()} + {renderPinnedApps()} + + @@ -129,6 +157,7 @@ const AvatarImg = styled(Avatar)` const MainMenus = styled.div` display: flex; flex: 1; + overflow: hidden; ` const Menus = styled.div` @@ -182,4 +211,31 @@ const StyledLink = styled.div` } ` +const AppIcon = styled.img` + border-radius: 6px; +` + +const ScrollContainer = styled.div` + overflow-y: auto; + overflow-x: hidden; + height: 100%; + + &::-webkit-scrollbar { + width: 0px; + } + + &:hover::-webkit-scrollbar { + width: 4px; + } + + &::-webkit-scrollbar-thumb { + background-color: var(--color-border); + border-radius: 4px; + } + + &::-webkit-scrollbar-track { + background: transparent; + } +` + export default Sidebar diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 48502411..07d00629 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -264,7 +264,9 @@ "error.get_embedding_dimensions": "Failed to get embedding dimensions" }, "minapp": { - "title": "MinApp" + "title": "MinApp", + "sidebar.add.title": "Add minAPP to sidebar", + "sidebar.remove.title": "Remove minAPP from sidebar" }, "ollama": { "keep_alive_time.description": "The time in minutes to keep the connection alive, default is 5 minutes.", @@ -405,6 +407,10 @@ "display.sidebar.disabled": "Hide my sidebar icons", "display.sidebar.chat.hiddenMessage": "Assistants are basic functions, not supported for hiding", "display.sidebar.empty": "Drag the hidden feature from the left side here", + "display.minApp.title": "MinApp Settings", + "display.minApp.visible": "Visible MinApp", + "display.minApp.disabled": "Hidden MinApp", + "display.minApp.empty": "Drag minApp from the left to hide them here", "display.topic.title": "Topic Settings", "display.custom.css": "Custom CSS", "display.custom.css.placeholder": "/* Put custom CSS here */", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 5175d0bc..67d72602 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -262,7 +262,9 @@ "copy.success": "コピーしました!" }, "minapp": { - "title": "ミニアプリ" + "title": "ミニアプリ", + "sidebar.add.title": "ミニプログラムをサイドバーに追加", + "sidebar.remove.title": "サイドバーからアプレットを削除する" }, "ollama": { "keep_alive_time.description": "モデルがメモリに保持される時間(デフォルト:5分)", @@ -406,6 +408,10 @@ "display.topic.title": "トピック設定", "display.custom.css": "カスタムCSS", "display.custom.css.placeholder": "/* ここにカスタムCSSを入力 */", + "display.minApp.title": "ミニプログラム表示設定", + "display.minApp.visible": "表示中ミニプログラム", + "display.minApp.disabled": "非表示ミニプログラム", + "display.minApp.empty": "非表示にしたいアプレットを左からここまでドラッグします", "input.auto_translate_with_space": "スペースを3回押して翻訳", "messages.divider": "メッセージ間に区切り線を表示", "messages.input.paste_long_text_as_file": "長いテキストをファイルとして貼り付け", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index bb9cb74d..ba4c767e 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -264,7 +264,9 @@ "error.get_embedding_dimensions": "Не удалось получить размерность встраивания" }, "minapp": { - "title": "Встроенные приложения" + "title": "Встроенные приложения", + "sidebar.add.title": "Добавить мини-программу на боковую панель", + "sidebar.remove.title": "Удалить апплет из боковой панели" }, "ollama": { "keep_alive_time.description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.", @@ -405,6 +407,10 @@ "display.sidebar.disabled": "Скрыть значок на боковой панели", "display.sidebar.chat.hiddenMessage": "Помощник является базовой функцией и не поддерживает скрытие", "display.sidebar.empty": "Перетащите скрываемую функцию с левой стороны сюда", + "display.minApp.title": "Настройки отображения мини программы", + "display.minApp.visible": "Отображаемый апплет", + "display.minApp.disabled": "скрытый апплет", + "display.minApp.empty": "Перетащите апплет, который хотите скрыть, слева сюда", "display.topic.title": "Настройки топиков", "display.custom.css": "Пользовательский CSS", "display.custom.css.placeholder": "/* Здесь введите пользовательский CSS */", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 936cd06a..95fcf109 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -265,7 +265,9 @@ "error.get_embedding_dimensions": "获取嵌入维度失败" }, "minapp": { - "title": "小程序" + "title": "小程序", + "sidebar.add.title": "添加小程序到侧边栏", + "sidebar.remove.title": "从侧边栏移除小程序" }, "ollama": { "keep_alive_time.description": "对话后模型在内存中保持的时间(默认:5分钟)", @@ -406,6 +408,10 @@ "display.sidebar.disabled": "隐藏我的侧边栏图标", "display.sidebar.chat.hiddenMessage": "助手是基础功能,不支持隐藏", "display.sidebar.empty": "把要隐藏的功能从左侧拖拽到这里", + "display.minApp.title": "小程序显示设置", + "display.minApp.visible": "显示的小程序", + "display.minApp.disabled": "隐藏的小程序", + "display.minApp.empty": "把要隐藏的小程序从左侧拖拽到这里", "display.topic.title": "话题设置", "display.custom.css": "自定义 CSS", "display.custom.css.placeholder": "/* 这里写自定义CSS */", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index b52272c4..22c4735a 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -264,7 +264,9 @@ "error.get_embedding_dimensions": "獲取嵌入維度失敗" }, "minapp": { - "title": "小程序" + "title": "小程序", + "sidebar.add.title": "新增小程式到側邊欄", + "sidebar.remove.title": "從側邊欄移除小程式" }, "ollama": { "keep_alive_time.description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)。", @@ -409,6 +411,14 @@ "display.custom.css": "自定義 CSS", "display.custom.css.placeholder": "/* 這裡寫自定義 CSS */", "input.auto_translate_with_space": "快速敲擊3次空格翻譯", + "display": { + "minApp": { + "title": "小程序顯示設定", + "visible": "顯示的小程序", + "disabled": "隱藏的小程序", + "empty": "把要隱藏的小程序從左側拖拽到這裡" + } + }, "messages.divider": "訊息間顯示分隔線", "messages.input.paste_long_text_as_file": "將長文本貼上為檔案", "messages.input.send_shortcuts": "發送快捷鍵", diff --git a/src/renderer/src/pages/apps/App.tsx b/src/renderer/src/pages/apps/App.tsx index cd939886..532bb9b6 100644 --- a/src/renderer/src/pages/apps/App.tsx +++ b/src/renderer/src/pages/apps/App.tsx @@ -1,8 +1,14 @@ import MinApp from '@renderer/components/MinApp' +import { useAppDispatch, useAppSelector } from '@renderer/store' +import { setMiniAppIcons } from '@renderer/store/settings' import { MinAppType } from '@renderer/types' +import type { MenuProps } from 'antd' +import { Dropdown } from 'antd' import { FC } from 'react' +import { useTranslation } from 'react-i18next' import styled from 'styled-components' + interface Props { app: MinAppType onClick?: () => void @@ -10,23 +16,49 @@ interface Props { } const App: FC = ({ app, onClick, size = 60 }) => { + const { t } = useTranslation() + const dispatch = useAppDispatch() + const { miniAppIcons } = useAppSelector((state) => state.settings) + const isPinned = miniAppIcons?.pinned.includes(app.id) + const handleClick = () => { MinApp.start(app) onClick?.() } + const menuItems: MenuProps['items'] = [ + { + key: 'togglePin', + label: isPinned ? t('minapp.sidebar.remove.title') : t('minapp.sidebar.add.title'), + onClick: () => { + const newPinned = isPinned + ? miniAppIcons.pinned.filter((id) => id !== app.id) + : [...(miniAppIcons.pinned || []), app.id] + + dispatch( + setMiniAppIcons({ + ...miniAppIcons, + pinned: newPinned + }) + ) + } + } + ] + return ( - - - {app.name} - + + + + {app.name} + + ) } diff --git a/src/renderer/src/pages/apps/AppsPage.tsx b/src/renderer/src/pages/apps/AppsPage.tsx index 4145310c..865e452a 100644 --- a/src/renderer/src/pages/apps/AppsPage.tsx +++ b/src/renderer/src/pages/apps/AppsPage.tsx @@ -2,9 +2,10 @@ import { SearchOutlined } from '@ant-design/icons' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { Center } from '@renderer/components/Layout' import { getAllMinApps } from '@renderer/config/minapps' +import { useSettings } from '@renderer/hooks/useSettings' import { Empty, Input } from 'antd' import { isEmpty } from 'lodash' -import { FC, useMemo, useState } from 'react' +import React, { FC, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -13,16 +14,34 @@ import App from './App' const AppsPage: FC = () => { const { t } = useTranslation() const [search, setSearch] = useState('') - const apps = useMemo(() => getAllMinApps(), []) + const { miniAppIcons } = useSettings() + const allApps = useMemo(() => getAllMinApps(), []) + + // 只显示可见的小程序 + const visibleApps = useMemo(() => { + if (!miniAppIcons?.visible) return allApps + return allApps.filter((app) => miniAppIcons.visible.includes(app.id)) + }, [allApps, miniAppIcons?.visible]) const filteredApps = search - ? apps.filter( + ? visibleApps.filter( (app) => app.name.toLowerCase().includes(search.toLowerCase()) || app.url.includes(search.toLowerCase()) ) - : apps + : visibleApps + + // Calculate the required number of lines + const itemsPerRow = Math.floor(930 / 115) // Maximum width divided by the width of each item (including spacing) + const rowCount = Math.ceil(filteredApps.length / itemsPerRow) + // Each line height is 85px (60px icon + 5px margin + 12px text + spacing) + const containerHeight = rowCount * 85 + (rowCount - 1) * 25 // 25px is the line spacing. + + // Disable right-click menu in blank area + const handleContextMenu = (e: React.MouseEvent) => { + e.preventDefault() + } return ( - + {t('minapp.title')} @@ -40,7 +59,7 @@ const AppsPage: FC = () => { - + {filteredApps.map((app) => ( ))} @@ -68,7 +87,7 @@ const ContentContainer = styled.div` flex-direction: row; justify-content: center; height: 100%; - overflow-y: scroll; + overflow-y: auto; padding: 50px; ` @@ -77,9 +96,7 @@ const AppsContainer = styled.div` min-width: 0; max-width: 930px; width: 100%; - max-height: 520px; - min-height: 520px; - grid-template-columns: repeat(auto-fill, minmax(90px, 1fr)); + grid-template-columns: repeat(auto-fill, 90px); gap: 25px; justify-content: center; ` diff --git a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx index 265a482a..6c6709e0 100644 --- a/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx +++ b/src/renderer/src/pages/settings/DisplaySettings/DisplaySettings.tsx @@ -3,9 +3,11 @@ import { useTheme } from '@renderer/context/ThemeProvider' import { useSettings } from '@renderer/hooks/useSettings' import { useAppDispatch } from '@renderer/store' import { + DEFAULT_MINIAPP_ICONS, DEFAULT_SIDEBAR_ICONS, setClickAssistantToShowTopic, setCustomCss, + setMiniAppIcons, setShowTopicTime, setSidebarIcons } from '@renderer/store/settings' @@ -16,6 +18,7 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +import MiniAppIconsManager from './MiniAppIconsManager' import SidebarIconsManager from './SidebarIconsManager' const DisplaySettings: FC = () => { @@ -29,7 +32,8 @@ const DisplaySettings: FC = () => { clickAssistantToShowTopic, showTopicTime, customCss, - sidebarIcons + sidebarIcons, + miniAppIcons } = useSettings() const { theme: themeMode } = useTheme() const { t } = useTranslation() @@ -37,6 +41,8 @@ const DisplaySettings: FC = () => { const [visibleIcons, setVisibleIcons] = useState(sidebarIcons?.visible || DEFAULT_SIDEBAR_ICONS) const [disabledIcons, setDisabledIcons] = useState(sidebarIcons?.disabled || []) + const [visibleMiniApps, setVisibleMiniApps] = useState(miniAppIcons?.visible || DEFAULT_MINIAPP_ICONS) + const [disabledMiniApps, setDisabledMiniApps] = useState(miniAppIcons?.disabled || []) // 使用useCallback优化回调函数 const handleWindowStyleChange = useCallback( @@ -52,6 +58,18 @@ const DisplaySettings: FC = () => { dispatch(setSidebarIcons({ visible: DEFAULT_SIDEBAR_ICONS, disabled: [] })) }, [dispatch]) + const handleResetMinApps = useCallback(() => { + setVisibleMiniApps(DEFAULT_MINIAPP_ICONS) + setDisabledMiniApps([]) + dispatch( + setMiniAppIcons({ + visible: DEFAULT_MINIAPP_ICONS, + disabled: [], + pinned: miniAppIcons?.pinned || [] + }) + ) + }, [dispatch, miniAppIcons?.pinned]) + return ( @@ -142,6 +160,22 @@ const DisplaySettings: FC = () => { }} /> + + + {t('settings.display.minApp.title')} + + + + + + + ) } diff --git a/src/renderer/src/pages/settings/DisplaySettings/MiniAppIconsManager.tsx b/src/renderer/src/pages/settings/DisplaySettings/MiniAppIconsManager.tsx new file mode 100644 index 00000000..48e3572c --- /dev/null +++ b/src/renderer/src/pages/settings/DisplaySettings/MiniAppIconsManager.tsx @@ -0,0 +1,256 @@ +import { CloseOutlined } from '@ant-design/icons' +import { + DragDropContext, + Draggable, + DraggableProvided, + Droppable, + DroppableProvided, + DropResult +} from '@hello-pangea/dnd' +import { getAllMinApps } from '@renderer/config/minapps' +import { useAppDispatch } from '@renderer/store' +import { FC, useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' + +import { MinAppIcon, setMiniAppIcons } from '../../../store/settings' + +interface MiniAppManagerProps { + visibleMiniApps: MinAppIcon[] + disabledMiniApps: MinAppIcon[] + setVisibleMiniApps: (programs: MinAppIcon[]) => void + setDisabledMiniApps: (programs: MinAppIcon[]) => void +} + +// 将可复用的类型和常量提取出来 +type ListType = 'visible' | 'disabled' +interface AppInfo { + name: string + logo?: string +} + +const MiniAppIconsManager: FC = ({ + visibleMiniApps, + disabledMiniApps, + setVisibleMiniApps, + setDisabledMiniApps +}) => { + const { t } = useTranslation() + const dispatch = useAppDispatch() + const allApps = useMemo(() => getAllMinApps(), []) + + // 创建 app 信息的 Map 缓存 + const appInfoMap = useMemo(() => { + return allApps.reduce( + (acc, app) => { + acc[String(app.id)] = { name: app.name, logo: app.logo } + return acc + }, + {} as Record + ) + }, [allApps]) + + const getAppInfo = useCallback( + (id: MinAppIcon) => { + return appInfoMap[String(id)] || { name: id, logo: '' } + }, + [appInfoMap] + ) + + const handleListUpdate = useCallback( + (visible: MinAppIcon[], disabled: MinAppIcon[]) => { + setVisibleMiniApps(visible) + setDisabledMiniApps(disabled) + dispatch(setMiniAppIcons({ visible, disabled, pinned: [] })) + }, + [dispatch, setVisibleMiniApps, setDisabledMiniApps] + ) + + const onDragEnd = useCallback( + (result: DropResult) => { + const { source, destination } = result + if (!destination) return + + const sourceList = source.droppableId === 'visible' ? visibleMiniApps : disabledMiniApps + const destList = destination.droppableId === 'visible' ? visibleMiniApps : disabledMiniApps + + if (source.droppableId === destination.droppableId) { + const newList = [...sourceList] + const [removed] = newList.splice(source.index, 1) + newList.splice(destination.index, 0, removed) + + handleListUpdate( + source.droppableId === 'visible' ? newList : visibleMiniApps, + source.droppableId === 'disabled' ? newList : disabledMiniApps + ) + } else { + const sourceNewList = [...sourceList] + const [removed] = sourceNewList.splice(source.index, 1) + const destNewList = [...destList] + destNewList.splice(destination.index, 0, removed) + + handleListUpdate( + destination.droppableId === 'visible' ? destNewList : sourceNewList, + destination.droppableId === 'disabled' ? destNewList : sourceNewList + ) + } + }, + [visibleMiniApps, disabledMiniApps, handleListUpdate] + ) + + const onMoveMiniApp = useCallback( + (program: MinAppIcon, fromList: ListType) => { + const isMovingToVisible = fromList === 'disabled' + const newVisible = isMovingToVisible + ? [...visibleMiniApps, program] + : visibleMiniApps.filter((p) => p !== program) + const newDisabled = isMovingToVisible + ? disabledMiniApps.filter((p) => p !== program) + : [...disabledMiniApps, program] + + handleListUpdate(newVisible, newDisabled) + }, + [visibleMiniApps, disabledMiniApps, handleListUpdate] + ) + + const renderProgramItem = (program: MinAppIcon, provided: DraggableProvided, listType: ListType) => { + const { name, logo } = getAppInfo(program) + + return ( + + + + {name} + + onMoveMiniApp(program, listType)}> + + + + ) + } + + return ( + + + {(['visible', 'disabled'] as const).map((listType) => ( + +

{t(`settings.display.minApp.${listType}`)}

+ + {(provided: DroppableProvided) => ( + + + {(listType === 'visible' ? visibleMiniApps : disabledMiniApps).map((program, index) => ( + + {(provided: DraggableProvided) => renderProgramItem(program, provided, listType)} + + ))} + {disabledMiniApps.length === 0 && listType === 'disabled' && ( + {t('settings.display.minApp.empty')} + )} + {provided.placeholder} + + + )} + +
+ ))} +
+
+ ) +} + +const AppLogo = styled.img` + width: 16px; + height: 16px; + border-radius: 4px; + object-fit: contain; +` + +const ScrollContainer = styled.div` + overflow-y: auto; + height: 100%; +` + +const ProgramSection = styled.div` + display: flex; + gap: 20px; + padding: 10px; + background: var(--color-background); +` + +const ProgramColumn = styled.div` + flex: 1; + + h4 { + margin-bottom: 10px; + color: var(--color-text); + font-weight: normal; + } +` + +const ProgramList = styled.div` + height: 365px; + min-height: 365px; + padding: 10px; + background: var(--color-background-soft); + border-radius: 8px; + border: 1px solid var(--color-border); + display: flex; + flex-direction: column; + overflow-y: hidden; +` + +const ProgramItem = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + margin-bottom: 8px; + background: var(--color-background); + border: 1px solid var(--color-border); + border-radius: 4px; + cursor: move; +` + +const ProgramContent = styled.div` + display: flex; + align-items: center; + gap: 10px; + + .iconfont { + font-size: 16px; + color: var(--color-text); + } + + span { + color: var(--color-text); + } +` + +const CloseButton = styled.div` + cursor: pointer; + color: var(--color-text-2); + opacity: 0; + transition: all 0.2s; + + &:hover { + color: var(--color-text); + } + + ${ProgramItem}:hover & { + opacity: 1; + } +` + +const EmptyPlaceholder = styled.div` + display: flex; + flex: 1; + align-items: center; + justify-content: center; + color: var(--color-text-2); + text-align: center; + padding: 20px; + font-size: 14px; +` + +export default MiniAppIconsManager diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 8f4d406a..53de8a93 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -9,7 +9,7 @@ import { isEmpty } from 'lodash' import { createMigrate } from 'redux-persist' import { RootState } from '.' -import { DEFAULT_SIDEBAR_ICONS } from './settings' +import { DEFAULT_MINIAPP_ICONS, DEFAULT_SIDEBAR_ICONS } from './settings' const migrateConfig = { '2': (state: RootState) => { @@ -789,6 +789,11 @@ const migrateConfig = { visible: DEFAULT_SIDEBAR_ICONS, disabled: [] } + state.settings.miniAppIcons = { + visible: DEFAULT_MINIAPP_ICONS, + disabled: [], + pinned: [] + } return state } } diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 3009e20a..6d124f3a 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -1,4 +1,5 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { getAllMinApps } from '@renderer/config/minapps' import { TRANSLATE_PROMPT } from '@renderer/config/prompts' import { CodeStyleVarious, LanguageVarious, ThemeMode } from '@renderer/types' @@ -15,6 +16,11 @@ export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [ 'knowledge', 'files' ] +const [minApps] = await Promise.all([getAllMinApps()]) + +export type MinAppIcon = (typeof minApps)[number]['id'] // 假设每个小程序对象有 type 字段 + +export const DEFAULT_MINIAPP_ICONS: MinAppIcon[] = minApps.map((app) => app.id) export interface SettingsState { showAssistants: boolean @@ -61,6 +67,11 @@ export interface SettingsState { disabled: SidebarIcon[] } narrowMode: boolean + miniAppIcons: { + visible: MinAppIcon[] + disabled: MinAppIcon[] + pinned: MinAppIcon[] + } } const initialState: SettingsState = { @@ -105,7 +116,12 @@ const initialState: SettingsState = { visible: DEFAULT_SIDEBAR_ICONS, disabled: [] }, - narrowMode: false + narrowMode: false, + miniAppIcons: { + visible: DEFAULT_MINIAPP_ICONS, + disabled: [], + pinned: [] + } } const settingsSlice = createSlice({ @@ -235,6 +251,12 @@ const settingsSlice = createSlice({ }, setNarrowMode: (state, action: PayloadAction) => { state.narrowMode = action.payload + }, + setMiniAppIcons: ( + state, + action: PayloadAction<{ visible: MinAppIcon[]; disabled: MinAppIcon[]; pinned: MinAppIcon[] }> + ) => { + state.miniAppIcons = action.payload } } }) @@ -280,7 +302,8 @@ export const { setCustomCss, setTopicNamingPrompt, setSidebarIcons, - setNarrowMode + setNarrowMode, + setMiniAppIcons } = settingsSlice.actions export default settingsSlice.reducer