feat(stage-tamagotchi): hot-reload i18n during Electron runtime

This commit is contained in:
Neko Ayaka
2026-03-05 03:25:24 +08:00
parent 242270a35b
commit a1ff12ced1
30 changed files with 439 additions and 88 deletions
@@ -46,6 +46,12 @@ export default defineConfig({
},
},
],
resolve: {
alias: {
'@proj-airi/i18n': resolve(join(import.meta.dirname, '..', '..', 'packages', 'i18n', 'src')),
},
},
},
preload: {
build: {
+2
View File
@@ -48,6 +48,7 @@
"@formkit/auto-animate": "^0.9.0",
"@guiiai/logg": "catalog:",
"@huggingface/transformers": "^3.8.1",
"@intlify/core": "catalog:",
"@modelcontextprotocol/sdk": "catalog:",
"@moeru/eventa": "^1.0.0-beta.1",
"@moeru/std": "catalog:",
@@ -84,6 +85,7 @@
"@xsai/stream-transcription": "0.4.0-beta.8",
"@xsai/tool": "catalog:",
"@xsai/utils-chat": "catalog:",
"alien-signals": "catalog:",
"animejs": "^4.3.6",
"colorjs.io": "^0.6.1",
"crossws": "^0.4.4",
@@ -0,0 +1,14 @@
import { object, optional, string } from 'valibot'
import { createConfig } from '../libs/electron/persistence'
export const globalAppConfigSchema = object({
language: optional(string(), 'en'),
})
export function createGlobalAppConfig() {
const config = createConfig('app', 'options.json', globalAppConfigSchema)
config.setup()
return config
}
+22 -9
View File
@@ -2,6 +2,8 @@ import { dirname } from 'node:path'
import { env, platform } from 'node:process'
import { fileURLToPath } from 'node:url'
import messages from '@proj-airi/i18n/locales'
import { electronApp, optimizer } from '@electron-toolkit/utils'
import { Format, LogLevel, setGlobalFormat, setGlobalLogLevel, useLogg } from '@guiiai/logg'
import { initScreenCaptureForMain } from '@proj-airi/electron-screen-capture/main'
@@ -13,8 +15,10 @@ import { isLinux } from 'std-env'
import icon from '../../resources/icon.png?asset'
import { openDebugger, setupDebugger } from './app/debugger'
import { createGlobalAppConfig } from './configs/global'
import { emitAppBeforeQuit, emitAppReady, emitAppWindowAllClosed } from './libs/bootkit/lifecycle'
import { setElectronMainDirname } from './libs/electron/location'
import { createI18n } from './libs/i18n'
import { setupServerChannel } from './services/airi/channel-server'
import { setupMcpStdioManager } from './services/airi/mcp-servers'
import { setupPluginHost } from './services/airi/plugins'
@@ -76,9 +80,15 @@ initScreenCaptureForMain()
app.whenReady().then(async () => {
injeca.setLogger(createLoggLogger(useLogg('injeca').useGlobalConfig()))
const appConfig = injeca.provide('configs:app', () => createGlobalAppConfig())
const electronApp = injeca.provide('host:electron:app', () => app)
const autoUpdater = injeca.provide('services:auto-updater', () => setupAutoUpdater())
const i18n = injeca.provide('libs:i18n', {
dependsOn: { appConfig },
build: ({ dependsOn }) => createI18n({ messages, locale: dependsOn.appConfig.get()?.language }),
})
const serverChannel = injeca.provide('modules:channel-server', {
dependsOn: { app: electronApp },
build: async () => setupServerChannel(),
@@ -97,40 +107,43 @@ app.whenReady().then(async () => {
const beatSync = injeca.provide('windows:beat-sync', () => setupBeatSync())
const devtoolsMarkdownStressWindow = injeca.provide('windows:devtools:markdown-stress', () => setupDevtoolsWindow())
const noticeWindow = injeca.provide('windows:notice', () => setupNoticeWindowManager())
const noticeWindow = injeca.provide('windows:notice', {
dependsOn: { i18n, serverChannel },
build: ({ dependsOn }) => setupNoticeWindowManager(dependsOn),
})
const widgetsManager = injeca.provide('windows:widgets', {
dependsOn: { serverChannel },
build: () => setupWidgetsWindowManager(),
dependsOn: { serverChannel, i18n },
build: ({ dependsOn }) => setupWidgetsWindowManager(dependsOn),
})
const aboutWindow = injeca.provide('windows:about', {
dependsOn: { autoUpdater },
dependsOn: { autoUpdater, i18n, serverChannel },
build: ({ dependsOn }) => setupAboutWindowReusable(dependsOn),
})
const chatWindow = injeca.provide('windows:chat', {
dependsOn: { widgetsManager, serverChannel, mcpStdioManager },
dependsOn: { widgetsManager, serverChannel, mcpStdioManager, i18n },
build: ({ dependsOn }) => setupChatWindowReusableFunc(dependsOn),
})
const settingsWindow = injeca.provide('windows:settings', {
dependsOn: { widgetsManager, beatSync, autoUpdater, devtoolsMarkdownStressWindow, serverChannel, mcpStdioManager },
dependsOn: { widgetsManager, beatSync, autoUpdater, devtoolsMarkdownStressWindow, serverChannel, mcpStdioManager, i18n },
build: async ({ dependsOn }) => setupSettingsWindowReusableFunc(dependsOn),
})
const mainWindow = injeca.provide('windows:main', {
dependsOn: { settingsWindow, chatWindow, widgetsManager, noticeWindow, beatSync, autoUpdater, serverChannel, mcpStdioManager },
dependsOn: { settingsWindow, chatWindow, widgetsManager, noticeWindow, beatSync, autoUpdater, serverChannel, mcpStdioManager, i18n },
build: async ({ dependsOn }) => setupMainWindow(dependsOn),
})
const captionWindow = injeca.provide('windows:caption', {
dependsOn: { mainWindow, serverChannel },
dependsOn: { mainWindow, serverChannel, i18n },
build: async ({ dependsOn }) => setupCaptionWindowManager(dependsOn),
})
const tray = injeca.provide('app:tray', {
dependsOn: { mainWindow, settingsWindow, captionWindow, widgetsWindow: widgetsManager, beatSyncBgWindow: beatSync, aboutWindow },
dependsOn: { mainWindow, settingsWindow, captionWindow, widgetsWindow: widgetsManager, beatSyncBgWindow: beatSync, aboutWindow, i18n },
build: async ({ dependsOn }) => setupTray(dependsOn),
})
@@ -49,12 +49,19 @@ function parseWithSchema<TSchema extends PersistedSchema>(
return { issues: result.issues }
}
export interface Config<TSchema extends PersistedSchema> {
setup: () => ConfigDiagnostics<InferOutput<TSchema>>
get: () => InferOutput<TSchema> | undefined
update: (newData: InferOutput<TSchema>) => void
getDiagnostics: () => ConfigDiagnostics<InferOutput<TSchema>> | undefined
}
export function createConfig<TSchema extends PersistedSchema>(
namespace: string,
filename: string,
schema: TSchema,
options?: CreateConfigOptions<InferOutput<TSchema>>,
) {
): Config<TSchema> {
const key = `${namespace}:${filename}`
const autoHeal = options?.autoHeal ?? Boolean(options?.default)
@@ -0,0 +1,184 @@
import type {
CoreOptions,
IsEmptyObject,
LocaleDetector,
NamedValue,
PickupPaths,
RemovedIndexResources,
TranslateOptions,
} from '@intlify/core'
import { useLogg } from '@guiiai/logg'
import { createCoreContext, translate } from '@intlify/core'
import { effect, signal } from 'alien-signals'
import { isString } from 'es-toolkit'
type ResolveResourceKeys<
// eslint-disable-next-line ts/no-empty-object-type
Schema extends Record<string, any> = {},
// eslint-disable-next-line ts/no-empty-object-type
DefineLocaleMessageSchema extends Record<string, any> = {},
DefinedLocaleMessage extends
RemovedIndexResources<DefineLocaleMessageSchema> = RemovedIndexResources<DefineLocaleMessageSchema>,
SchemaPaths = IsEmptyObject<Schema> extends false
? PickupPaths<{ [K in keyof Schema]: Schema[K] }>
: never,
DefineMessagesPaths = IsEmptyObject<DefinedLocaleMessage> extends false
? PickupPaths<{
[K in keyof DefinedLocaleMessage]: DefinedLocaleMessage[K]
}>
: never,
> = SchemaPaths | DefineMessagesPaths
interface TranslationFunction<
// eslint-disable-next-line ts/no-empty-object-type
Schema extends Record<string, any> = {},
// eslint-disable-next-line ts/no-empty-object-type
DefineLocaleMessageSchema extends Record<string, any> = {},
ResourceKeys = ResolveResourceKeys<Schema, DefineLocaleMessageSchema>,
> {
/**
* @param {Key | ResourceKeys} key - A translation key
* @returns {string} A translated message, if the key is not found, return the key
*/
<Key extends string>(key: Key | ResourceKeys): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {number} plural - A plural choice number
* @returns {string} A translated message, if the key is not found, return the key
*/
<Key extends string>(key: Key | ResourceKeys, plural: number): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {number} plural - A plural choice number
* @param {TranslateOptions} options - A translate options, about details see {@link TranslateOptions}
* @returns {string} A translated message, if the key is not found, return the key
*/
<Key extends string>(key: Key | ResourceKeys, plural: number, options: TranslateOptions): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {string} defaultMsg - A default message, if the key is not found
* @returns {string} A translated message, if the key is not found, return the `defaultMsg` argument
*/
<Key extends string>(key: Key | ResourceKeys, defaultMsg: string): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {string} defaultMsg - A default message, if the key is not found
* @param {TranslateOptions} options - A translate options, about details see {@link TranslateOptions}
* @returns {string} A translated message, if the key is not found, return the `defaultMsg` argument
*/
<Key extends string>(
key: Key | ResourceKeys,
defaultMsg: string,
options: TranslateOptions
): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {unknown[]} list - A list for list interpolation
* @returns {string} A translated message, if the key is not found, return the key
*/
<Key extends string>(key: Key | ResourceKeys, list: unknown[]): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {unknown[]} list - A list for list interpolation
* @param {number} plural - A plural choice number
* @returns {string} A translated message, if the key is not found, return the key
*/
<Key extends string>(key: Key | ResourceKeys, list: unknown[], plural: number): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {unknown[]} list - A list for list interpolation
* @param {string} defaultMsg - A default message, if the key is not found
* @returns {string} A translated message, if the key is not found, return the `defaultMsg` argument
*/
<Key extends string>(key: Key | ResourceKeys, list: unknown[], defaultMsg: string): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {unknown[]} list - A list for list interpolation
* @param {TranslateOptions} options - A translate options, about details see {@link TranslateOptions}
* @returns {string} A translated message, if the key is not found, return the key
*/
<Key extends string>(key: Key | ResourceKeys, list: unknown[], options: TranslateOptions): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {NamedValue} named - A named value for named interpolation
* @returns {string} A translated message, if the key is not found, return the key
*/
<Key extends string>(key: Key | ResourceKeys, named: NamedValue): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {NamedValue} named - A named value for named interpolation
* @param {number} plural - A plural choice number
* @returns {string} A translated message, if the key is not found, return the key
*/
<Key extends string>(key: Key | ResourceKeys, named: NamedValue, plural: number): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {NamedValue} named - A named value for named interpolation
* @param {string} defaultMsg - A default message, if the key is not found
* @returns {string} A translated message, if the key is not found, return the `defaultMsg` argument
*/
<Key extends string>(key: Key | ResourceKeys, named: NamedValue, defaultMsg: string): string
/**
* @param {Key | ResourceKeys} key - A translation key
* @param {NamedValue} named - A named value for named interpolation
* @param {TranslateOptions} options - A translate options, about details see {@link TranslateOptions}
* @returns {string} A translated message, if the key is not found, return the key
*/
<Key extends string>(
key: Key | ResourceKeys,
named: NamedValue,
options: TranslateOptions
): string
}
export interface I18n<Schema extends Record<string, any> = Record<string, any>> {
t: TranslationFunction<Schema>
locale:
(() => (string | LocaleDetector<any[]> | undefined)) | ((value: string | LocaleDetector<any[]> | undefined) => void)
}
export function createI18n<Schema extends Record<string, any> = Record<string, any>>(options: CoreOptions): I18n<Schema> {
const log = useLogg('i18n').useGlobalConfig()
const locale = signal(options.locale)
const context = createCoreContext({
fallbackLocale: options.fallbackLocale,
fallbackWarn: false,
missingWarn: false,
warnHtmlMessage: false,
fallbackFormat: true,
...options,
})
const t: TranslationFunction<Schema> = (
key: string,
...args: unknown[]
) => {
if (context == null) {
log.error('cannot initialize core context for i18n')
return key
}
const ret = Reflect.apply(translate, null, [context, key, ...args])
return isString(ret) ? ret : key
}
effect(() => {
locale()
if (context != null) {
const l = locale()
if (l != null) {
context.locale = l
}
}
})
return {
t,
locale,
}
}
@@ -0,0 +1,28 @@
import type { LocaleDetector } from '@intlify/core'
import type { createContext } from '@moeru/eventa/adapters/electron/main'
import type { BrowserWindow } from 'electron'
import type { ProvidedBy } from 'injeca'
import type { globalAppConfigSchema } from '../../../configs/global'
import type { Config } from '../../../libs/electron/persistence'
import type { I18n } from '../../../libs/i18n'
import { defineInvokeHandler } from '@moeru/eventa'
import { injeca } from 'injeca'
import { i18nGetLocale, i18nSetLocale } from '../../../../shared/eventa'
export async function createI18nService(params: { context: ReturnType<typeof createContext>['context'], window: BrowserWindow, i18n: I18n }) {
const { config } = await injeca.resolve({ config: 'configs:app' } as { config: ProvidedBy<Config<typeof globalAppConfigSchema>> })
params.i18n.locale(config.get()?.language || 'en')
defineInvokeHandler(params.context, i18nSetLocale, (locale) => {
config.update({ ...config.get(), language: locale })
params.i18n.locale(locale)
})
defineInvokeHandler(params.context, i18nGetLocale, () => {
const locale = params.i18n.locale as () => string | LocaleDetector<any[]> | undefined
return locale()
})
}
@@ -1,3 +1,5 @@
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import type { AutoUpdater } from '../../services/electron/auto-updater'
import { join, resolve } from 'node:path'
@@ -10,7 +12,11 @@ import { baseUrl, getElectronMainDirname, load, withHashRoute } from '../../libs
import { createReusableWindow } from '../../libs/electron/window-manager'
import { setupAboutWindowElectronInvokes } from './rpc/index.electron'
export function setupAboutWindowReusable(params: { autoUpdater: AutoUpdater }) {
export function setupAboutWindowReusable(params: {
autoUpdater: AutoUpdater
i18n: I18n
serverChannel: ServerChannel
}) {
return createReusableWindow(async () => {
const window = new BrowserWindow({
title: 'About AIRI',
@@ -35,7 +41,12 @@ export function setupAboutWindowReusable(params: { autoUpdater: AutoUpdater }) {
await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/about'))
setupAboutWindowElectronInvokes({ window, autoUpdater: params.autoUpdater })
await setupAboutWindowElectronInvokes({
window,
autoUpdater: params.autoUpdater,
i18n: params.i18n,
serverChannel: params.serverChannel,
})
return window
}).getWindow
@@ -1,13 +1,21 @@
import type { BrowserWindow } from 'electron'
import type { I18n } from '../../../libs/i18n'
import type { ServerChannel } from '../../../services/airi/channel-server'
import type { AutoUpdater } from '../../../services/electron/auto-updater'
import { createContext } from '@moeru/eventa/adapters/electron/main'
import { ipcMain } from 'electron'
import { createAutoUpdaterService } from '../../../services/electron'
import { setupBaseWindowElectronInvokes } from '../../shared/window'
export function setupAboutWindowElectronInvokes(params: { window: BrowserWindow, autoUpdater: AutoUpdater }) {
export async function setupAboutWindowElectronInvokes(params: {
window: BrowserWindow
autoUpdater: AutoUpdater
i18n: I18n
serverChannel: ServerChannel
}) {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
// manage events within eventa's context system.
@@ -15,5 +23,7 @@ export function setupAboutWindowElectronInvokes(params: { window: BrowserWindow,
const { context } = createContext(ipcMain, params.window)
await setupBaseWindowElectronInvokes({ context, window: params.window, i18n: params.i18n, serverChannel: params.serverChannel })
createAutoUpdaterService({ context, window: params.window, service: params.autoUpdater })
}
@@ -1,6 +1,7 @@
import type { BrowserWindow, BrowserWindowConstructorOptions, Rectangle } from 'electron'
import type { InferOutput } from 'valibot'
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import { createHash } from 'node:crypto'
@@ -20,7 +21,6 @@ import { captionGetIsFollowingWindow, captionIsFollowingWindowChanged } from '..
import { baseUrl, getElectronMainDirname, load, withHashRoute } from '../../libs/electron/location'
import { createConfig } from '../../libs/electron/persistence'
import { createReusableWindow } from '../../libs/electron/window-manager'
import { createServerChannelService } from '../../services/airi/channel-server'
import { mapForBreakpoints, resolutionBreakpoints, widthFrom } from '../shared/display'
import { setupBaseWindowElectronInvokes, transparentWindowConfig } from '../shared/window'
@@ -148,6 +148,7 @@ function createCaptionWindow(options?: BrowserWindowConstructorOptions) {
export function setupCaptionWindowManager(params: {
mainWindow: BrowserWindow
serverChannel: ServerChannel
i18n: I18n
}) {
const matrixHash = computeDisplayMatrixHash()
@@ -274,8 +275,7 @@ export function setupCaptionWindowManager(params: {
const { context } = createContext(ipcMain, window)
eventaContext = context
setupBaseWindowElectronInvokes({ context, window })
createServerChannelService({ serverChannel: params.serverChannel })
await setupBaseWindowElectronInvokes({ context, window, serverChannel: params.serverChannel, i18n: params.i18n })
const cfg = getConfig()
const saved = cfg?.matrices?.[matrixHash]?.bounds
@@ -1,3 +1,4 @@
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import type { McpStdioManager } from '../../services/airi/mcp-servers'
import type { WidgetsWindowManager } from '../widgets'
@@ -16,6 +17,7 @@ export function setupChatWindowReusableFunc(params: {
widgetsManager: WidgetsWindowManager
serverChannel: ServerChannel
mcpStdioManager: McpStdioManager
i18n: I18n
}) {
return createReusableWindow(async () => {
const window = new BrowserWindow({
@@ -38,11 +40,12 @@ export function setupChatWindowReusableFunc(params: {
await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/chat'))
setupChatWindowElectronInvokes({
await setupChatWindowElectronInvokes({
window,
widgetsManager: params.widgetsManager,
serverChannel: params.serverChannel,
mcpStdioManager: params.mcpStdioManager,
i18n: params.i18n,
})
return window
@@ -1,5 +1,6 @@
import type { BrowserWindow } from 'electron'
import type { I18n } from '../../../libs/i18n'
import type { ServerChannel } from '../../../services/airi/channel-server'
import type { McpStdioManager } from '../../../services/airi/mcp-servers'
import type { WidgetsWindowManager } from '../../widgets'
@@ -9,16 +10,16 @@ import { createContext } from '@moeru/eventa/adapters/electron/main'
import { ipcMain } from 'electron'
import { electronOpenMainDevtools } from '../../../../shared/eventa'
import { createServerChannelService } from '../../../services/airi/channel-server'
import { createMcpServersService } from '../../../services/airi/mcp-servers'
import { createWidgetsService } from '../../../services/airi/widgets'
import { createScreenService, createWindowService } from '../../../services/electron'
import { setupBaseWindowElectronInvokes } from '../../shared/window'
export function setupChatWindowElectronInvokes(params: {
export async function setupChatWindowElectronInvokes(params: {
window: BrowserWindow
widgetsManager: WidgetsWindowManager
serverChannel: ServerChannel
mcpStdioManager: McpStdioManager
i18n: I18n
}) {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
@@ -27,10 +28,9 @@ export function setupChatWindowElectronInvokes(params: {
const { context } = createContext(ipcMain, params.window)
createScreenService({ context, window: params.window })
createWindowService({ context, window: params.window })
await setupBaseWindowElectronInvokes({ context, window: params.window, i18n: params.i18n, serverChannel: params.serverChannel })
createWidgetsService({ context, widgetsManager: params.widgetsManager, window: params.window })
createServerChannelService({ serverChannel: params.serverChannel })
createMcpServersService({ context, manager: params.mcpStdioManager })
defineInvokeHandler(context, electronOpenMainDevtools, () => params.window.webContents.openDevTools({ mode: 'detach' }))
@@ -1,6 +1,8 @@
import type { Rectangle } from 'electron'
import type { InferOutput } from 'valibot'
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import type { NoticeWindowManager } from '../notice'
import { dirname, join, resolve } from 'node:path'
@@ -43,6 +45,8 @@ export async function setupDashboardWindow(params: {
chatWindow: () => Promise<BrowserWindow>
noticeWindow: NoticeWindowManager
onWindowCreated?: (window: BrowserWindow) => void
serverChannel: ServerChannel
i18n: I18n
}) {
const {
setup: setupConfig,
@@ -129,11 +133,13 @@ export async function setupDashboardWindow(params: {
await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/dashboard'))
setupDashboardWindowElectronInvokes({
await setupDashboardWindowElectronInvokes({
window,
settingsWindow: params.settingsWindow,
chatWindow: params.chatWindow,
noticeWindow: params.noticeWindow,
i18n: params.i18n,
serverChannel: params.serverChannel,
})
/**
@@ -1,5 +1,7 @@
import type { BrowserWindow } from 'electron'
import type { I18n } from '../../../libs/i18n'
import type { ServerChannel } from '../../../services/airi/channel-server'
import type { NoticeWindowManager } from '../../notice'
import { defineInvokeHandler } from '@moeru/eventa'
@@ -10,11 +12,13 @@ import { electronOpenChat, electronOpenMainDevtools, electronOpenSettings, notic
import { toggleWindowShow } from '../../shared'
import { setupBaseWindowElectronInvokes } from '../../shared/window'
export function setupDashboardWindowElectronInvokes(params: {
export async function setupDashboardWindowElectronInvokes(params: {
window: BrowserWindow
settingsWindow: () => Promise<BrowserWindow>
chatWindow: () => Promise<BrowserWindow>
noticeWindow: NoticeWindowManager
i18n: I18n
serverChannel: ServerChannel
}) {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
@@ -23,7 +27,7 @@ export function setupDashboardWindowElectronInvokes(params: {
const { context } = createContext(ipcMain, params.window)
setupBaseWindowElectronInvokes({ context, window: params.window })
await setupBaseWindowElectronInvokes({ context, window: params.window, serverChannel: params.serverChannel, i18n: params.i18n })
defineInvokeHandler(context, electronOpenMainDevtools, () => params.window.webContents.openDevTools({ mode: 'detach' }))
defineInvokeHandler(context, electronOpenSettings, async () => toggleWindowShow(await params.settingsWindow()))
@@ -1,3 +1,6 @@
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import { join, resolve } from 'node:path'
import { BrowserWindow, shell } from 'electron'
@@ -10,7 +13,10 @@ import { currentDisplayBounds, mapForBreakpoints, resolutionBreakpoints, widthFr
import { spotlightLikeWindowConfig } from '../shared/window'
import { setupInlayWindowInvokes } from './rpc/index.electron'
export async function setupInlayWindow() {
export async function setupInlayWindow(params: {
serverChannel: ServerChannel
i18n: I18n
}) {
const window = new BrowserWindow({
title: 'Inlay',
width: 450,
@@ -63,7 +69,7 @@ export async function setupInlayWindow() {
await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/inlay'))
setupInlayWindowInvokes({ inlayWindow: window })
await setupInlayWindowInvokes({ inlayWindow: window, serverChannel: params.serverChannel, i18n: params.i18n })
return window
}
@@ -1,12 +1,17 @@
import type { BrowserWindow } from 'electron'
import type { I18n } from '../../../libs/i18n'
import type { ServerChannel } from '../../../services/airi/channel-server'
import { createContext } from '@moeru/eventa/adapters/electron/main'
import { ipcMain } from 'electron'
import { createWindowService } from '../../../services/electron'
import { setupBaseWindowElectronInvokes } from '../../shared/window'
export async function setupInlayWindowInvokes(params: {
inlayWindow: BrowserWindow
serverChannel: ServerChannel
i18n: I18n
}) {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
@@ -15,5 +20,10 @@ export async function setupInlayWindowInvokes(params: {
const { context } = createContext(ipcMain, params.inlayWindow)
createWindowService({ context, window: params.inlayWindow })
await setupBaseWindowElectronInvokes({
context,
window: params.inlayWindow,
serverChannel: params.serverChannel,
i18n: params.i18n,
})
}
@@ -1,6 +1,7 @@
import type { Rectangle } from 'electron'
import type { InferOutput } from 'valibot'
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import type { McpStdioManager } from '../../services/airi/mcp-servers'
import type { AutoUpdater } from '../../services/electron/auto-updater'
@@ -52,6 +53,7 @@ export async function setupMainWindow(params: {
onWindowCreated?: (window: BrowserWindow) => void
serverChannel: ServerChannel
mcpStdioManager: McpStdioManager
i18n: I18n
}) {
const {
setup: setupConfig,
@@ -155,7 +157,7 @@ export async function setupMainWindow(params: {
await load(window, baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')))
setupMainWindowElectronInvokes({
await setupMainWindowElectronInvokes({
window,
settingsWindow: params.settingsWindow,
chatWindow: params.chatWindow,
@@ -164,6 +166,7 @@ export async function setupMainWindow(params: {
autoUpdater: params.autoUpdater,
serverChannel: params.serverChannel,
mcpStdioManager: params.mcpStdioManager,
i18n: params.i18n,
})
/**
@@ -1,5 +1,6 @@
import type { BrowserWindow } from 'electron'
import type { I18n } from '../../../libs/i18n'
import type { ServerChannel } from '../../../services/airi/channel-server'
import type { McpStdioManager } from '../../../services/airi/mcp-servers'
import type { AutoUpdater } from '../../../services/electron/auto-updater'
@@ -11,14 +12,13 @@ import { createContext } from '@moeru/eventa/adapters/electron/main'
import { ipcMain } from 'electron'
import { electronOpenChat, electronOpenMainDevtools, electronOpenSettings, noticeWindowEventa } from '../../../../shared/eventa'
import { createServerChannelService } from '../../../services/airi/channel-server'
import { createMcpServersService } from '../../../services/airi/mcp-servers'
import { createWidgetsService } from '../../../services/airi/widgets'
import { createAutoUpdaterService } from '../../../services/electron'
import { toggleWindowShow } from '../../shared'
import { setupBaseWindowElectronInvokes } from '../../shared/window'
export function setupMainWindowElectronInvokes(params: {
export async function setupMainWindowElectronInvokes(params: {
window: BrowserWindow
settingsWindow: () => Promise<BrowserWindow>
chatWindow: () => Promise<BrowserWindow>
@@ -27,6 +27,7 @@ export function setupMainWindowElectronInvokes(params: {
autoUpdater: AutoUpdater
serverChannel: ServerChannel
mcpStdioManager: McpStdioManager
i18n: I18n
}) {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
@@ -35,10 +36,9 @@ export function setupMainWindowElectronInvokes(params: {
const { context } = createContext(ipcMain, params.window)
setupBaseWindowElectronInvokes({ context, window: params.window })
await setupBaseWindowElectronInvokes({ context, window: params.window, serverChannel: params.serverChannel, i18n: params.i18n })
createWidgetsService({ context, widgetsManager: params.widgetsManager, window: params.window })
createAutoUpdaterService({ context, window: params.window, service: params.autoUpdater })
createServerChannelService({ serverChannel: params.serverChannel })
createMcpServersService({ context, manager: params.mcpStdioManager })
defineInvokeHandler(context, electronOpenMainDevtools, () => params.window.webContents.openDevTools({ mode: 'detach' }))
@@ -1,6 +1,8 @@
import type { BrowserWindow } from 'electron'
import type { RequestWindowPayload } from '../../../shared/eventa'
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import { join, resolve } from 'node:path'
@@ -17,7 +19,10 @@ export interface NoticeWindowManager {
open: (payload: RequestWindowPayload) => Promise<boolean>
}
export function setupNoticeWindowManager(): NoticeWindowManager {
export function setupNoticeWindowManager(params: {
i18n: I18n
serverChannel: ServerChannel
}): NoticeWindowManager {
const rendererBase = baseUrl(resolve(getElectronMainDirname(), '..', 'renderer'))
function createWindow(_id: string): BrowserWindow {
@@ -48,6 +53,8 @@ export function setupNoticeWindowManager(): NoticeWindowManager {
const manager = createReferencedWindowManager({
eventa: noticeWindowEventa,
i18n: params.i18n,
serverChannel: params.serverChannel,
createWindow,
loadRoute: loadNoticeRoute,
})
@@ -1,34 +0,0 @@
import type { BrowserWindow } from 'electron'
import type { RequestWindowActionDefault } from '../../../../shared/eventa'
import { defineInvokeHandler } from '@moeru/eventa'
import { createContext } from '@moeru/eventa/adapters/electron/main'
import { ipcMain } from 'electron'
import { noticeWindowEventa } from '../../../../shared/eventa'
import { createWindowService } from '../../../services/electron'
export function setupNoticeWindowInvokes(params: {
window: BrowserWindow
onAction: (payload: { id?: string, action: RequestWindowActionDefault }) => void
onPageMounted: () => { id: string, type?: string, payload?: Record<string, any> } | undefined
onPageUnmounted: () => void
}) {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
// manage events within eventa's context system.
ipcMain.setMaxListeners(0)
const { context } = createContext(ipcMain, params.window)
createWindowService({ context, window: params.window })
defineInvokeHandler(context, noticeWindowEventa.windowAction, (payload) => {
params.onAction({ id: payload?.id, action: payload?.action ?? 'close' })
})
defineInvokeHandler(context, noticeWindowEventa.pageMounted, () => params.onPageMounted())
defineInvokeHandler(context, noticeWindowEventa.pageUnmounted, () => params.onPageUnmounted())
return { context }
}
@@ -1,3 +1,4 @@
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import type { McpStdioManager } from '../../services/airi/mcp-servers'
import type { AutoUpdater } from '../../services/electron/auto-updater'
@@ -22,6 +23,7 @@ export function setupSettingsWindowReusableFunc(params: {
onWindowCreated?: (window: BrowserWindow) => void
serverChannel: ServerChannel
mcpStdioManager: McpStdioManager
i18n: I18n
}) {
return createReusableWindow(async () => {
const window = new BrowserWindow({
@@ -54,6 +56,7 @@ export function setupSettingsWindowReusableFunc(params: {
devtoolsMarkdownStressWindow: params.devtoolsMarkdownStressWindow,
serverChannel: params.serverChannel,
mcpStdioManager: params.mcpStdioManager,
i18n: params.i18n,
})
initScreenCaptureForWindow(window)
@@ -1,5 +1,6 @@
import type { BrowserWindow } from 'electron'
import type { I18n } from '../../../libs/i18n'
import type { ServerChannel } from '../../../services/airi/channel-server'
import type { McpStdioManager } from '../../../services/airi/mcp-servers'
import type { AutoUpdater } from '../../../services/electron/auto-updater'
@@ -11,10 +12,10 @@ import { createContext } from '@moeru/eventa/adapters/electron/main'
import { ipcMain } from 'electron'
import { electronOpenDevtoolsWindow, electronOpenSettingsDevtools } from '../../../../shared/eventa'
import { createServerChannelService } from '../../../services/airi/channel-server'
import { createMcpServersService } from '../../../services/airi/mcp-servers'
import { createWidgetsService } from '../../../services/airi/widgets'
import { createAutoUpdaterService, createScreenService, createWindowService } from '../../../services/electron'
import { createAutoUpdaterService } from '../../../services/electron'
import { setupBaseWindowElectronInvokes } from '../../shared/window'
export async function setupSettingsWindowInvokes(params: {
settingsWindow: BrowserWindow
@@ -23,6 +24,7 @@ export async function setupSettingsWindowInvokes(params: {
devtoolsMarkdownStressWindow: DevtoolsWindowManager
serverChannel: ServerChannel
mcpStdioManager: McpStdioManager
i18n: I18n
}) {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
@@ -31,11 +33,10 @@ export async function setupSettingsWindowInvokes(params: {
const { context } = createContext(ipcMain, params.settingsWindow)
createScreenService({ context, window: params.settingsWindow })
createWindowService({ context, window: params.settingsWindow })
await setupBaseWindowElectronInvokes({ context, window: params.settingsWindow, i18n: params.i18n, serverChannel: params.serverChannel })
createWidgetsService({ context, widgetsManager: params.widgetsManager, window: params.settingsWindow })
createAutoUpdaterService({ context, window: params.settingsWindow, service: params.autoUpdater })
createServerChannelService({ serverChannel: params.serverChannel })
createMcpServersService({ context, manager: params.mcpStdioManager })
defineInvokeHandler(context, electronOpenSettingsDevtools, async () => params.settingsWindow.webContents.openDevTools({ mode: 'detach' }))
@@ -1,11 +1,15 @@
import type { BrowserWindow } from 'electron'
import type { createRequestWindowEventa, RequestWindowPayload } from '../../../shared/eventa'
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import { defineInvokeHandler } from '@moeru/eventa'
import { createContext } from '@moeru/eventa/adapters/electron/main'
import { ipcMain } from 'electron'
import { setupBaseWindowElectronInvokes } from './window'
export interface ReferencedWindowHandle {
id: string
window: BrowserWindow
@@ -25,12 +29,14 @@ export interface ReferencedWindowManager<Payload extends RequestWindowPayload =
*/
export function createReferencedWindowManager<Payload extends RequestWindowPayload = RequestWindowPayload>(params: {
eventa: ReturnType<typeof createRequestWindowEventa>
i18n: I18n
serverChannel: ServerChannel
createWindow: (id: string) => BrowserWindow
loadRoute: (window: BrowserWindow, payload: Payload & { id: string }) => Promise<void>
}): ReferencedWindowManager<Payload> {
const windows = new Map<string, { window: BrowserWindow, context: ReturnType<typeof createContext>['context'] }>()
function bindContext(id: string, payload: Payload, win: BrowserWindow) {
async function bindContext(id: string, payload: Payload, win: BrowserWindow) {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
// manage events within eventa's context system.
@@ -49,6 +55,8 @@ export function createReferencedWindowManager<Payload extends RequestWindowPaylo
windows.delete(id)
})
await setupBaseWindowElectronInvokes({ context, window: win, i18n: params.i18n, serverChannel: params.serverChannel })
win.on('closed', () => windows.delete(id))
return { window: win, context }
@@ -60,7 +68,7 @@ export function createReferencedWindowManager<Payload extends RequestWindowPaylo
if (!ctx || ctx.window.isDestroyed()) {
const win = params.createWindow(id)
ctx = bindContext(id, payload, win)
ctx = await bindContext(id, payload, win)
windows.set(id, ctx)
}
@@ -2,8 +2,13 @@ import type { createContext } from '@moeru/eventa/adapters/electron/main'
import type { ResizeDirection } from '@proj-airi/electron-eventa'
import type { BrowserWindow, BrowserWindowConstructorOptions } from 'electron'
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import { isMacOS } from 'std-env'
import { createServerChannelService } from '../../services/airi/channel-server'
import { createI18nService } from '../../services/airi/i18n'
import { createAppService, createScreenService, createWindowService } from '../../services/electron'
export function toggleWindowShow(window?: BrowserWindow | null): void {
@@ -84,11 +89,16 @@ export function resizeWindowByDelta(params: {
params.window.setBounds({ x, y, width, height })
}
export function setupBaseWindowElectronInvokes(params: {
export async function setupBaseWindowElectronInvokes(params: {
context: ReturnType<typeof createContext>['context']
window: BrowserWindow
serverChannel: ServerChannel
i18n: I18n
}) {
createScreenService({ context: params.context, window: params.window })
createWindowService({ context: params.context, window: params.window })
createAppService({ context: params.context, window: params.window })
await createI18nService({ context: params.context, window: params.window, i18n: params.i18n })
createServerChannelService({ serverChannel: params.serverChannel })
}
@@ -2,6 +2,8 @@ import type { BrowserWindow, Rectangle } from 'electron'
import type { InferOutput } from 'valibot'
import type { WidgetsAddPayload, WidgetSnapshot } from '../../../shared/eventa'
import type { I18n } from '../../libs/i18n'
import type { ServerChannel } from '../../services/airi/channel-server'
import { join, resolve } from 'node:path'
@@ -93,7 +95,10 @@ interface WidgetWindowContext {
window?: BrowserWindow
}
export function setupWidgetsWindowManager(): WidgetsWindowManager {
export function setupWidgetsWindowManager(params: {
serverChannel: ServerChannel
i18n: I18n
}): WidgetsWindowManager {
const { setup, get: getConfigRaw, update } = createConfig('windows-widgets', 'config.json', widgetsWindowConfigSchema, {
default: {},
autoHeal: true,
@@ -144,7 +149,14 @@ export function setupWidgetsWindowManager(): WidgetsWindowManager {
const initialRoute = pendingRoute ?? defaultRoute
await loadWithRoute(window, initialRoute)
await setupWidgetsWindowInvokes({ widgetWindow: window, widgetsManager: widgetsManager! })
await setupWidgetsWindowInvokes({
widgetWindow: window,
widgetsManager: widgetsManager!,
i18n: params.i18n,
serverChannel: params.serverChannel,
})
pendingRoute = undefined
window.on('closed', () => {
@@ -1,14 +1,21 @@
import type { BrowserWindow } from 'electron'
import type { I18n } from '../../../libs/i18n'
import type { ServerChannel } from '../../../services/airi/channel-server'
import type { WidgetsWindowManager } from '../../widgets'
import { createContext } from '@moeru/eventa/adapters/electron/main'
import { ipcMain } from 'electron'
import { createWidgetsService } from '../../../services/airi/widgets'
import { createScreenService, createWindowService } from '../../../services/electron'
import { setupBaseWindowElectronInvokes } from '../../shared/window'
export async function setupWidgetsWindowInvokes(params: { widgetWindow: BrowserWindow, widgetsManager: WidgetsWindowManager }) {
export async function setupWidgetsWindowInvokes(params: {
widgetWindow: BrowserWindow
widgetsManager: WidgetsWindowManager
i18n: I18n
serverChannel: ServerChannel
}) {
// TODO: once we refactored eventa to support window-namespaced contexts,
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
// manage events within eventa's context system.
@@ -16,7 +23,7 @@ export async function setupWidgetsWindowInvokes(params: { widgetWindow: BrowserW
const { context } = createContext(ipcMain, params.widgetWindow)
createScreenService({ context, window: params.widgetWindow })
createWindowService({ context, window: params.widgetWindow })
setupBaseWindowElectronInvokes({ context, window: params.widgetWindow, i18n: params.i18n, serverChannel: params.serverChannel })
createWidgetsService({ context, widgetsManager: params.widgetsManager, window: params.widgetWindow })
}
@@ -38,6 +38,7 @@ import {
electronPluginUnload,
electronPluginUpdateCapability,
electronStartTrackMousePosition,
i18nSetLocale,
pluginProtocolListProviders,
pluginProtocolListProvidersEventName,
} from '../shared/eventa'
@@ -73,6 +74,7 @@ const startTrackingCursorPoint = useElectronEventaInvoke(electronStartTrackMouse
const reportPluginCapability = useElectronEventaInvoke(electronPluginUpdateCapability)
const listMcpTools = useElectronEventaInvoke(electronMcpListTools)
const callMcpTool = useElectronEventaInvoke(electronMcpCallTool)
const setLocale = useElectronEventaInvoke(i18nSetLocale)
// NOTICE: register plugin host bridge during setup to avoid race with pages using it in immediate watchers.
pluginHostInspectorStore.setBridge({
@@ -93,6 +95,7 @@ setMcpToolBridge({
watch(language, () => {
i18n.locale.value = language.value
setLocale(language.value)
})
const { updateThemeColor } = useThemeColor(themeColorFromValue({ light: 'rgb(255 255 255)', dark: 'rgb(18 18 18)' }))
@@ -1,3 +1,5 @@
import type { Locale } from '@intlify/core'
import { defineEventa, defineInvokeEventa } from '@moeru/eventa'
export const electronStartTrackMousePosition = defineInvokeEventa('eventa:invoke:electron:start-tracking-mouse-position')
@@ -203,5 +205,8 @@ export const widgetsRemoveEvent = defineEventa<{ id: string }>('eventa:event:ele
export const widgetsClearEvent = defineEventa('eventa:event:electron:windows:widgets:clear')
export const widgetsUpdateEvent = defineEventa<{ id: string, componentProps?: Record<string, any> }>('eventa:event:electron:windows:widgets:update')
export const i18nSetLocale = defineInvokeEventa<void, Locale>('eventa:invoke:electron:i18n:set-locale')
export const i18nGetLocale = defineInvokeEventa<Locale>('eventa:invoke:electron:i18n:get-locale')
export { electron } from '@proj-airi/electron-eventa'
export * from '@proj-airi/electron-eventa/electron-updater'
+24 -3
View File
@@ -39,6 +39,9 @@ catalogs:
'@iconify-json/tabler':
specifier: ^1.2.27
version: 1.2.27
'@intlify/core':
specifier: ^11.2.8
version: 11.2.8
'@modelcontextprotocol/sdk':
specifier: ^1.27.1
version: 1.27.1
@@ -120,6 +123,9 @@ catalogs:
'@xsai/utils-chat':
specifier: 0.4.0-beta.13
version: 0.4.0-beta.13
alien-signals:
specifier: ^3.1.2
version: 3.1.2
async-mutex:
specifier: 0.5.0
version: 0.5.0
@@ -920,6 +926,9 @@ importers:
'@huggingface/transformers':
specifier: ^3.8.1
version: 3.8.1
'@intlify/core':
specifier: 'catalog:'
version: 11.2.8
'@modelcontextprotocol/sdk':
specifier: 'catalog:'
version: 1.27.1(@cfworker/json-schema@4.1.1)(zod@4.3.6)
@@ -1028,6 +1037,9 @@ importers:
'@xsai/utils-chat':
specifier: 'catalog:'
version: 0.4.0-beta.13
alien-signals:
specifier: 'catalog:'
version: 3.1.2
animejs:
specifier: ^4.3.6
version: 4.3.6
@@ -3142,7 +3154,7 @@ importers:
version: 14.1.0(vue@3.5.29(typescript@5.9.3))
'@wxt-dev/module-vue':
specifier: ^1.0.3
version: 1.0.3(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
version: 1.0.3(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
nanoid:
specifier: ^5.1.6
version: 5.1.6
@@ -5780,6 +5792,10 @@ packages:
resolution: {integrity: sha512-nBq6Y1tVkjIUsLsdOjDSJj4AsjvD0UG3zsg9Fyc+OivwlA/oMHSKooUy9tpKj0HqZ+NWFifweHavdljlBLTwdA==}
engines: {node: '>= 16'}
'@intlify/core@11.2.8':
resolution: {integrity: sha512-su9kRlQAkG+SBP5cufTYmwPnqjur8etZVa2lnR80CgE5JqA0pXwGUF7W08dR/a6T2oDoYPh53/S8O0CGbfx1qg==}
engines: {node: '>= 16'}
'@intlify/message-compiler@11.2.8':
resolution: {integrity: sha512-A5n33doOjmHsBtCN421386cG1tWp5rpOjOYPNsnpjIJbQ4POF0QY2ezhZR9kr0boKwaHjbOifvyQvHj2UTrDFQ==}
engines: {node: '>= 16'}
@@ -19281,6 +19297,11 @@ snapshots:
'@intlify/message-compiler': 11.2.8
'@intlify/shared': 11.2.8
'@intlify/core@11.2.8':
dependencies:
'@intlify/core-base': 11.2.8
'@intlify/shared': 11.2.8
'@intlify/message-compiler@11.2.8':
dependencies:
'@intlify/shared': 11.2.8
@@ -23310,9 +23331,9 @@ snapshots:
'@types/filesystem': 0.0.36
'@types/har-format': 1.2.16
'@wxt-dev/module-vue@1.0.3(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
'@wxt-dev/module-vue@1.0.3(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))(wxt@0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@vitejs/plugin-vue': 6.0.4(vite@7.3.1(@types/node@24.10.14)(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))
'@vitejs/plugin-vue': 6.0.4(vite@8.0.0-beta.15(@types/node@24.10.14)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))
wxt: 0.20.18(@types/node@24.10.14)(canvas@3.2.1)(eslint@9.39.3(jiti@2.6.1))(jiti@2.6.1)(less@4.5.1)(lightningcss@1.31.1)(rollup@4.59.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
transitivePeerDependencies:
- vite
+2 -1
View File
@@ -26,7 +26,6 @@ patchedDependencies:
mineflayer-pathfinder: patches/mineflayer-pathfinder.patch
mineflayer@4.33.0: patches/mineflayer@4.33.0.patch
srvx@0.9.8: patches/srvx@0.9.8.patch
catalog:
'@capacitor/cli': ^8.1.0
'@capacitor/core': ^8.1.0
@@ -39,6 +38,7 @@ catalog:
'@histoire/plugin-vue': 1.0.0-beta.1
'@iconify-json/logos': ^1.2.10
'@iconify-json/tabler': ^1.2.27
'@intlify/core': ^11.2.8
'@modelcontextprotocol/sdk': ^1.27.1
'@moeru/eslint-config': 0.1.0-beta.15
'@moeru/eventa': 1.0.0-alpha.14
@@ -66,6 +66,7 @@ catalog:
'@xsai/stream-text': ^0.4.3
'@xsai/tool': ^0.4.3
'@xsai/utils-chat': 0.4.0-beta.13
alien-signals: ^3.1.2
async-mutex: 0.5.0
better-auth: ^1.4.19
builder-util-runtime: ^9.5.1