index.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  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="handlerHotWordClick(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="handlerModelClick(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="handlerStyleClick(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="handlerSizeClick(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="handlerGenerateImage">
  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. // TODO @fan:image 改成项目里自己的哈
  123. // TODO @fan:这个 image,要不看看网上有没合适的图片,作为占位符,啊哈哈
  124. const models = ref<ImageModelVO[]>([
  125. {
  126. key: 'dall-e-3',
  127. name: 'DALL·E 3',
  128. image: 'https://h5.cxyhub.com/images/model_2.png',
  129. },
  130. {
  131. key: 'dall-e-2',
  132. name: 'DALL·E 2',
  133. image: 'https://h5.cxyhub.com/images/model_1.png',
  134. },
  135. ]) // 模型
  136. const selectImageStyle = ref<string>('vivid') // style 样式
  137. // TODO @fan:image 改成项目里自己的哈
  138. const imageStyleList = ref<ImageModelVO[]>([
  139. {
  140. key: 'vivid',
  141. name: '清晰',
  142. image: 'https://h5.cxyhub.com/images/model_1.png',
  143. },
  144. {
  145. key: 'natural',
  146. name: '自然',
  147. image: 'https://h5.cxyhub.com/images/model_2.png',
  148. },
  149. ]) // style
  150. const selectImageSize = ref<string>('1024x1024') // 选中 size
  151. const imageSizeList = ref<ImageSizeVO[]>([
  152. {
  153. key: '1024x1024',
  154. name: '1:1',
  155. width: '1024',
  156. height: '1024',
  157. style: 'width: 30px; height: 30px;background-color: #dcdcdc;',
  158. },
  159. {
  160. key: '1024x1792',
  161. name: '3:5',
  162. width: '1024',
  163. height: '1792',
  164. style: 'width: 30px; height: 50px;background-color: #dcdcdc;',
  165. },
  166. {
  167. key: '1792x1024',
  168. name: '5:3',
  169. width: '1792',
  170. height: '1024',
  171. style: 'width: 50px; height: 30px;background-color: #dcdcdc;',
  172. }
  173. ]) // size
  174. // 定义 Props
  175. const props = defineProps({})
  176. // 定义 emits
  177. const emits = defineEmits(['onDrawStart', 'onDrawComplete'])
  178. // TODO @fan:如果是简单注释,建议用 /** */,主要是现在项目里是这种风格哈,保持一致好点~
  179. // TODO @fan:handler 应该改成 handle 哈
  180. /** 热词 - click */
  181. const handlerHotWordClick = async (hotWord: string) => {
  182. // 取消选中
  183. if (selectHotWord.value == hotWord) {
  184. selectHotWord.value = ''
  185. return
  186. }
  187. // 选中
  188. selectHotWord.value = hotWord
  189. // 替换提示词
  190. prompt.value = hotWord
  191. }
  192. /** 模型 - click */
  193. const handlerModelClick = async (model: ImageModelVO) => {
  194. selectModel.value = model.key
  195. }
  196. /** 样式 - click */
  197. const handlerStyleClick = async (imageStyle: ImageModelVO) => {
  198. selectImageStyle.value = imageStyle.key
  199. }
  200. /** size - click */
  201. const handlerSizeClick = async (imageSize: ImageSizeVO) => {
  202. selectImageSize.value = imageSize.key
  203. }
  204. /** 图片生产 */
  205. const handlerGenerateImage = async () => {
  206. // 二次确认
  207. await message.confirm(`确认生成内容?`)
  208. try {
  209. // 加载中
  210. drawIn.value = true
  211. // 回调
  212. emits('onDrawStart', selectModel.value)
  213. const imageSize = imageSizeList.value.find(item => item.key === selectImageSize.value) as ImageSizeVO
  214. const form = {
  215. platform: 'OpenAI',
  216. prompt: prompt.value, // 提示词
  217. model: selectModel.value, // 模型
  218. width: imageSize.width, // size 不能为空
  219. height: imageSize.height, // size 不能为空
  220. options: {
  221. style: selectImageStyle.value, // 图像生成的风格
  222. }
  223. } as ImageDrawReqVO
  224. // 发送请求
  225. await ImageApi.drawImage(form)
  226. } finally {
  227. // 回调
  228. emits('onDrawComplete', selectModel.value)
  229. // 加载结束
  230. drawIn.value = false
  231. }
  232. }
  233. /** 填充值 */
  234. const settingValues = async (imageDetail: ImageVO) => {
  235. prompt.value = imageDetail.prompt
  236. selectModel.value = imageDetail.model
  237. //
  238. selectImageStyle.value = imageDetail.options?.style
  239. //
  240. const imageSize = imageSizeList.value.find(item => item.key === `${imageDetail.width}x${imageDetail.height}`) as ImageSizeVO
  241. console.log('imageSize', imageSize)
  242. await handlerSizeClick(imageSize)
  243. }
  244. /** 暴露组件方法 */
  245. defineExpose({ settingValues })
  246. </script>
  247. <style scoped lang="scss">
  248. // 提示词
  249. .prompt {
  250. }
  251. // 热词
  252. .hot-words {
  253. display: flex;
  254. flex-direction: column;
  255. margin-top: 30px;
  256. .word-list {
  257. display: flex;
  258. flex-direction: row;
  259. flex-wrap: wrap;
  260. justify-content: start;
  261. margin-top: 15px;
  262. .btn {
  263. margin: 0;
  264. }
  265. }
  266. }
  267. // 模型
  268. .model {
  269. margin-top: 30px;
  270. .model-list {
  271. margin-top: 15px;
  272. .modal-item {
  273. width: 110px;
  274. //outline: 1px solid blue;
  275. overflow: hidden;
  276. display: flex;
  277. flex-direction: column;
  278. align-items: center;
  279. border: 3px solid transparent;
  280. cursor: pointer;
  281. .model-font {
  282. font-size: 14px;
  283. color: #3e3e3e;
  284. font-weight: bold;
  285. }
  286. }
  287. .selectModel {
  288. border: 3px solid #1293ff;
  289. border-radius: 5px;
  290. }
  291. }
  292. }
  293. // 样式 style
  294. .image-style {
  295. margin-top: 30px;
  296. .image-style-list {
  297. margin-top: 15px;
  298. .image-style-item {
  299. width: 110px;
  300. //outline: 1px solid blue;
  301. overflow: hidden;
  302. display: flex;
  303. flex-direction: column;
  304. align-items: center;
  305. border: 3px solid transparent;
  306. cursor: pointer;
  307. .style-font {
  308. font-size: 14px;
  309. color: #3e3e3e;
  310. font-weight: bold;
  311. }
  312. }
  313. .selectImageStyle {
  314. border: 3px solid #1293ff;
  315. border-radius: 5px;
  316. }
  317. }
  318. }
  319. // 尺寸
  320. .image-size {
  321. width: 100%;
  322. margin-top: 30px;
  323. .size-list {
  324. display: flex;
  325. flex-direction: row;
  326. justify-content: space-between;
  327. width: 100%;
  328. margin-top: 20px;
  329. .size-item {
  330. display: flex;
  331. flex-direction: column;
  332. align-items: center;
  333. cursor: pointer;
  334. .size-wrapper {
  335. display: flex;
  336. flex-direction: column;
  337. align-items: center;
  338. justify-content: center;
  339. border-radius: 7px;
  340. padding: 4px;
  341. width: 50px;
  342. height: 50px;
  343. background-color: #fff;
  344. border: 1px solid #fff;
  345. }
  346. .size-font {
  347. font-size: 14px;
  348. color: #3e3e3e;
  349. font-weight: bold;
  350. }
  351. }
  352. }
  353. .selectImageSize {
  354. border: 1px solid #1293ff !important;
  355. }
  356. }
  357. .btns {
  358. display: flex;
  359. justify-content: center;
  360. margin-top: 50px;
  361. }
  362. </style>