feat(stage-tamagotchi): hot-reload i18n during Electron runtime
This commit is contained in:
@@ -46,6 +46,12 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'@proj-airi/i18n': resolve(join(import.meta.dirname, '..', '..', 'packages', 'i18n', 'src')),
|
||||
},
|
||||
},
|
||||
},
|
||||
preload: {
|
||||
build: {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
Generated
+24
-3
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user