diff --git a/packages/stage-tamagotchi/electron.vite.config.ts b/packages/stage-tamagotchi/electron.vite.config.ts index ac639bc..ed9aa37 100644 --- a/packages/stage-tamagotchi/electron.vite.config.ts +++ b/packages/stage-tamagotchi/electron.vite.config.ts @@ -1,9 +1,6 @@ -import { join, resolve } from 'node:path' -import { Download } from '@proj-airi/unplugin-download' -import { DownloadLive2DSDK } from '@proj-airi/unplugin-live2d-sdk' -import vue from '@vitejs/plugin-vue' import { defineConfig, externalizeDepsPlugin } from 'electron-vite' -import unocss from 'unocss/vite' + +import rendererConfig from './renderer.vite.config' export default defineConfig({ main: { @@ -12,25 +9,5 @@ export default defineConfig({ preload: { plugins: [externalizeDepsPlugin()], }, - renderer: { - optimizeDeps: { - exclude: [ - '@proj-airi/stage-ui/*', - ], - }, - resolve: { - alias: { - '@renderer': resolve(join('src', 'renderer', 'src')), - '@proj-airi/stage-ui': resolve(join(import.meta.dirname, '..', 'stage-ui', 'dist')), - '@proj-airi/stage-ui/stores': resolve(join(import.meta.dirname, '..', 'stage-ui', 'dist', 'stores')), - }, - }, - plugins: [ - vue(), - unocss(), - DownloadLive2DSDK(), - Download('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', 'hiyori_free_zh.zip', 'assets/live2d/models'), - Download('https://dist.ayaka.moe/live2d-models/hiyori_pro_zh.zip', 'hiyori_pro_zh.zip', 'assets/live2d/models'), - ], - }, + renderer: rendererConfig, }) diff --git a/packages/stage-tamagotchi/renderer.vite.config.ts b/packages/stage-tamagotchi/renderer.vite.config.ts new file mode 100644 index 0000000..d2eb3c1 --- /dev/null +++ b/packages/stage-tamagotchi/renderer.vite.config.ts @@ -0,0 +1,33 @@ +import { join, resolve } from 'node:path' +import { Download } from '@proj-airi/unplugin-download' +import { DownloadLive2DSDK } from '@proj-airi/unplugin-live2d-sdk' +import Vue from '@vitejs/plugin-vue' +import UnoCss from 'unocss/vite' +import VueRouter from 'unplugin-vue-router/vite' +import { defineConfig } from 'vite' + +export default defineConfig({ + optimizeDeps: { + exclude: [ + '@proj-airi/stage-ui/*', + ], + }, + resolve: { + alias: { + '@renderer': resolve(join('src', 'renderer', 'src')), + '@proj-airi/stage-ui': resolve(join(import.meta.dirname, '..', 'stage-ui', 'dist')), + '@proj-airi/stage-ui/stores': resolve(join(import.meta.dirname, '..', 'stage-ui', 'dist', 'stores')), + }, + }, + plugins: [ + Vue(), + UnoCss(), + VueRouter({ + dts: resolve(import.meta.dirname, 'src/typed-router.d.ts'), + routesFolder: 'src/renderer/src/pages', + }), + DownloadLive2DSDK(), + Download('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', 'hiyori_free_zh.zip', 'assets/live2d/models'), + Download('https://dist.ayaka.moe/live2d-models/hiyori_pro_zh.zip', 'hiyori_pro_zh.zip', 'assets/live2d/models'), + ], +}) diff --git a/packages/stage-tamagotchi/src/main/index.ts b/packages/stage-tamagotchi/src/main/index.ts index d540a53..5b9ff13 100644 --- a/packages/stage-tamagotchi/src/main/index.ts +++ b/packages/stage-tamagotchi/src/main/index.ts @@ -1,7 +1,7 @@ import { join } from 'node:path' import { env, platform } from 'node:process' import { electronApp, is, optimizer } from '@electron-toolkit/utils' -import { app, BrowserWindow, ipcMain, shell } from 'electron' +import { app, BrowserWindow, dialog, ipcMain, Menu, shell } from 'electron' import icon from '../../build/icon.png?asset' function createWindow(): void { @@ -50,10 +50,78 @@ function createWindow(): void { }) } +let settingsWindow: BrowserWindow | null = null + +function createSettingsWindow(): void { + if (settingsWindow) { + settingsWindow.show() + return + } + + settingsWindow = new BrowserWindow({ + width: 300, + height: 400, + show: false, + webPreferences: { + preload: join(import.meta.dirname, '..', 'preload', 'index.js'), + sandbox: false, + }, + }) + + settingsWindow.on('ready-to-show', () => { + settingsWindow?.show() + }) + + settingsWindow.webContents.setWindowOpenHandler((details) => { + shell.openExternal(details.url) + return { action: 'deny' } + }) + + settingsWindow.on('close', () => { + settingsWindow = null + }) + + settingsWindow.show() + + if (is.dev && env.ELECTRON_RENDERER_URL) { + settingsWindow.loadURL(join(env.ELECTRON_RENDERER_URL, '#/settings')) + } + else { + settingsWindow.loadFile(join(import.meta.dirname, '..', '..', 'out', 'renderer', 'index.html'), { + hash: '/settings', + }) + } +} + // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.whenReady().then(() => { + // Menu + const menu = Menu.buildFromTemplate([ + { + label: 'airi', + role: 'appMenu', + submenu: [ + { + role: 'about', + }, + { + role: 'toggleDevTools', + }, + { + label: 'Settings', + click: () => createSettingsWindow(), + }, + { + label: 'Quit', + click: () => app.quit(), + }, + ], + }, + ]) + Menu.setApplicationMenu(menu) + // Set app user model id for windows electronApp.setAppUserModelId('com.github.moeru-ai.airi-tamagotchi') @@ -65,7 +133,21 @@ app.whenReady().then(() => { }) // IPC test - ipcMain.on('quit', () => app.quit()) + // TODO: i18n + ipcMain.on('quit', () => { + dialog.showMessageBox({ + type: 'info', + title: 'Quit', + message: 'Are you sure you want to quit?', + buttons: ['Quit', 'Cancel'], + }).then((result) => { + if (result.response === 0) { + app.quit() + } + }) + }) + + ipcMain.on('open-settings', () => createSettingsWindow()) createWindow() diff --git a/packages/stage-tamagotchi/src/renderer/src/App.vue b/packages/stage-tamagotchi/src/renderer/src/App.vue index 338963b..a78fb6f 100644 --- a/packages/stage-tamagotchi/src/renderer/src/App.vue +++ b/packages/stage-tamagotchi/src/renderer/src/App.vue @@ -1,49 +1,7 @@ - - - - - - + - - diff --git a/packages/stage-tamagotchi/src/renderer/src/components/InteractiveArea.vue b/packages/stage-tamagotchi/src/renderer/src/components/InteractiveArea.vue index 830acd6..6c58695 100644 --- a/packages/stage-tamagotchi/src/renderer/src/components/InteractiveArea.vue +++ b/packages/stage-tamagotchi/src/renderer/src/components/InteractiveArea.vue @@ -7,11 +7,9 @@ import { useChatStore, useSettings } from '@proj-airi/stage-ui/stores' // import { useDevicesList } from '@vueuse/core' import { storeToRefs } from 'pinia' -import { DrawerContent, DrawerPortal, DrawerRoot, DrawerTrigger } from 'vaul-vue' import { onMounted, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import TamagotchiChatHistory from './ChatHistory.vue' -import TamagotchiSettings from './Settings.vue' const messageInput = ref('') const listening = ref(false) @@ -72,6 +70,10 @@ function handleTranscription(_buffer: Float32Array) { // selectedAudioDevice.value = found // } +function openSettings() { + window.electron.ipcRenderer.send('open-settings') +} + watch(isAudioInputOn, async (value) => { if (value === 'false') { destroy() @@ -103,26 +105,16 @@ onMounted(() => { @submit="handleSend" /> - - - - - - - - - - - - + + + diff --git a/packages/stage-tamagotchi/src/renderer/src/main.ts b/packages/stage-tamagotchi/src/renderer/src/main.ts index 6dc2089..40a391b 100644 --- a/packages/stage-tamagotchi/src/renderer/src/main.ts +++ b/packages/stage-tamagotchi/src/renderer/src/main.ts @@ -4,8 +4,11 @@ import { MotionPlugin } from '@vueuse/motion' import { createPinia } from 'pinia' import { createApp } from 'vue' import { createI18n } from 'vue-i18n' -import App from './App.vue' +import { createRouter, createWebHashHistory } from 'vue-router' + +import { routes } from 'vue-router/auto-routes' +import App from './App.vue' import '@unocss/reset/tailwind.css' import 'uno.css' import './main.css' @@ -22,9 +25,15 @@ const i18n = createI18n({ }, }) +const router = createRouter({ + history: createWebHashHistory(), + routes, +}) + createApp(App) .use(MotionPlugin) .use(autoAnimatePlugin) + .use(router) .use(pinia) .use(i18n) .use(Tres) diff --git a/packages/stage-tamagotchi/src/renderer/src/pages/index.vue b/packages/stage-tamagotchi/src/renderer/src/pages/index.vue new file mode 100644 index 0000000..f179e9a --- /dev/null +++ b/packages/stage-tamagotchi/src/renderer/src/pages/index.vue @@ -0,0 +1,49 @@ + + + + + + + + + + + + diff --git a/packages/stage-tamagotchi/src/renderer/src/components/Settings.vue b/packages/stage-tamagotchi/src/renderer/src/pages/settings.vue similarity index 92% rename from packages/stage-tamagotchi/src/renderer/src/components/Settings.vue rename to packages/stage-tamagotchi/src/renderer/src/pages/settings.vue index 731d4bb..3567553 100644 --- a/packages/stage-tamagotchi/src/renderer/src/components/Settings.vue +++ b/packages/stage-tamagotchi/src/renderer/src/pages/settings.vue @@ -65,10 +65,14 @@ onMounted(async () => { supportedModels.value = await models(openAiApiBaseURL.value, openAiApiKey.value) }) + +function handleQuit() { + window.electron.ipcRenderer.send('quit') +} - + Settings @@ -197,5 +201,23 @@ onMounted(async () => { + + Other + + + + + + Quit + + + + + + + diff --git a/packages/stage-tamagotchi/src/typed-router.d.ts b/packages/stage-tamagotchi/src/typed-router.d.ts new file mode 100644 index 0000000..9b93e31 --- /dev/null +++ b/packages/stage-tamagotchi/src/typed-router.d.ts @@ -0,0 +1,24 @@ +/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️ +// It's recommended to commit this file. +// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry. + +declare module 'vue-router/auto-routes' { + import type { + RouteRecordInfo, + ParamValue, + ParamValueOneOrMore, + ParamValueZeroOrMore, + ParamValueZeroOrOne, + } from 'vue-router' + + /** + * Route name map generated by unplugin-vue-router + */ + export interface RouteNamedMap { + '/': RouteRecordInfo<'/', '/', Record, Record>, + '/settings': RouteRecordInfo<'/settings', '/settings', Record, Record>, + } +} diff --git a/packages/stage-tamagotchi/tsconfig.node.json b/packages/stage-tamagotchi/tsconfig.node.json index e513b7b..db7959a 100644 --- a/packages/stage-tamagotchi/tsconfig.node.json +++ b/packages/stage-tamagotchi/tsconfig.node.json @@ -5,9 +5,9 @@ "types": ["electron-vite/node"] }, "include": [ + "renderer.vite.config.*", "electron.vite.config.*", "src/main/**/*", - "src/preload/**/*", - "../../vite-plugins/**/*" + "src/preload/**/*" ] }