index.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  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
  24. round
  25. class="btn"
  26. :type="selectHotWord === hotWord ? 'primary' : 'default'"
  27. v-for="hotWord in hotWords"
  28. :key="hotWord"
  29. @click="handleHotWordClick(hotWord)"
  30. >
  31. {{ hotWord }}
  32. </el-button>
  33. </el-space>
  34. </div>
  35. <div class="group-item">
  36. <div>
  37. <el-text tag="b">采样方法</el-text>
  38. </div>
  39. <el-space wrap class="group-item-body">
  40. <el-select v-model="selectSampler" placeholder="Select" size="large" style="width: 350px">
  41. <el-option v-for="item in sampler" :key="item.key" :label="item.name" :value="item.key" />
  42. </el-select>
  43. </el-space>
  44. </div>
  45. <div class="group-item">
  46. <div>
  47. <el-text tag="b">CLIP</el-text>
  48. </div>
  49. <el-space wrap class="group-item-body">
  50. <el-select
  51. v-model="selectClipGuidancePreset"
  52. placeholder="Select"
  53. size="large"
  54. style="width: 350px"
  55. >
  56. <el-option
  57. v-for="item in clipGuidancePresets"
  58. :key="item.key"
  59. :label="item.name"
  60. :value="item.key"
  61. />
  62. </el-select>
  63. </el-space>
  64. </div>
  65. <div class="group-item">
  66. <div>
  67. <el-text tag="b">风格</el-text>
  68. </div>
  69. <el-space wrap class="group-item-body">
  70. <el-select v-model="selectStylePreset" placeholder="Select" size="large" style="width: 350px">
  71. <el-option
  72. v-for="item in stylePresets"
  73. :key="item.key"
  74. :label="item.name"
  75. :value="item.key"
  76. />
  77. </el-select>
  78. </el-space>
  79. </div>
  80. <div class="group-item">
  81. <div>
  82. <el-text tag="b">图片尺寸</el-text>
  83. </div>
  84. <el-space wrap class="group-item-body">
  85. <el-input v-model="imageWidth" style="width: 170px" placeholder="图片宽度" />
  86. <el-input v-model="imageHeight" style="width: 170px" placeholder="图片高度" />
  87. </el-space>
  88. </div>
  89. <div class="group-item">
  90. <div>
  91. <el-text tag="b">迭代步数</el-text>
  92. </div>
  93. <el-space wrap class="group-item-body">
  94. <el-input
  95. v-model="steps"
  96. type="number"
  97. size="large"
  98. style="width: 350px"
  99. placeholder="Please input"
  100. />
  101. </el-space>
  102. </div>
  103. <div class="group-item">
  104. <div>
  105. <el-text tag="b">引导系数</el-text>
  106. </div>
  107. <el-space wrap class="group-item-body">
  108. <el-input
  109. v-model="scale"
  110. type="number"
  111. size="large"
  112. style="width: 350px"
  113. placeholder="Please input"
  114. />
  115. </el-space>
  116. </div>
  117. <div class="group-item">
  118. <div>
  119. <el-text tag="b">随机因子</el-text>
  120. </div>
  121. <el-space wrap class="group-item-body">
  122. <el-input
  123. v-model="seed"
  124. type="number"
  125. size="large"
  126. style="width: 350px"
  127. placeholder="Please input"
  128. />
  129. </el-space>
  130. </div>
  131. <div class="btns">
  132. <el-button type="primary" size="large" round :loading="drawIn" @click="handleGenerateImage">
  133. {{ drawIn ? '生成中' : '生成内容' }}
  134. </el-button>
  135. </div>
  136. </template>
  137. <script setup lang="ts">
  138. import { ImageApi, ImageDrawReqVO, ImageVO } from '@/api/ai/image'
  139. import { hasChinese } from '@/views/ai/utils/utils'
  140. // image 模型
  141. interface ImageModelVO {
  142. key: string
  143. name: string
  144. }
  145. // 定义属性
  146. const prompt = ref<string>('') // 提示词
  147. const drawIn = ref<boolean>(false) // 生成中
  148. const selectHotWord = ref<string>('') // 选中的热词
  149. const imageWidth = ref<number>(512) // 图片宽度
  150. const imageHeight = ref<number>(512) // 图片高度
  151. const hotWords = ref<string[]>([
  152. '中国旗袍',
  153. '古装美女',
  154. '卡通头像',
  155. '机甲战士',
  156. '童话小屋',
  157. '中国长城'
  158. ]) // 热词
  159. // message
  160. const message = useMessage()
  161. // 采样方法
  162. const selectSampler = ref<string>('DDIM') // 模型
  163. // DDIM DDPM K_DPMPP_2M K_DPMPP_2S_ANCESTRAL K_DPM_2 K_DPM_2_ANCESTRAL K_EULER K_EULER_ANCESTRAL K_HEUN K_LMS
  164. const sampler = ref<ImageModelVO[]>([
  165. {
  166. key: 'DDIM',
  167. name: 'DDIM'
  168. },
  169. {
  170. key: 'DDPM',
  171. name: 'DDPM'
  172. },
  173. {
  174. key: 'K_DPMPP_2M',
  175. name: 'K_DPMPP_2M'
  176. },
  177. {
  178. key: 'K_DPMPP_2S_ANCESTRAL',
  179. name: 'K_DPMPP_2S_ANCESTRAL'
  180. },
  181. {
  182. key: 'K_DPM_2',
  183. name: 'K_DPM_2'
  184. },
  185. {
  186. key: 'K_DPM_2_ANCESTRAL',
  187. name: 'K_DPM_2_ANCESTRAL'
  188. },
  189. {
  190. key: 'K_EULER',
  191. name: 'K_EULER'
  192. },
  193. {
  194. key: 'K_EULER_ANCESTRAL',
  195. name: 'K_EULER_ANCESTRAL'
  196. },
  197. {
  198. key: 'K_HEUN',
  199. name: 'K_HEUN'
  200. },
  201. {
  202. key: 'K_LMS',
  203. name: 'K_LMS'
  204. }
  205. ])
  206. // 风格
  207. // 3d-model analog-film anime cinematic comic-book digital-art enhance fantasy-art isometric
  208. // line-art low-poly modeling-compound neon-punk origami photographic pixel-art tile-texture
  209. const selectStylePreset = ref<string>('3d-model') // 模型
  210. const stylePresets = ref<ImageModelVO[]>([
  211. {
  212. key: '3d-model',
  213. name: '3d-model'
  214. },
  215. {
  216. key: 'analog-film',
  217. name: 'analog-film'
  218. },
  219. {
  220. key: 'anime',
  221. name: 'anime'
  222. },
  223. {
  224. key: 'cinematic',
  225. name: 'cinematic'
  226. },
  227. {
  228. key: 'comic-book',
  229. name: 'comic-book'
  230. },
  231. {
  232. key: 'digital-art',
  233. name: 'digital-art'
  234. },
  235. {
  236. key: 'enhance',
  237. name: 'enhance'
  238. },
  239. {
  240. key: 'fantasy-art',
  241. name: 'fantasy-art'
  242. },
  243. {
  244. key: 'isometric',
  245. name: 'isometric'
  246. },
  247. {
  248. key: 'line-art',
  249. name: 'line-art'
  250. },
  251. {
  252. key: 'low-poly',
  253. name: 'low-poly'
  254. },
  255. {
  256. key: 'modeling-compound',
  257. name: 'modeling-compound'
  258. },
  259. // neon-punk origami photographic pixel-art tile-texture
  260. {
  261. key: 'neon-punk',
  262. name: 'neon-punk'
  263. },
  264. {
  265. key: 'origami',
  266. name: 'origami'
  267. },
  268. {
  269. key: 'photographic',
  270. name: 'photographic'
  271. },
  272. {
  273. key: 'pixel-art',
  274. name: 'pixel-art'
  275. },
  276. {
  277. key: 'tile-texture',
  278. name: 'tile-texture'
  279. }
  280. ])
  281. // 文本提示相匹配的图像(clip_guidance_preset) 简称 CLIP
  282. // https://platform.stability.ai/docs/api-reference#tag/SDXL-and-SD1.6/operation/textToImage
  283. // FAST_BLUE FAST_GREEN NONE SIMPLE SLOW SLOWER SLOWEST
  284. const selectClipGuidancePreset = ref<string>('NONE') // 模型
  285. const clipGuidancePresets = ref<ImageModelVO[]>([
  286. {
  287. key: 'NONE',
  288. name: 'NONE'
  289. },
  290. {
  291. key: 'FAST_BLUE',
  292. name: 'FAST_BLUE'
  293. },
  294. {
  295. key: 'FAST_GREEN',
  296. name: 'FAST_GREEN'
  297. },
  298. {
  299. key: 'SIMPLE',
  300. name: 'SIMPLE'
  301. },
  302. {
  303. key: 'SLOW',
  304. name: 'SLOW'
  305. },
  306. {
  307. key: 'SLOWER',
  308. name: 'SLOWER'
  309. },
  310. {
  311. key: 'SLOWEST',
  312. name: 'SLOWEST'
  313. }
  314. ])
  315. const steps = ref<number>(20) // 迭代步数
  316. const seed = ref<number>(42) // 控制生成图像的随机性
  317. const scale = ref<number>(7.5) // 引导系数
  318. // 定义 Props
  319. const props = defineProps({})
  320. // 定义 emits
  321. const emits = defineEmits(['onDrawStart', 'onDrawComplete'])
  322. /** 热词 - click */
  323. const handleHotWordClick = async (hotWord: string) => {
  324. // 取消选中
  325. if (selectHotWord.value == hotWord) {
  326. selectHotWord.value = ''
  327. return
  328. }
  329. // 选中
  330. selectHotWord.value = hotWord
  331. // 替换提示词
  332. prompt.value = hotWord
  333. }
  334. /** 图片生产 */
  335. const handleGenerateImage = async () => {
  336. // 二次确认
  337. await message.confirm(`确认生成内容?`)
  338. if (await hasChinese(prompt.value)) {
  339. message.alert('暂不支持中文!')
  340. return
  341. }
  342. try {
  343. // 加载中
  344. drawIn.value = true
  345. // 回调
  346. emits('onDrawStart', 'StableDiffusion')
  347. // 发送请求
  348. const form = {
  349. platform: 'StableDiffusion',
  350. model: 'stable-diffusion-v1-6',
  351. prompt: prompt.value, // 提示词
  352. width: imageWidth.value, // 图片宽度
  353. height: imageHeight.value, // 图片高度
  354. options: {
  355. seed: seed.value, // 随机种子
  356. steps: steps.value, // 图片生成步数
  357. scale: scale.value, // 引导系数
  358. sampler: selectSampler.value, // 采样算法
  359. clipGuidancePreset: selectClipGuidancePreset.value, // 文本提示相匹配的图像 CLIP
  360. stylePreset: selectStylePreset.value // 风格
  361. }
  362. } as ImageDrawReqVO
  363. await ImageApi.drawImage(form)
  364. } finally {
  365. // 回调
  366. emits('onDrawComplete', 'StableDiffusion')
  367. // 加载结束
  368. drawIn.value = false
  369. }
  370. }
  371. /** 填充值 */
  372. const settingValues = async (imageDetail: ImageVO) => {
  373. prompt.value = imageDetail.prompt
  374. imageWidth.value = imageDetail.width
  375. imageHeight.value = imageDetail.height
  376. seed.value = imageDetail.options?.seed
  377. steps.value = imageDetail.options?.steps
  378. scale.value = imageDetail.options?.scale
  379. selectSampler.value = imageDetail.options?.sampler
  380. selectClipGuidancePreset.value = imageDetail.options?.clipGuidancePreset
  381. selectStylePreset.value = imageDetail.options?.stylePreset
  382. }
  383. /** 暴露组件方法 */
  384. defineExpose({ settingValues })
  385. </script>
  386. <style scoped lang="scss">
  387. // 提示词
  388. .prompt {
  389. }
  390. // 热词
  391. .hot-words {
  392. display: flex;
  393. flex-direction: column;
  394. margin-top: 30px;
  395. .word-list {
  396. display: flex;
  397. flex-direction: row;
  398. flex-wrap: wrap;
  399. justify-content: start;
  400. margin-top: 15px;
  401. .btn {
  402. margin: 0;
  403. }
  404. }
  405. }
  406. // 模型
  407. .group-item {
  408. margin-top: 30px;
  409. .group-item-body {
  410. margin-top: 15px;
  411. width: 100%;
  412. }
  413. }
  414. .btns {
  415. display: flex;
  416. justify-content: center;
  417. margin-top: 50px;
  418. }
  419. </style>