diff --git a/package-lock.json b/package-lock.json
index 7ff999340143e912f425e9a777df5fe9667d7ebe..430ee69834b3bf15c65772fb827373b9fcd25186 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,15 +1,16 @@
{
"name": "tts-vue",
- "version": "1.5.0",
+ "version": "1.8.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "tts-vue",
- "version": "1.5.0",
+ "version": "1.8.3",
"license": "MIT",
"dependencies": {
"axios": "^0.27.2",
+ "electron-log": "^4.4.8",
"electron-store": "^8.0.2",
"element-plus": "2.2.9",
"nodejs-websocket": "^1.7.2",
@@ -20,7 +21,6 @@
"@vitejs/plugin-vue": "^2.3.3",
"electron": "^19.0.8",
"electron-builder": "^23.1.0",
- "typescript": "^4.7.4",
"vite": "^2.9.13",
"vite-plugin-electron": "^0.8.1",
"vue": "^3.2.37",
@@ -1860,6 +1860,11 @@
"node": ">= 10.0.0"
}
},
+ "node_modules/electron-log": {
+ "version": "4.4.8",
+ "resolved": "https://registry.npmmirror.com/electron-log/-/electron-log-4.4.8.tgz",
+ "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA=="
+ },
"node_modules/electron-osx-sign": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz",
@@ -4347,6 +4352,7 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"devOptional": true,
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6123,6 +6129,11 @@
}
}
},
+ "electron-log": {
+ "version": "4.4.8",
+ "resolved": "https://registry.npmmirror.com/electron-log/-/electron-log-4.4.8.tgz",
+ "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA=="
+ },
"electron-osx-sign": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz",
@@ -7901,7 +7912,8 @@
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
- "devOptional": true
+ "devOptional": true,
+ "peer": true
},
"unique-string": {
"version": "2.0.0",
diff --git a/package.json b/package.json
index 6e9845442ef0dcdcf7a43e2a67fc82fe760799b8..8f38984738e4ab2a97d7565b98d2d6478db8cd03 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,6 @@
"@vitejs/plugin-vue": "^2.3.3",
"electron": "^19.0.8",
"electron-builder": "^23.1.0",
- "typescript": "^4.7.4",
"vite": "^2.9.13",
"vite-plugin-electron": "^0.8.1",
"vue": "^3.2.37",
@@ -43,4 +42,4 @@
"pinia": "^2.0.17",
"uuid": "^8.3.2"
}
-}
\ No newline at end of file
+}
diff --git a/src/components/main/MainOptions.vue b/src/components/main/MainOptions.vue
index ce8ecd480876da3cd1e058f4d1b82f9e0d91343b..77219c5d056ed845de9c73251c4a46fd9a0920c3 100644
--- a/src/components/main/MainOptions.vue
+++ b/src/components/main/MainOptions.vue
@@ -2,58 +2,34 @@
-
+
-
-
+
+
{{
- item.DisplayName + "-" + item.LocalName
+ item.DisplayName + "-" + item.LocalName
}}
-
+
+
+
+
+
-
+
General
-
+
{{
- getStyleDes(item)?.emoji
+ getStyleDes(item)?.emoji
}}
{{ getStyleDes(item)?.word }}
@@ -63,15 +39,10 @@
Default
-
+
{{
- getRoleDes(item)?.emoji
+ getRoleDes(item)?.emoji
}}
{{ getRoleDes(item)?.word }}
@@ -79,43 +50,18 @@
-
+
-
+
- 保存配置
-
+ 保存配置
+
@@ -147,6 +93,8 @@ const {
config,
isLoading,
} = storeToRefs(ttsStore);
+
+
const Store = require("electron-store");
const store = new Store();
@@ -157,20 +105,18 @@ const audition = (value: string) => {
watch(formConfig.value, (newValue) => {
inputs.value.ssmlValue = `
-
+
+ (newValue.speed - 1) *
+ 100
+ ).toFixed()}%" pitch="${(
+ (newValue.pitch - 1) *
+ 50
+ ).toFixed()}%">
${inputs.value.inputValue}
@@ -296,45 +242,57 @@ const startBtn = () => {
border: 1px solid #dcdfe6;
border-radius: 5px;
}
+
.el-form {
height: 99%;
display: flex;
flex-direction: column;
}
+
.configOption {
display: flex;
flex-direction: column;
}
+
.el-form-item {
width: 270px;
}
+
.el-slider {
margin-left: 5px;
}
+
.el-select {
width: 100% !important;
}
+
.languageSelect {
width: 100% !important;
}
+
.get-cfg {
margin-top: 2px;
width: 100px;
}
+
:deep(.el-form-item__label) {
margin-bottom: 2px !important;
}
+
:deep(.el-slider__runway.show-input) {
margin-right: 10px;
}
+
:deep(.el-slider > .el-input-number) {
width: 40px;
}
+
:deep(.el-slider .el-input__wrapper) {
width: 100%;
padding: 0 !important;
margin: 0 !important;
}
+
/* From uiverse.io by @Zena4L */
.startBtn {
margin-bottom: 0 !important;
@@ -342,9 +300,11 @@ const startBtn = () => {
display: flex !important;
align-items: flex-end;
}
+
:deep(.startBtn > .el-form-item__content) {
justify-content: space-around;
}
+
.btn:link,
.btn:visited {
text-transform: uppercase;
diff --git a/src/store/store.ts b/src/store/store.ts
index 92f9ce65e0cdd87405fea9f12425564e3abb9389..4e17dc3e503097ac5a3c1a7783e0e9c2faf29cfa 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -1,44 +1,44 @@
// @/store/firstStore.js
-import { defineStore } from "pinia";
-import getTTSData from "./play";
-import { ElMessage } from "element-plus";
-const fs = require("fs");
-const path = require("path");
-const Store = require("electron-store");
-const { ipcRenderer } = require("electron");
-const store = new Store();
+import { defineStore } from 'pinia'
+import getTTSData from './play'
+import { ElMessage } from 'element-plus'
+const fs = require('fs')
+const path = require('path')
+const Store = require('electron-store')
+const { ipcRenderer } = require('electron')
+const store = new Store()
// 定义并导出容器,第一个参数是容器id,必须唯一,用来将所有的容器
// 挂载到根容器上
-export const useTtsStore = defineStore("ttsStore", {
+export const useTtsStore = defineStore('ttsStore', {
// 定义state,用来存储状态的
state: () => {
return {
inputs: {
- inputValue: "你好啊\n今天天气怎么样?",
- ssmlValue: "你好啊\n今天天气怎么样?",
+ inputValue: '你好啊\n今天天气怎么样?',
+ ssmlValue: '你好啊\n今天天气怎么样?'
},
- formConfig: store.get("FormConfig.默认"),
+ formConfig: store.get('FormConfig.默认'),
page: {
- asideIndex: "1",
- tabIndex: "1",
+ asideIndex: '1',
+ tabIndex: '1'
},
tableData: [], // 文件列表的数据
- currConfigName: "默认", // 当前配置的名字
+ currConfigName: '默认', // 当前配置的名字
config: {
- formConfigJson: store.get("FormConfig"),
+ formConfigJson: store.get('FormConfig'),
formConfigList: [],
configLable: [],
- savePath: store.get("savePath"),
- audition: store.get("audition"),
- autoplay: store.get("autoplay"),
- updateNotification: store.get("updateNotification"),
+ savePath: store.get('savePath'),
+ audition: store.get('audition'),
+ autoplay: store.get('autoplay'),
+ updateNotification: store.get('updateNotification')
},
isLoading: false,
currMp3Buffer: Buffer.alloc(0),
- currMp3Url: "",
- audioPlayer: null,
- };
+ currMp3Url: '',
+ audioPlayer: null
+ }
},
// 定义getters,类似于computed,具有缓存g功能
getters: {},
@@ -47,104 +47,95 @@ export const useTtsStore = defineStore("ttsStore", {
setDoneStatus(filePath: string) {
for (const item of this.tableData) {
if (item.filePath == filePath) {
- item.status = "done";
- return;
+ item.status = 'done'
+ return
}
}
},
- setSSMLValue(text = "") {
- if (text === "") text = this.inputs.inputValue;
- const voice = this.formConfig.voiceSelect;
- const express = this.formConfig.voiceStyleSelect;
- const role = this.formConfig.role;
- const rate = (this.formConfig.speed - 1) * 100;
- const pitch = (this.formConfig.pitch - 1) * 50;
+ setSSMLValue(text = '') {
+ if (text === '') text = this.inputs.inputValue
+ const voice = this.formConfig.voiceSelect
+ const express = this.formConfig.voiceStyleSelect
+ const role = this.formConfig.role
+ const rate = (this.formConfig.speed - 1) * 100
+ const pitch = (this.formConfig.pitch - 1) * 50
this.inputs.ssmlValue = `
-
+
${text}
- `;
+ `
},
setSavePath() {
- store.set("savePath", this.config.savePath);
+ store.set('savePath', this.config.savePath)
},
setAuditionConfig() {
- store.set("audition", this.config.audition);
+ store.set('audition', this.config.audition)
},
updateNotificationChange() {
- store.set("updateNotification", this.config.updateNotification);
+ store.set('updateNotification', this.config.updateNotification)
},
setAutoPlay() {
- store.set("autoplay", this.config.autoplay);
+ store.set('autoplay', this.config.autoplay)
},
addFormConfig() {
- this.config.formConfigJson[this.currConfigName] = this.formConfig;
- this.genFormConfig();
+ this.config.formConfigJson[this.currConfigName] = this.formConfig
+ this.genFormConfig()
},
genFormConfig() {
// store.set("FormConfig", this.config.formConfigJson);
- this.config.formConfigList = Object.keys(this.config.formConfigJson).map(
- (item) => ({
- tagName: item,
- content: this.config.formConfigJson[item],
- })
- );
- this.config.configLable = Object.keys(this.config.formConfigJson).map(
- (item) => ({
- value: item,
- label: item,
- })
- );
+ this.config.formConfigList = Object.keys(this.config.formConfigJson).map(item => ({
+ tagName: item,
+ content: this.config.formConfigJson[item]
+ }))
+ this.config.configLable = Object.keys(this.config.formConfigJson).map(item => ({
+ value: item,
+ label: item
+ }))
},
async start() {
// this.page.asideIndex == "1"单文本转换
- if (this.page.asideIndex == "1") {
- this.currMp3Url = "";
+ if (this.page.asideIndex == '1') {
+ this.currMp3Url = ''
const value = {
activeIndex: this.page.tabIndex,
- inputValue:
- this.page.tabIndex == "1"
- ? this.inputs.inputValue
- : this.inputs.ssmlValue,
- };
- if (this.page.tabIndex == "1" && this.inputs.inputValue.length > 400) {
- const delimiters = ",。?,.?".split("");
- const maxSize = 300;
- ipcRenderer.send("log.info", "字数过多,正在对文本切片。。。");
+ inputValue: this.page.tabIndex == '1' ? this.inputs.inputValue : this.inputs.ssmlValue
+ }
+ if (this.page.tabIndex == '1' && this.inputs.inputValue.length > 400) {
+ const delimiters = ',。?,.?'.split('')
+ const maxSize = 300
+ ipcRenderer.send('log.info', '字数过多,正在对文本切片。。。')
- const textHandler = this.inputs.inputValue.split("").reduce(
+ const textHandler = this.inputs.inputValue.split('').reduce(
(obj: any, char, index, arr) => {
- obj.buffer.push(char);
- if (delimiters.indexOf(char) >= 0) obj.end = index;
+ obj.buffer.push(char)
+ if (delimiters.indexOf(char) >= 0) obj.end = index
if (obj.buffer.length === maxSize) {
- obj.res.push(
- obj.buffer.splice(0, obj.end + 1 - obj.offset).join("")
- );
- obj.offset += obj.res[obj.res.length - 1].length;
+ obj.res.push(obj.buffer.splice(0, obj.end + 1 - obj.offset).join(''))
+ obj.offset += obj.res[obj.res.length - 1].length
}
- return obj;
+ return obj
},
{
buffer: [],
end: 0,
offset: 0,
- res: [],
+ res: []
}
- );
- textHandler.res.push(textHandler.buffer.join(""));
- const tasks = textHandler.res;
+ )
+ textHandler.res.push(textHandler.buffer.join(''))
+ const tasks = textHandler.res
for (let index = 0; index < tasks.length; index++) {
- ipcRenderer.send("log.info", `正在执行第${index + 1}次转换。。。`);
- const element = tasks[index];
- value.inputValue = element;
+ ipcRenderer.send('log.info', `正在执行第${index + 1}次转换。。。`)
+ const element = tasks[index]
+ value.inputValue = element
const buffers: any = await getTTSData(
value,
this.formConfig.voiceSelect,
@@ -152,19 +143,14 @@ export const useTtsStore = defineStore("ttsStore", {
this.formConfig.role,
(this.formConfig.speed - 1) * 100,
(this.formConfig.pitch - 1) * 50
- );
- this.currMp3Buffer = Buffer.concat([this.currMp3Buffer, buffers]);
- ipcRenderer.send(
- "log.info",
- `第${index + 1}次转换完成,此时Buffer长度为:${
- this.currMp3Buffer.length
- }`
- );
+ )
+ this.currMp3Buffer = Buffer.concat([this.currMp3Buffer, buffers])
+ ipcRenderer.send('log.info', `第${index + 1}次转换完成,此时Buffer长度为:${this.currMp3Buffer.length}`)
}
- const svlob = new Blob([this.currMp3Buffer]);
- this.currMp3Url = URL.createObjectURL(svlob);
- this.isLoading = false;
+ const svlob = new Blob([this.currMp3Buffer])
+ this.currMp3Url = URL.createObjectURL(svlob)
+ this.isLoading = false
} else {
// 字数少直接转换
await getTTSData(
@@ -175,144 +161,125 @@ export const useTtsStore = defineStore("ttsStore", {
(this.formConfig.speed - 1) * 100,
(this.formConfig.pitch - 1) * 50
).then((mp3buffer: any) => {
- this.currMp3Buffer = mp3buffer;
- const svlob = new Blob([mp3buffer]);
- this.currMp3Url = URL.createObjectURL(svlob);
- this.isLoading = false;
- });
+ this.currMp3Buffer = mp3buffer
+ const svlob = new Blob([mp3buffer])
+ this.currMp3Url = URL.createObjectURL(svlob)
+ this.isLoading = false
+ })
}
ElMessage({
- message: this.config.autoplay
- ? "成功,正在试听~"
- : "成功,请手动播放。",
- type: "success",
- duration: 2000,
- });
- ipcRenderer.send("log.info", `转换完成`);
+ message: this.config.autoplay ? '成功,正在试听~' : '成功,请手动播放。',
+ type: 'success',
+ duration: 2000
+ })
+ ipcRenderer.send('log.info', `转换完成`)
} else {
// this.page.asideIndex == "2" 批量转换
- this.page.tabIndex == "1";
+ this.page.tabIndex == '1'
// 分割方法
this.tableData.forEach(async (item: any) => {
const inps = {
activeIndex: 1, // 值转换普通文本
- inputValue: "",
- tableValue: item,
- };
- const filePath = path.join(
- this.config.savePath,
- item.fileName.split(path.extname(item.fileName))[0] + ".mp3"
- );
- await fs.readFile(
- item.filePath,
- "utf8",
- async (err: any, datastr: any) => {
- if (err) console.log(err);
+ inputValue: '',
+ tableValue: item
+ }
+ const filePath = path.join(this.config.savePath, item.fileName.split(path.extname(item.fileName))[0] + '.mp3')
+ await fs.readFile(item.filePath, 'utf8', async (err: any, datastr: any) => {
+ if (err) console.log(err)
- inps.inputValue = datastr;
- let buffer = Buffer.alloc(0);
+ inps.inputValue = datastr
+ let buffer = Buffer.alloc(0)
- if (datastr.length > 400) {
- const delimiters = ",。?,.?".split("");
- const maxSize = 300;
- ipcRenderer.send("log.info", "字数过多,正在对文本切片。。。");
+ if (datastr.length > 400) {
+ const delimiters = ',。?,.?'.split('')
+ const maxSize = 300
+ ipcRenderer.send('log.info', '字数过多,正在对文本切片。。。')
- const textHandler = datastr.split("").reduce(
- (obj: any, char: any, index: any, arr: any) => {
- obj.buffer.push(char);
- if (delimiters.indexOf(char) >= 0) obj.end = index;
- if (obj.buffer.length === maxSize) {
- obj.res.push(
- obj.buffer.splice(0, obj.end + 1 - obj.offset).join("")
- );
- obj.offset += obj.res[obj.res.length - 1].length;
- }
- return obj;
- },
- {
- buffer: [],
- end: 0,
- offset: 0,
- res: [],
+ const textHandler = datastr.split('').reduce(
+ (obj: any, char: any, index: any, arr: any) => {
+ obj.buffer.push(char)
+ if (delimiters.indexOf(char) >= 0) obj.end = index
+ if (obj.buffer.length === maxSize) {
+ obj.res.push(obj.buffer.splice(0, obj.end + 1 - obj.offset).join(''))
+ obj.offset += obj.res[obj.res.length - 1].length
}
- );
- textHandler.res.push(textHandler.buffer.join(""));
- const tasks = textHandler.res;
- for (let index = 0; index < tasks.length; index++) {
- ipcRenderer.send(
- "log.info",
- `正在执行第${index + 1}次转换。。。`
- );
- const element = tasks[index];
- inps.inputValue = element;
- const buffers: any = await getTTSData(
- inps,
- this.formConfig.voiceSelect,
- this.formConfig.voiceStyleSelect,
- this.formConfig.role,
- (this.formConfig.speed - 1) * 100,
- (this.formConfig.pitch - 1) * 50
- );
- buffer = Buffer.concat([buffer, buffers]);
- ipcRenderer.send(
- "log.info",
- `第${index + 1}次转换完成,此时Buffer长度为:${
- buffer.length
- }`
- );
+ return obj
+ },
+ {
+ buffer: [],
+ end: 0,
+ offset: 0,
+ res: []
}
-
- fs.writeFileSync(filePath, buffer);
- this.setDoneStatus(item.filePath);
- ElMessage({
- message: "成功,正在写入" + filePath,
- type: "success",
- duration: 2000,
- });
- this.isLoading = false;
- } else {
- await getTTSData(
+ )
+ textHandler.res.push(textHandler.buffer.join(''))
+ const tasks = textHandler.res
+ for (let index = 0; index < tasks.length; index++) {
+ ipcRenderer.send('log.info', `正在执行第${index + 1}次转换。。。`)
+ const element = tasks[index]
+ inps.inputValue = element
+ const buffers: any = await getTTSData(
inps,
this.formConfig.voiceSelect,
this.formConfig.voiceStyleSelect,
this.formConfig.role,
(this.formConfig.speed - 1) * 100,
(this.formConfig.pitch - 1) * 50
- ).then((mp3buffer: any) => {
- fs.writeFileSync(filePath, mp3buffer);
- this.setDoneStatus(item.filePath);
- ElMessage({
- message: "成功,正在写入" + filePath,
- type: "success",
- duration: 2000,
- });
- this.isLoading = false;
- });
+ )
+ buffer = Buffer.concat([buffer, buffers])
+ ipcRenderer.send('log.info', `第${index + 1}次转换完成,此时Buffer长度为:${buffer.length}`)
}
+
+ fs.writeFileSync(filePath, buffer)
+ this.setDoneStatus(item.filePath)
+ ElMessage({
+ message: '成功,正在写入' + filePath,
+ type: 'success',
+ duration: 2000
+ })
+ this.isLoading = false
+ } else {
+ await getTTSData(
+ inps,
+ this.formConfig.voiceSelect,
+ this.formConfig.voiceStyleSelect,
+ this.formConfig.role,
+ (this.formConfig.speed - 1) * 100,
+ (this.formConfig.pitch - 1) * 50
+ ).then((mp3buffer: any) => {
+ fs.writeFileSync(filePath, mp3buffer)
+ this.setDoneStatus(item.filePath)
+ ElMessage({
+ message: '成功,正在写入' + filePath,
+ type: 'success',
+ duration: 2000
+ })
+ this.isLoading = false
+ })
}
- );
- });
+ })
+ })
// this.isLoading = false;
}
},
writeFileSync() {
- const currTime = new Date().getTime().toString();
- const filePath = path.join(this.config.savePath, currTime + ".mp3");
- fs.writeFileSync(path.resolve(filePath), this.currMp3Buffer);
+ const currTime = new Date().getTime().toString()
+ const filePath = path.join(this.config.savePath, currTime + '.mp3')
+ fs.writeFileSync(path.resolve(filePath), this.currMp3Buffer)
ElMessage({
- message: "下载成功:" + filePath,
- type: "success",
- duration: 2000,
- });
- ipcRenderer.send("log.info", `下载完成:${filePath}`);
+ message: '下载成功:' + filePath,
+ type: 'success',
+ duration: 2000
+ })
+ ipcRenderer.send('log.info', `下载完成:${filePath}`)
},
async audition(val: string) {
const inps = {
activeIndex: 1, // 值转换普通文本
- inputValue: this.config.audition,
- };
+ inputValue: this.config.audition
+ }
await getTTSData(
inps,
val,
@@ -321,11 +288,11 @@ export const useTtsStore = defineStore("ttsStore", {
(this.formConfig.speed - 1) * 100,
(this.formConfig.pitch - 1) * 50
).then((mp3buffer: any) => {
- this.currMp3Buffer = mp3buffer;
- const svlob = new Blob([mp3buffer]);
- const sound = new Audio(URL.createObjectURL(svlob));
- sound.play();
- });
- },
- },
-});
+ this.currMp3Buffer = mp3buffer
+ const svlob = new Blob([mp3buffer])
+ const sound = new Audio(URL.createObjectURL(svlob))
+ sound.play()
+ })
+ }
+ }
+})
diff --git a/yarn.lock b/yarn.lock
index e6ee3f226d02d84ed2d270eb61e0f1810c45d49b..13b1d8388a21a23e4d252e63579d95fa3493ca8c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -991,7 +991,7 @@ electron-builder@^23.1.0:
electron-log@^4.4.8:
version "4.4.8"
- resolved "https://registry.npmmirror.com/electron-log/-/electron-log-4.4.8.tgz#fcb9f714dbcaefb6ac7984c4683912c74730248a"
+ resolved "https://registry.npmmirror.com/electron-log/-/electron-log-4.4.8.tgz"
integrity sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==
electron-osx-sign@^0.6.0:
@@ -2418,11 +2418,6 @@ typedarray@^0.0.6:
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
-typescript@^4.7.4:
- version "4.7.4"
- resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz"
- integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
-
unique-string@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz"