flat: 暂存

This commit is contained in:
Apcallover
2025-11-16 18:11:30 +08:00
parent 120bad4abe
commit e9c90d759b
18 changed files with 9912 additions and 8113 deletions

View File

@@ -1,45 +1,124 @@
import { ipcMain, BrowserWindow } from "electron";
import { preload, indexHtml, ELECTRON_RENDERER_URL } from "../config";
import { getSessionId, sendMessage } from "../Api/api";
import { showPrompt } from "../utils/tools";
let liveWindow: BrowserWindow | null = null;
// 直播状态管理
interface LiveState {
sessionId: string | null;
userId: string | null;
isVideoInserted: boolean;
isLiveOn: boolean;
jobList: any[];
currentJob: any;
cameraActive: boolean;
audioActive: boolean;
liveWindow: BrowserWindow | null;
}
// 全局直播状态
const liveState: LiveState = {
sessionId: null,
userId: null,
isVideoInserted: false,
isLiveOn: false,
jobList: [],
currentJob: null,
cameraActive: false,
audioActive: false,
liveWindow: null,
};
// 直播相关的主进程处理
export function setupLiveHandlers() {
let LiveSessionId = null;
let isVideoInserted = false;
// 获取直播状态
ipcMain.handle("get-live-status", async () => {
return {
success: true,
data: {
isLiveOn: liveState.isLiveOn,
hasLiveWindow: !!liveState.liveWindow,
isVideoInserted: liveState.isVideoInserted,
cameraActive: liveState.cameraActive,
},
};
});
// 切换摄像头插入状态
ipcMain.on("toggle-camera-insert", async () => {
if (liveWindow) {
isVideoInserted = !isVideoInserted;
liveWindow.webContents.send(
if (liveState.liveWindow) {
liveState.isVideoInserted = !liveState.isVideoInserted;
liveState.liveWindow.webContents.send(
"toggle-camera-insert",
isVideoInserted,
liveState.isVideoInserted,
);
}
});
// 插入摄像头视频
ipcMain.on("insert-camera-video", async (_) => {
if (liveWindow) {
liveWindow.webContents.send("insert-camera-video");
ipcMain.on("insert-camera-video", async () => {
if (liveState.liveWindow) {
liveState.liveWindow.webContents.send("insert-camera-video");
liveState.cameraActive = true;
} else {
showPrompt("直播窗口未打开", "error");
}
});
// 插入音频
ipcMain.on("insert-video-audio", async (_) => {
if (liveWindow) {
liveWindow.webContents.send("insert-video-audio");
ipcMain.on("insert-video-audio", async () => {
if (liveState.liveWindow) {
liveState.liveWindow.webContents.send("insert-video-audio");
liveState.audioActive = true;
} else {
showPrompt("直播窗口未打开", "error");
}
});
// 开始直播
ipcMain.handle("start-live", async (_) => {
// 开始直播 - 完整的直播启动流程
ipcMain.handle("start-live", async (_, { userId } = {}) => {
try {
console.log("Starting live stream...");
// TODO: 实现直播推流逻辑
return { success: true };
// 1. 设置用户ID
if (userId) {
liveState.userId = userId;
}
// 2. 创建或显示直播窗口
if (!liveState.liveWindow) {
await createLiveWindow();
}
// 3. 等待网页加载完成
// await waitForPageLoad();
// 4. 获取 sessionId
console.log("获取 sessionId");
const sessionResult = await getSessionId({
userId: liveState.userId,
});
console.log("sessionid", sessionResult);
if (!sessionResult.success) {
return { success: false, error: "获取会话ID失败" };
}
liveState.sessionId = sessionResult.sessionId;
// 5. 设置直播状态
liveState.isLiveOn = true;
// 6. 发送欢迎消息
await sendMessage({
sessionid: liveState.sessionId,
text: "大家好,欢迎来到我的直播间!",
type: "echo",
interrupt: true,
});
showPrompt("直播已开始", "info");
return { success: true, sessionId: liveState.sessionId };
} catch (error: any) {
console.error("Start live error:", error);
return { success: false, error: error.message };
}
});
@@ -47,151 +126,273 @@ export function setupLiveHandlers() {
// 结束直播
ipcMain.handle("stop-live", async () => {
try {
// TODO: 实现结束直播逻辑
liveState.isLiveOn = false;
// 关闭直播窗口
if (liveState.liveWindow) {
liveState.liveWindow.close();
liveState.liveWindow = null;
}
// 重置状态
liveState.sessionId = null;
liveState.isVideoInserted = false;
liveState.cameraActive = false;
liveState.audioActive = false;
showPrompt("直播已结束", "info");
return { success: true };
} catch (error: any) {
return { success: false, error: error.message };
}
});
ipcMain.handle("explain-position", async (_, content: string) => {
// 强制关闭直播窗口
ipcMain.handle("force-close-live", async () => {
try {
let params = {
sessionid: LiveSessionId,
console.log("Force closing live window...");
// 通知直播窗口清理所有媒体资源
if (liveState.liveWindow) {
try {
// 发送清理指令到直播窗口
liveState.liveWindow.webContents.send(
"force-cleanup-media",
);
// 等待一下确保清理指令被处理
await new Promise((resolve) => setTimeout(resolve, 100));
// 强制关闭窗口
liveState.liveWindow.removeAllListeners();
liveState.liveWindow.destroy();
} catch (error) {
console.warn("Error destroying window:", error);
}
liveState.liveWindow = null;
}
// 强制重置所有状态
liveState.isLiveOn = false;
liveState.sessionId = null;
liveState.isVideoInserted = false;
liveState.cameraActive = false;
liveState.audioActive = false;
showPrompt("直播窗口已强制关闭", "info");
return { success: true };
} catch (error: any) {
console.error("Force close error:", error);
return { success: false, error: error.message };
}
});
// 提交消息给数字人平台
ipcMain.handle("push-explain-position", async (_, content: string) => {
try {
if (!liveState.sessionId) {
return { success: false, error: "直播未开始,无法发送消息" };
}
const params = {
sessionid: liveState.sessionId,
text: content,
type: "echo",
interrupt: true,
};
await sendMessage(params);
// TODO: 实现结束直播逻辑
return { success: true };
} catch (error: any) {
console.error("Send message error:", error);
return { success: false, error: error.message };
}
});
// 打开直播窗口
ipcMain.handle("open-live-window", async (_, args) => {
try {
if (liveWindow) {
liveWindow.focus();
showPrompt("直播窗口已打开", "info");
return { success: true };
}
const { width, height, path, userId } = args;
let liveUrl = `${ELECTRON_RENDERER_URL}/#/${path}`;
if (userId) {
liveUrl += `?userId=${userId}`;
}
// TODO: 实现打开直播窗口逻辑
liveWindow = new BrowserWindow({
title: "直播窗口",
width,
height,
minimizable: false, // 是否可以最小化
maximizable: false, // 是否可以最小化
closable: true, // 窗口是否可关闭
alwaysOnTop: true, // 窗口是否永远在别的窗口的上面
webPreferences: {
preload,
nodeIntegration: true,
contextIsolation: false,
// 创建直播窗口
async function createLiveWindow() {
const width = 375;
const height = 690;
let liveUrl = `${ELECTRON_RENDERER_URL}/#/live`;
if (liveState.userId) {
liveUrl += `?userId=${liveState.userId}`;
}
liveState.liveWindow = new BrowserWindow({
title: "直播窗口",
width,
height,
minimizable: false,
maximizable: false,
closable: true,
alwaysOnTop: true,
webPreferences: {
preload,
nodeIntegration: true,
contextIsolation: false,
webSecurity: false, // 允许访问本地文件
allowRunningInsecureContent: true, // 允许运行本地内容
},
});
liveState.liveWindow.on("closed", () => {
liveState.liveWindow = null;
liveState.isLiveOn = false;
});
console.log(liveUrl);
if (ELECTRON_RENDERER_URL) {
await liveState.liveWindow.loadURL(liveUrl);
} else {
await liveState.liveWindow.loadFile(indexHtml, { hash: "/live" });
}
}
// 等待页面加载完成
// @ts-ignore - 暂时未使用但保留以备将来使用
async function waitForPageLoad() {
if (!liveState.liveWindow) {
throw new Error("直播窗口不存在");
}
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,
});
}
});
resolve(true);
}, 2000);
});
// 监听加载失败事件
liveState.liveWindow!.webContents.once(
"did-fail-load",
(_, errorCode, errorDesc) => {
clearTimeout(timeout);
reject(
new Error(`页面加载失败: ${errorDesc} (${errorCode})`),
);
},
});
// liveWindow.webContents.openDevTools();
liveWindow.on("closed", () => {
liveWindow = null;
});
if (ELECTRON_RENDERER_URL) {
liveWindow.loadURL(liveUrl);
setTimeout(async () => {
const res = await getSessionId({ userId: userId });
if (res.success) {
LiveSessionId = res.sessionId;
console.log(
"Session ID obtained successfully",
LiveSessionId,
);
}
}, 3000);
);
});
}
// 文件管理处理器
ipcMain.handle("show-files-in-live", async (_, { files }) => {
console.log("收到文件", files);
try {
// 使用当前直播窗口的 webContents
if (liveState.liveWindow && !liveState.liveWindow.isDestroyed()) {
console.log("发送文件路径到直播窗口:", files.length, "个文件");
// 直接传递文件路径信息,不读取文件内容
const processedFiles = files.map((file) => ({
name: file.name,
type: file.type,
size: file.size,
path: file.path,
// 为支持的文件类型生成 file:// URL
url: file.path
? `file://${file.path.replace(/\\/g, "/")}`
: null,
}));
console.log(
"处理后的文件数据:",
processedFiles.map((f) => ({
name: f.name,
type: f.type,
size: f.size,
hasPath: !!f.path,
hasUrl: !!f.url,
})),
);
// 发送文件数据到直播窗口
liveState.liveWindow.webContents.send("show-files-display", {
files: processedFiles,
timestamp: new Date().toISOString(),
});
return {
success: true,
message: `文件已发送到直播窗口 (${files.length}个文件)`,
};
} else {
liveWindow.loadFile(indexHtml, { hash: `/${path}` });
throw new Error("直播窗口未找到或已关闭");
}
return { success: true };
} catch (error: any) {
return { success: false, error: error.message };
console.error("展示文件失败:", error);
return {
success: false,
error: error.message || "展示文件失败",
};
}
});
ipcMain.handle("remove-file-from-live", async (_, { index, fileName }) => {
try {
if (liveState.liveWindow && !liveState.liveWindow.isDestroyed()) {
liveState.liveWindow.webContents.send("remove-file-display", {
index,
fileName,
timestamp: new Date().toISOString(),
});
}
return {
success: true,
message: "已通知直播窗口移除文件",
};
} catch (error: any) {
console.error("移除文件展示失败:", error);
return {
success: false,
error: error.message || "移除文件展示失败",
};
}
});
ipcMain.handle("clear-all-files-from-live", async () => {
try {
if (liveState.liveWindow && !liveState.liveWindow.isDestroyed()) {
liveState.liveWindow.webContents.send(
"clear-all-files-display",
{
timestamp: new Date().toISOString(),
},
);
}
return {
success: true,
message: "已通知直播窗口清空所有文件",
};
} catch (error: any) {
console.error("清空文件展示失败:", error);
return {
success: false,
error: error.message || "清空文件展示失败",
};
}
});
}
async function getSessionId(requestBody: object) {
try {
const response = await fetch("http://ywpt.hx.cn/dmhx/get_sessionid", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});
// 首先检查响应内容类型
const contentType = response.headers.get("content-type");
if (!contentType || !contentType.includes("application/json")) {
// 如果不是JSON读取原始文本进行调试
const rawText = await response.text();
console.warn("服务器返回非JSON响应:", rawText);
// 尝试解析可能的JSON响应即使Content-Type不正确
try {
const data = JSON.parse(rawText);
if (response.ok && data.sessionid) {
return { success: true, sessionId: data.sessionid };
} else {
return {
success: false,
error: data.message || "服务器返回非JSON格式",
};
}
} catch (parseError) {
return {
success: false,
error: `服务器响应格式错误: ${rawText.substring(0, 100)}...`,
};
}
}
// 如果是JSON正常解析
const data = await response.json();
if (response.ok && data.sessionid) {
return { success: true, sessionId: data.sessionid };
} else {
return { success: false, error: data.message || "未知错误" };
}
} catch (error: any) {
console.error("Error in getSessionId:", error);
return { success: false, error: error.message };
}
}
async function sendMessage(requestBody: object) {
try {
const response = await fetch(`http://ywpt.hx.cn/dmhx/human`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});
const data = await response.json();
if (response.ok) {
return { success: true };
} else {
return { success: false, error: data.message || "未知错误" };
}
} catch (error: any) {
console.error("Error in sendMessage:", error);
return { success: false, error: error.message };
}
}
// 导出状态(用于调试)
export const getLiveState = () => ({ ...liveState });

View File

@@ -9,6 +9,9 @@ let InstallWindows: BrowserWindow | null = null;
export function setupWorkflowHandlers() {
let lastJobSummary = "这是我们今天介绍的第一个岗位";
// 存储用户确认回调的Map
const modelDownloadCallbacks = new Map<string, { confirm: Function, reject: Function }>();
// 打开安装窗口
ipcMain.handle("open-install-window", async (_, args) => {
try {
@@ -197,6 +200,259 @@ export function setupWorkflowHandlers() {
return await checkOllamaServer();
});
// 检查指定模型是否存在
ipcMain.handle("check-model-exists", async (_, modelName = "qwen3:8b") => {
try {
const response = await fetch("http://127.0.0.1:11434/api/tags", {
method: "GET",
headers: { "Content-Type": "application/json" },
});
if (!response.ok) {
throw new Error(`Ollama API error: ${response.statusText}`);
}
const data = await response.json();
const models = data.models || [];
// 检查模型是否存在于本地
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 }))
};
} catch (error: any) {
console.error("Check model error:", error);
return {
success: false,
exists: false,
error: error.message
};
}
});
// 加载模型检查ollama状<61><E78AB6><EFBFBD>下载模型如果不存在
ipcMain.handle("load-model", async (_, modelName = "qwen3:8b") => {
const webContents = BrowserWindow.getFocusedWindow()?.webContents;
const sendStatus = (status: string, type = "info") => {
if (webContents && !webContents.isDestroyed()) {
webContents.send("model-load-progress", {
status,
type,
timestamp: new Date().toISOString()
});
}
};
// 发送询问是否下载模型的消息
const askUserToDownload = () => {
if (webContents && !webContents.isDestroyed()) {
webContents.send("model-download-confirm", {
modelName,
message: `模型 ${modelName} 不存在,是否下载?`,
timestamp: new Date().toISOString()
});
}
};
try {
sendStatus("正在检查Ollama服务状态...", "info");
// 1. 检查Ollama是否运行
const isOllamaRunning = await checkOllamaServer();
if (!isOllamaRunning) {
sendStatus("Ollama服务未运行正在启动...", "warning");
try {
// 尝试启动Ollama服务
await runCommand("ollama", ["ps"]);
await new Promise(resolve => setTimeout(resolve, 3000));
const isRunningNow = await checkOllamaServer();
if (!isRunningNow) {
throw new Error("无法启动Ollama服务请手动启动");
}
sendStatus("Ollama服务启动成功", "success");
} catch (error: any) {
sendStatus(`启动Ollama服务失败: ${error.message}`, "error");
return {
success: false,
message: `Ollama服务启动失败: ${error.message}`,
downloaded: false
};
}
} else {
sendStatus("Ollama服务正在运行", "success");
}
// 2. 检查模型是否存在
sendStatus(`正在检查模型 ${modelName} 是否存在...`, "info");
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);
});
if (modelCheckResult.exists) {
sendStatus(`模型 ${modelName} 已存在,无需下载`, "success");
return {
success: true,
message: `模型 ${modelName} 已就绪`,
downloaded: false
};
}
// 3. 模型不存在,询问用户是否下载
askUserToDownload();
// 等待用户确认
const userConfirmed = await new Promise<boolean>((resolve, reject) => {
const timeout = setTimeout(() => {
modelDownloadCallbacks.delete(modelName);
resolve(false); // 30秒超时自动取消
}, 30000);
// 存储回调函数
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
};
}
// 4. 用户确认,开始下载模型
sendStatus(`开始下载模型 ${modelName},这可能需要一些时间...`, "info");
await new Promise<void>((resolve, reject) => {
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()
});
}
};
process.stdout.on("data", sendProgress);
process.stderr.on("data", sendProgress);
process.on("close", (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`模型下载失败,退出码: ${code}`));
}
});
process.on("error", (err: any) => {
reject(new Error(`启动下载进程失败: ${err.message}`));
});
});
sendStatus(`模型 ${modelName} 下载完成!`, "success");
return {
success: true,
message: `模型 ${modelName} 下载并加载成功`,
downloaded: true
};
} catch (error: any) {
console.error("Load model error:", error);
sendStatus(`加载模型失败: ${error.message}`, "error");
return {
success: false,
message: error.message,
downloaded: false
};
}
});
// 处理用户对模型下载的确认响应
ipcMain.on("model-download-confirm-response", (_event, data) => {
const { modelName, confirmed } = data;
const callback = modelDownloadCallbacks.get(modelName);
if (callback) {
modelDownloadCallbacks.delete(modelName);
if (confirmed) {
callback.confirm();
} else {
callback.reject(new Error("用户取消了模型下载"));
}
}
});
// 润色文本的处理器
ipcMain.handle("polish-text", async (_, text) => {
try {
if (!text || typeof text !== 'string' || text.trim() === '') {
return {
success: false,
error: "输入文本不能为空"
};
}
const systemPrompt = `你是一个专业的文本润色专家。请将以下文本进行润色,使其更加流畅、自然、专业。要求:
1. 保持原意不变
2. 使语言更加流畅自然
3. 提升表达的准确性
4. 适合直播场合使用
5. 保持简洁明了
请直接返回润色后的文本,不要添加任何其他说明或解释。
原文:${text.trim()}`;
const polishedText = await runOllamaNonStream(systemPrompt, "qwen3:8b");
if (!polishedText) {
throw new Error("AI模型返回为空");
}
return {
success: true,
data: polishedText.trim()
};
} catch (error: any) {
console.error("润色文本失败:", error);
return {
success: false,
error: error.message || "润色服务出现错误"
};
}
});
// 处理器:检查服务,如果没运行,就用一个轻量命令唤醒它
ipcMain.handle("ensure-ollama-running", async () => {
let isRunning = await checkOllamaServer();