index.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. <!-- dall3 -->
  2. <template>
  3. <div class="prompt">
  4. <el-text tag="b">画面描述</el-text>
  5. <el-text tag="p">建议使用“形容词+动词+风格”的格式,使用“,”隔开</el-text>
  6. <!-- TODO @fan:style 看看能不能哟 unocss 替代 -->
  7. <el-input
  8. v-model="prompt"
  9. maxlength="1024"
  10. rows="5"
  11. style="width: 100%; margin-top: 15px;"
  12. input-style="border-radius: 7px;"
  13. placeholder="例如:童话里的小屋应该是什么样子?"
  14. show-word-limit
  15. type="textarea"
  16. />
  17. </div>
  18. <div class="hot-words">
  19. <div>
  20. <el-text tag="b">随机热词</el-text>
  21. </div>
  22. <el-space wrap class="word-list">
  23. <el-button round
  24. class="btn"
  25. :type="(selectHotWord === hotWord ? 'primary' : 'default')"
  26. v-for="hotWord in hotWords"
  27. :key="hotWord"
  28. @click="handleHotWordClick(hotWord)"
  29. >
  30. {{ hotWord }}
  31. </el-button>
  32. </el-space>
  33. </div>
  34. <div class="model">
  35. <div>
  36. <el-text tag="b">模型选择</el-text>
  37. </div>
  38. <el-space wrap class="model-list">
  39. <div
  40. :class="selectModel === model.key ? 'modal-item selectModel' : 'modal-item'"
  41. v-for="model in models"
  42. :key="model.key"
  43. >
  44. <el-image
  45. :src="model.image"
  46. fit="contain"
  47. @click="handleModelClick(model)"
  48. />
  49. <div class="model-font">{{model.name}}</div>
  50. </div>
  51. </el-space>
  52. </div>
  53. <div class="image-style">
  54. <div>
  55. <el-text tag="b">风格选择</el-text>
  56. </div>
  57. <el-space wrap class="image-style-list">
  58. <div
  59. :class="selectImageStyle === imageStyle.key ? 'image-style-item selectImageStyle' : 'image-style-item'"
  60. v-for="imageStyle in imageStyleList"
  61. :key="imageStyle.key"
  62. >
  63. <el-image
  64. :src="imageStyle.image"
  65. fit="contain"
  66. @click="handleStyleClick(imageStyle)"
  67. />
  68. <div class="style-font">{{imageStyle.name}}</div>
  69. </div>
  70. </el-space>
  71. </div>
  72. <div class="image-size">
  73. <div>
  74. <el-text tag="b">画面比例</el-text>
  75. </div>
  76. <el-space wrap class="size-list">
  77. <div class="size-item"
  78. v-for="imageSize in imageSizeList"
  79. :key="imageSize.key"
  80. @click="handleSizeClick(imageSize)">
  81. <div :class="selectImageSize === imageSize.key ? 'size-wrapper selectImageSize' : 'size-wrapper'">
  82. <div :style="imageSize.style"></div>
  83. </div>
  84. <div class="size-font">{{ imageSize.name }}</div>
  85. </div>
  86. </el-space>
  87. </div>
  88. <div class="btns">
  89. <el-button type="primary"
  90. size="large"
  91. round
  92. :loading="drawIn"
  93. @click="handleGenerateImage">
  94. {{drawIn ? '生成中' : '生成内容'}}
  95. </el-button>
  96. </div>
  97. </template>
  98. <script setup lang="ts">
  99. import {ImageApi, ImageDrawReqVO, ImageVO} from '@/api/ai/image';
  100. // image 模型
  101. interface ImageModelVO {
  102. key: string
  103. name: string
  104. image: string
  105. }
  106. // image 大小
  107. interface ImageSizeVO {
  108. key: string
  109. name: string,
  110. style: string,
  111. width: string,
  112. height: string,
  113. }
  114. // 定义属性
  115. const prompt = ref<string>('') // 提示词
  116. const drawIn = ref<boolean>(false) // 生成中
  117. const selectHotWord = ref<string>('') // 选中的热词
  118. const hotWords = ref<string[]>(['中国旗袍', '古装美女', '卡通头像', '机甲战士', '童话小屋', '中国长城']) // 热词
  119. const selectModel = ref<string>('dall-e-3') // 模型
  120. // message
  121. const message = useMessage()
  122. const models = ref<ImageModelVO[]>([
  123. {
  124. key: 'dall-e-3',
  125. name: 'DALL·E 3',
  126. image: `/src/assets/ai/dall2.jpg`,
  127. },
  128. {
  129. key: 'dall-e-2',
  130. name: 'DALL·E 2',
  131. image: `/src/assets/ai/dall3.jpg`,
  132. },
  133. ]) // 模型
  134. const selectImageStyle = ref<string>('vivid') // style 样式
  135. const imageStyleList = ref<ImageModelVO[]>([
  136. {
  137. key: 'vivid',
  138. name: '清晰',
  139. image: `/src/assets/ai/qingxi.jpg`,
  140. },
  141. {
  142. key: 'natural',
  143. name: '自然',
  144. image: `/src/assets/ai/ziran.jpg`,
  145. },
  146. ]) // style
  147. const selectImageSize = ref<string>('1024x1024') // 选中 size
  148. const imageSizeList = ref<ImageSizeVO[]>([
  149. {
  150. key: '1024x1024',
  151. name: '1:1',
  152. width: '1024',
  153. height: '1024',
  154. style: 'width: 30px; height: 30px;background-color: #dcdcdc;',
  155. },
  156. {
  157. key: '1024x1792',
  158. name: '3:5',
  159. width: '1024',
  160. height: '1792',
  161. style: 'width: 30px; height: 50px;background-color: #dcdcdc;',
  162. },
  163. {
  164. key: '1792x1024',
  165. name: '5:3',
  166. width: '1792',
  167. height: '1024',
  168. style: 'width: 50px; height: 30px;background-color: #dcdcdc;',
  169. }
  170. ]) // size
  171. // 定义 Props
  172. const props = defineProps({})
  173. // 定义 emits
  174. const emits = defineEmits(['onDrawStart', 'onDrawComplete'])
  175. /** 热词 - click */
  176. const handleHotWordClick = async (hotWord: string) => {
  177. // 取消选中
  178. if (selectHotWord.value == hotWord) {
  179. selectHotWord.value = ''
  180. return
  181. }
  182. // 选中
  183. selectHotWord.value = hotWord
  184. // 替换提示词
  185. prompt.value = hotWord
  186. }
  187. /** 模型 - click */
  188. const handleModelClick = async (model: ImageModelVO) => {
  189. selectModel.value = model.key
  190. }
  191. /** 样式 - click */
  192. const handleStyleClick = async (imageStyle: ImageModelVO) => {
  193. selectImageStyle.value = imageStyle.key
  194. }
  195. /** size - click */
  196. const handleSizeClick = async (imageSize: ImageSizeVO) => {
  197. selectImageSize.value = imageSize.key
  198. }
  199. /** 图片生产 */
  200. const handleGenerateImage = async () => {
  201. // 二次确认
  202. await message.confirm(`确认生成内容?`)
  203. try {
  204. // 加载中
  205. drawIn.value = true
  206. // 回调
  207. emits('onDrawStart', selectModel.value)
  208. const imageSize = imageSizeList.value.find(item => item.key === selectImageSize.value) as ImageSizeVO
  209. const form = {
  210. platform: 'OpenAI',
  211. prompt: prompt.value, // 提示词
  212. model: selectModel.value, // 模型
  213. width: imageSize.width, // size 不能为空
  214. height: imageSize.height, // size 不能为空
  215. options: {
  216. style: selectImageStyle.value, // 图像生成的风格
  217. }
  218. } as ImageDrawReqVO
  219. // 发送请求
  220. await ImageApi.drawImage(form)
  221. } finally {
  222. // 回调
  223. emits('onDrawComplete', selectModel.value)
  224. // 加载结束
  225. drawIn.value = false
  226. }
  227. }
  228. /** 填充值 */
  229. const settingValues = async (imageDetail: ImageVO) => {
  230. prompt.value = imageDetail.prompt
  231. selectModel.value = imageDetail.model
  232. //
  233. selectImageStyle.value = imageDetail.options?.style
  234. //
  235. const imageSize = imageSizeList.value.find(item => item.key === `${imageDetail.width}x${imageDetail.height}`) as ImageSizeVO
  236. await handleSizeClick(imageSize)
  237. }
  238. /** 暴露组件方法 */
  239. defineExpose({ settingValues })
  240. </script>
  241. <style scoped lang="scss">
  242. // 提示词
  243. .prompt {
  244. }
  245. // 热词
  246. .hot-words {
  247. display: flex;
  248. flex-direction: column;
  249. margin-top: 30px;
  250. .word-list {
  251. display: flex;
  252. flex-direction: row;
  253. flex-wrap: wrap;
  254. justify-content: start;
  255. margin-top: 15px;
  256. .btn {
  257. margin: 0;
  258. }
  259. }
  260. }
  261. // 模型
  262. .model {
  263. margin-top: 30px;
  264. .model-list {
  265. margin-top: 15px;
  266. .modal-item {
  267. width: 110px;
  268. //outline: 1px solid blue;
  269. overflow: hidden;
  270. display: flex;
  271. flex-direction: column;
  272. align-items: center;
  273. border: 3px solid transparent;
  274. cursor: pointer;
  275. .model-font {
  276. font-size: 14px;
  277. color: #3e3e3e;
  278. font-weight: bold;
  279. }
  280. }
  281. .selectModel {
  282. border: 3px solid #1293ff;
  283. border-radius: 5px;
  284. }
  285. }
  286. }
  287. // 样式 style
  288. .image-style {
  289. margin-top: 30px;
  290. .image-style-list {
  291. margin-top: 15px;
  292. .image-style-item {
  293. width: 110px;
  294. //outline: 1px solid blue;
  295. overflow: hidden;
  296. display: flex;
  297. flex-direction: column;
  298. align-items: center;
  299. border: 3px solid transparent;
  300. cursor: pointer;
  301. .style-font {
  302. font-size: 14px;
  303. color: #3e3e3e;
  304. font-weight: bold;
  305. }
  306. }
  307. .selectImageStyle {
  308. border: 3px solid #1293ff;
  309. border-radius: 5px;
  310. }
  311. }
  312. }
  313. // 尺寸
  314. .image-size {
  315. width: 100%;
  316. margin-top: 30px;
  317. .size-list {
  318. display: flex;
  319. flex-direction: row;
  320. justify-content: space-between;
  321. width: 100%;
  322. margin-top: 20px;
  323. .size-item {
  324. display: flex;
  325. flex-direction: column;
  326. align-items: center;
  327. cursor: pointer;
  328. .size-wrapper {
  329. display: flex;
  330. flex-direction: column;
  331. align-items: center;
  332. justify-content: center;
  333. border-radius: 7px;
  334. padding: 4px;
  335. width: 50px;
  336. height: 50px;
  337. background-color: #fff;
  338. border: 1px solid #fff;
  339. }
  340. .size-font {
  341. font-size: 14px;
  342. color: #3e3e3e;
  343. font-weight: bold;
  344. }
  345. }
  346. }
  347. .selectImageSize {
  348. border: 1px solid #1293ff !important;
  349. }
  350. }
  351. .btns {
  352. display: flex;
  353. justify-content: center;
  354. margin-top: 50px;
  355. }
  356. </style>