dsh-plugin-image-workbench 1.1.6 → 1.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server/app.py +42 -0
- package/server/smoke_check.py +1 -1
- package/server/static/index.html +42 -7
package/package.json
CHANGED
package/server/app.py
CHANGED
|
@@ -425,6 +425,48 @@ async def api_catalog():
|
|
|
425
425
|
return JSONResponse(CATALOG)
|
|
426
426
|
|
|
427
427
|
|
|
428
|
+
_SAMPLES_CACHE = {"at": 0.0, "data": None}
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _sample_progress() -> dict:
|
|
432
|
+
"""数一遍缩略图准备到哪了。
|
|
433
|
+
|
|
434
|
+
首次安装时 repo_images 是空的(这 500 多张图不进发布包),插件在后台慢慢补。
|
|
435
|
+
前端拿这个数字决定要不要在顶部提示条里说一句「先看到渐变占位是正常的」,
|
|
436
|
+
补完自动撤掉,不做成永久横幅。
|
|
437
|
+
"""
|
|
438
|
+
expected = have = 0
|
|
439
|
+
for key in ("styles", "layouts", "colors"):
|
|
440
|
+
for item in CATALOG.get(key) or []:
|
|
441
|
+
# catalog 里写成 images/individual/...,落到磁盘时去掉 images/ 前缀,
|
|
442
|
+
# 和前端 imgUrl() 的拼法、/img 路由的取法保持一致,否则永远数成 0 张。
|
|
443
|
+
rel = re.sub(r"^images/", "", str((item or {}).get("img") or "").strip())
|
|
444
|
+
if not rel:
|
|
445
|
+
continue
|
|
446
|
+
expected += 1
|
|
447
|
+
if (REPO_IMAGES / rel).is_file():
|
|
448
|
+
have += 1
|
|
449
|
+
return {
|
|
450
|
+
"total": expected,
|
|
451
|
+
"have": have,
|
|
452
|
+
"missing": expected - have,
|
|
453
|
+
"ready": expected == 0 or have >= expected,
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@app.get("/api/samples")
|
|
458
|
+
async def api_samples(fresh: int = 0):
|
|
459
|
+
"""缩略图同步进度(只读)。补图是后台进行的,这里只报数。"""
|
|
460
|
+
now = time.time()
|
|
461
|
+
cached = _SAMPLES_CACHE["data"]
|
|
462
|
+
if cached is not None and not fresh and now - _SAMPLES_CACHE["at"] < 10:
|
|
463
|
+
return JSONResponse(cached)
|
|
464
|
+
data = await run_in_threadpool(_sample_progress)
|
|
465
|
+
_SAMPLES_CACHE["at"] = now
|
|
466
|
+
_SAMPLES_CACHE["data"] = data
|
|
467
|
+
return JSONResponse(data)
|
|
468
|
+
|
|
469
|
+
|
|
428
470
|
@app.get("/api/pool")
|
|
429
471
|
async def api_pool():
|
|
430
472
|
# 前端每 10 秒轮询一次,顺手在这里做离线自愈:号池后起也能自己从「离线」翻成在线
|
package/server/smoke_check.py
CHANGED
|
@@ -102,7 +102,7 @@ except Exception as e:
|
|
|
102
102
|
print('\n=== 2. 读接口 ===')
|
|
103
103
|
client = httpx.Client(base_url=BASE, trust_env=False, timeout=30)
|
|
104
104
|
routes = [
|
|
105
|
-
('/', 200), ('/health', 200), ('/api/catalog', 200), ('/api/pool', 200),
|
|
105
|
+
('/', 200), ('/health', 200), ('/api/catalog', 200), ('/api/samples', 200), ('/api/pool', 200),
|
|
106
106
|
('/api/monitor', 200), ('/api/recent', 200), ('/api/history', 200),
|
|
107
107
|
('/api/tasks', 200), ('/api/drafts', 200), ('/api/pool/check', 200),
|
|
108
108
|
('/api/pool/fails', 200), ('/api/pool/panel', 200),
|
package/server/static/index.html
CHANGED
|
@@ -21,13 +21,15 @@
|
|
|
21
21
|
letter-spacing:0;line-height:1.6;color:#1f9d6b;background:#e8f6ef;border:1px solid #bfe8d4;
|
|
22
22
|
text-decoration:none;white-space:nowrap;transition:background .15s,color .15s}
|
|
23
23
|
header .gh-link:hover{background:#d7efe2;color:#17845a}
|
|
24
|
-
/*
|
|
25
|
-
.header-ad{flex:
|
|
24
|
+
/* 顶部竖滚轮播提示条:标题右侧、操作按钮左侧。宽度贴合当前这条提示(跟着轮播伸缩),不拉满 */
|
|
25
|
+
.header-ad{flex:0 1 auto;min-width:0;max-width:440px;display:flex;align-items:center;gap:8px;
|
|
26
26
|
margin:0 4px;height:30px;padding:0 10px;background:#f3fbf7;border:1px solid #d7efe2;border-radius:8px;overflow:hidden}
|
|
27
27
|
.header-ad .ad-tag{flex:none;font-size:10px;font-weight:600;color:#fff;background:#1f9d6b;
|
|
28
28
|
padding:2px 7px;border-radius:10px;letter-spacing:.5px}
|
|
29
|
-
.header-ad .ad-view{flex:1;min-width:0;height:20px;overflow:hidden;position:relative
|
|
30
|
-
|
|
29
|
+
.header-ad .ad-view{flex:0 1 auto;min-width:0;height:20px;overflow:hidden;position:relative;
|
|
30
|
+
transition:width .45s ease}
|
|
31
|
+
/* width:max-content:每条按自己的文字撑开,宽度动画时不会互相挤出省略号 */
|
|
32
|
+
.header-ad .ad-track{display:flex;flex-direction:column;width:max-content;transition:transform .7s ease}
|
|
31
33
|
.header-ad .ad-item{height:20px;line-height:20px;font-size:12px;color:#2f6b50;white-space:nowrap;
|
|
32
34
|
overflow:hidden;text-overflow:ellipsis}
|
|
33
35
|
/* 右上角「更多功能」下拉 */
|
|
@@ -348,10 +350,11 @@
|
|
|
348
350
|
.head-acts .status{margin:0;max-width:220px;overflow:hidden;text-overflow:ellipsis;
|
|
349
351
|
white-space:nowrap;font-size:11.5px;line-height:1.3}
|
|
350
352
|
.head-acts .ha-sep{width:1px;height:20px;background:var(--line);margin:0 3px;flex:none}
|
|
351
|
-
/*
|
|
352
|
-
|
|
353
|
+
/* 空间不够时:提示条留在标题同一行的右侧空档,操作区独占第二行,别把标题挤变形。
|
|
354
|
+
断点 1520 = 单行放得下最长提示条(文字 306px + 外壳 65px)的宽度,再窄就换行。 */
|
|
355
|
+
@media (max-width:1520px){
|
|
353
356
|
header{flex-wrap:wrap;row-gap:8px}
|
|
354
|
-
.header-ad{order:1;flex:
|
|
357
|
+
.header-ad{order:1;flex:0 1 auto}
|
|
355
358
|
.head-acts{order:2;flex:1 0 100%;margin-left:0;flex-wrap:wrap;justify-content:flex-start}
|
|
356
359
|
.head-acts .pool-chip{margin-left:auto}
|
|
357
360
|
}
|
|
@@ -612,6 +615,7 @@
|
|
|
612
615
|
<div class="header-ad" id="headerAd" title="使用提示">
|
|
613
616
|
<span class="ad-tag">提示</span>
|
|
614
617
|
<div class="ad-view"><div class="ad-track" id="adTrack">
|
|
618
|
+
<div class="ad-item" id="adSamples" title="刚安装成功时,缩略图会在后台自动补齐,补完刷新就是真图">刚安装完需要点时间同步图型、风格、主题色缩略图。</div>
|
|
615
619
|
<div class="ad-item">如果不知道怎么上手,可以试着直接点击「批量出图」。</div>
|
|
616
620
|
<div class="ad-item">使用过程中有任何问题,可以多看看公众号详细介绍。</div>
|
|
617
621
|
<div class="ad-item">如果发现其他问题,估计作者也搞不定,就直接问 AI 吧。</div>
|
|
@@ -4681,19 +4685,50 @@ setInterval(refreshPool, 10000);
|
|
|
4681
4685
|
if(items < 2) return;
|
|
4682
4686
|
track.appendChild(track.children[0].cloneNode(true)); // 克隆首条,衔接更顺
|
|
4683
4687
|
var h = 20, idx = 0, HOLD = 5200, EASE = 720;
|
|
4688
|
+
|
|
4689
|
+
/* 绿框宽度贴合当前这条提示的文字宽(每条长短不同,跟着轮播一起伸缩),
|
|
4690
|
+
免得短提示后面拖一条空绿。track 是 width:max-content,
|
|
4691
|
+
这里只需把 .ad-view 的宽度对准当前条,其余交给 CSS 过渡。 */
|
|
4692
|
+
var view = track.parentElement;
|
|
4693
|
+
function textW(el){
|
|
4694
|
+
var r = document.createRange(); r.selectNodeContents(el);
|
|
4695
|
+
return Math.ceil(r.getBoundingClientRect().width);
|
|
4696
|
+
}
|
|
4697
|
+
var ws = Array.prototype.map.call(track.children, textW);
|
|
4698
|
+
function fit(){ view.style.width = ws[idx] + 'px'; }
|
|
4699
|
+
view.style.transition = 'none'; // 首次直接落位,不播缩放入场
|
|
4700
|
+
fit();
|
|
4701
|
+
requestAnimationFrame(function(){ view.style.transition = ''; });
|
|
4702
|
+
|
|
4684
4703
|
setInterval(function(){
|
|
4685
4704
|
idx++;
|
|
4686
4705
|
track.style.transform = 'translateY(' + (-idx*h) + 'px)';
|
|
4706
|
+
fit();
|
|
4687
4707
|
if(idx === items){
|
|
4688
4708
|
setTimeout(function(){
|
|
4689
4709
|
track.style.transition = 'none';
|
|
4690
4710
|
idx = 0;
|
|
4691
4711
|
track.style.transform = 'translateY(0)';
|
|
4712
|
+
fit();
|
|
4692
4713
|
void track.offsetHeight; // 强制回流后恢复过渡
|
|
4693
4714
|
track.style.transition = '';
|
|
4694
4715
|
}, EASE);
|
|
4695
4716
|
}
|
|
4696
4717
|
}, HOLD);
|
|
4718
|
+
|
|
4719
|
+
/* 缩略图那条只改 hover 文案、不动文字和节点:条数一变,上面的位移计数就错位。
|
|
4720
|
+
具体差多少张放在 title 里。 */
|
|
4721
|
+
var el = document.getElementById('adSamples');
|
|
4722
|
+
if(!el) return;
|
|
4723
|
+
fetch('/api/samples', {cache: 'no-store'})
|
|
4724
|
+
.then(function(r){ return r.ok ? r.json() : null; })
|
|
4725
|
+
.then(function(s){
|
|
4726
|
+
if(!s || !s.total) return;
|
|
4727
|
+
el.title = s.missing > 0
|
|
4728
|
+
? '缩略图后台同步中:已 ' + s.have + ' / ' + s.total + ' 张,还差 ' + s.missing + ' 张,补完刷新即可。'
|
|
4729
|
+
: '缩略图已全部就位(共 ' + s.total + ' 张)。';
|
|
4730
|
+
})
|
|
4731
|
+
.catch(function(){}); // 状态读不到就保持默认说明,不影响轮播
|
|
4697
4732
|
})();
|
|
4698
4733
|
</script>
|
|
4699
4734
|
|