Files
drawing/pages/index.vue
2023-03-03 05:11:32 +08:00

376 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template lang="pug">
div(class="mt-[60px] grid grid-cols-1 lg:grid-cols-4 xl:grid-cols-5 text-white bg-[#05020E] h-[calc(100vh-62px)] border-t border-white/10 2xl:border-t-0 mx-auto 2xl:border-x")
// 左侧信息
aside.p-4(class="flex flex-col divide-y divide-white/10 pt-6 space-y-6 lg:overflow-y-auto scrollbar-hide")
div
p.font-bold 风格滤镜
p.text-gray-400 尝试可以应用于您的图像的不同风格样式
div.flex.flex-wrap.gap-2.items-center.pt-2
div.cursor-pointer.select-none(v-for="(item, index) in filters" :key="item.name" class="w-5/16 h-20 bg-gray-500 rounded-lg")
div.flex.justify-center.items-center.h-full(v-if="!item.name" @click="ModelsShow(index, item)")
img.w-6.h-6(:src="IconPlusCircle" alt="PlusCircle")
// background-image: url(${item.image});
div.flex.items-end.h-full.border-opacity-0.border-2.border-purple-700.rounded-md.transition-opacity.duration-150(
v-else
:style="`background-size: cover; background-position: center;`"
:class="{'border-opacity-100': new_task.ckpt === item.name}"
@click="ModelsChange(item)"
)
p(class="p-1") {{item.name}}
// 风格滤镜列表(弹出/悬浮)
div(v-show="imageCreate.models_show" class="absolute w-screen lg:w-[400px] bottom-2 flex flex-col" style="max-height: 80vh; left: 394px; top: 6vh;")
div(class="overflow-auto scrollbar-hide transition-[transform,opacity] duration-[50ms] z-50 pai-border rounded-lg bg-gray-900 shadow-2xl opacity-100")
div(class="space-y-2 items-center absolute overflow-hidden w-[calc(100%-2px)] px-4 pb-2 pt-4 lg:pt-2 z-50 bg-inherit rounded-t-lg border-b border-low border-white border-opacity-10")
div(class="flex justify-between")
h3(class="text-white font-bold") Filter
button(class="text-gray-600 pb-1 lg:hidden")
<svg viewBox="0 0 24 24" focusable="false" class="chakra-icon css-onkibi"><path fill="currentColor" d="M.439,21.44a1.5,1.5,0,0,0,2.122,2.121L11.823,14.3a.25.25,0,0,1,.354,0l9.262,9.263a1.5,1.5,0,1,0,2.122-2.121L14.3,12.177a.25.25,0,0,1,0-.354l9.263-9.262A1.5,1.5,0,0,0,21.439.44L12.177,9.7a.25.25,0,0,1-.354,0L2.561.44A1.5,1.5,0,0,0,.439,2.561L9.7,11.823a.25.25,0,0,1,0,.354Z"></path></svg>
div(class="relative")
input(type="text" placeholder="Search" class="input pai-border bg-gray-900 rounded-md mb-2 outline-none w-full p-2 pl-8" value="")
span(class="absolute left-2 top-2.5 text-gray-500")
<svg fill="none" height="20" shape-rendering="geometricPrecision" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" viewBox="0 0 24 24" width="20"><path d="M11 17.25a6.25 6.25 0 110-12.5 6.25 6.25 0 010 12.5z"></path><path d="M16 16l4.5 4.5"></path></svg>
div(class="h-32 lg:h-28")
div(class="grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-3 gap-2 p-2 pt-0")
template(v-for="item in models" :key="item.name")
button.h-24(
class="transition-[transform,opacity] origin-center filter-button relative aspect-[6/5] duration-200 border-2 rounded-lg overflow-hidden active:border-blue-300/50 border-transparent hover:border-high"
aria-label="Select filter style: Colorpop"
style="transition-delay: 10ms;"
@click="ModelsSelect(imageCreate.filter_ctive, item)"
)
span( style="box-sizing: border-box; display: block; overflow: hidden; width: initial; height: initial; background: none; opacity: 1; border: 0px; margin: 0px; padding: 0px; position: absolute; inset: 0px;")
img( alt="Colorpop" :src="item.image" decoding="async" data-nimg="fill" style="position: absolute; inset: 0px; box-sizing: border-box; padding: 0px; border: none; margin: auto; display: block; width: 0px; height: 0px; min-width: 100%; max-width: 100%; min-height: 100%; max-height: 100%; object-fit: cover;" sizes="100vw" srcset="https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png 640w, https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png 750w, https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png 828w, https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png 1080w, https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png 1200w, https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png 1920w, https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png 2048w, https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png 3840w")
div( class="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 flex flex-col justify-end text-gray-100 text-left text-sm p-1") {{ item.name }}
div
p.font-bold 提示词
p.text-gray-400 描述您期望出现在图像中的细节, 例如颜色, 物体或是风景
textarea.mt-4.rounded-lg.h-32.w-full.px-4.py-2.bg-gray-500.bg-opacity-5.border.border-gray-500.border-opacity-20.text-gray-500(
v-model="new_task.prompt" type="text" class="focus:outline-none"
)
div
div.flex.items-center.justify-between
span.font-bold 排除特征
label.switch
input(type="checkbox" :checked="imageCreate.exclude_on" @change="imageCreate.exclude_on=!imageCreate.exclude_on")
div.slider.round
div(v-show="imageCreate.exclude_on")
textarea.mt-4.rounded-lg.h-32.w-full.px-4.py-2.bg-gray-500.bg-opacity-5.border.border-gray-500.border-opacity-20.text-gray-400(
v-model="new_task.exclude" type="text" class="focus:outline-none"
)
p.pt-2.text-gray-400 描述您不希望出现在图像中的细节, 例如颜色, 物体或是风景
div.opacity-20
p.font-bold 通过图像生成图像
p.text-gray-400 上传或绘制图像以用作灵感
input.mt-4.rounded-lg.h-9.w-full.px-4.py-2.bg-gray-500.bg-opacity-5.border.border-gray-500.border-opacity-20.text-gray-500(value="Search" type="text" class="focus:outline-none")
div.overflow-hidden.flex.gap-2.items-center
img.relative.right-6.w-4(:src="IconEdit" style="filter: drop-shadow(#ffffff 24px 0);")
span 画点什么
div
button.mt-4.rounded-lg.h-9.w-full.bg-gray-500.bg-opacity-5.border.border-gray-500.border-opacity-20.text-gray-500.transition-all.duration-250(
class="focus:outline-none hover:border-indigo-600 hover:bg-indigo-600 hover:bg-opacity-20 hover:text-indigo-300"
@click="TaskSubmit()"
) 开始绘制
// 中间输出
main(class="xl:col-span-3 lg:col-span-2 lg:overflow-y-auto lg:overflow-x-hidden border-x border-white/10 relative scrollbar-hide")
div.flex.gap-2.pb-8
button.rounded-full.w-14.h-14.flex.justify-center.items-center(
:class="{ 'bg-gray-800': views.cardMode }"
@click="views.cardMode = !views.cardMode"
)
img(:src="IconGrid")
button.rounded-full.w-14.h-14.flex.justify-center.items-center(
:class="{ 'bg-gray-800': !views.cardMode }"
@click="views.cardMode = !views.cardMode"
)
img(:src="IconStack")
// 卡片模式
div.flex.gap-6.justify-center.items-center(:class="{'flex-col':!views.cardMode, 'flex-wrap':views.cardMode}")
template(v-for="task in tasks" :key="task.id")
div.relative.bg-green-900.rounded-md.overflow-hidden.w-512px.h-512px.min-w-512px
div.object-contain(v-if="task.data")
img(loading="lazy" :src="img" alt="task" v-for="img in task.data" :key="img")
div.absolute.left-2.right-2.bottom-2.bg-green-600.rounded-full.overflow-hidden.text-green-900.text-xs.transition-all.duration-750.ease-linear(
:class="{'h-8': task.progress<=0.9, 'h-2': task.progress>0.9, 'opacity-90': task.status!='done', 'opacity-10': task.status=='done'}"
)
div.flex.items-center.text-center.h-full.px-2.bg-green-400.rounded-r-full.transition-all.duration-8000.ease-linear(
:style="`width:${task.progress*100}%;`"
) {{ task.status }} {{ task.progress*100 }}%
div.absolute.inset-0.flex.items-end.justify-center.p-6.transition-opacity.bg-gradient-to-t.to-transparent.opacity-0(class="hover:cursor-pointer hover:opacity-100 from-black/90")
pre {{ task.prompt }}
pre {{ new_task }}
// 右侧参数
aside(class="lg:overflow-y-auto relative z-20 transition-all scrollbar-hide")
div(class="px-6 divide-y divide-white/10")
<fieldset class="create-fieldset py-8 opacity-20">
<label for="model-type">Model</label>
<p>Different AI models can produce different or better results so feel free to experiment.</p>
<div class="select">
<select name="model-type" id="model-type" disabled="">
<option value="stable-diffusion" data-version="1.5" selected="">Stable Diffusion 1.5</option>
<option value="stable-diffusion-2" data-version="2.0">Stable Diffusion 2.1</option>
<option value="dalle-2" data-version="2.0">DALL·E 2</option>
</select>
<svg data-testid="geist-icon" fill="none" height="16" shape-rendering="geometricPrecision" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" viewBox="0 0 24 24" width="24"><path d="M6 9l6 6 6-6"></path></svg>
</div>
</fieldset>
div(class="flex flex-col gap-y-8 py-8")
// 选择生成尺寸
fieldset(class="create-fieldset")
label 图像尺寸
p 完成图像的宽度×高度.
div(class="flex flex-row flex-wrap gap-x-2 gap-y-2")
div(class="flex flex-row flex-wrap" v-for="item in sizes" :key="item.id")
button.border-2.rounded-md.px-2(
:class="{'border-purple-600': item.width==new_task.width && new_task.height==item.height}"
@click="new_task.width=item.width;new_task.height=item.height"
) {{ item.width }} x {{ item.height }}
div(class="text-sm grey-100 mt-1")
p Buy a <a target="_blank" style="color:rgb(118 173 255)" href="/pricing">paid plan</a> for any width or height up to 1536px
fieldset(class="create-fieldset")
label 提示词权重(Prompt Guidance)
p 更高的指导将使您的图像更接近您的提示.
input(type="text" class="w-12 rounded-full bg-gray-900 text-xs text-center py-1 text-gray-200" v-model="new_task.prompt_guidance")
fieldset(class="create-fieldset")
label 质量和细节(Quality &amp; Details)
p 更多的步数能使细节更充分, 但也更为耗时.
div(id="slider-undefined" class="flex items-center gap-x-4 slider-container")
div(tabindex="-1" style="position: relative; touch-action: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); user-select: none; outline: 0px; padding-top: 8px; padding-bottom: 8px;" class="chakra-slider css-1t1xvy7")
div(id="slider-track-:R9onqmqulm:" style="position:absolute;top:50%;transform:translateY(-50%);width:100%" class="chakra-slider__track css-8v2r68")
div(class="css-11z5obk")
div(aria-valuenow="50" class="chakra-slider__filled-track css-19vmo6h" style="position: absolute; top: 50%; transform: translateY(-50%); width: 28.5714%; left: 0%;")
div(role="slider" tabindex="0" id="slider-thumb-:R9onqmqulm:" aria-valuemin="10" aria-valuemax="150" aria-valuenow="25" aria-orientation="horizontal" style="position: absolute; user-select: none; touch-action: none; left: calc(28.5714% - 8px);" class="chakra-slider__thumb css-12qisgv")
input(type="hidden" v-model="new_task.quality_details")
input(type="text" class="w-12 rounded-full bg-gray-900 text-xs text-center py-1 text-gray-200" v-model="new_task.quality_details")
div(class="flex flex-col gap-y-4 py-8")
fieldset(class="create-fieldset")
label(for="seed-input") 随机数种子(Seed)
p 不同的数字会导致图像产生新变体
input.bg-light-50.bg-opacity-20.px-2(v-model="new_task.seed" class="text-input" type="number")
<div class="flex items-center gap-x-2">
<label class="chakra-checkbox css-192puf7" data-checked="">
<input class="chakra-checkbox__input" type="checkbox" id="randomize-seed" style="border:0px;clip:rect(0px, 0px, 0px, 0px);height:1px;width:1px;margin:-1px;padding:0px;overflow:hidden;white-space:nowrap;position:absolute" checked="" value="">
<span class="chakra-checkbox__control css-19ag05x" aria-hidden="true" data-checked="">
<div style="display: flex; align-items: center; justify-content: center; height: 100%; transform: none;">
<svg viewBox="0 0 12 10" class="css-1x1o9fj" opacity="1" stroke-dashoffset="0" style="fill: none; stroke-width: 2; stroke: currentcolor; stroke-dasharray: 16;"><polyline points="1.5 6 4.5 9 10.5 1"></polyline></svg>
</div>
</span>
</label>
<label for="randomize-seed" class="text-sm text-gray-300 mt-1">Randomize each number to get new variations</label>
</div>
div(class="flex flex-col gap-y-4" style="border-color:transparent")
fieldset(class="create-fieldset")
div(class="advanced-options-container transition-all overflow-hidden opacity-100 max-h-[99rem] pb-8")
label 采样(Sampler)
p 扩散采样方法.
div(class="select")
select.bg-light-100.bg-opacity-10.px-2(name="advanced-options" id="advanced-options")
option.bg-dark-200(value="1" selected="") pndm (plms)
option.bg-dark-200(value="0") ddim
option.bg-dark-200(value="2") k_euler
option.bg-dark-200(value="3") k_euler_ancestral
option.bg-dark-200(value="4") k_heun
option.bg-dark-200(value="5") k_dpm_2
option.bg-dark-200(value="6") k_dpm_2_ancestral
option.bg-dark-200(value="7") k_lms
<svg data-testid="geist-icon" fill="none" height="16" shape-rendering="geometricPrecision" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" viewBox="0 0 24 24" width="24"><path d="M6 9l6 6 6-6"></path></svg>
<fieldset class="create-fieldset py-8">
<label>图像数量(Number of Images)</label>
<p>选择要生成的图像数量.</p>
div.flex.gap-x-2
input.bg-gray-700.border-none.outline-none(v-model="new_task.number" type="number")
</fieldset>
div(class="flex flex-col gap-y-4 py-8 opacity-20")
fieldset(class="create-fieldset")
<label>私人会话</label>
<p>只有在您共享图像之后别人才能看到这些图像<span> Buy a <a target="_blank" href="/pricing" style="color: rgb(118, 173, 255);">paid plan</a> to persist this setting across sessions.</span></p>
<div class="flex gap-x-3 w-44 items-center">
<label class="chakra-switch [&amp;>span]:bg-[#39324E] [&amp;>span[data-checked]]:bg-[#76ADFF] [&amp;>span]:p-1 css-ghot30" data-checked="">
<input class="chakra-switch__input" type="checkbox" value="" style="border: 0px; clip: rect(0px, 0px, 0px, 0px); height: 1px; width: 1px; margin: -1px; padding: 0px; overflow: hidden; white-space: nowrap; position: absolute;">
<span aria-hidden="true" class="chakra-switch__track css-j1l0qk" data-checked="">
<span class="chakra-switch__thumb css-7roig" data-checked=""></span>
</span>
</label>
</div>
</template>
<script setup>
import IconEdit from 'assets/icon/edit-2.svg'
import IconGrid from 'assets/icon/grid.svg'
import IconStack from 'assets/icon/stack.svg'
import ImageDemo from 'assets/image/demo.png'
import IconPlusCircle from 'assets/icon/plus-circle.svg'
const views = ref({
cardMode: true, // 卡片模式, 列表模式
active: true,
})
const imageCreate = ref({
filter_ctive: 0, // 被操作的模型位置
filter_list: [0, 1, 2, 3, 4, 5, 6],
exclude_on: false, // 排除开关
models_show: false, // 模型列表开关
})
// 新任务表单
const new_task = ref({
model: 'SD2', // 模型
ckpt: '768-v-ema', // 风格参数
prompt: 'A tabby cat, with a fluffy tail, basking in the sun, bright, warm, very cute!', // 渲染提示词
exclude: 'puppets, death, decay, mess', // 排除词汇
number: 1, // 生成图片数量
width: 512, // 图片宽度
height: 512, // 图片高度
seed: 0, // 随机种子
sampler: 'pndm', // 扩散采样器
prompt_guidance: 6, // 提示词权重
quality_details: 50, // 质量和细节(步数)
})
const filters = ref([
{ name:'768-v-ema', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
{ name:'768-v-test', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
//{ name:'baoxiang_2364', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
//{ name:'mmk', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
//{ name:'tx_2600', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
{ name:'', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
])
const models = ref([
{ name:'None', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
{ name:'Colorpop1', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
{ name:'Colorpop2', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
{ name:'Colorpop3', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
{ name:'Colorpop4', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
{ name:'Colorpop5', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
{ name:'Colorpop6', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
{ name:'Colorpop7', image:'https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png' },
])
const images = ref([])
const tasks = ref([])
const sizes = ref([
{ id:'image-dim-0', width:320, height:1024 },
{ id:'image-dim-1', width:512, height:512 },
{ id:'image-dim-2', width:768, height:768 },
{ id:'image-dim-3', width:1024, height:1024 },
{ id:'image-dim-4', width:640, height:384 },
{ id:'image-dim-5', width:384, height:640 },
{ id:'image-dim-6', width:768, height:512 },
])
// 显示模型列表
const ModelsShow = (index, model) => {
console.log(index, model)
imageCreate.value.filter_ctive = index // 正在操作的坐标
imageCreate.value.models_show = !imageCreate.value.models_show
}
// 切换要使用的模型
const ModelsChange = (model) => {
console.log(model)
new_task.value.ckpt = model.name
imageCreate.value.models_show = false
}
// 选择模型到常用模型
const ModelsSelect = (index, model) => {
console.log(index, model)
filters.value[index] = model
new_task.value.ckpt = model.name
imageCreate.value.models_show = false
}
// 提交新任务
const TaskSubmit = () => {
let data = new_task.value
data.exclude = imageCreate.value.exclude_on ? '' : data.exclude // 是否启用排除词汇
fetch('/api/drawing', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(res => res.json()).then(data => {
console.log(`${data.id}: ${data.status}`)
tasks.value.push(data)
// 轮询任务状态
let t2 = window.setInterval(() => {
fetch('/api/drawing/'+data.id).then(res => res.json()).then(data => {
console.log(data.id, data.status, data.progress)
tasks.value = tasks.value.map(x => x.id == data.id ? data : x)
if (data.status == 'done') {
//data.data = ['https://storage.googleapis.com/pai-marketing/filters/elizaport_style.png']
window.clearInterval(t2)
}
})
}, 1000)
})
}
</script>
<style>
.pai-border {
border-width: 1px;
border-style: solid;
border-color: rgba(255, 255, 255, 0.08);
border-image: initial;
}
.switch {
position: relative;
display: inline-block;
width: 38px;
height: 24px;
}
.switch input {display:none;}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #2196F3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .slider:before {
-webkit-transform: translateX(14px);
-ms-transform: translateX(14px);
transform: translateX(14px);
}
/* Rounded sliders */
.slider.round {
border-radius: 17px;
}
.slider.round:before {
border-radius: 50%;
}
</style>