Files
dsProject/dsLightRag/static/LibLib/copyface.html
2025-09-04 13:15:40 +08:00

582 lines
19 KiB
HTML
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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>人像换脸生成器</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
input[type="url"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
}
button {
background-color: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-right: 10px;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.image-preview {
margin-top: 20px;
text-align: center;
}
.image-preview img {
max-width: 300px;
max-height: 300px;
border: 2px solid #ddd;
border-radius: 5px;
}
.result {
margin-top: 30px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 5px;
border-left: 4px solid #007bff;
}
.result img {
max-width: 100%;
max-height: 400px;
border: 2px solid #28a745;
border-radius: 5px;
}
.loading {
display: none;
text-align: center;
margin: 20px 0;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.error {
color: #dc3545;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
.success {
color: #155724;
background-color: #d4edda;
border: 1px solid #c3e6cb;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
}
/* 修改为横版模型卡片样式一行显示3个 */
.model-cards {
display: flex;
gap: 20px;
margin: 20px 0;
flex-wrap: wrap;
justify-content: center;
}
.model-card {
border: 2px solid #ddd;
border-radius: 10px;
padding: 15px;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
background: white;
width: calc(33.333% - 20px);
box-sizing: border-box;
min-width: 280px;
}
.model-card:hover {
border-color: #007bff;
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 123, 255, 0.2);
}
.model-card.selected {
border-color: #007bff;
background-color: #e8f4fd;
}
.model-card img {
width: 100%;
height: 300px;
object-fit: cover;
border-radius: 8px;
margin-bottom: 10px;
}
.model-card h3 {
margin: 10px 0 5px;
color: #333;
font-size: 16px;
}
.model-card p {
color: #666;
font-size: 14px;
margin: 0;
}
/* 新增:提示词样式 */
.prompt-textarea {
width: 100%;
height: 120px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
resize: vertical;
font-family: monospace;
}
/* 新增:图片模态框样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
max-width: 90%;
max-height: 90%;
}
.close-modal {
position: absolute;
top: 20px;
right: 30px;
color: white;
font-size: 40px;
font-weight: bold;
cursor: pointer;
}
.close-modal:hover {
color: #ccc;
}
/* 新增:生成历史样式 */
.history-section {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #eee;
}
.history-title {
color: #333;
margin-bottom: 15px;
font-size: 18px;
font-weight: bold;
}
.history-images {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
}
.history-image-item {
width: calc(20% - 15px);
min-width: 120px;
cursor: pointer;
}
.history-image-item img {
width: 100%;
height: 250px; /* 修改为竖版高度 */
object-fit: cover;
border-radius: 8px;
border: 2px solid #ddd;
transition: all 0.3s ease;
}
.history-image-item img:hover {
border-color: #007bff;
transform: scale(1.05);
}
/* 响应式调整 */
@media (max-width: 992px) {
.model-card {
width: calc(50% - 15px);
}
}
@media (max-width: 768px) {
.model-card {
width: 100%;
min-width: auto;
}
.model-card img {
height: 250px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>🎭 人像换脸生成器</h1>
<div class="form-group">
<label>选择生成模型:</label>
<div class="model-cards" id="modelCards">
<div class="model-card" onclick="selectModel(this, '炫酷机甲美女_majicflus')">
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/LibLib/Sample/JiJiaMeiNv.jpg"
alt="炫酷机甲美女"
onerror="this.src='https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/HuangWanQiao.jpg'"
onclick="openModal(this.src)">
<h3>炫酷机甲美女</h3>
<p>majicflus模型</p>
</div>
<div class="model-card" onclick="selectModel(this, '可铯AI—3d潮玩手办')">
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/LibLib/Sample/3DChaoWanShouBan.jpg"
alt="3D潮玩手办"
onerror="this.src='https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/HuangWanQiao.jpg'"
onclick="openModal(this.src)">
<h3>3D潮玩手办</h3>
<p>可铯AI模型</p>
</div>
<div class="model-card" onclick="selectModel(this, 'F.1-武侠世界')">
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/LibLib/Sample/WuXiaMeiNv.jpg"
alt="武侠世界"
onerror="this.src='https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/HuangWanQiao.jpg'"
onclick="openModal(this.src)">
<h3>武侠世界</h3>
<p>F.1武侠风格</p>
</div>
</div>
</div>
<div class="form-group">
<label for="imageUrl">图片URL地址</label>
<input type="url" id="imageUrl" placeholder="请输入图片URL地址"
value="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/HuangWanQiao.jpg">
</div>
<!-- 新增:提示词编辑区域 -->
<div class="form-group">
<label for="promptInput">提示词:</label>
<textarea id="promptInput" class="prompt-textarea" placeholder="请输入提示词..."></textarea>
<div style="display: flex; justify-content: space-between; margin-top: 8px;">
<small style="color: #666;">提示:修改提示词可以改变生成图片的风格和细节</small>
<button id="resetPromptBtn" style="padding: 4px 8px; font-size: 12px;">重置默认</button>
</div>
</div>
<div class="image-preview" id="previewContainer">
<h3>人像照片</h3>
<img id="previewImage" src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/Backup/HuangWanQiao.jpg"
alt="参考图片" onerror="this.style.display='none'">
</div>
<button onclick="generateImage()" id="generateBtn">生成换脸图片</button>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>正在生成中,请稍候...</p>
</div>
<div class="result" id="resultContainer" style="display: none;">
<h3>生成结果:</h3>
<div id="resultMessage"></div>
<div id="resultImage"></div>
</div>
<!-- 新增:生成历史栏目 -->
<div class="history-section">
<h3 class="history-title">📜 生成历史</h3>
<div class="history-images">
<div class="history-image-item">
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/LibLib/7e2c6203189344388964c29963f666ad.jpg"
alt="生成历史图片1" onclick="openModal(this.src)"/>
</div>
<div class="history-image-item">
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/LibLib/bc5a75089f0047d990d7fe3cb6fbfab1.jpg"
alt="生成历史图片2" onclick="openModal(this.src)"/>
</div>
<div class="history-image-item">
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/LibLib/ffe9051f1f1d4009a78c744b7e68747c.jpg"
alt="生成历史图片3" onclick="openModal(this.src)"/>
</div>
<div class="history-image-item">
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/LibLib/484eba61d6e74f8ea0cb3d8ff2d14341.jpg"
alt="生成历史图片4" onclick="openModal(this.src)"/>
</div>
<div class="history-image-item">
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/LibLib/215df7724cba4bf9a64e7863ecb4ebc0.jpg"
alt="生成历史图片5" onclick="openModal(this.src)"/>
</div>
<div class="history-image-item">
<img src="https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/LibLib/77eb450d7aa74c888a852f66b3bfb3b2.jpg"
alt="生成历史图片6" onclick="openModal(this.src)"/>
</div>
</div>
</div>
<!-- 新增:图片模态框 -->
<div id="imageModal" class="modal">
<span class="close-modal" onclick="closeModal()">&times;</span>
<img class="modal-content" id="modalImage">
</div>
<script>
const API_BASE = '/api/copyface';
let selectedModel = '';
// 新增:存储模型提示词
let modelPrompts = {};
// 新增:模态框相关变量
const modal = document.getElementById('imageModal');
const modalImage = document.getElementById('modalImage');
// 页面加载时初始化
document.addEventListener('DOMContentLoaded', function () {
// 默认选择第一个模型
const firstCard = document.querySelector('.model-card');
if (firstCard) {
selectModel(firstCard, firstCard.getAttribute('onclick').split("'")[1]);
}
// 监听URL输入变化实时预览
document.getElementById('imageUrl').addEventListener('input', function () {
const url = this.value.trim();
const previewImg = document.getElementById('previewImage');
if (url) {
previewImg.src = url;
previewImg.style.display = 'block';
} else {
previewImg.style.display = 'none';
}
});
// 新增:获取所有模型提示词
fetchModelPrompts();
// 新增:重置提示词按钮事件
document.getElementById('resetPromptBtn').addEventListener('click', resetPromptToDefault);
});
// 新增:获取所有模型提示词
async function fetchModelPrompts() {
try {
const response = await fetch(`${API_BASE}/samples`);
if (response.ok) {
const models = await response.json();
models.forEach(model => {
modelPrompts[model.name] = model.prompt;
});
// 如果已有选中的模型,加载其提示词
if (selectedModel && modelPrompts[selectedModel]) {
document.getElementById('promptInput').value = modelPrompts[selectedModel];
}
}
} catch (error) {
console.error('获取模型提示词失败:', error);
}
}
// 新增:重置提示词为默认值
function resetPromptToDefault() {
if (selectedModel && modelPrompts[selectedModel]) {
document.getElementById('promptInput').value = modelPrompts[selectedModel];
}
}
// 新增:打开模态框显示大图
function openModal(imageUrl) {
modal.style.display = 'flex';
modalImage.src = imageUrl;
}
// 新增:关闭模态框
function closeModal() {
modal.style.display = 'none';
}
// 点击模态框背景也关闭
window.onclick = function (event) {
if (event.target == modal) {
closeModal();
}
}
// 修改:选择模型函数,加载对应提示词
function selectModel(card, modelName) {
// 移除所有选中状态
document.querySelectorAll('.model-card').forEach(card => {
card.classList.remove('selected');
});
// 添加选中状态
card.classList.add('selected');
selectedModel = modelName;
// 新增:加载模型对应的提示词
if (modelPrompts[selectedModel]) {
document.getElementById('promptInput').value = modelPrompts[selectedModel];
}
console.log('已选择模型:', selectedModel);
}
// 修改:生成换脸图片函数,支持自定义提示词
async function generateImage() {
const imageUrl = document.getElementById('imageUrl').value.trim();
const customPrompt = document.getElementById('promptInput').value.trim();
const generateBtn = document.getElementById('generateBtn');
const loading = document.getElementById('loading');
const resultContainer = document.getElementById('resultContainer');
const resultMessage = document.getElementById('resultMessage');
const resultImage = document.getElementById('resultImage');
// 验证输入
if (!imageUrl) {
alert('请输入图片URL地址');
return;
}
if (!selectedModel) {
alert('请选择生成模型');
return;
}
// 显示加载状态
generateBtn.disabled = true;
loading.style.display = 'block';
resultContainer.style.display = 'none';
try {
const apiUrl = `${API_BASE}/generate/${encodeURIComponent(selectedModel)}`;
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
image_url: imageUrl,
// 新增:传递自定义提示词
prompt: customPrompt
})
});
const result = await response.json();
if (response.ok && result.status === 'success') {
// 显示成功结果
resultMessage.innerHTML = `
<div class="success">
${result.message}<br>
📦 使用模型: ${result.model_used}<br>
🌐 OBS地址: <a href="${result.obs_url}" target="_blank">${result.obs_url}</a>
</div>
`;
resultImage.innerHTML = `
<h4>生成结果预览:</h4>
<img src="${result.obs_url}" alt="生成结果"
onerror="this.style.display='none'; alert('图片加载失败请检查OBS地址')">
`;
} else {
// 显示错误信息
resultMessage.innerHTML = `
<div class="error">
❌ 生成失败: ${result.detail || result.message || '未知错误'}
</div>
`;
resultImage.innerHTML = '';
}
resultContainer.style.display = 'block';
} catch (error) {
console.error('生成请求失败:', error);
resultMessage.innerHTML = `
<div class="error">
❌ 请求失败: ${error.message}
</div>
`;
resultContainer.style.display = 'block';
} finally {
generateBtn.disabled = false;
loading.style.display = 'none';
}
}
</script>
</body>
</html>