docs(api): 修改部分通信接口、更新 API 文档

- 重新定义了通信命令的命名规则和语义
- 修改了多个前端和后端之间的通信接口
- 为模型信息添加国际化
This commit is contained in:
himeditator
2025-07-04 18:38:56 +08:00
parent 0a10068b38
commit 0b279dedbf
16 changed files with 443 additions and 103 deletions

View File

@@ -2,10 +2,225 @@
本文档主要记录主进程和渲染进程的通信约定。
## 背景知识
## 命名方式
本项目渲染进程包含两个:字幕窗口和控制窗口主进程需要分别和两者进行通信通信命令一般有三个关键词组成,由点号隔开。
本项目渲染进程包含两个:字幕窗口和控制窗口主进程需要分别和两者进行通信通信命令的命名规则如下:
第一个词表示发送/接收处理对象,`config` 表示配置对象,`engine` 表示字幕引擎对象,`both` 表示两者同时
1. 命令一般由三个关键字组成,由点号隔开
2. 第一个关键字表示通信发送目标:
- `config` 表示控制窗口类实例(后端)或控制窗口(前端)
- `engine` 表示字幕窗口类实例(后端)或字幕窗口(前端)
- `both` 表示上述对象都有可能成为目标
3. 第二个关键字表示需要修改的对象 / 发生改变的对象,采用小驼峰命名
4. 第三个关键字一般是动词,表示通信发生时对应动作 / 需要进行的操作
比如 ``
根据上面的描述可以看出通信命令一般有两种语义,一种表示要求执行的操作,另一种表示当前发生的事件。
## 前端 <=> 后端
### `control.captionLog.clear`
**介绍:**清空字幕记录
**发起方:**前端控制窗口
**接收方:**后端控制窗口实例
**数据类型:**
- 发送:无数据
- 接收:`CaptionItem[]`
## 前端 ==> 后端
### `control.styles.change`
**介绍:**前端修改字幕样式,将修改同步给后端
**发起方:**前端控制窗口
**接收方:**后端控制窗口实例
**数据类型:**`Styles`
### `control.styles.reset`
**介绍:**将字幕样式恢复为默认
**发起方:**前端控制窗口
**接收方:**后端控制窗口实例
**数据类型:**无数据
### `control.controls.change`
**介绍:**前端修改了字幕引擎配置,将最新配置发送给后端
**发起方:**前端控制窗口
**接收方:**后端控制窗口实例
**数据类型:**`Controls`
### `control.captionWindow.activate`
**介绍:**激活字幕窗口
**发起方:**前端控制窗口
**接收方:**后端控制窗口实例
**数据类型:**无数据
### `control.engine.start`
**介绍:**启动字幕引擎
**发起方:**前端控制窗口
**接收方:**后端控制窗口实例
**数据类型:**无数据
### `control.engine.stop`
**介绍:**关闭字幕引擎
**发起方:**前端控制窗口
**接收方:**后端控制窗口实例
**数据类型:**无数据
### `caption.windowHeight.change`
**介绍:**字幕窗口宽度发生改变
**发起方:**前端字幕窗口
**接收方:**后端字幕窗口实例
**数据类型:**`number`
### `caption.pin.set`
**介绍:**是否将窗口置顶
**发起方:**前端字幕窗口
**接收方:**后端字幕窗口实例
**数据类型:**`boolean`
### `caption.controlWindow.activate`
**介绍:**激活控制窗口
**发起方:**前端字幕窗口
**接收方:**后端字幕窗口实例
**数据类型:**无数据
### `caption.window.close`
**介绍:**关闭字幕窗口
**发起方:**前端字幕窗口
**接收方:**后端字幕窗口实例
**数据类型:**无数据
## 后端 ==> 前端
### `control.engine.already`
**介绍:**引擎已经启动
**发起方:**后端
**接收方:**前端控制窗口
**数据类型:**无数据
### `control.engine.started`
**介绍:**引擎启动
**发起方:**后端
**接收方:**前端控制窗口
**数据类型:**无数据
### `control.engine.stopped`
**介绍:**引擎关闭
**发起方:**后端
**接收方:**前端控制窗口
**数据类型:**无数据
### `control.error.occurred`
**介绍:**发送错误
**发起方:**后端
**接收方:**前端控制窗口
**数据类型:**`string`
### `control.controls.set`
**介绍:**后端将最新字幕引擎配置发送给前端,前端进行设置
**发起方:**后端
**接收方:**前端控制窗口
**数据类型:**`Controls`
### `caption.styles.set`
**介绍:**后端将最新字幕样式发送给前端,前端进行设置
**发起方:**后端
**接收方:**前端
**数据类型:**`Styles`
### `both.captionLog.add`
**介绍:**添加一条新的字幕数据
**发起方:**后端
**接收方:**前端
**数据类型:**`CaptionItem`
### `both.captionLog.upd`
**介绍:**更新最后一条字幕数据
**发起方:**后端
**接收方:**前端
**数据类型:**`CaptionItem`
### `both.captionLog.set`
**介绍:**设置全部的字幕数据
**发起方:**后端
**接收方:**前端
**数据类型:**`CaptionItem[]`

