Bläddra i källkod

增加根据图片反推提示词

lh 3 månader sedan
förälder
incheckning
0eebfe10a7

+ 1 - 0
app/Http/Controllers/Anime/AnimeController.php

@@ -1060,6 +1060,7 @@ class AnimeController extends BaseController
         }
     }
 
+    // 文生图(通用)
     public function generateImg(Request $request) {
         $data = $request->all();
 

+ 83 - 3
app/Http/Controllers/DeepSeek/DeepSeekController.php

@@ -621,9 +621,89 @@ class DeepSeekController extends BaseController
         return $this->success($result);
     }
 
-    public function testChat(Request $request) {
+    /**
+     * 通用文生文接口 - 流式输出
+     * @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();
-        $this->deepseekService->chat($data);
-        return $this->success(true);
+        
+        // 处理上传的图片文件
+        if ($request->hasFile('images')) {
+            $data['images'] = $request->file('images');
+        } else if ($request->hasFile('image')) {
+            // 支持单张图片
+            $data['images'] = [$request->file('image')];
+        }
+
+        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->generateText($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'
+        ]);
     }
 }

+ 184 - 3
app/Services/DeepSeek/DeepSeekService.php

@@ -36,7 +36,13 @@ class DeepSeekService
             'Authorization' => 'Bearer '.$this->api_key,
             'Content-Type'  => 'application/json; charset=UTF-8'
         ];
-        $this->valid_text_models = ['doubao-seed-2-0-pro-260215', 'doubao-seed-2-0-lite-260215', 'deepseek-v3-2-251201'];
+        // 火山引擎模型映射:模型名称 => endpoint_id
+        $this->valid_text_models = [
+            'doubao-seed-2-0-pro-260215' => env('VOLC_DOUBAO_PRO_ENDPOINT', 'doubao-seed-2-0-pro-260215'),
+            'doubao-seed-2-0-lite-260215' => env('VOLC_DOUBAO_LITE_ENDPOINT', 'doubao-seed-2-0-lite-260215'),
+            'doubao-seed-2-0-mini-260215' => env('VOLC_DOUBAO_MINI_ENDPOINT', 'doubao-seed-2-0-mini-260215'),
+            'deepseek-v3-2-251201' => env('VOLC_DEEPSEEK_V3_ENDPOINT', 'deepseek-v3-2-251201')
+        ];
         $this->sys_message = [
                 'role'    => 'system',
                 'content' => "你是一个专业的文档分析助手及资深编剧,请根据用户提供的文档内容完成示例格式的内容输出(需通过以下几个板块进行回复: <故事梗概><剧本亮点><人物关系><核心矛盾><主体列表><美术风格><场景列表><分集剧本>,每个板块之间用###分隔;同时板块之间需满足以下要求:\n
@@ -139,6 +145,181 @@ class DeepSeekService
 
     }
 
+    /**
+     * 通用文生文方法 - 支持多模型流式输出和图片输入
+     * 
+     * @param array $data 请求参数
+     * @return \Generator 返回生成器,用于流式输出
+     */
+    public function generateText($data) {
+        // 获取请求参数
+        $model = getProp($data, 'model', 'deepseek-v3-2-251201'); // 默认使用deepseek-v3
+        $messages = getProp($data, 'messages', []);
+        $systemPrompt = getProp($data, 'system_prompt', '');
+        $prompt = getProp($data, 'prompt', '');
+        $images = getProp($data, 'images', []); // 支持多张图片
+        $imageUrls = getProp($data, 'image_urls', []); // 支持图片URL
+        
+        // 如果没有提供messages,则根据system_prompt、content和images构建
+        if (empty($messages)) {
+            $messages = [];
+            
+            // 添加系统提示词
+            if (!empty($systemPrompt)) {
+                $messages[] = [
+                    'role' => 'system',
+                    'content' => $systemPrompt
+                ];
+            }
+            
+            // 构建用户消息内容(支持文本+图片)
+            if (!empty($prompt) || !empty($images) || !empty($imageUrls)) {
+                $userMessage = [
+                    'role' => 'user',
+                    'content' => []
+                ];
+                
+                // 添加文本内容
+                if (!empty($prompt)) {
+                    $userMessage['content'][] = [
+                        'type' => 'text',
+                        'text' => $prompt
+                    ];
+                }
+                
+                // 处理上传的图片文件
+                if (!empty($images)) {
+                    if (!is_array($images)) {
+                        $images = [$images];
+                    }
+                    
+                    foreach ($images as $image) {
+                        $imageBase64 = $this->processImageToBase64($image);
+                        if ($imageBase64) {
+                            $userMessage['content'][] = [
+                                'type' => 'image_url',
+                                'image_url' => [
+                                    'url' => "data:image/jpeg;base64,{$imageBase64}"
+                                ]
+                            ];
+                        }
+                    }
+                }
+                
+                // 处理图片URL
+                if (!empty($imageUrls)) {
+                    if (!is_array($imageUrls)) {
+                        $imageUrls = [$imageUrls];
+                    }
+                    
+                    foreach ($imageUrls as $url) {
+                        if (!empty($url)) {
+                            $userMessage['content'][] = [
+                                'type' => 'image_url',
+                                'image_url' => [
+                                    'url' => $url
+                                ]
+                            ];
+                        }
+                    }
+                }
+                
+                // 如果只有文本内容,简化为字符串格式
+                if (count($userMessage['content']) === 1 && $userMessage['content'][0]['type'] === 'text') {
+                    $userMessage['content'] = $userMessage['content'][0]['text'];
+                }
+                
+                $messages[] = $userMessage;
+            }
+        }
+        
+        // 验证messages不为空
+        if (empty($messages)) {
+            Utils::throwError('20003:请提供有效的对话内容');
+        }
+        
+        // 构建请求参数
+        $post_data = [
+            'model' => $model,
+            'messages' => $messages,
+            'max_tokens' => getProp($data, 'max_tokens', 8192),
+            'temperature' => getProp($data, 'temperature', 1),
+            'frequency_penalty' => getProp($data, 'frequency_penalty', 0),
+            'presence_penalty' => getProp($data, 'presence_penalty', 0),
+            'thinking'=>'disabled',
+            'stream' => true  // 启用流式输出
+        ];
+        
+        // 添加可选参数
+        if (isset($data['top_p'])) {
+            $post_data['top_p'] = $data['top_p'];
+        }
+        
+        if (isset($data['response_format'])) {
+            $post_data['response_format'] = $data['response_format'];
+        }
+        
+        dLog('deepseek')->info('通用文生文请求参数', ['model' => $model, 'messages_count' => count($messages)]);
+        
+        // 根据模型类型选择调用方法
+        if (in_array($model, ['deepseek-reasoner', 'deepseek-chat'])) {
+            // DeepSeek 官方模型使用 DeepSeek API
+            return $this->deepSeekStreamResponse($post_data);
+        } else if (array_key_exists($model, $this->valid_text_models)) {
+            // 火山引擎支持的模型使用火山引擎 API
+            // 将模型名称转换为 endpoint_id
+            $post_data['model'] = $this->valid_text_models[$model];
+            return $this->volcEngineChatCompletion($post_data);
+        } else {
+            Utils::throwError('20003:不支持的模型: ' . $model);
+        }
+    }
+    
+    /**
+     * 处理图片转换为base64
+     * 
+     * @param mixed $image 图片文件对象或文件路径
+     * @return string|null base64编码的图片数据
+     */
+    private function processImageToBase64($image) {
+        try {
+            // 获取文件路径
+            if (is_string($image)) {
+                $filePath = $image;
+            } else {
+                // Laravel UploadedFile 对象
+                $filePath = $image->getRealPath();
+            }
+            
+            if (!file_exists($filePath)) {
+                dLog('deepseek')->warning('图片文件不存在', ['path' => $filePath]);
+                return null;
+            }
+            
+            // 验证是否为图片文件
+            $imageInfo = @getimagesize($filePath);
+            if ($imageInfo === false) {
+                dLog('deepseek')->warning('无效的图片文件', ['path' => $filePath]);
+                return null;
+            }
+            
+            // 读取图片并转换为base64
+            $imageData = file_get_contents($filePath);
+            $base64Image = base64_encode($imageData);
+            
+            dLog('deepseek')->info('图片转换成功', [
+                'size' => strlen($imageData),
+                'type' => $imageInfo['mime']
+            ]);
+            
+            return $base64Image;
+            
+        } catch (\Exception $e) {
+            dLog('deepseek')->error('图片处理失败: ' . $e->getMessage());
+            return null;
+        }
+    }
+
     private function getArtStylePromptMappings(): array
     {
         return [
@@ -4256,7 +4437,7 @@ class DeepSeekService
             'messages' => $params['messages'] ?? [],
         ];
 
-        if (!in_array($requestData['model'], $this->valid_text_models)) {
+        if (!array_key_exists($requestData['model'], $this->valid_text_models)) {
             Utils::throwError('20003:该模型不支持!');
         }
 
@@ -4310,7 +4491,7 @@ class DeepSeekService
         }
 
         if (isset($params['thinking'])) {
-            $requestData['thinking'] = $params['thinking'];
+            $requestData['thinking'] = ['type'=>$params['thinking']];
         }
 
         if (isset($params['reasoning_effort'])) {

+ 2 - 2
routes/api.php

@@ -164,8 +164,8 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
         // 完整视频合成任务
         Route::post('createCompleteVideoTask', [AnimeController::class, 'createCompleteVideoTask']);
         
-        // 文生图(通用)
-        Route::get('generateImg', [AnimeController::class, 'generateImg']);
+        Route::get('generateImg', [AnimeController::class, 'generateImg']);     // 文生图(通用)
+        Route::post('generateText', [DeepSeekController::class, 'generateText']);    // 文生文(通用)
     });
     
 });