aiDrawInputItem.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. <script setup lang="ts">
  2. import { ref,computed,watch,onMounted } from "vue";
  3. import config from "./draw.json";
  4. import { NSelect,NInput,NButton,NTag,NPopover, useMessage,NInputNumber} from 'naive-ui';
  5. import { SvgIcon } from '@/components/common'
  6. import { useBasicLayout } from '@/hooks/useBasicLayout'
  7. const { isMobile } = useBasicLayout()
  8. import AiMsg from './aiMsg.vue'
  9. //import aiFace from './aiFace.vue'
  10. import { mlog, train, upImg ,getMjAll, mjFetch } from '@/api'
  11. //import {copyText3} from "@/utils/format";
  12. import { homeStore ,useChatStore} from "@/store";
  13. const chatStore = useChatStore()
  14. import {t} from "@/locales"
  15. import axios from "@/utils/request/axios";
  16. //import { upImg } from "./mj";
  17. import { getToken } from '@/store/modules/auth/helper'
  18. const vf=[{s:'width: 100%; height: 100%;',label:'1:1'}
  19. ,{s:'width: 100%; height: 75%;',label:'4:3'}
  20. ,{s:'width: 75%; height: 100%;',label:'3:4'}
  21. ,{s:'width: 100%; height: 50%;',label:'16:9'}
  22. ,{s:'width: 50%; height: 100%;',label:'9:16'}
  23. ];
  24. const f=ref({bili:-1, quality:'',view:'',light:'',shot:'',style:'', styles:'',version:'--v 6.0',sref:'',cref:'',cw:'',});
  25. const st =ref({text:'',isDisabled:false,isLoad:false
  26. ,fileBase64:[],bot:'',showFace:false,upType:''
  27. });
  28. const farr= [
  29. { k:'style',v:t('mjchat.tStyle') }
  30. ,{ k:'view',v: t('mjchat.tView') }
  31. ,{ k:'shot',v: t('mjchat.tShot') }
  32. ,{ k:'light',v: t('mjchat.tLight') }
  33. ,{ k:'quality',v: t('mjchat.tQuality') }
  34. ,{ k:'styles',v:t('mjchat.tStyles') }
  35. ,{ k:'version',v:t('mjchat.tVersion') }
  36. ];
  37. const drawlocalized = computed(() => {
  38. let localizedConfig = {};
  39. Object.keys(config).forEach((key) => {
  40. localizedConfig[key] = config[key].map((option: { labelKey: any; }) => {
  41. // 假设 labelKey 如 "draw.qualityList.general"
  42. let path = option.labelKey; // 直接使用 labelKey 作为路径
  43. return {
  44. ...option,
  45. label: t(path), // 从 i18n 中获取本地化的标签
  46. };
  47. });
  48. });
  49. return localizedConfig;
  50. });
  51. const msgRef = ref()
  52. const fsRef= ref()
  53. const fsRef2 = ref()
  54. const fsRef3 = ref()
  55. const $emit=defineEmits(['drawSent','close']);
  56. const props = defineProps({buttonDisabled:Boolean});
  57. const isDisabled = computed(() => {
  58. return props.buttonDisabled || st.value.isLoad || st.value.text.trim()==''
  59. })
  60. const ms= useMessage();
  61. function create( ){
  62. st.value.isLoad=true
  63. train( st.value.text.trim()).then(ps=>{
  64. const rz={ prompt: st.value.text.trim() , drawText: createPrompt( ps) }
  65. if( ps ) drawSent(rz)
  66. st.value.text=''
  67. st.value.isLoad=false
  68. }).catch(err=>{
  69. msgRef.value.showError(err)
  70. st.value.isLoad=false
  71. })
  72. }
  73. const shorten= ()=>{
  74. if( st.value.text.trim()=='') {
  75. mlog('empty');
  76. msgRef.value.showError(t('mjchat.placeInput') );
  77. return;
  78. }
  79. let obj={
  80. action:'shorten',
  81. data:{prompt: st.value.text.trim(),botType: st.value.bot=='NIJI_JOURNEY'? 'NIJI_JOURNEY': 'MID_JOURNEY'}
  82. }
  83. homeStore.setMyData({act:'draw',actData:obj});
  84. }
  85. function drawSent(rz:any){
  86. let rz2= rz;
  87. if(st.value.fileBase64) {
  88. rz2.fileBase64=st.value.fileBase64
  89. }
  90. if( st.value.bot=='NIJI_JOURNEY' ){
  91. rz2.bot='NIJI_JOURNEY';
  92. }
  93. $emit('drawSent', rz2 )
  94. st.value.fileBase64= [];
  95. }
  96. function createPrompt(rz:string){
  97. if( rz =='') {
  98. msgRef.value.showError(t('mjchat.placeInput') );
  99. return '';
  100. }
  101. // for(let v of farr){
  102. // if( ! f.value[v.k] || f.value[v.k]==null || f.value[v.k]=='' ) continue;
  103. // mlog('k ', rz, f.value );
  104. // if(v.k=='quality') rz +=` --q ${f.value.quality}`;
  105. // else if(v.k=='styles') { if( f.value.styles ) rz +=` ${f.value.styles}`;}
  106. // else if(v.k=='version') {
  107. // st.value.bot= '';
  108. // if(['MID_JOURNEY','NIJI_JOURNEY'].indexOf(f.value.version)>-1 ){
  109. // st.value.bot= f.value.version ;
  110. // } else rz +=` ${f.value.version}`;
  111. // }
  112. // else if( f.value[v.k] ) rz +=` , ${f.value[v.k]}`;
  113. // }
  114. // mlog('createPrompt ', rz, f.value );
  115. // if(f.value.bili>-1) rz +=` --ar ${vf[f.value.bili].label}`;
  116. let rzp='' //参数组合字符串
  117. let rzk=''; //描述词组合字符串
  118. for(let v of farr){
  119. if( ! f.value[v.k] || f.value[v.k]==null || f.value[v.k]=='' ) continue;
  120. mlog('k ', rz, f.value );
  121. if(v.k=='quality') rzp +=` --q ${f.value.quality}`;
  122. else if(v.k=='styles') { if( f.value.styles ) rzp +=` ${f.value.styles} `;}
  123. else if(v.k=='version') {
  124. st.value.bot= '';
  125. if(['MID_JOURNEY','NIJI_JOURNEY'].indexOf(f.value.version)>-1 ){
  126. st.value.bot= f.value.version ;
  127. } else rzp +=` ${f.value.version}`;
  128. }
  129. else if( f.value[v.k] ) rzk +=`${f.value[v.k]},`;
  130. }
  131. mlog('createPrompt ', rz, f.value );
  132. if( f.value.sref.trim() != '' ) rzp += ` --sref ${f.value.sref}`
  133. if( f.value.cref.trim() != '' ) rzp += ` --cref ${f.value.cref}`
  134. if( f.value.cw && f.value.cw!='' ) rzp += ` --cw ${f.value.cw}`
  135. if (f.value.bili > -1) rzp += ` --ar ${vf[f.value.bili].label}`
  136. rz = rzk + rz +rzp;
  137. return rz ;
  138. }
  139. // const copy=()=>{
  140. // copyText3( '哦们sd').then(()=>msgRef.value.showMsg('复制成功345!'));
  141. // }
  142. // const copy2= ()=>{
  143. // copyRef.value.click();
  144. // }
  145. function selectFile(input:any){
  146. if(st.value.fileBase64.length>=5 ) {
  147. ms.error( t('mjchat.more5sb'));
  148. return;
  149. }
  150. upImg(input.target.files[0]).then( (d:any )=>{
  151. const index = st.value.fileBase64.findIndex(v=>v==d);
  152. if(index>-1) {
  153. ms.error( t('mjchat.no2add'));
  154. return ;
  155. }
  156. st.value.fileBase64.push(d);
  157. fsRef.value.value='';
  158. }).catch(e=>msgRef.value.showError(e));
  159. }
  160. //图生文
  161. function selectFile2(input:any){
  162. upImg(input.target.files[0]).then(d=>{
  163. mlog('f2base64>> ',d );
  164. let obj={
  165. action:'img2txt',
  166. data:{
  167. "base64":d
  168. ,"botType": "MID_JOURNEY"
  169. }
  170. }
  171. homeStore.setMyData({act:'draw',actData:obj});
  172. //input.value.value='';
  173. fsRef2.value.value='';
  174. })
  175. .catch(e=>msgRef.value.showError(e))
  176. }
  177. const same2=()=>{
  178. st.value.text= homeStore.myData.actData.prompt;
  179. f.value.version='';
  180. f.value.quality='';
  181. }
  182. watch(()=>homeStore.myData.act,(n)=>{
  183. // n=='copy' && copy2();
  184. n=='same2' && same2();
  185. });
  186. onMounted(()=>{
  187. homeStore.myData.act=='same2' && same2();
  188. });
  189. const exportToTxt= async ()=>{
  190. let txtContent ='';
  191. mlog('sss',txtContent,chatStore.$state.chat.length );
  192. let d = await getMjAll( chatStore.$state);
  193. if(d.length==0) {
  194. //ms.info('暂时没作品');
  195. ms.info( t('mjchat.noproducet'));
  196. return;
  197. }
  198. d.forEach((v:Chat.Chat,i:number)=>{
  199. if( v.opt&& v.opt?.status=='SUCCESS' && v.opt?.imageUrl ) {
  200. txtContent += v.opt?.imageUrl+ "\n\n";
  201. }
  202. })
  203. if(txtContent=='') {
  204. ms.info( t('mjchat.noproducet'));
  205. return;
  206. }
  207. let blob = new Blob([txtContent], { type: "text/plain" });
  208. let a = document.createElement("a");
  209. a.href = URL.createObjectURL(blob);
  210. a.download = t('mjchat.downloadSave') ;
  211. a.click();
  212. ms.success( t('mjchat.exSuccess'));
  213. }
  214. const clearAll=()=>{
  215. st.value.fileBase64=[];
  216. st.value.text='';
  217. f.value.bili=-1;
  218. f.value.version='';
  219. f.value.quality='';
  220. f.value.shot='';
  221. f.value.light='';
  222. f.value.style='';
  223. f.value.styles='';
  224. f.value.view='';
  225. f.value.cref='';
  226. f.value.cw='';
  227. f.value.sref='';
  228. }
  229. const uploader=(type:string)=>{
  230. st.value.upType= type;
  231. fsRef3.value.click();
  232. }
  233. const selectFile3= (input:any)=>{
  234. // ms.loading('上传中...');
  235. upImg(input.target.files[0]).then( async(d)=>{
  236. mlog('selectFile3>> ',d );
  237. let data={
  238. action:'img2txt',
  239. data:{
  240. "base64Array":d
  241. }
  242. }
  243. const blob = base64ToBlob(data.data.base64Array);
  244. const file = blobToFile(blob, 'image.png'); // 指定文件名和扩展名
  245. // 创建FormData
  246. const formData = new FormData();
  247. formData.append('file', file);
  248. console.log("formData========",formData)
  249. try{
  250. const result = await axios.post('/resource/oss/upload', formData, {
  251. headers: {
  252. 'Content-Type': 'multipart/form-data',
  253. 'Authorization': 'Bearer ' + getToken(),
  254. },
  255. });
  256. fsRef3.value.value='';
  257. if(result.data.code== 200){
  258. if( st.value.upType=='cref'){
  259. f.value.cref= result.data.data.url;
  260. }else{
  261. f.value.sref= result.data.data.url;
  262. }
  263. ms.success( t('mj.uploadSuccess'));
  264. }else{
  265. ms.error( t(result.data.msg));
  266. }
  267. mlog('selectFile3>> ',d );
  268. }catch(e ){
  269. msgRef.value.showError(e)
  270. }
  271. })
  272. .catch(e=>msgRef.value.showError(e))
  273. }
  274. // 将base64字符串转换为Blob对象
  275. function base64ToBlob(base64: string): Blob {
  276. const byteString = window.atob(base64.split(',')[1]);
  277. const mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
  278. const ab = new ArrayBuffer(byteString.length);
  279. const ia = new Uint8Array(ab);
  280. for (let i = 0; i < byteString.length; i++) {
  281. ia[i] = byteString.charCodeAt(i);
  282. }
  283. return new Blob([ab], { type: mimeString });
  284. }
  285. // 将Blob对象转换为File对象
  286. function blobToFile(blob: Blob, fileName: string): File {
  287. const file = new File([blob], fileName, { type: blob.type });
  288. return file;
  289. }
  290. </script>
  291. <template>
  292. <AiMsg ref="msgRef" />
  293. <input type="file" @change="selectFile" ref="fsRef" style="display: none" accept="image/jpeg, image/jpg, image/png, image/gif"/>
  294. <input type="file" @change="selectFile2" ref="fsRef2" style="display: none" accept="image/jpeg, image/jpg, image/png, image/gif"/>
  295. <input type="file" @change="selectFile3" ref="fsRef3" style="display: none" accept="image/jpeg, image/jpg, image/png, image/gif"/>
  296. <div class="overflow-y-auto bg-[#fafbfc] px-4 dark:bg-[#18181c] h-full draw-form">
  297. <section class="mb-4">
  298. <div class="mr-1 mb-2 flex justify-between items-center">
  299. <div class="text-sm">{{ $t('mjchat.imgBili') }}</div>
  300. <div>
  301. <NPopover trigger="hover">
  302. <template #trigger>
  303. <SvgIcon icon="iconoir:database-export" class="text-lg cursor-pointer" @click="exportToTxt"></SvgIcon>
  304. </template>
  305. <div>{{ $t('mjchat.imagEx') }}</div>
  306. </NPopover>
  307. </div>
  308. </div>
  309. <div class=" flex items-center justify-between space-x-1">
  310. <template v-for="(item,index) in vf" >
  311. <section class="aspect-item flex-1 rounded border-2 dark:border-neutral-700 cursor-pointer" :class="{'active':index==f.bili}" @click="f.bili=index">
  312. <div class="aspect-box-wrapper mx-auto my-2 flex h-5 w-5 items-center justify-center">
  313. <div class="aspect-box rounded border-2 dark:border-neutral-700" :style="item.s"></div>
  314. </div>
  315. <p class="mb-1 text-center text-sm">{{ item.label }}</p>
  316. </section>
  317. </template>
  318. </div>
  319. </section>
  320. <section class="mb-4 flex justify-between items-center" v-for=" v in farr">
  321. <div>{{ v.v }}</div>
  322. <n-select v-model:value="f[v.k]" :options="drawlocalized[v.k+'List']" size="small" class="!w-[60%]" :clearable="true" />
  323. </section>
  324. <!-- <template > </template> -->
  325. <section class="mb-4 flex justify-between items-center" >
  326. <div >cw(0-100)</div>
  327. <!-- 0-100 角色参考程度 -->
  328. <NInputNumber :min="0" :max="100" v-model:value="f.cw" class="!w-[60%]" size="small" clearable placeholder="" />
  329. </section >
  330. <section class="mb-4 flex justify-between items-center" >
  331. <div class="w-[45px]">sref</div>
  332. <!-- 图片url 生成风格一致的图像 -->
  333. <NInput v-model:value="f.sref" size="small" placeholder="" clearable >
  334. <template #suffix>
  335. <SvgIcon icon="ri:upload-line" class="cursor-pointer" @click="uploader('sref')"></SvgIcon>
  336. </template>
  337. </NInput>
  338. </section>
  339. <section class="mb-4 flex justify-between items-center" >
  340. <div class="w-[45px]">cref</div>
  341. <!--图片url 生成角色一致的图像 -->
  342. <NInput v-model:value="f.cref" size="small" placeholder="" clearable>
  343. <template #suffix>
  344. <SvgIcon icon="ri:upload-line" class="cursor-pointer" @click="uploader('cref')"></SvgIcon>
  345. </template>
  346. </NInput>
  347. </section>
  348. <div class="mb-1">
  349. <n-input type="textarea" v-model:value="st.text" :placeholder="$t('mjchat.prompt')" round clearable maxlength="2000" show-count
  350. :autosize="{ minRows:2, maxRows:5 }" />
  351. </div>
  352. <div class="mb-4 flex justify-between items-center">
  353. <div class="flex justify-start items-center flex-wrap">
  354. <div class="pt-1 pr-1 ">
  355. <NPopover trigger="hover">
  356. <template #trigger>
  357. <n-tag type="error" round size="small" style="cursor: pointer; " :bordered="false" @click="fsRef.click()" v-if="st.fileBase64.length">
  358. <div style="display: flex;"> <SvgIcon icon="mdi:file-chart-check-outline" /> {{ $t('mjchat.imgCYes') }} </div>
  359. </n-tag>
  360. <n-tag type="error" round size="small" style="cursor: pointer; " :bordered="false" @click="fsRef.click()" v-else="st.fileBase64">
  361. <div style="display: flex;"> <SvgIcon icon="mdi:file-document-plus-outline" /> {{ $t('mjchat.imgCUpload') }} </div>
  362. </n-tag>
  363. </template>
  364. <div style="max-width: 240px;">
  365. <p v-html="$t('mjchat.imgCInfo')"></p>
  366. 3.<a class="text-green-500 cursor-pointer" @click="fsRef.click()" v-html="$t('mjchat.imgCadd')"></a><br/>
  367. <div v-if="st.fileBase64.length>0" class="flex justify-start items-baseline">
  368. <div class="p-1" v-for="(v ) in st.fileBase64">
  369. <img class="w-[60px]" :src="v">
  370. <br/>
  371. <NButton size="small" @click="st.fileBase64= st.fileBase64.filter((item)=>item!=v) " type="warning" >{{$t('mjchat.del')}}</NButton>
  372. </div>
  373. </div>
  374. </div>
  375. </NPopover>
  376. </div>
  377. <div class="pr-1 pt-1">
  378. <NPopover trigger="hover">
  379. <template #trigger>
  380. <n-tag type="error" round size="small" style="cursor: pointer; " :bordered="false" @click="fsRef2.click()" >
  381. <div style="display: flex;"> <SvgIcon icon="fluent:image-edit-16-regular" /> {{$t('mjchat.img2text')}} </div>
  382. </n-tag>
  383. </template>
  384. <div style="max-width: 240px;" v-html="$t('mjchat.img2textinfo')">
  385. </div>
  386. </NPopover>
  387. </div>
  388. <div class="pt-1" >
  389. <n-tag type="info" round size="small" style="cursor: pointer; " :bordered="false" @click="shorten()" >
  390. <div style="display: flex;"> <SvgIcon icon="game-icons:bouncing-spring" /> Shorten </div>
  391. </n-tag>
  392. </div>
  393. </div>
  394. <!-- <div class="flex " v-if="$t('mjchat.imgcreate').indexOf('生成图片')!==-1">
  395. <n-button type="primary" :block="true" :disabled="isDisabled" @click="create()">
  396. <SvgIcon icon="mingcute:send-plane-fill" />
  397. <template v-if="st.isLoad">{{$t('mjchat.traning')}} </template>
  398. <template v-else> {{$t('mjchat.imgcreate')}}</template>
  399. </n-button>
  400. </div> -->
  401. </div>
  402. <div class="flex">
  403. <n-button type="success" style="border-radius: 12px; height: 48px; line-height: 48px; font-size: 17px;" :bordered="false" class="genner-button" :block="true" :disabled="isDisabled" @click="create()">
  404. <SvgIcon icon="mingcute:send-plane-fill" />
  405. <template v-if="st.isLoad">{{$t('mjchat.traning')}} </template>
  406. <template v-else> {{$t('mjchat.imgcreate')}}</template>
  407. </n-button>
  408. </div>
  409. <div class="flex justify-start items-center py-1">
  410. <div >
  411. <n-tag type="info" round size="small" style="cursor: pointer; " :bordered="false" @click="clearAll()" >
  412. <div style="display: flex;"> <SvgIcon icon="ant-design:clear-outlined" />{{ $t('mj.clearAll') }} </div>
  413. </n-tag>
  414. </div>
  415. </div>
  416. <!-- <div>
  417. <NDivider dashed>
  418. <NTag type="success" round ><div class="cursor-pointer" @click="st.showFace= !st.showFace">换脸服务</div></NTag>
  419. </NDivider>
  420. <aiFace v-if="st.showFace" />
  421. </div> -->
  422. <!-- <div class="mb-4 flex justify-between items-center">
  423. <div @click="copy()" ref="copyRef">复制</div>
  424. <div @click="copy2()" >复制2</div>
  425. </div> -->
  426. <ul class="pt-4" style="font-size: 12px;" v-if="!isMobile" v-html="$t('mjchat.imginfo')"></ul>
  427. </div>
  428. </template>
  429. <style>
  430. .aspect-item.active, .aspect-item.active .aspect-box{
  431. border-color:#86dfba ;
  432. }
  433. </style>