Spaces:
Sleeping
Sleeping
Upload 10 files
Browse files- data/models.json +2 -1
- public/index.html +54 -6
- server.js +13 -0
data/models.json
CHANGED
|
@@ -18,5 +18,6 @@
|
|
| 18 |
{ "id": "HassakuXL", "name": "HassakuXL", "free": false },
|
| 19 |
{ "id": "nova-cartoon-xl", "name": "nova-cartoon-xl", "free": false },
|
| 20 |
{ "id": "orphic-lora", "name": "orphic-lora", "free": false },
|
| 21 |
-
{ "id": "diagonalge/ConstShaper", "name": "ConstShaper", "free": false }
|
|
|
|
| 22 |
]
|
|
|
|
| 18 |
{ "id": "HassakuXL", "name": "HassakuXL", "free": false },
|
| 19 |
{ "id": "nova-cartoon-xl", "name": "nova-cartoon-xl", "free": false },
|
| 20 |
{ "id": "orphic-lora", "name": "orphic-lora", "free": false },
|
| 21 |
+
{ "id": "diagonalge/ConstShaper", "name": "ConstShaper", "free": false },
|
| 22 |
+
{ "id": "hunyuan-image-3", "name": "hunyuan-image-3", "free": false }
|
| 23 |
]
|
public/index.html
CHANGED
|
@@ -409,11 +409,13 @@
|
|
| 409 |
opacity: 0;
|
| 410 |
visibility: hidden;
|
| 411 |
transition: all 0.3s ease;
|
|
|
|
| 412 |
}
|
| 413 |
|
| 414 |
.overlay.show {
|
| 415 |
opacity: 1;
|
| 416 |
visibility: visible;
|
|
|
|
| 417 |
}
|
| 418 |
|
| 419 |
/* Mobile Generate Button - Draggable */
|
|
@@ -422,7 +424,7 @@
|
|
| 422 |
left: 20px;
|
| 423 |
top: 50%;
|
| 424 |
transform: translateY(-50%);
|
| 425 |
-
z-index:
|
| 426 |
background: linear-gradient(135deg, var(--accent), var(--accent-hover));
|
| 427 |
color: white;
|
| 428 |
border: none;
|
|
@@ -980,6 +982,8 @@
|
|
| 980 |
btn.addEventListener('touchstart', startDrag, { passive: false });
|
| 981 |
document.addEventListener('touchmove', drag, { passive: false });
|
| 982 |
document.addEventListener('touchend', endDrag);
|
|
|
|
|
|
|
| 983 |
}
|
| 984 |
|
| 985 |
function startDrag(e) {
|
|
@@ -1025,9 +1029,11 @@
|
|
| 1025 |
const btn = qs('#mobileGenerateBtn');
|
| 1026 |
const maxX = window.innerWidth - 60;
|
| 1027 |
const maxY = window.innerHeight - 60;
|
|
|
|
|
|
|
| 1028 |
|
| 1029 |
dragState.currentX = Math.max(0, Math.min(maxX, dragState.currentX));
|
| 1030 |
-
dragState.currentY = Math.max(
|
| 1031 |
|
| 1032 |
btn.style.left = dragState.currentX + 'px';
|
| 1033 |
btn.style.top = dragState.currentY + 'px';
|
|
@@ -1067,11 +1073,22 @@
|
|
| 1067 |
if (!btn) return;
|
| 1068 |
|
| 1069 |
const savedPos = ls.get('generateBtnPosition', null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1070 |
if (savedPos) {
|
| 1071 |
-
|
| 1072 |
-
|
| 1073 |
-
|
|
|
|
|
|
|
| 1074 |
}
|
|
|
|
|
|
|
|
|
|
| 1075 |
}
|
| 1076 |
|
| 1077 |
// 移动端优化的下载函数
|
|
@@ -1217,6 +1234,23 @@
|
|
| 1217 |
if (wasMobile !== state.isMobile && state.sidebarOpen) {
|
| 1218 |
closeSidebar();
|
| 1219 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1220 |
}
|
| 1221 |
|
| 1222 |
async function generate() {
|
|
@@ -1367,7 +1401,21 @@
|
|
| 1367 |
// 响应式检测
|
| 1368 |
window.addEventListener('resize', checkMobile);
|
| 1369 |
checkMobile();
|
| 1370 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1371 |
// 加载模型
|
| 1372 |
fetchModels();
|
| 1373 |
}
|
|
|
|
| 409 |
opacity: 0;
|
| 410 |
visibility: hidden;
|
| 411 |
transition: all 0.3s ease;
|
| 412 |
+
pointer-events: none;
|
| 413 |
}
|
| 414 |
|
| 415 |
.overlay.show {
|
| 416 |
opacity: 1;
|
| 417 |
visibility: visible;
|
| 418 |
+
pointer-events: auto;
|
| 419 |
}
|
| 420 |
|
| 421 |
/* Mobile Generate Button - Draggable */
|
|
|
|
| 424 |
left: 20px;
|
| 425 |
top: 50%;
|
| 426 |
transform: translateY(-50%);
|
| 427 |
+
z-index: 120;
|
| 428 |
background: linear-gradient(135deg, var(--accent), var(--accent-hover));
|
| 429 |
color: white;
|
| 430 |
border: none;
|
|
|
|
| 982 |
btn.addEventListener('touchstart', startDrag, { passive: false });
|
| 983 |
document.addEventListener('touchmove', drag, { passive: false });
|
| 984 |
document.addEventListener('touchend', endDrag);
|
| 985 |
+
document.addEventListener('touchcancel', endDrag, { passive: false });
|
| 986 |
+
btn.addEventListener('touchcancel', endDrag);
|
| 987 |
}
|
| 988 |
|
| 989 |
function startDrag(e) {
|
|
|
|
| 1029 |
const btn = qs('#mobileGenerateBtn');
|
| 1030 |
const maxX = window.innerWidth - 60;
|
| 1031 |
const maxY = window.innerHeight - 60;
|
| 1032 |
+
const headerH = (qs('header') && qs('header').offsetHeight) || 0;
|
| 1033 |
+
const minY = headerH + 8;
|
| 1034 |
|
| 1035 |
dragState.currentX = Math.max(0, Math.min(maxX, dragState.currentX));
|
| 1036 |
+
dragState.currentY = Math.max(minY, Math.min(maxY, dragState.currentY));
|
| 1037 |
|
| 1038 |
btn.style.left = dragState.currentX + 'px';
|
| 1039 |
btn.style.top = dragState.currentY + 'px';
|
|
|
|
| 1073 |
if (!btn) return;
|
| 1074 |
|
| 1075 |
const savedPos = ls.get('generateBtnPosition', null);
|
| 1076 |
+
const headerH = (qs('header') && qs('header').offsetHeight) || 0;
|
| 1077 |
+
const minY = headerH + 8;
|
| 1078 |
+
const maxX = window.innerWidth - 60;
|
| 1079 |
+
const maxY = window.innerHeight - 60;
|
| 1080 |
+
|
| 1081 |
+
let x, y;
|
| 1082 |
if (savedPos) {
|
| 1083 |
+
x = Math.max(0, Math.min(maxX, savedPos.x));
|
| 1084 |
+
y = Math.max(minY, Math.min(maxY, savedPos.y));
|
| 1085 |
+
} else {
|
| 1086 |
+
x = 20;
|
| 1087 |
+
y = Math.max(minY, Math.min(maxY, window.innerHeight / 2 - 30));
|
| 1088 |
}
|
| 1089 |
+
btn.style.left = x + 'px';
|
| 1090 |
+
btn.style.top = y + 'px';
|
| 1091 |
+
btn.style.transform = 'none';
|
| 1092 |
}
|
| 1093 |
|
| 1094 |
// 移动端优化的下载函数
|
|
|
|
| 1234 |
if (wasMobile !== state.isMobile && state.sidebarOpen) {
|
| 1235 |
closeSidebar();
|
| 1236 |
}
|
| 1237 |
+
|
| 1238 |
+
// Clamp floating button inside viewport and below header when geometry changes
|
| 1239 |
+
const btn = qs('#mobileGenerateBtn');
|
| 1240 |
+
if (btn) {
|
| 1241 |
+
const rect = btn.getBoundingClientRect();
|
| 1242 |
+
const maxX = window.innerWidth - 60;
|
| 1243 |
+
const maxY = window.innerHeight - 60;
|
| 1244 |
+
const headerH = (qs('header') && qs('header').offsetHeight) || 0;
|
| 1245 |
+
const minY = headerH + 8;
|
| 1246 |
+
let x = rect.left;
|
| 1247 |
+
let y = rect.top;
|
| 1248 |
+
x = Math.max(0, Math.min(maxX, x));
|
| 1249 |
+
y = Math.max(minY, Math.min(maxY, y));
|
| 1250 |
+
btn.style.left = x + 'px';
|
| 1251 |
+
btn.style.top = y + 'px';
|
| 1252 |
+
btn.style.transform = 'none';
|
| 1253 |
+
}
|
| 1254 |
}
|
| 1255 |
|
| 1256 |
async function generate() {
|
|
|
|
| 1401 |
// 响应式检测
|
| 1402 |
window.addEventListener('resize', checkMobile);
|
| 1403 |
checkMobile();
|
| 1404 |
+
|
| 1405 |
+
// 页面可见性/生命周期:后台恢复后确保拖拽结束,避免输入框无法点击
|
| 1406 |
+
window.addEventListener('visibilitychange', () => {
|
| 1407 |
+
if (document.hidden) {
|
| 1408 |
+
try { endDrag(new Event('touchcancel')); } catch (e) { if (typeof dragState !== 'undefined') dragState.isDragging = false; }
|
| 1409 |
+
}
|
| 1410 |
+
});
|
| 1411 |
+
window.addEventListener('pagehide', () => {
|
| 1412 |
+
try { endDrag(new Event('touchcancel')); } catch (e) { if (typeof dragState !== 'undefined') dragState.isDragging = false; }
|
| 1413 |
+
});
|
| 1414 |
+
window.addEventListener('blur', () => {
|
| 1415 |
+
try { endDrag(new Event('touchcancel')); } catch (e) { if (typeof dragState !== 'undefined') dragState.isDragging = false; }
|
| 1416 |
+
});
|
| 1417 |
+
document.addEventListener('touchcancel', endDrag, { passive: false });
|
| 1418 |
+
|
| 1419 |
// 加载模型
|
| 1420 |
fetchModels();
|
| 1421 |
}
|
server.js
CHANGED
|
@@ -220,7 +220,20 @@ app.get('/api/models', async (req, res) => {
|
|
| 220 |
});
|
| 221 |
// Merge free flags from local mapping
|
| 222 |
if (localList.length) {
|
|
|
|
| 223 |
models = mergeFreeFlags(models, localList);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
}
|
| 225 |
return res.json({ source: 'remote', models });
|
| 226 |
}
|
|
|
|
| 220 |
});
|
| 221 |
// Merge free flags from local mapping
|
| 222 |
if (localList.length) {
|
| 223 |
+
// 先合并免费标记
|
| 224 |
models = mergeFreeFlags(models, localList);
|
| 225 |
+
// 再把仅存在于本地配置但远端没有的模型追加进去(并集)
|
| 226 |
+
const remoteKeys = new Set(models.map(m => ((m.id || m.name || '') + '').toLowerCase()));
|
| 227 |
+
for (const lm of localList) {
|
| 228 |
+
const key = ((lm.id || lm.name || '') + '').toLowerCase();
|
| 229 |
+
if (key && !remoteKeys.has(key)) {
|
| 230 |
+
const id = (lm.id || lm.name || '').toString();
|
| 231 |
+
const name = (lm.name || id).toString();
|
| 232 |
+
const free = !!lm.free;
|
| 233 |
+
models.push({ id, name, free });
|
| 234 |
+
remoteKeys.add(key);
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
}
|
| 238 |
return res.json({ source: 'remote', models });
|
| 239 |
}
|