diff --git a/electron/main/common/conf.ts b/electron/main/common/conf.ts new file mode 100644 index 0000000000000000000000000000000000000000..0fb68b2ff8314cf5ba0c480208d50a60ac67e724 --- /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 23974261bd4360c48dc911eff57f809690f24691..0c3e1c5965a4649111f2c99b1a64f63671fb42f6 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 2053843e3fcbd7d1731ba83145f5cb2c44ab9ce6..c339dd859f4972a34d2853281dd4c47f56892742 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 b8c19749677597a2ac66416d0c1d4dbbd2028944..c46d65d2882345f2def73d5ac8b9dadad2085281 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 8ec327a12d7f7264e6f6557c1be8005781286e30..e0ffe97ecfc33b9f40cf213bf1dc778ce512001a 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 317ec9f388754d51d6ac932785b51d8ae332fc76..fdfe26114a5a68755e5cab133eaf2fcf9d2058ff 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 11e8ab4d99b68067a2113e4d17af2b7d35b131fd..12faa410380a4207804d83fae79f9bab3d2c51d3 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 dcc0f49f56708bcde301388a4ed64bda15959da9..92fceac3ab7b60417ef216d239843b2ada12ec53 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 693c5a786cba8dfc1cffd0f7cef4364dd90d891b..7413e463164ae78cdc87265374d87158767cf17f 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 6c7563097df0c4dab6c9e56b4b10a972bd342b39..7a24800ad450e2d7f5ad647f74c24382ef656f9a 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 baa1dfd1bbfa26825b2cceecd149535b9d0180bd..a55456965e51330806267d9cec94527370e2c067 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 813e953bbc65d6fe1f56d7ea5a6568dbd0275eec..b688d5f4e34dca5847f8db163613c478c79cec29 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 30b5ed186e0b3915fc3ea5fc1fba8f2015905b1a..8b4c0fe40ef60d20753d20b0adad8ed3557c6e5d 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 c1b484c173e1863fe8ddddde254d5d1b67032825..799437cdec305e480712d0f5c58df972d8973273 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 73a1f5bd16cb12ce5a0ee2636052581cfc271a90..4cc9785655a0f856540dcb76d40d5cec4199be97 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 766c6bfc105a12cab31aae09983b2ec9e34f7ad5..51a29f8b882bb35f5fc5d4b65729ab30b94ae0f9 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 f95669574b8b2464699776aab6d81171fa26654e..8d13fd8e7b8637b1b040150245f93a4bee5be360 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 98af97c46868ebcf1f0e2bddeff4e50a40209030..e92988c5023ff54c775e7cfb6c5ec44fd71af18a 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 44dfd47bb1a16cfbe068bb6f0544b1c180c8e460..ef1f1796096384b511113ea1cd9f5a6b0b29a30b 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 281fabfd3a3bdb275422ff8eb310255f463571b3..e1f95bb60d2a18fdd9264bff966a5dface280336 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 72c1177d79b3423ca286279f0ea1973267554597..26e22aa357b0f9db8d8c96d66f64331558e72488 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 9d03fb1bb641486b37150cf0a074effa634c589a..d0b9e32292e40383d9fabeb7e32ee45cd41cea23 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 7a7b57be090cbffc4dd069c90c20e343cf1bd833..a0941b6a8165c8290f348e3c0dc24d54c00b2a10 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 10109f61f6df5f1120d4dd3048795bc99a799774..61d81d99164c7250a0a76195a4aa6a9d166b070f 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 a069a5de330880e7ff2d33dda76ce1bda1c859a3..3ce297d101e5a8a8fad62d670b73fb19df2498f5 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(