deepseekService = $deepseekService; $this->taskCenterService = $taskCenterService; } /** * 可选供应商 * * @param Request $request * @return mixed */ public function chatWithReasoner(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); $result = $this->deepseekService->chatWithReasoner($data); return $this->success($result); } /** * 音色列表 * * @param Request $request * @return mixed */ public function timbreList(Request $request) { $data = $request->all(); $result = $this->deepseekService->timbreList($data); return $this->success($result); } // 重置段落音频状态 public function resetParagraphAudio(Request $request) { $data = $request->all(); $result = $this->deepseekService->resetParagraphAudio($data); return $this->success(['success'=>$result ? 1 : 0]); } // 保存段落音频 public function saveParagraphAudio(Request $request) { $data = $request->all(); $result = $this->deepseekService->saveParagraphAudio($data); return $this->success(['success'=>$result ? 1 : 0]); } // 保存段落音频 public function insertAudioEffect(Request $request) { $data = $request->all(); $result = $this->deepseekService->insertAudioEffect($data); return $this->success(['success'=>$result ? 1 : 0]); } // 保存段落音频 public function insertBgm(Request $request) { $data = $request->all(); $result = $this->deepseekService->insertBgm($data); return $this->success(['success'=>$result ? 1 : 0]); } /** * 获取段落视频 */ public function paragraphAudios(Request $request) { $data = $request->all(); $result = $this->deepseekService->getParagraphAudios($data); return $this->success($result); } /** * 新增合成任务 * * @param Request $request * @return mixed */ public function addGenerateTask(Request $request) { $data = $request->all(); $result = $this->deepseekService->addGenerateTask($data); return $this->success(['success'=>$result ? 1 : 0]); } // 获取火山临时token public function setStsToken(Request $request) { $data = $request->all(); $result = $this->deepseekService->setStsToken($data); return $this->success($result); } public function sseLink(Request $request) { $data = $request->all(); $bid = getProp($data, 'bid'); $version_id = getProp($data, 'version_id'); $cid = getProp($data, 'cid'); // 禁用输出缓冲 while (ob_get_level()) ob_end_clean(); // 设置SSE所需的HTTP头 header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); header('Connection: keep-alive'); header('X-Accel-Buffering: no'); // 防止Nginx缓冲 // 允许跨域请求(根据需求设置) header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Credentials: true'); // 设置脚本执行时间无限 set_time_limit(0); // 手动刷新输出缓冲区 ob_implicit_flush(true); if(ob_get_level()>0) ob_flush(); flush(); // 客户端ID(用于多用户区分) $clientId = $_SERVER['REMOTE_ADDR'] . ':' . $_SERVER['REMOTE_PORT']; $lastEventId = isset($_SERVER['HTTP_LAST_EVENT_ID']) ? intval($_SERVER['HTTP_LAST_EVENT_ID']) : 0; // dd($clientId, $lastEventId); // 发送初始欢迎消息 $this->sendEvent([ // 'message' => "欢迎使用SSE服务! 你的客户端ID: {$clientId}", // 'type' => 'info', // 'timestamp' => date('H:i:s') 'code' => 0, 'msg' => "已连接! 你的客户端ID: {$clientId}", 'data' => [], ]); if (!$bid && !$version_id && !$cid) { $this->sendEvent([ // 'message' => '参数异常', // 'type' => 'error', // 'timestamp' => date('H:i:s') 'code' => -1, 'msg' => "参数异常", 'data' => [], ]); } $redis_key = "select-{$bid}-{$version_id}-{$cid}"; // 主循环 - 定期发送数据 while (true) { // 检查客户端是否断开连接 if (connection_aborted()) { exit(); } // 判断是否有待更新数据,有则查询 $ids = Redis::smembers($redis_key); if (count($ids) > 0) { $count = DB::table('mp_chapter_paragraph_audios')->whereIn('id', $ids)->where(function($query) { return $query->where('generate_status', '!=', '制作中')->orWhere('error_msg', '!=', ''); })->count('id'); // 如果有更新数据,则发送消息 if ($count > 0) { // 查询更新后的信息 $paragraph_urls = DB::table('mp_chapter_paragraph_audios')->where('bid', $bid)->where('version_id', $version_id) ->where('cid', $cid)->where(function($query) { return $query->where('generate_status', '!=', '制作中')->orWhere('error_msg', '!=', ''); })->select('id', 'sequence', 'generate_status', 'paragraph_audio_url', 'error_msg') ->get()->map(function ($value) { return (array)$value; })->toArray(); $rem_ids = []; foreach ($paragraph_urls as $item) { $rem_ids[] = getProp($item, 'id'); } // 发送消息 $this->sendEvent([ 'code' => 0, 'msg' => "", 'data' => $paragraph_urls, // 'message' => '数据更新', // 'type' => 'update', // 'timestamp' => date('H:i:s') ]); // 删除有数据的id Redis::srem($redis_key, $rem_ids); } } // 等待1秒 sleep(1); } } private function sendEvent($data, $event = null, $id = null) { // // 事件ID // if ($id !== null) { // echo "id: {$id}\n"; // } // 事件类型 if ($event !== null) { echo "event: {$event}\n"; } // 数据部分(JSON格式) echo "data: " . json_encode($data) . "\n\n"; // 立即发送数据 if(ob_get_level()>0) ob_flush(); flush(); } public function emotionGroups(Request $request) { $data = $request->all(); $result = $this->deepseekService->emotionGroups($data); return $this->success($result); } /** * 剧本列表 * * @param Request $request * @return mixed */ public function scriptList(Request $request) { $data = $request->all(); $result = $this->deepseekService->scriptList($data); return $this->success($result, [new DeepSeekTransformer(), 'newBuildScriptList']); } public function scripts(Request $request) { $data = $request->all(); $result = $this->deepseekService->scripts($data); return $this->success($result); } // 剧本详情 public function scriptInfo(Request $request) { $data = $request->all(); $result = $this->deepseekService->scriptInfo($data); return $this->success($result); } /** * 创建剧本 * * @param Request $request * @return mixed */ public function createScript(Request $request) { $data = $request->all(); $result = $this->deepseekService->createScript($data); return $this->success(['script_id' => $result]); } // 编辑剧本 public function editScript(Request $request) { $data = $request->all(); $result = $this->deepseekService->editScript($data); return $this->success(['success' => $result ? 1 : 0]); } // 删除剧本 public function delScript(Request $request) { $data = $request->all(); $result = $this->deepseekService->delScript($data); return $this->success(['success' => $result ? 1 : 0]); } /** * 创建分集组 * * @param Request $request * @return mixed */ public function createEpisode(Request $request) { $data = $request->all(); $result = $this->deepseekService->createEpisode($data); return $this->success(['group_id' => $result]); } // 编辑剧本 public function editEpisode(Request $request) { $data = $request->all(); $result = $this->deepseekService->editEpisode($data); return $this->success(['success' => $result ? 1 : 0]); } // 删除剧本 public function delEpisode(Request $request) { $data = $request->all(); $result = $this->deepseekService->delEpisode($data); return $this->success(['success' => $result ? 1 : 0]); } /** * 与DeepSeek对话(带文件上传) * @param Request $request * @return mixed * @throws ApiException */ public function chatWithFile(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); $result = $this->deepseekService->chatWithFile($data); return $this->success($result); } /** * 与DeepSeek对话(带文件上传 - 流式输出版本) * @param Request $request * @return \Symfony\Component\HttpFoundation\StreamedResponse * @throws ApiException */ public function chatWithFileStream(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); return response()->stream(function () use ($data) { // 禁用所有输出缓冲 if (ob_get_level()) { ob_end_clean(); } // 设置输出缓冲为关闭 ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); // 立即刷新 if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { $generator = $this->deepseekService->chatWithFileStream($data); foreach ($generator as $chunk) { // 发送 SSE 格式的数据 $jsonData = json_encode($chunk, JSON_UNESCAPED_UNICODE); echo "data: {$jsonData}\n\n"; // 强制刷新输出缓冲区 if (ob_get_level() > 0) { ob_flush(); } flush(); // 检查客户端是否断开连接 if (connection_aborted()) { break; } } // 发送结束标记 echo "data: [DONE]\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } catch (\Exception $e) { // 发送错误信息 $error = [ 'type' => 'error', 'msg' => $e->getMessage(), 'code' => $e->getCode() ]; $jsonError = json_encode($error, JSON_UNESCAPED_UNICODE); echo "data: {$jsonError}\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', // 防止 Nginx 缓冲 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true' ]); } public function generateEpisodes(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); return response()->stream(function () use ($data) { // 禁用所有输出缓冲 if (ob_get_level()) { ob_end_clean(); } // 设置输出缓冲为关闭 ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); // 立即刷新 if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { $generator = $this->deepseekService->generateEpisodes($data); foreach ($generator as $chunk) { // 发送 SSE 格式的数据 $jsonData = json_encode($chunk, JSON_UNESCAPED_UNICODE); echo "data: {$jsonData}\n\n"; // 强制刷新输出缓冲区 if (ob_get_level() > 0) { ob_flush(); } flush(); // 检查客户端是否断开连接 if (connection_aborted()) { break; } } // 发送结束标记 echo "data: [DONE]\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } catch (\Exception $e) { // 发送错误信息 $error = [ 'type' => 'error', 'msg' => $e->getMessage(), 'code' => $e->getCode() ]; $jsonError = json_encode($error, JSON_UNESCAPED_UNICODE); echo "data: {$jsonError}\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', // 防止 Nginx 缓冲 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true' ]); } public function getEpisodeContent(Request $request) { $data = $request->all(); $result = $this->deepseekService->getEpisodeContent($data); return $this->success($result); } // 保存剧本 public function saveEpisodeContent(Request $request) { $data = $request->all(); $result = $this->deepseekService->saveEpisodeContent($data); return $this->success(['success'=>$result ? 1 : 0]); } // 导出剧本 public function exportScript(Request $request) { $data = $request->all(); // 验证必要参数 if (empty($data['script_id'])) { return $this->error('20003:缺少剧本ID参数'); } // 直接调用导出服务,该方法会直接输出文件并退出 $this->deepseekService->exportScript($data); } public function getBookContent(Request $request) { $data = $request->all(); // 直接调用导出服务,该方法会直接输出文件并退出 $result = $this->deepseekService->getBookContent($data); return $this->success($result); } public function createGenerateText(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); $result = $this->deepseekService->createGenerateText($data); return $this->success($result); } /** * 通用文生文(非流式版本) * @param Request $request * @return mixed */ public function newGenerateText(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); // stream=1 + script_id:剧本资产生成走异步任务模式(worker 执行,SSE 每 10s 轮询推送,最长 30 分钟) if ((int)getProp($data, 'stream', 0) === 1 && (int)getProp($data, 'script_id', 0) > 0) { $taskId = $this->deepseekService->createScriptGenerateAsyncTask($data); $uid = (int)\App\Facade\Site::getUid(); return response()->stream(function () use ($taskId, $uid) { if (ob_get_level()) { ob_end_clean(); } ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } $push = function (array $chunk) { echo "data: " . json_encode($chunk, JSON_UNESCAPED_UNICODE) . "\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); }; // SSE 轮询:每 10s 查询一次任务状态与新增 chunks,最长等待 30 分钟 $deadline = time() + 1800; $lastSeq = -1; $terminalPushed = false; try { while (time() < $deadline) { if (connection_aborted()) { // 浏览器断开:停止推送,后台 worker 继续执行并落库 return; } $poll = $this->deepseekService->pollScriptGenerateTask($taskId, $uid, $lastSeq); if (!$poll['found']) { $push([ 'type' => 'error', 'message' => '生成任务不存在或无权访问', ]); return; } foreach ($poll['chunks'] as $chunk) { $push($chunk); $lastSeq++; if (isset($chunk['type']) && in_array($chunk['type'], ['done', 'error'])) { $terminalPushed = true; } } $status = (string)getProp($poll, 'status', ''); // 任务已结束:补齐可能尚未写入的 done/error chunk 后关闭连接 if (in_array($status, ['success', 'failed'])) { $waitRound = 0; while (!$terminalPushed && $waitRound < 3) { sleep(3); $waitRound++; $pollRetry = $this->deepseekService->pollScriptGenerateTask($taskId, $uid, $lastSeq); foreach ($pollRetry['chunks'] as $chunk) { $push($chunk); $lastSeq++; if (isset($chunk['type']) && in_array($chunk['type'], ['done', 'error'])) { $terminalPushed = true; } } } if (!$terminalPushed && $status === 'failed') { $push([ 'type' => 'error', 'message' => (string)getProp($poll, 'error_message', '') !== '' ? (string)getProp($poll, 'error_message', '') : '内容生成失败', ]); } return; } // 未完成:每 10s 轮询一次 sleep(10); } // 30 分钟未完成:向前端提示超时,任务继续在后台执行 $push([ 'type' => 'error', 'message' => '生成超时,任务仍在后台继续执行,请稍后查看任务列表', ]); } catch (\Throwable $e) { $push([ 'type' => 'error', 'message' => $e->getMessage(), ]); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', ]); } // stream=1:SSE 流式输出(格式与 generateText 一致:yield type=content/reasoning/done) if ((int)getProp($data, 'stream', 0) === 1) { return response()->stream(function () use ($data) { if (ob_get_level()) { ob_end_clean(); } ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { $result = $this->deepseekService->newGenerateText($data); foreach ($result as $chunk) { echo "data: " . json_encode($chunk, JSON_UNESCAPED_UNICODE) . "\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); if (connection_aborted()) { break; } } } catch (\Throwable $e) { echo "data: " . json_encode([ 'type' => 'error', 'message' => $e->getMessage(), ], JSON_UNESCAPED_UNICODE) . "\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', ]); } $result = $this->deepseekService->newGenerateText($data); return $this->success($result); } /** * 获取剧本资产生成任务列表 * @param Request $request * @return mixed */ public function getScriptGenerateTasks(Request $request) { $data = $request->all(); $result = $this->deepseekService->getScriptGenerateTasks($data); return $this->success($result, [new DeepSeekTransformer(), 'newBuildScriptGenerateTaskList']); } // 保存剧本沟通记录 public function saveScriptChatHistory(Request $request) { $data = $request->all(); $result = $this->deepseekService->saveScriptChatHistory($data); return $this->success(['success'=>$result ? 1 : 0]); } /** * 获取剧本的历史对话记录 * @param Request $request * @return mixed */ public function getScriptChatHistory(Request $request) { $data = $request->all(); $result = $this->deepseekService->getScriptChatHistory($data); return $this->success($result); } // 新建对话 public function addChat(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); return response()->stream(function () use ($data) { // 禁用所有输出缓冲 if (ob_get_level()) { ob_end_clean(); } // 设置输出缓冲为关闭 ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); // 立即刷新 if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { $generator = $this->deepseekService->addChat($data); foreach ($generator as $chunk) { // 发送 SSE 格式的数据 $jsonData = json_encode($chunk, JSON_UNESCAPED_UNICODE); echo "data: {$jsonData}\n\n"; // 强制刷新输出缓冲区 if (ob_get_level() > 0) { ob_flush(); } flush(); // 检查客户端是否断开连接 if (connection_aborted()) { break; } } // 发送结束标记 echo "data: [DONE]\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } catch (\Exception $e) { // 发送错误信息 $error = [ 'type' => 'error', 'msg' => $e->getMessage(), 'code' => $e->getCode() ]; $jsonError = json_encode($error, JSON_UNESCAPED_UNICODE); echo "data: {$jsonError}\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', // 防止 Nginx 缓冲 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true' ]); } // 调整大纲 public function reGenerateAnime(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); return response()->stream(function () use ($data) { // 禁用所有输出缓冲 if (ob_get_level()) { ob_end_clean(); } // 设置输出缓冲为关闭 ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); // 立即刷新 if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { $generator = $this->deepseekService->reGenerateAnime($data); foreach ($generator as $chunk) { // 发送 SSE 格式的数据 $jsonData = json_encode($chunk, JSON_UNESCAPED_UNICODE); echo "data: {$jsonData}\n\n"; // 强制刷新输出缓冲区 if (ob_get_level() > 0) { ob_flush(); } flush(); // 检查客户端是否断开连接 if (connection_aborted()) { break; } } // 发送结束标记 echo "data: [DONE]\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } catch (\Exception $e) { // 发送错误信息 $error = [ 'type' => 'error', 'msg' => $e->getMessage(), 'code' => $e->getCode() ]; $jsonError = json_encode($error, JSON_UNESCAPED_UNICODE); echo "data: {$jsonError}\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', // 防止 Nginx 缓冲 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true' ]); } public function chat(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); return response()->stream(function () use ($data) { // 禁用所有输出缓冲 if (ob_get_level()) { ob_end_clean(); } // 设置输出缓冲为关闭 ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); // 立即刷新 if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { $generator = $this->deepseekService->chat($data); foreach ($generator as $chunk) { // 发送 SSE 格式的数据 $jsonData = json_encode($chunk, JSON_UNESCAPED_UNICODE); echo "data: {$jsonData}\n\n"; // 强制刷新输出缓冲区 if (ob_get_level() > 0) { ob_flush(); } flush(); // 检查客户端是否断开连接 if (connection_aborted()) { break; } } // 发送结束标记 echo "data: [DONE]\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } catch (\Exception $e) { // 发送错误信息 $error = [ 'type' => 'error', 'msg' => $e->getMessage(), 'code' => $e->getCode() ]; $jsonError = json_encode($error, JSON_UNESCAPED_UNICODE); echo "data: {$jsonError}\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', // 防止 Nginx 缓冲 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true' ]); } // 新建对话 public function addChatForAce(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); return response()->stream(function () use ($data) { // 禁用所有输出缓冲 if (ob_get_level()) { ob_end_clean(); } // 设置输出缓冲为关闭 ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); // 立即刷新 if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { $generator = $this->deepseekService->addChatForAce($data); foreach ($generator as $chunk) { // 发送 SSE 格式的数据 $jsonData = json_encode($chunk, JSON_UNESCAPED_UNICODE); echo "data: {$jsonData}\n\n"; // 强制刷新输出缓冲区 if (ob_get_level() > 0) { ob_flush(); } flush(); // 检查客户端是否断开连接 if (connection_aborted()) { break; } } // 发送结束标记 echo "data: [DONE]\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } catch (\Exception $e) { // 发送错误信息 $error = [ 'type' => 'error', 'msg' => $e->getMessage(), 'code' => $e->getCode() ]; $jsonError = json_encode($error, JSON_UNESCAPED_UNICODE); echo "data: {$jsonError}\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', // 防止 Nginx 缓冲 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true' ]); } // 调整大纲 public function reGenerateAnimeForAce(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); return response()->stream(function () use ($data) { // 禁用所有输出缓冲 if (ob_get_level()) { ob_end_clean(); } // 设置输出缓冲为关闭 ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); // 立即刷新 if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { $generator = $this->deepseekService->reGenerateAnimeForAce($data); foreach ($generator as $chunk) { // 发送 SSE 格式的数据 $jsonData = json_encode($chunk, JSON_UNESCAPED_UNICODE); echo "data: {$jsonData}\n\n"; // 强制刷新输出缓冲区 if (ob_get_level() > 0) { ob_flush(); } flush(); // 检查客户端是否断开连接 if (connection_aborted()) { break; } } // 发送结束标记 echo "data: [DONE]\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } catch (\Exception $e) { // 发送错误信息 $error = [ 'type' => 'error', 'msg' => $e->getMessage(), 'code' => $e->getCode() ]; $jsonError = json_encode($error, JSON_UNESCAPED_UNICODE); echo "data: {$jsonError}\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', // 防止 Nginx 缓冲 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true' ]); } public function chatForAce(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); return response()->stream(function () use ($data) { // 禁用所有输出缓冲 if (ob_get_level()) { ob_end_clean(); } // 设置输出缓冲为关闭 ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); // 立即刷新 if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { $generator = $this->deepseekService->chatForAce($data); foreach ($generator as $chunk) { // 发送 SSE 格式的数据 $jsonData = json_encode($chunk, JSON_UNESCAPED_UNICODE); echo "data: {$jsonData}\n\n"; // 强制刷新输出缓冲区 if (ob_get_level() > 0) { ob_flush(); } flush(); // 检查客户端是否断开连接 if (connection_aborted()) { break; } } // 发送结束标记 echo "data: [DONE]\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } catch (\Exception $e) { // 发送错误信息 $error = [ 'type' => 'error', 'msg' => $e->getMessage(), 'code' => $e->getCode() ]; $jsonError = json_encode($error, JSON_UNESCAPED_UNICODE); echo "data: {$jsonError}\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', // 防止 Nginx 缓冲 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true' ]); } // 对话改图 public function chatChangeImg(Request $request) { $data = $request->all(); $result = $this->deepseekService->chatChangeImg($data); return $this->success($result); } /** * 通用文生文接口 - 流式输出 * @param Request $request * @return \Symfony\Component\HttpFoundation\StreamedResponse */ public function generateText(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); // 处理上传的图片文件 if ($request->hasFile('images')) { $data['images'] = $request->file('images'); } else if ($request->hasFile('image')) { // 支持单张图片 $data['images'] = [$request->file('image')]; } // 处理上传的视频文件(仅 Gemini 模型支持) if ($request->hasFile('videos')) { $data['videos'] = $request->file('videos'); } else if ($request->hasFile('video')) { $data['videos'] = [$request->file('video')]; } return response()->stream(function () use ($data) { // 禁用所有输出缓冲 if (ob_get_level()) { ob_end_clean(); } // 设置输出缓冲为关闭 ini_set('output_buffering', 'off'); ini_set('zlib.output_compression', 'off'); // 立即刷新 if (function_exists('apache_setenv')) { apache_setenv('no-gzip', '1'); } try { // 构建可序列化的参数快照(剔除上传文件对象) $paramsSnapshot = $data; unset($paramsSnapshot['images'], $paramsSnapshot['videos']); // 创建任务中心记录(文字任务直接更新任务结果) $taskCenter = $this->taskCenterService->createTask('text', [ 'title' => '文生文', 'prompt' => getProp($data, 'prompt', ''), 'params' => json_encode($paramsSnapshot, JSON_UNESCAPED_UNICODE), ]); $taskCenterId = $taskCenter->id; $generator = $this->deepseekService->generateText($data); $fullContent = ''; $firstChunk = true; foreach ($generator as $chunk) { // 第一条消息附加任务中心ID(不改变原消息结构和输出顺序) if ($firstChunk) { $chunk['task_center_id'] = $taskCenterId; $firstChunk = false; } // 累积完整文本内容 if (isset($chunk['type']) && $chunk['type'] === 'content') { $fullContent .= (string)getProp($chunk, 'content', ''); } if (isset($chunk['type']) && $chunk['type'] === 'done') { $fullContent = (string)getProp($chunk, 'full_content', $fullContent); } // 发送 SSE 格式的数据 $jsonData = json_encode($chunk, JSON_UNESCAPED_UNICODE); echo "data: {$jsonData}\n\n"; // 强制刷新输出缓冲区 if (ob_get_level() > 0) { ob_flush(); } flush(); // 检查客户端是否断开连接 if (connection_aborted()) { break; } } // 发送结束标记 echo "data: [DONE]\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); // 文字任务直接更新任务中心结果 $this->taskCenterService->updateTask($taskCenterId, [ 'status' => 'success', 'result' => $fullContent, 'error_message' => null, ]); } catch (\Exception $e) { // 任务失败,更新任务中心状态(任务中心记录可能未创建成功) if (isset($taskCenterId)) { $this->taskCenterService->updateTask($taskCenterId, [ 'status' => 'failed', 'error_message' => $e->getMessage(), ]); } // 发送错误信息 $error = [ 'type' => 'error', 'msg' => $e->getMessage(), 'code' => $e->getCode() ]; $jsonError = json_encode($error, JSON_UNESCAPED_UNICODE); echo "data: {$jsonError}\n\n"; if (ob_get_level() > 0) { ob_flush(); } flush(); } }, 200, [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no', // 防止 Nginx 缓冲 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => 'true' ]); } /** * 重新生成分段剧本 * @param Request $request * @return mixed */ public function regenerateSegmentScript(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); $result = $this->deepseekService->regenerateSegmentScript($data); return $this->success($result); } /** * 保存剧集片段(预览确认模式使用):删除旧数据并保存新数据 * @param Request $request * @return mixed */ public function saveEpisodeActs(Request $request) { // 忽略所有超时限制 set_time_limit(0); ini_set('max_execution_time', '0'); $data = $request->all(); $result = $this->deepseekService->saveEpisodeActs($data); return $this->success($result); } /** * 批量生成多个分集 * @param Request $request * @return mixed */ public function batchGenerateEpisodes(Request $request) { $data = $request->all(); $result = $this->deepseekService->batchGenerateEpisodes($data); return $this->success($result); } /** * 获取批量生成任务状态 * @param Request $request * @return mixed */ public function getBatchGenerationTaskStatus(Request $request) { $data = $request->all(); $result = $this->deepseekService->getBatchGenerationTaskStatus($data); return $this->success($result); } }