index.vue 9.4 KB

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