diff --git a/src/background.ts b/src/background.ts index 22c3e0156a..a47e9fe677 100644 --- a/src/background.ts +++ b/src/background.ts @@ -32,7 +32,8 @@ import { defaultHotkeySettings, isMac, defaultToolbarButtonSetting, - engineSetting, + engineSettingSchema, + EngineId, } from "./type/preload"; import log from "electron-log"; @@ -133,15 +134,18 @@ const store = new Store({ }, ">=0.14": (store) => { // FIXME: できるならEngineManagerからEnginIDを取得したい - const engineId = JSON.parse(process.env.DEFAULT_ENGINE_INFOS ?? "[]")[0] - .uuid; + if (process.env.DEFAULT_ENGINE_INFOS == undefined) + throw new Error("DEFAULT_ENGINE_INFOS == undefined"); + const engineId = EngineId( + JSON.parse(process.env.DEFAULT_ENGINE_INFOS)[0].uuid + ); if (engineId == undefined) throw new Error("DEFAULT_ENGINE_INFOS[0].uuid == undefined"); const prevDefaultStyleIds = store.get("defaultStyleIds"); store.set( "defaultStyleIds", prevDefaultStyleIds.map((defaultStyle) => ({ - engineId: engineId, + engineId, speakerUuid: defaultStyle.speakerUuid, defaultStyleId: defaultStyle.defaultStyleId, })) @@ -178,7 +182,7 @@ const engineManager = new EngineManager({ const vvppManager = new VvppManager({ vvppEngineDir }); // エンジンのフォルダを開く -function openEngineDirectory(engineId: string) { +function openEngineDirectory(engineId: EngineId) { const engineDirectory = engineManager.fetchEngineDirectory(engineId); // Windows環境だとスラッシュ区切りのパスが動かない。 @@ -270,7 +274,7 @@ function checkMultiEngineEnabled(): boolean { * VVPPエンジンをアンインストールする。 * 関数を呼んだタイミングでアンインストール処理を途中まで行い、アプリ終了時に完遂する。 */ -async function uninstallVvppEngine(engineId: string) { +async function uninstallVvppEngine(engineId: EngineId) { let engineInfo: EngineInfo | undefined = undefined; try { engineInfo = engineManager.fetchEngineInfo(engineId); @@ -492,7 +496,7 @@ async function start() { for (const engineInfo of engineInfos) { if (!engineSettings[engineInfo.uuid]) { // 空オブジェクトをパースさせることで、デフォルト値を取得する - engineSettings[engineInfo.uuid] = engineSetting.parse({}); + engineSettings[engineInfo.uuid] = engineSettingSchema.parse({}); } } store.set("engineSettings", engineSettings); @@ -817,7 +821,7 @@ ipcMainHandle("INSTALL_VVPP_ENGINE", async (_, path: string) => { return await installVvppEngine(path); }); -ipcMainHandle("UNINSTALL_VVPP_ENGINE", async (_, engineId: string) => { +ipcMainHandle("UNINSTALL_VVPP_ENGINE", async (_, engineId: EngineId) => { return await uninstallVvppEngine(engineId); }); diff --git a/src/background/engineManager.ts b/src/background/engineManager.ts index 1a45070495..d27b717611 100644 --- a/src/background/engineManager.ts +++ b/src/background/engineManager.ts @@ -14,6 +14,8 @@ import { ElectronStoreType, EngineDirValidationResult, MinimumEngineManifest, + EngineId, + engineIdSchema, } from "@/type/preload"; import log from "electron-log"; @@ -33,7 +35,7 @@ function createDefaultEngineInfos(defaultEngineDir: string): EngineInfo[] { const envSchema = z .object({ - uuid: z.string().uuid(), + uuid: engineIdSchema, host: z.string(), name: z.string(), executionEnabled: z.boolean(), @@ -62,7 +64,7 @@ export class EngineManager { vvppEngineDir: string; defaultEngineInfos: EngineInfo[]; - engineProcessContainers: Record; + engineProcessContainers: Record; constructor({ store, @@ -160,7 +162,7 @@ export class EngineManager { /** * エンジンの情報を取得する。存在しない場合はエラーを返す。 */ - fetchEngineInfo(engineId: string): EngineInfo { + fetchEngineInfo(engineId: EngineId): EngineInfo { const engineInfos = this.fetchEngineInfos(); const engineInfo = engineInfos.find( (engineInfo) => engineInfo.uuid === engineId @@ -174,7 +176,7 @@ export class EngineManager { /** * エンジンのディレクトリを取得する。存在しない場合はエラーを返す。 */ - fetchEngineDirectory(engineId: string): string { + fetchEngineDirectory(engineId: EngineId): string { const engineInfo = this.fetchEngineInfo(engineId); const engineDirectory = engineInfo.path; if (engineDirectory == null) { @@ -202,7 +204,7 @@ export class EngineManager { * エンジンを起動する。 * FIXME: winを受け取らなくても良いようにする */ - async runEngine(engineId: string, win: BrowserWindow) { + async runEngine(engineId: EngineId, win: BrowserWindow) { const engineInfos = this.fetchEngineInfos(); const engineInfo = engineInfos.find( (engineInfo) => engineInfo.uuid === engineId @@ -233,7 +235,11 @@ export class EngineManager { const engineProcessContainer = this.engineProcessContainers[engineId]; engineProcessContainer.willQuitEngine = false; - const useGpu = this.store.get("engineSettings")[engineId].useGpu; + const engineSetting = this.store.get("engineSettings")[engineId]; + if (engineSetting == undefined) + throw new Error(`No such engineSetting: engineId == ${engineId}`); + + const useGpu = engineSetting.useGpu; log.info(`ENGINE ${engineId} mode: ${useGpu ? "GPU" : "CPU"}`); // エンジンプロセスの起動 @@ -286,10 +292,12 @@ export class EngineManager { /** * 全てのエンジンに対し、各エンジンを終了するPromiseを返す。 */ - killEngineAll(): Record> { - const killingProcessPromises: Record> = {}; + killEngineAll(): Record> { + const killingProcessPromises: Record> = {}; - for (const engineId of Object.keys(this.engineProcessContainers)) { + // FIXME: engineProcessContainersをMapにする + for (const engineIdStr of Object.keys(this.engineProcessContainers)) { + const engineId = EngineId(engineIdStr); const promise = this.killEngine(engineId); if (promise === undefined) continue; @@ -307,7 +315,7 @@ export class EngineManager { * Promise.reject: エンジンプロセスのキルに失敗した(非同期) * undefined: エンジンプロセスのキルが開始されなかった=エンジンプロセスがすでに停止している(同期) */ - killEngine(engineId: string): Promise | undefined { + killEngine(engineId: EngineId): Promise | undefined { const engineProcessContainer = this.engineProcessContainers[engineId]; if (!engineProcessContainer) { log.error(`No such engineProcessContainer: engineId == ${engineId}`); @@ -364,7 +372,7 @@ export class EngineManager { * エンジンを再起動する。 * FIXME: winを受け取らなくても良いようにする */ - async restartEngine(engineId: string, win: BrowserWindow) { + async restartEngine(engineId: EngineId, win: BrowserWindow) { // FIXME: killEngine関数を使い回すようにする await new Promise((resolve, reject) => { const engineProcessContainer: EngineProcessContainer | undefined = diff --git a/src/background/vvppManager.ts b/src/background/vvppManager.ts index 4098a2be3e..2ea1d3822f 100644 --- a/src/background/vvppManager.ts +++ b/src/background/vvppManager.ts @@ -4,7 +4,7 @@ import log from "electron-log"; import { moveFile } from "move-file"; import { Extract } from "unzipper"; import { dialog } from "electron"; -import { EngineInfo, MinimumEngineManifest } from "@/type/preload"; +import { EngineId, EngineInfo, MinimumEngineManifest } from "@/type/preload"; import MultiStream from "multistream"; import glob, { glob as callbackGlob } from "glob"; @@ -57,7 +57,7 @@ export const isVvppFile = (filePath: string) => { export class VvppManager { vvppEngineDir: string; - willDeleteEngineIds: Set; + willDeleteEngineIds: Set; willReplaceEngineDirs: Array<{ from: string; to: string }>; constructor({ vvppEngineDir }: { vvppEngineDir: string }) { @@ -73,7 +73,7 @@ export class VvppManager { }); } - markWillDelete(engineId: string) { + markWillDelete(engineId: EngineId) { this.willDeleteEngineIds.add(engineId); } diff --git a/src/components/EngineManageDialog.vue b/src/components/EngineManageDialog.vue index d8e0e5e1aa..139c16460a 100644 --- a/src/components/EngineManageDialog.vue +++ b/src/components/EngineManageDialog.vue @@ -333,7 +333,7 @@ import { computed, defineComponent, ref, watch } from "vue"; import { useStore } from "@/store"; import { useQuasar } from "quasar"; import { base64ImageToUri } from "@/helpers/imageHelper"; -import type { EngineDirValidationResult } from "@/type/preload"; +import { EngineDirValidationResult, EngineId } from "@/type/preload"; import type { SupportedFeatures } from "@/openapi/models/SupportedFeatures"; type EngineLoaderType = "dir" | "vvpp"; @@ -395,12 +395,14 @@ export default defineComponent({ ]) ) ); - const engineVersions = ref>({}); + const engineVersions = ref>({}); watch( [engineInfos, engineStates, engineManifests], async () => { - for (const id of Object.keys(engineInfos.value)) { + // FIXME: engineInfosをMapにする + for (const idStr of Object.keys(engineInfos.value)) { + const id = EngineId(idStr); if (engineStates.value[id] !== "READY") continue; if (engineVersions.value[id]) continue; const version = await store @@ -418,14 +420,21 @@ export default defineComponent({ { immediate: true } ); - const selectedId = ref(""); + const selectedId = ref(undefined); + const isDeletable = computed(() => { + // FIXME: いたるところにエンジンが選択済みかを検証するコードがある。 + // 選択後に現れる領域は別ComponentにしてselectedIdをpropで渡せばこれは不要になる + if (selectedId.value == undefined) + throw new Error("engine is not selected"); return ( engineInfos.value[selectedId.value] && engineInfos.value[selectedId.value].type === "path" ); }); const engineDir = computed(() => { + if (selectedId.value == undefined) + throw new Error("engine is not selected"); return engineInfos.value[selectedId.value]?.path || "(組み込み)"; }); @@ -522,6 +531,8 @@ export default defineComponent({ textColor: "warning", }, }).onOk(async () => { + if (selectedId.value == undefined) + throw new Error("engine is not selected"); switch (engineInfos.value[selectedId.value].type) { case "path": { const engineDir = store.state.engineInfos[selectedId.value].path; @@ -556,15 +567,19 @@ export default defineComponent({ }); }; - const selectEngine = (id: string) => { + const selectEngine = (id: EngineId) => { selectedId.value = id; }; const openSelectedEngineDirectory = () => { + if (selectedId.value == undefined) + throw new Error("assert selectedId.value != undefined"); store.dispatch("OPEN_ENGINE_DIRECTORY", { engineId: selectedId.value }); }; const restartSelectedEngine = () => { + if (selectedId.value == undefined) + throw new Error("assert selectedId.value != undefined"); store.dispatch("RESTART_ENGINES", { engineIds: [selectedId.value], }); @@ -644,13 +659,13 @@ export default defineComponent({ // ステートの移動 // 初期状態 const toInitialState = () => { - selectedId.value = ""; + selectedId.value = undefined; isAddingEngine.value = false; }; // エンジン追加状態 const toAddEngineState = () => { isAddingEngine.value = true; - selectedId.value = ""; + selectedId.value = undefined; newEngineDirValidationState.value = null; newEngineDir.value = ""; vvppFilePath.value = ""; diff --git a/src/components/LibraryPolicy.vue b/src/components/LibraryPolicy.vue index afe811e26a..c5aa702654 100644 --- a/src/components/LibraryPolicy.vue +++ b/src/components/LibraryPolicy.vue @@ -76,8 +76,9 @@ import { useStore } from "@/store"; import { computed, ref } from "vue"; import { useMarkdownIt } from "@/plugins/markdownItPlugin"; +import { EngineId } from "@/type/preload"; -type DetailKey = { engine: string; character: string }; +type DetailKey = { engine: EngineId; character: string }; const store = useStore(); const md = useMarkdownIt(); @@ -88,16 +89,19 @@ const engineInfos = computed( () => new Map( Object.entries(store.state.characterInfos).map( - ([engineId, characterInfos]) => [ - engineId, - { + ([engineIdStr, characterInfos]) => { + const engineId = EngineId(engineIdStr); + return [ engineId, - name: store.state.engineManifests[engineId].name, - characterInfos: new Map( - characterInfos.map((ci) => [ci.metas.speakerUuid, ci]) - ), - }, - ] + { + engineId, + name: store.state.engineManifests[engineId].name, + characterInfos: new Map( + characterInfos.map((ci) => [ci.metas.speakerUuid, ci]) + ), + }, + ]; + } ) ) ); diff --git a/src/components/SettingDialog.vue b/src/components/SettingDialog.vue index 398c009a1c..853e09533a 100644 --- a/src/components/SettingDialog.vue +++ b/src/components/SettingDialog.vue @@ -730,6 +730,7 @@ import { ActivePointScrollMode, SplitTextWhenPasteType, EditorFontType, + EngineId, } from "@/type/preload"; import FileNamePatternDialog from "./FileNamePatternDialog.vue"; @@ -921,7 +922,7 @@ export default defineComponent({ { label: "GPU", value: true }, ]; - const gpuSwitchEnabled = (engineId: string) => { + const gpuSwitchEnabled = (engineId: EngineId) => { // CPU版でもGPUモードからCPUモードに変更できるようにする return store.getters.ENGINE_CAN_USE_GPU(engineId) || engineUseGpu.value; }; @@ -1023,16 +1024,16 @@ export default defineComponent({ const showsFilePatternEditDialog = ref(false); - const selectedEngineIdRaw = ref(""); + const selectedEngineIdRaw = ref(undefined); const selectedEngineId = computed({ get: () => { return selectedEngineIdRaw.value || engineIds.value[0]; }, - set: (engineId: string) => { + set: (engineId: EngineId) => { selectedEngineIdRaw.value = engineId; }, }); - const renderEngineNameLabel = (engineId: string) => { + const renderEngineNameLabel = (engineId: EngineId) => { return engineInfos.value[engineId].name; }; diff --git a/src/electron/preload.ts b/src/electron/preload.ts index 1b65ad32f0..3709500122 100644 --- a/src/electron/preload.ts +++ b/src/electron/preload.ts @@ -5,7 +5,7 @@ import { IpcRendererEvent, } from "electron"; -import { Sandbox, ElectronStoreType } from "@/type/preload"; +import { Sandbox, ElectronStoreType, EngineId } from "@/type/preload"; import { IpcIHData, IpcSOData } from "@/type/ipc"; function ipcRendererInvoke( @@ -192,11 +192,11 @@ const api: Sandbox = { return ipcRendererInvoke("ENGINE_INFOS"); }, - restartEngine: (engineId: string) => { + restartEngine: (engineId: EngineId) => { return ipcRendererInvoke("RESTART_ENGINE", { engineId }); }, - openEngineDirectory: (engineId: string) => { + openEngineDirectory: (engineId: EngineId) => { return ipcRendererInvoke("OPEN_ENGINE_DIRECTORY", { engineId }); }, diff --git a/src/store/audio.ts b/src/store/audio.ts index 8dc73dd176..33b1241477 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -17,6 +17,7 @@ import { CharacterInfo, DefaultStyleId, Encoding as EncodingType, + EngineId, MoraDataType, MorphingInfo, StyleInfo, @@ -168,7 +169,7 @@ function generateWriteErrorMessage(writeFileErrorResult: WriteFileErrorResult) { // TODO: GETTERに移動する。buildFileNameから参照されているので、そちらも一緒に移動する。 export function getCharacterInfo( state: State, - engineId: string, + engineId: EngineId, styleId: number ): CharacterInfo | undefined { const engineCharacterInfos = state.characterInfos[engineId]; @@ -306,7 +307,7 @@ export const audioStore = createPartialStore({ { engineId, characterInfos, - }: { engineId: string; characterInfos: CharacterInfo[] } + }: { engineId: EngineId; characterInfos: CharacterInfo[] } ) { state.characterInfos[engineId] = characterInfos; }, @@ -777,7 +778,7 @@ export const audioStore = createPartialStore({ text, engineId, styleId, - }: { text: string; engineId: string; styleId: number } + }: { text: string; engineId: EngineId; styleId: number } ) { return dispatch("INSTANTIATE_ENGINE_CONNECTOR", { engineId, @@ -828,7 +829,7 @@ export const audioStore = createPartialStore({ isKana, }: { text: string; - engineId: string; + engineId: EngineId; styleId: number; isKana?: boolean; } @@ -963,7 +964,7 @@ export const audioStore = createPartialStore({ accentPhrases, engineId, styleId, - }: { accentPhrases: AccentPhrase[]; engineId: string; styleId: number } + }: { accentPhrases: AccentPhrase[]; engineId: EngineId; styleId: number } ) { return dispatch("INSTANTIATE_ENGINE_CONNECTOR", { engineId, @@ -996,7 +997,7 @@ export const audioStore = createPartialStore({ copyIndexes, }: { accentPhrases: AccentPhrase[]; - engineId: string; + engineId: EngineId; styleId: number; copyIndexes: number[]; } @@ -1174,7 +1175,7 @@ export const audioStore = createPartialStore({ { dispatch, state }, { encodedBlobs }: { encodedBlobs: string[] } ) => { - const engineId: string | undefined = state.engineIds[0]; // TODO: 複数エンジン対応, 暫定的に音声結合機能は0番目のエンジンのみを使用する + const engineId: EngineId | undefined = state.engineIds[0]; // TODO: 複数エンジン対応, 暫定的に音声結合機能は0番目のエンジンのみを使用する if (engineId === undefined) throw new Error(`No such engine registered: index == 0`); diff --git a/src/store/dictionary.ts b/src/store/dictionary.ts index d5a5a84310..6985457fe0 100644 --- a/src/store/dictionary.ts +++ b/src/store/dictionary.ts @@ -1,5 +1,6 @@ import { UserDictWord, UserDictWordToJSON } from "@/openapi"; import { DictionaryStoreState, DictionaryStoreTypes } from "@/store/type"; +import { EngineId } from "@/type/preload"; import { createPartialStore } from "./vuex"; export const dictionaryStoreState: DictionaryStoreState = {}; @@ -64,7 +65,7 @@ export const dictionaryStore = createPartialStore({ { surface, pronunciation, accentType, priority } ) { // 同じ単語IDで登録するために、1つのエンジンで登録したあと全エンジンに同期する。 - const engineId: string | undefined = state.engineIds[0]; + const engineId: EngineId | undefined = state.engineIds[0]; if (engineId === undefined) throw new Error(`No such engine registered: index == 0`); diff --git a/src/store/engine.ts b/src/store/engine.ts index bd757b9690..abb20770c3 100644 --- a/src/store/engine.ts +++ b/src/store/engine.ts @@ -2,7 +2,7 @@ import { EngineState, EngineStoreState, EngineStoreTypes } from "./type"; import { createUILockAction } from "./ui"; import { createPartialStore } from "./vuex"; import type { EngineManifest } from "@/openapi"; -import type { EngineInfo } from "@/type/preload"; +import type { EngineId, EngineInfo } from "@/type/preload"; export const engineStoreState: EngineStoreState = { engineStates: {}, @@ -15,7 +15,7 @@ export const engineStore = createPartialStore({ const engineInfos = await window.electron.engineInfos(); // マルチエンジンオフモード時はengineIdsをデフォルトエンジンのIDだけにする。 - let engineIds: string[]; + let engineIds: EngineId[]; if (state.isMultiEngineOffMode) { engineIds = engineInfos .filter((engineInfo) => engineInfo.type === "default") @@ -50,7 +50,7 @@ export const engineStore = createPartialStore({ { engineIds, engineInfos, - }: { engineIds: string[]; engineInfos: EngineInfo[] } + }: { engineIds: EngineId[]; engineInfos: EngineInfo[] } ) { state.engineIds = engineIds; state.engineInfos = Object.fromEntries( @@ -65,7 +65,7 @@ export const engineStore = createPartialStore({ SET_ENGINE_MANIFESTS: { mutation( state, - { engineManifests }: { engineManifests: Record } + { engineManifests }: { engineManifests: Record } ) { state.engineManifests = engineManifests; }, @@ -256,7 +256,10 @@ export const engineStore = createPartialStore({ SET_ENGINE_STATE: { mutation( state, - { engineId, engineState }: { engineId: string; engineState: EngineState } + { + engineId, + engineState, + }: { engineId: EngineId; engineState: EngineState } ) { state.engineStates[engineId] = engineState; }, @@ -347,7 +350,7 @@ export const engineStore = createPartialStore({ { engineId, engineManifest, - }: { engineId: string; engineManifest: EngineManifest } + }: { engineId: EngineId; engineManifest: EngineManifest } ) { state.engineManifests = { ...state.engineManifests, diff --git a/src/store/project.ts b/src/store/project.ts index e0a6c577b5..642466e406 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -6,6 +6,7 @@ import { createPartialStore } from "./vuex"; import { AccentPhrase } from "@/openapi"; import { z } from "zod"; +import { EngineId, engineIdSchema } from "@/type/preload"; const DEFAULT_SAMPLING_RATE = 24000; @@ -115,7 +116,7 @@ export const projectStore = createPartialStore({ }; // Migration - const engineId = "074fc39e-678b-4c13-8916-ffca8d505d1d"; + const engineId = EngineId("074fc39e-678b-4c13-8916-ffca8d505d1d"); if ( semver.satisfies(projectAppVersion, "<0.4", semverSatisfiesOptions) @@ -471,7 +472,7 @@ const audioQuerySchema = z.object({ const morphingInfoSchema = z.object({ rate: z.number(), - targetEngineId: z.string(), + targetEngineId: engineIdSchema, targetSpeakerId: z.string(), targetStyleId: z.number(), }); @@ -479,7 +480,7 @@ const morphingInfoSchema = z.object({ const audioItemSchema = z.object({ text: z.string(), voice: z.object({ - engineId: z.string().uuid(), + engineId: engineIdSchema, speakerId: z.string().uuid(), styleId: z.number(), }), diff --git a/src/store/setting.ts b/src/store/setting.ts index 7a865bf639..efee1e57c9 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -7,6 +7,7 @@ import { ThemeColorType, ThemeConf, ToolbarSetting, + EngineId, } from "@/type/preload"; import { SettingStoreState, SettingStoreTypes } from "./type"; import Mousetrap from "mousetrap"; @@ -118,11 +119,16 @@ export const settingStore = createPartialStore({ confirmedTips: await window.electron.getSetting("confirmedTips"), }); - for (const [engineId, engineSetting] of Object.entries( + // FIXME: engineSettingsをMapにする + for (const [engineIdStr, engineSetting] of Object.entries( await window.electron.getSetting("engineSettings") )) { + if (engineSetting == undefined) + throw new Error( + `engineSetting is undefined. engineIdStr: ${engineIdStr}` + ); commit("SET_ENGINE_SETTING", { - engineId, + engineId: EngineId(engineIdStr), engineSetting, }); } diff --git a/src/store/type.ts b/src/store/type.ts index 508ad35b73..076590511b 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -43,6 +43,7 @@ import { MorphableTargetInfoTable, EngineSetting, Voice, + EngineId, } from "@/type/preload"; import { IEngineConnectorFactory } from "@/infrastructures/EngineConnector"; import { QVueGlobals } from "quasar"; @@ -110,7 +111,7 @@ export type QuasarDialog = QVueGlobals["dialog"]; */ export type AudioStoreState = { - characterInfos: Record; + characterInfos: Record; morphableTargetsInfo: Record; audioKeyInitializingSpeaker?: string; audioItems: Record; @@ -139,15 +140,15 @@ export type AudioStoreTypes = { }; LOAD_CHARACTER: { - action(payload: { engineId: string }): void; + action(payload: { engineId: EngineId }): void; }; SET_CHARACTER_INFOS: { - mutation: { engineId: string; characterInfos: CharacterInfo[] }; + mutation: { engineId: EngineId; characterInfos: CharacterInfo[] }; }; CHARACTER_INFO: { - getter(engineId: string, styleId: number): CharacterInfo | undefined; + getter(engineId: EngineId, styleId: number): CharacterInfo | undefined; }; USER_ORDERED_CHARACTER_INFOS: { @@ -161,7 +162,7 @@ export type AudioStoreTypes = { SETUP_SPEAKER: { action(payload: { audioKey: string; - engineId: string; + engineId: EngineId; styleId: number; }): void; }; @@ -272,12 +273,12 @@ export type AudioStoreTypes = { }; LOAD_MORPHABLE_TARGETS: { - action(payload: { engineId: string; baseStyleId: number }): void; + action(payload: { engineId: EngineId; baseStyleId: number }): void; }; SET_MORPHABLE_TARGETS: { mutation: { - engineId: string; + engineId: EngineId; baseStyleId: number; morphableTargets?: Exclude< { [key: number]: MorphableTargetInfo }, @@ -309,7 +310,7 @@ export type AudioStoreTypes = { FETCH_AUDIO_QUERY: { action(payload: { text: string; - engineId: string; + engineId: EngineId; styleId: number; }): Promise; }; @@ -325,7 +326,7 @@ export type AudioStoreTypes = { FETCH_ACCENT_PHRASES: { action(payload: { text: string; - engineId: string; + engineId: EngineId; styleId: number; isKana?: boolean; }): Promise; @@ -356,7 +357,7 @@ export type AudioStoreTypes = { FETCH_MORA_DATA: { action(payload: { accentPhrases: AccentPhrase[]; - engineId: string; + engineId: EngineId; styleId: number; }): Promise; }; @@ -364,7 +365,7 @@ export type AudioStoreTypes = { FETCH_AND_COPY_MORA_DATA: { action(payload: { accentPhrases: AccentPhrase[]; - engineId: string; + engineId: EngineId; styleId: number; copyIndexes: number[]; }): Promise; @@ -704,8 +705,8 @@ export type CommandStoreTypes = { */ export type EngineStoreState = { - engineStates: Record; - engineSupportedDevices: Record; + engineStates: Record; + engineSupportedDevices: Record; }; export type EngineStoreTypes = { @@ -718,7 +719,7 @@ export type EngineStoreTypes = { }; SET_ENGINE_MANIFESTS: { - mutation: { engineManifests: Record }; + mutation: { engineManifests: Record }; }; FETCH_AND_SET_ENGINE_MANIFESTS: { @@ -730,45 +731,45 @@ export type EngineStoreTypes = { }; IS_ENGINE_READY: { - getter(engineId: string): boolean; + getter(engineId: EngineId): boolean; }; START_WAITING_ENGINE: { - action(payload: { engineId: string }): void; + action(payload: { engineId: EngineId }): void; }; RESTART_ENGINES: { - action(payload: { engineIds: string[] }): Promise<{ + action(payload: { engineIds: EngineId[] }): Promise<{ success: boolean; anyNewCharacters: boolean; }>; }; POST_ENGINE_START: { - action(payload: { engineIds: string[] }): Promise<{ + action(payload: { engineIds: EngineId[] }): Promise<{ success: boolean; anyNewCharacters: boolean; }>; }; DETECTED_ENGINE_ERROR: { - action(payload: { engineId: string }): void; + action(payload: { engineId: EngineId }): void; }; OPEN_ENGINE_DIRECTORY: { - action(payload: { engineId: string }): void; + action(payload: { engineId: EngineId }): void; }; SET_ENGINE_STATE: { - mutation: { engineId: string; engineState: EngineState }; + mutation: { engineId: EngineId; engineState: EngineState }; }; IS_INITIALIZED_ENGINE_SPEAKER: { - action(payload: { engineId: string; styleId: number }): Promise; + action(payload: { engineId: EngineId; styleId: number }): Promise; }; INITIALIZE_ENGINE_SPEAKER: { - action(payload: { engineId: string; styleId: number }): void; + action(payload: { engineId: EngineId; styleId: number }): void; }; VALIDATE_ENGINE_DIR: { @@ -788,31 +789,31 @@ export type EngineStoreTypes = { }; UNINSTALL_VVPP_ENGINE: { - action: (engineId: string) => Promise; + action: (engineId: EngineId) => Promise; }; SET_ENGINE_INFOS: { - mutation: { engineIds: string[]; engineInfos: EngineInfo[] }; + mutation: { engineIds: EngineId[]; engineInfos: EngineInfo[] }; }; SET_ENGINE_MANIFEST: { - mutation: { engineId: string; engineManifest: EngineManifest }; + mutation: { engineId: EngineId; engineManifest: EngineManifest }; }; FETCH_AND_SET_ENGINE_MANIFEST: { - action(payload: { engineId: string }): void; + action(payload: { engineId: EngineId }): void; }; SET_ENGINE_SUPPORTED_DEVICES: { - mutation: { engineId: string; supportedDevices: SupportedDevicesInfo }; + mutation: { engineId: EngineId; supportedDevices: SupportedDevicesInfo }; }; FETCH_AND_SET_ENGINE_SUPPORTED_DEVICES: { - action(payload: { engineId: string }): void; + action(payload: { engineId: EngineId }): void; }; ENGINE_CAN_USE_GPU: { - getter: (engineId: string) => boolean; + getter: (engineId: EngineId) => boolean; }; }; @@ -958,9 +959,9 @@ export type SettingStoreState = { savingSetting: SavingSetting; hotkeySettings: HotkeySetting[]; toolbarSetting: ToolbarSetting; - engineIds: string[]; - engineInfos: Record; - engineManifests: Record; + engineIds: EngineId[]; + engineInfos: Record; + engineManifests: Record; themeSetting: ThemeSetting; editorFont: EditorFontType; acceptRetrieveTelemetry: AcceptRetrieveTelemetryStatus; @@ -1034,15 +1035,15 @@ export type SettingStoreTypes = { }; SET_ENGINE_SETTING: { - mutation: { engineSetting: EngineSetting; engineId: string }; + mutation: { engineSetting: EngineSetting; engineId: EngineId }; action(payload: { engineSetting: EngineSetting; - engineId: string; + engineId: EngineId; }): Promise; }; CHANGE_USE_GPU: { - action(payload: { useGpu: boolean; engineId: string }): Promise; + action(payload: { useGpu: boolean; engineId: EngineId }): Promise; }; }; @@ -1269,7 +1270,7 @@ export type DictionaryStoreState = Record; export type DictionaryStoreTypes = { LOAD_USER_DICT: { action(payload: { - engineId: string; + engineId: EngineId; }): Promise>; }; LOAD_ALL_USER_DICT: { @@ -1321,7 +1322,7 @@ type IEngineConnectorFactoryActionsMapper = < export type ProxyStoreTypes = { INSTANTIATE_ENGINE_CONNECTOR: { action(payload: { - engineId: string; + engineId: EngineId; }): Promise<{ invoke: IEngineConnectorFactoryActionsMapper }>; }; }; diff --git a/src/type/ipc.ts b/src/type/ipc.ts index 490459e548..02f30be1f1 100644 --- a/src/type/ipc.ts +++ b/src/type/ipc.ts @@ -10,6 +10,7 @@ import { WriteFileErrorResult, NativeThemeType, EngineSetting, + EngineId, } from "@/type/preload"; /** @@ -201,12 +202,12 @@ export type IpcIHData = { }; RESTART_ENGINE: { - args: [obj: { engineId: string }]; + args: [obj: { engineId: EngineId }]; return: void; }; OPEN_ENGINE_DIRECTORY: { - args: [obj: { engineId: string }]; + args: [obj: { engineId: EngineId }]; return: void; }; @@ -259,7 +260,7 @@ export type IpcIHData = { }; SET_ENGINE_SETTING: { - args: [engineId: string, engineSetting: EngineSetting]; + args: [engineId: EngineId, engineSetting: EngineSetting]; return: void; }; @@ -274,7 +275,7 @@ export type IpcIHData = { }; UNINSTALL_VVPP_ENGINE: { - args: [engineId: string]; + args: [engineId: EngineId]; return: Promise; }; @@ -324,7 +325,7 @@ export type IpcSOData = { }; DETECTED_ENGINE_ERROR: { - args: [obj: { engineId: string }]; + args: [obj: { engineId: EngineId }]; return: void; }; diff --git a/src/type/preload.ts b/src/type/preload.ts index 5f3cd389c7..8c5b395af7 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -3,6 +3,11 @@ import { IpcSOData } from "./ipc"; import { z } from "zod"; export const isMac = process.platform === "darwin"; + +export const engineIdSchema = z.string().uuid().brand<"EngineId">(); +export type EngineId = z.infer; +export const EngineId = (id: string): EngineId => engineIdSchema.parse(id); + // ホットキーを追加したときは設定のマイグレーションが必要 export const defaultHotkeySettings: HotkeySetting[] = [ { @@ -166,8 +171,8 @@ export interface Sandbox { logWarn(...params: unknown[]): void; logInfo(...params: unknown[]): void; engineInfos(): Promise; - restartEngine(engineId: string): Promise; - openEngineDirectory(engineId: string): void; + restartEngine(engineId: EngineId): Promise; + openEngineDirectory(engineId: EngineId): void; hotkeySettings(newData?: HotkeySetting): Promise; checkFileExists(file: string): Promise; changePinWindow(): void; @@ -184,11 +189,11 @@ export interface Sandbox { newValue: ElectronStoreType[Key] ): Promise; setEngineSetting( - engineId: string, + engineId: EngineId, engineSetting: EngineSetting ): Promise; installVvppEngine(path: string): Promise; - uninstallVvppEngine(engineId: string): Promise; + uninstallVvppEngine(engineId: EngineId): Promise; validateEngineDir(engineDir: string): Promise; restartApp(obj: { isMultiEngineOffMode: boolean }): void; } @@ -203,7 +208,7 @@ export type StyleInfo = { styleId: number; iconPath: string; portraitPath: string | undefined; - engineId: string; + engineId: EngineId; voiceSamplePaths: string[]; }; @@ -230,7 +235,7 @@ export type UpdateInfo = { }; export type Voice = { - engineId: string; + engineId: EngineId; speakerId: string; styleId: number; }; @@ -262,9 +267,9 @@ export type SavingSetting = { audioOutputDevice: string; }; -export type EngineSettings = Record; +export type EngineSettings = Record; -export const engineSetting = z +export const engineSettingSchema = z .object({ useGpu: z.boolean().default(false), outputSamplingRate: z @@ -272,23 +277,23 @@ export const engineSetting = z .default("engineDefault"), }) .passthrough(); -export type EngineSetting = z.infer; +export type EngineSetting = z.infer; export type DefaultStyleId = { - engineId: string; + engineId: EngineId; speakerUuid: string; defaultStyleId: number; }; export type MinimumEngineManifest = { name: string; - uuid: string; + uuid: EngineId; command: string; port: string; }; export type EngineInfo = { - uuid: string; + uuid: EngineId; host: string; name: string; path?: string; // エンジンディレクトリのパス @@ -315,7 +320,7 @@ export type Preset = { export type MorphingInfo = { rate: number; - targetEngineId: string; + targetEngineId: EngineId; targetSpeakerId: string; targetStyleId: number; }; @@ -475,12 +480,14 @@ export const electronStoreSchema = z toolbarSetting: toolbarSettingSchema .array() .default(defaultToolbarButtonSetting), - engineSettings: z.record(engineSetting).default({}), + engineSettings: z.record(engineIdSchema, engineSettingSchema).default({}), userCharacterOrder: z.string().array().default([]), defaultStyleIds: z .object({ - // FIXME: マイグレーション前にバリテーションされてしまう問題に対処したら".or(z.literal("")).default("")"を外す - engineId: z.string().uuid().or(z.literal("")).default(""), + // FIXME: マイグレーション前にバリテーションされてしまう問題に対処したら.or(z.literal)を外す + engineId: engineIdSchema + .or(z.literal(EngineId("00000000-0000-0000-0000-000000000000"))) + .default(EngineId("00000000-0000-0000-0000-000000000000")), speakerUuid: z.string().uuid(), defaultStyleId: z.number(), }) @@ -504,7 +511,7 @@ export const electronStoreSchema = z morphingInfo: z .object({ rate: z.number(), - targetEngineId: z.string().uuid(), + targetEngineId: engineIdSchema, targetSpeakerId: z.string().uuid(), targetStyleId: z.number(), }) diff --git a/src/views/EditorHome.vue b/src/views/EditorHome.vue index 2ff98e6c20..fa22c558c1 100644 --- a/src/views/EditorHome.vue +++ b/src/views/EditorHome.vue @@ -196,6 +196,7 @@ import { AudioItem, EngineState } from "@/store/type"; import { QResizeObserver, useQuasar } from "quasar"; import path from "path"; import { + EngineId, HotkeyAction, HotkeyReturnType, SplitterPosition, @@ -521,7 +522,7 @@ export default defineComponent({ onMounted(async () => { await store.dispatch("GET_ENGINE_INFOS"); - let engineIds: string[]; + let engineIds: EngineId[]; if (store.state.isMultiEngineOffMode) { // デフォルトエンジンだけを含める const main = Object.values(store.state.engineInfos).find( diff --git a/tests/unit/store/Vuex.spec.ts b/tests/unit/store/Vuex.spec.ts index cb776f241a..44f1b6f105 100644 --- a/tests/unit/store/Vuex.spec.ts +++ b/tests/unit/store/Vuex.spec.ts @@ -12,15 +12,17 @@ import { assert } from "chai"; import { proxyStore } from "@/store/proxy"; import { dictionaryStore } from "@/store/dictionary"; import { engineStore } from "@/store/engine"; +import { EngineId } from "@/type/preload"; const isDevelopment = process.env.NODE_ENV == "development"; // TODO: Swap external files to Mock describe("store/vuex.js test", () => { it("create store", () => { + const engineId = EngineId("88022f86-c823-436e-85a3-500c629749c4"); const store = createStore({ state: { engineStates: { - "88022f86-c823-436e-85a3-500c629749c4": "STARTING", + [engineId]: "STARTING", }, engineSupportedDevices: {}, characterInfos: {}, @@ -63,7 +65,7 @@ describe("store/vuex.js test", () => { audioOutputDevice: "default", }, engineSettings: { - "88022f86-c823-436e-85a3-500c629749c4": { + [engineId]: { outputSamplingRate: "engineDefault", useGpu: false, }, @@ -81,10 +83,10 @@ describe("store/vuex.js test", () => { toolbarSetting: [], acceptRetrieveTelemetry: "Unconfirmed", acceptTerms: "Unconfirmed", - engineIds: ["88022f86-c823-436e-85a3-500c629749c4"], + engineIds: [engineId], engineInfos: { - "88022f86-c823-436e-85a3-500c629749c4": { - uuid: "88022f86-c823-436e-85a3-500c629749c4", + [engineId]: { + uuid: engineId, name: "Engine 1", executionEnabled: false, executionFilePath: "", @@ -94,7 +96,7 @@ describe("store/vuex.js test", () => { }, }, engineManifests: { - "88022f86-c823-436e-85a3-500c629749c4": { + [engineId]: { manifestVersion: "0.13.0", name: "DUMMY VOICEVOX ENGINE", brandName: "DUMMY VOICEVOX",