Popiai-skill仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

240 lines
8.9 KiB

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>舞剑分镜 - 图片选择</title>
<style>
:root {
--bg: #0f0f0f; --card: #1a1a1a; --border: #2a2a2a;
--text: #e0e0e0; --text-dim: #888;
--accent: #f0c040; --green: #4ade80;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
background: var(--bg); color: var(--text);
line-height: 1.6; padding: 2rem 2rem 12rem;
max-width: 1100px; margin: 0 auto;
}
.header { margin-bottom: 2rem; text-align: center; }
.header h1 { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.3rem; }
.header .sub { font-size: 0.85rem; color: var(--text-dim); }
.candidates-grid {
display: grid; grid-template-columns: repeat(2, 1fr);
gap: 16px; max-width: 900px; margin: 0 auto;
}
.candidate-item {
position: relative; border: 3px solid var(--border); border-radius: 10px;
overflow: hidden; cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s, transform 0.15s;
}
.candidate-item:hover { border-color: #555; transform: translateY(-2px); }
.candidate-item.selected {
border-color: var(--green);
box-shadow: 0 0 0 1px var(--green), 0 0 16px rgba(74,222,128,0.15);
}
.candidate-item img { width: 100%; height: auto; display: block; }
.candidate-label {
position: absolute; top: 8px; left: 8px;
background: rgba(0,0,0,0.75); backdrop-filter: blur(4px);
padding: 3px 10px; border-radius: 4px; font-size: 0.8rem; color: #ddd;
}
.selected-badge {
position: absolute; top: 8px; right: 8px;
background: var(--green); color: #111; padding: 3px 10px; border-radius: 4px;
font-size: 0.7rem; font-weight: 600; display: none;
}
.candidate-item.selected .selected-badge { display: block; }
/* --- 底部提交区 --- */
.submit-area {
position: fixed; bottom: 0; left: 0; right: 0;
background: rgba(15,15,15,0.95); backdrop-filter: blur(8px);
border-top: 1px solid var(--border); padding: 1rem 2rem;
}
.submit-row { max-width: 1100px; margin: 0 auto; display: flex; flex-direction: column; gap: 10px; }
.feedback-row { display: flex; align-items: flex-start; gap: 12px; }
.feedback-textarea {
flex: 1; min-height: 40px; max-height: 100px; padding: 8px 12px; resize: vertical;
background: #222; border: 1px solid #444; border-radius: 6px;
color: var(--text); font-size: 0.85rem; font-family: inherit;
}
.feedback-textarea::placeholder { color: #555; }
.feedback-textarea:focus { outline: none; border-color: var(--accent); }
.submit-top { display: flex; align-items: center; gap: 12px; }
.submit-info { font-size: 0.85rem; color: var(--text-dim); }
.submit-info .sel { color: var(--green); font-weight: 600; }
.submit-btn {
padding: 8px 24px; background: var(--accent); color: #1a1a1a; border: none;
border-radius: 8px; font-size: 0.9rem; font-weight: 600; cursor: pointer;
}
.submit-btn:hover { background: #e0b030; }
.submit-btn:disabled { background: #555; color: #999; cursor: not-allowed; }
#status-msg { font-size: 0.85rem; color: var(--green); margin-left: 8px; }
</style>
</head>
<body>
<div class="header">
<h1 id="page-title">图片选择</h1>
<p class="sub">点击选择最满意的一张 · 不满意可在下方输入修改建议重新生成</p>
</div>
<div class="candidates-grid" id="grid"></div>
<div class="submit-area">
<div class="submit-row">
<div class="feedback-row">
<textarea class="feedback-textarea" id="feedback-input"
placeholder="修改建议(选填):如「换成蓝色衣服」「背景改为竹林」,留空 = 确认选定当前图片"></textarea>
</div>
<div class="submit-top">
<span class="submit-info">已选:<span class="sel" id="sel-label">未选择</span></span>
<button class="submit-btn" id="submit-btn" onclick="submitSelection()">提交</button>
<span id="status-msg"></span>
</div>
</div>
</div>
<script>
const PREVIEW_LANG = null; /* __PREVIEW_LANG__ */
const LANG = PREVIEW_LANG || (navigator.language.startsWith('zh') ? 'zh' : 'en');
const I18N = {
zh: {
defaultTitle: '图片选择',
pageSub: '点击选择最满意的一张 · 不满意可在下方输入修改建议重新生成',
selectedBadge: '已选定',
feedbackPlaceholder: '修改建议(选填):如「换成蓝色衣服」「背景改为竹林」,留空 = 确认选定当前图片',
selectedLabel: '已选:',
notSelected: '未选择',
submitBtn: '提交',
submitFailed: '提交失败,请重试',
confirmNext: '已提交,Hub 将继续下一步',
pageClosing: '页面将自动关闭...',
canClose: '可以关闭此页面了',
selectPrefix: '选图:',
feedbackPrefix: '修改建议:',
},
en: {
defaultTitle: 'Image Selection',
pageSub: 'Click to select your favorite · Enter feedback below to regenerate',
selectedBadge: 'Selected',
feedbackPlaceholder: 'Feedback (optional): e.g. "change to blue outfit" "change background to bamboo forest", leave empty = confirm current selection',
selectedLabel: 'Selected: ',
notSelected: 'None',
submitBtn: 'Submit',
submitFailed: 'Submit failed, please retry',
confirmNext: 'Submitted. Hub will proceed to next step.',
pageClosing: 'Page will close automatically...',
canClose: 'You can close this page now.',
selectPrefix: 'Select: ',
feedbackPrefix: 'Feedback: ',
}
};
const t = (key) => (I18N[LANG] || I18N.en)[key] || key;
document.documentElement.lang = LANG === 'zh' ? 'zh-CN' : 'en';
const PREVIEW_DATA = null; /* __PREVIEW_DATA__ */
let selected = 0;
function init() {
const data = typeof PREVIEW_DATA === 'string' ? JSON.parse(PREVIEW_DATA) : PREVIEW_DATA;
if (!data || !data.candidates) return;
document.getElementById('page-title').textContent = data.title || t('defaultTitle');
document.querySelector('.header .sub').textContent = t('pageSub');
document.getElementById('feedback-input').placeholder = t('feedbackPlaceholder');
document.querySelector('.submit-info').innerHTML = t('selectedLabel') + '<span class="sel" id="sel-label">' + t('notSelected') + '</span>';
document.getElementById('submit-btn').textContent = t('submitBtn');
const grid = document.getElementById('grid');
data.candidates.forEach((cand, idx) => {
const div = document.createElement('div');
div.className = 'candidate-item' + (idx === 0 ? ' selected' : '');
div.id = 'cand-' + idx;
div.onclick = () => selectItem(idx);
div.innerHTML = `
<span class="candidate-label">${esc(cand.label)}</span>
<span class="selected-badge">${t('selectedBadge')}</span>
<img src="${cand.url}" alt="${esc(cand.label)}" loading="lazy">
`;
grid.appendChild(div);
});
updateLabel();
}
function esc(s) {
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function selectItem(idx) {
selected = idx;
document.querySelectorAll('.candidate-item').forEach((el, i) => {
el.classList.toggle('selected', i === idx);
});
updateLabel();
}
function updateLabel() {
const data = typeof PREVIEW_DATA === 'string' ? JSON.parse(PREVIEW_DATA) : PREVIEW_DATA;
document.getElementById('sel-label').textContent = data.candidates[selected].label;
}
async function submitSelection() {
const data = typeof PREVIEW_DATA === 'string' ? JSON.parse(PREVIEW_DATA) : PREVIEW_DATA;
const cand = data.candidates[selected];
const feedbackText = document.getElementById('feedback-input').value.trim();
let feedback = `${t('selectPrefix')}${cand.filename}\n`;
if (feedbackText) {
feedback += `\n${t('feedbackPrefix')}${feedbackText}\n`;
} else {
feedback = 'LGTM\n' + feedback;
}
try {
const resp = await fetch('/feedback', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ feedback: feedback.trim() })
});
const result = await resp.json();
if (result.ok) {
showDone(t('confirmNext'));
}
} catch (err) {
document.getElementById('status-msg').textContent = t('submitFailed');
}
}
init();
function showDone(msg) {
document.body.innerHTML = `
<div style="display:flex;align-items:center;justify-content:center;height:100vh;background:#0f0f0f;">
<div style="text-align:center;color:#e0e0e0;font-family:-apple-system,sans-serif;">
<div style="font-size:3rem;color:#4ade80;margin-bottom:1rem;">&#10003;</div>
<div style="font-size:1.2rem;font-weight:600;margin-bottom:0.5rem;">${msg}</div>
<div class="done-sub" style="font-size:0.85rem;color:#888;">${t('pageClosing')}</div>
</div>
</div>`;
setTimeout(() => {
window.close();
setTimeout(() => {
const sub = document.querySelector('.done-sub');
if (sub) sub.textContent = t('canClose');
}, 2000);
}, 800);
}
</script>
</body>
</html>