View File

@@ -14,8 +14,8 @@ class ControlWindow {
icon: icon,
width: 1200,
height: 800,
minWidth: 600,
minHeight: 400,
minWidth: 750,
minHeight: 500,
show: false,
center: true,
autoHideMenuBar: true,
@@ -58,8 +58,13 @@ class ControlWindow {
}
public handleMessage() {
// 控制窗口初始化完毕
ipcMain.handle('control.window.mounted', async () => {
return allConfig.getControlWindowConfig()
})
// 样式变更
ipcMain.on('control.style.change', (_, args) => {
ipcMain.on('control.styles.change', (_, args) => {
allConfig.setStyles(args)
if(captionWindow.window){
allConfig.sendStyles(captionWindow.window)
@@ -67,7 +72,7 @@ class ControlWindow {
})
// 样式重置
ipcMain.on('control.style.reset', () => {
ipcMain.on('control.styles.reset', () => {
allConfig.resetStyles()
if(this.window){
allConfig.sendStyles(this.window)
@@ -88,7 +93,7 @@ class ControlWindow {
})
// 字幕引擎配置更新
ipcMain.on('control.control.change', (_, args) => {
ipcMain.on('control.controls.change', (_, args) => {
allConfig.setControls(args)
})
@@ -109,13 +114,14 @@ class ControlWindow {
})
// 清空字幕记录
ipcMain.on('control.caption.clear', () => {
ipcMain.handle('control.captionLog.clear', () => {
allConfig.captionLog.splice(0)
return allConfig.captionLog
})
}
public sendErrorMessage(message: string) {
this.window?.webContents.send('control.error.send', message)
this.window?.webContents.send('control.error.occurred', message)
}
}

View File

@@ -1,5 +1,17 @@
export type UILanguage = "zh" | "en" | "ja"
export interface Controls {
engineEnabled: boolean,
sourceLang: string,
targetLang: string,
engine: 'gummy',
audio: 0 | 1,
translation: boolean,
customized: boolean,
customizedApp: string,
customizedCommand: string
}
export interface Styles {
fontFamily: string,
fontSize: number,
@@ -20,14 +32,9 @@ export interface CaptionItem {
translation: string
}
export interface Controls {
engineEnabled: boolean,
sourceLang: string,
targetLang: string,
engine: 'gummy',
audio: 0 | 1,
translation: boolean,
customized: boolean,
customizedApp: string,
customizedCommand: string
export interface ControlWindowConfig {
uiLanguage: UILanguage,
leftBarWidth: number,
styles: Styles,
controls: Controls
}

View File

@@ -1,4 +1,7 @@
import { UILanguage, Styles, CaptionItem, Controls } from '../types'
import {
UILanguage, Styles, CaptionItem, Controls,
ControlWindowConfig
} from '../types'
import { app, BrowserWindow } from 'electron'
import * as path from 'path'
import * as fs from 'fs'
@@ -29,7 +32,8 @@ const defaultControls: Controls = {
class AllConfig {
uiLanguage: UILanguage = 'ja'
uiLanguage: UILanguage = 'zh';
leftBarWidth: number = 8;
styles: Styles = {...defaultStyles};
controls: Controls = {...defaultControls};
captionLog: CaptionItem[] = [];
@@ -41,6 +45,7 @@ class AllConfig {
if(fs.existsSync(configPath)){
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
if(config.uiLanguage) this.uiLanguage = config.uiLanguage
if(config.leftBarWidth) this.leftBarWidth = config.leftBarWidth
if(config.styles) this.setStyles(config.styles)
if(config.controls) this.setControls(config.controls)
console.log('[INFO] Read Config from:', configPath)
@@ -58,8 +63,21 @@ class AllConfig {
console.log('[INFO] Write Config to:', configPath)
}
public setStyles(args: any) {
this.styles = {...args}
public getControlWindowConfig(): ControlWindowConfig {
return {
uiLanguage: this.uiLanguage,
leftBarWidth: this.leftBarWidth,
styles: this.styles,
controls: this.controls
}
}
public setStyles(args: Styles) {
for(let key in this.styles) {
if(key in args) {
this.styles[key] = args[key]
}
}
console.log('[INFO] Set Styles:', this.styles)
}
@@ -68,40 +86,49 @@ class AllConfig {
}
public sendStyles(window: BrowserWindow) {
window.webContents.send('caption.style.set', this.styles)
window.webContents.send('caption.styles.set', this.styles)
console.log(`[INFO] Send Styles to #${window.id}:`, this.styles)
}
public setControls(args: any) {
public setControls(args: Controls) {
const engineEnabled = args.engineEnabled
this.controls = {...args}
for(let key in this.controls){
if(key in args) {
this.controls[key] = args[key]
}
}
this.controls.engineEnabled = engineEnabled
console.log('[INFO] Set Controls:', this.controls)
}
public sendControls(window: BrowserWindow) {
window.webContents.send('control.control.set', this.controls)
window.webContents.send('control.controls.set', this.controls)
console.log(`[INFO] Send Controls to #${window.id}:`, this.controls)
}
public updateCaptionLog(log: CaptionItem) {
let command: 'add' | 'upd' = 'add'
if(this.captionLog.length && this.captionLog[this.captionLog.length - 1].index === log.index) {
this.captionLog.splice(this.captionLog.length - 1, 1, log)
command = 'upd'
}
else {
this.captionLog.push(log)
}
for(const window of BrowserWindow.getAllWindows()){
this.sendCaptionLog(window, 'add')
this.sendCaptionLog(window, command)
}
}
public sendCaptionLog(window: BrowserWindow, command: 'add' | 'set') {
public sendCaptionLog(window: BrowserWindow, command: 'add' | 'upd' | 'set') {
if(command === 'add'){
window.webContents.send(`both.log.add`, this.captionLog[this.captionLog.length - 1])
window.webContents.send(`both.captionLog.add`, this.captionLog[this.captionLog.length - 1])
}
else if(command === 'upd'){
window.webContents.send(`both.captionLog.upd`, this.captionLog[this.captionLog.length - 1])
}
else if(command === 'set'){
window.webContents.send(`both.log.${command}`, this.captionLog)
window.webContents.send(`both.captionLog.set`, this.captionLog)
}
}
}

View File

@@ -3,4 +3,11 @@
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
onMounted(() => {
// window.electron.ipcRenderer.invoke('').then((data: any) => {
// })
})
</script>

View File

@@ -168,7 +168,7 @@ function applyStyle(){
captionStyle.transFontSize = currentTransFontSize.value;
captionStyle.transFontColor = currentTransFontColor.value;
captionStyle.sendStyleChange();
captionStyle.sendStylesChange();
}
function backStyle(){
@@ -185,7 +185,7 @@ function backStyle(){
}
function resetStyle() {
captionStyle.sendStyleReset();
captionStyle.sendStylesReset();
}
watch(changeSignal, (val) => {

View File

@@ -115,7 +115,7 @@ function applyChange(){
engineControl.customizedApp = currentCustomizedApp.value
engineControl.customizedCommand = currentCustomizedCommand.value
engineControl.sendControlChange()
engineControl.sendControlsChange()
notification.open({
message: '字幕控制已更改',

View File

@@ -0,0 +1,32 @@
export const audioTypes = {
zh: [
{
value: 0,
label: '系统音频输出(扬声器)'
},
{
value: 1,
label: '系统音频输入(麦克风)'
}
],
en: [
{
value: 0,
label: 'System Audio Output (Speaker)'
},
{
value: 1,
label: 'System Audio Input (Microphone)'
}
],
ja: [
{
value: 0,
label: 'システム音声出力(スピーカー)'
},
{
value: 1,
label: 'システム音声入力(マイク)'
}
]
}

View File

@@ -0,0 +1,57 @@
export const engines = {
zh: [
{
value: 'gummy',
label: '云端 - 阿里云 - Gummy',
languages: [
{ value: 'auto', label: '自动检测' },
{ value: 'en', label: '英语' },
{ value: 'zh', label: '中文' },
{ value: 'ja', label: '日语' },
{ value: 'ko', label: '韩语' },
{ value: 'de', label: '德语' },
{ value: 'fr', label: '法语' },
{ value: 'ru', label: '俄语' },
{ value: 'es', label: '西班牙语' },
{ value: 'it', label: '意大利语' },
]
},
],
en: [
{
value: 'gummy',
label: 'Cloud - Alibaba Cloud - Gummy',
languages: [
{ value: 'auto', label: 'Auto Detect' },
{ value: 'en', label: 'English' },
{ value: 'zh', label: 'Chinese' },
{ value: 'ja', label: 'Japanese' },
{ value: 'ko', label: 'Korean' },
{ value: 'de', label: 'German' },
{ value: 'fr', label: 'French' },
{ value: 'ru', label: 'Russian' },
{ value: 'es', label: 'Spanish' },
{ value: 'it', label: 'Italian' },
]
},
],
ja: [
{
value: 'gummy',
label: 'クラウド - アリババクラウド - Gummy',
languages: [
{ value: 'auto', label: '自動検出' },
{ value: 'en', label: '英語' },
{ value: 'zh', label: '中国語' },
{ value: 'ja', label: '日本語' },
{ value: 'ko', label: '韓国語' },
{ value: 'de', label: 'ドイツ語' },
{ value: 'fr', label: 'フランス語' },
{ value: 'ru', label: 'ロシア語' },
{ value: 'es', label: 'スペイン語' },
{ value: 'it', label: 'イタリア語' },
]
},
]
}

View File

@@ -4,7 +4,7 @@ import zh from './lang/zh';
import en from './lang/en';
import ja from './lang/ja';
const i18n = createI18n({
export const i18n = createI18n({
legacy: false,
locale: 'zh',
messages: {
@@ -14,4 +14,5 @@ const i18n = createI18n({
}
});
export default i18n;
export * from './config/engine'
export * from './config/audio'

View File

@@ -4,7 +4,7 @@ import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import i18n from './i18n'
import { i18n } from './i18n'
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';

View File

@@ -5,21 +5,21 @@ import { CaptionItem } from '../types'
export const useCaptionLogStore = defineStore('captionLog', () => {
const captionData = ref<CaptionItem[]>([])
window.electron.ipcRenderer.on('both.log.add', (_, log) => {
if(captionData.value.length && log.index === captionData.value[captionData.value.length - 1].index) {
captionData.value.splice(captionData.value.length - 1, 1, log)
}
else {
captionData.value.push(log)
}
})
function clear() {
captionData.value = []
window.electron.ipcRenderer.send('control.caption.clear')
window.electron.ipcRenderer.invoke('control.captionLog.clear').then((data: CaptionItem[]) => {
captionData.value = data
})
}
window.electron.ipcRenderer.on('both.log.set', (_, logs) => {
window.electron.ipcRenderer.on('both.captionLog.add', (_, log) => {
captionData.value.push(log)
})
window.electron.ipcRenderer.on('both.captionLog.upd', (_, log) => {
captionData.value.splice(captionData.value.length - 1, 1, log)
})
window.electron.ipcRenderer.on('both.captionLog.set', (_, logs) => {
captionData.value = logs
})

View File

@@ -26,7 +26,7 @@ export const useCaptionStyleStore = defineStore('captionStyle', () => {
return addOpicityToColor(background.value, opacity.value)
})
function sendStyleChange() {
function sendStylesChange() {
const styles: Styles = {
fontFamily: fontFamily.value,
fontSize: fontSize.value,
@@ -38,14 +38,14 @@ export const useCaptionStyleStore = defineStore('captionStyle', () => {
transFontSize: transFontSize.value,
transFontColor: transFontColor.value
}
window.electron.ipcRenderer.send('control.style.change', styles)
window.electron.ipcRenderer.send('control.styles.change', styles)
}
function sendStyleReset() {
window.electron.ipcRenderer.send('control.style.reset')
function sendStylesReset() {
window.electron.ipcRenderer.send('control.styles.reset')
}
window.electron.ipcRenderer.on('caption.style.set', (_, args) => {
window.electron.ipcRenderer.on('caption.styles.set', (_, args: Styles) => {
fontFamily.value = args.fontFamily
fontSize.value = args.fontSize
fontColor.value = args.fontColor
@@ -69,8 +69,8 @@ export const useCaptionStyleStore = defineStore('captionStyle', () => {
transFontSize, // 翻译字体大小
transFontColor, // 翻译字体颜色
backgroundRGBA, // 带透明度的背景颜色
sendStyleChange, // 发送样式改变
sendStyleReset, // 恢复默认样式
sendStylesChange, // 发送样式改变
sendStylesReset, // 恢复默认样式
changeSignal // 样式改变信号
}
})

View File

@@ -6,39 +6,14 @@ import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import { h } from 'vue'
import { Controls } from '@renderer/types'
import { engines, audioTypes } from '@renderer/i18n'
import { useGeneralSettingStore } from './generalSetting'
export const useEngineControlStore = defineStore('engineControl', () => {
const captionEngine = ref([
{
value: 'gummy',
label: '云端-阿里云-Gummy',
languages: [
{ value: 'auto', label: '自动检测' },
{ value: 'en', label: '英语' },
{ value: 'zh', label: '中文' },
{ value: 'ja', label: '日语' },
{ value: 'ko', label: '韩语' },
{ value: 'de', label: '德语' },
{ value: 'fr', label: '法语' },
{ value: 'ru', label: '俄语' },
{ value: 'es', label: '西班牙语' },
{ value: 'it', label: '意大利语' },
]
},
])
const audioType = ref([
{
value: 0,
label: '系统音频输出(扬声器)'
},
{
value: 1,
label: '系统音频输入(麦克风)'
}
])
const captionEngine = ref(engines[useGeneralSettingStore().uiLanguage])
const audioType = ref(audioTypes[useGeneralSettingStore().uiLanguage])
const engineEnabled = ref(false)
const sourceLang = ref<string>('en')
const targetLang = ref<string>('zh')
const engine = ref<'gummy'>('gummy')
@@ -50,7 +25,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
const changeSignal = ref<boolean>(false)
function sendControlChange() {
function sendControlsChange() {
const controls: Controls = {
engineEnabled: engineEnabled.value,
sourceLang: sourceLang.value,
@@ -62,10 +37,10 @@ export const useEngineControlStore = defineStore('engineControl', () => {
customizedApp: customizedApp.value,
customizedCommand: customizedCommand.value
}
window.electron.ipcRenderer.send('control.control.change', controls)
window.electron.ipcRenderer.send('control.controls.change', controls)
}
window.electron.ipcRenderer.on('control.control.set', (_, controls) => {
window.electron.ipcRenderer.on('control.controls.set', (_, controls: Controls) => {
sourceLang.value = controls.sourceLang
targetLang.value = controls.targetLang
engine.value = controls.engine
@@ -79,6 +54,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
})
window.electron.ipcRenderer.on('control.engine.already', () => {
// TODO 修改为重启
notification.open({
message: '字幕引擎已经启动',
description: '字幕引擎已经启动,请勿重复启动'
@@ -104,7 +80,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
});
})
window.electron.ipcRenderer.on('control.error.send', (_, message) => {
window.electron.ipcRenderer.on('control.error.occurred', (_, message) => {
notification.open({
message: '发生错误',
description: message,
@@ -126,7 +102,7 @@ export const useEngineControlStore = defineStore('engineControl', () => {
customized, // 是否使用自定义字幕引擎
customizedApp, // 自定义字幕引擎的应用程序
customizedCommand, // 自定义字幕引擎的命令
sendControlChange, // 发送最新控制消息到后端
sendControlsChange, // 发送最新控制消息到后端
changeSignal, // 配置改变信号
}
})

View File

@@ -1,8 +1,11 @@
import { ref, watch } from 'vue'
import { defineStore } from 'pinia'
import i18n from '../i18n'
import { i18n } from '../i18n'
import type { UILanguage } from '../types'
import { engines, audioTypes } from '../i18n'
import { useEngineControlStore } from './engineControl'
export const useGeneralSettingStore = defineStore('generalSetting', () => {
const uiLanguage = ref<UILanguage>('zh')
const leftBarWidth = ref<number>(8)
@@ -10,6 +13,8 @@ export const useGeneralSettingStore = defineStore('generalSetting', () => {
watch(uiLanguage, (newValue) => {
i18n.global.locale.value = newValue
console.log(newValue)
useEngineControlStore().captionEngine = engines[newValue]
useEngineControlStore().audioType = audioTypes[newValue]
})
return {

View File

@@ -1,5 +1,17 @@
export type UILanguage = "zh" | "en" | "ja"
export interface Controls {
engineEnabled: boolean,
sourceLang: string,
targetLang: string,
engine: 'gummy',
audio: 0 | 1,
translation: boolean,
customized: boolean,
customizedApp: string,
customizedCommand: string
}
export interface Styles {
fontFamily: string,
fontSize: number,
@@ -20,14 +32,9 @@ export interface CaptionItem {
translation: string
}
export interface Controls {
engineEnabled: boolean,
sourceLang: string,
targetLang: string,
engine: 'gummy',
audio: 0 | 1,
translation: boolean,
customized: boolean,
customizedApp: string,
customizedCommand: string
export interface ControlWindowConfig {
uiLanguage: UILanguage,
leftBarWidth: number,
styles: Styles,
controls: Controls
}