diff --git a/package-lock.json b/package-lock.json index 5245d82..ff4a926 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "laitool", - "version": "3.1.2", + "version": "3.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "laitool", - "version": "3.1.2", + "version": "3.1.3", "hasInstallScript": true, "dependencies": { "@alicloud/alimt20181012": "^1.2.0", diff --git a/package.json b/package.json index a37684f..d8a3735 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "laitool", - "version": "3.1.2", + "version": "3.1.3", "description": "An AI tool for image processing, video processing, and other functions.", "main": "./out/main/index.js", "author": "laitool.cn", diff --git a/resources/scripts/db/book.realm.lock b/resources/scripts/db/book.realm.lock index fdb0407..d7ed3a6 100644 Binary files a/resources/scripts/db/book.realm.lock and b/resources/scripts/db/book.realm.lock differ diff --git a/resources/scripts/db/software.realm.lock b/resources/scripts/db/software.realm.lock index 26841f8..7219f60 100644 Binary files a/resources/scripts/db/software.realm.lock and b/resources/scripts/db/software.realm.lock differ diff --git a/src/define/define.js b/src/define/define.js index c84e948..c62cc9d 100644 --- a/src/define/define.js +++ b/src/define/define.js @@ -144,6 +144,7 @@ if (!app.isPackaged) { define['remotemj_api'] = 'https://api.laitool.net/' define['serverUrl'] = 'http://lapi.laitool.cn' +// define['serverUrl'] = 'http://localhost:1578' define['hkServerUrl'] = 'https://laitool.net/' define['bakServerUrl'] = 'https://laitool.net/' define['API'] = 'f85d39ed5a40fd09966f13f12b6cf0f0' diff --git a/src/define/define_string.ts b/src/define/define_string.ts index 5f01dd5..7a4ce67 100644 --- a/src/define/define_string.ts +++ b/src/define/define_string.ts @@ -125,7 +125,11 @@ export const DEFINE_STRING = { INIT_SERVER_GPT_OPTIONS: 'INIT_SERVER_GPT_OPTIONS', GET_AI_SETTING: 'GET_AI_SETTING', SAVE_AI_SETTING: 'SAVE_AI_SETTING', - SYNC_GPT_KEY: "SYNC_GPT_KEY" + SYNC_GPT_KEY: "SYNC_GPT_KEY", + /** + * GPT流式返回的监听 + */ + GPT_STREAM_RETURN: "GPT_STREAM_RETURN" }, QUEUE_BATCH: { @@ -303,7 +307,7 @@ export const DEFINE_STRING = { /** * 获取MJ的消息ID,下载图片并分割 */ - GET_IMAGE_URL_AND_DOWNLOAD : "GET_IMAGE_URL_AND_DOWNLOAD", + GET_IMAGE_URL_AND_DOWNLOAD: "GET_IMAGE_URL_AND_DOWNLOAD", //#endregion diff --git a/src/main/Service/writing.js b/src/main/Service/writing.ts similarity index 68% rename from src/main/Service/writing.js rename to src/main/Service/writing.ts index 646e474..87f0a97 100644 --- a/src/main/Service/writing.js +++ b/src/main/Service/writing.ts @@ -13,20 +13,166 @@ import { GetKimiErrorResponse, GetOpenAISuccessResponse, GetRixApiErrorResponse -} from '../../define/response/openAIResponse.ts' +} from '../../define/response/openAIResponse' import axios from 'axios' -import { ValidateJson } from '../../define/Tools/validate.ts' +import { ValidateJson } from '../../define/Tools/validate' +import { RetryWithBackoff } from '../../define/Tools/common' const { v4: uuidv4 } = require('uuid') // 引入UUID库来生成唯一标识符 let tools = new Tools() export class Writing extends ServiceBase { + pm: PublicMethod constructor(global) { super() - this.global = global this.pm = new PublicMethod(global) axios.defaults.baseURL = define.serverUrl } + /** + * AI 请求发送 + * @param {*} setting + * @param {*} aiData + * @param {*} word + * @returns + */ + async AIRequest(setting, aiData, word): Promise { + // 开始请求AI + let axiosRes = await axios.post('/api/Forward/ForwardWord', { + promptTypeId: setting.gptType, + promptId: setting.gptData, + gptUrl: aiData.gpt_url + '/v1/chat/completions', + model: aiData.model, + machineId: global.machineId, + apiKey: aiData.api_key, + word: word + }) + + // 判断返回的状态,如果是失败的话直接返回错误信息 + if (axiosRes.status != 200) { + throw new Error('请求失败') + } + let dataRes = axiosRes.data + if (dataRes.code == 1) { + // 获取成功 + // 解析返回的数据 + return GetOpenAISuccessResponse(dataRes.data); + + } else { + // 系统报错 + if (dataRes.code == 5000) { + throw new Error('系统错误,错误信息如下:' + dataRes.message) + } else { + // 处理不同类型的错误消息 + if (setting.gptAI == 'laiapi') { + throw new Error(GetRixApiErrorResponse(dataRes.data)) + } else if (setting.gptAI == 'kimi') { + throw new Error(GetKimiErrorResponse(dataRes.data)) + } else if (setting.gptAI == 'doubao') { + throw new Error(GetDoubaoErrorResponse(dataRes.data)) + } else { + throw new Error(dataRes.data) + } + } + } + } + + /** + * 流式请求接口 + * @param setting + * @param aiData + * @param word + */ + async AIRequestStream(setting, aiData, word, oldData: string) { + let body = { + promptTypeId: setting.gptType, + promptId: setting.gptData, + gptUrl: aiData.gpt_url, + model: aiData.model, + machineId: global.machineId, + apiKey: aiData.api_key, + word: word + } + var myHeaders = new Headers(); + myHeaders.append("User-Agent", "Apifox/1.0.0 (https://apifox.com)"); + myHeaders.append("Content-Type", "application/json"); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: JSON.stringify(body), + }; + + let resData = oldData; + return new Promise((resolve, reject) => { + fetch(define.serverUrl + "/api/Forward/ForwardWordStream", requestOptions) + .then(response => { + if (!response.body) { + throw new Error('ReadableStream not yet supported in this browser.'); + } + const reader = response.body.getReader(); + return new ReadableStream({ + start(controller) { + function push() { + reader.read().then(({ + done, + value + }) => { + if (done) { + controller.close(); + resolve(resData) + return; + } + // 假设服务器发送的是文本数据 + const text = new TextDecoder().decode(value); + resData += text + // 将数据返回前端 + global.newWindow[0].win.webContents.send(DEFINE_STRING.GPT.GPT_STREAM_RETURN, resData) + controller.enqueue(value); // 可选:将数据块放入流中 + push(); + }).catch(err => { + controller.error(err); + reject(err) + }); + } + push(); + } + }); + }) + .catch(error => { + reject(error) + }); + }) + } + + async SplitWord(word: string, wordCount: number) { + let word_list = word.split('\n') + let new_word = [] + let tmp_str = '' + let result = [] + for (let i = 0; i < word_list.length; i++) { + const element = word_list[i]; + if (tmp_str.length + element.length > wordCount) { + result.push({ + index: i, + word: new_word.join('\n') + }) + + new_word = [] + tmp_str = "" + new_word.push(element); + } else { + tmp_str += ',' + element + new_word.push(element); + } + } + result.push({ + index: word_list.length, + word: new_word.join('\n') + }) + return result + + } + /** * 文案处理,GPT进行推理 * @param {*} setting @@ -67,48 +213,38 @@ export class Writing extends ServiceBase { if (isEmpty(word)) { throw new Error('请先设置文案') } - - // 开始请求AI - let axiosRes = await axios.post('/api/Forward/ForwardWord', { - promptTypeId: setting.gptType, - promptId: setting.gptData, - gptUrl: aiData.gpt_url, - model: aiData.model, - machineId: this.global.machineId, - apiKey: aiData.api_key, - word: word - }) - - // 判断返回的状态,如果是失败的话直接返回错误信息 - if (axiosRes.status != 200) { - throw new Error('请求失败') - } - let dataRes = axiosRes.data - if (dataRes.code == 1) { - // 获取成功 - // 解析返回的数据 - return successMessage( - GetOpenAISuccessResponse(dataRes.data), - '请求成功', - 'Writing_ActionStart' - ) - } else { - // 系统报错 - if (dataRes.code == 5000) { - throw new Error('系统错误,错误信息如下:' + dataRes.message) - } else { - // 处理不同类型的错误消息 - if (setting.gptAI == 'laiapi') { - throw new Error(GetRixApiErrorResponse(dataRes.data)) - } else if (setting.gptAI == 'kimi') { - throw new Error(GetKimiErrorResponse(dataRes.data)) - } else if (setting.gptAI == 'doubao') { - throw new Error(GetDoubaoErrorResponse(dataRes.data)) + let result = '' + if (setting.isSplit) { + // 这边拆分文案 + let spiltWord = await this.SplitWord(word, setting.splitNumber) + console.log(spiltWord) + for (let i = 0; i < spiltWord.length; i++) { + const element = spiltWord[i]; + if (setting.isStream) { + result += await RetryWithBackoff(async () => { + return await this.AIRequestStream(setting, aiData, element.word, result) + }, 3, 1000) + '\n' } else { - throw new Error(dataRes.data) + result = await RetryWithBackoff(async () => { + return await this.AIRequest(setting, aiData, word) + }, 3, 1000) } } + } else { + if (setting.isStream) { + result += await RetryWithBackoff(async () => { + return await this.AIRequestStream(setting, aiData, word, '') + }, 3, 1000) + '\r\n\n' + } else { + result = await RetryWithBackoff(async () => { + return await this.AIRequest(setting, aiData, word) + }, 3, 1000) + } } + // let tasks = + + // ExecuteConcurrently + return successMessage(result, "执行文案相关任务成功", 'Writing_ActionStart'); } catch (error) { return errorMessage( '执行文案相关任务失败,失败信息如下:' + error.toString(), @@ -307,13 +443,12 @@ export class Writing extends ServiceBase { let text_count = 0 let tmp_str = '' - for (let i = 0; i < data.length; ) { + for (let i = 0; i < data.length;) { iii = i sss = data[i].after_gpt let srt_value = data[i].text - current_text = `字幕: “${srt_value}” 和文案第${text_count + 1} 行数据 “${ - textData[text_count].after_gpt - }” 数据不匹配(检查一下上下文)` + current_text = `字幕: “${srt_value}” 和文案第${text_count + 1} 行数据 “${textData[text_count].after_gpt + }” 数据不匹配(检查一下上下文)` let start_time = data[i].start let end_time = data[i].end let obj = { @@ -403,7 +538,7 @@ export class Writing extends ServiceBase { textData.push(obj) } } - this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { + global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, { code: 0, message: current_text }) diff --git a/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue b/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue index 866d79d..190d5b8 100644 --- a/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue +++ b/src/renderer/src/components/Book/Components/ManageBookDetailButton.vue @@ -102,6 +102,9 @@ > 设置 +
+ +
@@ -134,6 +137,7 @@ import { TranslateType } from '../../../../../define/enum/translate' import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common' import ImportWordAndSrt from '../../Original/Components/ImportWordAndSrt.vue' import GetWaterMaskRectangle from '../../Watermark/GetWaterMaskRectangle.vue' +import MonitorStatus from './MonitorStatus.vue' export default defineComponent({ components: { @@ -141,7 +145,8 @@ export default defineComponent({ NCheckbox, NDropdown, NDivider, - GetWaterMaskRectangle + GetWaterMaskRectangle, + MonitorStatus }, setup() { diff --git a/src/renderer/src/components/Book/Components/MonitorStatus.vue b/src/renderer/src/components/Book/Components/MonitorStatus.vue new file mode 100644 index 0000000..b23cda6 --- /dev/null +++ b/src/renderer/src/components/Book/Components/MonitorStatus.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/renderer/src/components/Book/MJReverse/ManageBookReverseTable.vue b/src/renderer/src/components/Book/MJReverse/ManageBookReverseTable.vue index ea7d632..d0e0e57 100644 --- a/src/renderer/src/components/Book/MJReverse/ManageBookReverseTable.vue +++ b/src/renderer/src/components/Book/MJReverse/ManageBookReverseTable.vue @@ -1,6 +1,7 @@