Browse Source

first commit

liuhairui 12 hours ago
parent
commit
34de13c337

+ 1 - 1
application/admin/view/index/login.html

@@ -102,7 +102,7 @@
         <div class="login-screen">
             <div class="well">
                 <div class="login-head">
-                    <h1>达成WMS管理系统</h1>
+<!--                    <h1>达成WMS管理系统</h1>-->
                     <img src="__CDN__/assets/img/login-head.png" style="width:50%; margin-top: 10px;"/>
                 </div>
                 <div class="login-form" style="margin: 20px; padding: 10px;">

+ 173 - 82
application/api/controller/WorkOrder.php

@@ -13,45 +13,195 @@ use think\Queue;
 use think\queue\job\Redis;
 use think\Request;
 
-class WorkOrder extends Api
-{
+class WorkOrder extends Api{
     protected $noNeedLogin = ['*'];
     protected $noNeedRight = ['*'];
-
-    public function indexx()
-    {
-        echo 123;
-    }
+    public function index(){echo '访问成功';}
 
     /**
-     * 出图接口
+     * AI队列入口处理  出图接口
      * 此方法处理图像转换为文本的请求,将图像信息存入队列以供后续处理。
      */
     public function imageToText()
     {
         $params = $this->request->param();
-            $service = new ImageService();
-            $service->handleImage($params);
-            $this->success('任务成功提交至队列');
+        $service = new ImageService();
+        $service->handleImage($params);
+        $this->success('任务成功提交至队列');
     }
 
+    /**
+     * 将远程图片保存到本地指定路径
+     * @param string $url 远程图片URL
+     * @return string|null 本地保存的相对路径,如果保存失败则返回null
+     */
+    private function saveImageFromUrl($url) {
+        if (!function_exists('curl_init')) {
+            return null;
+        }
+
+        // 设置保存路径:/uploads/operate/ai/dall-e/default/年月日/
+        $date = date('Ymd');
+        $basePath = '/uploads/operate/ai/dall-e/default/' . $date . '/';
+        $savePath = ROOT_PATH . 'public' . $basePath;
+
+        // 创建目录结构
+        if (!is_dir($savePath)) {
+            if (!mkdir($savePath, 0777, true)) {
+                return null;
+            }
+        }
+
+        // 生成唯一的文件名
+        $extension = pathinfo($url, PATHINFO_EXTENSION);
+        if (empty($extension)) {
+            $extension = 'png';
+        }
+        $filename = uniqid() . '.' . $extension;
+        $fullPath = $savePath . $filename;
+
+        // 使用curl下载图片
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36');
+
+        $imageData = curl_exec($ch);
+        curl_close($ch);
+
+        // 检查下载是否成功
+        if (empty($imageData)) {
+            return null;
+        }
+
+        // 保存图片到本地
+        if (file_put_contents($fullPath, $imageData)) {
+            // 返回相对路径
+            return $basePath . $filename;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 图片下载接口
+     * 该方法用于从远程URL下载图片并返回给客户端
+     */
+    public function download_image() {
+        $imageUrl = $this->request->post('image_url');
+        
+        if (!$imageUrl) {
+            return json(['code' => 1, 'message' => '图片URL不能为空']);
+        }
+        
+        // 使用curl获取图片数据
+        if (!function_exists('curl_init')) {
+            return json(['code' => 1, 'message' => '服务器不支持curl']);
+        }
+        
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $imageUrl);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+        
+        $imageData = curl_exec($ch);
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
+        curl_close($ch);
+        
+        if ($httpCode != 200 || empty($imageData)) {
+            return json(['code' => 1, 'message' => '图片下载失败']);
+        }
+        $extension = 'png';
+        if (strpos($contentType, 'image/jpeg') !== false) {
+            $extension = 'jpg';
+        } elseif (strpos($contentType, 'image/png') !== false) {
+            $extension = 'png';
+        } elseif (strpos($contentType, 'image/gif') !== false) {
+            $extension = 'gif';
+        }
+        
+        // 设置响应头
+        header('Content-Type: application/octet-stream');
+        header('Content-Disposition: attachment; filename="generated-image-' . date('YmdHis') . '.' . $extension . '"');
+        header('Content-Length: ' . strlen($imageData));
+        
+        // 输出图片数据
+        echo $imageData;
+        exit;
+    }
+    
     //扩写文本内容提示词
     public function GetTxtToTxt(){
-        // 确保所有必要的变量都已初始化
         $params = $this->request->param();
-        $prompt = $params['prompt'];
+        if($params['status_val'] == '文生图'){
+            $service = new ImageService();
+            $result = $service->handleTextToImg($params);
 
-        $ai = new AIGatewayService();
-        $gptRes = $ai->txtGptApi($prompt,'gemini-2.0-flash');
-        echo "<pre>";
-        print_r($gptRes);
-        echo "<pre>";
+            // 处理文生图结果
+            if ($result['success']) {
+                $remoteUrl = $result['url'];
+
+//                // 保存图片到本地指定路径
+//                $localFilePath = $this->saveImageFromUrl($remoteUrl);
+//
+//                // 保存图片信息到product表
+//                $productData = [
+//                    'chinese_description' => $params['prompt'],
+//                    'txt_image_url' => $localFilePath,
+//                    'create_time' => date('Y-m-d H:i:s')
+//                ];
+//                $productId = Db::name('product')->insertGetId($productData);
+
+                $this->success('图片生成成功', [
+//                    'local_url' => $localFilePath,
+//                    'product_id' => $productId,
+                    'url' => $remoteUrl
+                ]);
+            } else {
+                $this->error('文生图请求失败', ['message' => $result['message']]);
+            }
+
+        }elseif($params['status_val'] == '文生文'){
+            $fullPrompt = $params['prompt'];
+            // 优化提示词指令:放在所有内容之后,明确要求生成连续的一段话
+            $fullPrompt .= "
+            请根据上述内容生成一段完整的话术,要求:
+            1. 内容必须是连贯的一段话,不要使用列表、分隔线或其他结构化格式
+            2. 不要包含非文本元素的描述
+            3. 不要添加任何额外的引导语、解释或开场白
+            4. 语言流畅自然";
+
+            $textToTextParams = [
+                'prompt' => $fullPrompt,
+                'model' => $params['model'] ?? 'gemini-2.0-flash' // 默认使用文生文gtp-4模型
+            ];
+
+            $service = new ImageService();
+            $result = $service->handleTextToText($textToTextParams);
+
+            if ($result['success']) {
+                $this->success('文生文生成成功', [
+                    'content' => $result['data']
+                ]);
+            } else {
+                $this->error('文生文请求失败', $result['message']);
+            }
+
+        }else{
+            $this->error('请求失败');
+        }
     }
 
-    //获取URL地址与端口
+    //获取服务器URL IP地址:端口
     public function GetHttpUrl(){
         $data = Db::name('http_url')->find();
-        // 拼接完整的HTTP URL
         $fullUrl = "http://" . $data['baseUrl'] . ":" . $data['port'];
         $res = [
             'code' => 0,
@@ -67,6 +217,7 @@ class WorkOrder extends Api
     }
 
 
+    //获取视频列表
     public function Getvideolist(){
         if (!$this->request->isGet()) {
             $this->error('请求方式错误');
@@ -94,10 +245,8 @@ class WorkOrder extends Api
         return json($res);
     }
 
-
-        //video_691c078dbb648190a17625bbef815ce50cbc1621ce1702d7
-    public function video()
-    {
+    //文生视频
+    public function video(){
         $params = $this->request->param();
 
         $apiUrl = 'https://chatapi.onechats.ai/v1/videos';
@@ -941,64 +1090,6 @@ class WorkOrder extends Api
     }
 
 
-    /**
-     * 获取 SD 模型列表
-     * 接口地址: /sdapi/v1/sd-models
-     */
-    public function sd_models() {
-        // $url = "http://20.0.17.188:45001/sdapi/v1/sd-models";
-
-        // // 初始化 cURL
-        // $ch = curl_init();
-
-        // // 设置请求参数
-        // curl_setopt($ch, CURLOPT_URL, $url);
-        // curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-        // curl_setopt($ch, CURLOPT_TIMEOUT, 10);
-        // curl_setopt($ch, CURLOPT_HTTPHEADER, [
-        //     'Content-Type: application/json',
-        //     'Accept: application/json',
-        // ]);
-
-        // // 发送请求
-        // $response = curl_exec($ch);
-
-        // // 错误处理
-        // if (curl_errno($ch)) {
-        //     curl_close($ch);
-        //     return json([
-        //         'code' => 1,
-        //         'msg'  => '请求失败: ' . curl_error($ch),
-        //         'data' => [],
-        //         'count' => 0
-        //     ]);
-        // }
-
-        // curl_close($ch);
-
-        // // 解析 JSON 响应
-        // $result = json_decode($response, true);
-
-        // // 判断返回数据是否有效
-        // if (!is_array($result)) {
-        //     return json([
-        //         'code' => 1,
-        //         'msg'  => '数据解析失败',
-        //         'data' => [],
-        //         'count' => 0
-        //     ]);
-        // }
-
-        // 正常返回
-        return json([
-            'code' => 0,
-            'msg'  => '查询成功',
-            'data' => '',
-            'count' => 2,
-        ]);
-    }
-
-
     /**
      * 查询队列列表
      * 统计文件对应的队列情况

+ 20 - 39
application/index/controller/Index.php

@@ -15,45 +15,26 @@ class Index extends Frontend
 
     public function index()
     {
-        $this->redirect("/ReZpnAIXhw.php");   //关键代码->重定向
-
-
-
-        $data =  [
-            'sczl_gdbh' =>  '2310201',
-            'sczl_yjno' => '1',
-            'sczl_gxh' => '3',
-            'sczl_type' =>  '03-喷码                     ',
-            'sczl_rq' =>  '2024-01-03 00:00:00',
-            'sczl_jtbh' => 'PM02#',
-            '班组车头产量' => '1500.0',
-            '工价系数' =>  '0.0000',
-            '工序难度系数' => '1.100',
-            '装版工时' => '0.00',
-            '保养工时' => '0.00',
-            '打样工时' =>  '0.00',
-            '异常停机工时' => '0.00',
-            '车头产量占用机时' => '0.00',
-            '日定额' => '32000.00',
-            '千件工价' =>  '3.30',
-            '补产标准' =>  '5400',
-            '班组换算产量' => 0,
-            '计时补差额工资' =>  '0.00',
-            'bh' =>  'ZM00585',
-            'xm' =>  '徐钱鹏    ',
-            'Rate' => '0.68000',
-            'sczl_ms' => '0.00',
-            '工时占比' => '0.0516',
-            '达标定额' => '363.00',
-            '个人计件工资' => '0.81',
-            '个人加班工资' =>  '4.33',
-            'UniqID' => 253318494,
-            'sys_ny' =>  '202401',
-            'sys_rq' => '2024-03-20 13:44:50',
-            'sys_id' =>  '[0001/测试]',
-            '法定天数' =>  '22'];
-        $job = new InsertDataJob($data); // 创建任务实例
-        Queue::push($job); // 推送任务到队列
+//        $this->redirect("/ReZpnAIXhw.php");   //关键代码->重定向
+        // 查询product表数据
+        $products = Db::name('product')->where('product_category','模版')->select();
+        //baseUrl ip服务器地址 port端口号
+        $http_url = Db::name('http_url')->field('baseUrl,port')->find();
+
+        // 拼接完整的图片URL
+        if ($products && $http_url) {
+            $base_url = 'http://' . $http_url['baseUrl'] . ':' . $http_url['port'];
+            foreach ($products as &$product) {
+                if (isset($product['txt_image_url'])) {
+                    $product['full_image_url'] = $base_url . $product['txt_image_url'];
+                }
+            }
+        }
+
+        // 将数据传递给前端视图
+        $this->assign('products', $products);
+        $this->assign('http_url', $http_url);
+        return $this->view->fetch();
     }
 
 }

+ 1310 - 16
application/index/view/index/index.html

@@ -1,34 +1,1328 @@
 <!DOCTYPE html>
-<html>
-
+<html lang="zh-CN">
 <head>
-
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-
-    <title>{$site.name|htmlentities}</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
+    <title>AI 图片生成</title>
+    <!-- 引入Bootstrap CSS -->
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
+    <!-- 引入Font Awesome图标 -->
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
+    <!-- 引入自定义CSS -->
     <link rel="shortcut icon" href="__CDN__/assets/img/favicon.ico"/>
     <link href="__CDN__/assets/css/index.css" rel="stylesheet">
+    <style>
+        /* 自定义样式 */
+        body {
+            font-family: 'Microsoft YaHei', sans-serif;
+            background-color: #f7f8fa;
+            margin: 0;
+            padding: 0;
+            overflow: hidden;
+            height: 100vh;
+            display: flex;
+            flex-direction: column;
+        }
+
+        /* 主内容区 */
+        .main-content {
+            display: flex;
+            flex: 1;
+            overflow: hidden;
+        }
+        
+        /* 左侧区域 */
+        .left-section {
+            flex: 2;
+            display: flex;
+            flex-direction: column;
+            overflow: hidden;
+            border-right: 1px solid #e5e7eb;
+        }
+        
+        /* 图片生成结果区域 */
+        .chat-container {
+            flex: 1;
+            overflow-y: auto;
+            padding: 20px;
+        }
+        
+        /* 右侧模板区域 */
+        .right-section {
+            flex: 1;
+            overflow-y: auto;
+            padding: 0;
+            scrollbar-width: thin;
+            scrollbar-color: #b0b8c1 transparent;
+        }
+        
+        .right-section::-webkit-scrollbar {
+            width: 4px;
+        }
+        
+        .right-section::-webkit-scrollbar-track {
+            background: transparent;
+        }
+        
+        .right-section::-webkit-scrollbar-thumb {
+            background: #b0b8c1;
+            border-radius: 2px;
+            transition: background-color 0.3s ease;
+        }
+        
+        .right-section::-webkit-scrollbar-thumb:hover {
+            background: #8b949e;
+        }
+        
+        /* 底部输入区域 */
+        .input-container {
+            border-top: 1px solid #e5e7eb;
+            padding: 15px 20px;
+            background-color: #ffffff;
+        }
+        
+        /* 提示词卡片 */
+        .prompt-card {
+            max-width: 100%;
+            margin: 0;
+        }
+        
+        /* 已移除右侧模板区域,改用顶部模板区域 */
+        
+        /* 图片放大模态框 */
+        .modal {
+            display: none;
+            position: fixed;
+            z-index: 1000;
+            left: 0;
+            top: 0;
+            width: 100%;
+            height: 100%;
+            background-color: rgba(0, 0, 0, 0.95);
+            overflow: hidden;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            opacity: 0;
+            transition: opacity 0.3s ease;
+        }
+        
+        .modal.show {
+            opacity: 1;
+        }
+        
+        .modal-content {
+            margin: 0;
+            display: block;
+            max-width: 95%;
+            max-height: 95vh;
+            width: auto;
+            height: auto;
+            object-fit: contain;
+            box-shadow: 0 5px 25px rgba(0, 0, 0, 0.5);
+            transition: transform 0.3s ease;
+        }
+        
+        .close {
+            position: absolute;
+            top: 20px;
+            right: 35px;
+            color: #ffffff;
+            font-size: 40px;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            text-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
+        }
+        
+        .close:hover,
+        .close:focus {
+            color: #ccc;
+            transform: scale(1.1);
+            text-decoration: none;
+            cursor: pointer;
+        }
+        
+        .close:hover,
+        .close:focus {
+            color: #bbb;
+            text-decoration: none;
+            cursor: pointer;
+        }
+
+        /* 滚动条样式 */
+        .chat-container::-webkit-scrollbar {
+            width: 4px;
+        }
+
+        .chat-container::-webkit-scrollbar-track {
+            background: transparent;
+        }
+
+        .chat-container::-webkit-scrollbar-thumb {
+            background: #b0b8c1;
+            border-radius: 2px;
+            transition: background-color 0.3s ease;
+        }
+
+        .chat-container::-webkit-scrollbar-thumb:hover {
+            background: #8b949e;
+        }
+
+        /* 顶部栏 */
+        .top-bar {
+            background-color: #ffffff;
+            padding: 12px 20px;
+            border-bottom: 1px solid #e5e7eb;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+        }
+
+        .back-btn {
+            background: none;
+            border: none;
+            font-size: 20px;
+            color: #374151;
+            cursor: pointer;
+            padding: 4px;
+            border-radius: 4px;
+            transition: background-color 0.2s;
+        }
+
+        .back-btn:hover {
+            background-color: #f3f4f6;
+        }
+
+        .page-title {
+            font-size: 18px;
+            font-weight: 600;
+            color: #111827;
+            margin: 0;
+        }
+
+        /* 图片生成结果区域样式 */
+        .image-result-container {
+            margin: 0;
+            padding: 20px;
+            background-color: #ffffff;
+            border-radius: 12px;
+            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
+            min-height: 600px;
+            height: 600px;
+            width: 100%;
+            max-width: 100%;
+            overflow: hidden;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
+
+        .image-result {
+            text-align: center;
+            height: 100%;
+            width: 100%;
+            overflow: hidden;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
+
+        .image-container {
+            position: relative;
+            display: inline-block;
+            height: calc(100% - 40px);
+            width: auto;
+            overflow: hidden;
+            border: 1px solid #e0e0e0;
+            border-radius: 8px;
+            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+        }
+
+        .generated-image {
+            max-width: 100%;
+            max-height: 100%;
+            height: auto;
+            width: auto;
+            cursor: pointer;
+            display: block;
+        }
+
+        .download-box {
+            margin-top: 10px;
+            text-align: center;
+        }
+
+        .download-btn {
+            background-color: #dc3545;
+            color: white;
+            border: none;
+            padding: 8px 16px;
+            border-radius: 4px;
+            cursor: pointer;
+            font-size: 14px;
+            display: flex;
+            align-items: center;
+            gap: 5px;
+            transition: background-color 0.3s;
+        }
+
+        .download-btn:hover {
+            background-color: #c82333;
+        }
+        }
+
+        /* 确保加载状态下也保持固定大小 */
+        .image-loading {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            height: 100%;
+            width: 100%;
+        }
+
+        /* 确保错误状态下也保持固定大小 */
+        .image-error {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            height: 100%;
+            width: 100%;
+        }
+
+        /* 默认占位符样式 */
+        .image-placeholder {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            height: 100%;
+            width: 100%;
+            border: 1px solid #e0e0e0;
+            border-radius: 8px;
+            background-color: #ffffff;
+            color: #6c757d;
+            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+        }
+
+        .image-placeholder i {
+            font-size: 48px;
+            margin-bottom: 16px;
+            color: #adb5bd;
+        }
+
+        .image-placeholder p {
+            font-size: 16px;
+            margin: 0;
+            color: #868e96;
+        }
+
+        .image-actions {
+            position: absolute;
+            top: 10px;
+            right: 10px;
+            margin-top: 0;
+            display: flex;
+            justify-content: flex-end;
+            gap: 10px;
+            z-index: 10;
+        }
+
+        .image-loading {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            padding: 60px 20px;
+        }
+
+        .image-loading i {
+            font-size: 48px;
+            color: #007bff;
+            margin-bottom: 20px;
+            animation: spin 1s linear infinite;
+        }
+
+        @keyframes spin {
+            from { transform: rotate(0deg); }
+            to { transform: rotate(360deg); }
+        }
+
+        .image-error {
+            padding: 40px 20px;
+            text-align: center;
+            color: #dc3545;
+        }
+
+        .download-btn, .regenerate-btn {
+            padding: 10px 20px;
+            border: none;
+            border-radius: 20px;
+            font-size: 14px;
+            font-weight: 500;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            display: flex;
+            align-items: center;
+            gap: 5px;
+        }
+
+        .download-btn {
+            background-color: #28a745;
+            color: white;
+        }
+
+        .download-btn:hover {
+            background-color: #218838;
+        }
+
+        .regenerate-btn {
+            background-color: #007bff;
+            color: white;
+        }
+
+        .regenerate-btn:hover {
+            background-color: #0056b3;
+        }
+
+        /* 底部输入区域 */
+        .input-container {
+            background-color: #ffffff;
+            padding: 12px 20px;
+            border-top: 1px solid #e5e7eb;
+            box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
+        }
+
+        .prompt-card {
+            max-width: 800px;
+            margin: 0 auto;
+            border: 1px solid #e5e7eb;
+            border-radius: 5px;
+            text-align: left;
+            background-color: #ffffff;
+            transition: all 0.3s ease;
+        }
+        
+        /* 提示词卡片底部样式 */
+        .prompt-footer {
+            display: flex;
+            justify-content: flex-end;
+            align-items: center;
+            margin-top: 12px;
+            gap: 16px;
+        }
+        
+        /* 尺寸选择器样式 */
+        .size-selector {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            flex: 1;
+        }
+        
+        .size-selector label {
+            font-size: 14px;
+            color: #4b5563;
+            font-weight: 600;
+        }
+        
+        .size-select {
+            padding: 8px 16px;
+            border: 1px solid #d1d5db;
+            border-radius: 8px;
+            font-size: 14px;
+            background-color: #ffffff;
+            color: #374151;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            min-width: 120px;
+        }
+        
+        .size-select:hover {
+            border-color: #9ca3af;
+        }
+        
+        .size-select:focus {
+            outline: none;
+            border-color: #007bff;
+            box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
+        }
+
+        .prompt-card:focus-within {
+            border-color: #007bff;
+            box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
+        }
+
+        .prompt-textarea {
+            width: 100%;
+            min-height: 60px;
+            border: none;
+            border-radius: 8px;
+            font-size: 16px;
+            line-height: 1.5;
+            color: #374151;
+            resize: vertical;
+            outline: none;
+            font-family: 'Microsoft YaHei', sans-serif;
+            background-color: #ffffff;
+            overflow-y: auto;
+            scrollbar-width: thin;
+        }
+
+        .prompt-textarea::-webkit-scrollbar {
+            width: 4px;
+        }
+
+        .prompt-textarea::-webkit-scrollbar-track {
+            background: transparent;
+        }
+
+        .prompt-textarea::-webkit-scrollbar-thumb {
+            background-color: #b0b8c1;
+            border-radius: 2px;
+            transition: background-color 0.3s ease;
+        }
+
+        .prompt-textarea::-webkit-scrollbar-thumb:hover {
+            background-color: #8b949e;
+        }
+
+        .char-count {
+            text-align: right;
+            font-size: 12px;
+            color: #6b7280;
+            margin-top: 4px;
+        }
+
+        .current-count {
+            font-weight: 500;
+        }
+
+        .max-count {
+            color: #9ca3af;
+        }
+
+        .prompt-actions {
+            display: flex;
+            justify-content: flex-end;
+            gap: 10px;
+            margin-top: 0;
+        }
+
+        .clear-btn {
+            background-color: #ffffff;
+            color: #dc3545;
+            border: 1px solid #f87171;
+            border-radius: 8px;
+            font-size: 14px;
+            font-weight: 500;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            display: flex;
+            align-items: center;
+            gap: 6px;
+        }
+
+        .clear-btn:hover {
+            background-color: #fee2e2;
+            border-color: #ef4444;
+            transform: translateY(-1px);
+        }
+
+        .optimize-btn {
+            background-color: #ffffff;
+            color: #4b5563;
+            padding: 10px 18px;
+            border: 1px solid #d1d5db;
+            border-radius: 8px;
+            font-size: 14px;
+            font-weight: 500;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            display: flex;
+            align-items: center;
+            gap: 6px;
+        }
+
+        .optimize-btn:hover {
+            background-color: #f9fafb;
+            border-color: #9ca3af;
+            transform: translateY(-1px);
+        }
+
+        .arrow-btn {
+            background-color: #007bff;
+            color: white;
+            padding: 10px 14px;
+            border: none;
+            border-radius: 8px;
+            font-size: 16px;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            min-width: 40px;
+        }
+
+        .arrow-btn:hover {
+            background-color: #0056b3;
+            transform: translateY(-1px);
+            box-shadow: 0 4px 8px rgba(0, 123, 255, 0.25);
+        }
+
+        /* 分类标签 */
+        .category-tab {
+            display: inline-block;
+            padding: 6px 16px;
+            margin: 0 3px;
+            background-color: #f3f4f6;
+            border-radius: 20px;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            font-size: 13px;
+            color: #4b5563;
+            font-weight: 500;
+        }
+
+        .category-tab.active {
+            background-color: #007bff;
+            color: #ffffff;
+        }
+        
+        /* 分类标签滚动条 */
+        .category-tabs::-webkit-scrollbar {
+            height: 2px;
+        }
+        
+        .category-tabs::-webkit-scrollbar-track {
+            background: transparent;
+            border-radius: 1px;
+        }
+        
+        .category-tabs::-webkit-scrollbar-thumb {
+            background: #b0b8c1;
+            border-radius: 1px;
+            transition: background-color 0.3s ease;
+        }
+        
+        .category-tabs::-webkit-scrollbar-thumb:hover {
+            background: #8b949e;
+        }
+
+        /* 模板卡片样式 */
+        /* 顶部模板区域样式 */
+        .top-templates {
+            background-color: #ffffff;
+            padding: 20px;
+            margin-bottom: 0;
+            border-radius: 0;
+            box-shadow: none;
+        }
+        
+        .top-templates .category-tabs {
+            background-color: #ffffff;
+            padding: 10px 0;
+            margin-bottom: 15px;
+            border-bottom: 1px solid #e9ecef;
+        }
+        
+        .top-templates .category-tab {
+            background-color: #f3f4f6;
+            color: #4b5563;
+            padding: 8px 16px;
+            margin: 0 3px;
+            border-radius: 20px;
+            font-size: 14px;
+            font-weight: 500;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+        
+        .top-templates .category-tab.active {
+            background-color: #007bff;
+            color: white;
+        }
+        
+        .top-templates .category-tab:hover {
+            background-color: #e5e7eb;
+        }
+        
+        .top-templates .category-tab.active:hover {
+            background-color: #0069d9;
+        }
+        
+        .top-templates .template-grid {
+            display: grid;
+            grid-template-columns: repeat(5, 1fr);
+            gap: 15px;
+            padding: 10px 0;
+        }
+
+        .top-templates .template-card {
+            background-color: #ffffff;
+            border-radius: 8px;
+            overflow: hidden;
+            transition: all 0.3s ease;
+            cursor: pointer;
+            box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
+        }
+
+        .top-templates .template-card:hover {
+            transform: translateY(-3px);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+        }
+
+        .top-templates .template-image {
+            width: 100%;
+            height: 180px;
+            object-fit: cover;
+            transition: transform 0.3s ease;
+        }
+        
+        /* 已移除右侧模板区域相关样式 */
+
+        .template-title {
+            padding: 10px;
+            font-size: 14px;
+            color: #333;
+            text-align: center;
+            background-color: #fafafa;
+        }
+
+        /* 图片容器样式 */
+        .template-image-container {
+            position: relative;
+            overflow: hidden;
+        }
+
+        /* 图片覆盖层样式 */
+        .template-overlay {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            background-color: rgba(0, 0, 0, 0.5);
+            opacity: 0;
+            transition: opacity 0.3s ease;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+
+        /* 鼠标悬停或触屏时显示覆盖层 */
+        .template-card:hover .template-overlay {
+            opacity: 1;
+        }
+
+        /* 使用模版按钮样式 */
+        .use-template-btn {
+            background-color: #007bff;
+            color: white;
+            border: none;
+            padding: 8px 16px;
+            border-radius: 4px;
+            font-size: 14px;
+            cursor: pointer;
+            transition: background-color 0.3s ease;
+        }
+
+        .use-template-btn:hover {
+            background-color: #0056b3;
+        }
+
+        /* 底部区域样式 */
+        .bottom-section {
+            display: flex;
+            gap: 20px;
+            padding: 20px;
+            max-width: 1200px;
+            margin: 0 auto;
+        }
+        
+        .bottom-left {
+            flex: 2;
+        }
+        
+        .bottom-right {
+            flex: 1;
+        }
+        
+        /* 底部模板区域样式 */
+        .bottom-right .category-tabs {
+            background-color: #ffffff;
+            padding: 15px 20px;
+            margin-bottom: 0;
+            border-bottom: 1px solid #e9ecef;
+        }
+        
+        .bottom-right .category-tab {
+            background-color: #f3f4f6;
+            color: #4b5563;
+            padding: 8px 16px;
+            margin: 0 3px;
+            border-radius: 20px;
+            font-size: 13px;
+            font-weight: 500;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+        
+        .bottom-right .category-tab.active {
+            background-color: #007bff;
+            color: white;
+        }
+        
+        .bottom-right .category-tab:hover {
+            background-color: #e5e7eb;
+        }
+        
+        .bottom-right .category-tab.active:hover {
+            background-color: #0069d9;
+        }
+        
+        .bottom-right .template-grid {
+            grid-template-columns: repeat(3, 1fr);
+            gap: 10px;
+            padding: 10px 0;
+        }
+        
+        .bottom-right .template-card {
+            border-radius: 8px;
+            overflow: hidden;
+            box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+            transition: transform 0.3s ease, box-shadow 0.3s ease;
+        }
+        
+        .bottom-right .template-image {
+            width: 100%;
+            height: 80px;
+            object-fit: cover;
+            transition: transform 0.3s ease;
+        }
+        
+        .bottom-right .template-card:hover {
+            transform: translateY(-2px);
+            box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+        }
+        
+        .bottom-right .template-card:hover .template-image {
+            transform: scale(1.05);
+        }
+        
+        .footer {
+            background-color: #ffffff;
+            padding: 20px 0;
+            border-top: 1px solid #e9ecef;
+            margin-top: 40px;
+        }
+
+        /* 响应式调整 */
+        @media (max-width: 768px) {
+            /* 主内容区域改为上下布局 */
+            .main-content {
+                flex-direction: column;
+            }
+            
+            /* 左右区域调整 */
+            .left-section {
+                flex: 1;
+                border-right: none;
+                border-bottom: 1px solid #e5e7eb;
+            }
+            
+            .right-section {
+                flex: none;
+                max-height: 300px;
+            }
+            
+            /* 搜索容器 */
+            .search-container {
+                padding: 20px 0 15px;
+            }
+
+            .search-input {
+                font-size: 14px;
+                padding: 12px 15px 12px 70px;
+            }
+
+            .search-tag {
+                padding: 6px 12px;
+                font-size: 13px;
+            }
+
+            /* 分类标签 */
+            .category-tab {
+                padding: 6px 15px;
+                font-size: 13px;
+            }
+
+            /* 模板网格 */
+            .template-grid {
+                grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+                gap: 10px;
+            }
+
+            .template-image {
+                height: 150px;
+            }
+            
+            /* 底部输入区域 */
+            .input-container {
+                padding: 10px;
+            }
+            
+            .prompt-card {
+                padding: 12px;
+            }
+            
+            /* 操作按钮 */
+            .prompt-actions {
+                gap: 8px;
+            }
+            
+            .clear-btn, .optimize-btn {
+                padding: 8px 12px;
+                font-size: 13px;
+            }
+            
+            .arrow-btn {
+                padding: 8px 12px;
+                font-size: 14px;
+            }
+        }
+
+        @media (max-width: 576px) {
+            /* 模板网格 */
+            .template-grid {
+                grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
+                gap: 8px;
+            }
+
+            .template-image {
+                height: 130px;
+            }
+
+            .template-title {
+                font-size: 12px;
+                padding: 6px;
+            }
+            
+            /* 底部输入区域 */
+            .prompt-footer {
+                justify-content: flex-end;
+            }
+            
+            .prompt-actions {
+                justify-content: stretch;
+            }
+            
+            .clear-btn, .optimize-btn, .arrow-btn {
+                flex: 1;
+            }
+            
+            .size-selector {
+                justify-content: center;
+            }
+        }
+    </style>
 </head>
 
 <body>
+    <!-- 顶部栏 -->
+    <div class="top-bar">
+        <button class="back-btn" onclick="window.history.back()">
+            <i class="fas fa-arrow-left"></i>
+        </button>
+        <h1 class="page-title">AI 图片生成</h1>
+        <div></div> <!-- 占位符,使标题居中 -->
+    </div>
 
-<div id="mainbody">
-    <div class="container">
-        <div class="text-center">
-            <h1>{$site.name|htmlentities}</h1>
-            <a href="{:url('index/user/index', '', false, true)}">{:__('Member center')}</a>
+    <!-- 主要内容区域 -->
+    <div class="main-content">
+        <!-- 左侧区域 -->
+        <div class="left-section">
+            <!-- 图片生成结果区域 -->
+            <div class="chat-container">
+                <div class="image-result-container">
+                    <div class="image-result" id="imageResult">
+                        <!-- 默认占位符 -->
+                        <div class="image-placeholder">
+                        </div>
+                    </div>
+                </div>
+            </div>
+            
+            <!-- 底部输入区域 -->
+            <div class="input-container">
+                <div class="prompt-card">
+                    <textarea class="prompt-textarea" rows="3" placeholder="描述你想要生成的图片内容" oninput="updateCharCount()"></textarea>
+                    <div class="prompt-footer">
+                        <div class="char-count">
+                            <span class="current-count">0</span>/<span class="max-count">500</span>
+                        </div>
+                    </div>
+                    <div class="prompt-actions">
+                        <div class="size-selector">
+                            <label for="imageSize">尺寸:</label>
+                            <select id="imageSize" class="size-select">
+                                <option value="1024x1024">1024×1024 (正方形)</option>
+                                <option value="1792x1024">1792×1024 (宽屏)</option>
+                                <option value="1024x1792">1024×1792 (竖屏)</option>
+                            </select>
+                        </div>
+                        <button class="clear-btn" onclick="clearPrompt()">
+                            <i class="fas fa-trash"></i>
+                            清空
+                        </button>
+                        <button class="optimize-btn" onclick="optimizePrompt()">
+                            <i class="fas fa-magic"></i>
+                            一键优化
+                        </button>
+                        <button class="arrow-btn" onclick="generateImage()">
+                            <i class="fas fa-arrow-up"></i>
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+        
+        <!-- 右侧模板区域 -->
+        <div class="right-section">
+            <!-- 模板区域 -->
+            <div class="top-templates">
+                <!-- 分类标签 -->
+                <div class="category-tabs">
+                    <div class="container">
+                        <div class="category-tab active" data-category="all">推荐模板</div>
+                    </div>
+                </div>
+                
+                <!-- 模板展示区域 -->
+                <div class="container template-container">
+                    <div class="template-grid">
+                        <!-- 动态生成产品卡片 -->
+                        {volist name="products" id="product"}
+                            <div class="template-card">
+                                <div class="template-image-container">
+                                    <img src="{$product.full_image_url}" class="template-image">
+                                    <div class="template-overlay">
+                                        <button class="use-template-btn" onclick="useTemplate('{$product.chinese_description|addslashes}')">使用模版</button>
+                                    </div>
+                                </div>
+                            </div>
+                        {/volist}
+                    </div>
+                </div>
+            </div>
         </div>
     </div>
-</div>
 
-<div class="footer">
-    <div class="container">
-        <p>Copyright @ {$site.name|htmlentities} {:date('Y',time())} 版权所有 <a href="https://beian.miit.gov.cn" target="_blank">{$site.beian|htmlentities}</a></p>
+    <!-- 图片放大查看模态框 -->
+    <div id="imageModal" class="modal">
+        <span class="close">&times;</span>
+        <img class="modal-content" id="modalImage">
     </div>
-</div>
 
+    <!-- 引入jQuery库 -->
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
+    <!-- 引入Bootstrap JS -->
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
+
+    <!-- 自定义JavaScript -->
+    <script>
+        // 使用模板功能
+        function useTemplate(description) {
+            // 将模板描述赋值到提示词文本框
+            const textarea = document.querySelector('.prompt-textarea');
+            if (textarea) {
+                textarea.value = description;
+                // 更新字符计数
+                updateCharCount();
+                // 可选:滚动到顶部的提示词文本框
+                textarea.scrollIntoView({ behavior: 'smooth' });
+            }
+        }
+
+        // 清空提示词功能
+        function clearPrompt() {
+            const textarea = document.querySelector('.prompt-textarea');
+            if (textarea) {
+                textarea.value = '';
+                textarea.focus();
+            }
+        }
+
+        // 一键优化提示词功能
+        function optimizePrompt() {
+            const textarea = document.querySelector('.prompt-textarea');
+            const optimizeBtn = document.querySelector('.optimize-btn');
+
+            if (textarea && textarea.value.trim() !== '') {
+                // 显示加载状态
+                const originalBtnText = optimizeBtn.innerHTML;
+                optimizeBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 优化中...';
+                optimizeBtn.disabled = true;
+
+                // 构造请求参数
+                const params = {
+                    status_val: '文生文',
+                    prompt: textarea.value,
+                    model: 'gemini-2.0-flash'
+                };
+
+                // 发送AJAX请求
+                $.ajax({
+                    url: '/index.php/api/work_order/GetTxtToTxt',
+                    type: 'POST',
+                    data: params,
+                    dataType: 'json',
+                    success: function(data) {
+                        // 恢复按钮状态
+                        optimizeBtn.innerHTML = originalBtnText;
+                        optimizeBtn.disabled = false;
+
+                        // 优化成功,更新文本框内容
+                        textarea.value = data.data.content;
+                        textarea.scrollTop = textarea.scrollHeight;
+                        updateCharCount();
+
+                        // 显示优化成功提示
+                        const alert = document.createElement('div');
+                        alert.className = 'alert alert-success';
+                        alert.innerHTML = '提示词已优化!';
+                        alert.style.position = 'fixed';
+                        alert.style.top = '20px';
+                        alert.style.right = '20px';
+                        alert.style.zIndex = '9999';
+                        document.body.appendChild(alert);
+
+                        // 3秒后移除提示
+                        setTimeout(() => {
+                            alert.remove();
+                        }, 3000);
+
+                    }
+                });
+            } else {
+                alert('请先输入提示词!');
+            }
+        }
+
+        // 滚动到顶部功能
+        function scrollToTop() {
+            window.scrollTo({ top: 0, behavior: 'smooth' });
+        }
+
+        // 字符计数功能
+        function updateCharCount() {
+            const textarea = document.querySelector('.prompt-textarea');
+            const charCountElement = document.querySelector('.current-count');
+            if (textarea && charCountElement) {
+                const count = textarea.value.length;
+                charCountElement.textContent = count;
+
+                // 根据字符数改变颜色
+                if (count > 450) {
+                    charCountElement.style.color = '#dc3545';
+                } else if (count > 400) {
+                    charCountElement.style.color = '#ffc107';
+                } else {
+                    charCountElement.style.color = '';
+                }
+            }
+        }
+
+        // 初始化字符计数
+        document.addEventListener('DOMContentLoaded', function() {
+            updateCharCount();
+        });
+
+        // 生成图片函数
+        function generateImage() {
+            const promptText = document.querySelector('.prompt-textarea').value.trim();
+            const imageSize = document.getElementById('imageSize').value;
+            const imageResultDiv = document.getElementById('imageResult');
+
+            // 检查是否有提示词
+            if (!promptText) {
+                alert('请先输入图片描述');
+                return;
+            }
+
+            // 显示加载状态
+            imageResultDiv.innerHTML = `
+                <div class="image-loading">
+                    <i class="fas fa-spinner"></i>
+                    <p>正在生成图片,请稍候...</p>
+                </div>
+            `;
+
+            // 发送请求到文生图接口
+            $.ajax({
+                url: '/index.php/api/work_order/GetTxtToTxt',
+                type: 'POST',
+                dataType: 'json',
+                data: {
+                    status_val: '文生图',
+                    prompt: promptText,
+                    model: 'dall-e-3',
+                    size: imageSize
+                },
+                success: function(response) {
+                    // 处理成功响应
+                    const imageData = response.data;
+                    if (imageData && imageData.url) {
+                        // 显示生成的图片
+                        imageResultDiv.innerHTML = `
+                            <div class="image-container">
+                                <img src="${imageData.url}" alt="生成的图片" class="generated-image" onclick="showImageModal('${imageData.url}')" oncontextmenu="downloadImage('${imageData.url}'); return false;" ontouchstart="touchStartHandler(event, '${imageData.url}')" ontouchend="touchEndHandler()">
+                            </div>
+                            <div class="download-box">
+                                <button class="download-btn" onclick="downloadImage('${imageData.url}')">
+                                    <i class="fas fa-download"></i> 下载图片
+                                </button>
+                            </div>
+                        `;
+                    } else {
+                        imageResultDiv.innerHTML = `
+                            <div class="image-error">
+                                <i class="fas fa-exclamation-triangle"></i>
+                                <p>图片生成失败:未返回有效图片数据</p>
+                            </div>
+                        `;
+                    }
+
+                }
+            });
+        }
+
+        // 触屏长按下载相关变量
+        let longPressTimer;
+        const LONG_PRESS_DELAY = 500; // 长按触发时间(毫秒)
+        
+        // 触屏开始事件处理
+        function touchStartHandler(event, imageUrl) {
+            longPressTimer = setTimeout(() => {
+                downloadImage(imageUrl);
+            }, LONG_PRESS_DELAY);
+        }
+        
+        // 触屏结束事件处理
+        function touchEndHandler() {
+            clearTimeout(longPressTimer);
+        }
+        
+        // 下载图片函数
+        function downloadImage(imageUrl) {
+            console.log('开始下载图片:', imageUrl);
+            
+            // 使用服务器端代理下载图片
+            const proxyUrl = '/index.php/api/work_order/download_image';
+            
+            // 创建一个表单来发送请求
+            const form = document.createElement('form');
+            form.method = 'POST';
+            form.action = proxyUrl;
+            form.target = '_blank';
+            
+            // 创建一个隐藏的输入字段来传递图片URL
+            const urlInput = document.createElement('input');
+            urlInput.type = 'hidden';
+            urlInput.name = 'image_url';
+            urlInput.value = imageUrl;
+            
+            // 将输入字段添加到表单中
+            form.appendChild(urlInput);
+            
+            // 将表单添加到页面并提交
+            document.body.appendChild(form);
+            form.submit();
+            
+            // 清理资源
+            setTimeout(() => {
+                document.body.removeChild(form);
+            }, 100);
+        }
+        
+        // 图片放大查看功能
+        const modal = document.getElementById('imageModal');
+        const modalImg = document.getElementById('modalImage');
+        const closeBtn = document.querySelector('.close');
+        
+        function showImageModal(imageUrl) {
+            modal.style.display = 'flex';
+            // 添加一个短暂的延迟,确保display属性生效后再添加show类
+            setTimeout(() => {
+                modal.classList.add('show');
+            }, 10);
+            modalImg.src = imageUrl;
+        }
+        
+        // 关闭模态框
+        function closeImageModal() {
+            modal.classList.remove('show');
+            // 等待过渡动画完成后再隐藏模态框
+            setTimeout(() => {
+                modal.style.display = 'none';
+            }, 300);
+        }
+        
+        // 点击关闭按钮关闭模态框
+        closeBtn.onclick = closeImageModal;
+        
+        // 点击模态框外部关闭模态框
+        window.onclick = function(event) {
+            if (event.target === modal) {
+                closeImageModal();
+            }
+        }
+        
+        // 按ESC键关闭模态框
+        document.addEventListener('keydown', function(event) {
+            if (event.key === 'Escape' && modal.style.display === 'block') {
+                closeImageModal();
+            }
+        });
+    
+
+        // 分类筛选功能
+        document.addEventListener('DOMContentLoaded', function() {
+            const categoryTabs = document.querySelectorAll('.category-tab');
+            const templateCards = document.querySelectorAll('.template-card');
+
+            categoryTabs.forEach(tab => {
+                tab.addEventListener('click', function() {
+                    // 移除所有active类
+                    categoryTabs.forEach(t => t.classList.remove('active'));
+                    // 添加当前tab的active类
+                    this.classList.add('active');
+
+                    const category = this.getAttribute('data-category');
+
+                    // 筛选模板(当前简单实现,可根据需要扩展)
+                    templateCards.forEach(card => {
+                        card.style.display = 'block';
+                    });
+                });
+            });
+
+            // 平滑滚动
+            document.querySelectorAll('a[href^="#"]').forEach(anchor => {
+                anchor.addEventListener('click', function (e) {
+                    e.preventDefault();
+                    document.querySelector(this.getAttribute('href')).scrollIntoView({
+                        behavior: 'smooth'
+                    });
+                });
+            });
+        });
+    </script>
 </body>
 
 </html>

+ 1 - 4
application/job/ImageArrJob.php

@@ -21,7 +21,6 @@ class ImageArrJob
      */
     public function fire(Job $job, $data)
     {
-
         $task_id = $data['task_id'];
         $images = $data['data'];
 
@@ -75,12 +74,10 @@ class ImageArrJob
             }
 
         }
-
         //更新任务状态为已启动
         Db::name('queue_logs')->where('id', $task_id)->update(['status' => '已启动队列']);
         echo date('Y-m-d H:i:s') . " 队列已启动\n";
-
-        //删除当前队列任务
+        //推送后删除当前队列任务
         $job->delete();
     }
 

+ 0 - 4
application/job/TextToImageJob.php

@@ -204,12 +204,8 @@ class TextToImageJob
 
         // AI 图像生成调用
         $ai = new AIGatewayService();
-
-
         $response = $ai->callDalleApi($template['content'].$prompt, $selectedOption);
 
-
-
         if (isset($response['error'])) {
             throw new \Exception("❌ 图像生成失败:" . $response['error']['message']);
         }

+ 18 - 9
application/job/TextToTextJob.php

@@ -46,7 +46,7 @@ class TextToTextJob
                     echo "处理时间:{$currentTime}\n";
                     echo "👉 正在处理第 " . ($index + 1) . " 条,ID: {$row['id']}\n";
 
-                    $result = $this->textToTxt($row['id'],$data["txttotxt_selectedOption"],$fullPath,$data['sourceDir']);
+                    $result = $this->textToTxt($data,$row['id'],$data["txttotxt_selectedOption"],$fullPath,$data['sourceDir']);
                     echo $result;
                     echo "✅ 处理结果:完成\n";
                     echo "完成时间:" . date('Y-m-d H:i:s') . "\n";
@@ -99,33 +99,41 @@ class TextToTextJob
                 ]);
             }
         }
-
         $job->delete();
     }
 
+
     /**
      * 文生文核心处理逻辑(调用 GPT 接口)
+     * @param array $data 任务数据
      * @param int $id text_to_image 表主键
+     * @param string $txttotxt_selectedOption 文生文模型
+     * @param string $fullPath 完整路径
+     * @param string $sourceDir 源目录
      * @return string
      */
-    public function textToTxt($id,$txttotxt_selectedOption,$fullPath,$sourceDir)
+    public function textToTxt($data, $id, $txttotxt_selectedOption, $fullPath, $sourceDir)
     {
         $template = Db::name('template')
             ->field('id,english_content,content')
-            ->where('path',$sourceDir)
-            ->where('ids',1)
+            ->where('path', $sourceDir)
+            ->where('ids', 1)
             ->find();
 
         $record = Db::name('text_to_image')
             ->field('id,english_description,chinese_description')
-            ->where('id',$id)
+            ->where('id', $id)
             ->order('id desc')
             ->find();
-        if (!$record) {return '没有找到匹配的图像记录';}
 
-        // 拼接提示词调用 文生文 接口
+        if (!$record) {
+            return '没有找到匹配的图像记录';
+        }
+
+        // 拼接提示词调用文生文接口
         $ai = new AIGatewayService();
-        $gptRes = $ai->txtGptApi($template['english_content'].$record['chinese_description'],$txttotxt_selectedOption);
+        $prompt = $template['english_content'] . $record['chinese_description'];
+        $gptRes = $ai->txtGptApi($prompt, $txttotxt_selectedOption);
         $gptText = trim($gptRes['choices'][0]['message']['content'] ?? '');
 
         // 更新数据库记录
@@ -133,6 +141,7 @@ class TextToTextJob
             'english_description' => $gptText,
             'status_name' => "文生文"
         ]);
+
         return 0;
     }
 

+ 25 - 142
application/service/AIGatewayService.php

@@ -48,11 +48,10 @@ class AIGatewayService{
      * @param string $imageUrl 图像 URL,支持公网可访问地址
      * @param string $prompt   对图像的提问内容或提示文本
      */
-    public function callGptApi($imageUrl, $prompt,$imgtotxt_selectedOption)
+    public function callGptApi($imageUrl,$prompt,$model)
     {
-        //方式一
         $data = [
-            "model" => $imgtotxt_selectedOption,
+            "model" => $model,
             "messages" => [[
                 "role" => "user",
                 "content" => [
@@ -80,7 +79,6 @@ class AIGatewayService{
 //            ]],
 //            "max_tokens" => 1000
 //        ];
-
         return $this->callApi($this->config['imgtotxt']['api_url'], $this->config['imgtotxt']['api_key'], $data);
     }
 
@@ -89,16 +87,12 @@ class AIGatewayService{
      * 文生文
      * @param string $prompt 用户输入的文本提示内容
      */
-    public function txtGptApi($prompt,$txttotxt_selectedOption)
+    public function txtGptApi($prompt,$model)
     {
-        if (empty($prompt)) {
-            throw new \Exception("Prompt 不允许为空");
-        }
-
         //判断使用模型
-        if ($txttotxt_selectedOption === 'gemini-2.0-flash') {
+        if ($model === 'gemini-2.0-flash') {
             $data = [
-                'model' => 'gemini-2.0-flash',
+                'model' => $model,
                 'messages' => [
                     ['role' => 'user', 'content' => $prompt]
                 ],
@@ -110,9 +104,9 @@ class AIGatewayService{
                 $this->config['txttotxtgemini']['api_key'],
                 $data
             );
-        }else if ($txttotxt_selectedOption === 'gpt-4') {
+        }else if ($model === 'gpt-4') {
             $data = [
-                'model' => 'gpt-4',
+                'model' => $model,
                 'messages' => [
                     ['role' => 'user', 'content' => $prompt]
                 ],
@@ -125,7 +119,6 @@ class AIGatewayService{
                 $data
             );
         }
-
     }
 
     /**
@@ -152,33 +145,35 @@ class AIGatewayService{
      *
      * @return array 返回接口响应,成功时包含 'data' 字段,失败时包含 'error' 信息
      */
-    public function callDalleApi($prompt, $selectedOption)
+    public function callDalleApi($prompt, $model,$size)
     {
-        if ($selectedOption === 'dall-e-3') {
+        // 尺寸优先级:传入有效值 > 默认值
+        $imageSize = !empty($size) ? $size : '1024x1024';
+        if ($model === 'dall-e-3') {
             $data = [
                 'prompt'  => $prompt,
-                'model'   => $selectedOption,
+                'model'   => $model,
                 'n'       => 1,
-                'size'    => '1024x1024',
+                'size'    => $imageSize,
                 'quality' => 'hd',
                 'style'   => 'vivid',
                 'response_format' => 'url',
             ];
             return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data);
-        } else if ($selectedOption === 'black-forest-labs/FLUX.1-kontext-pro') {
+        } else if ($model === 'black-forest-labs/FLUX.1-kontext-pro') {
             $data = [
                 'prompt'  => $prompt,
-                'model'   => $selectedOption,
+                'model'   => $model,
                 'n'       => 1,
-                'size'    => '1024x1024',
+                'size'    => $imageSize,
                 'quality' => 'hd',
                 'style'   => 'vivid',
                 'response_format' => 'url',
             ];
             return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data);
-        } else if ($selectedOption === 'MID_JOURNEY') {
+        } else if ($model === 'MID_JOURNEY') {
             $data = [
-                'botType' => $selectedOption,
+                'botType' => $model,
                 'prompt' => $prompt,
                 'base64Array' => [],
                 'accountFilter' => [
@@ -193,18 +188,18 @@ class AIGatewayService{
                 'state' => ""
             ];
             return $this->callApi($this->config['submitimage']['api_url'],$this->config['submitimage']['api_key'],$data);
-        }else if ($selectedOption === 'gpt-image-1') {
+        }else if ($model === 'gpt-image-1') {
             $data = [
                 'prompt'  => $prompt,
-                'model'   => $selectedOption,
+                'model'   => $model,
                 'n'       => 1,
-                'size'    => '1024x1024',
+                'size'    => $imageSize,
                 'quality' => 'hd',
                 'style'   => 'vivid',
                 'response_format' => 'url',
             ];
             return $this->callApi($this->config['txttoimg']['api_url'],$this->config['txttoimg']['api_key'],$data);
-        } else if ($selectedOption === 'gemini-3-pro-image-preview') {
+        } else if ($model === 'gemini-3-pro-image-preview') {
             // 使用Google Gemini模型的正确参数格式,与curl命令保持一致
             $data = [
                 "contents" => [
@@ -227,15 +222,13 @@ class AIGatewayService{
         }else{
             return [
                 'code' => 400,
-                'msg' => '未配置的文生图模型: ' . $selectedOption,
+                'msg' => '未配置的文生图模型: ' . $model,
                 'data' => null
             ];
         }
     }
 
 
-
-
     /**
      * 图生图
      * @param string $prompt 用户输入的文本提示内容
@@ -472,116 +465,6 @@ class AIGatewayService{
             ]
         ];
     }
-//    public function imgtogqGptApi($imageRelPath, $options = [])
-//    {
-//        // 构造图片路径
-//        $imgPath = ROOT_PATH . 'public/' . $imageRelPath;
-//
-//        if (!file_exists($imgPath)) {
-//            return ['code' => 1, 'msg' => '原图不存在:' . $imageRelPath];
-//        }
-//
-//        // 默认放大配置
-//        $defaultParams = [
-//            'resize_mode' => 0,
-//            'show_extras_results' => true,
-//            'gfpgan_visibility' => 0,
-//            'codeformer_visibility' => 0,
-//            'codeformer_weight' => 0,
-//            'upscaling_resize' => 2.45,
-//            'upscaling_crop' => true,
-//            'upscaler_1' => 'R-ESRGAN 4x+ Anime6B',
-//            'upscaler_2' => 'None',
-//            'extras_upscaler_2_visibility' => 0,
-//            'upscale_first' => false
-//        ];
-//
-//        // 合并配置参数
-//        $params = array_merge($defaultParams, $options);
-//
-//        // 编码原始图片
-//        try {
-//            $imgData = file_get_contents($imgPath);
-//            if ($imgData === false) {
-//                throw new Exception('无法读取图片文件');
-//            }
-//            $params['image'] = base64_encode($imgData);
-//        } catch (Exception $e) {
-//            return ['code' => 1, 'msg' => '图片读取失败:' . $e->getMessage()];
-//        }
-//
-//        $apiUrl = "http://20.0.17.188:45001/sdapi/v1/extra-single-image";
-//        $headers = ['Content-Type: application/json'];
-//
-//        // 调用接口
-//        $ch = curl_init();
-//        curl_setopt_array($ch, [
-//            CURLOPT_URL => $apiUrl,
-//            CURLOPT_RETURNTRANSFER => true,
-//            CURLOPT_POST => true,
-//            CURLOPT_HTTPHEADER => $headers,
-//            CURLOPT_POSTFIELDS => json_encode($params),
-//            CURLOPT_TIMEOUT => 120
-//        ]);
-//
-//        $response = curl_exec($ch);
-//        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
-//        $curlErr = curl_error($ch);
-//        curl_close($ch);
-//
-//        // 网络请求失败
-//        if ($curlErr) {
-//            return ['code' => 1, 'msg' => '请求失败:' . $curlErr];
-//        }
-//
-//        // 状态码错误
-//        if ($httpCode !== 200) {
-//            return ['code' => 1, 'msg' => 'API请求失败,HTTP状态码:' . $httpCode];
-//        }
-//
-//        // 解析响应
-//        $data = json_decode($response, true);
-//        if (json_last_error() !== JSON_ERROR_NONE) {
-//            return ['code' => 1, 'msg' => 'API返回数据解析失败:' . json_last_error_msg()];
-//        }
-//
-//        if (empty($data['image'])) {
-//            return ['code' => 1, 'msg' => '接口未返回有效的图像数据'];
-//        }
-//
-//        // 保存新图片
-//        try {
-//            $baseName = pathinfo($imageRelPath, PATHINFO_FILENAME);
-//            $ext = pathinfo($imageRelPath, PATHINFO_EXTENSION);
-//            $outputDir = 'uploads/extra_image/';
-//            $outputPath = ROOT_PATH . 'public/' . $outputDir;
-//
-//            if (!is_dir($outputPath)) {
-//                mkdir($outputPath, 0755, true);
-//            }
-//
-//            $saveFileName = $baseName . '-hd.' . $ext;
-//            $saveFullPath = $outputPath . $saveFileName;
-//            $resultImg = base64_decode($data['image']);
-//
-//            if ($resultImg === false || file_put_contents($saveFullPath, $resultImg) === false) {
-//                throw new Exception('保存图片失败');
-//            }
-//
-//            return [
-//                'code' => 0,
-//                'msg' => '高清图生成成功',
-//                'data' => [
-//                    'url' => '/' . $outputDir . $saveFileName,
-//                    'original_size' => filesize($imgPath),
-//                    'processed_size' => filesize($saveFullPath),
-//                    'resolution' => getimagesize($saveFullPath)
-//                ]
-//            ];
-//        } catch (Exception $e) {
-//            return ['code' => 1, 'msg' => '保存失败:' . $e->getMessage()];
-//        }
-//    }
 
     /**
      * 通用 API 调用方法(支持重试机制)
@@ -623,8 +506,8 @@ class AIGatewayService{
                         'Authorization: Bearer ' . $apiKey
                     ],
                     CURLOPT_TIMEOUT => 120,
-                    CURLOPT_SSL_VERIFYPEER => true,
-                    CURLOPT_SSL_VERIFYHOST => 2,
+                    CURLOPT_SSL_VERIFYPEER => false,
+                    CURLOPT_SSL_VERIFYHOST => false,
                     CURLOPT_CONNECTTIMEOUT => 30,
                     CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
                     CURLOPT_FAILONERROR => false

+ 99 - 0
application/service/ImageService.php

@@ -1,5 +1,6 @@
 <?php
 namespace app\service;
+use app\service\AIGatewayService;
 use think\Db;
 use think\Queue;
 /**
@@ -7,6 +8,104 @@ use think\Queue;
  * 该类将前端传过来的多个图像信息推送到处理队列中。
  */
 class ImageService{
+    /**
+     * 直接调用文生文API并返回结果
+     * @param array $params 请求参数,包含文生文提示词、模型类型等
+     * @return array GPT生成的结果
+     */
+    public function handleTextToText($params) {
+        // 直接调用文生文API
+        $ai = new AIGatewayService();
+        $prompt = $params['prompt'];
+        $model = $params['model'];
+
+        $gptRes = $ai->txtGptApi($prompt, $model);
+        $gptText = trim($gptRes['choices'][0]['message']['content'] ?? '');
+
+        // 返回结果
+        return [
+            'success' => true,
+            'message' => '文生文生成成功',
+            'data' => $gptText
+        ];
+    }
+
+    /**
+     * 直接调用文生图API并返回结果
+     * @param array $params 请求参数,包含文生文提示词、模型类型等
+     * @return array GPT生成的结果
+     */
+    public function handleTextToImg($params) {
+        $prompt = $params['prompt'];
+        $model = $params['model'];
+        $size = $params['size'];
+
+//        $ai = new AIGatewayService();
+//        $response = $ai->callDalleApi($prompt, $model, $size);
+
+        // 使用模拟数据进行测试
+        $response = array(
+            'created' => 1766732917,
+            'data' => array(
+                0 => array(
+                    'revised_prompt' => "A high-end real estate poster for New Year's Day featuring an indigo texture background. On the left, there is an ancient style folding fan with a red plum blossom branch painting and white cloud decorations. On the right, there are vertical golden characters wishing 'Happy New Year', accompanied by smaller text saying 'Future goals should be set, goals need to be firm'. At the bottom, the text reads 'High-end real estate' and 'Artistic East, Everlasting Mansion' with a phone number. The design includes Chinese elements like plum blossoms and folding fans, luxurious gold color scheme, minimal typography, and has a high-end business poster style, in 8K resolution.",
+                    'url' => 'https://oaidalleapiprodscus.blob.core.windows.net/private/org-cVDLOO48jmNEm0j82aW7iWKC/user-Qr4ZM43TMl5KP37UnSn0RhzQ/img-krzDOBoXrcsk9cTSVeGSVLpd.png?st=2025-12-26T06%3A08%3A37Z&se=2025-12-26T08%3A08%3A37Z&sp=r&sv=2024-08-04&sr=b&rscd=inline&rsct=image/png&skoid=0e2a3d55-e963-40c9-9c89-2a1aa28cb3ac&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2025-12-26T05%3A15%3A04Z&ske=2025-12-27T05%3A15%3A04Z&sks=b&skv=2024-08-04&sig=XIXQbk/abDibZL%2BNeQiNzKv4qo5fgpxSckJcyn6BbOM%3D'
+                )
+            )
+        );
+        $revised_prompt = $response['data'][0]['revised_prompt'];
+        $Url = $response['data'][0]['url'];
+        return [
+            'success' => true,
+            'revised_prompt' => $revised_prompt,
+            'url' => $Url
+        ];
+    }
+
+
+    /**
+     * 保存图片URL到本地
+     * @param string $imageUrl 图片URL
+     * @return string|null 本地图片路径或null(如果保存失败)
+     */
+    private function saveImageFromUrl($imageUrl) {
+        // 创建保存目录结构
+        $dateDir = date('Ymd');
+        $savePath = ROOT_PATH . 'public/uploads/operate/ai/dall-e/' . $dateDir . '/';
+
+        // 确保目录存在
+        if (!is_dir($savePath)) {
+            mkdir($savePath, 0755, true);
+        }
+
+        // 生成唯一的图片文件名
+        $fileName = uniqid() . '.png';
+        $fullFilePath = $savePath . $fileName;
+        $relativePath = '/uploads/operate/ai/dall-e/' . $dateDir . '/' . $fileName;
+
+        // 使用curl下载图片
+        $ch = curl_init($imageUrl);
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
+
+        $imageData = curl_exec($ch);
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        curl_close($ch);
+
+        if ($httpCode == 200 && $imageData !== false) {
+            // 保存图片到本地
+            if (file_put_contents($fullFilePath, $imageData) !== false) {
+                // 返回相对URL路径
+                return $relativePath;
+            }
+        }
+        return null;
+    }
+
+
     /**
      * 推送图像任务到队列(支持链式和单独模式)
      * @param array $params 请求参数,包含图像批次、模型类型、尺寸等