aitu-app 0.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/.DS_Store +0 -0
  2. package/README.md +47 -0
  3. package/_headers +84 -0
  4. package/_redirects +2 -0
  5. package/assets/ChatMessagesArea-CkUX81uB.js +251 -0
  6. package/assets/ChatMessagesArea-Di0Z80Zh.css +1 -0
  7. package/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  8. package/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  9. package/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  10. package/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  11. package/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  12. package/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  13. package/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  14. package/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  15. package/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  16. package/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  17. package/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  18. package/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  19. package/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  20. package/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  21. package/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  22. package/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  23. package/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  24. package/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  25. package/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  26. package/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  27. package/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  28. package/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  29. package/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  30. package/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  31. package/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  32. package/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  33. package/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  34. package/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  35. package/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  36. package/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  37. package/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  38. package/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  39. package/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  40. package/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  41. package/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  42. package/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  43. package/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  44. package/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  45. package/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  46. package/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  47. package/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  48. package/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  49. package/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  50. package/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  51. package/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  52. package/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  53. package/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  54. package/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  55. package/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  56. package/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  57. package/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  58. package/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  59. package/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  60. package/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  61. package/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  62. package/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  63. package/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  64. package/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  65. package/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  66. package/assets/Tableau10-B-NsZVaP.js +1 -0
  67. package/assets/Tableau10-Dnlau_Wv.js +1 -0
  68. package/assets/ToolboxDrawer-By1XMh8B.js +87 -0
  69. package/assets/ToolboxDrawer-fPqvDLQE.css +1 -0
  70. package/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  71. package/assets/ai-analyze-Db-iXol6.js +1 -0
  72. package/assets/arc-BZXVqUcI.js +1 -0
  73. package/assets/arc-ajYHRRnk.js +1 -0
  74. package/assets/array-B5oSNiGi.js +1 -0
  75. package/assets/array-BKyUJesY.js +1 -0
  76. package/assets/batch-image-generation-Baqb01Lm.js +6 -0
  77. package/assets/batch-image-generation-CbLMWmjk.css +1 -0
  78. package/assets/blockDiagram-38ab4fdb-BT3H_WVv.js +118 -0
  79. package/assets/blockDiagram-38ab4fdb-u0xYP3Lt.js +118 -0
  80. package/assets/c4Diagram-3d4e48cf-CBvM6zjM.js +10 -0
  81. package/assets/c4Diagram-3d4e48cf-WOIEVidH.js +10 -0
  82. package/assets/channel-BP25wTsw.js +1 -0
  83. package/assets/channel-HzrLNFUg.js +1 -0
  84. package/assets/classDiagram-70f12bd4-BMutcvFi.js +2 -0
  85. package/assets/classDiagram-70f12bd4-Cl9U1r5F.js +2 -0
  86. package/assets/classDiagram-v2-f2320105-C0agtbR4.js +2 -0
  87. package/assets/classDiagram-v2-f2320105-tCBzATK6.js +2 -0
  88. package/assets/clone-B69pF7Y_.js +1 -0
  89. package/assets/clone-oX7o-l4R.js +1 -0
  90. package/assets/createText-2e5e7dd3-CZ9_fscE.js +5 -0
  91. package/assets/createText-2e5e7dd3-idrqgJjU.js +7 -0
  92. package/assets/edges-e0da2a9e-C-RyePMV.js +4 -0
  93. package/assets/edges-e0da2a9e-DJXAjJSL.js +4 -0
  94. package/assets/erDiagram-9861fffd-DWJR_3zL.js +51 -0
  95. package/assets/erDiagram-9861fffd-x2Kcy95-.js +51 -0
  96. package/assets/flowDb-956e92f1-BgKjOIdz.js +10 -0
  97. package/assets/flowDb-956e92f1-CF6y18Tn.js +10 -0
  98. package/assets/flowDiagram-66a62f08-BPPw0wPU.js +4 -0
  99. package/assets/flowDiagram-66a62f08-CSAllSFf.js +4 -0
  100. package/assets/flowDiagram-v2-96b9c2cf-B-UGyXRi.js +1 -0
  101. package/assets/flowDiagram-v2-96b9c2cf-Cm596kxZ.js +1 -0
  102. package/assets/flowchart-elk-definition-4a651766-9XSRJbsr.js +139 -0
  103. package/assets/flowchart-elk-definition-4a651766-DWFN9DN3.js +139 -0
  104. package/assets/ganttDiagram-c361ad54-D9tbz9tQ.js +257 -0
  105. package/assets/ganttDiagram-c361ad54-ot5pUYpT.js +257 -0
  106. package/assets/gitGraphDiagram-72cf32ee-BFV3Mt8C.js +70 -0
  107. package/assets/gitGraphDiagram-72cf32ee-C6qFzgGh.js +70 -0
  108. package/assets/graph-BxwlF7JS.js +1 -0
  109. package/assets/graph-D-2Ldvxg.js +1 -0
  110. package/assets/grid-image-cM9AmYC8.js +1 -0
  111. package/assets/has-CgdIPiQG.js +1 -0
  112. package/assets/hasIn-4iY02rGN.js +1 -0
  113. package/assets/index-3862675e-CVZnpwDN.js +1 -0
  114. package/assets/index-3862675e-DqdI9cab.js +1 -0
  115. package/assets/index-B2dvADz8.css +1 -0
  116. package/assets/index-BicRPzXC.js +1 -0
  117. package/assets/index-Bs7-jmv6.css +1 -0
  118. package/assets/index-BwSGXyRr.js +99 -0
  119. package/assets/index-C1XdOOAn.css +1 -0
  120. package/assets/index-C4AKKbpQ.css +1 -0
  121. package/assets/index-CkpXFt8n.js +1 -0
  122. package/assets/index-CrxF9gFe.js +42 -0
  123. package/assets/index-DBWqXBIQ.js +93 -0
  124. package/assets/index-DI_5V2-m.js +3 -0
  125. package/assets/index-DWUAFoZG.js +2064 -0
  126. package/assets/index-Dn0YtZ2R.js +3 -0
  127. package/assets/index-e05Rs4M6.js +12 -0
  128. package/assets/index.dom-C3-224fz.js +1 -0
  129. package/assets/infoDiagram-f8f76790-CnrpwoOt.js +7 -0
  130. package/assets/infoDiagram-f8f76790-FKC1Sy9Y.js +7 -0
  131. package/assets/init-A0kIFD9x.js +1 -0
  132. package/assets/init-Gi6I4Gst.js +1 -0
  133. package/assets/inspiration-board-B_-BBBHt.js +1 -0
  134. package/assets/isEmpty-Dj2GV0v-.js +1 -0
  135. package/assets/journeyDiagram-49397b02-B7fP21sU.js +139 -0
  136. package/assets/journeyDiagram-49397b02-Dp3X9XWq.js +139 -0
  137. package/assets/katex-BbEIqZs1.js +261 -0
  138. package/assets/katex-Cu_Erd72.js +261 -0
  139. package/assets/layout-BD3yCK_X.js +1 -0
  140. package/assets/layout-DHHYqX7p.js +1 -0
  141. package/assets/line-B3bNrkzn.js +1 -0
  142. package/assets/line-B86HLuqu.js +1 -0
  143. package/assets/linear-DU2Ciymb.js +1 -0
  144. package/assets/linear-wCAlMhOS.js +1 -0
  145. package/assets/mermaid.core-DfVvnpgz.js +91 -0
  146. package/assets/mindmap-definition-fc14e90a-D1sxE3xG.js +425 -0
  147. package/assets/mindmap-definition-fc14e90a-YuSOJC7P.js +425 -0
  148. package/assets/ordinal-BRr1uYdk.js +1 -0
  149. package/assets/ordinal-Cboi1Yqb.js +1 -0
  150. package/assets/path-CY0bYimO.js +1 -0
  151. package/assets/path-CbwjOpE9.js +1 -0
  152. package/assets/photo-wall-splitter-BVU2e0aS.js +1 -0
  153. package/assets/pick-Cvlwra4g.js +1 -0
  154. package/assets/pieDiagram-8a3498a8-B6mJUqro.js +35 -0
  155. package/assets/pieDiagram-8a3498a8-B91bWgo_.js +35 -0
  156. package/assets/quadrantDiagram-120e2f19-BxS8fQEz.js +7 -0
  157. package/assets/quadrantDiagram-120e2f19-DwudONqx.js +7 -0
  158. package/assets/requirementDiagram-deff3bca-DygaMIoy.js +52 -0
  159. package/assets/requirementDiagram-deff3bca-v9xlgfS8.js +52 -0
  160. package/assets/sankeyDiagram-04a897e0-BV23dp4l.js +8 -0
  161. package/assets/sankeyDiagram-04a897e0-BXCiXiyw.js +8 -0
  162. package/assets/sequenceDiagram-704730f1-CObRpNi4.js +122 -0
  163. package/assets/sequenceDiagram-704730f1-Ck69A6wI.js +122 -0
  164. package/assets/settings-dialog-BlCO49C4.js +1 -0
  165. package/assets/settings-dialog-QUxXj54T.css +1 -0
  166. package/assets/stateDiagram-587899a1-J_G6I0oo.js +1 -0
  167. package/assets/stateDiagram-587899a1-z-tKclr3.js +1 -0
  168. package/assets/stateDiagram-v2-d93cdb3a-DsThtOzP.js +1 -0
  169. package/assets/stateDiagram-v2-d93cdb3a-XIvq5t8a.js +1 -0
  170. package/assets/styles-6aaf32cf-1fjuNMUk.js +207 -0
  171. package/assets/styles-6aaf32cf-DT2rVNfQ.js +207 -0
  172. package/assets/styles-9a916d00-fLeUSina.js +160 -0
  173. package/assets/styles-9a916d00-q64Umkis.js +160 -0
  174. package/assets/styles-c10674c1-BWlxVc3Q.js +116 -0
  175. package/assets/styles-c10674c1-CtYpjMYU.js +116 -0
  176. package/assets/svgDrawCommon-08f97a94-C_DhKfny.js +1 -0
  177. package/assets/svgDrawCommon-08f97a94-DSBqmUv2.js +1 -0
  178. package/assets/timeline-definition-85554ec2-AKpzwLPN.js +61 -0
  179. package/assets/timeline-definition-85554ec2-dTkYwoLF.js +61 -0
  180. package/assets/ttd-dialog-CxiaIUuJ.js +47 -0
  181. package/assets/ttd-dialog-DCapefb6.css +1 -0
  182. package/assets/upload-4sxUU7q_.js +1 -0
  183. package/assets/video-recovery-service-BckHbSyK.js +1 -0
  184. package/assets/web-vitals-DcvjKPr-.js +1 -0
  185. package/assets/winbox.bundle.min-CoRPjCs5.js +1 -0
  186. package/assets/xlsx-CkFp8p6R.js +105 -0
  187. package/assets/xychartDiagram-e933f94c-DCmvL0ag.js +7 -0
  188. package/assets/xychartDiagram-e933f94c-aqOiXp_u.js +7 -0
  189. package/batch-image.html +1616 -0
  190. package/favicon.ico +0 -0
  191. package/icons/README.md +55 -0
  192. package/icons/aitu10.png +0 -0
  193. package/icons/android-chrome-192x192.png +0 -0
  194. package/icons/android-chrome-512x512.png +0 -0
  195. package/icons/apple-touch-icon.png +0 -0
  196. package/icons/favicon-16x16.png +0 -0
  197. package/icons/favicon-16x16.svg +539 -0
  198. package/icons/favicon-32x32.png +0 -0
  199. package/icons/favicon-32x32.svg +539 -0
  200. package/icons/favicon-new.svg +539 -0
  201. package/icons/favicon-new.svg.png +0 -0
  202. package/icons/icon-192x192.svg +539 -0
  203. package/icons/icon-512x512.svg +539 -0
  204. package/icons/icon-96x96.png +0 -0
  205. package/icons/icon-96x96.svg +539 -0
  206. package/iframe-test.html +340 -0
  207. package/index.html +105 -0
  208. package/init.json +6 -0
  209. package/logo/cardid.jpg +0 -0
  210. package/logo/group-qr.png +0 -0
  211. package/logo/logo_drawnix_h.svg +539 -0
  212. package/logo/logo_drawnix_h_dark.svg +539 -0
  213. package/logo/logo_drawnix_new.svg +539 -0
  214. package/manifest.json +52 -0
  215. package/package.json +31 -0
  216. package/product_showcase/aitu-01.png +0 -0
  217. package/product_showcase/aitu-02.png +0 -0
  218. package/product_showcase/aitu-03.png +0 -0
  219. package/product_showcase/aitu-04.png +0 -0
  220. package/product_showcase/aitu-05.png +0 -0
  221. package/product_showcase/aitu-06.png +0 -0
  222. package/product_showcase/case-1.png +0 -0
  223. package/product_showcase/case-2.png +0 -0
  224. package/robots.txt +13 -0
  225. package/sitemap.xml +29 -0
  226. package/sw-debug/app.js +3069 -0
  227. package/sw-debug/console-entry.js +80 -0
  228. package/sw-debug/log-entry.js +452 -0
  229. package/sw-debug/log-panel.js +309 -0
  230. package/sw-debug/postmessage-entry.js +117 -0
  231. package/sw-debug/status-panel.js +125 -0
  232. package/sw-debug/styles.css +2103 -0
  233. package/sw-debug/sw-communication.js +208 -0
  234. package/sw-debug/utils.js +112 -0
  235. package/sw-debug.html +685 -0
  236. package/sw.js +58 -0
  237. package/version.json +10 -0
