Files
drawing/pages/index.vue
2023-03-01 01:48:09 +08:00

392 lines
22 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")
div.flex.items-end.h-full.border-purple-700.rounded-md(
v-else
:style="`background-image: url(${item.image}); background-size: cover; background-position: center;`"
:class="{'border-2': new_task.model === 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">
<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">
<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")
input.hidden.radio-input(type="radio" :id="item.id" checked="")
label.border.border-2.border-gray-500.rounded-md.px-4.py-1.whitespace-nowrap.text-center(:for="item.id" class="!text-[11px]" style="width:98px") {{ 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 Higher guidance will make your image closer to your prompt.
SliderUndefined
//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-:R5mnqmqulm:" style="position:absolute;top:50%;transform:translateY(-50%);width:100%" class="chakra-slider__track css-8v2r68")
// div(class="css-11z5obk")
// div(aria-valuenow="6" class="chakra-slider__filled-track css-19vmo6h" style="position: absolute; top: 50%; transform: translateY(-50%); width: 20%; left: 0%;")
// div(role="slider" tabindex="0" id="slider-thumb-:R5mnqmqulm:" aria-valuemin="0" aria-valuemax="30" aria-valuenow="6" aria-orientation="horizontal" style="position: absolute; user-select: none; touch-action: none; left: calc(20% - 8px);" class="chakra-slider__thumb css-12qisgv")
// input(type="hidden" value="6")
// input(type="text" class="w-12 rounded-full bg-gray-900 text-xs text-center py-1 text-gray-200" value="6")
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">
<button aria-label="Toggle advanced options" id="advanced-options-toggle"><div class="flex flex-row justify-center"><p class="advanced-options-toggle">Hide <!-- -->Advanced Options</p></div></button>
<div class="advanced-options-container transition-all overflow-hidden opacity-100 max-h-[99rem] pb-8">
<label>Sampler</label>
<p>The diffusion sampling method.</p>
<div class="select">
<select name="advanced-options" id="advanced-options">
<option value="1" selected="">pndm (plms)</option>
<option value="0">ddim</option>
<option value="2">k_euler</option>
<option value="3">k_euler_ancestral</option>
<option value="4">k_heun</option>
<option value="5">k_dpm_2</option>
<option value="6">k_dpm_2_ancestral</option>
<option value="7">k_lms</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>
</div>
</fieldset>
</div>
<fieldset class="create-fieldset py-8">
<label>Number of Images</label>
<p>Select the number of images you would like to generate.</p>
div.flex.gap-x-2
input.bg-gray-700.border-none.outline-none(v-model="new_task.number" type="number")
//input(v-model="new_task.number" type="radio" class="radio-input" id="num-images-1" checked="")
//label(for="num-images-1") 1
// input(type="radio" class="radio-input" id="num-images-2")
//label(for="num-images-2") 2
// input(type="radio" class="radio-input" id="num-images-3")
//label(for="num-images-3") 3
// input(type="radio" class="radio-input" id="num-images-4")
//label(for="num-images-4") 4
</fieldset>
div(class="flex flex-col gap-y-4 py-8")
fieldset(class="create-fieldset")
<label>Private Session</label>
<p>Images will only be visible to you until you're ready to share them. <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, // 生成图片数量
w: 512, // 图片宽度
h: 512, // 图片高度
seed: 0, // 随机种子
sampler: 'pndm', // 扩散采样器
quality_details: 50, // 质量和细节(步数)
})
const filters = 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:'', 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-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 = () => {
fetch('/api/drawing', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(new_task.value)
}).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>