From 8fd36e3e0fe43bc525108575cc8e9a1e8dcca004 Mon Sep 17 00:00:00 2001 From: Hu Gang <18768366022@163.com> Date: Tue, 8 Apr 2025 17:11:10 +0800 Subject: [PATCH] feat: load "userData" configuration file --- electron/main/common/conf.ts | 33 ++++ electron/main/common/nls.ts | 7 + electron/main/common/platform.ts | 7 + electron/main/common/product.ts | 7 + electron/main/index.ts | 79 +++++---- electron/main/node/userDataPath.ts | 7 + electron/main/window/create.ts | 54 +++++- electron/main/window/index.ts | 7 + electron/main/window/options.ts | 8 +- electron/main/window/tray.ts | 14 +- electron/preload/index.ts | 7 + scripts/rollup.config.ts | 46 ++--- src/assets/styles/main.scss | 4 + src/assets/styles/theme.scss | 2 + .../dialoguePanel/DialoguePanel.vue | 2 +- src/i18n/index.ts | 12 +- src/qiankun.ts | 4 +- src/store/conversation.ts | 43 +++-- src/store/lang.ts | 25 ++- src/utils/electron.ts | 13 +- src/views/chat/index.vue | 13 +- .../dialogue/components/DialogueSession.vue | 1 - .../dialogue/components/ReportPopover.vue | 2 +- src/views/dialogue/components/TitleBar.vue | 14 +- src/views/dialogue/dialogueView.vue | 164 ------------------ 25 files changed, 297 insertions(+), 278 deletions(-) create mode 100644 electron/main/common/conf.ts diff --git a/electron/main/common/conf.ts b/electron/main/common/conf.ts new file mode 100644 index 0000000..0fb68b2 --- /dev/null +++ b/electron/main/common/conf.ts @@ -0,0 +1,33 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. +import path from 'path'; +import fs from 'node:fs'; +import { getUserDataPath } from '../node/userDataPath'; +import { productObj } from './product'; + +interface ICacheConf { + theme: 'system' | 'light' | 'dark'; + userLocale; +} + +export const userDataPath = getUserDataPath(productObj.name); +export const cachePath = getCachePath(); +export const commonCacheConfPath = path.join( + cachePath, + 'eulercopilot-common-storage.json', +); + +export function getCachePath(): string { + return path.join(userDataPath, 'CachedData'); +} + +export function updateConf(conf: Partial) { + const oldConf = fs.readFileSync(commonCacheConfPath, 'utf-8'); + const updateConf = { ...JSON.parse(oldConf), ...conf }; + fs.writeFileSync(commonCacheConfPath, JSON.stringify(updateConf)); +} diff --git a/electron/main/common/nls.ts b/electron/main/common/nls.ts index 2397426..0c3e1c5 100644 --- a/electron/main/common/nls.ts +++ b/electron/main/common/nls.ts @@ -1,3 +1,10 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. export interface INLSConfiguration { /** * Locale as defined in `argv.json` or `app.getLocale()`. diff --git a/electron/main/common/platform.ts b/electron/main/common/platform.ts index 2053843..c339dd8 100644 --- a/electron/main/common/platform.ts +++ b/electron/main/common/platform.ts @@ -1,3 +1,10 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. import * as nls from './nls'; export const LANGUAGE_DEFAULT = 'en'; diff --git a/electron/main/common/product.ts b/electron/main/common/product.ts index b8c1974..c46d65d 100644 --- a/electron/main/common/product.ts +++ b/electron/main/common/product.ts @@ -1,3 +1,10 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. export interface IProductConfiguration { readonly name: string; } diff --git a/electron/main/index.ts b/electron/main/index.ts index 8ec327a..e0ffe97 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1,4 +1,10 @@ -import path from 'node:path'; +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. import fs from 'node:fs'; import { app, @@ -8,19 +14,16 @@ import { BrowserWindow, } from 'electron'; import { createDefaultWindow, createTray, createChatWindow } from './window'; -import { getUserDataPath } from './node/userDataPath'; import type { INLSConfiguration } from './common/nls'; -import { productObj } from './common/product'; +import { cachePath, commonCacheConfPath, updateConf } from './common/conf'; interface ICacheConf { userLocale: string; theme: 'system' | 'light' | 'dark'; } -const userDataPath = getUserDataPath(productObj.name); -const cachePath = getCachePath(); - -const commonCacheConf: Partial = getUserDefinedConf(userDataPath); +const commonCacheConf: Partial = + getUserDefinedConf(commonCacheConfPath); const osLocale = processZhLocale( (app.getPreferredSystemLanguages()?.[0] ?? 'en').toLowerCase(), @@ -36,21 +39,25 @@ app.on('will-quit', () => { app.on('window-all-closed', () => { ipcMain.removeAllListeners(); + if (process.platform !== 'darwin') { + app.quit(); + } }); async function onReady() { try { - const [, nlsConfig] = await Promise.all([ + const [, nlsConfig, themeConfig] = await Promise.all([ mkdirpIgnoreError(cachePath), resolveNlsConfiguration(), + resolveThemeConfiguration(), ]); - nativeTheme.themeSource = commonCacheConf.theme || 'light'; - process.env['EULERCOPILOT_THEME'] = commonCacheConf.theme || 'light'; + nativeTheme.themeSource = themeConfig.theme || 'light'; + process.env['EULERCOPILOT_THEME'] = themeConfig.theme || 'light'; process.env['EULERCOPILOT_NLS_CONFIG'] = JSON.stringify(nlsConfig); process.env['EULERCOPILOT_CACHE_PATH'] = cachePath || ''; - startup(); + await startup(); } catch (error) {} } @@ -71,20 +78,6 @@ async function startup() { } }); - ipcMain.handle('copilot:theme', (e, args) => { - chatWindow.setTitleBarOverlay({ - color: args.backgroundColor, - height: 40, - symbolColor: args.theme === 'dark' ? 'white' : 'black', - }); - - win.setTitleBarOverlay({ - color: args.backgroundColor, - height: 48, - symbolColor: args.theme === 'dark' ? 'white' : 'black', - }); - }); - tray.on('click', () => { win.show(); }); @@ -116,10 +109,10 @@ function registerIpcListener() { function getUserDefinedConf(dir: string) { try { - return fs.readFileSync( - path.join(dir, 'eulercopilot-common-storage.json'), - 'utf-8', - ); + if (!fs.existsSync(dir)) { + fs.writeFileSync(dir, JSON.stringify({})); + } + return JSON.parse(fs.readFileSync(dir, 'utf-8')); } catch (error) { // Ignore error return {}; @@ -140,15 +133,28 @@ function processZhLocale(appLocale: string): string { // country codes, assume they use Simplified Chinese. // For other cases, assume they use Traditional. if (['hans', 'cn', 'sg', 'my'].includes(region)) { - return 'zh-cn'; + return 'zh_cn'; } - return 'zh-tw'; + return 'zh_tw'; } return appLocale; } +async function resolveThemeConfiguration(): Promise { + if (commonCacheConf.theme) { + return { + theme: commonCacheConf.theme, + }; + } + const isDarkMode = nativeTheme.shouldUseDarkColors; + updateConf({ theme: isDarkMode ? 'dark' : 'light' }); + return { + theme: isDarkMode ? 'dark' : 'light', + }; +} + /** * 国际化支持 * @returns @@ -163,7 +169,9 @@ async function resolveNlsConfiguration(): Promise { } let userLocale = app.getLocale(); + if (!userLocale) { + updateConf({ userLocale: 'en' }); return { userLocale: 'en', osLocale, @@ -172,7 +180,7 @@ async function resolveNlsConfiguration(): Promise { } userLocale = processZhLocale(userLocale.toLowerCase()); - + updateConf({ userLocale: 'en' }); return { userLocale, osLocale, @@ -185,6 +193,9 @@ async function mkdirpIgnoreError( ): Promise { if (typeof dir === 'string') { try { + if (fs.existsSync(dir)) { + return dir; + } await fs.promises.mkdir(dir, { recursive: true }); return dir; @@ -195,7 +206,3 @@ async function mkdirpIgnoreError( return undefined; } - -function getCachePath(): string | undefined { - return path.join(userDataPath, 'CachedData'); -} diff --git a/electron/main/node/userDataPath.ts b/electron/main/node/userDataPath.ts index 317ec9f..fdfe261 100644 --- a/electron/main/node/userDataPath.ts +++ b/electron/main/node/userDataPath.ts @@ -1,3 +1,10 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. import * as os from 'os'; import * as path from 'node:path'; diff --git a/electron/main/window/create.ts b/electron/main/window/create.ts index 11e8ab4..12faa41 100644 --- a/electron/main/window/create.ts +++ b/electron/main/window/create.ts @@ -1,11 +1,15 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. import path from 'node:path'; import * as electron from 'electron'; -import { BrowserWindow, app, globalShortcut } from 'electron'; +import { BrowserWindow, app, globalShortcut, ipcMain } from 'electron'; import { options as allWindow } from './options'; - -class BaseWindow { - constructor() {} -} +import { updateConf } from '../common/conf'; export function createWindow( options: Electron.BrowserWindowConstructorOptions, @@ -23,7 +27,6 @@ export function createWindow( if (app.isPackaged) { win.loadFile(path.join(__dirname, `../index.html`), { hash }); } else { - // win.webContents.openDevTools(); win.loadURL(`http://localhost:${process.env.PORT}/#${hash}`); } @@ -31,13 +34,13 @@ export function createWindow( } let defaultWindow: BrowserWindow | null = null; -const theme = process.env.EULERCOPILOT_THEME || 'light'; export function createDefaultWindow(): BrowserWindow { if (defaultWindow) return defaultWindow; const hash = allWindow.mainWindow.hash; const defaultWindowOptions = allWindow.mainWindow.window; + const theme = process.env.EULERCOPILOT_THEME || 'light'; defaultWindowOptions.titleBarOverlay = { color: theme === 'dark' ? '#1f2329' : '#ffffff', @@ -50,22 +53,55 @@ export function createDefaultWindow(): BrowserWindow { return defaultWindow; } +let chatWindow: BrowserWindow | null = null; export function createChatWindow(): BrowserWindow { + if (chatWindow) return chatWindow; const hash = allWindow.chatWindow.hash; const chatWindowOptions = allWindow.chatWindow.window; + const theme = process.env.EULERCOPILOT_THEME || 'light'; + chatWindowOptions.titleBarOverlay = { color: theme === 'dark' ? '#1f2329' : '#ffffff', height: 40, symbolColor: theme === 'dark' ? 'white' : 'black', }; - const chatWindow = createWindow(chatWindowOptions, hash); + chatWindow = createWindow(chatWindowOptions, hash); const shortcutKey = process.platform === 'darwin' ? 'Cmd+Option+O' : 'Ctrl+Alt+O'; globalShortcut.register(shortcutKey, () => { - chatWindow.show(); + chatWindow && chatWindow.show(); }); return chatWindow; } + +ipcMain.handle('copilot:theme', (e, args) => { + electron.nativeTheme.themeSource = args.theme; + if (chatWindow) { + chatWindow.setTitleBarOverlay({ + color: args.backgroundColor, + height: 40, + symbolColor: args.theme === 'dark' ? 'white' : 'black', + }); + } + + if (defaultWindow) { + defaultWindow.setTitleBarOverlay({ + color: args.backgroundColor, + height: 48, + symbolColor: args.theme === 'dark' ? 'white' : 'black', + }); + } + + updateConf({ + theme: args.theme, + }); +}); + +ipcMain.handle('copilot:lang', (e, args) => { + updateConf({ + userLocale: args.lang, + }); +}); diff --git a/electron/main/window/index.ts b/electron/main/window/index.ts index dcc0f49..92fceac 100644 --- a/electron/main/window/index.ts +++ b/electron/main/window/index.ts @@ -1,2 +1,9 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. export { createWindow, createDefaultWindow, createChatWindow } from './create'; export { createTray } from './tray'; diff --git a/electron/main/window/options.ts b/electron/main/window/options.ts index 693c5a7..7413e46 100644 --- a/electron/main/window/options.ts +++ b/electron/main/window/options.ts @@ -1,3 +1,10 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. export interface allWindowType { [propName: string]: { id: string; @@ -33,7 +40,6 @@ export const options: allWindowType = { alwaysOnTop: false, useContentSize: true, minWidth: 680, - minHeight: 960, titleBarStyle: 'hidden', icon: 'dist/favicon.ico', }, diff --git a/electron/main/window/tray.ts b/electron/main/window/tray.ts index 6c75630..7a24800 100644 --- a/electron/main/window/tray.ts +++ b/electron/main/window/tray.ts @@ -1,3 +1,10 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. import path from 'node:path'; import { app, Tray, Menu } from 'electron'; import type { MenuItemConstructorOptions } from 'electron'; @@ -15,10 +22,11 @@ export function createTray(): Tray { }, }, ]; - - const iconPath = path.join(__dirname, '../favicon.ico'); + const iconPath = + process.platform === 'darwin' + ? path.join(__dirname, '../favicon.ico') + : path.join(__dirname, '../app_favicon.ico'); appTray = new Tray(iconPath); - const contextMenu = Menu.buildFromTemplate(trayMenus); appTray.setToolTip('EulerCopilot'); diff --git a/electron/preload/index.ts b/electron/preload/index.ts index baa1dfd..a554569 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -1,3 +1,10 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. import { ipcRenderer, contextBridge } from 'electron'; function validateIPC(channel: string): true | never { diff --git a/scripts/rollup.config.ts b/scripts/rollup.config.ts index 813e953..b688d5f 100644 --- a/scripts/rollup.config.ts +++ b/scripts/rollup.config.ts @@ -1,23 +1,23 @@ -import path from 'path' -import type { RollupOptions } from 'rollup' -import copy from 'rollup-plugin-copy' -import nodeResolve from '@rollup/plugin-node-resolve' -import typescript from '@rollup/plugin-typescript' -import commonjs from '@rollup/plugin-commonjs' -import replace from '@rollup/plugin-replace' -import alias from '@rollup/plugin-alias' -import json from '@rollup/plugin-json' -import { builtins, getEnv } from './utils' +import path from 'path'; +import type { RollupOptions } from 'rollup'; +import copy from 'rollup-plugin-copy'; +import nodeResolve from '@rollup/plugin-node-resolve'; +import typescript from '@rollup/plugin-typescript'; +import commonjs from '@rollup/plugin-commonjs'; +import replace from '@rollup/plugin-replace'; +import alias from '@rollup/plugin-alias'; +import json from '@rollup/plugin-json'; +import { builtins, getEnv } from './utils'; export interface ConfigOptions { - env?: typeof process.env.NODE_ENV - proc: 'main' | 'render' | 'preload' + env?: typeof process.env.NODE_ENV; + proc: 'main' | 'render' | 'preload'; } -const compilationInclude = ['electron/**/*.ts'] +const compilationInclude = ['electron/**/*.ts']; export default function (opts: ConfigOptions) { - const sourcemap = opts.proc === 'render' + const sourcemap = opts.proc === 'render'; const options: RollupOptions = { input: path.join(__dirname, `../electron/${opts.proc}/index.ts`), output: { @@ -30,13 +30,13 @@ export default function (opts: ConfigOptions) { extensions: ['.ts', '.js', 'json'], }), commonjs({ - include: compilationInclude + include: compilationInclude, }), json(), typescript({ sourceMap: sourcemap, noEmitOnError: true, - include: compilationInclude + include: compilationInclude, }), alias({ entries: { @@ -45,11 +45,15 @@ export default function (opts: ConfigOptions) { }), copy({ // 复制 favicon.ico 到指定目录 - targets: [{ src: 'app_favicon.ico', dest: 'dist' }], + targets: [ + { src: 'public/app_favicon.ico', dest: 'dist' }, + { src: 'public/favicon.ico', dest: 'dist' }, + ], }), replace({ ...Object.entries({ ...getEnv(), NODE_ENV: opts.env }).reduce( - (acc, [k, v]) => Object.assign(acc, { [`process.env.${k}`]: JSON.stringify(v) }), + (acc, [k, v]) => + Object.assign(acc, { [`process.env.${k}`]: JSON.stringify(v) }), {}, ), preventAssignment: true, @@ -59,10 +63,10 @@ export default function (opts: ConfigOptions) { onwarn: (warning) => { // https://github.com/rollup/rollup/issues/1089#issuecomment-365395213 if (warning.code !== 'CIRCULAR_DEPENDENCY') { - console.error(`(!) ${warning.message}`) + console.error(`(!) ${warning.message}`); } }, - } + }; - return options + return options; } diff --git a/src/assets/styles/main.scss b/src/assets/styles/main.scss index 30b5ed1..8b4c0fe 100644 --- a/src/assets/styles/main.scss +++ b/src/assets/styles/main.scss @@ -5,4 +5,8 @@ // 全局重写tooltip样式 .el-popper { max-width: 376px !important; +} +body { + will-change: transform; + transition: transform 0.3s ease; } \ No newline at end of file diff --git a/src/assets/styles/theme.scss b/src/assets/styles/theme.scss index c1b484c..799437c 100644 --- a/src/assets/styles/theme.scss +++ b/src/assets/styles/theme.scss @@ -1,4 +1,5 @@ body[theme='dark'] { + background-color: #000000; --o-bg-image: url('../../assets/images/dark_background.png'); --o-shell-image: url('../../assets/images/dark_shell.png'); --o-sider: url('../../assets/images/dark_sider.png'); @@ -76,6 +77,7 @@ body[theme='dark'] { } body[theme='light'] { + background-color: #ffffff; --o-bg-image: url('../../assets/images/light_background.png'); --o-shell-image: url('../../assets/images/light_shell.png'); --o-sider: url('../../assets/images/light_sider.png'); diff --git a/src/components/dialoguePanel/DialoguePanel.vue b/src/components/dialoguePanel/DialoguePanel.vue index 73a1f5b..4cc9785 100644 --- a/src/components/dialoguePanel/DialoguePanel.vue +++ b/src/components/dialoguePanel/DialoguePanel.vue @@ -399,7 +399,7 @@ const selectQuestion = (item: Suggest) => { }; const popperSize = () => { - if (language.value == 'EN') { + if (language.value == 'en') { size.width = 418; size.height = 496; return size; diff --git a/src/i18n/index.ts b/src/i18n/index.ts index 766c6bf..51a29f8 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,14 +1,16 @@ import { createI18n } from 'vue-i18n'; // 语言包 -import CN from './lang/zh-cn'; -import EN from './lang/en'; +import zh_cn from './lang/zh-cn'; +import en from './lang/en'; + +const locale = localStorage.getItem('localeLang') || 'zh_cn'; const i18n = createI18n({ legacy: false, // 设置为 false,启用 composition API 模式 - locale: localStorage.getItem('localeLang') || 'CN', + locale, messages: { - CN, - EN, + zh_cn, + en, }, }); export default i18n; diff --git a/src/qiankun.ts b/src/qiankun.ts index f956695..8d13fd8 100644 --- a/src/qiankun.ts +++ b/src/qiankun.ts @@ -4,7 +4,7 @@ import { useAccountStore } from 'src/store'; export function qiankunMounted(props: QiankunProps) { const { theme, lang } = props.inittailData; - const language = lang === 'en' ? 'EN' : 'CN'; + const language = lang; localStorage.setItem('localeLang', language); localStorage.setItem('theme', theme); i18n.global.locale.value = language; @@ -20,7 +20,7 @@ function onQiankunGlobalChange(globalState: any) { } if (globalState.lang) { - const language = globalState.lang === 'en' ? 'EN' : 'CN'; + const language = globalState.lang; localStorage.setItem('localeLang', language); i18n.global.locale.value = language; } diff --git a/src/store/conversation.ts b/src/store/conversation.ts index 98af97c..e92988c 100644 --- a/src/store/conversation.ts +++ b/src/store/conversation.ts @@ -8,7 +8,7 @@ // PURPOSE. // See the Mulan PSL v2 for more details. import { defineStore } from 'pinia'; -import { ref, nextTick, watch, onMounted } from 'vue'; +import { ref, nextTick, watch, onMounted, onBeforeUnmount } from 'vue'; import { useAccountStore, useHistorySessionStore } from 'src/store'; import { AppShowType, @@ -100,7 +100,7 @@ export const useSessionStore = defineStore('conversation', () => { }, ind?: number, ): Promise => { - const language = localStorage.getItem('localeLang') === 'EN' ? 'en' : 'zh'; + const language = localStorage.getItem('localeLang'); const { currentSelectedSession } = useHistorySessionStore(); params.conversationId = currentSelectedSession; // 当前问答在整个问答记录中的索引 openouler有什么ai特性 @@ -873,19 +873,42 @@ export const useSessionStore = defineStore('conversation', () => { }; }); +import { electronProcess } from '@/utils/electron'; export const useChangeThemeStore = defineStore('theme', () => { const theme = ref(''); - if (localStorage.getItem('theme')) { - theme.value = localStorage.getItem('theme') || 'dark'; + // if (localStorage.getItem('theme')) { + // theme.value = localStorage.getItem('theme') || 'dark'; + // } + + function updateTheme(t: 'dark' | 'light') { + theme.value = t; + } + + function storageListener(e: StorageEvent) { + if (e.key === 'theme') { + theme.value = e.newValue || 'light'; + document.body.setAttribute('theme', theme.value); + } } + watch( + () => theme.value, + (newVal) => { + localStorage.setItem('theme', newVal); + document.body.setAttribute('theme', newVal); + }, + ); + onMounted(() => { - window.addEventListener('storage', (e: StorageEvent) => { - if (e.key === 'theme') { - theme.value = e.newValue || 'light'; - document.body.setAttribute('theme', theme.value); - } - }); + if (electronProcess) { + electronProcess.env['EULERCOPILOT_THEME'] && + updateTheme(electronProcess.env['EULERCOPILOT_THEME']); + } + window.addEventListener('storage', storageListener); + }); + + onBeforeUnmount(() => { + window.removeEventListener('storage', storageListener); }); return { theme, diff --git a/src/store/lang.ts b/src/store/lang.ts index 44dfd47..ef1f179 100644 --- a/src/store/lang.ts +++ b/src/store/lang.ts @@ -1,15 +1,30 @@ import { defineStore } from 'pinia'; -import { ref } from 'vue'; +import { onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; +import { electronProcess, ipcRenderer } from '@/utils/electron'; export const useLangStore = defineStore('lang', () => { const i18n = useI18n(); - const language = ref(localStorage.getItem('localeLang') || 'CN'); - const changeLanguage = (data: 'CN' | 'EN') => { - language.value = data.toString(); + const language = ref(localStorage.getItem('localeLang') || 'en'); + const changeLanguage = (lang: 'zh_cn' | 'en') => { + language.value = lang; i18n.locale.value = language.value; - localStorage.setItem('localeLang', data); + localStorage.setItem('localeLang', lang); + if (ipcRenderer) { + ipcRenderer.invoke('copilot:lang', { lang }); + } }; + + onMounted(() => { + if (electronProcess) { + const nlsConfig = JSON.parse( + electronProcess.env['EULERCOPILOT_NLS_CONFIG'], + ); + + electronProcess.env['EULERCOPILOT_NLS_CONFIG'] && + changeLanguage(nlsConfig.userLocale); + } + }); return { language, changeLanguage, diff --git a/src/utils/electron.ts b/src/utils/electron.ts index 281fabf..e1f95bb 100644 --- a/src/utils/electron.ts +++ b/src/utils/electron.ts @@ -1,3 +1,10 @@ +// You can use this software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN 'AS IS' BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR +// PURPOSE. +// See the Mulan PSL v2 for more details. export const electronProcess = window.eulercopilot ? window.eulercopilot.process : undefined; @@ -5,9 +12,3 @@ export const electronProcess = window.eulercopilot export const ipcRenderer = window.eulercopilot ? window.eulercopilot.ipcRenderer : undefined; -// export const isElectron = window.eulercopilot.process.versions.electron -// ? true -// : false; - -// export const ipcRenderer = window.eulercopilot.ipcRenderer; -// export const platform = window.eulercopilot.process.platform; diff --git a/src/views/chat/index.vue b/src/views/chat/index.vue index 72c1177..26e22aa 100644 --- a/src/views/chat/index.vue +++ b/src/views/chat/index.vue @@ -128,14 +128,9 @@ onBeforeUnmount(() => { background-size: cover; background-position: center; color: var(--o-text-color-primary); - padding: 40px 0 0 0; } .chat-header { - position: fixed; - top: 0; - left: 0; - width: 100%; display: flex; justify-content: space-between; align-items: center; @@ -143,19 +138,21 @@ onBeforeUnmount(() => { padding: 0 24px; background-color: var(--o-bg-color-base); box-shadow: 2px 0 8px 0 rgba(29, 35, 41, 0.1); - // -webkit-app-region: drag; + -webkit-app-region: drag; &-logo { display: flex; align-items: center; gap: 10px; flex: 1; - -webkit-app-region: drag; + } + &-operation { + -webkit-app-region: no-drag; } } .chat-container { - height: 100%; + height: calc(100% - 40px); display: flex; flex-direction: column; align-items: center; diff --git a/src/views/dialogue/components/DialogueSession.vue b/src/views/dialogue/components/DialogueSession.vue index 9d03fb1..d0b9e32 100644 --- a/src/views/dialogue/components/DialogueSession.vue +++ b/src/views/dialogue/components/DialogueSession.vue @@ -170,7 +170,6 @@ const handleSendMessage = async ( user_selected_flow?: string[], ) => { if (isAnswerGenerating.value || !isAllowToSend.value) return; - const language = localStorage.getItem('localeLang') === 'CN' ? 'zh' : 'en'; const len = conversationList.value.length; if ( len > 0 && diff --git a/src/views/dialogue/components/ReportPopover.vue b/src/views/dialogue/components/ReportPopover.vue index 7a7b57b..a0941b6 100644 --- a/src/views/dialogue/components/ReportPopover.vue +++ b/src/views/dialogue/components/ReportPopover.vue @@ -70,7 +70,7 @@ const handleComplaint = () => {
    diff --git a/src/views/dialogue/components/TitleBar.vue b/src/views/dialogue/components/TitleBar.vue index 10109f6..61d81d9 100644 --- a/src/views/dialogue/components/TitleBar.vue +++ b/src/views/dialogue/components/TitleBar.vue @@ -15,9 +15,9 @@ const { language } = storeToRefs(useLangStore()); const { changeLanguage } = useLangStore(); const themeStore = useChangeThemeStore(); -const lang = computed(() => (language.value === 'EN' ? 'English' : '简体中文')); +const lang = computed(() => (language.value === 'en' ? 'English' : '简体中文')); -const changeLanguagefun = (lang: 'CN' | 'EN') => { +const changeLanguagefun = (lang: 'zh_cn' | 'en') => { changeLanguage(lang); // 同步语言到iframe const iframe = document.querySelector('#my-iframe'); @@ -26,6 +26,9 @@ const changeLanguagefun = (lang: 'CN' | 'EN') => { let target = `${window.location.origin}/witchaind`; iframe.contentWindow.postMessage(data, target); } + if (ipcRenderer) { + ipcRenderer.invoke('copilot:lang', { lang: lang }); + } }; const theme = ref(localStorage.getItem('theme') || 'light'); @@ -87,7 +90,7 @@ const headerStyles = computed(() => {
    English
    @@ -95,7 +98,7 @@ const headerStyles = computed(() => {
    简体中文
    @@ -141,8 +144,8 @@ const headerStyles = computed(() => { height: 48px; padding: 0 24px; background-color: var(--o-bg-color-base); + -webkit-app-region: drag; &-left { - -webkit-app-region: drag; flex: 1; } span { @@ -173,6 +176,7 @@ const headerStyles = computed(() => { } } .header-right { + -webkit-app-region: no-drag; display: flex; .mode { cursor: pointer; diff --git a/src/views/dialogue/dialogueView.vue b/src/views/dialogue/dialogueView.vue index a069a5d..3ce297d 100644 --- a/src/views/dialogue/dialogueView.vue +++ b/src/views/dialogue/dialogueView.vue @@ -6,12 +6,8 @@ import { useHistorySessionStore, useSessionStore, useAccountStore, - useLangStore, } from 'src/store'; -import { marked } from 'marked'; -import { ARGEEMENT_VERSION } from 'src/conf/version'; import { useChangeThemeStore } from 'src/store'; -import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'; import { api } from 'src/apis'; import { ElMessage } from 'element-plus'; import { watch } from 'vue'; @@ -37,12 +33,6 @@ const { createNewSession } = useHistorySessionStore(); // 挂载全局事件 window.onHtmlEventDispatch = onHtmlEventDispatch as any; -const { logout } = useAccountStore(); -const { historySession } = storeToRefs(useHistorySessionStore()); -const { conversationList } = storeToRefs(useSessionStore()); -const { language } = storeToRefs(useLangStore()); -const { changeLanguage } = useLangStore(); -const dialogVisible = ref(false); const themeStore = useChangeThemeStore(); const apikeyVisible = ref(false); const KnowledgeVisible = ref(false); @@ -114,46 +104,8 @@ const formValidateStatus = ref({ kb_id: true, }); -const logoutHandler = () => { - logout(); - historySession.value = []; - conversationList.value = []; -}; - -// 协议内容 -const agreement = ref(''); -// 协议版本 -const agreementVersion = ref(ARGEEMENT_VERSION); - -/** - * 读取协议 - */ -const readAgreement = async () => { - const response = await import('src/conf/agreement.md?raw'); - agreement.value = marked.parse(response.default) as string; -}; - -/** - * 处理服务协议是否显示 - * @param CheckedVersion - */ -const handleAgreement = async (CheckedVersion: string | null) => { - if (agreementVersion.value === CheckedVersion) { - return; - } - await readAgreement(); - dialogVisible.value = true; -}; - const theme = ref(localStorage.getItem('theme') || 'light'); -const changeTheme = () => { - theme.value = theme.value === 'dark' ? 'light' : 'dark'; - document.body.setAttribute('theme', theme.value); - localStorage.setItem('theme', theme.value); - themeStore.theme = theme.value; -}; - const createApi = async () => { apikey.value = ''; revoke.value = false; @@ -194,8 +146,6 @@ const copy = () => { navigator.clipboard.writeText(apikey.value); }; -const lang = computed(() => (language.value === 'EN' ? 'English' : '简体中文')); - const handleConfirmCreateModel = async (formData: any | undefined) => { const [_, res] = await api.updateKnowledgeList({ kb_id: ruleForm.kb_id || '', @@ -211,17 +161,6 @@ const handleConfirmCreateModel = async (formData: any | undefined) => { } }; -const changeLanguagefun = (lang: 'CN' | 'EN') => { - changeLanguage(lang); - // 同步语言到iframe - const iframe = document.querySelector('#my-iframe'); - if (iframe?.contentWindow) { - const data = { lang: localStorage.getItem('localeLang') }; - let target = `${window.location.origin}/witchaind`; - iframe.contentWindow.postMessage(data, target); - } -}; - const handleFormValidate = (prop: any, isValid: boolean, message: string) => { formValidateStatus.value[prop] = isValid; }; @@ -310,66 +249,6 @@ watch(