text_to_image.html 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>生成模板 - 文生图</title>
  8. <!-- 引入Font Awesome图标 -->
  9. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  10. <!-- 引入自定义CSS -->
  11. <link rel="shortcut icon" href="__CDN__/assets/img/favicon.ico"/>
  12. <style>
  13. * {
  14. margin: 0;
  15. padding: 0;
  16. box-sizing: border-box;
  17. }
  18. /* 内容区域 */
  19. .content-area {
  20. display: grid;
  21. grid-template-columns: 1fr 1fr;
  22. gap: 30px;
  23. }
  24. /* 左侧输入区域 */
  25. .input-section {
  26. background-color: #fff;
  27. border-radius: 8px;
  28. padding: 20px;
  29. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  30. }
  31. /* 右侧预览区域 */
  32. .preview-section {
  33. background-color: #fff;
  34. border-radius: 8px;
  35. padding: 20px;
  36. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  37. }
  38. /* 表单组样式 */
  39. .form-group {
  40. margin-bottom: 20px;
  41. }
  42. .form-group label {
  43. display: block;
  44. font-size: 14px;
  45. font-weight: bold;
  46. color: #333;
  47. margin-bottom: 8px;
  48. }
  49. /* 文本输入区域 */
  50. .prompt-textarea {
  51. width: 100%;
  52. height: 150px;
  53. padding: 12px;
  54. border: 1px solid #ddd;
  55. border-radius: 8px;
  56. resize: vertical;
  57. font-size: 14px;
  58. line-height: 1.5;
  59. }
  60. /* 参数设置区域 */
  61. .params-grid {
  62. display: grid;
  63. grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  64. gap: 15px;
  65. }
  66. .param-item {
  67. margin-bottom: 15px;
  68. }
  69. .param-item label {
  70. display: block;
  71. font-size: 13px;
  72. font-weight: bold;
  73. color: #333;
  74. margin-bottom: 5px;
  75. }
  76. .param-item select, .param-item input[type="number"] {
  77. width: 100%;
  78. padding: 8px 12px;
  79. border: 1px solid #ddd;
  80. border-radius: 4px;
  81. font-size: 14px;
  82. }
  83. /* 提示词示例 */
  84. .prompt-examples {
  85. background-color: #f8f9fa;
  86. border-radius: 8px;
  87. padding: 15px;
  88. margin-bottom: 20px;
  89. }
  90. .prompt-examples h4 {
  91. font-size: 14px;
  92. font-weight: bold;
  93. margin-bottom: 10px;
  94. color: #333;
  95. }
  96. .example-list {
  97. display: flex;
  98. flex-wrap: wrap;
  99. gap: 8px;
  100. }
  101. .example-tag {
  102. font-size: 12px;
  103. background-color: #e9ecef;
  104. color: #666;
  105. padding: 4px 10px;
  106. border-radius: 4px;
  107. cursor: pointer;
  108. transition: background-color 0.2s;
  109. }
  110. .example-tag:hover {
  111. background-color: #dee2e6;
  112. }
  113. /* 按钮样式 */
  114. .button-group {
  115. display: flex;
  116. gap: 10px;
  117. }
  118. .btn {
  119. padding: 12px 24px;
  120. border: none;
  121. border-radius: 8px;
  122. font-size: 14px;
  123. cursor: pointer;
  124. transition: background-color 0.2s;
  125. }
  126. .btn-primary {
  127. background-color: #007bff;
  128. color: #fff;
  129. }
  130. .btn-primary:hover {
  131. background-color: #0056b3;
  132. }
  133. .btn-secondary {
  134. background-color: #6c757d;
  135. color: #fff;
  136. }
  137. .btn-secondary:hover {
  138. background-color: #545b62;
  139. }
  140. /* 预览区域 */
  141. .preview-container {
  142. width: 100%;
  143. min-height: 300px;
  144. background-color: #f8f9fa;
  145. border-radius: 8px;
  146. display: flex;
  147. align-items: center;
  148. justify-content: center;
  149. margin-bottom: 20px;
  150. overflow: hidden;
  151. }
  152. .preview-image {
  153. max-width: 100%;
  154. max-height: 100%;
  155. object-fit: contain;
  156. }
  157. .preview-placeholder {
  158. font-size: 14px;
  159. color: #666;
  160. text-align: center;
  161. }
  162. /* 模板信息 */
  163. .template-info {
  164. margin-bottom: 20px;
  165. }
  166. .template-info h4 {
  167. font-size: 14px;
  168. font-weight: bold;
  169. margin-bottom: 10px;
  170. color: #333;
  171. }
  172. .template-form input {
  173. width: 100%;
  174. padding: 10px 12px;
  175. border: 1px solid #ddd;
  176. border-radius: 8px;
  177. font-size: 14px;
  178. margin-bottom: 10px;
  179. }
  180. .optimize-btn {
  181. background-color: #ffffff;
  182. color: #4b5563;
  183. padding: 10px 18px;
  184. border: 1px solid #d1d5db;
  185. border-radius: 8px;
  186. font-size: 14px;
  187. font-weight: 500;
  188. cursor: pointer;
  189. transition: all 0.3s ease;
  190. display: flex;
  191. align-items: center;
  192. gap: 6px;
  193. }
  194. .optimize-btn:hover {
  195. background-color: #f9fafb;
  196. border-color: #9ca3af;
  197. transform: translateY(-1px);
  198. }
  199. .clear-btn {
  200. background-color: #ffffff;
  201. color: #dc3545;
  202. border: 1px solid #f87171;
  203. border-radius: 8px;
  204. font-size: 14px;
  205. font-weight: 500;
  206. cursor: pointer;
  207. transition: all 0.3s ease;
  208. display: flex;
  209. align-items: center;
  210. gap: 6px;
  211. }
  212. .clear-btn:hover {
  213. background-color: #fee2e2;
  214. border-color: #ef4444;
  215. transform: translateY(-1px);
  216. }
  217. /* 操作按钮 */
  218. .prompt-actions {
  219. gap: 8px;
  220. }
  221. .clear-btn, .optimize-btn {
  222. padding: 8px 12px;
  223. font-size: 13px;
  224. }
  225. /* 菜单样式 */
  226. .menu-container {
  227. background-color: #f5f7fa;
  228. border-radius: 8px;
  229. padding: 10px;
  230. margin-bottom: 20px;
  231. display: flex;
  232. gap: 10px;
  233. }
  234. .menu-item {
  235. flex: 1;
  236. }
  237. .menu-item a {
  238. display: block;
  239. padding: 12px;
  240. background-color: white;
  241. border: 1px solid #e1e8ed;
  242. border-radius: 6px;
  243. text-align: center;
  244. text-decoration: none;
  245. color: #657786;
  246. font-size: 14px;
  247. font-weight: 500;
  248. transition: all 0.3s ease;
  249. }
  250. .menu-item a:hover {
  251. background-color: #e8f0fe;
  252. border-color: #1da1f2;
  253. color: #1da1f2;
  254. }
  255. .menu-item.active a {
  256. background-color: #1da1f2;
  257. border-color: #1da1f2;
  258. color: white;
  259. }
  260. .menu-item i {
  261. margin-right: 6px;
  262. font-size: 16px;
  263. }
  264. </style>
  265. </head>
  266. <body>
  267. <div class="container">
  268. <!-- 菜单功能区 -->
  269. <div class="menu-container">
  270. <div class="menu-item active">
  271. <a href="{:url('user/index')}">
  272. <i class="fas fa-image"></i>
  273. 文生图
  274. </a>
  275. </div>
  276. <div class="menu-item">
  277. <a href="{:url('user/text_to_video')}">
  278. <i class="fas fa-video"></i>
  279. 文生视频
  280. </a>
  281. </div>
  282. </div>
  283. <div class="content-area">
  284. <!-- 左侧输入区域 -->
  285. <div class="input-section">
  286. <h3 style="font-size: 18px; font-weight: bold; margin-bottom: 15px; color: #333;">文生图生成参数设置</h3>
  287. <input type="text" value="{$user.id}" id="user_id" disabled style="display: none">
  288. <!-- 提示词示例 -->
  289. <div class="prompt-examples">
  290. <label for="prompt">提示词</label>
  291. <textarea id="prompt" class="prompt-textarea" placeholder="请输入您想要生成的图像描述,越详细越好..."></textarea>
  292. </div>
  293. <!-- 生成参数 -->
  294. <div class="form-group">
  295. <label>生成参数</label>
  296. <div class="params-grid">
  297. <div class="param-item">
  298. <label for="style">风格</label>
  299. <select id="style">
  300. <option value="">默认</option>
  301. <option value="anime">动漫</option>
  302. <option value="oil">油画</option>
  303. <option value="landscape">风景</option>
  304. <option value="portrait">人像</option>
  305. </select>
  306. </div>
  307. <div class="param-item">
  308. <label for="imageSize">尺寸</label>
  309. <select id="imageSize">
  310. <option value="1:1">1:1</option>
  311. <option value="4:3">4:3</option>
  312. <option value="3:4">3:4</option>
  313. <option value="16:9">16:9</option>
  314. <option value="9:16">9:16</option>
  315. </select>
  316. </div>
  317. </div>
  318. </div>
  319. <div class="button-group">
  320. <button class="clear-btn" onclick="clearPrompt()">
  321. <i class="fas fa-trash"></i>
  322. 清空
  323. </button>
  324. <button class="optimize-btn" onclick="optimizePrompt()">
  325. <i class="fas fa-magic"></i>
  326. 一键优化
  327. </button>
  328. <button class="btn btn-primary" id="generateBtn" onclick="generateImage()">
  329. <i class="fas fa-arrow-up"></i>
  330. 生成图像
  331. </button>
  332. </div>
  333. </div>
  334. <!-- 右侧预览区域 -->
  335. <div class="preview-section">
  336. <h3 style="font-size: 18px; font-weight: bold; margin-bottom: 15px; color: #333;">图像预览</h3>
  337. <!-- 预览容器 -->
  338. <div id="imageResult" class="preview-container">
  339. <div class="preview-placeholder">
  340. <p>点击"生成图像"按钮开始创建您的模板</p>
  341. <p style="font-size: 12px; margin-top: 10px; color: #999;">生成时间可能需要几秒到几十秒不等</p>
  342. </div>
  343. </div>
  344. <!-- 模板信息 -->
  345. <div class="template-info">
  346. <h4>模板信息</h4>
  347. <div class="template-form">
  348. <input type="text" id="templateName" placeholder="请输入模板名称">
  349. </div>
  350. </div>
  351. <!-- 保存按钮 -->
  352. <div class="button-group">
  353. <button class="btn btn-primary" id="saveBtn" disabled onclick="saveTemplate()">保存为模板</button>
  354. <button class="btn btn-secondary" id="downloadBtn" disabled onclick="downloadImage()">下载图像</button>
  355. </div>
  356. </div>
  357. </div>
  358. </div>
  359. <script>
  360. // 自定义提示框函数
  361. function showAlert(message, type = 'info', duration = 3000) {
  362. // 检查是否已存在提示框容器,如果不存在则创建
  363. let alertElement = document.getElementById('customAlert');
  364. if (!alertElement) {
  365. alertElement = document.createElement('div');
  366. alertElement.id = 'customAlert';
  367. alertElement.className = 'custom-alert';
  368. document.body.appendChild(alertElement);
  369. }
  370. alertElement.textContent = message;
  371. alertElement.className = `custom-alert ${type}`;
  372. // 显示提示框
  373. alertElement.classList.add('show');
  374. // 自动隐藏
  375. setTimeout(() => {
  376. alertElement.classList.remove('show');
  377. }, duration);
  378. }
  379. // 生成图片函数
  380. function generateImage() {
  381. const promptText = document.querySelector('.prompt-textarea').value.trim();
  382. const imageStyle = document.getElementById('style').value;
  383. const imageSize = document.getElementById('imageSize').value;
  384. const imageResultDiv = document.getElementById('imageResult');
  385. // 检查是否有提示词
  386. if (!promptText) {
  387. showAlert('请先输入图片描述', 'warning');
  388. return;
  389. }
  390. // 显示加载状态
  391. imageResultDiv.innerHTML = `
  392. <div class="image-loading">
  393. <i class="fas fa-spinner fa-spin"></i>
  394. <p>正在生成图片,请稍候...</p>
  395. <p style="font-size: 12px; margin-top: 10px; color: #999;">生成时间可能需要几秒到几十秒不等</p>
  396. </div>
  397. `;
  398. // 拼接风格和尺寸到提示词末尾
  399. let fullPrompt = promptText;
  400. if (imageStyle) {
  401. fullPrompt += '风格:' + imageStyle;
  402. }
  403. if (imageSize) {
  404. fullPrompt += '尺寸比例:' + imageSize;
  405. }
  406. // 发送请求到文生图接口
  407. $.ajax({
  408. url: '/index.php/api/work_order/GetTxtToImg',
  409. type: 'POST',
  410. dataType: 'json',
  411. data: {
  412. status_val: '文生图',
  413. prompt: fullPrompt,
  414. model: 'gemini-3-pro-image-preview',
  415. style: imageStyle,
  416. size: imageSize
  417. },
  418. success: function(response) {
  419. // 处理成功响应
  420. if (response.code === 0 && response.data && response.data.url) {
  421. // 显示生成的图片
  422. imageResultDiv.innerHTML = `
  423. <div class="image-container">
  424. <img src="${response.data.url}" alt="生成的图片" class="generated-image" id="previewImage" style="max-width: 100%; height: auto; max-height: 600px; cursor: pointer;">
  425. </div>
  426. `;
  427. // 启用下载和保存按钮
  428. document.getElementById('downloadBtn').disabled = false;
  429. document.getElementById('saveBtn').disabled = false;
  430. // 添加图片放大模态框(如果还不存在)
  431. if (!document.getElementById('imageModal')) {
  432. const modal = document.createElement('div');
  433. modal.id = 'imageModal';
  434. modal.style.cssText = `
  435. display: none;
  436. position: fixed;
  437. z-index: 9999;
  438. left: 0;
  439. top: 0;
  440. width: 100%;
  441. height: 100%;
  442. overflow: auto;
  443. background-color: rgba(0, 0, 0, 0.9);
  444. justify-content: center;
  445. align-items: center;
  446. `;
  447. modal.style.display = 'flex';
  448. modal.style.display = 'none';
  449. const modalContent = document.createElement('img');
  450. modalContent.id = 'modalImage';
  451. modalContent.style.cssText = `
  452. max-width: 90%;
  453. max-height: 90%;
  454. margin: auto;
  455. `;
  456. const closeBtn = document.createElement('span');
  457. closeBtn.innerHTML = '&times;';
  458. closeBtn.style.cssText = `
  459. position: absolute;
  460. top: 20px;
  461. right: 35px;
  462. color: #f1f1f1;
  463. font-size: 40px;
  464. font-weight: bold;
  465. transition: 0.3s;
  466. cursor: pointer;
  467. `;
  468. closeBtn.onclick = function() {
  469. modal.style.display = 'none';
  470. };
  471. modal.appendChild(closeBtn);
  472. modal.appendChild(modalContent);
  473. document.body.appendChild(modal);
  474. // 点击模态框背景关闭
  475. modal.onclick = function(event) {
  476. if (event.target.id === 'imageModal') {
  477. this.style.display = 'none';
  478. }
  479. };
  480. }
  481. // 为生成的图片添加点击事件
  482. const generatedImage = imageResultDiv.querySelector('.generated-image');
  483. generatedImage.onclick = function() {
  484. const modal = document.getElementById('imageModal');
  485. const modalImage = document.getElementById('modalImage');
  486. modalImage.src = this.src;
  487. modal.style.display = 'flex';
  488. };
  489. // 键盘ESC键关闭
  490. document.addEventListener('keydown', function(event) {
  491. if (event.key === 'Escape') {
  492. const modal = document.getElementById('imageModal');
  493. if (modal && modal.style.display === 'flex') {
  494. modal.style.display = 'none';
  495. }
  496. }
  497. });
  498. } else {
  499. // 显示错误信息
  500. imageResultDiv.innerHTML = `
  501. <div class="image-error">
  502. <i class="fas fa-exclamation-triangle"></i>
  503. <p>图片生成失败:${response.msg || '未知错误'}</p>
  504. </div>
  505. `;
  506. }
  507. },
  508. error: function(xhr, status, error) {
  509. // 处理网络错误
  510. imageResultDiv.innerHTML = `
  511. <div class="image-error">
  512. <i class="fas fa-exclamation-triangle"></i>
  513. <p>图像生成失败</p>
  514. </div>
  515. `;
  516. }
  517. });
  518. }
  519. // 一键优化提示词功能
  520. function optimizePrompt() {
  521. const textarea = document.querySelector('.prompt-textarea');
  522. const optimizeBtn = document.querySelector('.optimize-btn');
  523. if (textarea && textarea.value.trim() !== '') {
  524. // 显示加载状态
  525. const originalBtnText = optimizeBtn.innerHTML;
  526. optimizeBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 优化中...';
  527. optimizeBtn.disabled = true;
  528. // 构造请求参数
  529. const params = {
  530. status_val: '文生文',
  531. prompt: textarea.value,
  532. model: 'gemini-2.0-flash'
  533. };
  534. // 发送AJAX请求
  535. $.ajax({
  536. url: '/index.php/api/work_order/GetTxtToTxt',
  537. type: 'POST',
  538. data: params,
  539. dataType: 'json',
  540. success: function(data) {
  541. console.log(data);
  542. // 恢复按钮状态
  543. optimizeBtn.innerHTML = originalBtnText;
  544. optimizeBtn.disabled = false;
  545. // 优化成功,更新文本框内容
  546. textarea.value = data.content;
  547. textarea.scrollTop = textarea.scrollHeight;
  548. updateCharCount();
  549. // 显示优化成功提示
  550. const alert = document.createElement('div');
  551. alert.className = 'alert alert-success';
  552. alert.innerHTML = '提示词已优化!';
  553. alert.style.position = 'fixed';
  554. alert.style.top = '20px';
  555. alert.style.right = '20px';
  556. alert.style.zIndex = '9999';
  557. document.body.appendChild(alert);
  558. // 3秒后移除提示
  559. setTimeout(() => {
  560. alert.remove();
  561. }, 3000);
  562. }
  563. });
  564. } else {
  565. showAlert('请先输入提示词!', 'warning');
  566. }
  567. }
  568. // 清空提示词功能
  569. function clearPrompt() {
  570. const textarea = document.querySelector('.prompt-textarea');
  571. if (textarea) {
  572. textarea.value = '';
  573. textarea.focus();
  574. }
  575. }
  576. // 保存模板函数
  577. function saveTemplate() {
  578. // 获取所有需要的参数
  579. const templateName = document.getElementById('templateName').value;
  580. const prompt = document.getElementById('prompt').value.trim();
  581. const style = document.getElementById('style').value;
  582. const imageSize = document.getElementById('imageSize').value;
  583. const previewImage = document.getElementById('previewImage');
  584. const user_id = document.getElementById('user_id');
  585. // 验证必填项
  586. if (!templateName.trim()) {
  587. showAlert('请输入模板名称', 'error');
  588. return;
  589. }
  590. if (!prompt) {
  591. showAlert('请输入提示词', 'error');
  592. return;
  593. }
  594. if (!previewImage || !previewImage.src) {
  595. showAlert('请先生成图片', 'error');
  596. return;
  597. }
  598. // 准备请求参数
  599. // 将完整URL转换为相对路径
  600. let imageUrl = previewImage.src;
  601. try {
  602. const url = new URL(imageUrl);
  603. imageUrl = url.pathname;
  604. } catch (e) {
  605. // 如果已经是相对路径,直接使用
  606. }
  607. const params = {
  608. chinese_description: prompt,
  609. template_image_url: imageUrl,
  610. template_name: templateName,
  611. style: style,
  612. size: imageSize,
  613. user_id:user_id,
  614. type: '文生图' // 固定为文生图类型
  615. };
  616. // 显示加载状态
  617. const saveBtn = document.getElementById('saveBtn');
  618. const originalText = saveBtn.innerHTML;
  619. saveBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 保存中...';
  620. saveBtn.disabled = true;
  621. // 发送AJAX请求
  622. $.ajax({
  623. url: '/index.php/api/work_order/Add_Product_Template',
  624. type: 'POST',
  625. dataType: 'json',
  626. data: params,
  627. success: function(response) {
  628. if (response.code === 0) {
  629. showAlert('模板保存成功!', 'success');
  630. // 清空模板名称输入框
  631. document.getElementById('templateName').value = '';
  632. } else {
  633. showAlert('模板保存失败:' + (response.msg || '未知错误'), 'error');
  634. }
  635. },
  636. error: function(xhr, status, error) {
  637. console.error('AJAX请求失败:', error);
  638. showAlert('网络错误,模板保存失败', 'error');
  639. },
  640. complete: function() {
  641. // 恢复按钮状态
  642. saveBtn.innerHTML = originalText;
  643. saveBtn.disabled = false;
  644. }
  645. });
  646. }
  647. // 下载图像函数
  648. function downloadImage() {
  649. const previewImage = document.getElementById('previewImage');
  650. if (previewImage && previewImage.src) {
  651. // 创建临时链接下载图片
  652. const link = document.createElement('a');
  653. link.href = previewImage.src;
  654. // 生成更有意义的文件名
  655. const timestamp = new Date().getTime();
  656. const promptText = document.querySelector('.prompt-textarea').value.trim();
  657. // 从提示词中提取前20个字符作为文件名的一部分
  658. const promptPart = promptText.substring(0, 20).replace(/[^\w\u4e00-\u9fa5]/g, '') || 'image';
  659. // 组合文件名:提示词部分_时间戳.jpg
  660. const fileName = `${promptPart}_${timestamp}.jpg`;
  661. link.download = fileName;
  662. link.click();
  663. }
  664. }
  665. </script>
  666. </body>
  667. </html>