Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,15 @@ export default {
needImg: 'Please upload a reference image for it to take effect!',
seed: 'Seed number 1~2147483647',
klingInfo: 'Description: <li>1. High Quality is 3.5 times the price</li> <li>2. 10s is 2 times the price</li> <li>3. The last frame must have a reference image to take effect</li>'


,aspectRatio:'Aspect Ratio'
,responseFormat:'Response Format'
,imageSize:'Image Size'
,nanoBananaSupport:'All Banana models support'
,onlyNanoBanana2:'Only nano-banana-2 support'
,refImages:'Ref Images'
,refImagesDesc:'Supports multiple image references'

,"camera_type": "Lens",
"cnull": "Smart Matching",
"down_back": "Move Down and Zoom Out",
Expand Down
7 changes: 7 additions & 0 deletions src/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ export default {
,dalleInfo:' 说明: <li>1 dall-e 是openAi提供的画图模型</li> <li>2 openAi的图片有时效性,请做好备份</li> <li>3 注意:1790px的图片价格是双倍</li> <li>4 注意:dall-e-2、gpt-image-1 支持多图参考</li> '
,version:'版本'
,size:'尺寸'
,aspectRatio:'宽高比'
,responseFormat:'返回格式'
,imageSize:'图片尺寸'
,nanoBananaSupport:'所有 Banana 模型支持'
,onlyNanoBanana2:'仅 nano-banana-2 支持'
,refImages:'参考图'
,refImagesDesc:'支持多图参考'
,blendInfo:'说明: <li>1 合成至少2张图片</li> <li>2 最多可传6张图</li> '
,blendStart:'开始合成'
,no2add:'请勿重复添加图片'
Expand Down
205 changes: 155 additions & 50 deletions src/views/mj/aiDall.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref ,computed,watch} from 'vue';
import {useMessage, NButton,NSelect,NInput, NImage, c} from 'naive-ui';
import {useMessage, NButton,NSelect,NInput, NImage} from 'naive-ui';
import {gptFetch, mlog, upImg} from '@/api'
import { homeStore } from '@/store';
import { SvgIcon } from '@/components/common';
Expand All @@ -9,13 +9,13 @@ import { t } from '@/locales';
const ms = useMessage();
const config = ref( {
model:[
{ "label": "DALL·E 3", "value": "dall-e-3" }
{ "label": "nano-banana-2", "value": "nano-banana-2" }
,{ "label": "nano-banana-hd", "value": "nano-banana-hd" }
,{ "label": "nano-banana", "value": "nano-banana" }
,{ "label": "DALL·E 3", "value": "dall-e-3" }
,{ "label": "GPT-Image-1", "value": "gpt-image-1" }
,{ "label": "flux-kontext-pro", "value": "flux-kontext-pro" }
,{ "label": "flux-kontext-max", "value": "flux-kontext-max" }
,{ "label": "nano-banana-2", "value": "nano-banana-2" }
,{ "label": "nano-banana", "value": "nano-banana" }
,{ "label": "nano-banana-hd", "value": "nano-banana-hd" }
,{ "label": "DALL·E 2", "value": "dall-e-2" }
,{ "label": "Flux", "value": "flux" }
,{ "label": "Flux-Dev", "value": "flux-dev" }
Expand All @@ -28,64 +28,117 @@ interface myFile{
file:any
base64:string
}
const st =ref({isGo:false,quality:'medium' });
const fsRef= ref() ;
const base64Array= ref<myFile[]>([]);
const f = ref({size:'1024x1024', prompt:'',"model": "dall-e-3","n": 1});
const st =ref({
isGo:false,
quality:'medium',
response_format:'url',
image_size:'1K',
aspect_ratio:'4:3'
});
const fsRef= ref();
const base64Array= ref<myFile[]>([]);
const f = ref({size:'1024x1024', prompt:'',"model": "nano-banana-2","n": 1});
const isDisabled= computed(()=>{
if(st.value.isGo) {
//console.log('st.value.isGo',st.value.isGo);
return true;
}
if(f.value.prompt.trim()=='') {
//console.log('prompt',"空");
return true;
}
return false;
});

const isNanoBanana2 = computed(()=>{
return f.value.model === 'nano-banana-2';
});

const isNanoBanana = computed(()=>{
return f.value.model.includes('banana');
});

const create= async ()=>{
// const d= await gptFetch('/v1/embeddings',{
// "input": f.value.prompt,
// "model": "text-embedding-ada-002"
// });
// mlog('test',d );
//return ;
let obj= {
action:'gpt.dall-e-3',
data:{} //f.value
}
obj.data= { ...f.value}
if(isCanImageEdit.value){

if(isNanoBanana.value){
obj.data.aspect_ratio = st.value.aspect_ratio;
obj.data.response_format = st.value.response_format;

if(isNanoBanana2.value){
obj.data.image_size = st.value.image_size;
}

delete obj.data.size;

if(base64Array.value.length>0){
obj.data.image = base64Array.value.map(item => item.base64);
}
}

if(isCanImageEdit.value && !isNanoBanana.value){
obj.data= {...obj.data ,quality:st.value.quality};
}
if (isCanImageEdit.value && base64Array.value.length>0){

if (isCanImageEdit.value && base64Array.value.length>0 && !isNanoBanana.value){
obj.data= {...obj.data, 'base64Array':base64Array.value,quality:st.value.quality};
mlog("data", '我加东西了:', base64Array.value )
}
homeStore.setMyData({act:'draw', actData:obj});
st.value.isGo=true;
}

watch(()=>homeStore.myData.act,(n)=>{
if(n=='dallReload') {
st.value.isGo=false;
f.value.prompt='';
}
if(n=='updateChat') st.value.isGo=false;
if(n=='updateChat') st.value.isGo=false;
})

const qualityOption= computed(()=>{

const qualityOption= computed(()=>{
return [
{label:'High',value: 'high'}
,{label:'Medium',value: 'medium'}
,{label:'Low',value: 'low'}

]
});

const responseFormatOption= computed(()=>{
return [
{label:'URL',value: 'url'}
,{label:'Base64',value: 'b64_json'}
]
});

const imageSizeOption= computed(()=>{
return [
{label:'1K',value: '1K'}
,{label:'2K',value: '2K'}
,{label:'4K',value: '4K'}
]
});

const aspectRatioOption= computed(()=>{
return [
{label:'4:3',value: '4:3'}
,{label:'3:4',value: '3:4'}
,{label:'16:9',value: '16:9'}
,{label:'9:16',value: '9:16'}
,{label:'2:3',value: '2:3'}
,{label:'3:2',value: '3:2'}
,{label:'1:1',value: '1:1'}
,{label:'4:5',value: '4:5'}
,{label:'5:4',value: '5:4'}
,{label:'21:9',value: '21:9'}
]
});

const dimensionsList= computed(()=>{
if(f.value.model=='dall-e-2'){
return [{
return [{
"label": "1024px*1024px",
"value": "1024x1024"
}, {
Expand All @@ -96,9 +149,9 @@ const dimensionsList= computed(()=>{
"value": "256x256"
}
];
}
}
if(f.value.model=='gpt-image-1'){
return [{
return [{
"label": "1024px*1024px",
"value": "1024x1024"
}, {
Expand Down Expand Up @@ -139,7 +192,7 @@ const dimensionsList= computed(()=>{
}
];
}
return [{
return [{
"label": "1024px*1024px",
"value": "1024x1024"
}, {
Expand All @@ -150,10 +203,18 @@ const dimensionsList= computed(()=>{
"value": "1024x1792"
}
]

})
watch(()=>f.value.model,(n)=>{
watch(()=>f.value.model,(n, oldVal)=>{
f.value.size='1024x1024';

if(isNanoBanana.value && !oldVal?.includes('banana')){
st.value.aspect_ratio = '4:3';
}
})

watch(()=>st.value.aspect_ratio,(n, oldVal)=>{
mlog('aspect_ratio:', n);
})
const isCanImageEdit= computed(()=>{
if(f.value.model=='dall-e-2') return true;
Expand All @@ -173,59 +234,103 @@ const selectFile=(input:any)=>{
return ;
}
base64Array.value.push({file: ff ,base64:d});
//if(base64Array.value.length>1) st.value.isGo=true;
//if(st)
}).catch(e=>ms.error(e));
}

const removeImage=(item: myFile)=>{
const index = base64Array.value.indexOf(item);
if(index>-1){
base64Array.value.splice(index,1);
}
}

</script>
<template>
<section class="mb-4 flex justify-between items-center" >
<section class="mb-4 flex justify-between items-center">
<div>{{ $t('mjchat.version') }} </div>
<n-select v-model:value="f.model" :options="config.model" filterable tag size="small" class="!w-[70%]" :clearable="false" />
</section>
<section class="mb-4 flex justify-between items-center" >
<div>{{ $t('mjchat.size') }}</div>
<n-select v-model:value="f.size" :options="dimensionsList" filterable tag size="small" class="!w-[70%]" :clearable="false" />
</section>
<section class="mb-4 flex justify-between items-center" v-if="isCanImageEdit" >
<div>Quality</div>
<n-select v-model:value="st.quality" :options="qualityOption" filterable tag size="small" class="!w-[70%]" :clearable="false" />
</section>

<!-- Nano-banana 专属选项 -->
<template v-if="isNanoBanana">
<section class="mb-4 flex justify-between items-center">
<div>
{{ $t('mjchat.aspectRatio') }}
<span v-if="isNanoBanana2" class="text-xs text-gray-500 ml-2">({{ $t('mjchat.nanoBananaSupport') }})</span>
</div>
<n-select v-model:value="st.aspect_ratio" :options="aspectRatioOption" size="small" class="!w-[70%]" :clearable="false" />
</section>

<section class="mb-4 flex justify-between items-center">
<div>{{ $t('mjchat.responseFormat') }}</div>
<n-select v-model:value="st.response_format" :options="responseFormatOption" size="small" class="!w-[70%]" :clearable="false" />
</section>

<section class="mb-4 flex justify-between items-center" v-if="isNanoBanana2">
<div>
{{ $t('mjchat.imageSize') }}
<span class="text-xs text-orange-500 ml-1">({{ $t('mjchat.onlyNanoBanana2') }})</span>
</div>
<n-select v-model:value="st.image_size" :options="imageSizeOption" size="small" class="!w-[70%]" :clearable="false" />
</section>
</template>

<!-- 其他模型的传统选项 -->
<template v-else>
<section class="mb-4 flex justify-between items-center">
<div>{{ $t('mjchat.size') }}</div>
<n-select v-model:value="f.size" :options="dimensionsList" filterable tag size="small" class="!w-[70%]" :clearable="false" />
</section>
<section class="mb-4 flex justify-between items-center" v-if="isCanImageEdit" >
<div>Quality</div>
<n-select v-model:value="st.quality" :options="qualityOption" filterable tag size="small" class="!w-[70%]" :clearable="false" />
</section>
</template>

<div class="mb-1">
<n-input type="textarea" v-model:value="f.prompt" :placeholder="$t('mjchat.prompt')" round clearable maxlength="500" show-count
<n-input type="textarea" v-model:value="f.prompt" :placeholder="$t('mjchat.prompt')" round clearable maxlength="500" show-count
:autosize="{ minRows:3, maxRows:10 }" />
</div>
<div class="mb-1" v-if="isCanImageEdit">

<!-- 参考图上传 -->
<div class="mb-1" v-if="isCanImageEdit">
<div class="mb-2 text-sm text-gray-600 dark:text-gray-400">
<template v-if="isNanoBanana">
{{ $t('mjchat.refImages') }}
<span class="text-xs">({{ $t('mjchat.refImagesDesc') }})</span>
</template>
<template v-else>
{{ $t('mjchat.refImages') }}
</template>
</div>
<div class="flex justify-start items-center flex-wrap myblend">

<div class="w-[var(--my-blend-img-size)] h-[var(--my-blend-img-size)] mr-2 mt-2 bg-[#ddd] overflow-hidden rounded-sm relative group " v-for="item in base64Array">
<NImage :src="item.base64" object-fit="cover"></NImage>
<SvgIcon icon="fluent:delete-12-filled"
class="absolute top-0 right-0 text-red-600 text-[20px] cursor-pointer hidden group-hover:block "
@click="base64Array.splice(base64Array.indexOf(item),1)"></SvgIcon>
@click="removeImage(item)"></SvgIcon>
</div>

<div @click="fsRef.click()" v-if="base64Array.length<3"
class="w-[var(--my-blend-img-size)] h-[var(--my-blend-img-size)] mt-2 bg-[#999] overflow-hidden rounded-sm flex justify-center items-center cursor-pointer">
<SvgIcon icon="mdi:add-bold" class="text-[40px] text-[#fff]"></SvgIcon>
</div>
</div>

</div>
</div>

<div class="mb-4 flex justify-end items-center">
<div class="flex ">
<n-button type="primary" :block="true" :disabled="isDisabled" @click="create()" >
<SvgIcon icon="mingcute:send-plane-fill" />
{{ $t('mjchat.imgcreate') }}
<SvgIcon icon="mingcute:send-plane-fill" />
{{ $t('mjchat.imgcreate') }}
</n-button>
</div>
</div>

<ul class="pt-4" v-html="$t('mjchat.dalleInfo')">

</ul>

<input type="file" @change="selectFile" ref="fsRef" style="display: none" accept="image/jpeg, image/jpg, image/png, image/gif"/>
Expand All @@ -236,4 +341,4 @@ const selectFile=(input:any)=>{
.myblend{
--my-blend-img-size:75px
}
</style>
</style>