Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
05764a0
feat: move translation to web worker and update transformers.js
jeremio Jul 3, 2025
70fccbb
Update README.md
naeruru Jul 7, 2025
f1e8879
update settings tooltips (#60)
fuwako Jul 7, 2025
ad8311b
update number input fields (#61)
fuwako Jul 9, 2025
62c7f58
add custom icon import system
naeruru Jul 9, 2025
ad4ce64
fix import error of translation worker file
naeruru Jul 11, 2025
354628e
version 0.5.0
naeruru Jul 11, 2025
1d785b3
update deps
jeremio Jul 22, 2025
0520c32
Merge branch 'main' into feature/transformers-v3
jeremio Jul 22, 2025
7e1a272
fix: correct singleton race condition in translation worker
jeremio Mar 17, 2026
9b172c2
fix: fallback to wasm when webgpu is unavailable in translation worker
jeremio Mar 17, 2026
641ae87
fix: handle translation errors to prevent logs from getting stuck
jeremio Mar 17, 2026
895fb27
fix: show model download progress regardless of device
jeremio Mar 17, 2026
a77fb61
fix: migrate per-trigger OSC ports and fix invalid fallback value
jeremio Mar 17, 2026
d97a04f
fix: initialize new OSC trigger port to 9000 instead of NaN
jeremio Mar 17, 2026
b849a73
chore: remove dead transformers-translate-render IPC listener
jeremio Mar 17, 2026
c2bca5c
fix: remove mimiuchi-websocketserver-close listener on unmount
jeremio Mar 19, 2026
7cdcf76
chore: remove incomplete Whisper.ts
jeremio Mar 19, 2026
49eb063
chore: remove unused webfontloader dependency
jeremio Mar 19, 2026
201ff13
chore: fix 25 npm audit vulnerabilities
jeremio Mar 19, 2026
45b4280
chore: remove redundant 'declare const window: any' declarations
jeremio Mar 19, 2026
c0c01c6
chore: update minor dependencies and vue-router to v5
jeremio Mar 19, 2026
1de1a5d
chore: upgrade eslint to v10 and @antfu/eslint-config to v7
jeremio Mar 19, 2026
b2eafc5
update @mdi/font 5.9.55 → 7.4.47
jeremio Mar 19, 2026
ab28931
update node-osc 9.1.7 → 11.3.0, remove @types/node-osc
jeremio Mar 19, 2026
01d5929
update electron-store 10.1.0 → 11.0.2
jeremio Mar 19, 2026
0a3c120
fix SpeechRecognitionResultList.at() not a function
jeremio Mar 19, 2026
f368edb
fix deprecated Vuetify theme API and SpeechRecognition .at()
jeremio Mar 19, 2026
3757a87
update electron 37.2.3 → 41.0.3
jeremio Mar 19, 2026
2b2b8d5
fix dead code, memory leak, JSON construction, and self-reference
jeremio Mar 19, 2026
625884e
fix IPC listener registered on every re-render in Footer.vue
jeremio Mar 19, 2026
2658eb7
fix TTS double-fire, TikTok retry, and wrong log paused on new-line d…
jeremio Mar 19, 2026
fbb8bfc
fix stt.type stored as object and UDP socket leak in emit_osc
jeremio Mar 19, 2026
d43b1b0
fix race condition in Electron text queue
jeremio Mar 19, 2026
41c5d84
move log scroll to Home.vue and fix broken element ID
jeremio Mar 19, 2026
debf466
rename connection_count to connections_count in default store
jeremio Mar 19, 2026
2b2694a
remove dead code and redundant patterns (JetBrains inspection)
jeremio Mar 19, 2026
3dbc894
encapsulate wait_interval in logs store
jeremio Mar 19, 2026
8f92f83
remove redundant instanceof OBSWebSocket checks in reconnect_websocket
jeremio Mar 19, 2026
11f4c0c
merge origin2/main into feature/transformers-v3
jeremio Mar 19, 2026
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
4 changes: 2 additions & 2 deletions .vscode/.debug.script.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { spawn } from 'node:child_process'
import fs from 'node:fs'
import { createRequire } from 'node:module'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { spawn } from 'node:child_process'

const pkg = createRequire(import.meta.url)('../package.json')
const __dirname = path.dirname(fileURLToPath(import.meta.url))
Expand Down
89 changes: 49 additions & 40 deletions electron/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { createRequire } from 'node:module'
import * as os from 'node:os'
import * as path from 'node:path'
import { fileURLToPath } from 'node:url'
import { Worker } from 'node:worker_threads'
import { app, BrowserWindow, ipcMain, shell } from 'electron'

import Store from 'electron-store'
Expand Down Expand Up @@ -33,7 +31,6 @@ const store = new Store<Schema>({
// const image = nativeImage.createFromPath(`${app.getAppPath()}/public/logo-256x256.png`)
// app.dock?.setIcon(image)

const require = createRequire(import.meta.url)
const __dirname = path.dirname(fileURLToPath(import.meta.url))

// The built directory structure
Expand Down Expand Up @@ -119,26 +116,30 @@ async function createWindow() {
})
// win.webContents.on('will-navigate', (event, url) => { }) #344

win.on('maximize', () => win.webContents.send('maximized_state', true))
win.on('unmaximize', () => win.webContents.send('maximized_state', false))
win.on('close', () => {
const update_obj = {}
Object.assign(update_obj, { isMaximized: win.isMaximized() }, win.getNormalBounds())
store.set('win_bounds', update_obj)
})
if (win) {
win.on('maximize', () => {
if (win)
win.webContents.send('maximized_state', true)
})
win.on('unmaximize', () => {
if (win)
win.webContents.send('maximized_state', false)
})
win.on('close', () => {
if (win) {
const update_obj = {}
Object.assign(update_obj, { isMaximized: win.isMaximized() }, win.getNormalBounds())
store.set('win_bounds', update_obj)
}
})
}
win.webContents.once('dom-ready', () => {
// the window is never maximized on load
// if (window_config.isMaximized)
// win.webContents.send('maximized_state', true)
})
}

const transformersWorkerPath = `file://${path.join(__dirname, 'worker', 'translation.js')}`
const transformersWorker = new Worker(new URL(transformersWorkerPath, import.meta.url))

transformersWorker.on('message', (x) => {
win.webContents.send('transformers-translate-render', x)
})

app.whenReady().then(() => {
if (store.get('auto-open-web-app-on-launch')) {
Expand Down Expand Up @@ -196,11 +197,13 @@ ipcMain.on('close_app', () => {
})
// event for toggling maximized
ipcMain.on('toggle_maximize', () => {
win.isMaximized() ? win.unmaximize() : win.maximize()
if (win)
win.isMaximized() ? win.unmaximize() : win.maximize()
})
// event for minimizing
ipcMain.on('minimize', () => {
win.minimize()
if (win)
win.minimize()
})

// event for text typing indicator
Expand All @@ -209,46 +212,58 @@ ipcMain.on('typing-text-event', (event, args) => {
})

// event for sending text
let text_queue = []
const text_queue: string[] = []
let queue_running = false
ipcMain.on('send-text-event', (event, args) => {
args = JSON.parse(args)
const new_text = args.transcript.includes(' ') ? args.transcript.match(/.{1,140}(\s|$)/g) : args.transcript.match(/.{1,140}/g)
text_queue = [...text_queue, ...new_text]
if (text_queue.length >= 1)
empty_queue(text_queue, args.hide_ui, args.sfx)
if (new_text)
text_queue.push(...new_text)
if (text_queue.length >= 1 && !queue_running) {
queue_running = true
empty_queue(text_queue, args.hide_ui, args.sfx, 8, () => {
queue_running = false
})
}
})

// event for sending osc messages
ipcMain.on('send-osc-message', (event, args) => {
emit_osc([args.route, args.value], args.ip, args.port)
})

let wsserver: WebSocketServer = null
let wsserver: WebSocketServer | null = null
// websocket events
ipcMain.on('start-mimiuchi-websocketserver', (event, args) => {
wsserver = new WebSocketServer({ port: args })

initialize_wsserver(win, wsserver)
.then(() => {
win.webContents.send('mimiuchi-websocketserver-started')
})
.catch((error) => {
win.webContents.send('mimiuchi-websocketserver-error', error)
})
if (win) {
initialize_wsserver(win, wsserver)
.then(() => {
if (win)
win.webContents.send('mimiuchi-websocketserver-started')
})
.catch((error) => {
if (win)
win.webContents.send('mimiuchi-websocketserver-error', error)
})
}
})

ipcMain.on('close-mimiuchi-websocketserver', () => {
if (!wsserver) return

win.webContents.send('mimiuchi-websocketserver-close')
if (win)
win.webContents.send('mimiuchi-websocketserver-close')

wsserver.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN)
client.close(1001, 'mimiuchi WebSocket server shut down.')
})

wsserver.close(() => {
win.webContents.send('mimiuchi-websocketserver-closed')
if (win)
win.webContents.send('mimiuchi-websocketserver-closed')
})

wsserver.removeAllListeners()
Expand All @@ -258,7 +273,8 @@ ipcMain.on('close-mimiuchi-websocketserver', () => {

ipcMain.on('update-check', async () => {
const latest = await check_update()
win.webContents.send('update-check', latest)
if (win)
win.webContents.send('update-check', latest)
})

// Setting: Open web app on app launch
Expand All @@ -279,11 +295,4 @@ ipcMain.on('delete-auto-open-web-app-on-launch', () => {
// Footer (user submission)
// → Speech Store
// → [Condition: translations are enabled]
// → Electron ('transformers-translate')
// → Worker (worker thread)
// → Electron ('transformers-translate-output')
// → Footer ('transformers-translate-render')

ipcMain.on('transformers-translate', async (event, args) => {
transformersWorker.postMessage({ type: 'transformers-translate', data: args })
})
2 changes: 1 addition & 1 deletion electron/main/modules/check_update.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export async function check_update() {
const response = await fetch('https://github.com/naeruru/mimiuchi/releases/latest')
const parsed_url = response.url.split('/')
return parsed_url[parsed_url.length - 1]
return parsed_url.at(-1)
}
14 changes: 9 additions & 5 deletions electron/main/modules/osc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ import { Bundle, Client } from 'node-osc'
* ip (string): ip to send bundle to
* port (number): port to send bundle to
*/
export function emit_osc(value: any, ip: string = '127.0.0.1', port: number = 9000) {
export async function emit_osc(value: any, ip: string = '127.0.0.1', port: number = 9000) {
const bundle = new Bundle(value)
const client = new Client(ip, port)
client.send(bundle)
await client.send(bundle)
await client.close()
console.log(`${value[0]} -> ${value[1]}`)
}

export function empty_queue(queue: any, hide_ui: boolean = true, sfx: boolean = true, seconds: number = 8) {
export function empty_queue(queue: any, hide_ui: boolean = true, sfx: boolean = true, seconds: number = 8, onDone?: () => void) {
emit_osc(['/chatbox/input', queue.length > 1 ? `${queue[0]} ...` : queue[0], hide_ui, sfx])
queue.shift()
if (queue.length) {
setTimeout(() => emit_osc(['/chatbox/typing', true]), 400)
setTimeout(() => empty_queue(queue, hide_ui, sfx, seconds), seconds * 1000)
setTimeout(emit_osc, 400, ['/chatbox/typing', true])
setTimeout(empty_queue, seconds * 1000, queue, hide_ui, sfx, seconds, onDone)
}
else {
onDone?.()
}
}
49 changes: 0 additions & 49 deletions electron/main/worker/translation.ts

This file was deleted.

6 changes: 3 additions & 3 deletions electron/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contextBridge.exposeInMainWorld('ipcRenderer', {
return ipcRenderer.invoke(channel, ...omit)
},
removeListener(...args: Parameters<typeof ipcRenderer.invoke>) {
const [channel, ...omit] = args
const [channel, ..._omit] = args
return ipcRenderer.removeAllListeners(channel)
},

Expand All @@ -45,12 +45,12 @@ function domReady(condition: DocumentReadyState[] = ['complete', 'interactive'])

const safeDOM = {
append(parent: HTMLElement, child: HTMLElement) {
if (!Array.from(parent.children).find(e => e === child)) {
if (!Array.from(parent.children).includes(child)) {
return parent.appendChild(child)
}
},
remove(parent: HTMLElement, child: HTMLElement) {
if (Array.from(parent.children).find(e => e === child)) {
if (Array.from(parent.children).includes(child)) {
return parent.removeChild(child)
}
},
Expand Down
30 changes: 27 additions & 3 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,35 @@ import antfu from '@antfu/eslint-config'

export default antfu({
rules: {
'vue/block-order': ['error', {
order: [['template', 'script'], 'style'],
}],
'no-console': 'off',
'curly': 0,
'antfu/if-newline': 0,
// Markdown structure is intentional in README
'markdown/heading-increment': 'off',
'markdown/no-multiple-h1': 'off',
'markdown/require-alt-text': 'off',
// .vscode config files use escape sequences that are flagged incorrectly
'jsonc/no-useless-escape': 'off',
// Too opinionated for this codebase
'e18e/prefer-static-regex': 'off',
},
}, {
files: ['**/*.vue'],
rules: {
'vue/block-order': ['error', {
order: [['template', 'script'], 'style'],
}],
},
}, {
// process global is always available in Node.js/Vite config context
files: ['electron/**/*.ts', 'electron/**/*.mjs', '.vscode/**/*.mjs', 'vite.config.ts', 'src/helpers/is_electron.ts'],
rules: {
'node/prefer-global/process': 'off',
},
}, {
// Web Worker: self is the correct global
files: ['src/workers/**/*.ts'],
rules: {
'no-restricted-globals': 'off',
},
})
Loading