Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,14 @@ apps/stage-tamagotchi/electron.vite.config.*.mjs

# Tools - Obsidian
.obsidian/

# Visual Chat
.visual-chat-tunnel.json
.visual-chat-public-endpoints.json

# Local editor / browser runtime artifacts
.cursor/*
!.cursor/commands/
.cursor/commands/*
!.cursor/commands/deslop.md
.tmp-edge-headless/
2 changes: 2 additions & 0 deletions apps/stage-tamagotchi/electron-builder.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export default {
asar: true,
asarUnpack: [
'**/*.node',
'node_modules/@proj-airi/visual-chat-gateway/**',
'node_modules/@proj-airi/visual-chat-worker-minicpmo/**',
],
extraMetadata: {
name: 'ai.moeru.airi',
Expand Down
13 changes: 13 additions & 0 deletions apps/stage-tamagotchi/electron.vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { join, resolve } from 'node:path'
import { env } from 'node:process'

import VueI18n from '@intlify/unplugin-vue-i18n/vite'
import templateCompilerOptions from '@tresjs/core/template-compiler-options'
Expand All @@ -18,6 +19,13 @@ import { defineConfig } from 'electron-vite'

const stageUIAssetsRoot = resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-ui', 'src', 'assets'))
const sharedCacheDir = resolve(join(import.meta.dirname, '..', '..', '.cache'))
const additionalAllowedRemoteHosts = (env.AIRI_VISUAL_CHAT_ALLOWED_HOSTS || '')
.split(',')
.map(host => host.trim())
.filter(Boolean)
const rendererAllowedHosts: true | string[] = additionalAllowedRemoteHosts.length > 0
? [...new Set(['.trycloudflare.com', ...additionalAllowedRemoteHosts])]
: true

export default defineConfig({
main: {
Expand Down Expand Up @@ -136,10 +144,15 @@ export default defineConfig({
'@proj-airi/stage-ui': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-ui', 'src')),
'@proj-airi/stage-pages': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-pages', 'src')),
'@proj-airi/stage-shared': resolve(join(import.meta.dirname, '..', '..', 'packages', 'stage-shared', 'src')),
'@proj-airi/visual-chat-shared/electron': resolve(join(import.meta.dirname, '..', '..', 'packages', 'visual-chat-shared', 'src', 'electron.ts')),
},
},

server: {
host: '0.0.0.0',
port: 5174,
strictPort: true,
allowedHosts: rendererAllowedHosts,
fs: {
// To mute errors like:
// The request id ".../node_modules/@fontsource/sniglet/files/sniglet-latin-400-normal.woff" is outside of Vite serving allow list.
Expand Down
8 changes: 6 additions & 2 deletions apps/stage-tamagotchi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"app:dev": "pnpm run dev",
"app:build": "pnpm run build",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"build": "electron-vite build",
"dev": "tsx scripts/electron-vite-dev.ts",
"build": "pnpm -F @proj-airi/visual-chat-ops build && pnpm -F @proj-airi/visual-chat-gateway build && pnpm -F @proj-airi/visual-chat-worker-minicpmo build && electron-vite build",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "pnpm run build && electron-builder --dir",
"build:win": "pnpm run build && electron-builder --win",
Expand Down Expand Up @@ -66,6 +66,10 @@
"@proj-airi/stage-ui-live2d": "workspace:^",
"@proj-airi/stage-ui-three": "workspace:^",
"@proj-airi/ui": "workspace:^",
"@proj-airi/visual-chat-gateway": "workspace:^",
"@proj-airi/visual-chat-ops": "workspace:^",
"@proj-airi/visual-chat-shared": "workspace:^",
"@proj-airi/visual-chat-worker-minicpmo": "workspace:^",
"@shikijs/markdown-it": "^4.0.2",
"@tresjs/cientos": "^5.6.0",
"@tresjs/core": "^5.7.0",
Expand Down
65 changes: 65 additions & 0 deletions apps/stage-tamagotchi/scripts/electron-vite-dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import process from 'node:process'

import { spawn } from 'node:child_process'

const env = { ...process.env }

// Some Windows setups leak `ELECTRON_RUN_AS_NODE=1` into child processes.
// When that reaches `electron-vite dev`, Electron starts as plain Node.js,
// so imports like `import { BrowserWindow } from "electron"` fail at runtime.
delete env.ELECTRON_RUN_AS_NODE

const child = spawn('pnpm', ['exec', 'electron-vite', 'dev'], {
cwd: process.cwd(),
env,
stdio: 'inherit',
shell: true,
})
Comment on lines +12 to +17
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

On non-Windows platforms, process.kill(-child.pid!, 'SIGTERM') is used to terminate the entire process group. For this to work correctly, the child process must be spawned as a process group leader using the detached: true option. Without it, child.pid is not a valid process group ID, and the signal might fail or be sent to the parent's process group.

Suggested change
const child = spawn('pnpm', ['exec', 'electron-vite', 'dev'], {
cwd: process.cwd(),
env,
stdio: 'inherit',
shell: true,
})
const child = spawn('pnpm', ['exec', 'electron-vite', 'dev'], {
cwd: process.cwd(),
env,
stdio: 'inherit',
shell: true,
detached: process.platform !== 'win32',
})


let exiting = false

function shutdown() {
if (exiting)
return
exiting = true

if (process.platform === 'win32') {
// On Windows, child.kill() only kills the shell wrapper, not the
// process tree underneath. `taskkill /T` terminates the whole tree.
try {
spawn('taskkill', ['/T', '/F', '/PID', String(child.pid)], { stdio: 'ignore' })
}
catch {}
}
else {
try {
// Negative PID sends signal to the entire process group
process.kill(-child.pid!, 'SIGTERM')
}
catch {
child.kill('SIGTERM')
}
}

setTimeout(() => process.exit(1), 2000).unref()
}

process.on('SIGINT', shutdown)
process.on('SIGTERM', shutdown)

child.once('error', (error) => {
console.error(error)
process.exit(1)
})

child.once('exit', (code, signal) => {
if (exiting)
return process.exit(code ?? 0)

if (signal) {
process.kill(process.pid, signal)
return
}

process.exit(code ?? 1)
})
2 changes: 2 additions & 0 deletions apps/stage-tamagotchi/src/main/services/electron/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { app, shell } from 'electron'
import { isLinux, isMacOS, isWindows } from 'std-env'

import { electron, electronAppOpenUserDataFolder, electronAppQuit } from '../../../shared/eventa'
import { createVisualChatDesktopService } from './visual-chat'

export function createAppService(params: { context: ReturnType<typeof createContext>['context'], window: BrowserWindow }) {
defineInvokeHandler(params.context, electron.app.isMacOS, () => isMacOS)
defineInvokeHandler(params.context, electron.app.isWindows, () => isWindows)
defineInvokeHandler(params.context, electron.app.isLinux, () => isLinux)
createVisualChatDesktopService(params)
defineInvokeHandler(params.context, electronAppOpenUserDataFolder, async () => {
const path = app.getPath('userData')
const openResult = await shell.openPath(path)
Expand Down
1 change: 1 addition & 0 deletions apps/stage-tamagotchi/src/main/services/electron/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './app'
export * from './auto-updater'
export * from './powerMonitor'
export * from './screen'
export * from './visual-chat'
export * from './window'
Loading
Loading