From d2e1c1210435066fa746f600d2f27cac71bace1d Mon Sep 17 00:00:00 2001 From: Hu Gang <18768366022@163.com> Date: Mon, 7 Apr 2025 14:52:47 +0800 Subject: [PATCH] chat window --- electron/main/common/nls.ts | 17 ++ electron/main/common/platform.ts | 83 ++++++ electron/main/common/product.ts | 7 + electron/main/index.ts | 198 ++++++++++++- electron/main/node/userDataPath.ts | 44 +++ electron/main/window/create.ts | 71 ++++- electron/main/window/index.ts | 2 +- electron/main/window/options.ts | 32 +- electron/main/window/tray.ts | 2 +- electron/preload/index.ts | 44 +++ env.d.ts | 1 + package.json | 2 +- src/App.vue | 10 +- src/assets/images/edit.png | Bin 0 -> 776 bytes src/components/bubble/index.vue | 74 +++++ src/router/index.ts | 121 ++++---- src/router/route.ts | 11 + src/store/conversation.ts | 11 +- src/utils/electron.ts | 13 + src/views/chat/Operations.vue | 72 +++++ src/views/chat/Welcome.vue | 33 +++ src/views/chat/index.vue | 277 ++++++++++++++++++ src/views/dialogue/Copilot.vue | 34 +-- .../dialogue/components/DialogueSession.vue | 2 +- src/views/dialogue/components/TitleBar.vue | 184 ++++++++++++ src/views/dialogue/dialogueView.vue | 7 +- 26 files changed, 1239 insertions(+), 113 deletions(-) create mode 100644 electron/main/common/nls.ts create mode 100644 electron/main/common/platform.ts create mode 100644 electron/main/common/product.ts create mode 100644 electron/main/node/userDataPath.ts create mode 100644 src/assets/images/edit.png create mode 100644 src/components/bubble/index.vue create mode 100644 src/router/route.ts create mode 100644 src/utils/electron.ts create mode 100644 src/views/chat/Operations.vue create mode 100644 src/views/chat/Welcome.vue create mode 100644 src/views/chat/index.vue create mode 100644 src/views/dialogue/components/TitleBar.vue diff --git a/electron/main/common/nls.ts b/electron/main/common/nls.ts new file mode 100644 index 00000000..23974261 --- /dev/null +++ b/electron/main/common/nls.ts @@ -0,0 +1,17 @@ +export interface INLSConfiguration { + /** + * Locale as defined in `argv.json` or `app.getLocale()`. + */ + readonly userLocale: string; + + /** + * Locale as defined by the OS (e.g. `app.getPreferredSystemLanguages()`). + */ + readonly osLocale: string; + + /** + * The actual language of the UI that ends up being used considering `userLocale` + * and `osLocale`. + */ + readonly resolvedLanguage: string; +} diff --git a/electron/main/common/platform.ts b/electron/main/common/platform.ts new file mode 100644 index 00000000..2053843e --- /dev/null +++ b/electron/main/common/platform.ts @@ -0,0 +1,83 @@ +import * as nls from './nls'; + +export const LANGUAGE_DEFAULT = 'en'; + +let _isWindows = false; +let _isMacintosh = false; +let _isLinux = false; +let _isElectron = false; +let _locale: string | undefined = undefined; +let _language: string = LANGUAGE_DEFAULT; + +export interface IProcessEnvironment { + [key: string]: string | undefined; +} + +/** + * This interface is intentionally not identical to node.js + * process because it also works in sandboxed environments + * where the process object is implemented differently. We + * define the properties here that we need for `platform` + * to work and nothing else. + */ +export interface INodeProcess { + platform: string; + arch: string; + env: IProcessEnvironment; + versions?: { + node?: string; + electron?: string; + chrome?: string; + }; + type?: string; + cwd: () => string; +} + +let nodeProcess: INodeProcess | undefined = process; + +const isElectronProcess = typeof nodeProcess?.versions?.electron === 'string'; + +if (typeof nodeProcess === 'object') { + _isWindows = nodeProcess.platform === 'win32'; + _isMacintosh = nodeProcess.platform === 'darwin'; + _isLinux = nodeProcess.platform === 'linux'; + _isElectron = isElectronProcess; + _locale = LANGUAGE_DEFAULT; + _language = LANGUAGE_DEFAULT; + const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG']; + if (rawNlsConfig) { + try { + const nlsConfig: nls.INLSConfiguration = JSON.parse(rawNlsConfig); + _locale = nlsConfig.userLocale; + _language = nlsConfig.resolvedLanguage || LANGUAGE_DEFAULT; + } catch (e) {} + } +} else { + console.error('Unable to resolve platform.'); +} + +export const enum Platform { + Web, + Mac, + Linux, + Windows, +} +export type PlatformName = 'Web' | 'Windows' | 'Mac' | 'Linux'; + +let _platform: Platform = Platform.Web; +if (_isMacintosh) { + _platform = Platform.Mac; +} else if (_isWindows) { + _platform = Platform.Windows; +} else if (_isLinux) { + _platform = Platform.Linux; +} + +export const isWindows = _isWindows; +export const isMacintosh = _isMacintosh; +export const isLinux = _isLinux; +export const isElectron = _isElectron; +export const platform = _platform; + +export const locale = _locale; +export const language = _language; diff --git a/electron/main/common/product.ts b/electron/main/common/product.ts new file mode 100644 index 00000000..b8c19749 --- /dev/null +++ b/electron/main/common/product.ts @@ -0,0 +1,7 @@ +export interface IProductConfiguration { + readonly name: string; +} + +export const productObj: IProductConfiguration = { + name: 'eulercopilot', +}; diff --git a/electron/main/index.ts b/electron/main/index.ts index 021f6924..8ec327a1 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1,21 +1,201 @@ -import { app, ipcMain } from 'electron'; -import { createDefaultWindow, createTray } from './window'; +import path from 'node:path'; +import fs from 'node:fs'; +import { + app, + ipcMain, + globalShortcut, + nativeTheme, + 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'; + +interface ICacheConf { + userLocale: string; + theme: 'system' | 'light' | 'dark'; +} + +const userDataPath = getUserDataPath(productObj.name); +const cachePath = getCachePath(); + +const commonCacheConf: Partial = getUserDefinedConf(userDataPath); + +const osLocale = processZhLocale( + (app.getPreferredSystemLanguages()?.[0] ?? 'en').toLowerCase(), +); + +app.once('ready', () => { + onReady(); +}); + +app.on('will-quit', () => { + globalShortcut.unregisterAll(); +}); + +app.on('window-all-closed', () => { + ipcMain.removeAllListeners(); +}); + +async function onReady() { + try { + const [, nlsConfig] = await Promise.all([ + mkdirpIgnoreError(cachePath), + resolveNlsConfiguration(), + ]); + + nativeTheme.themeSource = commonCacheConf.theme || 'light'; + process.env['EULERCOPILOT_THEME'] = commonCacheConf.theme || 'light'; + process.env['EULERCOPILOT_NLS_CONFIG'] = JSON.stringify(nlsConfig); + process.env['EULERCOPILOT_CACHE_PATH'] = cachePath || ''; + + startup(); + } catch (error) {} +} + +async function startup() { + registerIpcListener(); -app.whenReady().then(() => { const tray = createTray(); - const win = createDefaultWindow(); + let win: BrowserWindow; + let chatWindow: BrowserWindow; + + win = createDefaultWindow(); + chatWindow = createChatWindow(); + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + win = createDefaultWindow(); + chatWindow = createChatWindow(); + } + }); + + 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(); }); - win.on('close', (event) => { event.preventDefault(); win.hide(); }); -}); -app.on('window-all-closed', () => { - ipcMain.removeAllListeners(); -}); + chatWindow.on('close', (event) => { + event.preventDefault(); + chatWindow.hide(); + }); +} + +function registerIpcListener() { + ipcMain.handle('copilot:toggle', () => { + if (nativeTheme.shouldUseDarkColors) { + nativeTheme.themeSource = 'light'; + } else { + nativeTheme.themeSource = 'dark'; + } + return nativeTheme.shouldUseDarkColors; + }); + + ipcMain.handle('copilot:system', () => { + nativeTheme.themeSource = 'system'; + }); +} + +function getUserDefinedConf(dir: string) { + try { + return fs.readFileSync( + path.join(dir, 'eulercopilot-common-storage.json'), + 'utf-8', + ); + } catch (error) { + // Ignore error + return {}; + } +} + +function processZhLocale(appLocale: string): string { + if (appLocale.startsWith('zh')) { + const region = appLocale.split('-')[1]; + + // On Windows and macOS, Chinese languages returned by + // app.getPreferredSystemLanguages() start with zh-hans + // for Simplified Chinese or zh-hant for Traditional Chinese, + // so we can easily determine whether to use Simplified or Traditional. + // However, on Linux, Chinese languages returned by that same API + // are of the form zh-XY, where XY is a country code. + // For China (CN), Singapore (SG), and Malaysia (MY) + // 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-tw'; + } + + return appLocale; +} + +/** + * 国际化支持 + * @returns + */ +async function resolveNlsConfiguration(): Promise { + if (commonCacheConf.userLocale) { + return { + userLocale: commonCacheConf.userLocale, + osLocale, + resolvedLanguage: commonCacheConf.userLocale, + }; + } + + let userLocale = app.getLocale(); + if (!userLocale) { + return { + userLocale: 'en', + osLocale, + resolvedLanguage: 'en', + }; + } + + userLocale = processZhLocale(userLocale.toLowerCase()); + + return { + userLocale, + osLocale, + resolvedLanguage: osLocale, + }; +} + +async function mkdirpIgnoreError( + dir: string | undefined, +): Promise { + if (typeof dir === 'string') { + try { + await fs.promises.mkdir(dir, { recursive: true }); + + return dir; + } catch (error) { + // ignore + } + } + + 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 new file mode 100644 index 00000000..317ec9f3 --- /dev/null +++ b/electron/main/node/userDataPath.ts @@ -0,0 +1,44 @@ +import * as os from 'os'; +import * as path from 'node:path'; + +const cwd = process.cwd(); + +export function getUserDataPath(productName: string): string { + const userDataPath = doGetUserDataPath(productName); + const pathsToResolve = [userDataPath]; + + if (!path.isAbsolute(userDataPath)) { + pathsToResolve.unshift(cwd); + } + + return path.resolve(...pathsToResolve); +} + +function doGetUserDataPath(productName: string): string { + let appDataPath = process.env['VSCODE_APPDATA']; + switch (process.platform) { + case 'win32': + appDataPath = process.env['APPDATA']; + if (!appDataPath) { + const userProfile = process.env['USERPROFILE']; + if (typeof userProfile !== 'string') { + throw new Error( + 'Windows: Unexpected undefined %USERPROFILE% environment variable', + ); + } + appDataPath = path.join(userProfile, 'AppData', 'Roaming'); + } + break; + case 'darwin': + appDataPath = path.join(os.homedir(), 'Library', 'Application Support'); + break; + case 'linux': + appDataPath = + process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config'); + break; + default: + throw new Error('Platform not supported'); + } + + return path.join(appDataPath, productName); +} diff --git a/electron/main/window/create.ts b/electron/main/window/create.ts index 1d47863d..11e8ab4d 100644 --- a/electron/main/window/create.ts +++ b/electron/main/window/create.ts @@ -1,32 +1,71 @@ -import path from 'node:path' -import { BrowserWindow, app } from 'electron' -import { options as allWindow } from './options' +import path from 'node:path'; +import * as electron from 'electron'; +import { BrowserWindow, app, globalShortcut } from 'electron'; +import { options as allWindow } from './options'; -export function createWindow(options: Electron.BrowserWindowConstructorOptions, hash: string): BrowserWindow { +class BaseWindow { + constructor() {} +} + +export function createWindow( + options: Electron.BrowserWindowConstructorOptions, + hash: string, +): BrowserWindow { const win = new BrowserWindow({ ...options, webPreferences: { - devTools: true, + nodeIntegration: true, + contextIsolation: false, preload: path.join(__dirname, '../preload/index.js'), }, - }) + }); if (app.isPackaged) { - win.loadFile(path.join(__dirname, `../index.html`)) - } - else { - win.webContents.openDevTools() - win.loadURL(`http://localhost:${process.env.PORT}`) + win.loadFile(path.join(__dirname, `../index.html`), { hash }); + } else { + // win.webContents.openDevTools(); + win.loadURL(`http://localhost:${process.env.PORT}/#${hash}`); } - return win + return win; } -let defaultWindow: BrowserWindow | null = null +let defaultWindow: BrowserWindow | null = null; +const theme = process.env.EULERCOPILOT_THEME || 'light'; + export function createDefaultWindow(): BrowserWindow { - if (defaultWindow) return defaultWindow + if (defaultWindow) return defaultWindow; + + const hash = allWindow.mainWindow.hash; + const defaultWindowOptions = allWindow.mainWindow.window; + + defaultWindowOptions.titleBarOverlay = { + color: theme === 'dark' ? '#1f2329' : '#ffffff', + height: 48, + symbolColor: theme === 'dark' ? 'white' : 'black', + }; + + defaultWindow = createWindow(defaultWindowOptions, hash); + + return defaultWindow; +} + +export function createChatWindow(): BrowserWindow { + const hash = allWindow.chatWindow.hash; + const chatWindowOptions = allWindow.chatWindow.window; + chatWindowOptions.titleBarOverlay = { + color: theme === 'dark' ? '#1f2329' : '#ffffff', + height: 40, + symbolColor: theme === 'dark' ? 'white' : 'black', + }; + const chatWindow = createWindow(chatWindowOptions, hash); + + const shortcutKey = + process.platform === 'darwin' ? 'Cmd+Option+O' : 'Ctrl+Alt+O'; - defaultWindow = createWindow(allWindow.defaultWin.window, allWindow.defaultWin.hash) + globalShortcut.register(shortcutKey, () => { + chatWindow.show(); + }); - return defaultWindow + return chatWindow; } diff --git a/electron/main/window/index.ts b/electron/main/window/index.ts index f23de228..dcc0f49f 100644 --- a/electron/main/window/index.ts +++ b/electron/main/window/index.ts @@ -1,2 +1,2 @@ -export { createWindow, createDefaultWindow } from './create'; +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 ff63cf9c..693c5a78 100644 --- a/electron/main/window/options.ts +++ b/electron/main/window/options.ts @@ -1,24 +1,42 @@ export interface allWindowType { [propName: string]: { + id: string; window: Electron.BrowserWindowConstructorOptions; hash: string; }; } export const options: allWindowType = { - defaultWin: { + mainWindow: { + id: 'mainWindow', window: { - width: 1700, - height: 900, + width: 1440, + height: 810, + minWidth: 1440, + minHeight: 810, + titleBarStyle: 'hidden', resizable: true, show: true, alwaysOnTop: false, useContentSize: true, - autoHideMenuBar: true, - frame: true, - backgroundColor: '#ffffff', icon: 'dist/favicon.ico', }, - hash: 'defaultWin', + hash: '/', + }, + chatWindow: { + id: 'chatWindow', + window: { + width: 680, + height: 960, + resizable: true, + show: false, + alwaysOnTop: false, + useContentSize: true, + minWidth: 680, + minHeight: 960, + titleBarStyle: 'hidden', + icon: 'dist/favicon.ico', + }, + hash: '/chat', }, }; diff --git a/electron/main/window/tray.ts b/electron/main/window/tray.ts index 84a2eba8..b4e19670 100644 --- a/electron/main/window/tray.ts +++ b/electron/main/window/tray.ts @@ -20,7 +20,7 @@ export function createTray(): Tray { appTray = new Tray(iconPath); const contextMenu = Menu.buildFromTemplate(trayMenus); - appTray.setToolTip('Eulercopilot'); + appTray.setToolTip('EulerCopilot'); appTray.setContextMenu(contextMenu); return appTray; diff --git a/electron/preload/index.ts b/electron/preload/index.ts index e69de29b..baa1dfd1 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -0,0 +1,44 @@ +import { ipcRenderer, contextBridge } from 'electron'; + +function validateIPC(channel: string): true | never { + if (!channel || !channel.startsWith('copilot:')) { + throw new Error(`Unsupported event IPC channel '${channel}'`); + } + + return true; +} + +const globals = { + ipcRenderer: { + invoke(channel: string, ...args: any[]): Promise { + validateIPC(channel); + + return ipcRenderer.invoke(channel, ...args); + }, + }, + + process: { + get platform() { + return process.platform; + }, + get arch() { + return process.arch; + }, + get versions() { + return process.versions; + }, + get env() { + return { ...process.env }; + }, + }, +}; + +if (process.contextIsolated) { + try { + contextBridge.exposeInMainWorld('eulercopilot', globals); + } catch (error) { + console.error(error); + } +} else { + (window as any).eulercopilot = globals; +} diff --git a/env.d.ts b/env.d.ts index ad8d6d5a..9c99de5f 100644 --- a/env.d.ts +++ b/env.d.ts @@ -10,6 +10,7 @@ /// declare interface Window { onHtmlEventDispatch: any; + eulercopilot: any; } declare interface ImportMetaEnv { diff --git a/package.json b/package.json index 32e5cded..d5e41723 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "euler-copilot-web", + "name": "eulercopilot", "version": "1.0.0", "description": "Openeuler intelligent question and answer assistant", "author": { diff --git a/src/App.vue b/src/App.vue index 8211c621..2ee3fe88 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,7 +2,6 @@ import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'; import '@vue-flow/core/dist/style.css'; import '@vue-flow/core/dist/theme-default.css'; -import DialogueView from 'src/views/dialogue/dialogueView.vue'; + + diff --git a/src/assets/images/edit.png b/src/assets/images/edit.png new file mode 100644 index 0000000000000000000000000000000000000000..0265c1548b034bf1bea18b9027c0d13b439ca03d GIT binary patch literal 776 zcmV+j1NZ!iP)~f2Q!Y0bj4#QIdX%`GA|v6hsrE9HqZ4{y008RGLzGuE zRrC}=0J$`20D!tGTOgXg;<>-Jv1fwByM8DLpitwbQ$)uILQVt3V~l)|xFJZQ(;CrM zXsW%`qaG8gH&Jqut+P~VL^EkQQbfxVdB zVM)6tkczL%O(wqp(P`rJ_??JP5x4!wpU=ZCmvjiJU(pwJp%&Joohrzgb0j*Agu9V& zXP44sYiyOrt{;kt_$z|6tI$ZetE;kYs=aKgy{xOU9SM!E$MGTxwonC(58|(uKaMS! zwev(6edJjl#gcx+FiL@IS=&D=WrQB>{{;vjMT#SHLGN-Z|6B-Ni@{YqM2?B~LQ{Qg z;go4z#RJvEIr}~Zqt-s3S2-e#HcR` zorg;oGe6ET@oR(A&1b|pCVp*jypwa!siy}46kA69++SOcck-XByx38*G26_ztB{wt zEy^vTuCEb0T?n5YdQAKpNVNq);yx3<22wXO(bHO&iO;9!M$TsDJ$3^C%sOh<4>FIU z8&9h+t;IQAU>{`89N-E_agOZ&fVMdJOmU7$N5{YE@4~;8VFdeiajysf0000 +import { computed } from 'vue'; +import type { CSSProperties } from 'vue'; + +type Style = Record<'avatar' | 'content', CSSProperties>; + +interface BubbleProps { + avatar?: string; + content: string; + loading?: boolean; + placement?: 'start' | 'end'; + shape?: 'round' | 'corner'; + styles?: Partial diff --git a/src/router/index.ts b/src/router/index.ts index 167a16fa..e9d2db1e 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -7,67 +7,80 @@ // IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR // PURPOSE. // See the Mulan PSL v2 for more details. -import { createRouter, createWebHashHistory } from 'vue-router'; +import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; import NotFoundComponent from '@/views/404.vue'; import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'; +import { dynamicRoutes } from './route'; + +const staticRoutes: Array = [ + { + path: '/', + redirect: '/', + component: () => import('src/views/dialogue/dialogueView.vue'), + children: [ + { + path: '/', + name: 'dialogue', + component: () => import('src/views/dialogue/Copilot.vue'), + }, + { + path: '/api', + name: 'api', + component: (): Promise => + import('src/views/api/index.vue'), + }, + { + path: '/app', + name: 'app', + component: (): Promise => + import('src/views/app/index.vue'), + }, + { + path: '/createApp', + name: 'createApp', + component: (): Promise< + typeof import('src/views/createapp/index.vue') + > => import('src/views/createapp/index.vue'), + }, + { + path: '/witchainD', + name: 'witchainD', + component: (): Promise => + import('src/views/tools/index.vue'), + }, + ], + }, + { + path: '/login', + name: 'login', + component: (): Promise => + import('src/views/dialogue/Copilot.vue'), + }, + { + path: '/copilot', + name: 'copilot', + component: (): Promise => + import('src/views/dialogue/Copilot.vue'), + }, + + { + path: '/404', + component: NotFoundComponent, + name: 'NotFound', + }, + { + path: '/:pathMatch(.*)*', + redirect: '/404', + }, +]; + +const routes = [...staticRoutes, ...dynamicRoutes]; const router = createRouter({ history: createWebHashHistory( qiankunWindow.__POWERED_BY_QIANKUN__ ? '/eulercopilot/' : '/', ), - routes: [ - { - path: '/', - name: 'dialogue', - component: (): Promise => - import('src/views/dialogue/Copilot.vue'), - }, - { - path: '/login', - name: 'login', - component: (): Promise => - import('src/views/dialogue/Copilot.vue'), - }, - { - path: '/copilot', - name: 'copilot', - component: (): Promise => - import('src/views/dialogue/Copilot.vue'), - }, - { - path: '/api', - name: 'api', - component: (): Promise => - import('src/views/api/index.vue'), - }, - { - path: '/app', - name: 'app', - component: (): Promise => - import('src/views/app/index.vue'), - }, - { - path: '/createApp', - name: 'createApp', - component: (): Promise => - import('src/views/createapp/index.vue'), - }, - { - path: '/witchainD', - name: 'witchainD', - component: (): Promise => - import('src/views/tools/index.vue'), - }, - { - path: '/404', - component: NotFoundComponent, - name: 'NotFound', - }, - { - path: '/:pathMatch(.*)*', - redirect: '/404', - }, - ], + routes, }); export default router; diff --git a/src/router/route.ts b/src/router/route.ts new file mode 100644 index 00000000..048a16f9 --- /dev/null +++ b/src/router/route.ts @@ -0,0 +1,11 @@ +import { RouteRecordRaw } from 'vue-router'; + +const dynamicRoutes: Array = [ + { + path: '/chat', + name: 'chat', + component: () => import('../views/chat/index.vue'), + }, +]; + +export { dynamicRoutes }; diff --git a/src/store/conversation.ts b/src/store/conversation.ts index e29a7539..32ade08c 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 } from 'vue'; +import { ref, nextTick, watch, onMounted } from 'vue'; import { useAccountStore, useHistorySessionStore } from 'src/store'; import { AppShowType, @@ -878,6 +878,15 @@ export const useChangeThemeStore = defineStore('theme', () => { if (localStorage.getItem('theme')) { theme.value = localStorage.getItem('theme') || 'dark'; } + + onMounted(() => { + window.addEventListener('storage', (e: StorageEvent) => { + if (e.key === 'theme') { + theme.value = e.newValue || 'light'; + document.body.setAttribute('theme', theme.value); + } + }); + }); return { theme, }; diff --git a/src/utils/electron.ts b/src/utils/electron.ts new file mode 100644 index 00000000..281fabfd --- /dev/null +++ b/src/utils/electron.ts @@ -0,0 +1,13 @@ +export const electronProcess = window.eulercopilot + ? window.eulercopilot.process + : undefined; + +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/Operations.vue b/src/views/chat/Operations.vue new file mode 100644 index 00000000..62664ecc --- /dev/null +++ b/src/views/chat/Operations.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/views/chat/Welcome.vue b/src/views/chat/Welcome.vue new file mode 100644 index 00000000..db295741 --- /dev/null +++ b/src/views/chat/Welcome.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/views/chat/index.vue b/src/views/chat/index.vue new file mode 100644 index 00000000..72c1177d --- /dev/null +++ b/src/views/chat/index.vue @@ -0,0 +1,277 @@ + + +