index.vue 9.6 KB

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