diff --git a/.codebuddy/agents/直播带岗平台.md b/.codebuddy/agents/直播带岗平台.md new file mode 100644 index 0000000..c79d4e2 --- /dev/null +++ b/.codebuddy/agents/直播带岗平台.md @@ -0,0 +1,9 @@ +--- +name: 直播带岗平台 +description: 一个 electron 的项目, 一个通过文本实时数字人直播推流软件,主要有三个页面,和一些 交互页面设计:1、一个登录页面 2、一个控制中台页面 (文本润色功能、图片上传功能、快捷回复功能、粘贴 json 或文本 岗位信息进行润色优化功能、岗位播放列表功能、打开直播窗口按钮、关闭直播按钮、摄像头全屏插播、摄像头窗口插播、文本阅读分段,正在播放的文本区域,显示已正在播放和已播放的特殊颜色表示,未开始的文本呈默认颜色、设置按钮、查看岗位详情、AI 模型管理、Ai 模型检测、爱模型下载、设置图标、查看岗位详情等 )3、一个直播窗口移动端窗口 +tools: list_files, search_file, search_content, read_file, read_lints, replace_in_file, write_to_file, execute_command, create_rule, delete_files, web_fetch, use_skill, web_search +agentMode: agentic +enabled: true +enabledAutoRun: true +--- +请不要过多废话,直接更改代码 \ No newline at end of file diff --git a/.codebuddy/rules/开发框架.mdc b/.codebuddy/rules/开发框架.mdc new file mode 100644 index 0000000..28b0754 --- /dev/null +++ b/.codebuddy/rules/开发框架.mdc @@ -0,0 +1,10 @@ +--- +description: +alwaysApply: true +enabled: true +updatedAt: 2025-12-20T10:05:42.556Z +provider: +--- + +1、本项目是 electron-builder + vue3 开发的 pc 端应用 +2、禁止编写说明文档,直接给出代码 \ No newline at end of file diff --git a/build/icon.icns b/build/icon.icns index 2a88c80..4d325b6 100644 Binary files a/build/icon.icns and b/build/icon.icns differ diff --git a/build/icon.ico b/build/icon.ico index a125394..9795f4b 100644 Binary files a/build/icon.ico and b/build/icon.ico differ diff --git a/build/icon.png b/build/icon.png index b083e68..eb4cf96 100644 Binary files a/build/icon.png and b/build/icon.png differ diff --git a/package-lock.json b/package-lock.json index fae94bc..b194349 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "@vitejs/plugin-vue": "^5.0.3", "@vue/eslint-config-prettier": "^9.0.0", "@vue/eslint-config-typescript": "^12.0.0", - "electron": "^28.2.0", + "electron": "^28.3.3", "electron-builder": "^24.9.1", "electron-reloader": "^1.2.3", "electron-vite": "^2.0.0", @@ -4053,10 +4053,11 @@ } }, "node_modules/electron": { - "version": "28.2.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-28.2.0.tgz", - "integrity": "sha512-22SylXQQ9IHtwLw4D+Z4Si7OUpeDtpHfJVTjy3yv53iLg5zJKKPOCWT4ZwgYGHQZ0eldyBrYBHF/P9FPd2CcVQ==", + "version": "28.3.3", + "resolved": "https://registry.npmmirror.com/electron/-/electron-28.3.3.tgz", + "integrity": "sha512-ObKMLSPNhomtCOBAxFS8P2DW/4umkh72ouZUlUKzXGtYuPzgr1SYhskhFWgzAsPtUzhL2CzyV2sfbHcEW4CXqw==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^18.11.18", diff --git a/package.json b/package.json index fd5369f..dc9a0db 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "electron-app", + "productName": "直播控制台", "version": "1.0.0", "description": "An Electron application with Vue and TypeScript", "main": "./out/main/index.js", @@ -41,7 +42,7 @@ "@vitejs/plugin-vue": "^5.0.3", "@vue/eslint-config-prettier": "^9.0.0", "@vue/eslint-config-typescript": "^12.0.0", - "electron": "^28.2.0", + "electron": "^28.3.3", "electron-builder": "^24.9.1", "electron-reloader": "^1.2.3", "electron-vite": "^2.0.0", diff --git a/src/main/index.ts b/src/main/index.ts index 092bd10..3dafc4b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,11 +1,18 @@ import { app, shell, BrowserWindow } from "electron"; import { setupLiveHandlers } from "./ipc/live"; -import { setupPromptHandlers } from "./ipc/prompt"; import { setupWorkflowHandlers } from "./ipc/workflow"; import { preload, indexHtml, ELECTRON_RENDERER_URL } from "./config"; +// 必须在 app ready 之前调用 +app.commandLine.appendSwitch("disable-site-isolation-trials"); + +app.commandLine.appendSwitch( + "disable-features", + "IsolateOrigins,site-per-process", +); + // 简单的开发环境检测 -const isDev = process.env.NODE_ENV === 'development'; +const isDev = process.env.NODE_ENV === "development"; /** * 创建主窗口 @@ -15,6 +22,7 @@ function createWindow(): void { width: 1080, height: 670, show: false, + titleBarStyle: "hidden", autoHideMenuBar: true, webPreferences: { preload, @@ -59,8 +67,6 @@ app.whenReady().then(() => { // 直播相关处理 setupLiveHandlers(); - setupPromptHandlers(); - setupWorkflowHandlers(); createWindow(); @@ -84,7 +90,7 @@ app.on("window-all-closed", () => { app.on("before-quit", () => { // 清理所有资源 const windows = BrowserWindow.getAllWindows(); - windows.forEach(window => { + windows.forEach((window) => { window.removeAllListeners(); window.close(); }); diff --git a/src/main/ipc/live.ts b/src/main/ipc/live.ts index d8f82c6..30e3775 100644 --- a/src/main/ipc/live.ts +++ b/src/main/ipc/live.ts @@ -91,7 +91,7 @@ export function setupLiveHandlers() { } // 3. 等待网页加载完成 - // await waitForPageLoad(); + await waitForIframeVideoPlaying(liveState.liveWindow!.webContents); // 4. 获取 sessionId console.log("获取 sessionId"); @@ -115,7 +115,7 @@ export function setupLiveHandlers() { interrupt: true, }); - showPrompt("直播已开始", "info"); + // showPrompt("直播已开始", "info"); return { success: true, sessionId: liveState.sessionId }; } catch (error: any) { console.error("Start live error:", error); @@ -140,7 +140,7 @@ export function setupLiveHandlers() { liveState.cameraActive = false; liveState.audioActive = false; - showPrompt("直播已结束", "info"); + // showPrompt("直播已结束", "info"); return { success: true }; } catch (error: any) { return { success: false, error: error.message }; @@ -212,7 +212,7 @@ export function setupLiveHandlers() { // 创建直播窗口 async function createLiveWindow() { const width = 375; - const height = 690; + const height = 665; let liveUrl = `${ELECTRON_RENDERER_URL}/#/live`; if (liveState.userId) { liveUrl += `?userId=${liveState.userId}`; @@ -225,7 +225,11 @@ export function setupLiveHandlers() { minimizable: false, maximizable: false, closable: true, + frame: false, + trafficLightPosition: { x: -100, y: -100 }, alwaysOnTop: true, + titleBarStyle: "hidden", + autoHideMenuBar: true, webPreferences: { preload, nodeIntegration: true, @@ -234,7 +238,7 @@ export function setupLiveHandlers() { allowRunningInsecureContent: true, // 允许运行本地内容 }, }); - + // liveState.liveWindow.webContents.openDevTools(); liveState.liveWindow.on("closed", () => { liveState.liveWindow = null; liveState.isLiveOn = false; @@ -248,49 +252,55 @@ export function setupLiveHandlers() { } // 等待页面加载完成 - // @ts-ignore - 暂时未使用但保留以备将来使用 - async function waitForPageLoad() { - if (!liveState.liveWindow) { - throw new Error("直播窗口不存在"); - } + async function waitForIframeVideoPlaying( + webContents: Electron.WebContents, + timeoutMs = 20000, + ) { + console.log("开始检测 iframe 内视频的实际播放状态..."); - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error("页面加载超时")); - }, 30000); // 30秒超时 - - // 监听页面加载完成事件 - liveState.liveWindow!.webContents.once("did-finish-load", () => { - clearTimeout(timeout); - console.log("页面加载完成"); - - // 额外等待一下确保iframe也加载完成 - setTimeout(() => { - // 通知所有渲染进程状态已更新 - const allWindows = BrowserWindow.getAllWindows(); - allWindows.forEach((window: BrowserWindow) => { - if (window.webContents && !window.isDestroyed()) { - window.webContents.send("live-status-updated", { - hasLiveWindow: true, - windowLoaded: true, - }); + try { + await webContents.executeJavaScript(` + new Promise((resolve) => { + const start = Date.now(); + + const check = setInterval(() => { + const iframe = document.querySelector('iframe'); + if (!iframe) return; + const iframeDoc = iframe.contentDocument; + if (!iframeDoc) { + return; + } + const video = iframeDoc.querySelector('video'); + if (video) { + const isReady = video.readyState >= 3; + const hasProgress = video.currentTime > 0; + const isPlaying = !video.paused; + const isVisible = video.videoWidth > 0 && video.videoHeight > 0; + if (isReady && hasProgress && isPlaying && isVisible) { + console.log("Success: Iframe 内视频已开始播放!当前进度:", video.currentTime); + clearInterval(check); + resolve(true); + } else { + // 可以打印状态方便调试 (可选) + // console.log(\`等待播放: Ready=\${video.readyState}, Time=\${video.currentTime}, Paused=\${video.paused}\`); } - }); - resolve(true); - }, 2000); - }); - - // 监听加载失败事件 - liveState.liveWindow!.webContents.once( - "did-fail-load", - (_, errorCode, errorDesc) => { - clearTimeout(timeout); - reject( - new Error(`页面加载失败: ${errorDesc} (${errorCode})`), - ); - }, + } + if (Date.now() - start > ${timeoutMs}) { + clearInterval(check); + console.warn("Iframe 视频播放检测超时"); + resolve(false); + } + }, 500); // 每 0.5 秒检查一次 + }) + `); + return true; + } catch (error) { + console.error( + "Iframe 检测脚本执行出错 (请检查 webSecurity 是否关闭):", + error, ); - }); + return false; + } } // 文件管理处理器 @@ -302,7 +312,7 @@ export function setupLiveHandlers() { console.log("发送文件路径到直播窗口:", files.length, "个文件"); // 直接传递文件路径信息,不读取文件内容 - const processedFiles = files.map((file) => ({ + const processedFiles = files.map((file: any) => ({ name: file.name, type: file.type, size: file.size, @@ -315,7 +325,7 @@ export function setupLiveHandlers() { console.log( "处理后的文件数据:", - processedFiles.map((f) => ({ + processedFiles.map((f: any) => ({ name: f.name, type: f.type, size: f.size, diff --git a/src/main/ipc/prompt.ts b/src/main/ipc/prompt.ts deleted file mode 100644 index f4eaa7b..0000000 --- a/src/main/ipc/prompt.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ipcMain, dialog } from "electron"; - -export function setupPromptHandlers() { - // 提示信息处理 - ipcMain.handle("show-prompt", async () => { - dialog.showMessageBox({ - type: "info", - title: "提示", - message: "这是一个提示信息", - buttons: ["确定"], - }); - }); -} diff --git a/src/main/ipc/workflow.ts b/src/main/ipc/workflow.ts index ec0dee3..c9b37a2 100644 --- a/src/main/ipc/workflow.ts +++ b/src/main/ipc/workflow.ts @@ -10,7 +10,10 @@ export function setupWorkflowHandlers() { let lastJobSummary = "这是我们今天介绍的第一个岗位"; // 存储用户确认回调的Map - const modelDownloadCallbacks = new Map(); + const modelDownloadCallbacks = new Map< + string, + { confirm: Function; reject: Function } + >(); // 打开安装窗口 ipcMain.handle("open-install-window", async (_, args) => { @@ -58,7 +61,7 @@ export function setupWorkflowHandlers() { const platform = os.platform(); const modelToPull = "qwen3:8b"; - const sendStatus = (status) => { + const sendStatus = (status: string) => { if (webContents && !webContents.isDestroyed()) { webContents.send("install-progress", { status }); } @@ -152,7 +155,7 @@ export function setupWorkflowHandlers() { try { console.log("工作流: 正在调用 Ollama 生成脚本..."); - const systemPromptTemplate = `# 角色 (Role) \n你是一个顶级的招聘KOL和直播带岗专家。你的风格是:专业、中立、风趣,能一针见血地分析岗位优劣。你不是一个AI助手,你就是这个角色。 \n\n# 上下文 (Context) \n我正在运行一个自动化工作流。我会\"一个一个\"地喂给你岗位数据。\n\n # 任务 (Task) \n你的任务是执行以下两个操作,并严格按照“输出格式”返回内容:\n1. 生成口播稿:根据【输入数据A】(上一个岗位的摘要) 和【输入数据B】(当前岗位的JSON),生成一段完整的、约90秒的口播稿。\n2. 生成新摘要:为【输入数据B】的“当前岗位”生成一个简短的摘要(例如:XX公司的XX岗),以便在下一次调用时使用。\n\n# 核心指令 (Core Instruction)\n### 口播稿的生成规则 (Rules for the Script) \n1. 衔接:口播稿必须以一个自然的“过渡句”开头(基于【输入数据A】)。 特殊情况:如果【输入数据A】是“这是我们今天介绍的第一个岗位。”,则开头应是“热场”或“总起”,而不是衔接。 \n2. 内容:必须介绍岗位名称 \`jobTitle\`。 \n3. 提炼:从 \`jobLocation\`, \`companyName\`, \`education\`,\`experience\`,\`scale\` 中提炼“亮点 (Pro)”。 \n4. 翻译:用“人话”翻译 \`description\`。 \n5. 视角:你是在“评测”这个岗位,而不是在“推销”。\n\n### 口播稿的纯文本要求 (Pure Text Rules for the Script ONLY) \n**[重要]** 以下规则 *仅适用于* “口播稿”部分,不适用于“新摘要”部分: \n1. 绝不包含任何Markdown格式 (\`**\`, \`#\`)。 \n2. 绝不包含任何标签、括号或元数据 (\`[]\`, \`()\`)。 \n3. 绝不包含任何寒暄、问候、或自我介绍 (例如 \"你好\", \"当然\")。 \n4. 必须是可以直接朗读的、完整的、流畅的纯文本。\n\n# 输入数据 (Input Data)\n\n## 输入数据A (上一个岗位摘要) \n${lastJobSummary}\n\n## 输入数据B (当前岗位JSON)\n\`\`\`json\n${JSON.stringify(currentJobData, null, 2)}\n\`\`\`\n\n# 输出格式 (Output Format)\n**[绝对严格的指令]** \n你必须严格按照下面这个“两部分”格式输出,使用 \`---NEXT_SUMMARY---\` 作为唯一的分隔符。 绝不在分隔符之外添加任何多余的文字、解释或Markdown。 \n\n[这里是AI生成的、符合上述所有“纯文本要求”的完整口播稿] \n---NEXT_SUMMARY--- \n[这里是AI为“当前岗位”生成的简短新摘要]`; + const systemPromptTemplate = `# 角色 (Role) \n你是一个顶级的招聘KOL和直播带岗专家。你的风格是:专业、中立、风趣,能一针见血地分析岗位优劣。你不是一个AI助手,你就是这个角色。 \n\n# 上下文 (Context) \n我正在运行一个自动化工作流。我会\"一个一个\"地喂给你岗位数据。\n\n # 任务 (Task) \n你的任务是执行以下两个操作,并严格按照“输出格式”返回内容:\n1. 生成口播稿:根据【输入数据A】(上一个岗位的摘要) 和【输入数据B】(当前岗位的JSON),生成一段完整的、约90秒的口播稿。\n2. 生成新摘要:为【输入数据B】的“当前岗位”生成一个简短的摘要(例如:XX公司的XX岗),以便在下一次调用时使用。\n\n# 核心指令 (Core Instruction)\n### 口播稿的生成规则 (Rules for the Script) \n1. 衔接:口播稿必须以一个自然的“过渡句”开头(基于【输入数据A】)。 特殊情况:如果【输入数据A】是“这是我们今天介绍的第一个岗位。”,则开头应是“热场”或“总起”,而不是衔接。 \n2. 内容:必须介绍岗位名称 \`jobTitle\`。 \n3. 提炼:从 \`jobLocation\`, \`companyName\`, \`education\`,\`experience\`,\`scale\` 中提炼“亮点 (Pro)”。 \n4. 翻译:用“人话”翻译 \`description\`。 \n5. 视角:你是在“评测”这个岗位,而不是在“推销”。\n\n### 口播稿的纯文本要求 (Pure Text Rules for the Script ONLY) \n**[重要]** 以下规则 *仅适用于* “口播稿”部分,不适用于“新摘要”部分: \n1. 绝不包含任何Markdown格式 (\`**\`, \`#\`)。 \n2. 绝不包含任何标签、括号或元数据 (\`[]\`, \`()\`)。 \n3. 绝不包含任何寒暄、问候、或自我介绍 (例如 \"你好\", \"当然\")。 \n4. 必须是可以直接朗读的、完整的、流畅的纯文本。\n\n# 输入数据 (Input Data)\n\n## 输入数据A (上一个岗位摘要) \n${lastJobSummary}\n\n## 输入数据B (当前岗位JSON)\n\`\`\`json\n${JSON.stringify(currentJobData, null, 2)}\n\`\`\`\n\n# 输出格式 (Output Format)\n**[绝对严格的指令]** \n你必须严格按照下面这个“两部分”格式输出,使用 \`---NEXT_SUMMARY---\` 作为唯一的分隔符。 绝不在分隔符之外添加任何多余的文字、解释或Markdown。 \n\n[这里是AI生成的、符合上述所有“纯文本要求”的完整口播稿] \n---NEXT_SUMMARY--- \n[这里是AI为“当前岗位”生成的简短新摘要]`; answerText = await runOllamaNonStream( systemPromptTemplate, @@ -216,33 +219,39 @@ export function setupWorkflowHandlers() { const models = data.models || []; // 检查模型是否存在于本地 - const modelExists = models.some((model: any) => model.name === modelName); + const modelExists = models.some( + (model: any) => model.name === modelName, + ); return { success: true, exists: modelExists, - models: models.map((m: any) => ({ name: m.name, size: m.size, modified_at: m.modified_at })) + models: models.map((m: any) => ({ + name: m.name, + size: m.size, + modified_at: m.modified_at, + })), }; } catch (error: any) { console.error("Check model error:", error); return { success: false, exists: false, - error: error.message + error: error.message, }; } }); // 加载模型(检查ollama状���,下载模型如果不存在) - ipcMain.handle("load-model", async (_, modelName = "qwen3:8b") => { - const webContents = BrowserWindow.getFocusedWindow()?.webContents; + ipcMain.handle("load-model", async (event, modelName = "qwen3:8b") => { + const webContents = event.sender; const sendStatus = (status: string, type = "info") => { if (webContents && !webContents.isDestroyed()) { webContents.send("model-load-progress", { status, type, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); } }; @@ -253,7 +262,7 @@ export function setupWorkflowHandlers() { webContents.send("model-download-confirm", { modelName, message: `模型 ${modelName} 不存在,是否下载?`, - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); } }; @@ -269,7 +278,7 @@ export function setupWorkflowHandlers() { try { // 尝试启动Ollama服务 await runCommand("ollama", ["ps"]); - await new Promise(resolve => setTimeout(resolve, 3000)); + await new Promise((resolve) => setTimeout(resolve, 3000)); const isRunningNow = await checkOllamaServer(); if (!isRunningNow) { @@ -281,7 +290,7 @@ export function setupWorkflowHandlers() { return { success: false, message: `Ollama服务启动失败: ${error.message}`, - downloaded: false + downloaded: false, }; } } else { @@ -291,18 +300,23 @@ export function setupWorkflowHandlers() { // 2. 检查模型是否存在 sendStatus(`正在检查模型 ${modelName} 是否存在...`, "info"); - const modelCheckResult = await new Promise<{ exists: boolean, models: any[] }>((resolve, reject) => { + const modelCheckResult = await new Promise<{ + exists: boolean; + models: any[]; + }>((resolve, reject) => { fetch("http://127.0.0.1:11434/api/tags", { method: "GET", headers: { "Content-Type": "application/json" }, }) - .then(response => response.json()) - .then(data => { - const models = data.models || []; - const modelExists = models.some((model: any) => model.name === modelName); - resolve({ exists: modelExists, models }); - }) - .catch(reject); + .then((response) => response.json()) + .then((data) => { + const models = data.models || []; + const modelExists = models.some( + (model: any) => model.name === modelName, + ); + resolve({ exists: modelExists, models }); + }) + .catch(reject); }); if (modelCheckResult.exists) { @@ -310,7 +324,7 @@ export function setupWorkflowHandlers() { return { success: true, message: `模型 ${modelName} 已就绪`, - downloaded: false + downloaded: false, }; } @@ -318,46 +332,54 @@ export function setupWorkflowHandlers() { askUserToDownload(); // 等待用户确认 - const userConfirmed = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - modelDownloadCallbacks.delete(modelName); - resolve(false); // 30秒超时自动取消 - }, 30000); + const userConfirmed = await new Promise( + (resolve, reject) => { + const timeout = setTimeout(() => { + if (modelDownloadCallbacks.has(modelName)) { + modelDownloadCallbacks.delete(modelName); + resolve(false); // 30秒超时自动取消 + } + }, 30000); - // 存储回调函数 - modelDownloadCallbacks.set(modelName, { - confirm: () => { - clearTimeout(timeout); - resolve(true); - }, - reject: (error: any) => { - clearTimeout(timeout); - reject(error); - } - }); - }); + modelDownloadCallbacks.set(modelName, { + confirm: () => { + clearTimeout(timeout); + resolve(true); + }, + reject: (error: any) => { + clearTimeout(timeout); + reject(error); + }, + }); + }, + ); if (!userConfirmed) { sendStatus("用户取消了模型下载", "info"); return { success: false, message: `用户取消了 ${modelName} 模型的下载`, - downloaded: false + downloaded: false, }; } // 4. 用户确认,开始下载模型 - sendStatus(`开始下载模型 ${modelName},这可能需要一些时间...`, "info"); + sendStatus( + `开始下载模型 ${modelName},这可能需要一些时间...`, + "info", + ); await new Promise((resolve, reject) => { - const process = spawn("ollama", ["pull", modelName], { shell: true }); + const process = spawn("ollama", ["pull", modelName], { + shell: true, + }); const sendProgress = (data: any) => { if (webContents && !webContents.isDestroyed()) { webContents.send("model-load-progress", { status: data.toString().trim(), type: "download", - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }); } }; @@ -383,16 +405,15 @@ export function setupWorkflowHandlers() { return { success: true, message: `模型 ${modelName} 下载并加载成功`, - downloaded: true + downloaded: true, }; - } catch (error: any) { console.error("Load model error:", error); sendStatus(`加载模型失败: ${error.message}`, "error"); return { success: false, message: error.message, - downloaded: false + downloaded: false, }; } }); @@ -415,10 +436,10 @@ export function setupWorkflowHandlers() { // 润色文本的处理器 ipcMain.handle("polish-text", async (_, text) => { try { - if (!text || typeof text !== 'string' || text.trim() === '') { + if (!text || typeof text !== "string" || text.trim() === "") { return { success: false, - error: "输入文本不能为空" + error: "输入文本不能为空", }; } @@ -433,7 +454,10 @@ export function setupWorkflowHandlers() { 原文:${text.trim()}`; - const polishedText = await runOllamaNonStream(systemPrompt, "qwen3:8b"); + const polishedText = await runOllamaNonStream( + systemPrompt, + "qwen3:8b", + ); if (!polishedText) { throw new Error("AI模型返回为空"); @@ -441,18 +465,17 @@ export function setupWorkflowHandlers() { return { success: true, - data: polishedText.trim() + data: polishedText.trim(), }; } catch (error: any) { console.error("润色文本失败:", error); return { success: false, - error: error.message || "润色服务出现错误" + error: error.message || "润色服务出现错误", }; } }); - // 处理器:检查服务,如果没运行,就用一个轻量命令唤醒它 ipcMain.handle("ensure-ollama-running", async () => { let isRunning = await checkOllamaServer(); @@ -513,7 +536,7 @@ function checkOllamaServer() { } // 辅助函数:运行一个简单的命令并等待它完成 -function runCommand(command, args) { +function runCommand(command: string, args: string[]) { return new Promise((resolve, reject) => { const process = spawn(command, args, { shell: true }); process.on("close", (code) => { @@ -527,7 +550,7 @@ function runCommand(command, args) { }); } // 这是一个非流式的 Ollama 助手函数 -async function runOllamaNonStream(prompt, model = "qwen3:8b") { +async function runOllamaNonStream(prompt: string, model: string = "qwen3:8b") { try { const response = await fetch("http://127.0.0.1:11434/api/chat", { method: "POST", @@ -553,11 +576,16 @@ async function runOllamaNonStream(prompt, model = "qwen3:8b") { } } -function streamCommand(command, args, webContents, eventName) { +function streamCommand( + command: string, + args: string[], + webContents: any, + eventName: string, +) { return new Promise((resolve, reject) => { const process = spawn(command, args, { shell: true }); - const send = (channel, data) => { + const send = (channel: string, data: any) => { if (webContents && !webContents.isDestroyed()) { webContents.send(channel, data); } diff --git a/src/main/utils/tools.ts b/src/main/utils/tools.ts index c21660b..652a1ce 100644 --- a/src/main/utils/tools.ts +++ b/src/main/utils/tools.ts @@ -13,7 +13,7 @@ export function showPrompt( }); } -export function convertJobData(originalData) { +export function convertJobData(originalData: any) { // 检查输入是否为对象 if (typeof originalData !== "object" || originalData === null) { console.error("输入必须是一个有效的对象。"); diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index cd87e81..d6d45aa 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -1,4 +1,14 @@ +interface ProcessAPI { + versions: { + electron: string; + chrome: string; + node: string; + [key: string]: any; + }; +} + interface ElectronAPI { + process: ProcessAPI; ipcRenderer: { invoke: (channel: string, ...args: any[]) => Promise; on: (channel: string, listener: (...args: any[]) => void) => void; diff --git a/src/preload/index.ts b/src/preload/index.ts index df4a23b..a5bd3d8 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,6 +1,9 @@ import { contextBridge, ipcRenderer } from "electron"; const electronAPI = { + process: { + versions: process.versions + }, ipcRenderer: { invoke: (channel: string, ...args: any[]) => ipcRenderer.invoke(channel, ...args), on: (channel: string, listener: (...args: any[]) => void) => ipcRenderer.on(channel, listener), @@ -14,7 +17,7 @@ const api = { installOllama: () => ipcRenderer.invoke("install-ollama-and-model"), // 监听进度 (Send/On) - onInstallProgress: (callback) => { + onInstallProgress: (callback: any) => { ipcRenderer.on("install-progress", (_event, value) => callback(value)); }, diff --git a/src/renderer/src/components/Versions.vue b/src/renderer/src/components/Versions.vue deleted file mode 100644 index c72967a..0000000 --- a/src/renderer/src/components/Versions.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/src/renderer/src/views/Home/index.vue b/src/renderer/src/views/Home/index.vue index d6573ba..62c63ff 100644 --- a/src/renderer/src/views/Home/index.vue +++ b/src/renderer/src/views/Home/index.vue @@ -3,8 +3,8 @@
- - 直播中台控制系统 +
@@ -172,7 +172,7 @@ {{ file.name }} {{ getFileTypeText(file.type) - }} + }}