@@ -0,0 +1,1616 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>批量出图工具</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
16
+ background: #f5f5f5;
17
+ height: 100vh;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .container {
22
+ height: 100%;
23
+ display: block;
24
+ padding: 16px;
25
+ }
26
+
27
+ .main-content {
28
+ width: 100%;
29
+ height: 100%;
30
+ display: flex;
31
+ flex-direction: column;
32
+ }
33
+
34
+ .image-library-container {
35
+ position: fixed;
36
+ right: 16px;
37
+ top: 16px;
38
+ width: 320px;
39
+ max-height: calc(100vh - 32px);
40
+ display: flex;
41
+ flex-direction: column;
42
+ background: white;
43
+ border-radius: 8px;
44
+ box-shadow: 0 4px 16px rgba(0,0,0,0.2);
45
+ transition: all 0.3s;
46
+ z-index: 1000;
47
+ }
48
+
49
+ .image-library-container.collapsed {
50
+ width: 50px;
51
+ height: 50px;
52
+ max-height: 50px;
53
+ overflow: hidden;
54
+ }
55
+
56
+ .library-header {
57
+ padding: 12px;
58
+ border-bottom: 1px solid #eee;
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: space-between;
62
+ background: linear-gradient(135deg, #F39C12 0%, #E67E22 100%);
63
+ color: white;
64
+ border-radius: 8px 8px 0 0;
65
+ }
66
+
67
+ .library-header h3 {
68
+ font-size: 14px;
69
+ font-weight: 600;
70
+ margin: 0;
71
+ }
72
+
73
+ .toggle-btn {
74
+ background: none;
75
+ border: none;
76
+ color: white;
77
+ cursor: pointer;
78
+ font-size: 18px;
79
+ padding: 0;
80
+ width: 24px;
81
+ height: 24px;
82
+ display: flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ }
86
+
87
+ .library-content {
88
+ flex: 1;
89
+ overflow-y: auto;
90
+ padding: 12px;
91
+ display: flex;
92
+ flex-direction: column;
93
+ gap: 12px;
94
+ }
95
+
96
+ .collapsed .library-content {
97
+ display: none;
98
+ }
99
+
100
+ .upload-section {
101
+ display: flex;
102
+ flex-direction: column;
103
+ gap: 8px;
104
+ padding-bottom: 12px;
105
+ border-bottom: 2px solid #f0f0f0;
106
+ }
107
+
108
+ .upload-btn {
109
+ width: 100%;
110
+ padding: 8px 12px;
111
+ background: linear-gradient(135deg, #F39C12 0%, #E67E22 100%);
112
+ color: white;
113
+ border: none;
114
+ border-radius: 6px;
115
+ font-size: 13px;
116
+ font-weight: 500;
117
+ cursor: pointer;
118
+ transition: all 0.2s;
119
+ }
120
+
121
+ .upload-btn:hover {
122
+ transform: translateY(-1px);
123
+ box-shadow: 0 4px 12px rgba(243, 156, 18, 0.3);
124
+ }
125
+
126
+ #imageUploadInput {
127
+ display: none;
128
+ }
129
+
130
+ .upload-hint {
131
+ font-size: 11px;
132
+ color: #999;
133
+ text-align: center;
134
+ }
135
+
136
+ .library-grid {
137
+ display: grid;
138
+ grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
139
+ gap: 8px;
140
+ }
141
+
142
+ .library-image {
143
+ aspect-ratio: 1;
144
+ border-radius: 4px;
145
+ overflow: hidden;
146
+ cursor: pointer;
147
+ border: 2px solid transparent;
148
+ transition: all 0.2s;
149
+ position: relative;
150
+ }
151
+
152
+ .library-image:hover {
153
+ border-color: #F39C12;
154
+ transform: scale(1.05);
155
+ }
156
+
157
+ .library-image img {
158
+ width: 100%;
159
+ height: 100%;
160
+ object-fit: cover;
161
+ }
162
+
163
+ .library-image .delete-image-btn {
164
+ position: absolute;
165
+ top: 4px;
166
+ right: 4px;
167
+ width: 20px;
168
+ height: 20px;
169
+ background: rgba(220, 53, 69, 0.9);
170
+ color: white;
171
+ border: none;
172
+ border-radius: 50%;
173
+ font-size: 14px;
174
+ line-height: 1;
175
+ cursor: pointer;
176
+ display: none;
177
+ align-items: center;
178
+ justify-content: center;
179
+ }
180
+
181
+ .library-image:hover .delete-image-btn {
182
+ display: flex;
183
+ }
184
+
185
+ .empty-library {
186
+ text-align: center;
187
+ padding: 20px;
188
+ color: #999;
189
+ font-size: 13px;
190
+ }
191
+
192
+ /* 图片单元格样式 */
193
+ .image-cell-content {
194
+ display: flex;
195
+ gap: 4px;
196
+ flex-wrap: wrap;
197
+ align-items: flex-start;
198
+ padding: 0;
199
+ padding-right: 15px;
200
+ padding-bottom: 15px;
201
+ width: 100%;
202
+ max-width: calc(100% - 15px);
203
+ position: relative;
204
+ }
205
+
206
+ .cell-image-thumb {
207
+ width: 40px;
208
+ height: 40px;
209
+ border-radius: 4px;
210
+ overflow: hidden;
211
+ position: relative;
212
+ border: 1px solid #ddd;
213
+ }
214
+
215
+ .cell-image-thumb img {
216
+ width: 100%;
217
+ height: 100%;
218
+ object-fit: cover;
219
+ }
220
+
221
+ .cell-image-thumb .remove-btn {
222
+ position: absolute;
223
+ top: 0;
224
+ right: 0;
225
+ background: rgba(220, 53, 69, 0.9);
226
+ color: white;
227
+ border: none;
228
+ width: 16px;
229
+ height: 16px;
230
+ font-size: 10px;
231
+ line-height: 1;
232
+ cursor: pointer;
233
+ display: none;
234
+ padding: 0;
235
+ z-index: 2;
236
+ }
237
+
238
+ .cell-image-thumb:hover .remove-btn {
239
+ display: block;
240
+ }
241
+
242
+ .add-image-btn {
243
+ width: 40px;
244
+ height: 40px;
245
+ border: 2px dashed #ccc;
246
+ border-radius: 4px;
247
+ background: #f9f9f9;
248
+ color: #999;
249
+ font-size: 20px;
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ cursor: pointer;
254
+ transition: all 0.2s;
255
+ flex-shrink: 0;
256
+ }
257
+
258
+ .add-image-btn:hover {
259
+ border-color: #F39C12;
260
+ color: #F39C12;
261
+ background: #fff;
262
+ }
263
+
264
+ .header {
265
+ background: white;
266
+ padding: 16px 20px;
267
+ border-radius: 8px;
268
+ margin-bottom: 16px;
269
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
270
+ }
271
+
272
+ .header h1 {
273
+ font-size: 20px;
274
+ font-weight: 600;
275
+ color: #333;
276
+ margin-bottom: 8px;
277
+ }
278
+
279
+ .header p {
280
+ font-size: 14px;
281
+ color: #666;
282
+ }
283
+
284
+ .content {
285
+ flex: 1;
286
+ background: white;
287
+ border-radius: 8px;
288
+ padding: 20px;
289
+ overflow: auto;
290
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
291
+ display: flex;
292
+ flex-direction: column;
293
+ }
294
+
295
+ .button {
296
+ padding: 10px 20px;
297
+ border: none;
298
+ border-radius: 6px;
299
+ font-size: 14px;
300
+ font-weight: 500;
301
+ cursor: pointer;
302
+ transition: all 0.2s;
303
+ }
304
+
305
+ .button-primary {
306
+ background: linear-gradient(135deg, #F39C12 0%, #E67E22 100%);
307
+ color: white;
308
+ }
309
+
310
+ .button-primary:hover:not(:disabled) {
311
+ transform: translateY(-1px);
312
+ box-shadow: 0 4px 12px rgba(243, 156, 18, 0.3);
313
+ }
314
+
315
+ .button-primary:disabled {
316
+ opacity: 0.5;
317
+ cursor: not-allowed;
318
+ }
319
+
320
+ .button-secondary {
321
+ background: white;
322
+ color: #666;
323
+ border: 1px solid #ddd;
324
+ }
325
+
326
+ .button-secondary:hover {
327
+ background: #f5f5f5;
328
+ }
329
+
330
+ .button-group {
331
+ display: flex;
332
+ gap: 8px;
333
+ margin-bottom: 16px;
334
+ }
335
+
336
+ .info-box {
337
+ background: #fff3cd;
338
+ border-left: 4px solid #F39C12;
339
+ padding: 12px 16px;
340
+ border-radius: 4px;
341
+ margin-bottom: 16px;
342
+ }
343
+
344
+ .info-box p {
345
+ font-size: 13px;
346
+ color: #856404;
347
+ margin: 4px 0;
348
+ }
349
+
350
+ /* Excel 式表格样式 */
351
+ .excel-table-container {
352
+ flex: 1;
353
+ overflow: auto;
354
+ border: 2px solid #ddd;
355
+ border-radius: 6px;
356
+ background: white;
357
+ position: relative;
358
+ user-select: none;
359
+ }
360
+
361
+ .excel-table {
362
+ border-collapse: separate;
363
+ border-spacing: 0;
364
+ table-layout: fixed;
365
+ min-width: 100%;
366
+ }
367
+
368
+ .excel-table th,
369
+ .excel-table td {
370
+ border: 1px solid #e0e0e0;
371
+ padding: 0;
372
+ position: relative;
373
+ height: 36px;
374
+ min-width: 120px;
375
+ }
376
+
377
+ .excel-table th {
378
+ background: #f5f5f5;
379
+ font-weight: 600;
380
+ font-size: 13px;
381
+ color: #666;
382
+ text-align: center;
383
+ position: sticky;
384
+ top: 0;
385
+ z-index: 10;
386
+ }
387
+
388
+ /* 行号列 */
389
+ .excel-table th:first-child,
390
+ .excel-table td:first-child {
391
+ width: 50px;
392
+ min-width: 50px;
393
+ background: #f5f5f5;
394
+ text-align: center;
395
+ font-weight: 600;
396
+ color: #666;
397
+ position: sticky;
398
+ left: 0;
399
+ z-index: 5;
400
+ }
401
+
402
+ .excel-table th:first-child {
403
+ z-index: 15;
404
+ }
405
+
406
+ /* 单元格 */
407
+ .excel-cell {
408
+ width: 100%;
409
+ height: 100%;
410
+ padding: 8px;
411
+ display: flex;
412
+ align-items: center;
413
+ cursor: cell;
414
+ position: relative;
415
+ overflow: visible;
416
+ }
417
+
418
+ .excel-cell input,
419
+ .excel-cell select,
420
+ .excel-cell textarea {
421
+ width: 100%;
422
+ border: none;
423
+ outline: none;
424
+ background: transparent;
425
+ font-size: 13px;
426
+ font-family: inherit;
427
+ padding: 0;
428
+ }
429
+
430
+ .excel-cell textarea {
431
+ resize: none;
432
+ min-height: 20px;
433
+ }
434
+
435
+ /* 选中状态 */
436
+ .excel-cell.selected {
437
+ background: #e3f2fd;
438
+ outline: 2px solid #2196F3;
439
+ outline-offset: -2px;
440
+ z-index: 1;
441
+ }
442
+
443
+ .excel-cell.range-selected {
444
+ background: #e3f2fd;
445
+ }
446
+
447
+ .excel-cell.active {
448
+ background: white;
449
+ outline: 2px solid #F39C12;
450
+ outline-offset: -2px;
451
+ z-index: 20;
452
+ }
453
+
454
+ /* 填充柄 */
455
+ .fill-handle {
456
+ position: absolute;
457
+ bottom: -3px;
458
+ right: -3px;
459
+ width: 8px;
460
+ height: 8px;
461
+ background: #F39C12;
462
+ border: 1px solid white;
463
+ cursor: crosshair;
464
+ z-index: 100;
465
+ display: none;
466
+ pointer-events: auto;
467
+ }
468
+
469
+ .excel-cell.active .fill-handle {
470
+ display: block;
471
+ }
472
+
473
+ /* 拖拽填充预览 */
474
+ .excel-cell.fill-preview {
475
+ background: #fff3e0 !important;
476
+ outline: 2px dashed #F39C12 !important;
477
+ outline-offset: -2px;
478
+ }
479
+
480
+ .column-fill-btn {
481
+ background: linear-gradient(135deg, #F39C12 0%, #E67E22 100%);
482
+ color: white;
483
+ border: none;
484
+ border-radius: 4px;
485
+ padding: 2px 6px;
486
+ font-size: 11px;
487
+ cursor: pointer;
488
+ margin-left: 4px;
489
+ transition: all 0.2s;
490
+ }
491
+
492
+ .column-fill-btn:hover {
493
+ transform: scale(1.05);
494
+ box-shadow: 0 2px 8px rgba(243, 156, 18, 0.4);
495
+ }
496
+
497
+ .th-content {
498
+ display: flex;
499
+ align-items: center;
500
+ justify-content: center;
501
+ gap: 4px;
502
+ padding: 8px;
503
+ }
504
+
505
+ /* 提示信息 */
506
+ .hint-text {
507
+ font-size: 11px;
508
+ color: #999;
509
+ margin-top: 8px;
510
+ text-align: center;
511
+ }
512
+ </style>
513
+ </head>
514
+ <body>
515
+ <div class="container">
516
+ <!-- 主内容区 -->
517
+ <div class="main-content">
518
+ <div class="header">
519
+ <h1>🎨 批量出图工具</h1>
520
+ <p>Excel式批量AI图片生成,支持拖拽填充、多选、键盘导航</p>
521
+ </div>
522
+
523
+ <div class="content">
524
+ <div class="info-box">
525
+ <p><strong>快捷键:</strong>方向键导航 | Enter编辑 | Ctrl+C复制 | Ctrl+V粘贴 | 拖拽右下角填充柄批量复制</p>
526
+ <p id="debug-info" style="color: #333;">
527
+ <strong>状态:</strong>
528
+ <span id="config-status">等待配置...</span>
529
+ </p>
530
+ </div>
531
+
532
+ <div class="button-group">
533
+ <button class="button button-secondary" onclick="addRows(5)">
534
+ ➕ 添加 5 行
535
+ </button>
536
+ <button class="button button-secondary" onclick="deleteSelected()">
537
+ 🗑️ 删除选中
538
+ </button>
539
+ <button class="button button-primary" id="generateBtn" onclick="startBatchGeneration()">
540
+ 🚀 提交到任务队列
541
+ </button>
542
+ </div>
543
+
544
+ <div class="excel-table-container" id="tableContainer">
545
+ <table class="excel-table" id="excelTable">
546
+ <thead>
547
+ <tr>
548
+ <th>#</th>
549
+ <th style="width: 300px;">
550
+ <div class="th-content">
551
+ 提示词
552
+ <button class="column-fill-btn" onclick="fillColumn('prompt')" title="批量填充提示词">⬇</button>
553
+ </div>
554
+ </th>
555
+ <th style="width: 80px;">
556
+ <div class="th-content">
557
+ 尺寸
558
+ <button class="column-fill-btn" onclick="fillColumn('size')" title="批量填充尺寸">⬇</button>
559
+ </div>
560
+ </th>
561
+ <th style="width: 150px;">
562
+ <div class="th-content">
563
+ 参考图片
564
+ <button class="column-fill-btn" onclick="fillColumn('images')" title="批量填充图片">⬇</button>
565
+ </div>
566
+ </th>
567
+ <th style="width: 60px;">
568
+ <div class="th-content">
569
+ 数量
570
+ <button class="column-fill-btn" onclick="fillColumn('count')" title="批量填充数量">⬇</button>
571
+ </div>
572
+ </th>
573
+ </tr>
574
+ </thead>
575
+ <tbody id="tableBody">
576
+ </tbody>
577
+ </table>
578
+ </div>
579
+
580
+ <p class="hint-text">
581
+ 提示:双击单元格编辑 | 拖拽填充柄批量填充 | Shift+点击多选 | 选中行后点击右侧图片库添加参考图
582
+ </p>
583
+ </div>
584
+ </div>
585
+
586
+ <!-- 图片库 -->
587
+ <div class="image-library-container" id="imageLibrary">
588
+ <div class="library-header">
589
+ <h3>图片库</h3>
590
+ <button class="toggle-btn" onclick="toggleLibrary()">◀</button>
591
+ </div>
592
+ <div class="library-content">
593
+ <!-- 上传区域 -->
594
+ <div class="upload-section">
595
+ <input type="file" id="imageUploadInput" accept="image/*" multiple onchange="handleImageUpload(event)">
596
+ <button class="upload-btn" onclick="document.getElementById('imageUploadInput').click()">
597
+ 📤 上传图片
598
+ </button>
599
+ <p class="upload-hint">支持 JPG、PNG、GIF 等格式</p>
600
+ </div>
601
+
602
+ <!-- 图片网格 -->
603
+ <div class="library-grid" id="libraryGrid">
604
+ <!-- 图片将通过 JS 动态加载 -->
605
+ </div>
606
+ </div>
607
+ </div>
608
+ </div>
609
+
610
+ <script>
611
+ // 更新调试信息(需要先定义)
612
+ function updateDebugInfo(text) {
613
+ const statusEl = document.getElementById('config-status');
614
+ if (statusEl) {
615
+ statusEl.textContent = text;
616
+ }
617
+ console.log('[BatchImage Debug]', text);
618
+ }
619
+
620
+ // ===============================
621
+ // 全局配置和状态
622
+ // ===============================
623
+ let appConfig = {
624
+ apiKey: '',
625
+ baseUrl: 'https://api.tu-zi.com/v1',
626
+ imageModel: 'gemini-2.5-flash-image-vip'
627
+ };
628
+
629
+ // 从 URL 参数获取 toolId
630
+ const urlParams = new URLSearchParams(window.location.search);
631
+ let toolId = urlParams.get('toolId') || 'batch-image-tool';
632
+
633
+ console.log('[BatchImage] Tool ID from URL:', toolId);
634
+ updateDebugInfo('工具 ID: ' + toolId);
635
+
636
+ // 任务数据
637
+ let tasks = [];
638
+ let taskIdCounter = 1;
639
+ let isGenerating = false;
640
+
641
+ // Excel 表格状态
642
+ let activeCell = null;
643
+ let selectedCells = [];
644
+ let isSelecting = false;
645
+ let isDraggingFillHandle = false;
646
+ let fillStartCell = null;
647
+ let clipboard = [];
648
+
649
+ // 图片库状态
650
+ let isLibraryCollapsed = false;
651
+ let imageLibrary = []; // 初始为空,等待用户上传
652
+
653
+ // ===============================
654
+ // 工具通信协议
655
+ // ===============================
656
+ let isInitialized = false; // 防止重复初始化
657
+
658
+ window.addEventListener('message', (event) => {
659
+ const message = event.data;
660
+
661
+ if (message && message.version === '1.0' && message.type === 'board:init') {
662
+ console.log('[BatchImage] Received init message:', message);
663
+ updateDebugInfo('收到初始化消息');
664
+
665
+ if (message.toolId) {
666
+ toolId = message.toolId;
667
+ }
668
+
669
+ if (message.payload && message.payload.config) {
670
+ const config = message.payload.config;
671
+ if (config.apiKey) {
672
+ appConfig.apiKey = config.apiKey;
673
+ updateDebugInfo('API Key 已接收 ✓');
674
+ }
675
+ if (config.baseUrl) {
676
+ appConfig.baseUrl = config.baseUrl;
677
+ }
678
+ if (config.imageModel) {
679
+ appConfig.imageModel = config.imageModel;
680
+ }
681
+ }
682
+
683
+ isInitialized = true;
684
+ // 收到 init 后不再发送 ready,避免循环
685
+ }
686
+ });
687
+
688
+ window.addEventListener('load', () => {
689
+ console.log('[BatchImage] Tool loaded');
690
+ updateDebugInfo('页面已加载,发送就绪通知');
691
+
692
+ // 初始化表格
693
+ initTable();
694
+ setupTableEvents();
695
+
696
+ // 页面加载后发送 ready,触发主应用发送 init
697
+ sendToolMessage({
698
+ type: 'tool:ready',
699
+ payload: {}
700
+ });
701
+ });
702
+
703
+ function sendToolMessage(message, timeout = 120000) {
704
+ if (message.type === 'tool:generate-image') {
705
+ return new Promise((resolve, reject) => {
706
+ const messageId = `batch-${Date.now()}-${Math.random()}`;
707
+
708
+ const toolMessage = {
709
+ version: '1.0',
710
+ type: message.type,
711
+ toolId: toolId,
712
+ messageId: messageId,
713
+ payload: {
714
+ ...message.payload,
715
+ messageId: messageId
716
+ },
717
+ timestamp: Date.now()
718
+ };
719
+
720
+ const timeoutId = setTimeout(() => {
721
+ window.removeEventListener('message', messageHandler);
722
+ reject(new Error('生成超时'));
723
+ }, timeout);
724
+
725
+ function messageHandler(event) {
726
+ if (event.data.responseId === messageId) {
727
+ clearTimeout(timeoutId);
728
+ window.removeEventListener('message', messageHandler);
729
+
730
+ if (event.data.success && event.data.result) {
731
+ resolve(event.data.result);
732
+ } else {
733
+ reject(new Error(event.data.error || '生成失败'));
734
+ }
735
+ }
736
+ }
737
+
738
+ window.addEventListener('message', messageHandler);
739
+ window.parent.postMessage(toolMessage, '*');
740
+ });
741
+ } else {
742
+ const toolMessage = {
743
+ version: '1.0',
744
+ type: message.type,
745
+ toolId: toolId,
746
+ messageId: `msg-${Date.now()}-${Math.random()}`,
747
+ payload: message.payload || {},
748
+ timestamp: Date.now()
749
+ };
750
+
751
+ window.parent.postMessage(toolMessage, '*');
752
+ }
753
+ }
754
+
755
+ // ===============================
756
+ // Excel 表格功能
757
+ // ===============================
758
+
759
+ function initTable() {
760
+ // 添加初始行
761
+ for (let i = 0; i < 10; i++) {
762
+ addRow(i === 0 ? 'A cute cat sitting on a windowsill' : '');
763
+ }
764
+ renderTable();
765
+ // 初始化图片库
766
+ renderImageLibrary();
767
+ }
768
+
769
+ function addRow(promptText = '') {
770
+ const task = {
771
+ id: taskIdCounter++,
772
+ prompt: promptText,
773
+ size: '1x1', // 修改为 API 支持的比例格式
774
+ images: [], // 参考图片数组
775
+ count: 1, // 生成数量
776
+ status: 'pending',
777
+ progress: 0,
778
+ imageUrl: null,
779
+ error: null
780
+ };
781
+ tasks.push(task);
782
+ }
783
+
784
+ function addRows(count) {
785
+ for (let i = 0; i < count; i++) {
786
+ addRow();
787
+ }
788
+ renderTable();
789
+ }
790
+
791
+ function deleteSelected() {
792
+ if (selectedCells.length === 0) {
793
+ alert('请先选择要删除的行');
794
+ return;
795
+ }
796
+
797
+ const rowsToDelete = new Set(selectedCells.map(c => c.row));
798
+ tasks = tasks.filter((_, index) => !rowsToDelete.has(index));
799
+
800
+ selectedCells = [];
801
+ activeCell = null;
802
+ renderTable();
803
+ }
804
+
805
+ function renderTable() {
806
+ const tbody = document.getElementById('tableBody');
807
+ tbody.innerHTML = '';
808
+
809
+ tasks.forEach((task, rowIndex) => {
810
+ const tr = document.createElement('tr');
811
+
812
+ // 行号
813
+ const tdIndex = document.createElement('td');
814
+ tdIndex.textContent = rowIndex + 1;
815
+ tr.appendChild(tdIndex);
816
+
817
+ // 提示词
818
+ tr.appendChild(createCell(rowIndex, 'prompt', task.prompt, 'textarea'));
819
+
820
+ // 尺寸
821
+ tr.appendChild(createCell(rowIndex, 'size', task.size, 'select'));
822
+
823
+ // 参考图片
824
+ const tdImages = document.createElement('td');
825
+ const imagesCell = createCellElement(rowIndex, 'images');
826
+ imagesCell.innerHTML = getImageCellHTML(task, rowIndex);
827
+ tdImages.appendChild(imagesCell);
828
+ tr.appendChild(tdImages);
829
+
830
+ // 数量
831
+ tr.appendChild(createCell(rowIndex, 'count', task.count || 1, 'number'));
832
+
833
+ tbody.appendChild(tr);
834
+ });
835
+ }
836
+
837
+ function createCell(rowIndex, colName, value, inputType) {
838
+ const td = document.createElement('td');
839
+ const cellDiv = createCellElement(rowIndex, colName);
840
+
841
+ if (inputType === 'textarea') {
842
+ const textarea = document.createElement('textarea');
843
+ textarea.value = value;
844
+ textarea.rows = 2;
845
+ textarea.readOnly = true;
846
+ textarea.addEventListener('blur', () => {
847
+ saveCell(rowIndex, colName, textarea.value);
848
+ });
849
+ textarea.addEventListener('keydown', (e) => handleCellKeydown(e, rowIndex, colName));
850
+ cellDiv.appendChild(textarea);
851
+ } else if (inputType === 'select') {
852
+ // 创建一个只读显示的 div
853
+ const displayDiv = document.createElement('div');
854
+ displayDiv.className = 'select-display';
855
+ displayDiv.textContent = value;
856
+ displayDiv.style.cssText = `
857
+ width: 100%;
858
+ height: 100%;
859
+ padding: 8px;
860
+ cursor: cell;
861
+ background: white;
862
+ `;
863
+
864
+ // 创建真正的 select(隐藏)
865
+ const select = document.createElement('select');
866
+ const sizes = ['1x1', '2x3', '3x2', '3x4', '4x3', '4x5', '5x4', '9x16', '16x9', '21x9'];
867
+ sizes.forEach(size => {
868
+ const option = document.createElement('option');
869
+ option.value = size;
870
+ option.textContent = size;
871
+ option.selected = value === size;
872
+ select.appendChild(option);
873
+ });
874
+ select.style.display = 'none';
875
+
876
+ select.addEventListener('change', (e) => {
877
+ console.log('Select changed to:', select.value);
878
+ displayDiv.textContent = select.value;
879
+ saveCell(rowIndex, colName, select.value);
880
+ // 隐藏 select,显示 display
881
+ select.style.display = 'none';
882
+ displayDiv.style.display = 'block';
883
+ });
884
+
885
+ select.addEventListener('blur', () => {
886
+ console.log('Select blurred');
887
+ // 隐藏 select,显示 display
888
+ select.style.display = 'none';
889
+ displayDiv.style.display = 'block';
890
+ });
891
+
892
+ // 双击时显示 select,隐藏 display
893
+ displayDiv.addEventListener('dblclick', (e) => {
894
+ e.stopPropagation();
895
+ console.log('Display div double clicked');
896
+ displayDiv.style.display = 'none';
897
+ select.style.display = 'block';
898
+ select.focus();
899
+ });
900
+
901
+ cellDiv.appendChild(displayDiv);
902
+ cellDiv.appendChild(select);
903
+ } else if (inputType === 'number') {
904
+ const input = document.createElement('input');
905
+ input.type = 'number';
906
+ input.min = '1';
907
+ input.max = '10';
908
+ input.value = value;
909
+ input.readOnly = true;
910
+ input.style.width = '50px';
911
+ input.style.textAlign = 'center';
912
+ input.addEventListener('blur', () => {
913
+ const numValue = Math.max(1, Math.min(10, parseInt(input.value) || 1));
914
+ input.value = numValue;
915
+ saveCell(rowIndex, colName, numValue);
916
+ });
917
+ input.addEventListener('keydown', (e) => handleCellKeydown(e, rowIndex, colName));
918
+ cellDiv.appendChild(input);
919
+ } else {
920
+ const input = document.createElement('input');
921
+ input.value = value;
922
+ input.readOnly = true;
923
+ input.addEventListener('blur', () => {
924
+ saveCell(rowIndex, colName, input.value);
925
+ });
926
+ input.addEventListener('keydown', (e) => handleCellKeydown(e, rowIndex, colName));
927
+ cellDiv.appendChild(input);
928
+ }
929
+
930
+ td.appendChild(cellDiv);
931
+ return td;
932
+ }
933
+
934
+ function createCellElement(rowIndex, colName) {
935
+ const cellDiv = document.createElement('div');
936
+ cellDiv.className = 'excel-cell';
937
+ cellDiv.dataset.row = rowIndex;
938
+ cellDiv.dataset.col = colName;
939
+
940
+ // 填充柄(只在可编辑列显示)
941
+ if (['prompt', 'size', 'images', 'count'].includes(colName)) {
942
+ const fillHandle = document.createElement('div');
943
+ fillHandle.className = 'fill-handle';
944
+ cellDiv.appendChild(fillHandle);
945
+
946
+ fillHandle.addEventListener('mousedown', (e) => {
947
+ e.stopPropagation();
948
+ startFillDrag(rowIndex, colName);
949
+ });
950
+ }
951
+
952
+ // 单击选中
953
+ cellDiv.addEventListener('click', (e) => handleCellClick(e, rowIndex, colName));
954
+
955
+ // 双击进入编辑
956
+ cellDiv.addEventListener('dblclick', (e) => {
957
+ e.stopPropagation();
958
+ activateCell(rowIndex, colName);
959
+ });
960
+
961
+ return cellDiv;
962
+ }
963
+
964
+ function handleCellClick(e, row, col) {
965
+ if (e.shiftKey && activeCell) {
966
+ // Shift + 点击:选择范围
967
+ selectRange(activeCell.row, activeCell.col, row, col);
968
+ } else if (e.ctrlKey || e.metaKey) {
969
+ // Ctrl + 点击:添加到选区
970
+ toggleCellSelection(row, col);
971
+ } else {
972
+ // 普通点击:单选
973
+ selectCell(row, col);
974
+ }
975
+ }
976
+
977
+ function selectCell(row, col) {
978
+ clearSelection();
979
+ activeCell = { row, col };
980
+ selectedCells = [{ row, col }];
981
+ updateCellStyles();
982
+ }
983
+
984
+ function toggleCellSelection(row, col) {
985
+ const index = selectedCells.findIndex(c => c.row === row && c.col === col);
986
+ if (index >= 0) {
987
+ selectedCells.splice(index, 1);
988
+ } else {
989
+ selectedCells.push({ row, col });
990
+ }
991
+ updateCellStyles();
992
+ }
993
+
994
+ function selectRange(startRow, startCol, endRow, endCol) {
995
+ clearSelection();
996
+ selectedCells = [];
997
+
998
+ const minRow = Math.min(startRow, endRow);
999
+ const maxRow = Math.max(startRow, endRow);
1000
+
1001
+ for (let r = minRow; r <= maxRow; r++) {
1002
+ selectedCells.push({ row: r, col: startCol });
1003
+ }
1004
+
1005
+ activeCell = { row: endRow, col: endCol };
1006
+ updateCellStyles();
1007
+ }
1008
+
1009
+ function clearSelection() {
1010
+ document.querySelectorAll('.excel-cell').forEach(cell => {
1011
+ cell.classList.remove('selected', 'active', 'range-selected');
1012
+ });
1013
+ }
1014
+
1015
+ function updateCellStyles() {
1016
+ clearSelection();
1017
+
1018
+ selectedCells.forEach(({ row, col }) => {
1019
+ const cell = document.querySelector(`.excel-cell[data-row="${row}"][data-col="${col}"]`);
1020
+ if (cell) {
1021
+ cell.classList.add('range-selected');
1022
+ }
1023
+ });
1024
+
1025
+ if (activeCell) {
1026
+ const cell = document.querySelector(`.excel-cell[data-row="${activeCell.row}"][data-col="${activeCell.col}"]`);
1027
+ if (cell) {
1028
+ cell.classList.remove('range-selected');
1029
+ cell.classList.add('active');
1030
+ }
1031
+ }
1032
+ }
1033
+
1034
+ function activateCell(row, col) {
1035
+ selectCell(row, col);
1036
+ const cell = document.querySelector(`.excel-cell[data-row="${row}"][data-col="${col}"]`);
1037
+ if (cell) {
1038
+ const input = cell.querySelector('input, textarea');
1039
+
1040
+ if (input) {
1041
+ input.readOnly = false;
1042
+ input.focus();
1043
+ if (input.select) input.select();
1044
+ }
1045
+ // select 现在通过 displayDiv 的双击事件处理
1046
+ }
1047
+ }
1048
+
1049
+ function saveCell(row, col, value) {
1050
+ console.log('=== saveCell called ===');
1051
+ console.log('Row:', row, 'Col:', col, 'Value:', value);
1052
+
1053
+ const task = tasks[row];
1054
+ if (task && col in task) {
1055
+ console.log('Before update - task[col]:', task[col]);
1056
+ task[col] = value;
1057
+ console.log('After update - task[col]:', task[col]);
1058
+ console.log('Full task:', task);
1059
+
1060
+ // 重新渲染表格以显示更新后的值
1061
+ console.log('Calling renderTable...');
1062
+ renderTable();
1063
+ } else {
1064
+ console.error('Task not found or col not in task:', { task, col });
1065
+ }
1066
+
1067
+ const cell = document.querySelector(`.excel-cell[data-row="${row}"][data-col="${col}"]`);
1068
+ if (cell) {
1069
+ const input = cell.querySelector('input, textarea');
1070
+ if (input) input.readOnly = true;
1071
+ }
1072
+ }
1073
+
1074
+ function handleCellKeydown(e, row, col) {
1075
+ const input = e.target;
1076
+
1077
+ // Enter: 保存并移动到下一行
1078
+ if (e.key === 'Enter' && !e.shiftKey) {
1079
+ e.preventDefault();
1080
+ input.blur();
1081
+ if (row < tasks.length - 1) {
1082
+ activateCell(row + 1, col);
1083
+ }
1084
+ }
1085
+ // Escape: 取消编辑
1086
+ else if (e.key === 'Escape') {
1087
+ input.blur();
1088
+ }
1089
+ // 方向键导航
1090
+ else if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key) && input.readOnly) {
1091
+ e.preventDefault();
1092
+ navigateCell(e.key, row, col);
1093
+ }
1094
+ // Ctrl+C: 复制
1095
+ else if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
1096
+ copySelection();
1097
+ }
1098
+ // Ctrl+V: 粘贴
1099
+ else if ((e.ctrlKey || e.metaKey) && e.key === 'v') {
1100
+ pasteSelection(row, col);
1101
+ }
1102
+ }
1103
+
1104
+ function navigateCell(key, row, col) {
1105
+ let newRow = row;
1106
+ let newCol = col;
1107
+
1108
+ const cols = ['prompt', 'size', 'images', 'count'];
1109
+ const colIndex = cols.indexOf(col);
1110
+
1111
+ switch (key) {
1112
+ case 'ArrowUp':
1113
+ newRow = Math.max(0, row - 1);
1114
+ break;
1115
+ case 'ArrowDown':
1116
+ newRow = Math.min(tasks.length - 1, row + 1);
1117
+ break;
1118
+ case 'ArrowLeft':
1119
+ if (colIndex > 0) newCol = cols[colIndex - 1];
1120
+ break;
1121
+ case 'ArrowRight':
1122
+ if (colIndex < cols.length - 1) newCol = cols[colIndex + 1];
1123
+ break;
1124
+ }
1125
+
1126
+ selectCell(newRow, newCol);
1127
+ }
1128
+
1129
+ function copySelection() {
1130
+ clipboard = selectedCells.map(({ row, col }) => {
1131
+ const value = tasks[row]?.[col] || '';
1132
+ return {
1133
+ col,
1134
+ // 如果是 images 列,深拷贝数组
1135
+ value: col === 'images' && Array.isArray(value) ? [...value] : value
1136
+ };
1137
+ });
1138
+ console.log('Copied:', clipboard);
1139
+ }
1140
+
1141
+ function pasteSelection(row, col) {
1142
+ if (clipboard.length === 0) return;
1143
+
1144
+ // 获取剪贴板中的第一个值(用于粘贴到所有选中的行)
1145
+ const firstClipItem = clipboard[0];
1146
+ if (!firstClipItem) return;
1147
+
1148
+ // 如果有多个选中的行,将第一个剪贴板内容粘贴到所有选中行
1149
+ if (selectedCells.length > 0) {
1150
+ // 获取所有选中的行(去重)
1151
+ const selectedRows = [...new Set(selectedCells.map(c => c.row))];
1152
+
1153
+ selectedRows.forEach(targetRow => {
1154
+ if (targetRow < tasks.length && firstClipItem.col in tasks[targetRow]) {
1155
+ // 如果是 images 列,深拷贝数组
1156
+ if (firstClipItem.col === 'images' && Array.isArray(firstClipItem.value)) {
1157
+ tasks[targetRow][firstClipItem.col] = [...firstClipItem.value];
1158
+ } else {
1159
+ tasks[targetRow][firstClipItem.col] = firstClipItem.value;
1160
+ }
1161
+ }
1162
+ });
1163
+ } else {
1164
+ // 没有选中行时,使用原来的逻辑:按顺序粘贴
1165
+ clipboard.forEach(({ col: clipCol, value }, index) => {
1166
+ const targetRow = row + index;
1167
+ if (targetRow < tasks.length && clipCol in tasks[targetRow]) {
1168
+ if (clipCol === 'images' && Array.isArray(value)) {
1169
+ tasks[targetRow][clipCol] = [...value];
1170
+ } else {
1171
+ tasks[targetRow][clipCol] = value;
1172
+ }
1173
+ }
1174
+ });
1175
+ }
1176
+
1177
+ renderTable();
1178
+ }
1179
+
1180
+ /**
1181
+ * 批量填充整列
1182
+ * @param {string} colName - 列名
1183
+ */
1184
+ function fillColumn(colName) {
1185
+ // 检查是否有选中的单元格
1186
+ if (selectedCells.length === 0 || !activeCell) {
1187
+ alert('请先选中一个单元格作为填充的源数据');
1188
+ return;
1189
+ }
1190
+
1191
+ // 获取当前活动单元格的值
1192
+ const sourceRow = activeCell.row;
1193
+ const sourceValue = tasks[sourceRow]?.[colName];
1194
+
1195
+ if (sourceValue === undefined || sourceValue === null ||
1196
+ (typeof sourceValue === 'string' && sourceValue.trim() === '') ||
1197
+ (Array.isArray(sourceValue) && sourceValue.length === 0)) {
1198
+ alert('选中的单元格没有数据,无法填充');
1199
+ return;
1200
+ }
1201
+
1202
+ // 构建显示文本
1203
+ let displayValue = '';
1204
+ if (colName === 'images') {
1205
+ displayValue = `${sourceValue.length} 张图片`;
1206
+ } else if (colName === 'prompt') {
1207
+ displayValue = sourceValue.length > 30 ? sourceValue.substring(0, 30) + '...' : sourceValue;
1208
+ } else {
1209
+ displayValue = String(sourceValue);
1210
+ }
1211
+
1212
+ const confirmMsg = `是否使用选中的「${displayValue}」填充整列?\n\n这将覆盖所有行的 ${getColumnDisplayName(colName)} 列。`;
1213
+
1214
+ if (!confirm(confirmMsg)) {
1215
+ return;
1216
+ }
1217
+
1218
+ // 填充所有行
1219
+ tasks.forEach((task, index) => {
1220
+ if (colName === 'images' && Array.isArray(sourceValue)) {
1221
+ task[colName] = [...sourceValue];
1222
+ } else {
1223
+ task[colName] = sourceValue;
1224
+ }
1225
+ });
1226
+
1227
+ renderTable();
1228
+ alert(`已将「${displayValue}」填充到所有行的 ${getColumnDisplayName(colName)} 列`);
1229
+ }
1230
+
1231
+ /**
1232
+ * 获取列的显示名称
1233
+ */
1234
+ function getColumnDisplayName(colName) {
1235
+ const names = {
1236
+ 'prompt': '提示词',
1237
+ 'size': '尺寸',
1238
+ 'images': '参考图片',
1239
+ 'count': '数量'
1240
+ };
1241
+ return names[colName] || colName;
1242
+ }
1243
+
1244
+ function startFillDrag(row, col) {
1245
+ isDraggingFillHandle = true;
1246
+ fillStartCell = { row, col };
1247
+ document.body.style.cursor = 'crosshair';
1248
+
1249
+ // 清除之前的高亮
1250
+ document.querySelectorAll('.excel-cell.fill-preview').forEach(cell => {
1251
+ cell.classList.remove('fill-preview');
1252
+ });
1253
+
1254
+ const handleMouseMove = (e) => {
1255
+ // 清除之前的高亮
1256
+ document.querySelectorAll('.excel-cell.fill-preview').forEach(cell => {
1257
+ cell.classList.remove('fill-preview');
1258
+ });
1259
+
1260
+ // 获取当前鼠标下的单元格
1261
+ const target = document.elementFromPoint(e.clientX, e.clientY);
1262
+ const cell = target?.closest('.excel-cell');
1263
+
1264
+ if (cell && cell.dataset.col === col) {
1265
+ const currentRow = parseInt(cell.dataset.row);
1266
+ const minRow = Math.min(row, currentRow);
1267
+ const maxRow = Math.max(row, currentRow);
1268
+
1269
+ // 高亮填充区域
1270
+ for (let r = minRow; r <= maxRow; r++) {
1271
+ const targetCell = document.querySelector(`.excel-cell[data-row="${r}"][data-col="${col}"]`);
1272
+ if (targetCell) {
1273
+ targetCell.classList.add('fill-preview');
1274
+ }
1275
+ }
1276
+ }
1277
+ };
1278
+
1279
+ const handleMouseUp = (e) => {
1280
+ isDraggingFillHandle = false;
1281
+ document.body.style.cursor = '';
1282
+
1283
+ // 清除高亮
1284
+ document.querySelectorAll('.excel-cell.fill-preview').forEach(cell => {
1285
+ cell.classList.remove('fill-preview');
1286
+ });
1287
+
1288
+ document.removeEventListener('mousemove', handleMouseMove);
1289
+ document.removeEventListener('mouseup', handleMouseUp);
1290
+
1291
+ // 查找结束单元格
1292
+ const target = document.elementFromPoint(e.clientX, e.clientY);
1293
+ const cell = target?.closest('.excel-cell');
1294
+ if (cell) {
1295
+ const endRow = parseInt(cell.dataset.row);
1296
+ const endCol = cell.dataset.col;
1297
+
1298
+ if (endCol === col) {
1299
+ fillCells(row, endRow, col);
1300
+ }
1301
+ }
1302
+ };
1303
+
1304
+ document.addEventListener('mousemove', handleMouseMove);
1305
+ document.addEventListener('mouseup', handleMouseUp);
1306
+ }
1307
+
1308
+ function fillCells(startRow, endRow, col) {
1309
+ const startValue = tasks[startRow][col];
1310
+ const minRow = Math.min(startRow, endRow);
1311
+ const maxRow = Math.max(startRow, endRow);
1312
+
1313
+ for (let r = minRow; r <= maxRow; r++) {
1314
+ if (r !== startRow && tasks[r]) {
1315
+ // 如果是 images 列,需要深拷贝数组
1316
+ if (col === 'images' && Array.isArray(startValue)) {
1317
+ tasks[r][col] = [...startValue];
1318
+ } else {
1319
+ tasks[r][col] = startValue;
1320
+ }
1321
+ }
1322
+ }
1323
+
1324
+ renderTable();
1325
+ }
1326
+
1327
+ function setupTableEvents() {
1328
+ // 全局键盘事件
1329
+ document.addEventListener('keydown', (e) => {
1330
+ if (!activeCell) return;
1331
+
1332
+ const { row, col } = activeCell;
1333
+ const cell = document.querySelector(`.excel-cell[data-row="${row}"][data-col="${col}"]`);
1334
+ const input = cell?.querySelector('input, textarea, select');
1335
+
1336
+ if (input && !input.readOnly) return; // 正在编辑,不处理
1337
+
1338
+ handleCellKeydown(e, row, col);
1339
+ });
1340
+ }
1341
+
1342
+ // ===============================
1343
+ // 批量生成功能
1344
+ // ===============================
1345
+
1346
+ async function startBatchGeneration() {
1347
+ if (!appConfig.apiKey) {
1348
+ alert('未检测到 API Key,请先在 aitu 中配置 API Key');
1349
+ return;
1350
+ }
1351
+
1352
+ if (isGenerating) {
1353
+ alert('正在提交任务中...');
1354
+ return;
1355
+ }
1356
+
1357
+ // 获取所有有提示词的行
1358
+ const validTasks = tasks.filter(t => t.prompt && t.prompt.trim() !== '');
1359
+ if (validTasks.length === 0) {
1360
+ alert('请至少填写一行提示词');
1361
+ return;
1362
+ }
1363
+
1364
+ isGenerating = true;
1365
+ const generateBtn = document.getElementById('generateBtn');
1366
+ generateBtn.disabled = true;
1367
+ generateBtn.textContent = '⏳ 提交中...';
1368
+
1369
+ // 收集所有需要提交的子任务
1370
+ const allSubTasks = [];
1371
+ const globalBatchTimestamp = Date.now();
1372
+ let subTaskCounter = 0;
1373
+
1374
+ for (const task of validTasks) {
1375
+ const generateCount = task.count || 1;
1376
+ // 使用全局时间戳 + 任务ID 确保 batchId 唯一
1377
+ const batchId = `batch_${task.id}_${globalBatchTimestamp}`;
1378
+
1379
+ // 将图片转换为 uploadedImages 格式
1380
+ const uploadedImages = (task.images || []).map((url, index) => ({
1381
+ type: 'url',
1382
+ url: url,
1383
+ name: `reference_${index + 1}`
1384
+ }));
1385
+
1386
+ // 为每个 count 创建子任务
1387
+ for (let i = 0; i < generateCount; i++) {
1388
+ subTaskCounter++;
1389
+ allSubTasks.push({
1390
+ task,
1391
+ batchId,
1392
+ batchIndex: i + 1,
1393
+ batchTotal: generateCount,
1394
+ uploadedImages,
1395
+ globalIndex: subTaskCounter
1396
+ });
1397
+ }
1398
+ }
1399
+
1400
+ // 提交所有子任务到全局队列
1401
+ let submittedCount = 0;
1402
+ for (const subTask of allSubTasks) {
1403
+ try {
1404
+ const messageId = `batch-${Date.now()}-${Math.random()}`;
1405
+
1406
+ const toolMessage = {
1407
+ version: '1.0',
1408
+ type: 'tool:generate-image',
1409
+ toolId: toolId,
1410
+ messageId: messageId,
1411
+ payload: {
1412
+ prompt: subTask.task.prompt,
1413
+ size: subTask.task.size,
1414
+ uploadedImages: subTask.uploadedImages,
1415
+ aspectRatio: 'auto',
1416
+ batchId: subTask.batchId,
1417
+ batchIndex: subTask.batchIndex,
1418
+ batchTotal: subTask.batchTotal,
1419
+ globalIndex: subTask.globalIndex,
1420
+ messageId: messageId
1421
+ },
1422
+ timestamp: Date.now()
1423
+ };
1424
+
1425
+ window.parent.postMessage(toolMessage, '*');
1426
+ submittedCount++;
1427
+ console.log(`[BatchImage] Submitted task ${subTask.globalIndex}/${allSubTasks.length}`);
1428
+ } catch (error) {
1429
+ console.error(`[BatchImage] Failed to submit task:`, error);
1430
+ }
1431
+ }
1432
+
1433
+ // 提交完成
1434
+ isGenerating = false;
1435
+ generateBtn.disabled = false;
1436
+ generateBtn.textContent = '🚀 提交到任务队列';
1437
+ alert(`已提交 ${submittedCount} 个任务到全局队列,请在左下角任务队列查看进度`);
1438
+ }
1439
+
1440
+ // ===============================
1441
+ // 图片库功能
1442
+ // ===============================
1443
+
1444
+ /**
1445
+ * 渲染图片库
1446
+ */
1447
+ function renderImageLibrary() {
1448
+ const libraryGrid = document.getElementById('libraryGrid');
1449
+ libraryGrid.innerHTML = '';
1450
+
1451
+ if (imageLibrary.length === 0) {
1452
+ libraryGrid.innerHTML = '<div class="empty-library">暂无图片<br>请点击上方按钮上传</div>';
1453
+ return;
1454
+ }
1455
+
1456
+ imageLibrary.forEach((imageUrl, index) => {
1457
+ const imageItem = document.createElement('div');
1458
+ imageItem.className = 'library-image';
1459
+ imageItem.innerHTML = `
1460
+ <img src="${imageUrl}" alt="Image ${index + 1}">
1461
+ <button class="delete-image-btn" onclick="deleteLibraryImage(${index})">×</button>
1462
+ `;
1463
+
1464
+ // 点击图片添加到选中的行
1465
+ imageItem.querySelector('img').addEventListener('click', () => {
1466
+ onLibraryImageClick(imageUrl);
1467
+ });
1468
+
1469
+ libraryGrid.appendChild(imageItem);
1470
+ });
1471
+ }
1472
+
1473
+ /**
1474
+ * 处理图片上传
1475
+ */
1476
+ function handleImageUpload(event) {
1477
+ const files = event.target.files;
1478
+ if (!files || files.length === 0) return;
1479
+
1480
+ Array.from(files).forEach(file => {
1481
+ if (!file.type.startsWith('image/')) {
1482
+ console.warn('跳过非图片文件:', file.name);
1483
+ return;
1484
+ }
1485
+
1486
+ const reader = new FileReader();
1487
+ reader.onload = (e) => {
1488
+ const dataUrl = e.target.result;
1489
+ imageLibrary.push(dataUrl);
1490
+ renderImageLibrary();
1491
+ };
1492
+ reader.readAsDataURL(file);
1493
+ });
1494
+
1495
+ // 清空 input 以允许重复上传同一文件
1496
+ event.target.value = '';
1497
+ }
1498
+
1499
+ /**
1500
+ * 删除图片库中的图片
1501
+ */
1502
+ function deleteLibraryImage(index) {
1503
+ if (confirm('确定要删除这张图片吗?')) {
1504
+ imageLibrary.splice(index, 1);
1505
+ renderImageLibrary();
1506
+ }
1507
+ }
1508
+
1509
+ /**
1510
+ * 切换图片库显示/隐藏
1511
+ */
1512
+ function toggleLibrary() {
1513
+ const library = document.getElementById('imageLibrary');
1514
+ isLibraryCollapsed = !isLibraryCollapsed;
1515
+
1516
+ if (isLibraryCollapsed) {
1517
+ library.classList.add('collapsed');
1518
+ } else {
1519
+ library.classList.remove('collapsed');
1520
+ }
1521
+ }
1522
+
1523
+ /**
1524
+ * 点击图片库中的图片
1525
+ */
1526
+ function onLibraryImageClick(imageUrl) {
1527
+ // 检查是否有选中的行
1528
+ if (selectedCells.length === 0) {
1529
+ alert('请先选中要添加参考图片的行');
1530
+ return;
1531
+ }
1532
+
1533
+ // 获取选中的行(去重)
1534
+ const selectedRows = [...new Set(selectedCells.map(c => c.row))];
1535
+
1536
+ // 为每个选中的行添加图片
1537
+ selectedRows.forEach(rowIndex => {
1538
+ if (tasks[rowIndex]) {
1539
+ // 检查是否已添加该图片
1540
+ if (!tasks[rowIndex].images.includes(imageUrl)) {
1541
+ tasks[rowIndex].images.push(imageUrl);
1542
+ }
1543
+ }
1544
+ });
1545
+
1546
+ // 重新渲染表格
1547
+ renderTable();
1548
+ }
1549
+
1550
+ /**
1551
+ * 获取图片单元格的 HTML
1552
+ */
1553
+ function getImageCellHTML(task, rowIndex) {
1554
+ const images = task.images || [];
1555
+
1556
+ let html = '<div class="image-cell-content">';
1557
+
1558
+ // 渲染已添加的图片
1559
+ images.forEach(imageUrl => {
1560
+ html += `
1561
+ <div class="cell-image-thumb">
1562
+ <img src="${imageUrl}" alt="Reference">
1563
+ <button class="remove-btn" onclick="removeImageFromCell(${rowIndex}, '${imageUrl}')">×</button>
1564
+ </div>
1565
+ `;
1566
+ });
1567
+
1568
+ // 添加"添加图片"按钮
1569
+ html += `
1570
+ <div class="add-image-btn" onclick="showLibraryForRow(${rowIndex})">
1571
+ +
1572
+ </div>
1573
+ `;
1574
+
1575
+ // 添加一个填充占位符,确保右下角有空间显示填充柄
1576
+ html += '<div style="flex: 1; min-height: 12px;"></div>';
1577
+
1578
+ html += '</div>';
1579
+ return html;
1580
+ }
1581
+
1582
+ /**
1583
+ * 从单元格移除图片
1584
+ */
1585
+ function removeImageFromCell(rowIndex, imageUrl) {
1586
+ if (tasks[rowIndex]) {
1587
+ const imageIndex = tasks[rowIndex].images.indexOf(imageUrl);
1588
+ if (imageIndex > -1) {
1589
+ tasks[rowIndex].images.splice(imageIndex, 1);
1590
+ renderTable();
1591
+ }
1592
+ }
1593
+ }
1594
+
1595
+ /**
1596
+ * 点击"+"按钮时选中该行并提示
1597
+ */
1598
+ function showLibraryForRow(rowIndex) {
1599
+ // 选中该行
1600
+ selectCell(rowIndex, 'images');
1601
+
1602
+ // 确保图片库展开
1603
+ const library = document.getElementById('imageLibrary');
1604
+ if (isLibraryCollapsed) {
1605
+ toggleLibrary();
1606
+ }
1607
+
1608
+ // 滚动到图片库顶部
1609
+ const libraryContent = library.querySelector('.library-content');
1610
+ if (libraryContent) {
1611
+ libraryContent.scrollTop = 0;
1612
+ }
1613
+ }
1614
+ </script>
1615
+ </body>
1616
+ </html>