1129 lines
58 KiB
HTML
1129 lines
58 KiB
HTML
<html>
|
||
|
||
<head>
|
||
<meta charset="utf-8">
|
||
|
||
<title>{{ config.TITLE }}</title>
|
||
<meta name="description" content="Stable Diffusion Online">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||
|
||
<link href="{{ url_for('static',filename='bootstrap.min.css') }}" rel="stylesheet">
|
||
</head>
|
||
|
||
<body>
|
||
<div class="container">
|
||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||
<div class="container-fluid">
|
||
<a class="navbar-brand" href="#">{{ config.TITLE }}</a>
|
||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
|
||
aria-expanded="false" aria-label="Toggle navigation">
|
||
<span class="navbar-toggler-icon"></span>
|
||
</button>
|
||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||
<li class="nav-item">
|
||
<a class="nav-link active" data-en_XX="Home" data-zh_CN="主页" href=".">Home</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" data-en_XX="Restoration" data-zh_CN="图像修复"
|
||
href="restoration">Restoration</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" data-en_XX="Help" data-zh_CN="帮助">Help</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" data-en_XX="About" data-zh_CN="关于">About</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="input-group">
|
||
<label class="input-group-text" for="language" data-en_XX="Language"
|
||
data-zh_CN="语言">Language</label>
|
||
<select class="form-select" id="language">
|
||
<option value="zh_CN">中文(测试)</option>
|
||
<option selected value="en_XX">English</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-header">
|
||
<span>Stable Diffusion</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="col mb-3">
|
||
<div class="input-group">
|
||
<label for="apiKey" class="input-group-text" data-en_XX="API Key" data-zh_CN="API 密钥">API
|
||
Key</label>
|
||
<input type="password" class="form-control" id="apiKey" value="">
|
||
<div class="input-group-text">
|
||
<input class="form-check-input" type="checkbox" value="" id="isPrivate">
|
||
</div>
|
||
<label for="isPrivate" class="input-group-text" data-en_XX="Generate Private Images"
|
||
data-zh_CN="生成非公开图片">Generate Private Images</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="row mb-3">
|
||
<div class="input-group">
|
||
<label for="prompt" class="input-group-text" data-en_XX="This image is.."
|
||
data-zh_CN="这张图..">This image is..</label>
|
||
<input class="form-control" id="prompt" aria-describedby="promptHelp">
|
||
</div>
|
||
<div id="promptHelp" class="form-text" data-en_XX="Describe your image. Example: photo of a cat, cute, black and white. Use () to
|
||
emphasize." data-zh_CN="形容这张图。比如:一张猫的照片,可爱,黑白色。用括号()强调重要性。">Describe your image. Example: photo
|
||
of a cat,
|
||
cute, black and white. Use () to emphasize.</div>
|
||
</div>
|
||
<div class="row mb-3">
|
||
<div class="input-group">
|
||
<label for="negPrompt" class="input-group-text" data-en_XX="This image is NOT.."
|
||
data-zh_CN="这张图不是..">This image is NOT..</label>
|
||
<input type="text" class="form-control" id="negPrompt" aria-describedby="negPromptHelp">
|
||
</div>
|
||
<div id="negPromptHelp" class="form-text" data-en_XX="Optional. Describe what's NOT the image."
|
||
data-zh_CN="非必填。形容这张图不长什么样。">Optional. Describe what's NOT the image.</div>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-md-3 mb-3">
|
||
<div class="input-group input-group-sm">
|
||
<label for="inputSeed" class="input-group-text" data-en_XX="Seed"
|
||
data-zh_CN="随机数">Seed</label>
|
||
<input type="text" class="form-control" id="inputSeed" aria-describedby="inputSeedHelp">
|
||
</div>
|
||
<div id="inputSeedHelp" class="form-text"
|
||
data-en_XX="Leave it empty or set 0 to use a random seed" data-zh_CN="非必填。留白或填0使用默认随机数">
|
||
Leave it empty or set 0 to use a random seed
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3 mb-3">
|
||
<div class="input-group input-group-sm">
|
||
<label for="guidanceScale" class="input-group-text" data-en_XX="Guidance"
|
||
data-zh_CN="指示强度">Guidance</label>
|
||
<input type="number" class="form-control" id="inputGuidanceScale"
|
||
aria-describedby="inputGuidanceScaleHelp" placeholder="7.5" min="1" max="30">
|
||
</div>
|
||
<div id="inputGuidanceScaleHelp" class="form-text"
|
||
data-en_XX="20 = follow prompt, 7 = creative/artistic. Lower it if you see bad images."
|
||
data-zh_CN="20 = 提示词重要,7 = 更有创造性。如果结果较差,适当降低该数。">
|
||
20 = follow prompt, 7 = creative/artistic. Lower it if you see bad images.
|
||
</div>
|
||
</div>
|
||
<div class="col-md-2 mb-3">
|
||
<div class="input-group input-group-sm">
|
||
<label for="inputSteps" class="input-group-text" data-en_XX="Steps"
|
||
data-zh_CN="迭代次数">Steps</label>
|
||
<input type="number" class="form-control" id="inputSteps" aria-describedby="inputStepsHelp"
|
||
placeholder="50">
|
||
</div>
|
||
<div id="inputStepsHelp" class="form-text"
|
||
data-en_XX="More steps better image but longer time to generate"
|
||
data-zh_CN="迭代次数越多图片越好,但生成时间越久">More steps better image but longer time to generate
|
||
</div>
|
||
</div>
|
||
<div class="col-md-2 mb-3">
|
||
<div class="input-group input-group-sm">
|
||
<label for="inputWidth" class="input-group-text" data-en_XX="Width"
|
||
data-zh_CN="图片宽度">Width</label>
|
||
<input type="number" class="form-control" id="inputWidth" placeholder="512" min="1"
|
||
max="1024">
|
||
</div>
|
||
</div>
|
||
<div class="col-md-2 mb-3">
|
||
<div class="input-group input-group-sm">
|
||
<label for="inputHeight" class="input-group-text" data-en_XX="Height"
|
||
data-zh_CN="图片高度">Height</label>
|
||
<input type="number" class="form-control" id="inputHeight" placeholder="512" min="1"
|
||
max="1024">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="stable-diffusion card-header">
|
||
<ul class="nav nav-pills card-header-pills">
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="#card-txt" data-en_XX="Text-to-Image"
|
||
data-zh_CN="文字->图片">Text-to-Image</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="#card-img" data-en_XX="Image-to-Image"
|
||
data-zh_CN="图片->图片">Image-to-Image</a>
|
||
</li>
|
||
<li class="nav-item">
|
||
<a class="nav-link" href="#card-inpainting" data-en_XX="Inpainting"
|
||
data-zh_CN="图片修复">Inpainting</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<div class="card-body stable-diffusion-specific" id="card-img" style="display:none">
|
||
<div class="row">
|
||
<div class="col-md-4">
|
||
<div class="card">
|
||
<div class="card-header" data-en_XX="1. Choose Reference Image"
|
||
data-zh_CN="1. 选择参照图">
|
||
1. Choose Reference Image
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<button id="copy-txt-to-img" class="btn btn-primary mb-3"
|
||
data-en_XX="Copy from text-to-image" data-zh_CN="从【文字->图片】结果复制">Copy
|
||
from text-to-image</button>
|
||
<button id="copy-last-img" class="btn btn-primary mb-3"
|
||
data-en_XX="Copy from last image result" data-zh_CN="从【图片->图片】结果复制">Copy
|
||
from last image result</button>
|
||
<button id="upload-img" class="btn btn-primary mb-3"
|
||
data-en_XX="Upload image" data-zh_CN="上传一张图片">Upload
|
||
image</button>
|
||
</div>
|
||
<div class="row mb-3">
|
||
<div class="input-group input-group-sm">
|
||
<label for="strength" class="input-group-text" data-en_XX="Strength"
|
||
data-zh_CN="改变程度">Strength</label>
|
||
<input type="number" class="form-control" id="inputStrength"
|
||
aria-describedby="inputStrengthHelp" placeholder="0.5" min="0"
|
||
max="1">
|
||
</div>
|
||
<div id="inputStrengthHelp" class="form-text"
|
||
data-en_XX="How different from the original image. 0 means the same, 1 means very different."
|
||
data-zh_CN="和参照图有多么的不同。0指一样,1指非常不一样。">How different from the
|
||
original image
|
||
</div>
|
||
</div>
|
||
<div class="row">
|
||
<button id="newImg2ImgJob" class="btn btn-primary mb-3"
|
||
data-en_XX="Let's Go with Image Below!" data-zh_CN="就用下面的图生成!">Let's
|
||
Go with Image Below!</button>
|
||
</div>
|
||
</div>
|
||
<img class="card-img-bottom" id="reference-img">
|
||
</div>
|
||
</div>
|
||
<div class="col-md-8">
|
||
<div class="card">
|
||
<div class="card-header" data-en_XX="2. Result" data-zh_CN="2. 结果">
|
||
2. Result
|
||
</div>
|
||
<div class="card-body">
|
||
<ul class="list-group">
|
||
<li
|
||
class="list-group-item d-flex justify-content-between align-items-center">
|
||
<span id="img2ImgJobUUID"></span>
|
||
<span class="badge bg-primary rounded-pill" data-en_XX="Job UUID"
|
||
data-zh_CN="图片唯一识别码">Job UUID</span>
|
||
</li>
|
||
<li
|
||
class="list-group-item d-flex justify-content-between align-items-center">
|
||
<span id="img2ImgStatus"></span>
|
||
<span class="badge bg-primary rounded-pill" data-en_XX="Job Status"
|
||
data-zh_CN="生成状态">Job Status</span>
|
||
</li>
|
||
<li
|
||
class="list-group-item d-flex justify-content-between align-items-center">
|
||
<span id="img2ImgSeed"></span>
|
||
<span class="badge bg-primary rounded-pill" data-en_XX="Job Seed"
|
||
data-zh_CN="图片随机数">Job Seed</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<img class="card-img-bottom" id="img2ImgImg">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-body stable-diffusion-specific" id="card-inpainting" style="display:none">
|
||
<div class="row">
|
||
<div class="col-md-4">
|
||
<div class="card">
|
||
<div class="card-header" data-en_XX="1. Choose Original Image" data-zh_CN="1. 选择原图">
|
||
1. Choose Original Image
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<button id="copy-txt-to-img-inpaint" class="btn btn-primary mb-3"
|
||
data-en_XX="Copy from text-to-image" data-zh_CN="从【文字->图片】结果复制">Copy
|
||
from text-to-image</button>
|
||
<button id="copy-last-img-inpaint" class="btn btn-primary mb-3"
|
||
data-en_XX="Copy from last image result" data-zh_CN="从【图片->图片】结果复制">Copy
|
||
from last image result</button>
|
||
<button id="upload-img-inpaint" class="btn btn-primary mb-3"
|
||
data-en_XX="Upload image" data-zh_CN="上传一张图片">Upload
|
||
image</button>
|
||
</div>
|
||
</div>
|
||
<img class="card-img-bottom" id="inpaint-img">
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="card">
|
||
<div class="card-header" data-en_XX="2. Remove Area (Mask)"
|
||
data-zh_CN="2. 抹去要修复的部分">
|
||
2. Remove Area (Mask)
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<button id="newInpaintingJob" class="btn btn-primary mb-3"
|
||
data-en_XX="Let's Go with Image + Mask Below!" data-zh_CN="进行修复!">Let's
|
||
Go with Image + Mask Below!</button>
|
||
</div>
|
||
<div class="input-group mb-3">
|
||
<button id="inpaint-reset" class="btn btn-outline-secondary"
|
||
data-en_XX="Reset" data-zh_CN="重置">Reset</button>
|
||
<button id="inpaint-undo" class="btn btn-outline-secondary"
|
||
data-en_XX="Undo" data-zh_CN="撤销">Undo</button>
|
||
<button id="inpaint-redo" class="btn btn-outline-secondary"
|
||
data-en_XX="Redo" data-zh_CN="重做">Redo</button>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="inpaint-strike-size" class="form-label" data-en_XX="Strike Size"
|
||
data-zh_CN="笔触大小">Strike Size</label>
|
||
<input type="range" class="form-range" min="1" max="30"
|
||
id="inpaint-strike-size">
|
||
<output id='range-value'></output>
|
||
</div>
|
||
</div>
|
||
<div class="card-img-bottom" style="position: relative;">
|
||
<img id="inpaint-img-for-mask" width="100%">
|
||
<div id="inpaint-mask-canvas-container"
|
||
style="cursor: pointer; position: absolute; top: 0; left: 0;">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-4">
|
||
<div class="card">
|
||
<div class="card-header" data-en_XX="3. Result" data-zh_CN="3. 结果">
|
||
3. Result
|
||
</div>
|
||
<div class="card-body">
|
||
<ul class="list-group">
|
||
<li
|
||
class="list-group-item d-flex justify-content-between align-items-center">
|
||
<span id="inpaintJobUUID"></span>
|
||
<span class="badge bg-primary rounded-pill" data-en_XX="Job UUID"
|
||
data-zh_CN="图片唯一识别码">Job UUID</span>
|
||
</li>
|
||
<li
|
||
class="list-group-item d-flex justify-content-between align-items-center">
|
||
<span id="inpaintStatus"></span>
|
||
<span class="badge bg-primary rounded-pill" data-en_XX="Job Status"
|
||
data-zh_CN="生成状态">Job Status</span>
|
||
</li>
|
||
<li
|
||
class="list-group-item d-flex justify-content-between align-items-center">
|
||
<span id="inpaintSeed"></span>
|
||
<span class="badge bg-primary rounded-pill" data-en_XX="Job Seed"
|
||
data-zh_CN="图片随机数">Job Seed</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<img class="card-img-bottom" id="inpaintImg">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="card-body stable-diffusion-specific" id="card-txt" style="display:none">
|
||
<div class="row mb-3">
|
||
<div class="col-md-4">
|
||
<div class="card">
|
||
<div class="card-header" data-en_XX="1. Options" data-zh_CN="1. 选项">
|
||
1. Options
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<button id="newTxt2ImgJob" class="btn btn-primary mb-3"
|
||
data-en_XX="Let's Go!" data-zh_CN="生成图片!">Let's Go!</button>
|
||
</div>
|
||
</div>
|
||
<img class="card-img-bottom" id="reference-img">
|
||
</div>
|
||
</div>
|
||
<div class="col-md-8">
|
||
<div class="card">
|
||
<div class="card-header" data-en_XX="2. Result" data-zh_CN="2. 结果">
|
||
2. Result
|
||
</div>
|
||
<div class="card-body">
|
||
<ul class="list-group">
|
||
<li
|
||
class="list-group-item d-flex justify-content-between align-items-center">
|
||
<span id="txt2ImgJobUUID"></span>
|
||
<span class="badge bg-primary rounded-pill" data-en_XX="Job UUID"
|
||
data-zh_CN="图片唯一识别码">Job UUID</span>
|
||
</li>
|
||
<li
|
||
class="list-group-item d-flex justify-content-between align-items-center">
|
||
<span id="txt2ImgStatus"></span>
|
||
<span class="badge bg-primary rounded-pill" data-en_XX="Job Status"
|
||
data-zh_CN="生成状态">Job Status</span>
|
||
</li>
|
||
<li
|
||
class="list-group-item d-flex justify-content-between align-items-center">
|
||
<span id="txt2ImgSeed"></span>
|
||
<span class="badge bg-primary rounded-pill" data-en_XX="Job Seed"
|
||
data-zh_CN="图片随机数">Job Seed</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
<img class="card-img-bottom" id="txt2ImgImg">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-header">
|
||
<span data-en_XX="History" data-zh_CN="历史">History</span>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="input-group mb-3">
|
||
<label for="lookupUUID" class="input-group-text" data-en_XX="UUID (Optional)"
|
||
data-zh_CN="图片唯一识别码(选填)">UUID (Optional)</label>
|
||
<input type="text" class="form-control" id="lookupUUID" value="">
|
||
<button id="getJobHistory" class="btn btn-primary" data-en_XX="Get Job(s)" data-zh_CN="搜索历史">Get
|
||
Job(s)</button>
|
||
</div>
|
||
<div id="joblist"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-3">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<span data-en_XX="Get Inspirations from Others" data-zh_CN="从别人那里找灵感">Get Inspirations from
|
||
Others</span>
|
||
<button id="feelingLucky" class="btn btn-outline-secondary" data-en_XX="Change a batch"
|
||
data-zh_CN="换一批">Change a batch</button>
|
||
</div>
|
||
<div class="card-body">
|
||
<div id="othersJobList"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- CSS code -->
|
||
<style>
|
||
.private-card {
|
||
filter: blur(12px);
|
||
opacity: 0.7;
|
||
transition: filter 0.3s ease, opacity 0.3s ease;
|
||
}
|
||
|
||
.reveal-text {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
color: white;
|
||
border-radius: 10px;
|
||
transition: filter 0.3s ease, opacity 0.3s ease;
|
||
text-align: center;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.flying-value {
|
||
position: absolute;
|
||
z-index: 9999;
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #000;
|
||
}
|
||
|
||
.reveal-text svg {
|
||
width: 80px;
|
||
height: 80px;
|
||
fill: white;
|
||
filter: drop-shadow(2px 2px 4px rgba(126, 126, 126, 0.8));
|
||
}
|
||
</style>
|
||
<script src="{{ url_for('static',filename='jquery-3.6.1.min.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='bootstrap.bundle.min.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='jsketch.min.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='jquery.sketchable.min.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='jquery.sketchable.memento.min.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='masonry.pkgd.min.js') }}"></script>
|
||
<script src="{{ url_for('static',filename='imagesloaded.pkgd.min.js') }}"></script>
|
||
|
||
<script>
|
||
function waitForImage(apikeyVal, uuidValue) {
|
||
// Wait until image is done
|
||
$.ajax({
|
||
type: 'POST',
|
||
url: '/get_jobs',
|
||
contentType: 'application/json; charset=utf-8',
|
||
dataType: 'json',
|
||
data: JSON.stringify({ 'apikey': apikeyVal, 'uuid': uuidValue }),
|
||
success: function (response) {
|
||
console.log(response);
|
||
if (response.jobs.length == 1) {
|
||
if (response.jobs[0].type == 'txt') {
|
||
$('#txt2ImgStatus').html(response.jobs[0].status);
|
||
$('#txt2ImgSeed').html(response.jobs[0].seed);
|
||
$('#txt2ImgJobUUID').html(uuidValue);
|
||
if (response.jobs[0].status == "done") {
|
||
$('#txt2ImgImg').attr('src', response.jobs[0].img);
|
||
return;
|
||
}
|
||
if (response.jobs[0].status == "failed") {
|
||
return;
|
||
}
|
||
} else if (response.jobs[0].type == 'img') {
|
||
$('#img2ImgStatus').html(response.jobs[0].status);
|
||
$('#img2ImgSeed').html(response.jobs[0].seed);
|
||
$('#img2ImgJobUUID').html(uuidValue);
|
||
if (response.jobs[0].status == "done") {
|
||
$('#img2ImgImg').attr('src', response.jobs[0].img);
|
||
return;
|
||
}
|
||
if (response.jobs[0].status == "failed") {
|
||
return;
|
||
}
|
||
} else if (response.jobs[0].type == 'inpaint') {
|
||
$('#inpaintStatus').html(response.jobs[0].status);
|
||
$('#inpaintSeed').html(response.jobs[0].seed);
|
||
$('#inpaintJobUUID').html(uuidValue);
|
||
if (response.jobs[0].status == "done") {
|
||
$('#inpaintImg').attr('src', response.jobs[0].img);
|
||
return;
|
||
}
|
||
if (response.jobs[0].status == "failed") {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
setTimeout(function () { waitForImage(apikeyVal, uuidValue); }, 1500); // refresh every 1.5 second
|
||
},
|
||
error: function (xhr, status, error) {
|
||
console.log(error);
|
||
setTimeout(function () { waitForImage(apikeyVal, uuidValue); }, 1500); // refresh every 1.5 second
|
||
}
|
||
});
|
||
}
|
||
|
||
function truncateText(text, maxLength) {
|
||
if (text.length <= maxLength) {
|
||
return text; // No truncation necessary
|
||
}
|
||
|
||
// Truncate the text and add an ellipsis (...) at the end
|
||
return text.slice(0, maxLength) + '...';
|
||
}
|
||
|
||
function parsePromptString(prompt, is_negative) {
|
||
// Split the prompt input by ","
|
||
var promptArray = prompt.replace(/[^\w\s,\p{Script=Han}]|\d/gu, '').split(/[,,]/);
|
||
|
||
var result = "";
|
||
for (var i = 0; i < promptArray.length; i++) {
|
||
var trimmedString = promptArray[i].trim();
|
||
|
||
// Truncate the split string if it exceeds 100 words
|
||
if (trimmedString.split(' ').length > 100) {
|
||
trimmedString = trimmedString.split(' ').slice(0, 100).join(' ') + '...';
|
||
}
|
||
|
||
if (trimmedString.length > 0) {
|
||
// Wrap the string in a <span> element with required attributes and events
|
||
var spanElement = $("<span class='" + (is_negative ? "negative-prompt-example" : "prompt-example") + " badge rounded-pill text-bg-light' style='white-space:normal'>")
|
||
.text(trimmedString)
|
||
.css("cursor", "pointer");
|
||
|
||
result += spanElement[0].outerHTML;
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
$(document).ready(function () {
|
||
$(document).on("click", ".negative-prompt-example, .prompt-example", function () {
|
||
var clickedText = $(this).text();
|
||
var inputId = ($(this).hasClass("negative-prompt-example")) ? "#negPrompt" : "#prompt";
|
||
var input = $(inputId);
|
||
var key = input.attr('id');
|
||
var currentValue = input.val();
|
||
if (currentValue && currentValue.includes(clickedText)) {
|
||
// If currentValue already contains clickedText, do nothing
|
||
return;
|
||
}
|
||
var newValue = (currentValue === "" ? "" : (currentValue + ", ")) + clickedText;
|
||
|
||
// Create a temporary span element to hold the flying value
|
||
var flyingValue = $('<span class="flying-value"></span>').text(clickedText);
|
||
|
||
// Append the temporary span to the body
|
||
$('body').append(flyingValue);
|
||
|
||
// Get the position of the clicked element
|
||
var clickedPosition = $(this).offset();
|
||
|
||
// Animate the flying value to the input field
|
||
flyingValue.css({
|
||
top: clickedPosition.top,
|
||
left: clickedPosition.left
|
||
}).animate({
|
||
top: input.offset().top,
|
||
left: input.offset().left,
|
||
opacity: 0
|
||
}, 500, function () {
|
||
// Remove the temporary span
|
||
flyingValue.remove();
|
||
});
|
||
|
||
input.val(newValue);
|
||
localStorage.setItem(key, newValue);
|
||
});
|
||
|
||
$(document).on({
|
||
mouseenter: function () {
|
||
$(this).toggleClass("text-bg-light text-bg-dark");
|
||
},
|
||
mouseleave: function () {
|
||
$(this).toggleClass("text-bg-light text-bg-dark");
|
||
}
|
||
}, ".negative-prompt-example, .prompt-example");
|
||
|
||
$('input[id]').on('input', function () {
|
||
var input = $(this);
|
||
var key = input.attr('id');
|
||
var value = input.val();
|
||
if (input.attr('type') == "checkbox") {
|
||
value = input.is(":checked");
|
||
}
|
||
localStorage.setItem(key, value);
|
||
}).each(function () {
|
||
var input = $(this);
|
||
var key = input.attr('id');
|
||
var value = localStorage.getItem(key);
|
||
if (input.attr('type') == "checkbox") {
|
||
input.prop('checked', value == "true");
|
||
} else if (value) {
|
||
input.val(value);
|
||
}
|
||
});
|
||
|
||
// Cache variable to store the selected image data for img2img
|
||
var imageData = null;
|
||
|
||
$("#copy-txt-to-img, #copy-last-img").click(function () {
|
||
var data;
|
||
var sourceId = $(this).data("source-id");
|
||
data = $("#" + sourceId).attr("src");
|
||
if (data == null || data == "") {
|
||
alert("Nothing found from the result");
|
||
return;
|
||
}
|
||
imageData = data;
|
||
$("#reference-img").attr("src", imageData);
|
||
});
|
||
|
||
$('#txt2ImgJobUUID, #img2ImgJobUUID, #inpaintJobUUID').click(function () {
|
||
var jobUUID = $(this).html();
|
||
$('#lookupUUID').val(jobUUID);
|
||
});
|
||
|
||
$('#getJobHistory').click(function () {
|
||
var apikeyValue = $('#apiKey').val();
|
||
var uuidValue = $('#lookupUUID').val();
|
||
$.ajax({
|
||
type: 'POST',
|
||
url: '/get_jobs',
|
||
contentType: 'application/json; charset=utf-8',
|
||
dataType: 'json',
|
||
data: JSON.stringify({ 'apikey': apikeyValue, 'uuid': uuidValue, 'type': 'txt,img,inpaint' }),
|
||
success: function (response) {
|
||
var jobsLength = response.jobs.length;
|
||
if (jobsLength == 0) {
|
||
$('#joblist').html("found nothing");
|
||
return;
|
||
}
|
||
|
||
var $joblist = $('#joblist');
|
||
var $grid = $('<div class="row"></div>');
|
||
$joblist.html($grid);
|
||
for (var i = 0; i < jobsLength; i++) {
|
||
var isPrivate = response.jobs[i].is_private;
|
||
var element = $("<div class='col col-sm-6 col-md-6 col-lg-4 mb-3'><div class='card'>" +
|
||
(response.jobs[i].img ? ("<img src='" + response.jobs[i].img + "' class='card-img-top'><div class='card-body'>") : "") +
|
||
"<ul class='list-group list-group-flush'>" +
|
||
"<li class='list-group-item'>status: " + response.jobs[i].status + "</li>" +
|
||
"<li class='list-group-item'>prompt: " + parsePromptString(response.jobs[i].prompt, false) + "</li>" +
|
||
"<li class='list-group-item'>neg prompt: " + parsePromptString(response.jobs[i].neg_prompt, true) + "</li>" +
|
||
"<li class='list-group-item'>seed: " + response.jobs[i].seed + "</li>" +
|
||
"<li class='list-group-item'>uuid: " + response.jobs[i].uuid + "</li>" +
|
||
"<li class='list-group-item'>w x h: " + response.jobs[i].width + " x " + response.jobs[i].height + "</li>" +
|
||
"</ul></div>" +
|
||
(response.jobs[i].ref_img ? ("<img src='" + response.jobs[i].ref_img + "' class='card-img-bottom'>") : "") +
|
||
"</div></div>");
|
||
// Add event handler for click to toggle blurriness
|
||
if (isPrivate === 1) {
|
||
element.find('.card').addClass('private-card');
|
||
element.append("<div class='reveal-text'><svg viewBox='0 0 16 16'><path d='M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z'/></svg></div>");
|
||
|
||
element.on('click', function (event) {
|
||
if ($(event.target).hasClass('negative-prompt-example') || $(event.target).hasClass('prompt-example')) {
|
||
return; // Ignore the click event for those elements
|
||
}
|
||
$(this).find('.card').toggleClass('private-card');
|
||
$(this).find('.reveal-text').toggle();
|
||
});
|
||
}
|
||
$grid.append(element);
|
||
};
|
||
$grid.imagesLoaded().progress(function () {
|
||
$grid.masonry({
|
||
itemSelector: '.col',
|
||
columnWidth: '.col',
|
||
percentPosition: true
|
||
});
|
||
});
|
||
|
||
},
|
||
error: function (xhr, status, error) {
|
||
// Handle error response
|
||
console.log(xhr.responseText);
|
||
$('#joblist').html("found nothing");
|
||
}
|
||
});
|
||
});
|
||
|
||
$('#feelingLucky').click(function () {
|
||
$.ajax({
|
||
type: 'GET',
|
||
url: '/random_jobs',
|
||
contentType: 'application/json; charset=utf-8',
|
||
dataType: 'json',
|
||
success: function (response) {
|
||
var jobsLength = response.jobs.length;
|
||
if (jobsLength == 0) {
|
||
$('#othersJobList').html("found nothing");
|
||
return;
|
||
}
|
||
|
||
var $joblist = $('#othersJobList');
|
||
var $grid = $('<div class="row"></div>');
|
||
$joblist.html($grid);
|
||
for (var i = 0; i < jobsLength; i++) {
|
||
var element = ("<div class='col col-sm-6 col-md-6 col-lg-4 mb-3'><div class='card'>" +
|
||
(response.jobs[i].img ? ("<img src='" + response.jobs[i].img + "' class='card-img-top'><div class='card-body'>") : "") +
|
||
"<ul class='list-group list-group-flush'>" +
|
||
"<li class='list-group-item'>prompt: " + parsePromptString(response.jobs[i].prompt, false) + "</li>" +
|
||
"<li class='list-group-item'>neg prompt: " + parsePromptString(response.jobs[i].neg_prompt, true) + "</li>" +
|
||
"<li class='list-group-item'>seed: " + response.jobs[i].seed + "</li>" +
|
||
"<li class='list-group-item'>w x h: " + response.jobs[i].width + " x " + response.jobs[i].height + "</li>" +
|
||
"</ul>" +
|
||
"</div></div></div>")
|
||
$grid.append(element);
|
||
};
|
||
$grid.imagesLoaded().progress(function () {
|
||
$grid.masonry({
|
||
itemSelector: '.col',
|
||
columnWidth: '.col',
|
||
percentPosition: true
|
||
});
|
||
});
|
||
|
||
},
|
||
error: function (xhr, status, error) {
|
||
$('#othersJobList').html(error);
|
||
}
|
||
});
|
||
});
|
||
$('#feelingLucky').click();
|
||
|
||
$("#upload-img").click(function () {
|
||
var input = $("<input type='file' accept='image/*'>").on("change", function () {
|
||
var reader = new FileReader();
|
||
reader.onload = function (e) {
|
||
imageData = e.target.result;
|
||
$("#reference-img").attr("src", imageData);
|
||
};
|
||
reader.readAsDataURL(this.files[0]);
|
||
});
|
||
input.click();
|
||
});
|
||
|
||
var activeLink = localStorage.getItem('activeLink') || $('.stable-diffusion .nav-link:first').attr('href');
|
||
|
||
// Set the active link as the current active link
|
||
$('.stable-diffusion .nav-link[href="' + activeLink + '"]').addClass('active');
|
||
$(activeLink).show();
|
||
|
||
$(".stable-diffusion .nav-link").click(function (e) {
|
||
e.preventDefault();
|
||
var target = $(this).attr("href"); // get the href value of the clicked link
|
||
|
||
// hide all card divs and show the corresponding one
|
||
$(".stable-diffusion-specific").hide();
|
||
$(".stable-diffusion .nav-link").removeClass("active");
|
||
$(this).addClass("active");
|
||
$(target).show();
|
||
|
||
localStorage.setItem('activeLink', target);
|
||
});
|
||
|
||
|
||
function submitJob(formData, uuidSelector, statusSelector) {
|
||
$.ajax({
|
||
type: 'POST',
|
||
url: '/add_job',
|
||
contentType: 'application/json; charset=utf-8',
|
||
dataType: 'json',
|
||
data: JSON.stringify(formData),
|
||
success: function (response) {
|
||
if (response.uuid) {
|
||
$(uuidSelector).html(response.uuid);
|
||
}
|
||
$(statusSelector).html('Submitting new job..');
|
||
waitForImage(formData.apikey, response.uuid);
|
||
},
|
||
error: function (xhr, status, error) {
|
||
// Handle error response
|
||
console.log(xhr.responseText);
|
||
$(statusSelector).html('Failed');
|
||
}
|
||
});
|
||
}
|
||
$('#newTxt2ImgJob').click(function (e) {
|
||
e.preventDefault(); // Prevent the default form submission
|
||
|
||
// Helper function to get input field value or a default value if empty
|
||
function getInputValue(id, defaultValue) {
|
||
var value = $(id).val().trim();
|
||
return value !== '' ? value : defaultValue;
|
||
}
|
||
|
||
// Validate input field values
|
||
var promptVal = getInputValue('#prompt', '');
|
||
var guidanceScaleVal = parseFloat(getInputValue('#inputGuidanceScale', '7.5'));
|
||
var stepsVal = parseInt(getInputValue('#inputSteps', '50'));
|
||
var widthVal = parseInt(getInputValue('#inputWidth', '512'));
|
||
var heightVal = parseInt(getInputValue('#inputHeight', '512'));
|
||
|
||
if (promptVal === '') {
|
||
alert("Missing prompt!");
|
||
return;
|
||
}
|
||
|
||
if (guidanceScaleVal < 1 || guidanceScaleVal > 30) {
|
||
alert("Guidance scale must be between 1 and 30!");
|
||
return;
|
||
}
|
||
|
||
if (widthVal < 8 || widthVal > 960 || widthVal % 8 !== 0) {
|
||
alert("Width must be between 8 and 960 and divisible by 8!");
|
||
return;
|
||
}
|
||
|
||
if (heightVal < 8 || heightVal > 960 || heightVal % 8 !== 0) {
|
||
alert("Height must be between 8 and 960 and divisible by 8!");
|
||
return;
|
||
}
|
||
|
||
var apikeyVal = $('#apiKey').val();
|
||
var negPromptVal = $('#negPrompt').val();
|
||
var seedVal = getInputValue('#inputSeed', '0');
|
||
|
||
var formData = {
|
||
'apikey': apikeyVal,
|
||
'type': 'txt',
|
||
'prompt': promptVal,
|
||
'seed': seedVal,
|
||
'steps': stepsVal,
|
||
'width': widthVal,
|
||
'height': heightVal,
|
||
'lang': $("#language option:selected").val(),
|
||
'guidance_scale': guidanceScaleVal,
|
||
'neg_prompt': negPromptVal,
|
||
'is_private': $('#isPrivate').is(":checked") ? 1 : 0
|
||
};
|
||
|
||
submitJob(formData, '#txt2ImgJobUUID', '#txt2ImgStatus');
|
||
});
|
||
|
||
$('#newImg2ImgJob').click(function (e) {
|
||
e.preventDefault(); // Prevent the default form submission
|
||
|
||
if (imageData == null) {
|
||
alert("No image cached");
|
||
return;
|
||
}
|
||
|
||
// Helper function to get input field value or a default value if empty
|
||
function getInputValue(id, defaultValue) {
|
||
var value = $(id).val().trim();
|
||
return value !== '' ? value : defaultValue;
|
||
}
|
||
|
||
// Validate input field values
|
||
var promptVal = getInputValue('#prompt', '');
|
||
var guidanceScaleVal = parseFloat(getInputValue('#inputGuidanceScale', '25.0'));
|
||
var stepsVal = parseInt(getInputValue('#inputSteps', '50'));
|
||
var widthVal = parseInt(getInputValue('#inputWidth', '512'));
|
||
var heightVal = parseInt(getInputValue('#inputHeight', '512'));
|
||
|
||
if (promptVal === '') {
|
||
alert("Missing prompt!");
|
||
return;
|
||
}
|
||
|
||
if (guidanceScaleVal < 1 || guidanceScaleVal > 30) {
|
||
alert("Guidance scale must be between 1 and 30!");
|
||
return;
|
||
}
|
||
|
||
if (widthVal < 8 || widthVal > 960 || widthVal % 8 !== 0) {
|
||
alert("Width must be between 8 and 960 and divisible by 8!");
|
||
return;
|
||
}
|
||
|
||
if (heightVal < 8 || heightVal > 960 || heightVal % 8 !== 0) {
|
||
alert("Height must be between 8 and 960 and divisible by 8!");
|
||
return;
|
||
}
|
||
|
||
var apikeyVal = $('#apiKey').val();
|
||
var negPromptVal = $('#negPrompt').val();
|
||
var seedVal = $('#inputSeed').val();
|
||
|
||
if (seedVal == "0" || seedVal == "") {
|
||
seedVal = "0";
|
||
}
|
||
|
||
var strengthVal = parseFloat(getInputValue('#inputStrength', '0.5'));
|
||
|
||
if (strengthVal < 0 || strengthVal > 1) {
|
||
alert("Strength must be between 0 and 1!");
|
||
return;
|
||
}
|
||
|
||
var formData = {
|
||
'apikey': apikeyVal,
|
||
'type': 'img',
|
||
'ref_img': imageData,
|
||
'prompt': promptVal,
|
||
'seed': seedVal,
|
||
'steps': stepsVal,
|
||
'width': widthVal,
|
||
'height': heightVal,
|
||
'lang': $("#language option:selected").val(),
|
||
'guidance_scale': guidanceScaleVal,
|
||
'strength': strengthVal,
|
||
'neg_prompt': negPromptVal,
|
||
'is_private': $('#isPrivate').is(":checked") ? 1 : 0
|
||
};
|
||
|
||
submitJob(formData, '#img2ImgJobUUID', '#img2ImgStatus');
|
||
});
|
||
|
||
$('#newInpaintingJob').click(function (e) {
|
||
e.preventDefault(); // Prevent the default form submission
|
||
|
||
if (inpaintOriginalImg == null) {
|
||
alert("No image cached");
|
||
return;
|
||
}
|
||
|
||
var canvas = $('#inpaint-img-mask')[0];
|
||
var ctx = canvas.getContext('2d');
|
||
var maskImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||
|
||
// Loop through the pixels and change the colors
|
||
for (var i = 0; i < maskImageData.data.length; i += 4) {
|
||
if (maskImageData.data[i + 3] == 0) { // If pixel is transparent, change to black
|
||
maskImageData.data[i] = 0;
|
||
maskImageData.data[i + 1] = 0;
|
||
maskImageData.data[i + 2] = 0;
|
||
maskImageData.data[i + 3] = 255;
|
||
} else { // If pixel is not transparent, change to white
|
||
maskImageData.data[i] = 255;
|
||
maskImageData.data[i + 1] = 255;
|
||
maskImageData.data[i + 2] = 255;
|
||
maskImageData.data[i + 3] = 255;
|
||
}
|
||
}
|
||
|
||
var tempCanvas = document.createElement('canvas'); // Create a new canvas element
|
||
tempCanvas.width = canvas.width; // Set the width of the new canvas to match the original canvas
|
||
tempCanvas.height = canvas.height; // Set the height of the new canvas to match the original canvas
|
||
var tempCtx = tempCanvas.getContext('2d');
|
||
tempCtx.putImageData(maskImageData, 0, 0); // Put modified image data onto the new canvas
|
||
var inpaintMaskImg = tempCanvas.toDataURL(); // Get the modified base64-encoded image data
|
||
|
||
// Helper function to get input field value or a default value if empty
|
||
function getInputValue(id, defaultValue) {
|
||
var value = $(id).val().trim();
|
||
return value !== '' ? value : defaultValue;
|
||
}
|
||
|
||
// Validate input field values
|
||
var apikeyVal = $('#apiKey').val();
|
||
var promptVal = getInputValue('#prompt', '');
|
||
var negPromptVal = $('#negPrompt').val();
|
||
var seedVal = getInputValue('#inputSeed', '0');
|
||
var guidanceScaleVal = parseFloat(getInputValue('#inputGuidanceScale', '25.0'));
|
||
var stepsVal = parseInt(getInputValue('#inputSteps', '50'));
|
||
var widthVal = parseInt(getInputValue('#inputWidth', '512'));
|
||
var heightVal = parseInt(getInputValue('#inputHeight', '512'));
|
||
|
||
if (promptVal === '') {
|
||
alert("Missing prompt!");
|
||
return;
|
||
}
|
||
|
||
if (guidanceScaleVal < 1 || guidanceScaleVal > 30) {
|
||
alert("Guidance scale must be between 1 and 30!");
|
||
return;
|
||
}
|
||
|
||
if (widthVal < 8 || widthVal > 960 || widthVal % 8 !== 0) {
|
||
alert("Width must be between 8 and 960 and divisible by 8!");
|
||
return;
|
||
}
|
||
|
||
if (heightVal < 8 || heightVal > 960 || heightVal % 8 !== 0) {
|
||
alert("Height must be between 8 and 960 and divisible by 8!");
|
||
return;
|
||
}
|
||
|
||
var formData = {
|
||
'apikey': apikeyVal,
|
||
'type': 'inpaint',
|
||
'ref_img': inpaintOriginalImg,
|
||
'mask_img': inpaintMaskImg,
|
||
'prompt': promptVal,
|
||
'seed': seedVal,
|
||
'steps': stepsVal,
|
||
'width': widthVal,
|
||
'height': heightVal,
|
||
'lang': $("#language option:selected").val(),
|
||
'guidance_scale': guidanceScaleVal,
|
||
'neg_prompt': negPromptVal,
|
||
'is_private': $('#isPrivate').is(":checked") ? 1 : 0
|
||
};
|
||
|
||
submitJob(formData, '#inpaintJobUUID', '#inpaintStatus');
|
||
});
|
||
|
||
// Define the function to update the text based on the selected language
|
||
function updateText(language) {
|
||
$("[data-" + language + "]").each(function () {
|
||
$(this).text($(this).data(language.toLowerCase()));
|
||
});
|
||
}
|
||
|
||
// Listen for changes to the select element
|
||
$("#language").change(function () {
|
||
// Get the newly selected value
|
||
var newLanguage = $(this).val();
|
||
|
||
// Store the selected value in cache
|
||
localStorage.setItem("selectedLanguage", newLanguage);
|
||
|
||
// Update the text based on the selected language
|
||
updateText(newLanguage);
|
||
});
|
||
|
||
// Get the selected value from cache (if it exists)
|
||
var cachedLanguage = localStorage.getItem("selectedLanguage");
|
||
if (cachedLanguage) {
|
||
// Set the selected value
|
||
$("#language").val(cachedLanguage);
|
||
|
||
// Update the text based on the selected language
|
||
updateText(cachedLanguage);
|
||
}
|
||
|
||
// Cache variable to store the selected image data for inpainting
|
||
var inpaintOriginalImg = null;
|
||
|
||
function setInpaintOriginalImg(data) {
|
||
inpaintOriginalImg = data;
|
||
$("#inpaint-img").attr("src", inpaintOriginalImg);
|
||
$("#inpaint-img").trigger("change");
|
||
}
|
||
|
||
$("#copy-txt-to-img-inpaint").click(function () {
|
||
var data = $("#txt2ImgImg").attr("src");
|
||
if (!data) {
|
||
alert("Nothing found from txt-to-img result");
|
||
return;
|
||
}
|
||
setInpaintOriginalImg(data);
|
||
});
|
||
|
||
$("#copy-last-img-inpaint").click(function () {
|
||
var data = $("#img2ImgImg").attr("src");
|
||
if (!data) {
|
||
alert("Nothing found from img-to-img result");
|
||
return;
|
||
}
|
||
setInpaintOriginalImg(data);
|
||
});
|
||
|
||
$("#upload-img-inpaint").click(function () {
|
||
var input = $("<input type='file' accept='image/*'>");
|
||
input.on("change", function () {
|
||
var reader = new FileReader();
|
||
reader.onload = function (e) {
|
||
setInpaintOriginalImg(e.target.result);
|
||
var img = new Image();
|
||
img.src = inpaintOriginalImg;
|
||
img.onload = function () {
|
||
$("#inpaint-img").trigger("change");
|
||
};
|
||
};
|
||
reader.readAsDataURL(input[0].files[0]);
|
||
});
|
||
input.click();
|
||
});
|
||
|
||
$("#inpaint-strike-size").on("input", function () {
|
||
var strikeSize = $(this).val();
|
||
$('#range-value').val(strikeSize);
|
||
$("#inpaint-strike-size").trigger("inputChange", strikeSize);
|
||
});
|
||
$('#range-value').val($('#inpaint-strike-size').val());
|
||
|
||
$("#inpaint-img").on("change", function () {
|
||
var src = $(this).attr("src");
|
||
$("#inpaint-img-for-mask").attr("src", src);
|
||
$("#inpaint-mask-canvas-container").html("<canvas id='inpaint-img-mask' width=" + $(this).width() + " height=" + $(this).height() + ">");
|
||
|
||
var options = {
|
||
graphics: {
|
||
firstPointSize: 0,
|
||
lineWidth: $("#inpaint-strike-size").val(),
|
||
strokeStyle: 'black',
|
||
}
|
||
};
|
||
|
||
$sketcher = $('#inpaint-img-mask').sketchable(options);
|
||
$('#inpaint-reset').click(function () {
|
||
$sketcher.sketchable('clear');
|
||
});
|
||
$('#inpaint-undo').click(function () {
|
||
$sketcher.sketchable('memento.undo');
|
||
});
|
||
$('#inpaint-redo').click(function () {
|
||
$sketcher.sketchable('memento.redo');
|
||
});
|
||
$("#inpaint-strike-size").on("inputChange", function (e, strikeSize) {
|
||
$sketcher.sketchable('config', { graphics: { lineWidth: strikeSize } });
|
||
});
|
||
});
|
||
});
|
||
|
||
</script>
|
||
</body>
|
||
|
||
</html> |